summaryrefslogtreecommitdiff
path: root/packages/astro/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/src')
-rw-r--r--packages/astro/src/@types/astro.ts160
-rw-r--r--packages/astro/src/assets/build/generate.ts4
-rw-r--r--packages/astro/src/assets/internal.ts2
-rw-r--r--packages/astro/src/cli/build/index.ts1
-rw-r--r--packages/astro/src/cli/flags.ts3
-rw-r--r--packages/astro/src/cli/index.ts11
-rw-r--r--packages/astro/src/cli/info/index.ts28
-rw-r--r--packages/astro/src/cli/install-package.ts2
-rw-r--r--packages/astro/src/cli/preferences/index.ts289
-rw-r--r--packages/astro/src/cli/telemetry/index.ts12
-rw-r--r--packages/astro/src/cli/throw-and-exit.ts2
-rw-r--r--packages/astro/src/content/server-listeners.ts14
-rw-r--r--packages/astro/src/content/types-generator.ts93
-rw-r--r--packages/astro/src/core/app/index.ts67
-rw-r--r--packages/astro/src/core/app/node.ts26
-rw-r--r--packages/astro/src/core/build/buildPipeline.ts8
-rw-r--r--packages/astro/src/core/build/generate.ts65
-rw-r--r--packages/astro/src/core/build/index.ts43
-rw-r--r--packages/astro/src/core/build/internal.ts8
-rw-r--r--packages/astro/src/core/build/plugins/plugin-css.ts4
-rw-r--r--packages/astro/src/core/build/plugins/plugin-manifest.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts4
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts13
-rw-r--r--packages/astro/src/core/build/static-build.ts9
-rw-r--r--packages/astro/src/core/build/types.ts2
-rw-r--r--packages/astro/src/core/build/util.ts2
-rw-r--r--packages/astro/src/core/compile/compile.ts3
-rw-r--r--packages/astro/src/core/config/config.ts1
-rw-r--r--packages/astro/src/core/config/schema.ts64
-rw-r--r--packages/astro/src/core/config/settings.ts3
-rw-r--r--packages/astro/src/core/config/tsconfig.ts16
-rw-r--r--packages/astro/src/core/create-vite.ts20
-rw-r--r--packages/astro/src/core/dev/dev.ts10
-rw-r--r--packages/astro/src/core/dev/restart.ts26
-rw-r--r--packages/astro/src/core/endpoint/index.ts142
-rw-r--r--packages/astro/src/core/errors/dev/vite.ts1
-rw-r--r--packages/astro/src/core/errors/errors-data.ts7
-rw-r--r--packages/astro/src/core/errors/utils.ts5
-rw-r--r--packages/astro/src/core/logger/console.ts40
-rw-r--r--packages/astro/src/core/logger/core.ts65
-rw-r--r--packages/astro/src/core/logger/node.ts107
-rw-r--r--packages/astro/src/core/messages.ts225
-rw-r--r--packages/astro/src/core/middleware/callMiddleware.ts42
-rw-r--r--packages/astro/src/core/middleware/index.ts4
-rw-r--r--packages/astro/src/core/middleware/namespace.ts1
-rw-r--r--packages/astro/src/core/middleware/sequence.ts6
-rw-r--r--packages/astro/src/core/pipeline.ts37
-rw-r--r--packages/astro/src/core/preview/static-preview-server.ts6
-rw-r--r--packages/astro/src/core/preview/vite-plugin-astro-preview.ts67
-rw-r--r--packages/astro/src/core/render/core.ts8
-rw-r--r--packages/astro/src/core/render/index.ts2
-rw-r--r--packages/astro/src/core/render/result.ts2
-rw-r--r--packages/astro/src/core/render/route-cache.ts7
-rw-r--r--packages/astro/src/core/request.ts6
-rw-r--r--packages/astro/src/core/routing/manifest/create.ts28
-rw-r--r--packages/astro/src/core/routing/validation.ts8
-rw-r--r--packages/astro/src/core/sync/index.ts4
-rw-r--r--packages/astro/src/i18n/middleware.ts4
-rw-r--r--packages/astro/src/i18n/vite-plugin-i18n.ts2
-rw-r--r--packages/astro/src/integrations/astroFeaturesValidation.ts32
-rw-r--r--packages/astro/src/integrations/index.ts36
-rw-r--r--packages/astro/src/preferences/README.md33
-rw-r--r--packages/astro/src/preferences/defaults.ts8
-rw-r--r--packages/astro/src/preferences/index.ts104
-rw-r--r--packages/astro/src/preferences/store.ts62
-rw-r--r--packages/astro/src/prefetch/vite-plugin-prefetch.ts6
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/entrypoint.ts78
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/overlay.ts475
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts411
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts96
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts56
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts13
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts43
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts59
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts76
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts71
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts88
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts47
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts1
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts51
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts46
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts8
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts8
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts60
-rw-r--r--packages/astro/src/runtime/server/endpoint.ts43
-rw-r--r--packages/astro/src/runtime/server/render/common.ts4
-rw-r--r--packages/astro/src/runtime/server/transition.ts4
-rw-r--r--packages/astro/src/transitions/router.ts4
-rw-r--r--packages/astro/src/transitions/vite-plugin-transitions.ts8
-rw-r--r--packages/astro/src/virtual-modules/README.md3
-rw-r--r--packages/astro/src/virtual-modules/i18n.ts1
-rw-r--r--packages/astro/src/virtual-modules/middleware.ts1
-rw-r--r--packages/astro/src/virtual-modules/prefetch.ts1
-rw-r--r--packages/astro/src/virtual-modules/transitions-events.ts1
-rw-r--r--packages/astro/src/virtual-modules/transitions-router.ts1
-rw-r--r--packages/astro/src/virtual-modules/transitions-types.ts1
-rw-r--r--packages/astro/src/virtual-modules/transitions.ts1
-rw-r--r--packages/astro/src/vite-plugin-astro-server/base.ts11
-rw-r--r--packages/astro/src/vite-plugin-astro-server/common.ts6
-rw-r--r--packages/astro/src/vite-plugin-astro-server/request.ts7
-rw-r--r--packages/astro/src/vite-plugin-astro-server/response.ts2
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts48
-rw-r--r--packages/astro/src/vite-plugin-astro/hmr.ts11
-rw-r--r--packages/astro/src/vite-plugin-astro/index.ts48
-rw-r--r--packages/astro/src/vite-plugin-inject-env-ts/index.ts6
-rw-r--r--packages/astro/src/vite-plugin-integrations-container/index.ts2
-rw-r--r--packages/astro/src/vite-plugin-load-fallback/index.ts6
-rw-r--r--packages/astro/src/vite-plugin-scanner/index.ts7
-rw-r--r--packages/astro/src/vite-plugin-scanner/scan.ts2
-rw-r--r--packages/astro/src/vite-plugin-scripts/index.ts3
-rw-r--r--packages/astro/src/vite-plugin-utils/index.ts5
111 files changed, 2452 insertions, 1596 deletions
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 499170f25..5cbf4faf8 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -19,17 +19,23 @@ import type { AstroConfigType } from '../core/config/index.js';
import type { AstroTimer } from '../core/config/timer.js';
import type { TSConfig } from '../core/config/tsconfig.js';
import type { AstroCookies } from '../core/cookies/index.js';
-import type { ResponseWithEncoding } from '../core/endpoint/index.js';
import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger/core.js';
import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
-import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js';
-import type { DevOverlayToggle } from '../runtime/client/dev-overlay/ui-library/toggle.js';
-import type { DevOverlayTooltip } from '../runtime/client/dev-overlay/ui-library/tooltip.js';
-import type { DevOverlayWindow } from '../runtime/client/dev-overlay/ui-library/window.js';
+import type {
+ DevOverlayBadge,
+ DevOverlayButton,
+ DevOverlayCard,
+ DevOverlayHighlight,
+ DevOverlayIcon,
+ DevOverlayToggle,
+ DevOverlayTooltip,
+ DevOverlayWindow,
+} from '../runtime/client/dev-overlay/ui-library/index.js';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js';
import type { DeepPartial, OmitIndexSignature, Simplify } from '../type-utils.js';
import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
+import type { AstroPreferences } from '../preferences/index.js';
export { type AstroIntegrationLogger };
@@ -143,7 +149,6 @@ export interface CLIFlags {
host?: string | boolean;
port?: number;
config?: string;
- drafts?: boolean;
open?: boolean;
}
@@ -886,33 +891,6 @@ export interface AstroUserConfig {
* ```
*/
inlineStylesheets?: 'always' | 'auto' | 'never';
-
- /**
- * @docs
- * @name build.split
- * @type {boolean}
- * @default `false`
- * @deprecated Deprecated since version 3.0.
- * @description
- * The build config option `build.split` has been replaced by the adapter configuration option [`functionPerRoute`](/en/reference/adapter-reference/#functionperroute).
- *
- * Please see your [SSR adapter's documentation](/en/guides/integrations-guide/#official-integrations) for using `functionPerRoute` to define how your SSR code is bundled.
- *
- */
- split?: boolean;
-
- /**
- * @docs
- * @name build.excludeMiddleware
- * @type {boolean}
- * @default `false`
- * @deprecated Deprecated since version 3.0.
- * @description
- * The build config option `build.excludeMiddleware` has been replaced by the adapter configuration option [`edgeMiddleware`](/en/reference/adapter-reference/#edgemiddleware).
- *
- * Please see your [SSR adapter's documentation](/en/guides/integrations-guide/#official-integrations) for using `edgeMiddleware` to define whether or not any SSR middleware code will be bundled when built.
- */
- excludeMiddleware?: boolean;
};
/**
@@ -1180,31 +1158,37 @@ export interface AstroUserConfig {
/**
* @docs
* @kind heading
- * @name Markdown Options
+ * @name Dev Overlay Options
*/
- markdown?: {
+ devOverlay?: {
/**
* @docs
- * @name markdown.drafts
+ * @name devOverlay.enabled
* @type {boolean}
- * @default `false`
- * @deprecated Deprecated since version 3.0. Use content collections instead.
+ * @default `true`
* @description
- * Control whether Markdown draft pages should be included in the build.
+ * Whether to enable the dev overlay. This overlay allows you to inspect your page islands, see helpful audits on performance and accessibility, and more.
*
- * A Markdown page is considered a draft if it includes `draft: true` in its frontmatter. Draft pages are always included & visible during development (`astro dev`) but by default they will not be included in your final build.
- *
- * ```js
- * {
- * markdown: {
- * // Example: Include all drafts in your final build
- * drafts: true,
- * }
- * }
- * ```
+ * This option is scoped to the entire project, to only disable the overlay for yourself, run `npm run astro preferences disable devOverlay`. To disable the overlay for all your Astro projects, run `npm run astro preferences disable devOverlay --global`.
+ */
+ enabled: boolean;
+ /**
+ * @docs
+ * @name devOverlay.defaultState
+ * @type {'minimized' | 'expanded'}
+ * @default `minimized`
+ * @description
+ * Whether the dev overlay should be expanded or minimized by default.
*/
- drafts?: boolean;
+ defaultState: 'minimized' | 'expanded';
+ };
+ /**
+ * @docs
+ * @kind heading
+ * @name Markdown Options
+ */
+ markdown?: {
/**
* @docs
* @name markdown.shikiConfig
@@ -1317,7 +1301,7 @@ export interface AstroUserConfig {
* {
* markdown: {
* // Example: Translate the footnotes text to another language, here are the default English values
- * remarkRehype: { footnoteLabel: "Footnotes", footnoteBackLabel: "Back to content"},
+ * remarkRehype: { footnoteLabel: "Footnotes", footnoteBackLabel: "Back to reference 1"},
* },
* };
* ```
@@ -1426,25 +1410,6 @@ export interface AstroUserConfig {
/**
* @docs
- * @name experimental.devOverlay
- * @type {boolean}
- * @default `false`
- * @version 3.4.0
- * @description
- * Enable a dev overlay in development mode. This overlay allows you to inspect your page islands, see helpful audits on performance and accessibility, and more.
- *
- * ```js
- * {
- * experimental: {
- * devOverlay: true,
- * },
- * }
- * ```
- */
- devOverlay?: boolean;
-
- /**
- * @docs
* @name experimental.i18n
* @type {object}
* @version 3.5.0
@@ -1598,7 +1563,7 @@ export type InjectedScriptStage = 'before-hydration' | 'head-inline' | 'page' |
export interface InjectedRoute {
pattern: string;
- entryPoint: string;
+ entrypoint: string;
prerender?: boolean;
}
@@ -1734,6 +1699,7 @@ export interface AstroAdapterFeatures {
export interface AstroSettings {
config: AstroConfig;
adapter: AstroAdapter | undefined;
+ preferences: AstroPreferences;
injectedRoutes: InjectedRoute[];
resolvedInjectedRoutes: ResolvedInjectedRoute[];
pageExtensions: string[];
@@ -1769,10 +1735,6 @@ export interface ComponentInstance {
css?: string[];
partial?: boolean;
prerender?: boolean;
- /**
- * Only used for logging if deprecated drafts feature is used
- */
- frontmatter?: Record<string, any>;
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
}
@@ -2076,11 +2038,9 @@ export interface AstroAdapter {
*
* If the adapter is not able to handle certain configurations, Astro will throw an error.
*/
- supportedAstroFeatures?: AstroFeatureMap;
+ supportedAstroFeatures: AstroFeatureMap;
}
-type Body = string;
-
export type ValidRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;
// Shared types between `Astro` global and API context object
@@ -2165,7 +2125,7 @@ export interface APIContext<
* ];
* }
*
- * export async function get({ params }) {
+ * export async function GET({ params }) {
* return {
* body: `Hello user ${params.id}!`,
* }
@@ -2188,7 +2148,7 @@ export interface APIContext<
* ];
* }
*
- * export function get({ props }) {
+ * export function GET({ props }) {
* return {
* body: `Hello ${props.name}!`,
* }
@@ -2204,7 +2164,7 @@ export interface APIContext<
* Example usage:
* ```ts
* // src/pages/secret.ts
- * export function get({ redirect }) {
+ * export function GET({ redirect }) {
* return redirect('/login');
* }
* ```
@@ -2237,7 +2197,6 @@ export interface APIContext<
* ```
*/
locals: App.Locals;
- ResponseWithEncoding: typeof ResponseWithEncoding;
/**
* Available only when `experimental.i18n` enabled and in SSR.
@@ -2273,26 +2232,18 @@ export interface APIContext<
currentLocale: string | undefined;
}
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Routing = {
prefixDefaultLocale: boolean;
strategy: 'pathname';
};
-export type EndpointOutput =
- | {
- body: Body;
- encoding?: BufferEncoding;
- }
- | {
- body: Uint8Array;
- encoding: 'binary';
- };
export type APIRoute<Props extends Record<string, any> = Record<string, any>> = (
context: APIContext<Props>
-) => EndpointOutput | Response | Promise<EndpointOutput | Response>;
+) => Response | Promise<Response>;
export interface EndpointHandler {
- [method: string]: APIRoute | ((params: Params, request: Request) => EndpointOutput | Response);
+ [method: string]: APIRoute | ((params: Params, request: Request) => Response);
}
export type Props = Record<string, unknown>;
@@ -2397,20 +2348,16 @@ export interface AstroIntegration {
};
}
-export type MiddlewareNext<R> = () => Promise<R>;
-export type MiddlewareHandler<R> = (
+export type MiddlewareNext = () => Promise<Response>;
+export type MiddlewareHandler = (
context: APIContext,
- next: MiddlewareNext<R>
-) => Promise<R> | R | Promise<void> | void;
-
-export type MiddlewareResponseHandler = MiddlewareHandler<Response>;
-export type MiddlewareEndpointHandler = MiddlewareHandler<Response | EndpointOutput>;
-export type MiddlewareNextResponse = MiddlewareNext<Response>;
+ next: MiddlewareNext
+) => Promise<Response> | Response | Promise<void> | void;
// NOTE: when updating this file with other functions,
// remember to update `plugin-page.ts` too, to add that function as a no-op function.
-export type AstroMiddlewareInstance<R> = {
- onRequest?: MiddlewareHandler<R>;
+export type AstroMiddlewareInstance = {
+ onRequest?: MiddlewareHandler;
};
export type AstroIntegrationMiddleware = {
@@ -2607,7 +2554,10 @@ export interface DevOverlayPlugin {
export type DevOverlayMetadata = Window &
typeof globalThis & {
__astro_dev_overlay__: {
+ defaultState: AstroConfig['devOverlay']['defaultState'];
root: string;
+ version: string;
+ debugInfo: string;
};
};
@@ -2619,5 +2569,9 @@ declare global {
'astro-dev-overlay-tooltip': DevOverlayTooltip;
'astro-dev-overlay-highlight': DevOverlayHighlight;
'astro-dev-overlay-toggle': DevOverlayToggle;
+ 'astro-dev-overlay-badge': DevOverlayBadge;
+ 'astro-dev-overlay-button': DevOverlayButton;
+ 'astro-dev-overlay-icon': DevOverlayIcon;
+ 'astro-dev-overlay-card': DevOverlayCard;
}
}
diff --git a/packages/astro/src/assets/build/generate.ts b/packages/astro/src/assets/build/generate.ts
index be637c26d..c4109ea1e 100644
--- a/packages/astro/src/assets/build/generate.ts
+++ b/packages/astro/src/assets/build/generate.ts
@@ -58,7 +58,7 @@ export async function prepareAssetsGenerationEnv(
await fs.promises.mkdir(assetsCacheDir, { recursive: true });
} catch (err) {
logger.warn(
- 'astro:assets',
+ null,
`An error was encountered while creating the cache directory. Proceeding without caching. Error: ${err}`
);
useCache = false;
@@ -231,7 +231,7 @@ export async function generateImagesForPath(
}
} catch (e) {
env.logger.warn(
- 'astro:assets',
+ null,
`An error was encountered while creating the cache directory. Proceeding without caching. Error: ${e}`
);
} finally {
diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts
index 46319ed3b..9e615d5c9 100644
--- a/packages/astro/src/assets/internal.ts
+++ b/packages/astro/src/assets/internal.ts
@@ -19,7 +19,7 @@ export function injectImageEndpoint(settings: AstroSettings, mode: 'dev' | 'buil
settings.injectedRoutes.push({
pattern: '/_image',
- entryPoint: endpointEntrypoint,
+ entrypoint: endpointEntrypoint,
prerender: false,
});
diff --git a/packages/astro/src/cli/build/index.ts b/packages/astro/src/cli/build/index.ts
index 1a7d5aa52..15ff58431 100644
--- a/packages/astro/src/cli/build/index.ts
+++ b/packages/astro/src/cli/build/index.ts
@@ -14,7 +14,6 @@ export async function build({ flags }: BuildOptions) {
usage: '[...flags]',
tables: {
Flags: [
- ['--drafts', `Include Markdown draft pages in the build.`],
['--outDir <directory>', `Specify the output directory for the build.`],
['--help (-h)', 'See all available flags.'],
],
diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts
index c97f1801a..c384d9867 100644
--- a/packages/astro/src/cli/flags.ts
+++ b/packages/astro/src/cli/flags.ts
@@ -15,9 +15,6 @@ export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig {
site: typeof flags.site === 'string' ? flags.site : undefined,
base: typeof flags.base === 'string' ? flags.base : undefined,
outDir: typeof flags.outDir === 'string' ? flags.outDir : undefined,
- markdown: {
- drafts: typeof flags.drafts === 'boolean' ? flags.drafts : undefined,
- },
server: {
port: typeof flags.port === 'number' ? flags.port : undefined,
host:
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index 0421258a5..7ca4d21a9 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -14,6 +14,7 @@ type CLICommand =
| 'sync'
| 'check'
| 'info'
+ | 'preferences'
| 'telemetry';
/** Display --help flag */
@@ -33,6 +34,7 @@ async function printAstroHelp() {
['info', 'List info about your current Astro setup.'],
['preview', 'Preview your build locally.'],
['sync', 'Generate content collection types.'],
+ ['preferences', 'Configure user preferences.'],
['telemetry', 'Configure telemetry settings.'],
],
'Global Flags': [
@@ -64,6 +66,7 @@ function resolveCommand(flags: yargs.Arguments): CLICommand {
'add',
'sync',
'telemetry',
+ 'preferences',
'dev',
'build',
'preview',
@@ -114,6 +117,12 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
const exitCode = await sync({ flags });
return process.exit(exitCode);
}
+ case 'preferences': {
+ const { preferences } = await import('./preferences/index.js');
+ const [subcommand, key, value] = flags._.slice(3).map((v) => v.toString());
+ const exitCode = await preferences(subcommand, key, value, { flags });
+ return process.exit(exitCode);
+ }
}
// In verbose/debug mode, we log the debug logs asap before any potential errors could appear
@@ -177,7 +186,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) {
/** The primary CLI action */
export async function cli(args: string[]) {
- const flags = yargs(args);
+ const flags = yargs(args, { boolean: ['global'], alias: { g: 'global' } });
const cmd = resolveCommand(flags);
try {
await runCommand(cmd, flags);
diff --git a/packages/astro/src/cli/info/index.ts b/packages/astro/src/cli/info/index.ts
index 46e7d3c69..c6586b28d 100644
--- a/packages/astro/src/cli/info/index.ts
+++ b/packages/astro/src/cli/info/index.ts
@@ -4,6 +4,7 @@ import { execSync } from 'node:child_process';
import { arch, platform } from 'node:os';
import prompts from 'prompts';
import type yargs from 'yargs-parser';
+import type { AstroConfig, AstroUserConfig } from '../../@types/astro.js';
import { resolveConfig } from '../../core/config/index.js';
import { ASTRO_VERSION } from '../../core/constants.js';
import { flagsToAstroInlineConfig } from '../flags.js';
@@ -12,7 +13,13 @@ interface InfoOptions {
flags: yargs.Arguments;
}
-export async function printInfo({ flags }: InfoOptions) {
+export async function getInfoOutput({
+ userConfig,
+ print,
+}: {
+ userConfig: AstroUserConfig | AstroConfig;
+ print: boolean;
+}): Promise<string> {
const rows: Array<[string, string | string[]]> = [
['Astro', `v${ASTRO_VERSION}`],
['Node', process.version],
@@ -20,9 +27,7 @@ export async function printInfo({ flags }: InfoOptions) {
['Package Manager', getPackageManager()],
];
- const inlineConfig = flagsToAstroInlineConfig(flags);
try {
- const { userConfig } = await resolveConfig(inlineConfig, 'info');
rows.push(['Output', userConfig.output ?? 'static']);
rows.push(['Adapter', userConfig.adapter?.name ?? 'none']);
const integrations = (userConfig?.integrations ?? [])
@@ -35,10 +40,17 @@ export async function printInfo({ flags }: InfoOptions) {
let output = '';
for (const [label, value] of rows) {
- output += printRow(label, value);
+ output += printRow(label, value, print);
}
- await copyToClipboard(output.trim());
+ return output.trim();
+}
+
+export async function printInfo({ flags }: InfoOptions) {
+ const { userConfig } = await resolveConfig(flagsToAstroInlineConfig(flags), 'info');
+ const output = await getInfoOutput({ userConfig, print: true });
+
+ await copyToClipboard(output);
}
async function copyToClipboard(text: string) {
@@ -105,7 +117,7 @@ function getPackageManager() {
}
const MAX_PADDING = 25;
-function printRow(label: string, value: string | string[]) {
+function printRow(label: string, value: string | string[], print: boolean) {
const padding = MAX_PADDING - label.length;
const [first, ...rest] = Array.isArray(value) ? value : [value];
let plaintext = `${label}${' '.repeat(padding)}${first}`;
@@ -117,6 +129,8 @@ function printRow(label: string, value: string | string[]) {
}
}
plaintext += '\n';
- console.log(richtext);
+ if (print) {
+ console.log(richtext);
+ }
return plaintext;
}
diff --git a/packages/astro/src/cli/install-package.ts b/packages/astro/src/cli/install-package.ts
index 689f81e3e..667037a0c 100644
--- a/packages/astro/src/cli/install-package.ts
+++ b/packages/astro/src/cli/install-package.ts
@@ -26,7 +26,7 @@ export async function getPackage<T>(
packageImport = await import(packageName);
} catch (e) {
logger.info(
- '',
+ null,
`To continue, Astro requires the following dependency to be installed: ${bold(packageName)}.`
);
const result = await installPackage([packageName, ...otherDeps], options, logger);
diff --git a/packages/astro/src/cli/preferences/index.ts b/packages/astro/src/cli/preferences/index.ts
new file mode 100644
index 000000000..48d6a298d
--- /dev/null
+++ b/packages/astro/src/cli/preferences/index.ts
@@ -0,0 +1,289 @@
+/* eslint-disable no-console */
+import type yargs from 'yargs-parser';
+import type { AstroSettings } from '../../@types/astro.js';
+
+import { bold } from 'kleur/colors';
+import { fileURLToPath } from 'node:url';
+
+import * as msg from '../../core/messages.js';
+import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js';
+import { resolveConfig } from '../../core/config/config.js';
+import { createSettings } from '../../core/config/settings.js';
+import { coerce, isValidKey, type PreferenceKey } from '../../preferences/index.js';
+import { DEFAULT_PREFERENCES } from '../../preferences/defaults.js';
+import dlv from 'dlv';
+// @ts-expect-error flattie types are mispackaged
+import { flattie } from 'flattie';
+import { formatWithOptions } from 'node:util';
+import { collectErrorMetadata } from '../../core/errors/dev/utils.js';
+
+interface PreferencesOptions {
+ flags: yargs.Arguments;
+}
+
+const PREFERENCES_SUBCOMMANDS = [
+ 'get',
+ 'set',
+ 'enable',
+ 'disable',
+ 'delete',
+ 'reset',
+ 'list',
+] as const;
+export type Subcommand = (typeof PREFERENCES_SUBCOMMANDS)[number];
+
+function isValidSubcommand(subcommand: string): subcommand is Subcommand {
+ return PREFERENCES_SUBCOMMANDS.includes(subcommand as Subcommand);
+}
+
+export async function preferences(
+ subcommand: string,
+ key: string,
+ value: string | undefined,
+ { flags }: PreferencesOptions
+): Promise<number> {
+ if (!isValidSubcommand(subcommand) || flags?.help || flags?.h) {
+ msg.printHelp({
+ commandName: 'astro preferences',
+ usage: '[command]',
+ tables: {
+ Commands: [
+ ['list', 'Pretty print all current preferences'],
+ ['list --json', 'Log all current preferences as a JSON object'],
+ ['get [key]', 'Log current preference value'],
+ ['set [key] [value]', 'Update preference value'],
+ ['reset [key]', 'Reset preference value to default'],
+ ['enable [key]', 'Set a boolean preference to true'],
+ ['disable [key]', 'Set a boolean preference to false'],
+ ],
+ Flags: [
+ [
+ '--global',
+ 'Scope command to global preferences (all Astro projects) rather than the current project',
+ ],
+ ],
+ },
+ });
+ return 0;
+ }
+
+ const inlineConfig = flagsToAstroInlineConfig(flags);
+ const logger = createLoggerFromFlags(flags);
+ const { astroConfig } = await resolveConfig(inlineConfig ?? {}, 'dev');
+ const settings = await createSettings(astroConfig, fileURLToPath(astroConfig.root));
+ const opts: SubcommandOptions = {
+ location: flags.global ? 'global' : undefined,
+ json: flags.json,
+ };
+
+ if (subcommand === 'list') {
+ return listPreferences(settings, opts);
+ }
+
+ if (subcommand === 'enable' || subcommand === 'disable') {
+ key = `${key}.enabled` as PreferenceKey;
+ }
+
+ if (!isValidKey(key)) {
+ logger.error('preferences', `Unknown preference "${key}"\n`);
+ return 1;
+ }
+
+ if (subcommand === 'set' && value === undefined) {
+ const type = typeof dlv(DEFAULT_PREFERENCES, key);
+ console.error(
+ msg.formatErrorMessage(
+ collectErrorMetadata(new Error(`Please provide a ${type} value for "${key}"`)),
+ true
+ )
+ );
+ return 1;
+ }
+
+ switch (subcommand) {
+ case 'get':
+ return getPreference(settings, key, opts);
+ case 'set':
+ return setPreference(settings, key, value, opts);
+ case 'reset':
+ case 'delete':
+ return resetPreference(settings, key, opts);
+ case 'enable':
+ return enablePreference(settings, key, opts);
+ case 'disable':
+ return disablePreference(settings, key, opts);
+ }
+}
+
+interface SubcommandOptions {
+ location?: 'global' | 'project';
+ json?: boolean;
+}
+
+// Default `location` to "project" to avoid reading default preferencesa
+async function getPreference(
+ settings: AstroSettings,
+ key: PreferenceKey,
+ { location = 'project' }: SubcommandOptions
+) {
+ try {
+ let value = await settings.preferences.get(key, { location });
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
+ if (Object.keys(value).length === 0) {
+ value = dlv(DEFAULT_PREFERENCES, key);
+ console.log(msg.preferenceDefaultIntro(key));
+ }
+ prettyPrint({ [key]: value });
+ return 0;
+ }
+ if (value === undefined) {
+ const defaultValue = await settings.preferences.get(key);
+ console.log(msg.preferenceDefault(key, defaultValue));
+ return 0;
+ }
+ console.log(msg.preferenceGet(key, value));
+ return 0;
+ } catch {}
+ return 1;
+}
+
+async function setPreference(
+ settings: AstroSettings,
+ key: PreferenceKey,
+ value: unknown,
+ { location }: SubcommandOptions
+) {
+ try {
+ const defaultType = typeof dlv(DEFAULT_PREFERENCES, key);
+ if (typeof coerce(key, value) !== defaultType) {
+ throw new Error(`${key} expects a "${defaultType}" value!`);
+ }
+
+ await settings.preferences.set(key, coerce(key, value), { location });
+ console.log(msg.preferenceSet(key, value));
+ return 0;
+ } catch (e) {
+ if (e instanceof Error) {
+ console.error(msg.formatErrorMessage(collectErrorMetadata(e), true));
+ return 1;
+ }
+ throw e;
+ }
+}
+
+async function enablePreference(
+ settings: AstroSettings,
+ key: PreferenceKey,
+ { location }: SubcommandOptions
+) {
+ try {
+ await settings.preferences.set(key, true, { location });
+ console.log(msg.preferenceEnabled(key.replace('.enabled', '')));
+ return 0;
+ } catch {}
+ return 1;
+}
+
+async function disablePreference(
+ settings: AstroSettings,
+ key: PreferenceKey,
+ { location }: SubcommandOptions
+) {
+ try {
+ await settings.preferences.set(key, false, { location });
+ console.log(msg.preferenceDisabled(key.replace('.enabled', '')));
+ return 0;
+ } catch {}
+ return 1;
+}
+
+async function resetPreference(
+ settings: AstroSettings,
+ key: PreferenceKey,
+ { location }: SubcommandOptions
+) {
+ try {
+ await settings.preferences.set(key, undefined as any, { location });
+ console.log(msg.preferenceReset(key));
+ return 0;
+ } catch {}
+ return 1;
+}
+
+async function listPreferences(settings: AstroSettings, { location, json }: SubcommandOptions) {
+ const store = await settings.preferences.getAll({ location });
+ if (json) {
+ console.log(JSON.stringify(store, null, 2));
+ return 0;
+ }
+ prettyPrint(store);
+ return 0;
+}
+
+function prettyPrint(value: Record<string, string | number | boolean>) {
+ const flattened = flattie(value);
+ const table = formatTable(flattened, ['Preference', 'Value']);
+ console.log(table);
+}
+
+const chars = {
+ h: '─',
+ hThick: '━',
+ hThickCross: '┿',
+ v: '│',
+ vRight: '├',
+ vRightThick: '┝',
+ vLeft: '┤',
+ vLeftThick: '┥',
+ hTop: '┴',
+ hBottom: '┬',
+ topLeft: '╭',
+ topRight: '╮',
+ bottomLeft: '╰',
+ bottomRight: '╯',
+};
+
+function formatTable(
+ object: Record<string, string | number | boolean>,
+ columnLabels: [string, string]
+) {
+ const [colA, colB] = columnLabels;
+ const colALength = [colA, ...Object.keys(object)].reduce(longest, 0) + 3;
+ const colBLength = [colB, ...Object.values(object)].reduce(longest, 0) + 3;
+ function formatRow(
+ i: number,
+ a: string,
+ b: string | number | boolean,
+ style: (value: string | number | boolean) => string = (v) => v.toString()
+ ): string {
+ return `${chars.v} ${style(a)} ${space(colALength - a.length - 2)} ${chars.v} ${style(
+ b
+ )} ${space(colBLength - b.toString().length - 3)} ${chars.v}`;
+ }
+ const top = `${chars.topLeft}${chars.h.repeat(colALength + 1)}${chars.hBottom}${chars.h.repeat(
+ colBLength
+ )}${chars.topRight}`;
+ const bottom = `${chars.bottomLeft}${chars.h.repeat(colALength + 1)}${chars.hTop}${chars.h.repeat(
+ colBLength
+ )}${chars.bottomRight}`;
+ const divider = `${chars.vRightThick}${chars.hThick.repeat(colALength + 1)}${
+ chars.hThickCross
+ }${chars.hThick.repeat(colBLength)}${chars.vLeftThick}`;
+ const rows: string[] = [top, formatRow(-1, colA, colB, bold), divider];
+ let i = 0;
+ for (const [key, value] of Object.entries(object)) {
+ rows.push(formatRow(i, key, value, (v) => formatWithOptions({ colors: true }, v)));
+ i++;
+ }
+ rows.push(bottom);
+ return rows.join('\n');
+}
+
+function space(len: number) {
+ return ' '.repeat(len);
+}
+
+const longest = (a: number, b: string | number | boolean) => {
+ const { length: len } = b.toString();
+ return a > len ? a : len;
+};
diff --git a/packages/astro/src/cli/telemetry/index.ts b/packages/astro/src/cli/telemetry/index.ts
index fd664fcc9..277b1cab6 100644
--- a/packages/astro/src/cli/telemetry/index.ts
+++ b/packages/astro/src/cli/telemetry/index.ts
@@ -1,23 +1,23 @@
/* eslint-disable no-console */
-import whichPm from 'which-pm';
import type yargs from 'yargs-parser';
import * as msg from '../../core/messages.js';
import { telemetry } from '../../events/index.js';
+import { createLoggerFromFlags } from '../flags.js';
interface TelemetryOptions {
flags: yargs.Arguments;
}
export async function notify() {
- const packageManager = (await whichPm(process.cwd()))?.name ?? 'npm';
await telemetry.notify(() => {
- console.log(msg.telemetryNotice(packageManager) + '\n');
+ console.log(msg.telemetryNotice() + '\n');
return true;
});
}
export async function update(subcommand: string, { flags }: TelemetryOptions) {
const isValid = ['enable', 'disable', 'reset'].includes(subcommand);
+ const logger = createLoggerFromFlags(flags);
if (flags.help || flags.h || !isValid) {
msg.printHelp({
@@ -37,17 +37,17 @@ export async function update(subcommand: string, { flags }: TelemetryOptions) {
switch (subcommand) {
case 'enable': {
telemetry.setEnabled(true);
- console.log(msg.telemetryEnabled());
+ logger.info('SKIP_FORMAT', msg.telemetryEnabled());
return;
}
case 'disable': {
telemetry.setEnabled(false);
- console.log(msg.telemetryDisabled());
+ logger.info('SKIP_FORMAT', msg.telemetryDisabled());
return;
}
case 'reset': {
telemetry.clear();
- console.log(msg.telemetryReset());
+ logger.info('SKIP_FORMAT', msg.telemetryReset());
return;
}
}
diff --git a/packages/astro/src/cli/throw-and-exit.ts b/packages/astro/src/cli/throw-and-exit.ts
index 3196092d2..1a8916ede 100644
--- a/packages/astro/src/cli/throw-and-exit.ts
+++ b/packages/astro/src/cli/throw-and-exit.ts
@@ -20,7 +20,7 @@ export async function throwAndExit(cmd: string, err: unknown) {
const errorWithMetadata = collectErrorMetadata(createSafeError(err));
telemetryPromise = telemetry.record(eventError({ cmd, err: errorWithMetadata, isFatal: true }));
- errorMessage = formatErrorMessage(errorWithMetadata);
+ errorMessage = formatErrorMessage(errorWithMetadata, true);
// Timeout the error reporter (very short) because the user is waiting.
// NOTE(fks): It is better that we miss some events vs. holding too long.
diff --git a/packages/astro/src/content/server-listeners.ts b/packages/astro/src/content/server-listeners.ts
index 699d5f271..3ff3148cb 100644
--- a/packages/astro/src/content/server-listeners.ts
+++ b/packages/astro/src/content/server-listeners.ts
@@ -1,4 +1,4 @@
-import { bold, cyan } from 'kleur/colors';
+import { bold, cyan, underline } from 'kleur/colors';
import type fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -26,7 +26,7 @@ export async function attachContentServerListeners({
const contentPaths = getContentPaths(settings.config, fs);
if (fs.existsSync(contentPaths.contentDir)) {
- logger.info(
+ logger.debug(
'content',
`Watching ${cyan(
contentPaths.contentDir.href.replace(settings.config.root.href, '')
@@ -39,7 +39,7 @@ export async function attachContentServerListeners({
viteServer.watcher.on('addDir', contentDirListener);
async function contentDirListener(dir: string) {
if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) {
- logger.info('content', `Content dir found. Watching for changes`);
+ logger.debug('content', `Content directory found. Watching for changes`);
await attachListeners();
viteServer.watcher.removeListener('addDir', contentDirListener);
}
@@ -55,7 +55,7 @@ export async function attachContentServerListeners({
contentConfigObserver: globalContentConfigObserver,
});
await contentGenerator.init();
- logger.info('content', 'Types generated');
+ logger.debug('content', 'Types generated');
viteServer.watcher.on('add', (entry) => {
contentGenerator.queueEvent({ name: 'add', entry });
@@ -90,9 +90,9 @@ function warnAllowJsIsFalse({
'true'
)} in your ${bold(tsConfigFileName)} file to have autocompletion in your ${bold(
contentConfigFileName
- )} file.
-See ${bold('https://www.typescriptlang.org/tsconfig#allowJs')} for more information.
- `
+ )} file. See ${underline(
+ cyan('https://www.typescriptlang.org/tsconfig#allowJs')
+ )} for more information.`
);
}
diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts
index b50c597fd..dc9c1ecc7 100644
--- a/packages/astro/src/content/types-generator.ts
+++ b/packages/astro/src/content/types-generator.ts
@@ -1,5 +1,5 @@
import glob from 'fast-glob';
-import { cyan } from 'kleur/colors';
+import { bold, cyan } from 'kleur/colors';
import type fsMod from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -56,13 +56,6 @@ type CreateContentGeneratorParams = {
fs: typeof fsMod;
};
-type EventOpts = { logLevel: 'info' | 'warn' };
-
-type EventWithOptions = {
- type: ContentEvent;
- opts: EventOpts | undefined;
-};
-
class UnsupportedFileTypeError extends Error {}
export async function createContentTypesGenerator({
@@ -78,7 +71,7 @@ export async function createContentTypesGenerator({
const contentEntryExts = [...contentEntryConfigByExt.keys()];
const dataEntryExts = getDataEntryExts(settings);
- let events: EventWithOptions[] = [];
+ let events: ContentEvent[] = [];
let debounceTimeout: NodeJS.Timeout | undefined;
const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8');
@@ -90,10 +83,7 @@ export async function createContentTypesGenerator({
return { typesGenerated: false, reason: 'no-content-dir' };
}
- events.push({
- type: { name: 'add', entry: contentPaths.config.url },
- opts: { logLevel: 'warn' },
- });
+ events.push({ name: 'add', entry: contentPaths.config.url });
const globResult = await glob('**', {
cwd: fileURLToPath(contentPaths.contentDir),
@@ -110,12 +100,9 @@ export async function createContentTypesGenerator({
const entryURL = pathToFileURL(fullPath);
if (entryURL.href.startsWith(contentPaths.config.url.href)) continue;
if (entry.dirent.isFile()) {
- events.push({
- type: { name: 'add', entry: entryURL },
- opts: { logLevel: 'warn' },
- });
+ events.push({ name: 'add', entry: entryURL });
} else if (entry.dirent.isDirectory()) {
- events.push({ type: { name: 'addDir', entry: entryURL }, opts: { logLevel: 'warn' } });
+ events.push({ name: 'addDir', entry: entryURL });
}
}
await runEvents();
@@ -123,11 +110,8 @@ export async function createContentTypesGenerator({
}
async function handleEvent(
- event: ContentEvent,
- opts?: EventOpts
+ event: ContentEvent
): Promise<{ shouldGenerateTypes: boolean; error?: Error }> {
- const logLevel = opts?.logLevel ?? 'info';
-
if (event.name === 'addDir' || event.name === 'unlinkDir') {
const collection = normalizePath(
path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
@@ -140,9 +124,7 @@ export async function createContentTypesGenerator({
switch (event.name) {
case 'addDir':
collectionEntryMap[JSON.stringify(collection)] = { type: 'unknown', entries: {} };
- if (logLevel === 'info') {
- logger.info('content', `${cyan(collection)} collection added`);
- }
+ logger.debug('content', `${cyan(collection)} collection added`);
break;
case 'unlinkDir':
if (collectionKey in collectionEntryMap) {
@@ -186,16 +168,14 @@ export async function createContentTypesGenerator({
const collection = getEntryCollectionName({ entry, contentDir });
if (collection === undefined) {
- if (['info', 'warn'].includes(logLevel)) {
- logger.warn(
- 'content',
- `${cyan(
- normalizePath(
- path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
- )
- )} must be nested in a collection directory. Skipping.`
- );
- }
+ logger.warn(
+ 'content',
+ `${bold(
+ normalizePath(
+ path.relative(fileURLToPath(contentPaths.contentDir), fileURLToPath(event.entry))
+ )
+ )} must live in a ${bold('content/...')} collection subdirectory.`
+ );
return { shouldGenerateTypes: false };
}
@@ -308,22 +288,19 @@ export async function createContentTypesGenerator({
}
}
- function queueEvent(rawEvent: RawContentEvent, opts?: EventOpts) {
+ function queueEvent(rawEvent: RawContentEvent) {
const event = {
- type: {
- entry: pathToFileURL(rawEvent.entry),
- name: rawEvent.name,
- },
- opts,
+ entry: pathToFileURL(rawEvent.entry),
+ name: rawEvent.name,
};
- if (!event.type.entry.pathname.startsWith(contentPaths.contentDir.pathname)) return;
+ if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) return;
events.push(event);
debounceTimeout && clearTimeout(debounceTimeout);
const runEventsSafe = async () => {
try {
- await runEvents(opts);
+ await runEvents();
} catch {
// Prevent frontmatter errors from crashing the server. The errors
// are still reported on page reflects as desired.
@@ -333,30 +310,25 @@ export async function createContentTypesGenerator({
debounceTimeout = setTimeout(runEventsSafe, 50 /* debounce to batch chokidar events */);
}
- async function runEvents(opts?: EventOpts) {
- const logLevel = opts?.logLevel ?? 'info';
+ async function runEvents() {
const eventResponses = [];
for (const event of events) {
- const response = await handleEvent(event.type, event.opts);
+ const response = await handleEvent(event);
eventResponses.push(response);
}
events = [];
- let unsupportedFiles = [];
for (const response of eventResponses) {
if (response.error instanceof UnsupportedFileTypeError) {
- unsupportedFiles.push(response.error.message);
+ logger.warn(
+ 'content',
+ `Unsupported file type ${bold(
+ response.error.message
+ )} found. Prefix filename with an underscore (\`_\`) to ignore.`
+ );
}
}
- if (unsupportedFiles.length > 0 && ['info', 'warn'].includes(logLevel)) {
- logger.warn(
- 'content',
- `Unsupported file types found. Prefix with an underscore (\`_\`) to ignore:\n- ${unsupportedFiles.join(
- '\n'
- )}`
- );
- }
const observable = contentConfigObserver.get();
if (eventResponses.some((r) => r.shouldGenerateTypes)) {
await writeContentFiles({
@@ -369,7 +341,7 @@ export async function createContentTypesGenerator({
viteServer,
});
invalidateVirtualMod(viteServer);
- if (observable.status === 'loaded' && ['info', 'warn'].includes(logLevel)) {
+ if (observable.status === 'loaded') {
warnNonexistentCollections({
logger,
contentConfig: observable.config,
@@ -475,6 +447,7 @@ async function writeContentFiles({
let configPathRelativeToCacheDir = normalizePath(
path.relative(contentPaths.cacheDir.pathname, contentPaths.config.url.pathname)
);
+
if (!isRelativePath(configPathRelativeToCacheDir))
configPathRelativeToCacheDir = './' + configPathRelativeToCacheDir;
@@ -514,9 +487,9 @@ function warnNonexistentCollections({
if (!collectionEntryMap[JSON.stringify(configuredCollection)]) {
logger.warn(
'content',
- `The ${JSON.stringify(
- configuredCollection
- )} collection does not have an associated folder in your \`content\` directory. Make sure the folder exists, or check your content config for typos.`
+ `The ${bold(configuredCollection)} collection is defined but no ${bold(
+ 'content/' + configuredCollection
+ )} folder exists in the content directory. Create a new folder for the collection, or check your content configuration file for typos.`
);
}
}
diff --git a/packages/astro/src/core/app/index.ts b/packages/astro/src/core/app/index.ts
index 3210a2d4f..6abd2f734 100644
--- a/packages/astro/src/core/app/index.ts
+++ b/packages/astro/src/core/app/index.ts
@@ -1,7 +1,6 @@
import type {
EndpointHandler,
ManifestData,
- MiddlewareEndpointHandler,
RouteData,
SSRElement,
SSRManifest,
@@ -36,9 +35,11 @@ const responseSentSymbol = Symbol.for('astro.responseSent');
const STATUS_CODES = new Set([404, 500]);
-export interface MatchOptions {
- matchNotFound?: boolean | undefined;
+export interface RenderOptions {
+ routeData?: RouteData;
+ locals?: object;
}
+
export interface RenderErrorOptions {
routeData?: RouteData;
response?: Response;
@@ -63,6 +64,7 @@ export class App {
#baseWithoutTrailingSlash: string;
#pipeline: SSRRoutePipeline;
#adapterLogger: AstroIntegrationLogger;
+ #renderOptionsDeprecationWarningShown = false;
constructor(manifest: SSRManifest, streaming = true) {
this.#manifest = manifest;
@@ -134,7 +136,7 @@ export class App {
return pathname;
}
- match(request: Request, _opts: MatchOptions = {}): RouteData | undefined {
+ match(request: Request): RouteData | undefined {
const url = new URL(request.url);
// ignore requests matching public assets
if (this.#manifest.assets.has(url.pathname)) return undefined;
@@ -145,7 +147,38 @@ export class App {
return routeData;
}
- async render(request: Request, routeData?: RouteData, locals?: object): Promise<Response> {
+ async render(request: Request, options?: RenderOptions): Promise<Response>;
+ /**
+ * @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
+ * See https://github.com/withastro/astro/pull/9199 for more information.
+ */
+ async render(request: Request, routeData?: RouteData, locals?: object): Promise<Response>;
+ async render(
+ request: Request,
+ routeDataOrOptions?: RouteData | RenderOptions,
+ maybeLocals?: object
+ ): Promise<Response> {
+ let routeData: RouteData | undefined;
+ let locals: object | undefined;
+
+ if (
+ routeDataOrOptions &&
+ ('routeData' in routeDataOrOptions || 'locals' in routeDataOrOptions)
+ ) {
+ if ('routeData' in routeDataOrOptions) {
+ routeData = routeDataOrOptions.routeData;
+ }
+ if ('locals' in routeDataOrOptions) {
+ locals = routeDataOrOptions.locals;
+ }
+ } else {
+ routeData = routeDataOrOptions as RouteData | undefined;
+ locals = maybeLocals;
+ if (routeDataOrOptions || locals) {
+ this.#logRenderOptionsDeprecationWarning();
+ }
+ }
+
// Handle requests with duplicate slashes gracefully by cloning with a cleaned-up request URL
if (request.url !== collapseDuplicateSlashes(request.url)) {
request = new Request(collapseDuplicateSlashes(request.url), request);
@@ -156,7 +189,6 @@ export class App {
if (!routeData) {
return this.#renderError(request, { status: 404 });
}
-
Reflect.set(request, clientLocalsSymbol, locals ?? {});
const pathname = this.#getPathnameFromRequest(request);
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
@@ -181,16 +213,14 @@ export class App {
);
if (i18nMiddleware) {
if (mod.onRequest) {
- this.#pipeline.setMiddlewareFunction(
- sequence(i18nMiddleware, mod.onRequest as MiddlewareEndpointHandler)
- );
+ this.#pipeline.setMiddlewareFunction(sequence(i18nMiddleware, mod.onRequest));
} else {
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
}
this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else {
if (mod.onRequest) {
- this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
+ this.#pipeline.setMiddlewareFunction(mod.onRequest);
}
}
response = await this.#pipeline.renderRoute(renderContext, pageModule);
@@ -198,7 +228,7 @@ export class App {
if (err instanceof EndpointNotFoundError) {
return this.#renderError(request, { status: 404, response: err.originalResponse });
} else {
- this.#logger.error('ssr', err.stack || err.message || String(err));
+ this.#logger.error(null, err.stack || err.message || String(err));
return this.#renderError(request, { status: 500 });
}
}
@@ -216,6 +246,15 @@ export class App {
return response;
}
+ #logRenderOptionsDeprecationWarning() {
+ if (this.#renderOptionsDeprecationWarningShown) return;
+ this.#logger.warn(
+ 'deprecated',
+ `The adapter ${this.#manifest.adapterName} is using a deprecated signature of the 'app.render()' method. From Astro 4.0, locals and routeData are provided as properties on an optional object to this method. Using the old signature will cause an error in Astro 5.0. See https://github.com/withastro/astro/pull/9199 for more information.`
+ );
+ this.#renderOptionsDeprecationWarningShown = true;
+ }
+
setCookieHeaders(response: Response) {
return getSetCookiesFromResponse(response);
}
@@ -322,7 +361,7 @@ export class App {
);
const page = (await mod.page()) as any;
if (skipMiddleware === false && mod.onRequest) {
- this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
+ this.#pipeline.setMiddlewareFunction(mod.onRequest);
}
if (skipMiddleware) {
// make sure middleware set by other requests is cleared out
@@ -367,8 +406,8 @@ export class App {
const status = override?.status
? override.status
: oldResponse.status === 200
- ? newResponse.status
- : oldResponse.status;
+ ? newResponse.status
+ : oldResponse.status;
return new Response(newResponse.body, {
status,
diff --git a/packages/astro/src/core/app/node.ts b/packages/astro/src/core/app/node.ts
index 1df931eca..46882f58e 100644
--- a/packages/astro/src/core/app/node.ts
+++ b/packages/astro/src/core/app/node.ts
@@ -1,11 +1,12 @@
import type { RouteData } from '../../@types/astro.js';
import type { SerializedSSRManifest, SSRManifest } from './types.js';
+import type { RenderOptions } from './index.js';
import * as fs from 'node:fs';
import { IncomingMessage } from 'node:http';
import { TLSSocket } from 'node:tls';
import { deserializeManifest } from './common.js';
-import { App, type MatchOptions } from './index.js';
+import { App } from './index.js';
export { apply as applyPolyfills } from '../polyfill.js';
const clientAddressSymbol = Symbol.for('astro.clientAddress');
@@ -108,19 +109,34 @@ class NodeIncomingMessage extends IncomingMessage {
}
export class NodeApp extends App {
- match(req: NodeIncomingMessage | Request, opts: MatchOptions = {}) {
+ match(req: NodeIncomingMessage | Request) {
if (!(req instanceof Request)) {
req = createRequestFromNodeRequest(req, {
emptyBody: true,
});
}
- return super.match(req, opts);
+ return super.match(req);
}
- render(req: NodeIncomingMessage | Request, routeData?: RouteData, locals?: object) {
+ render(request: NodeIncomingMessage | Request, options?: RenderOptions): Promise<Response>;
+ /**
+ * @deprecated Instead of passing `RouteData` and locals individually, pass an object with `routeData` and `locals` properties.
+ * See https://github.com/withastro/astro/pull/9199 for more information.
+ */
+ render(
+ request: NodeIncomingMessage | Request,
+ routeData?: RouteData,
+ locals?: object
+ ): Promise<Response>;
+ render(
+ req: NodeIncomingMessage | Request,
+ routeDataOrOptions?: RouteData | RenderOptions,
+ maybeLocals?: object
+ ) {
if (!(req instanceof Request)) {
req = createRequestFromNodeRequest(req);
}
- return super.render(req, routeData, locals);
+ // @ts-expect-error The call would have succeeded against the implementation, but implementation signatures of overloads are not externally visible.
+ return super.render(req, routeDataOrOptions, maybeLocals);
}
}
diff --git a/packages/astro/src/core/build/buildPipeline.ts b/packages/astro/src/core/build/buildPipeline.ts
index e9b3c683e..87166b4f4 100644
--- a/packages/astro/src/core/build/buildPipeline.ts
+++ b/packages/astro/src/core/build/buildPipeline.ts
@@ -142,15 +142,15 @@ export class BuildPipeline extends Pipeline {
retrieveRoutesToGenerate(): Map<PageBuildData, string> {
const pages = new Map<PageBuildData, string>();
- for (const [entryPoint, filePath] of this.#internals.entrySpecifierToBundleMap) {
+ for (const [entrypoint, filePath] of this.#internals.entrySpecifierToBundleMap) {
// virtual pages can be emitted with different prefixes:
// - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages
// - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID
if (
- entryPoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
- entryPoint.includes(RESOLVED_SPLIT_MODULE_ID)
+ entrypoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
+ entrypoint.includes(RESOLVED_SPLIT_MODULE_ID)
) {
- const [, pageName] = entryPoint.split(':');
+ const [, pageName] = entrypoint.split(':');
const pageData = this.#internals.pagesByComponent.get(
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
);
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index 454f29b40..e35585ca7 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -1,16 +1,13 @@
-import * as colors from 'kleur/colors';
-import { bgGreen, black, cyan, dim, green, magenta } from 'kleur/colors';
+import { bgGreen, black, blue, bold, dim, green, magenta, red } from 'kleur/colors';
import fs from 'node:fs';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
import PQueue from 'p-queue';
import type { OutputAsset, OutputChunk } from 'rollup';
-import type { BufferEncoding } from 'vfile';
import type {
AstroSettings,
ComponentInstance,
GetStaticPathsItem,
- MiddlewareEndpointHandler,
RouteData,
RouteType,
SSRError,
@@ -113,16 +110,6 @@ async function getEntryForFallbackRoute(
return RedirectSinglePageBuiltModule;
}
-function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean {
- return (
- // Drafts are disabled
- !settings.config.markdown.drafts &&
- // This is a draft post
- 'frontmatter' in pageModule &&
- (pageModule as any).frontmatter?.draft === true
- );
-}
-
// Gives back a facadeId that is relative to the root.
// ie, src/pages/index.astro instead of /Users/name..../src/pages/index.astro
export function rootRelativeFacadeId(facadeId: string, settings: AstroSettings): string {
@@ -149,7 +136,7 @@ export function chunkIsPage(
}
export async function generatePages(opts: StaticBuildOptions, internals: BuildInternals) {
- const timer = performance.now();
+ const generatePagesTimer = performance.now();
const ssr = isServerLikeOutput(opts.settings.config);
let manifest: SSRManifest;
if (ssr) {
@@ -179,7 +166,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
}
const verb = ssr ? 'prerendering' : 'generating';
- logger.info(null, `\n${bgGreen(black(` ${verb} static routes `))}`);
+ logger.info('SKIP_FORMAT', `\n${bgGreen(black(` ${verb} static routes `))}`);
const builtPaths = new Set<string>();
const pagesToGenerate = pipeline.retrieveRoutesToGenerate();
if (ssr) {
@@ -187,11 +174,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
if (pageData.route.prerender) {
const ssrEntryURLPage = createEntryURL(filePath, outFolder);
const ssrEntryPage = await import(ssrEntryURLPage.toString());
- if (
- // TODO: remove in Astro 4.0
- opts.settings.config.build.split ||
- opts.settings.adapter?.adapterFeatures?.functionPerRoute
- ) {
+ if (opts.settings.adapter?.adapterFeatures?.functionPerRoute) {
// forcing to use undefined, so we fail in an expected way if the module is not even there.
const ssrEntry = ssrEntryPage?.pageModule;
if (ssrEntry) {
@@ -223,12 +206,14 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
}
}
}
-
- logger.info(null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
+ logger.info(
+ null,
+ green(`✓ Completed in ${getTimeStat(generatePagesTimer, performance.now())}.\n`)
+ );
const staticImageList = getStaticImageList();
if (staticImageList.size) {
- logger.info(null, `\n${bgGreen(black(` generating optimized images `))}`);
+ logger.info('SKIP_FORMAT', `${bgGreen(black(` generating optimized images `))}`);
const totalCount = Array.from(staticImageList.values())
.map((x) => x.transforms.size)
@@ -244,7 +229,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
await queue.onIdle();
const assetsTimeEnd = performance.now();
- logger.info(null, dim(`Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.\n`));
+ logger.info(null, green(`✓ Completed in ${getTimeStat(assetsTimer, assetsTimeEnd)}.\n`));
delete globalThis?.astroAsset?.addStaticImage;
}
@@ -283,15 +268,13 @@ async function generatePage(
);
if (config.experimental.i18n && i18nMiddleware) {
if (onRequest) {
- pipeline.setMiddlewareFunction(
- sequence(i18nMiddleware, onRequest as MiddlewareEndpointHandler)
- );
+ pipeline.setMiddlewareFunction(sequence(i18nMiddleware, onRequest));
} else {
pipeline.setMiddlewareFunction(i18nMiddleware);
}
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
- pipeline.setMiddlewareFunction(onRequest as MiddlewareEndpointHandler);
+ pipeline.setMiddlewareFunction(onRequest);
}
if (!pageModulePromise) {
throw new Error(
@@ -299,16 +282,6 @@ async function generatePage(
);
}
const pageModule = await pageModulePromise();
- if (shouldSkipDraft(pageModule, pipeline.getSettings())) {
- logger.info(null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`);
- // TODO: Remove in Astro 4.0
- logger.warn(
- 'astro',
- `The drafts feature is deprecated. You should migrate to content collections instead. See https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries for more information.`
- );
- return;
- }
-
const generationOptions: Readonly<GeneratePathOptions> = {
pageData,
linkIds,
@@ -321,7 +294,7 @@ async function generatePage(
pageData.route.type === 'page' ||
pageData.route.type === 'redirect' ||
pageData.route.type === 'fallback'
- ? green('▶')
+ ? blue('▶')
: magenta('λ');
if (isRelativePath(pageData.route.component)) {
logger.info(null, `${icon} ${pageData.route.route}`);
@@ -341,10 +314,10 @@ async function generatePage(
await generatePath(path, generationOptions, pipeline);
const timeEnd = performance.now();
const timeChange = getTimeStat(prevTimeEnd, timeEnd);
- const timeIncrease = `(+${timeChange})`;
+ const timeIncrease = `(${timeChange})`;
const filePath = getOutputFilename(pipeline.getConfig(), path, pageData.route.type);
const lineIcon = i === paths.length - 1 ? '└─' : '├─';
- logger.info(null, ` ${cyan(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
+ logger.info(null, ` ${blue(lineIcon)} ${dim(filePath)} ${dim(timeIncrease)}`);
prevTimeEnd = timeEnd;
}
}
@@ -383,14 +356,14 @@ async function getPathsForRoute(
logger,
ssr: isServerLikeOutput(opts.settings.config),
}).catch((err) => {
- logger.debug('build', `├── ${colors.bold(colors.red('✗'))} ${route.component}`);
+ logger.debug('build', `├── ${bold(red('✗'))} ${route.component}`);
throw err;
});
const label = staticPaths.length === 1 ? 'page' : 'pages';
logger.debug(
'build',
- `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.magenta(
+ `├── ${bold(green('✔'))} ${route.component} → ${magenta(
`[${staticPaths.length} ${label}]`
)}`
);
@@ -584,7 +557,6 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
});
let body: string | Uint8Array;
- let encoding: BufferEncoding | undefined;
let response: Response;
try {
@@ -627,7 +599,6 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
// If there's no body, do nothing
if (!response.body) return;
body = Buffer.from(await response.arrayBuffer());
- encoding = (response.headers.get('X-Astro-Encoding') as BufferEncoding | null) ?? 'utf-8';
}
const outFolder = getOutFolder(pipeline.getConfig(), pathname, route.type);
@@ -635,7 +606,7 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
route.distURL = outFile;
await fs.promises.mkdir(outFolder, { recursive: true });
- await fs.promises.writeFile(outFile, body, encoding);
+ await fs.promises.writeFile(outFile, body);
}
}
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index f096b8f76..0245e50c2 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,4 +1,4 @@
-import * as colors from 'kleur/colors';
+import { blue, bold, green } from 'kleur/colors';
import fs from 'node:fs';
import { performance } from 'node:perf_hooks';
import { fileURLToPath } from 'node:url';
@@ -69,8 +69,9 @@ export default async function build(
if (astroConfig.experimental.contentCollectionCache && options.force) {
const contentCacheDir = new URL('./content/', astroConfig.cacheDir);
if (fs.existsSync(contentCacheDir)) {
- logger.warn('content', 'clearing cache');
+ logger.debug('content', 'clearing content cache');
await fs.promises.rm(contentCacheDir, { force: true, recursive: true });
+ logger.warn('content', 'content cache cleared (force)');
}
}
@@ -157,9 +158,10 @@ class AstroBuilder {
await runHookBuildStart({ config: this.settings.config, logging: this.logger });
this.validateConfig();
- this.logger.info('build', `output target: ${colors.green(this.settings.config.output)}`);
+ this.logger.info('build', `output: ${blue('"' + this.settings.config.output + '"')}`);
+ this.logger.info('build', `directory: ${blue(fileURLToPath(this.settings.config.outDir))}`);
if (this.settings.adapter) {
- this.logger.info('build', `deploy adapter: ${colors.green(this.settings.adapter.name)}`);
+ this.logger.info('build', `adapter: ${green(this.settings.adapter.name)}`);
}
this.logger.info('build', 'Collecting build info...');
this.timer.loadStart = performance.now();
@@ -179,7 +181,7 @@ class AstroBuilder {
this.timer.buildStart = performance.now();
this.logger.info(
'build',
- colors.dim(`Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
+ green(`✓ Completed in ${getTimeStat(this.timer.init, performance.now())}.`)
);
const opts: StaticBuildOptions = {
@@ -251,31 +253,6 @@ class AstroBuilder {
`the outDir cannot be the root folder. Please build to a folder such as dist.`
);
}
-
- if (config.build.split === true) {
- if (config.output === 'static') {
- this.logger.warn(
- 'configuration',
- 'The option `build.split` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
- );
- }
- this.logger.warn(
- 'configuration',
- 'The option `build.split` is deprecated. Use the adapter options.'
- );
- }
- if (config.build.excludeMiddleware === true) {
- if (config.output === 'static') {
- this.logger.warn(
- 'configuration',
- 'The option `build.excludeMiddleware` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
- );
- }
- this.logger.warn(
- 'configuration',
- 'The option `build.excludeMiddleware` is deprecated. Use the adapter options.'
- );
- }
}
/** Stats */
@@ -294,12 +271,12 @@ class AstroBuilder {
let messages: string[] = [];
if (buildMode === 'static') {
- messages = [`${pageCount} page(s) built in`, colors.bold(total)];
+ messages = [`${pageCount} page(s) built in`, bold(total)];
} else {
- messages = ['Server built in', colors.bold(total)];
+ messages = ['Server built in', bold(total)];
}
logger.info('build', messages.join(' '));
- logger.info('build', `${colors.bold('Complete!')}`);
+ logger.info('build', `${bold('Complete!')}`);
}
}
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 3babef38f..34e76ab1d 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -248,15 +248,15 @@ export function* eachPageFromAllPages(allPages: AllPagesData): Generator<[string
export function* eachPageDataFromEntryPoint(
internals: BuildInternals
): Generator<[PageBuildData, string]> {
- for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) {
+ for (const [entrypoint, filePath] of internals.entrySpecifierToBundleMap) {
// virtual pages can be emitted with different prefixes:
// - the classic way are pages emitted with prefix ASTRO_PAGE_RESOLVED_MODULE_ID -> plugin-pages
// - pages emitted using `build.split`, in this case pages are emitted with prefix RESOLVED_SPLIT_MODULE_ID
if (
- entryPoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
- entryPoint.includes(RESOLVED_SPLIT_MODULE_ID)
+ entrypoint.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
+ entrypoint.includes(RESOLVED_SPLIT_MODULE_ID)
) {
- const [, pageName] = entryPoint.split(':');
+ const [, pageName] = entrypoint.split(':');
const pageData = internals.pagesByComponent.get(
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
);
diff --git a/packages/astro/src/core/build/plugins/plugin-css.ts b/packages/astro/src/core/build/plugins/plugin-css.ts
index f8611898f..318a1fee5 100644
--- a/packages/astro/src/core/build/plugins/plugin-css.ts
+++ b/packages/astro/src/core/build/plugins/plugin-css.ts
@@ -223,8 +223,8 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
inlineConfig === 'always'
? true
: inlineConfig === 'never'
- ? false
- : assetSize <= assetsInlineLimit;
+ ? false
+ : assetSize <= assetsInlineLimit;
// there should be a single js object for each stylesheet,
// allowing the single reference to be shared and checked for duplicates
diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts
index c1205a338..6f9ec6326 100644
--- a/packages/astro/src/core/build/plugins/plugin-manifest.ts
+++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts
@@ -98,8 +98,6 @@ export function pluginManifest(
const manifest = await createManifest(options, internals);
const shouldPassMiddlewareEntryPoint =
- // TODO: remove in Astro 4.0
- options.settings.config.build.excludeMiddleware ||
options.settings.adapter?.adapterFeatures?.edgeMiddleware;
await runHookBuildSsr({
config: options.settings.config,
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index 2a348f18f..fee94071e 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -92,10 +92,6 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
}
export function shouldBundleMiddleware(settings: AstroSettings) {
- // TODO: Remove in Astro 4.0
- if (settings.config.build.excludeMiddleware === true) {
- return false;
- }
if (settings.adapter?.adapterFeatures?.edgeMiddleware === true) {
return false;
}
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index fd892c9b6..6a6dd224a 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -102,10 +102,7 @@ export function pluginSSR(
hooks: {
'build:before': () => {
let vitePlugin =
- ssr &&
- // TODO: Remove in Astro 4.0
- options.settings.config.build.split === false &&
- functionPerRouteEnabled === false
+ ssr && functionPerRouteEnabled === false
? vitePluginSSR(internals, options.settings.adapter!, options)
: undefined;
@@ -119,7 +116,7 @@ export function pluginSSR(
return;
}
- if (options.settings.config.build.split || functionPerRouteEnabled) {
+ if (functionPerRouteEnabled) {
return;
}
@@ -146,7 +143,7 @@ function vitePluginSSRSplit(
name: '@astrojs/vite-plugin-astro-ssr-split',
enforce: 'post',
options(opts) {
- if (options.settings.config.build.split || functionPerRouteEnabled) {
+ if (functionPerRouteEnabled) {
const inputs = new Set<string>();
for (const [path, pageData] of eachPageFromAllPages(options.allPages)) {
@@ -223,7 +220,7 @@ export function pluginSSRSplit(
hooks: {
'build:before': () => {
let vitePlugin =
- ssr && (options.settings.config.build.split || functionPerRouteEnabled)
+ ssr && functionPerRouteEnabled
? vitePluginSSRSplit(internals, options.settings.adapter!, options)
: undefined;
@@ -240,7 +237,7 @@ function generateSSRCode(config: AstroConfig, adapter: AstroAdapter) {
const imports: string[] = [];
const contents: string[] = [];
let pageMap;
- if (config.build.split || isFunctionPerRouteEnabled(adapter)) {
+ if (isFunctionPerRouteEnabled(adapter)) {
pageMap = 'pageModule';
} else {
pageMap = 'pageMap';
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index c54de6b44..2580e585e 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -1,7 +1,7 @@
import { teardown } from '@astrojs/compiler';
import * as eslexer from 'es-module-lexer';
import glob from 'fast-glob';
-import { bgGreen, bgMagenta, black, dim } from 'kleur/colors';
+import { bgGreen, bgMagenta, black, green } from 'kleur/colors';
import fs from 'node:fs';
import path, { extname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -78,7 +78,8 @@ export async function viteBuild(opts: StaticBuildOptions) {
const ssrTime = performance.now();
opts.logger.info('build', `Building ${settings.config.output} entrypoints...`);
const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
- opts.logger.info('build', dim(`Completed in ${getTimeStat(ssrTime, performance.now())}.`));
+ opts.logger.info('build', green(`✓ Completed in ${getTimeStat(ssrTime, performance.now())}.`));
+
settings.timer.end('SSR build');
settings.timer.start('Client build');
@@ -268,7 +269,6 @@ async function clientBuild(
container: AstroBuildPluginContainer
) {
const { settings, viteConfig } = opts;
- const timer = performance.now();
const ssr = isServerLikeOutput(settings.config);
const out = ssr ? settings.config.build.client : getOutDirWithinCwd(settings.config.outDir);
@@ -283,7 +283,7 @@ async function clientBuild(
}
const { lastVitePlugins, vitePlugins } = await container.runBeforeHook('client', input);
- opts.logger.info(null, `\n${bgGreen(black(' building client '))}`);
+ opts.logger.info('SKIP_FORMAT', `\n${bgGreen(black(' building client (vite) '))}`);
const viteBuildConfig: vite.InlineConfig = {
...viteConfig,
@@ -322,7 +322,6 @@ async function clientBuild(
});
const buildResult = await vite.build(viteBuildConfig);
- opts.logger.info(null, dim(`Completed in ${getTimeStat(timer, performance.now())}.\n`));
return buildResult;
}
diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts
index 59fa06f6b..0722a293d 100644
--- a/packages/astro/src/core/build/types.ts
+++ b/packages/astro/src/core/build/types.ts
@@ -54,7 +54,7 @@ export interface SinglePageBuiltModule {
/**
* The `onRequest` hook exported by the middleware
*/
- onRequest?: MiddlewareHandler<unknown>;
+ onRequest?: MiddlewareHandler;
renderers: SSRLoadedRenderer[];
}
diff --git a/packages/astro/src/core/build/util.ts b/packages/astro/src/core/build/util.ts
index e46a0713a..fc12b486f 100644
--- a/packages/astro/src/core/build/util.ts
+++ b/packages/astro/src/core/build/util.ts
@@ -2,7 +2,7 @@ import type { AstroConfig } from '../../@types/astro.js';
export function getTimeStat(timeStart: number, timeEnd: number) {
const buildTime = timeEnd - timeStart;
- return buildTime < 750 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
+ return buildTime < 1000 ? `${Math.round(buildTime)}ms` : `${(buildTime / 1000).toFixed(2)}s`;
}
/**
diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts
index f270e123e..2985dcab9 100644
--- a/packages/astro/src/core/compile/compile.ts
+++ b/packages/astro/src/core/compile/compile.ts
@@ -46,7 +46,8 @@ export async function compile({
scopedStyleStrategy: astroConfig.scopedStyleStrategy,
resultScopedSlot: true,
transitionsAnimationURL: 'astro/components/viewtransitions.css',
- annotateSourceFile: !viteConfig.isProduction && astroConfig.experimental.devOverlay,
+ annotateSourceFile:
+ !viteConfig.isProduction && astroConfig.devOverlay && astroConfig.devOverlay.enabled,
preprocessStyle: createStylePreprocessor({
filename,
viteConfig,
diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts
index 53acb6924..82bb872b1 100644
--- a/packages/astro/src/core/config/config.ts
+++ b/packages/astro/src/core/config/config.ts
@@ -67,7 +67,6 @@ export function resolveFlags(flags: Partial<Flags>): CLIFlags {
config: typeof flags.config === 'string' ? flags.config : undefined,
host:
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
- drafts: typeof flags.drafts === 'boolean' ? flags.drafts : undefined,
};
}
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 70ef7a86a..c12689654 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -8,10 +8,9 @@ import { markdownConfigDefaults } from '@astrojs/markdown-remark';
import { bundledThemes, type BuiltinTheme } from 'shikiji';
import type { AstroUserConfig, ViteUserConfig } from '../../@types/astro.js';
-import fs from 'node:fs';
import type { OutgoingHttpHeaders } from 'node:http';
import path from 'node:path';
-import { fileURLToPath, pathToFileURL } from 'node:url';
+import { pathToFileURL } from 'node:url';
import { z } from 'zod';
import { appendForwardSlash, prependForwardSlash, removeTrailingForwardSlash } from '../path.js';
@@ -38,12 +37,14 @@ const ASTRO_CONFIG_DEFAULTS = {
serverEntry: 'entry.mjs',
redirects: true,
inlineStylesheets: 'auto',
- split: false,
- excludeMiddleware: false,
},
image: {
service: { entrypoint: 'astro/assets/services/sharp', config: {} },
},
+ devOverlay: {
+ enabled: true,
+ defaultState: 'minimized',
+ },
compressHTML: true,
server: {
host: false,
@@ -51,16 +52,12 @@ const ASTRO_CONFIG_DEFAULTS = {
open: false,
},
integrations: [],
- markdown: {
- drafts: false,
- ...markdownConfigDefaults,
- },
+ markdown: markdownConfigDefaults,
vite: {},
legacy: {},
redirects: {},
experimental: {
optimizeHoistedScript: false,
- devOverlay: false,
contentCollectionCache: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };
@@ -141,20 +138,6 @@ export const AstroConfigSchema = z.object({
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
-
- /**
- * @deprecated
- * Use the adapter feature instead
- */
- split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
- /**
- * @deprecated
- * Use the adapter feature instead
- */
- excludeMiddleware: z
- .boolean()
- .optional()
- .default(ASTRO_CONFIG_DEFAULTS.build.excludeMiddleware),
})
.default({}),
server: z.preprocess(
@@ -245,9 +228,16 @@ export const AstroConfigSchema = z.object({
.default([]),
})
.default(ASTRO_CONFIG_DEFAULTS.image),
+ devOverlay: z
+ .object({
+ enabled: z.boolean().default(ASTRO_CONFIG_DEFAULTS.devOverlay.enabled),
+ defaultState: z
+ .enum(['minimized', 'expanded'])
+ .default(ASTRO_CONFIG_DEFAULTS.devOverlay.defaultState),
+ })
+ .default(ASTRO_CONFIG_DEFAULTS.devOverlay),
markdown: z
.object({
- drafts: z.boolean().default(false),
syntaxHighlight: z
.union([z.literal('shiki'), z.literal('prism'), z.literal(false)])
.default(ASTRO_CONFIG_DEFAULTS.markdown.syntaxHighlight),
@@ -260,25 +250,6 @@ export const AstroConfigSchema = z.object({
for (const lang of langs) {
// shiki -> shikiji compat
if (typeof lang === 'object') {
- // shikiji does not support `path`
- // https://github.com/shikijs/shiki/blob/facb6ff37996129626f8066a5dccb4608e45f649/packages/shiki/src/loader.ts#L98
- const langPath = (lang as any).path;
- if (langPath) {
- // shiki resolves path from within its package directory :shrug:
- const astroRoot = fileURLToPath(new URL('../../../', import.meta.url));
- const normalizedPath = path.isAbsolute(langPath)
- ? langPath
- : path.resolve(astroRoot, langPath);
- try {
- const content = fs.readFileSync(normalizedPath, 'utf-8');
- const parsed = JSON.parse(content);
- Object.assign(lang, parsed);
- } catch (e) {
- throw new Error(`Unable to find language file at ${normalizedPath}`, {
- cause: e,
- });
- }
- }
// `id` renamed to `name` (always override)
if ((lang as any).id) {
lang.name = (lang as any).id;
@@ -341,7 +312,6 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.optimizeHoistedScript),
- devOverlay: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.devOverlay),
i18n: z.optional(
z
.object({
@@ -468,12 +438,6 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) {
.enum(['always', 'auto', 'never'])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
-
- split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
- excludeMiddleware: z
- .boolean()
- .optional()
- .default(ASTRO_CONFIG_DEFAULTS.build.excludeMiddleware),
})
.optional()
.default({}),
diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts
index fca392c97..29df00eaf 100644
--- a/packages/astro/src/core/config/settings.ts
+++ b/packages/astro/src/core/config/settings.ts
@@ -10,11 +10,14 @@ import { formatYAMLException, isYAMLException } from '../errors/utils.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../constants.js';
import { AstroTimer } from './timer.js';
import { loadTSConfig } from './tsconfig.js';
+import createPreferences from '../../preferences/index.js';
export function createBaseSettings(config: AstroConfig): AstroSettings {
const { contentDir } = getContentPaths(config);
+ const preferences = createPreferences(config);
return {
config,
+ preferences,
tsConfig: undefined,
tsConfigPath: undefined,
adapter: undefined,
diff --git a/packages/astro/src/core/config/tsconfig.ts b/packages/astro/src/core/config/tsconfig.ts
index db4b1392f..3375be9c8 100644
--- a/packages/astro/src/core/config/tsconfig.ts
+++ b/packages/astro/src/core/config/tsconfig.ts
@@ -162,14 +162,14 @@ export type StripEnums<T extends Record<string, any>> = {
[K in keyof T]: T[K] extends boolean
? T[K]
: T[K] extends string
- ? T[K]
- : T[K] extends object
- ? T[K]
- : T[K] extends Array<any>
- ? T[K]
- : T[K] extends undefined
- ? undefined
- : any;
+ ? T[K]
+ : T[K] extends object
+ ? T[K]
+ : T[K] extends Array<any>
+ ? T[K]
+ : T[K] extends undefined
+ ? undefined
+ : any;
};
export interface TSConfig {
diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts
index de6443729..c9716af89 100644
--- a/packages/astro/src/core/create-vite.ts
+++ b/packages/astro/src/core/create-vite.ts
@@ -1,10 +1,9 @@
-import type { AstroSettings } from '../@types/astro.js';
-import type { Logger } from './logger/core.js';
-
import nodeFs from 'node:fs';
import { fileURLToPath } from 'node:url';
+import type { Logger as ViteLogger } from 'vite';
import * as vite from 'vite';
import { crawlFrameworkPkgs } from 'vitefu';
+import type { AstroSettings } from '../@types/astro.js';
import astroAssetsPlugin from '../assets/vite-plugin-assets.js';
import {
astroContentAssetPropagationPlugin,
@@ -31,6 +30,7 @@ import astroScannerPlugin from '../vite-plugin-scanner/index.js';
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js';
import { vitePluginSSRManifest } from '../vite-plugin-ssr-manifest/index.js';
+import type { Logger } from './logger/core.js';
import { vitePluginMiddleware } from './middleware/vite-plugin.js';
import { joinPaths } from './path.js';
@@ -102,6 +102,17 @@ export async function createVite(
},
});
+ const viteCustomLogger: ViteLogger = {
+ ...vite.createLogger('warn'),
+ // All error log messages are also thrown as real errors,
+ // so we can safely ignore them here and let the error handler
+ // log them for the user instead.
+ error: (msg) => logger.debug('vite', 'ERROR ' + msg),
+ // Warnings are usually otherwise ignored by Vite, so it's
+ // important that we catch and log them here.
+ warn: (msg) => logger.warn('vite', msg),
+ };
+
// Start with the Vite configuration that Astro core needs
const commonConfig: vite.InlineConfig = {
// Tell Vite not to combine config from vite.config.js with our provided inline config
@@ -109,6 +120,7 @@ export async function createVite(
cacheDir: fileURLToPath(new URL('./node_modules/.vite/', settings.config.root)), // using local caches allows Astro to be used in monorepos, etc.
clearScreen: false, // we want to control the output, not Vite
logLevel: 'warn', // log warnings and errors only
+ customLogger: viteCustomLogger,
appType: 'custom',
optimizeDeps: {
entries: ['src/**/*'],
@@ -180,7 +192,7 @@ export async function createVite(
},
{
find: 'astro:middleware',
- replacement: 'astro/middleware/namespace',
+ replacement: 'astro/virtual-modules/middleware.js',
},
{
find: 'astro:components',
diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts
index 02ba9d872..980b93463 100644
--- a/packages/astro/src/core/dev/dev.ts
+++ b/packages/astro/src/core/dev/dev.ts
@@ -9,6 +9,7 @@ import { telemetry } from '../../events/index.js';
import * as msg from '../messages.js';
import { startContainer } from './container.js';
import { createContainerWithAutomaticRestart } from './restart.js';
+import { green } from 'kleur/colors';
export interface DevServer {
address: AddressInfo;
@@ -33,9 +34,8 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
// Start listening to the port
const devServerAddressInfo = await startContainer(restart.container);
-
logger.info(
- null,
+ 'SKIP_FORMAT',
msg.serverStart({
startupTime: performance.now() - devStart,
resolvedUrls: restart.container.viteServer.resolvedUrls || { local: [], network: [] },
@@ -46,14 +46,16 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
if (currentVersion.includes('-')) {
- logger.warn(null, msg.prerelease({ currentVersion }));
+ logger.warn('SKIP_FORMAT', msg.prerelease({ currentVersion }));
}
if (restart.container.viteServer.config.server?.fs?.strict === false) {
- logger.warn(null, msg.fsStrictWarning());
+ logger.warn('SKIP_FORMAT', msg.fsStrictWarning());
}
await attachContentServerListeners(restart.container);
+ logger.info(null, green('watching for file changes...'));
+
return {
address: devServerAddressInfo,
get watcher() {
diff --git a/packages/astro/src/core/dev/restart.ts b/packages/astro/src/core/dev/restart.ts
index f03db39cf..b19eae4ac 100644
--- a/packages/astro/src/core/dev/restart.ts
+++ b/packages/astro/src/core/dev/restart.ts
@@ -29,6 +29,9 @@ async function createRestartedContainer(
return newContainer;
}
+const configRE = new RegExp(`.*astro\.config\.((mjs)|(cjs)|(js)|(ts))$`);
+const preferencesRE = new RegExp(`.*\.astro\/settings\.json$`);
+
export function shouldRestartContainer(
{ settings, inlineConfig, restartInFlight }: Container,
changedFile: string
@@ -43,9 +46,9 @@ export function shouldRestartContainer(
}
// Otherwise, watch for any astro.config.* file changes in project root
else {
- const exp = new RegExp(`.*astro\.config\.((mjs)|(cjs)|(js)|(ts))$`);
const normalizedChangedFile = vite.normalizePath(changedFile);
- shouldRestart = exp.test(normalizedChangedFile);
+ shouldRestart =
+ configRE.test(normalizedChangedFile) || preferencesRE.test(normalizedChangedFile);
}
if (!shouldRestart && settings.watchFiles.length > 0) {
@@ -71,7 +74,10 @@ export async function restartContainer(container: Container): Promise<Container
const error = createSafeError(_err);
// Print all error messages except ZodErrors from AstroConfig as the pre-logged error is sufficient
if (!isAstroConfigZodError(_err)) {
- logger.error('config', formatErrorMessage(collectErrorMetadata(error)) + '\n');
+ logger.error(
+ 'config',
+ formatErrorMessage(collectErrorMetadata(error), logger.level() === 'debug') + '\n'
+ );
}
// Inform connected clients of the config error
container.viteServer.ws.send({
@@ -82,7 +88,7 @@ export async function restartContainer(container: Container): Promise<Container
},
});
container.restartInFlight = false;
- logger.error('astro', 'Continuing with previous valid configuration\n');
+ logger.error(null, 'Continuing with previous valid configuration\n');
return error;
}
}
@@ -121,8 +127,8 @@ export async function createContainerWithAutomaticRestart({
},
};
- async function handleServerRestart(logMsg: string) {
- logger.info('astro', logMsg + '\n');
+ async function handleServerRestart(logMsg = '') {
+ logger.info(null, (logMsg + ' Restarting...').trim());
const container = restart.container;
const result = await restartContainer(container);
if (result instanceof Error) {
@@ -150,13 +156,13 @@ export async function createContainerWithAutomaticRestart({
// Set up watches
function addWatches() {
const watcher = restart.container.viteServer.watcher;
- watcher.on('change', handleChangeRestart('Configuration updated. Restarting...'));
- watcher.on('unlink', handleChangeRestart('Configuration removed. Restarting...'));
- watcher.on('add', handleChangeRestart('Configuration added. Restarting...'));
+ watcher.on('change', handleChangeRestart('Configuration file updated.'));
+ watcher.on('unlink', handleChangeRestart('Configuration file removed.'));
+ watcher.on('add', handleChangeRestart('Configuration file added.'));
// Restart the Astro dev server instead of Vite's when the API is called by plugins.
// Ignore the `forceOptimize` parameter for now.
- restart.container.viteServer.restart = () => handleServerRestart('Restarting...');
+ restart.container.viteServer.restart = () => handleServerRestart();
}
addWatches();
return restart;
diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts
index a18e01c90..eeaec2244 100644
--- a/packages/astro/src/core/endpoint/index.ts
+++ b/packages/astro/src/core/endpoint/index.ts
@@ -1,12 +1,4 @@
-import mime from 'mime';
-import type {
- APIContext,
- EndpointHandler,
- EndpointOutput,
- MiddlewareEndpointHandler,
- MiddlewareHandler,
- Params,
-} from '../../@types/astro.js';
+import type { APIContext, EndpointHandler, MiddlewareHandler, Params } from '../../@types/astro.js';
import { renderEndpoint } from '../../runtime/server/index.js';
import { ASTRO_VERSION } from '../constants.js';
import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js';
@@ -19,8 +11,6 @@ import {
} from '../render/context.js';
import { type Environment, type RenderContext } from '../render/index.js';
-const encoder = new TextEncoder();
-
const clientAddressSymbol = Symbol.for('astro.clientAddress');
const clientLocalsSymbol = Symbol.for('astro.locals');
@@ -69,7 +59,6 @@ export function createAPIContext({
},
});
},
- ResponseWithEncoding,
get preferredLocale(): string | undefined {
if (preferredLocale) {
return preferredLocale;
@@ -143,36 +132,11 @@ export function createAPIContext({
return context;
}
-type ResponseParameters = ConstructorParameters<typeof Response>;
-
-export class ResponseWithEncoding extends Response {
- constructor(body: ResponseParameters[0], init: ResponseParameters[1], encoding?: BufferEncoding) {
- // If a body string is given, try to encode it to preserve the behaviour as simple objects.
- // We don't do the full handling as simple objects so users can control how headers are set instead.
- if (typeof body === 'string') {
- // In NodeJS, we can use Buffer.from which supports all BufferEncoding
- if (typeof Buffer !== 'undefined' && Buffer.from) {
- body = Buffer.from(body, encoding);
- }
- // In non-NodeJS, use the web-standard TextEncoder for utf-8 strings
- else if (encoding == null || encoding === 'utf8' || encoding === 'utf-8') {
- body = encoder.encode(body);
- }
- }
-
- super(body, init);
-
- if (encoding) {
- this.headers.set('X-Astro-Encoding', encoding);
- }
- }
-}
-
-export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
+export async function callEndpoint(
mod: EndpointHandler,
env: Environment,
ctx: RenderContext,
- onRequest: MiddlewareHandler<MiddlewareResult> | undefined
+ onRequest: MiddlewareHandler | undefined
): Promise<Response> {
const context = createAPIContext({
request: ctx.request,
@@ -187,107 +151,13 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
let response;
if (onRequest) {
- response = await callMiddleware<Response | EndpointOutput>(
- env.logger,
- onRequest as MiddlewareEndpointHandler,
- context,
- async () => {
- return await renderEndpoint(mod, context, env.ssr, env.logger);
- }
- );
+ response = await callMiddleware(onRequest, context, async () => {
+ return await renderEndpoint(mod, context, env.ssr, env.logger);
+ });
} else {
response = await renderEndpoint(mod, context, env.ssr, env.logger);
}
- const isEndpointSSR = env.ssr && !ctx.route?.prerender;
-
- if (response instanceof Response) {
- if (isEndpointSSR && response.headers.get('X-Astro-Encoding')) {
- env.logger.warn(
- 'ssr',
- '`ResponseWithEncoding` is ignored in SSR. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
- );
- }
- attachCookiesToResponse(response, context.cookies);
- return response;
- }
-
- // The endpoint returned a simple object, convert it to a Response
-
- // TODO: Remove in Astro 4.0
- env.logger.warn(
- 'astro',
- `${ctx.route.component} returns a simple object which is deprecated. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.`
- );
-
- if (isEndpointSSR) {
- if (response.hasOwnProperty('headers')) {
- env.logger.warn(
- 'ssr',
- 'Setting headers is not supported when returning an object. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
- );
- }
-
- if (response.encoding) {
- env.logger.warn(
- 'ssr',
- '`encoding` is ignored in SSR. To return a charset other than UTF-8, please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
- );
- }
- }
-
- let body: BodyInit;
- const headers = new Headers();
-
- // Try to get the MIME type for this route
- const pathname = ctx.route
- ? // Try the static route `pathname`
- ctx.route.pathname ??
- // Dynamic routes don't include `pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
- ctx.route.segments.map((s) => s.map((p) => p.content).join('')).join('/')
- : // Fallback to pathname of the request
- ctx.pathname;
- const mimeType = mime.getType(pathname) || 'text/plain';
- headers.set('Content-Type', `${mimeType};charset=utf-8`);
-
- // Save encoding to X-Astro-Encoding to be used later during SSG with `fs.writeFile`.
- // It won't work in SSR and is already warned above.
- if (response.encoding) {
- headers.set('X-Astro-Encoding', response.encoding);
- }
-
- // For Uint8Array (binary), it can passed to Response directly
- if (response.body instanceof Uint8Array) {
- body = response.body;
- headers.set('Content-Length', body.byteLength.toString());
- }
- // In NodeJS, we can use Buffer.from which supports all BufferEncoding
- else if (typeof Buffer !== 'undefined' && Buffer.from) {
- body = Buffer.from(response.body, response.encoding);
- headers.set('Content-Length', body.byteLength.toString());
- }
- // In non-NodeJS, use the web-standard TextEncoder for utf-8 strings only
- // to calculate the content length
- else if (
- response.encoding == null ||
- response.encoding === 'utf8' ||
- response.encoding === 'utf-8'
- ) {
- body = encoder.encode(response.body);
- headers.set('Content-Length', body.byteLength.toString());
- }
- // Fallback pass it to Response directly. It will mainly rely on X-Astro-Encoding
- // to be further processed in SSG.
- else {
- body = response.body;
- // NOTE: Can't calculate the content length as we can't encode to figure out the real length.
- // But also because we don't need the length for SSG as it's only being written to disk.
- }
-
- response = new Response(body, {
- status: 200,
- headers,
- });
attachCookiesToResponse(response, context.cookies);
return response;
}
diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts
index b3e49234d..7d67806f8 100644
--- a/packages/astro/src/core/errors/dev/vite.ts
+++ b/packages/astro/src/core/errors/dev/vite.ts
@@ -83,7 +83,6 @@ export function enhanceViteSSRError({
if (globPattern) {
safeError.message = InvalidGlob.message(globPattern);
safeError.name = 'InvalidGlob';
- safeError.hint = InvalidGlob.hint;
safeError.title = InvalidGlob.title;
const line = lns.findIndex((ln) => ln.includes(globPattern));
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index ec84888d4..ac815b06f 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -490,9 +490,10 @@ export const PageNumberParamNotFound = {
*/
export const ImageMissingAlt = {
name: 'ImageMissingAlt',
- title: 'Missing alt property.',
- message: 'The alt property is required.',
- hint: "The `alt` property is important for the purpose of accessibility, without it users using screen readers or other assistive technologies won't be able to understand what your image is supposed to represent. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-alt for more information.",
+ title: 'Image missing required "alt" property.',
+ message:
+ 'Image missing "alt" property. "alt" text is required to describe important images on the page.',
+ hint: 'Use an empty string ("") for decorative images.',
} satisfies ErrorData;
/**
* @docs
diff --git a/packages/astro/src/core/errors/utils.ts b/packages/astro/src/core/errors/utils.ts
index 300787dd3..ef0a10b4f 100644
--- a/packages/astro/src/core/errors/utils.ts
+++ b/packages/astro/src/core/errors/utils.ts
@@ -93,9 +93,8 @@ export function createSafeError(err: any): Error {
} else {
const error = new Error(JSON.stringify(err));
- (
- error as SSRError
- ).hint = `To get as much information as possible from your errors, make sure to throw Error objects instead of \`${typeof err}\`. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error for more information.`;
+ (error as SSRError).hint =
+ `To get as much information as possible from your errors, make sure to throw Error objects instead of \`${typeof err}\`. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error for more information.`;
return error;
}
diff --git a/packages/astro/src/core/logger/console.ts b/packages/astro/src/core/logger/console.ts
index f39f6b74d..d55318d4c 100644
--- a/packages/astro/src/core/logger/console.ts
+++ b/packages/astro/src/core/logger/console.ts
@@ -1,10 +1,6 @@
-import { bold, cyan, dim, red, reset, yellow } from 'kleur/colors';
-import type { LogMessage } from './core.js';
-import { dateTimeFormat, levels } from './core.js';
+import { getEventPrefix, levels, type LogMessage, type LogWritable } from './core.js';
-let lastMessage: string;
-let lastMessageCount = 1;
-export const consoleLogDestination = {
+export const consoleLogDestination: LogWritable<LogMessage> = {
write(event: LogMessage) {
// eslint-disable-next-line no-console
let dest = console.error;
@@ -12,37 +8,11 @@ export const consoleLogDestination = {
// eslint-disable-next-line no-console
dest = console.log;
}
-
- function getPrefix() {
- let prefix = '';
- let type = event.label;
- if (type) {
- // hide timestamp when type is undefined
- prefix += dim(dateTimeFormat.format(new Date()) + ' ');
- if (event.level === 'info') {
- type = bold(cyan(`[${type}]`));
- } else if (event.level === 'warn') {
- type = bold(yellow(`[${type}]`));
- } else if (event.level === 'error') {
- type = bold(red(`[${type}]`));
- }
-
- prefix += `${type} `;
- }
- return reset(prefix);
- }
-
- let message = event.message;
- // For repeat messages, only update the message counter
- if (message === lastMessage) {
- lastMessageCount++;
- message = `${message} ${yellow(`(x${lastMessageCount})`)}`;
+ if (event.label === 'SKIP_FORMAT') {
+ dest(event.message);
} else {
- lastMessage = message;
- lastMessageCount = 1;
+ dest(getEventPrefix(event) + ' ' + event.message);
}
- const outMessage = getPrefix() + message;
- dest(outMessage);
return true;
},
};
diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts
index 11804dd01..5dab12213 100644
--- a/packages/astro/src/core/logger/core.ts
+++ b/packages/astro/src/core/logger/core.ts
@@ -1,12 +1,35 @@
-import { dim } from 'kleur/colors';
+import { blue, bold, dim, red, yellow } from 'kleur/colors';
import stringWidth from 'string-width';
-interface LogWritable<T> {
+export interface LogWritable<T> {
write: (chunk: T) => boolean;
}
export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
+/**
+ * Defined logger labels. Add more as needed, but keep them high-level & reusable,
+ * rather than specific to a single command, function, use, etc. The label will be
+ * shown in the log message to the user, so it should be relevant.
+ */
+export type LoggerLabel =
+ | 'add'
+ | 'build'
+ | 'check'
+ | 'config'
+ | 'content'
+ | 'deprecated'
+ | 'markdown'
+ | 'router'
+ | 'types'
+ | 'vite'
+ | 'watch'
+ | 'middleware'
+ | 'preferences'
+ // SKIP_FORMAT: A special label that tells the logger not to apply any formatting.
+ // Useful for messages that are already formatted, like the server start message.
+ | 'SKIP_FORMAT';
+
export interface LogOptions {
dest: LogWritable<LogMessage>;
level: LoggerLevel;
@@ -25,6 +48,7 @@ export const dateTimeFormat = new Intl.DateTimeFormat([], {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
+ hour12: false,
});
export interface LogMessage {
@@ -98,6 +122,35 @@ function padStr(str: string, len: number) {
return str + spaces;
}
+/**
+ * Get the prefix for a log message.
+ * This includes the timestamp, log level, and label all properly formatted
+ * with colors. This is shared across different loggers, so it's defined here.
+ */
+export function getEventPrefix({ level, label }: LogMessage) {
+ const timestamp = `${dateTimeFormat.format(new Date())}`;
+ const prefix = [];
+ if (level === 'error' || level === 'warn') {
+ prefix.push(bold(timestamp));
+ prefix.push(`[${level.toUpperCase()}]`);
+ } else {
+ prefix.push(timestamp);
+ }
+ if (label) {
+ prefix.push(`[${label}]`);
+ }
+ if (level === 'error') {
+ return red(prefix.join(' '));
+ }
+ if (level === 'warn') {
+ return yellow(prefix.join(' '));
+ }
+ if (prefix.length === 1) {
+ return dim(prefix[0]);
+ }
+ return dim(prefix[0]) + ' ' + blue(prefix.splice(1).join(' '));
+}
+
export let defaultLogLevel: LoggerLevel;
if (typeof process !== 'undefined') {
// This could be a shimmed environment so we don't know that `process` is the full
@@ -133,16 +186,16 @@ export class Logger {
this.options = options;
}
- info(label: string | null, message: string) {
+ info(label: LoggerLabel | null, message: string) {
info(this.options, label, message);
}
- warn(label: string | null, message: string) {
+ warn(label: LoggerLabel | null, message: string) {
warn(this.options, label, message);
}
- error(label: string | null, message: string) {
+ error(label: LoggerLabel | null, message: string) {
error(this.options, label, message);
}
- debug(label: string | null, ...messages: any[]) {
+ debug(label: LoggerLabel, ...messages: any[]) {
debug(label, ...messages);
}
diff --git a/packages/astro/src/core/logger/node.ts b/packages/astro/src/core/logger/node.ts
index 57aa59ed0..727cafd1b 100644
--- a/packages/astro/src/core/logger/node.ts
+++ b/packages/astro/src/core/logger/node.ts
@@ -1,113 +1,35 @@
import debugPackage from 'debug';
-import { bold, cyan, dim, red, reset, yellow } from 'kleur/colors';
-import * as readline from 'node:readline';
import { Writable } from 'node:stream';
-import stringWidth from 'string-width';
-import { dateTimeFormat, error, info, warn } from './core.js';
+import { getEventPrefix, levels, type LogMessage, type LogWritable } from './core.js';
type ConsoleStream = Writable & {
fd: 1 | 2;
};
-let lastMessage: string;
-let lastMessageCount = 1;
-export const nodeLogDestination = new Writable({
- objectMode: true,
- write(event: LogMessage, _, callback) {
+export const nodeLogDestination: LogWritable<LogMessage> = {
+ write(event: LogMessage) {
let dest: ConsoleStream = process.stderr;
if (levels[event.level] < levels['error']) {
dest = process.stdout;
}
-
- function getPrefix() {
- let prefix = '';
- let label = event.label;
- if (label) {
- // hide timestamp when type is undefined
- prefix += dim(dateTimeFormat.format(new Date()) + ' ');
- if (event.level === 'info') {
- label = bold(cyan(`[${label}]`));
- } else if (event.level === 'warn') {
- label = bold(yellow(`[${label}]`));
- } else if (event.level === 'error') {
- label = bold(red(`[${label}]`));
- }
-
- prefix += `${label} `;
- }
- return reset(prefix);
- }
-
- // console.log({msg: event.message, args: event.args});
- let message = event.message;
- // For repeat messages, only update the message counter
- if (message === lastMessage) {
- lastMessageCount++;
- if (levels[event.level] < levels['error']) {
- let lines = 1;
- let len = stringWidth(`${getPrefix()}${message}`);
- let cols = (dest as unknown as typeof process.stdout).columns;
- if (len > cols) {
- lines = Math.ceil(len / cols);
- }
- for (let i = 0; i < lines; i++) {
- readline.clearLine(dest, 0);
- readline.cursorTo(dest, 0);
- readline.moveCursor(dest, 0, -1);
- }
- }
- message = `${message} ${yellow(`(x${lastMessageCount})`)}`;
+ if (event.label === 'SKIP_FORMAT') {
+ dest.write(event.message + '\n');
} else {
- lastMessage = message;
- lastMessageCount = 1;
+ dest.write(getEventPrefix(event) + ' ' + event.message + '\n');
}
-
- dest.write(getPrefix());
- dest.write(message);
- dest.write('\n');
- callback();
+ return true;
},
-});
-
-interface LogWritable<T> {
- write: (chunk: T) => boolean;
-}
-
-export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino
-export type LoggerEvent = 'info' | 'warn' | 'error';
-
-export interface LogOptions {
- dest?: LogWritable<LogMessage>;
- level?: LoggerLevel;
-}
-
-export const nodeLogOptions: Required<LogOptions> = {
- dest: nodeLogDestination,
- level: 'info',
-};
-
-export interface LogMessage {
- label: string | null;
- level: LoggerLevel;
- message: string;
-}
-
-export const levels: Record<LoggerLevel, number> = {
- debug: 20,
- info: 30,
- warn: 40,
- error: 50,
- silent: 90,
};
const debuggers: Record<string, debugPackage.Debugger['log']> = {};
+
/**
* Emit a message only shown in debug mode.
* Astro (along with many of its dependencies) uses the `debug` package for debug logging.
* You can enable these logs with the `DEBUG=astro:*` environment variable.
* More info https://github.com/debug-js/debug#environment-variables
*/
-export function debug(type: string, ...messages: Array<any>) {
+function debug(type: string, ...messages: Array<any>) {
const namespace = `astro:${type}`;
debuggers[namespace] = debuggers[namespace] || debugPackage(namespace);
return debuggers[namespace](...messages);
@@ -116,16 +38,9 @@ export function debug(type: string, ...messages: Array<any>) {
// This is gross, but necessary since we are depending on globals.
(globalThis as any)._astroGlobalDebug = debug;
-// A default logger for when too lazy to pass LogOptions around.
-export const logger = {
- info: info.bind(null, nodeLogOptions),
- warn: warn.bind(null, nodeLogOptions),
- error: error.bind(null, nodeLogOptions),
-};
-
export function enableVerboseLogging() {
- debugPackage.enable('*,-babel');
- debug('cli', '--verbose flag enabled! Enabling: DEBUG="*,-babel"');
+ debugPackage.enable('astro:*,vite:*');
+ debug('cli', '--verbose flag enabled! Enabling: DEBUG="astro:*,vite:*"');
debug(
'cli',
'Tip: Set the DEBUG env variable directly for more control. Example: "DEBUG=astro:*,vite:* astro build".'
diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts
index 758f5e581..135aa5813 100644
--- a/packages/astro/src/core/messages.ts
+++ b/packages/astro/src/core/messages.ts
@@ -1,10 +1,11 @@
import {
- bgCyan,
bgGreen,
bgRed,
bgWhite,
bgYellow,
+ bgCyan,
black,
+ blue,
bold,
cyan,
dim,
@@ -22,36 +23,29 @@ import {
CompilerError,
type ErrorWithMetadata,
} from './errors/index.js';
-import { emoji, padMultilineString } from './util.js';
-
-const PREFIX_PADDING = 6;
+import { padMultilineString } from './util.js';
/** Display */
export function req({
url,
+ method,
statusCode,
reqTime,
}: {
url: string;
statusCode: number;
+ method?: string;
reqTime?: number;
}): string {
- let color = dim;
- if (statusCode >= 500) color = red;
- else if (statusCode >= 400) color = yellow;
- else if (statusCode >= 300) color = dim;
- else if (statusCode >= 200) color = green;
- return `${bold(color(`${statusCode}`.padStart(PREFIX_PADDING)))} ${url.padStart(40)} ${
- reqTime ? dim(Math.round(reqTime) + 'ms') : ''
- }`.trim();
-}
-
-export function reload({ file }: { file: string }): string {
- return `${green('reload'.padStart(PREFIX_PADDING))} ${file}`;
-}
-
-export function hmr({ file, style = false }: { file: string; style?: boolean }): string {
- return `${green('update'.padStart(PREFIX_PADDING))} ${file}${style ? ` ${dim('style')}` : ''}`;
+ const color = statusCode >= 400 ? red : statusCode >= 300 ? yellow : blue;
+ return (
+ color(`[${statusCode}]`) +
+ ` ` +
+ (method && method !== 'GET' ? color(method) + ' ' : '') +
+ url +
+ ` ` +
+ (reqTime ? dim(Math.round(reqTime) + 'ms') : '')
+ );
}
/** Display server host and startup time */
@@ -60,13 +54,11 @@ export function serverStart({
resolvedUrls,
host,
base,
- isRestart = false,
}: {
startupTime: number;
resolvedUrls: ResolvedServerUrls;
host: string | boolean;
base: string;
- isRestart?: boolean;
}): string {
// PACKAGE_VERSION is injected at build-time
const version = process.env.PACKAGE_VERSION ?? '0.0.0';
@@ -75,10 +67,10 @@ export function serverStart({
const emptyPrefix = ' '.repeat(11);
const localUrlMessages = resolvedUrls.local.map((url, i) => {
- return `${i === 0 ? localPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
+ return `${i === 0 ? localPrefix : emptyPrefix}${cyan(new URL(url).origin + base)}`;
});
const networkUrlMessages = resolvedUrls.network.map((url, i) => {
- return `${i === 0 ? networkPrefix : emptyPrefix}${bold(cyan(new URL(url).origin + base))}`;
+ return `${i === 0 ? networkPrefix : emptyPrefix}${cyan(new URL(url).origin + base)}`;
});
if (networkUrlMessages.length === 0) {
@@ -91,58 +83,88 @@ export function serverStart({
}
const messages = [
- `${emoji('🚀 ', '')}${bgGreen(black(` astro `))} ${green(`v${version}`)} ${dim(
- `${isRestart ? 're' : ''}started in ${Math.round(startupTime)}ms`
- )}`,
+ '',
+ `${bgGreen(bold(` astro `))} ${green(`v${version}`)} ${dim(`ready in`)} ${Math.round(
+ startupTime
+ )} ${dim('ms')}`,
'',
...localUrlMessages,
...networkUrlMessages,
'',
];
- return messages
- .filter((msg) => typeof msg === 'string')
- .map((msg) => ` ${msg}`)
- .join('\n');
+ return messages.filter((msg) => typeof msg === 'string').join('\n');
}
-export function telemetryNotice(packageManager = 'npm') {
- const headline = `${cyan('◆')} Astro collects completely anonymous usage data.`;
- const why = dim(' This optional program helps shape our roadmap.');
- const disable = dim(` Run \`${packageManager} run astro telemetry disable\` to opt-out.`);
- const details = ` Details: ${underline('https://astro.build/telemetry')}`;
- return [headline, why, disable, details].map((v) => ' ' + v).join('\n');
+export function telemetryNotice() {
+ const headline = blue(`▶ Astro collects anonymous usage data.`);
+ const why = ' This information helps us improve Astro.';
+ const disable = ` Run "astro telemetry disable" to opt-out.`;
+ const details = ` ${cyan(underline('https://astro.build/telemetry'))}`;
+ return [headline, why, disable, details].join('\n');
}
export function telemetryEnabled() {
- return `${green('◉')} Anonymous telemetry is now ${bgGreen(black(' enabled '))}\n ${dim(
- 'Thank you for improving Astro!'
+ return [
+ green('▶ Anonymous telemetry ') + bgGreen(' enabled '),
+ ` Thank you for helping us improve Astro!`,
+ ``,
+ ].join('\n');
+}
+
+export function preferenceEnabled(name: string) {
+ return `${green('◉')} ${name} is now ${bgGreen(black(' enabled '))}\n`;
+}
+
+export function preferenceSet(name: string, value: any) {
+ return `${green('◉')} ${name} has been set to ${bgGreen(black(` ${JSON.stringify(value)} `))}\n`;
+}
+
+export function preferenceGet(name: string, value: any) {
+ return `${green('◉')} ${name} is set to ${bgGreen(black(` ${JSON.stringify(value)} `))}\n`;
+}
+
+export function preferenceDefaultIntro(name: string) {
+ return `${yellow('◯')} ${name} has not been set. It defaults to\n`;
+}
+
+export function preferenceDefault(name: string, value: any) {
+ return `${yellow('◯')} ${name} has not been set. It defaults to ${bgYellow(
+ black(` ${JSON.stringify(value)} `)
)}\n`;
}
+export function preferenceDisabled(name: string) {
+ return `${yellow('◯')} ${name} is now ${bgYellow(black(' disabled '))}\n`;
+}
+
+export function preferenceReset(name: string) {
+ return `${cyan('◆')} ${name} has been ${bgCyan(black(' reset '))}\n`;
+}
+
export function telemetryDisabled() {
- return `${yellow('◯')} Anonymous telemetry is now ${bgYellow(black(' disabled '))}\n ${dim(
- "We won't ever record your usage data."
- )}\n`;
+ return [
+ green('▶ Anonymous telemetry ') + bgGreen(' disabled '),
+ ` Astro is no longer collecting anonymous usage data.`,
+ ``,
+ ].join('\n');
}
export function telemetryReset() {
- return `${cyan('◆')} Anonymous telemetry has been ${bgCyan(black(' reset '))}\n ${dim(
- 'You may be prompted again.'
- )}\n`;
+ return [green('▶ Anonymous telemetry preferences reset.'), ``].join('\n');
}
export function fsStrictWarning() {
- return yellow(
- '⚠️ Serving with vite.server.fs.strict: false. Note that all files on your machine will be accessible to anyone on your network!'
- );
+ const title = yellow('▶ ' + `${bold('vite.server.fs.strict')} has been disabled!`);
+ const subtitle = ` Files on your machine are likely accessible on your network.`;
+ return `${title}\n${subtitle}\n`;
}
export function prerelease({ currentVersion }: { currentVersion: string }) {
- const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '');
+ const tag = currentVersion.split('-').slice(1).join('-').replace(/\..*$/, '') || 'unknown';
const badge = bgYellow(black(` ${tag} `));
- const headline = yellow(`▶ This is a ${badge} prerelease build`);
- const warning = ` Feedback? ${underline('https://astro.build/issues')}`;
- return [headline, warning, ''].map((msg) => ` ${msg}`).join('\n');
+ const title = yellow('▶ ' + `This is a ${badge} prerelease build!`);
+ const subtitle = ` Report issues here: ${cyan(underline('https://astro.build/issues'))}`;
+ return `${title}\n${subtitle}\n`;
}
export function success(message: string, tip?: string) {
@@ -196,58 +218,81 @@ export function formatConfigErrorMessage(err: ZodError) {
)}`;
}
-export function formatErrorMessage(err: ErrorWithMetadata, args: string[] = []): string {
+// a regex to match the first line of a stack trace
+const STACK_LINE_REGEXP = /^\s+at /g;
+const IRRELEVANT_STACK_REGEXP = /(node_modules|astro[\/\\]dist)/g;
+function formatErrorStackTrace(
+ err: Error | ErrorWithMetadata,
+ showFullStacktrace: boolean
+): string {
+ const stackLines = (err.stack || '').split('\n').filter((line) => STACK_LINE_REGEXP.test(line));
+ // If full details are required, just return the entire stack trace.
+ if (showFullStacktrace) {
+ return stackLines.join('\n');
+ }
+ // Grab every string from the user's codebase, exit when you hit node_modules or astro/dist
+ const irrelevantStackIndex = stackLines.findIndex((line) => IRRELEVANT_STACK_REGEXP.test(line));
+ if (irrelevantStackIndex <= 0) {
+ const errorId = (err as ErrorWithMetadata).id;
+ const errorLoc = (err as ErrorWithMetadata).loc;
+ if (errorId || errorLoc?.file) {
+ const prettyLocation = ` at ${errorId ?? errorLoc?.file}${
+ errorLoc?.line && errorLoc.column ? `:${errorLoc.line}:${errorLoc.column}` : ''
+ }`;
+ return (
+ prettyLocation + '\n [...] See full stack trace in the browser, or rerun with --verbose.'
+ );
+ } else {
+ return stackLines.join('\n');
+ }
+ }
+ // If the error occurred inside of a dependency, grab the entire stack.
+ // Otherwise, only grab the part of the stack that is relevant to the user's codebase.
+ return (
+ stackLines.splice(0, irrelevantStackIndex).join('\n') +
+ '\n [...] See full stack trace in the browser, or rerun with --verbose.'
+ );
+}
+
+export function formatErrorMessage(err: ErrorWithMetadata, showFullStacktrace: boolean): string {
const isOurError = AstroError.is(err) || CompilerError.is(err) || AstroUserError.is(err);
+ let message = '';
+ if (isOurError) {
+ message += red(`[${err.name}]`) + ' ' + renderErrorMarkdown(err.message, 'cli');
+ } else {
+ message += err.message;
+ }
+ const output = [message];
- args.push(
- `${bgRed(black(` error `))}${red(
- padMultilineString(isOurError ? renderErrorMarkdown(err.message, 'cli') : err.message)
- )}`
- );
if (err.hint) {
- args.push(` ${bold('Hint:')}`);
- args.push(
- yellow(padMultilineString(isOurError ? renderErrorMarkdown(err.hint, 'cli') : err.hint, 4))
- );
+ output.push(` ${bold('Hint:')}`);
+ output.push(yellow(padMultilineString(renderErrorMarkdown(err.hint, 'cli'), 4)));
}
+
const docsLink = getDocsForError(err);
if (docsLink) {
- args.push(` ${bold('Error reference:')}`);
- args.push(` ${underline(docsLink)}`);
+ output.push(` ${bold('Error reference:')}`);
+ output.push(` ${cyan(underline(docsLink))}`);
}
- if (err.id || err.loc?.file) {
- args.push(` ${bold('File:')}`);
- args.push(
- red(
- ` ${err.id ?? err.loc?.file}${
- err.loc?.line && err.loc.column ? `:${err.loc.line}:${err.loc.column}` : ''
- }`
- )
- );
- }
- if (err.frame) {
- args.push(` ${bold('Code:')}`);
- args.push(red(padMultilineString(err.frame.trim(), 4)));
- }
- if (args.length === 1 && err.stack) {
- args.push(dim(err.stack));
- } else if (err.stack) {
- args.push(` ${bold('Stacktrace:')}`);
- args.push(dim(err.stack));
- args.push(``);
+
+ if (err.stack) {
+ output.push(` ${bold('Stack trace:')}`);
+ output.push(dim(formatErrorStackTrace(err, showFullStacktrace)));
}
if (err.cause) {
- args.push(` ${bold('Cause:')}`);
+ output.push(` ${bold('Caused by:')}`);
+ let causeMessage = ' ';
if (err.cause instanceof Error) {
- args.push(dim(err.cause.stack ?? err.cause.toString()));
+ causeMessage +=
+ err.cause.message + '\n' + formatErrorStackTrace(err.cause, showFullStacktrace);
} else {
- args.push(JSON.stringify(err.cause));
+ causeMessage += JSON.stringify(err.cause);
}
-
- args.push(``);
+ output.push(dim(causeMessage));
}
- return args.join('\n');
+
+ return output.join('\n');
}
export function printHelp({
diff --git a/packages/astro/src/core/middleware/callMiddleware.ts b/packages/astro/src/core/middleware/callMiddleware.ts
index 40513c152..4d79cd566 100644
--- a/packages/astro/src/core/middleware/callMiddleware.ts
+++ b/packages/astro/src/core/middleware/callMiddleware.ts
@@ -1,13 +1,6 @@
-import { bold } from 'kleur/colors';
-import type {
- APIContext,
- EndpointOutput,
- MiddlewareHandler,
- MiddlewareNext,
-} from '../../@types/astro.js';
+import type { APIContext, MiddlewareHandler, MiddlewareNext } from '../../@types/astro.js';
import { attachCookiesToResponse, responseHasCookies } from '../cookies/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
-import type { Environment } from '../render/index.js';
/**
* Utility function that is in charge of calling the middleware.
@@ -43,15 +36,14 @@ import type { Environment } from '../render/index.js';
* @param apiContext The API context
* @param responseFunction A callback function that should return a promise with the response
*/
-export async function callMiddleware<R>(
- logger: Environment['logger'],
- onRequest: MiddlewareHandler<R>,
+export async function callMiddleware(
+ onRequest: MiddlewareHandler,
apiContext: APIContext,
- responseFunction: () => Promise<R>
-): Promise<Response | R> {
+ responseFunction: () => Promise<Response>
+): Promise<Response> {
let nextCalled = false;
- let responseFunctionPromise: Promise<R> | undefined = undefined;
- const next: MiddlewareNext<R> = async () => {
+ let responseFunctionPromise: Promise<Response> | undefined = undefined;
+ const next: MiddlewareNext = async () => {
nextCalled = true;
responseFunctionPromise = responseFunction();
return responseFunctionPromise;
@@ -60,14 +52,6 @@ export async function callMiddleware<R>(
let middlewarePromise = onRequest(apiContext, next);
return await Promise.resolve(middlewarePromise).then(async (value) => {
- if (isEndpointOutput(value)) {
- logger.warn(
- 'middleware',
- 'Using simple endpoints can cause unexpected issues in the chain of middleware functions.' +
- `\nIt's strongly suggested to use full ${bold('Response')} objects.`
- );
- }
-
// first we check if `next` was called
if (nextCalled) {
/**
@@ -83,7 +67,7 @@ export async function callMiddleware<R>(
if (value instanceof Response === false) {
throw new AstroError(AstroErrorData.MiddlewareNotAResponse);
}
- return ensureCookiesAttached(apiContext, value as Response);
+ return ensureCookiesAttached(apiContext, value);
} else {
/**
* Here we handle the case where `next` was called and returned nothing.
@@ -106,7 +90,7 @@ export async function callMiddleware<R>(
throw new AstroError(AstroErrorData.MiddlewareNotAResponse);
} else {
// Middleware did not call resolve and returned a value
- return ensureCookiesAttached(apiContext, value as Response);
+ return ensureCookiesAttached(apiContext, value);
}
});
}
@@ -117,11 +101,3 @@ function ensureCookiesAttached(apiContext: APIContext, response: Response): Resp
}
return response;
}
-
-function isEndpointOutput(endpointResult: any): endpointResult is EndpointOutput {
- return (
- !(endpointResult instanceof Response) &&
- typeof endpointResult === 'object' &&
- typeof endpointResult.body === 'string'
- );
-}
diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts
index c02761351..ffaafb3e5 100644
--- a/packages/astro/src/core/middleware/index.ts
+++ b/packages/astro/src/core/middleware/index.ts
@@ -1,8 +1,8 @@
-import type { MiddlewareEndpointHandler, Params } from '../../@types/astro.js';
+import type { MiddlewareHandler, Params } from '../../@types/astro.js';
import { createAPIContext } from '../endpoint/index.js';
import { sequence } from './sequence.js';
-function defineMiddleware(fn: MiddlewareEndpointHandler) {
+function defineMiddleware(fn: MiddlewareHandler) {
return fn;
}
diff --git a/packages/astro/src/core/middleware/namespace.ts b/packages/astro/src/core/middleware/namespace.ts
deleted file mode 100644
index 55a84f666..000000000
--- a/packages/astro/src/core/middleware/namespace.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { defineMiddleware, sequence } from './index.js';
diff --git a/packages/astro/src/core/middleware/sequence.ts b/packages/astro/src/core/middleware/sequence.ts
index d20641ee3..9a6896394 100644
--- a/packages/astro/src/core/middleware/sequence.ts
+++ b/packages/astro/src/core/middleware/sequence.ts
@@ -1,4 +1,4 @@
-import type { APIContext, MiddlewareEndpointHandler } from '../../@types/astro.js';
+import type { APIContext, MiddlewareHandler } from '../../@types/astro.js';
import { defineMiddleware } from './index.js';
// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
@@ -6,11 +6,11 @@ import { defineMiddleware } from './index.js';
*
* It accepts one or more middleware handlers and makes sure that they are run in sequence.
*/
-export function sequence(...handlers: MiddlewareEndpointHandler[]): MiddlewareEndpointHandler {
+export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
const filtered = handlers.filter((h) => !!h);
const length = filtered.length;
if (!length) {
- const handler: MiddlewareEndpointHandler = defineMiddleware((context, next) => {
+ const handler: MiddlewareHandler = defineMiddleware((context, next) => {
return next();
});
return handler;
diff --git a/packages/astro/src/core/pipeline.ts b/packages/astro/src/core/pipeline.ts
index c748bc99d..67081a47e 100644
--- a/packages/astro/src/core/pipeline.ts
+++ b/packages/astro/src/core/pipeline.ts
@@ -1,10 +1,4 @@
-import type {
- ComponentInstance,
- EndpointHandler,
- MiddlewareEndpointHandler,
- MiddlewareHandler,
- MiddlewareResponseHandler,
-} from '../@types/astro.js';
+import type { ComponentInstance, EndpointHandler, MiddlewareHandler } from '../@types/astro.js';
import { callEndpoint, createAPIContext } from './endpoint/index.js';
import { callMiddleware } from './middleware/callMiddleware.js';
import { renderPage } from './render/core.js';
@@ -28,7 +22,7 @@ export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance |
*/
export class Pipeline {
env: Environment;
- #onRequest?: MiddlewareEndpointHandler;
+ #onRequest?: MiddlewareHandler;
#hooks: PipelineHooks = {
before: [],
};
@@ -60,7 +54,7 @@ export class Pipeline {
/**
* A middleware function that will be called before each request.
*/
- setMiddlewareFunction(onRequest: MiddlewareEndpointHandler) {
+ setMiddlewareFunction(onRequest: MiddlewareHandler) {
this.#onRequest = onRequest;
}
@@ -115,11 +109,11 @@ export class Pipeline {
*
* It throws an error if the page can't be rendered.
*/
- async #tryRenderRoute<MiddlewareReturnType = Response>(
+ async #tryRenderRoute(
renderContext: Readonly<RenderContext>,
env: Readonly<Environment>,
mod: Readonly<ComponentInstance> | undefined,
- onRequest?: MiddlewareHandler<MiddlewareReturnType>
+ onRequest?: MiddlewareHandler
): Promise<Response> {
const apiContext = createAPIContext({
request: renderContext.request,
@@ -137,19 +131,14 @@ export class Pipeline {
case 'fallback':
case 'redirect': {
if (onRequest) {
- return await callMiddleware<Response>(
- env.logger,
- onRequest as MiddlewareResponseHandler,
- apiContext,
- () => {
- return renderPage({
- mod,
- renderContext,
- env,
- cookies: apiContext.cookies,
- });
- }
- );
+ return await callMiddleware(onRequest, apiContext, () => {
+ return renderPage({
+ mod,
+ renderContext,
+ env,
+ cookies: apiContext.cookies,
+ });
+ });
} else {
return await renderPage({
mod,
diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts
index 937ba1c99..ba692611e 100644
--- a/packages/astro/src/core/preview/static-preview-server.ts
+++ b/packages/astro/src/core/preview/static-preview-server.ts
@@ -42,7 +42,7 @@ export default async function createStaticPreviewServer(
});
} catch (err) {
if (err instanceof Error) {
- logger.error('astro', err.stack || err.message);
+ logger.error(null, err.stack || err.message);
}
throw err;
}
@@ -51,7 +51,7 @@ export default async function createStaticPreviewServer(
// Log server start URLs
logger.info(
- null,
+ 'SKIP_FORMAT',
msg.serverStart({
startupTime: performance.now() - startServerTime,
resolvedUrls: previewServer.resolvedUrls ?? { local: [], network: [] },
@@ -72,8 +72,6 @@ export default async function createStaticPreviewServer(
host: getResolvedHostForHttpServer(settings.config.server.host),
port: settings.config.server.port,
closed,
- // In Vite 5, `httpServer` may be a `Http2SecureServer`, but we know we are only starting a HTTP server
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
server: previewServer.httpServer as http.Server,
stop: async () => {
await new Promise((resolve, reject) => {
diff --git a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts
index 7f9979275..aafd69cb4 100644
--- a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts
+++ b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts
@@ -1,13 +1,13 @@
import fs from 'node:fs';
+import type { IncomingMessage, ServerResponse } from 'node:http';
import { fileURLToPath } from 'node:url';
import type { Connect, Plugin } from 'vite';
-import { version } from 'vite';
import type { AstroSettings } from '../../@types/astro.js';
import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js';
+import { cleanUrl } from '../../vite-plugin-utils/index.js';
import { stripBase } from './util.js';
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
-const IS_VITE_5 = version.startsWith('5.');
export function vitePluginAstroPreview(settings: AstroSettings): Plugin {
const { base, outDir, trailingSlash } = settings.config;
@@ -24,8 +24,7 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin {
return;
}
- const strippedPathname = stripBase(req.url!, base);
- const pathname = new URL(strippedPathname, 'https://a.b').pathname;
+ const pathname = cleanUrl(stripBase(req.url!, base));
const isRoot = pathname === '/';
// Validate trailingSlash
@@ -53,29 +52,49 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin {
});
return () => {
- const fourOhFourMiddleware: Connect.NextHandleFunction = (req, res) => {
- const errorPagePath = fileURLToPath(outDir + '/404.html');
- if (fs.existsSync(errorPagePath)) {
- res.statusCode = 404;
- res.setHeader('Content-Type', 'text/html;charset=utf-8');
- res.end(fs.readFileSync(errorPagePath));
- } else {
- const pathname = stripBase(req.url!, base);
- res.statusCode = 404;
- res.end(notFoundTemplate(pathname, 'Not Found'));
- }
- };
+ // NOTE: the `base` is stripped from `req.url` for post middlewares
- // Vite 5 has its own 404 middleware, we replace it with ours instead.
- if (IS_VITE_5) {
- for (const middleware of server.middlewares.stack) {
- // This hardcoded name will not break between Vite versions
- if ((middleware.handle as Connect.HandleFunction).name === 'vite404Middleware') {
- middleware.handle = fourOhFourMiddleware;
+ server.middlewares.use((req, res, next) => {
+ const pathname = cleanUrl(req.url!);
+
+ // Vite doesn't handle /foo/ if /foo.html exists, we handle it anyways
+ if (pathname.endsWith('/')) {
+ const pathnameWithoutSlash = pathname.slice(0, -1);
+ const htmlPath = fileURLToPath(outDir + pathnameWithoutSlash + '.html');
+ if (fs.existsSync(htmlPath)) {
+ req.url = pathnameWithoutSlash + '.html';
+ return next();
}
}
- } else {
- server.middlewares.use(fourOhFourMiddleware);
+ // Vite doesn't handle /foo if /foo/index.html exists, we handle it anyways
+ else {
+ const htmlPath = fileURLToPath(outDir + pathname + '/index.html');
+ if (fs.existsSync(htmlPath)) {
+ req.url = pathname + '/index.html';
+ return next();
+ }
+ }
+
+ next();
+ });
+
+ // Vite has its own 404 middleware, we replace it with ours instead.
+ for (const middleware of server.middlewares.stack) {
+ // This hardcoded name will not break between Vite versions
+ if ((middleware.handle as Connect.HandleFunction).name === 'vite404Middleware') {
+ // Fallback to 404 page if it exists
+ middleware.handle = (req: IncomingMessage, res: ServerResponse) => {
+ const errorPagePath = fileURLToPath(outDir + '/404.html');
+ if (fs.existsSync(errorPagePath)) {
+ res.statusCode = 404;
+ res.setHeader('Content-Type', 'text/html;charset=utf-8');
+ res.end(fs.readFileSync(errorPagePath));
+ } else {
+ res.statusCode = 404;
+ res.end(notFoundTemplate(req.url!, 'Not Found'));
+ }
+ };
+ }
}
};
},
diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts
index 270bda062..1175f55d7 100644
--- a/packages/astro/src/core/render/core.ts
+++ b/packages/astro/src/core/render/core.ts
@@ -64,14 +64,6 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag
routingStrategy: renderContext.routing,
});
- // TODO: Remove in Astro 4.0
- if (mod.frontmatter && typeof mod.frontmatter === 'object' && 'draft' in mod.frontmatter) {
- env.logger.warn(
- 'astro',
- `The drafts feature is deprecated and used in ${renderContext.route.component}. You should migrate to content collections instead. See https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries for more information.`
- );
- }
-
const response = await runtimeRenderPage(
result,
Component,
diff --git a/packages/astro/src/core/render/index.ts b/packages/astro/src/core/render/index.ts
index 6d466c411..5f3a702a3 100644
--- a/packages/astro/src/core/render/index.ts
+++ b/packages/astro/src/core/render/index.ts
@@ -24,5 +24,5 @@ export interface SSROptions {
/**
* Optional middlewares
*/
- middleware?: AstroMiddlewareInstance<unknown>;
+ middleware?: AstroMiddlewareInstance;
}
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index e9c8302a1..2c37f38c4 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -100,7 +100,7 @@ class Slots {
const result = this.#result;
if (!Array.isArray(args)) {
this.#logger.warn(
- 'Astro.slots.render',
+ null,
`Expected second parameter to be an array, received a ${typeof args}. If you're trying to pass an array as a single argument and getting unexpected results, make sure you're passing your array as a item of an array. Ex: Astro.slots.render('default', [["Hello", "World"]])`
);
} else if (args.length > 0) {
diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts
index c318b8c44..8aca230f4 100644
--- a/packages/astro/src/core/render/route-cache.ts
+++ b/packages/astro/src/core/render/route-cache.ts
@@ -107,10 +107,7 @@ export class RouteCache {
// Warn here so that an unexpected double-call of getStaticPaths()
// isn't invisible and developer can track down the issue.
if (this.mode === 'production' && this.cache[route.component]?.staticPaths) {
- this.logger.warn(
- 'routeCache',
- `Internal Warning: route cache overwritten. (${route.component})`
- );
+ this.logger.warn(null, `Internal Warning: route cache overwritten. (${route.component})`);
}
this.cache[route.component] = entry;
}
@@ -131,5 +128,5 @@ export function findPathItemByKey(
if (matchedStaticPath) {
return matchedStaticPath;
}
- logger.debug('findPathItemByKey', `Unexpected cache miss looking for ${paramsKey}`);
+ logger.debug('router', `findPathItemByKey() - Unexpected cache miss looking for ${paramsKey}`);
}
diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts
index f478b0a32..6d55caa15 100644
--- a/packages/astro/src/core/request.ts
+++ b/packages/astro/src/core/request.ts
@@ -42,7 +42,7 @@ export function createRequest({
Object.defineProperties(request, {
params: {
get() {
- logger.warn('deprecation', `Astro.request.params has been moved to Astro.params`);
+ logger.warn('deprecated', `Astro.request.params has been moved to Astro.params`);
return undefined;
},
},
@@ -56,8 +56,8 @@ export function createRequest({
...headersDesc,
get() {
logger.warn(
- 'ssg',
- `Headers are not exposed in static (SSG) output mode. To enable headers: set \`output: "server"\` in your config file.`
+ null,
+ `\`Astro.request.headers\` is not available in "static" output mode. To enable header access: set \`output: "server"\` or \`output: "hybrid"\` in your config file.`
);
return _headers;
},
diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts
index 2fe9fd9ee..c9a49cbbe 100644
--- a/packages/astro/src/core/routing/manifest/create.ts
+++ b/packages/astro/src/core/routing/manifest/create.ts
@@ -8,6 +8,7 @@ import type {
} from '../../../@types/astro.js';
import type { Logger } from '../../logger/core.js';
+import { bold } from 'kleur/colors';
import { createRequire } from 'module';
import nodeFs from 'node:fs';
import path from 'node:path';
@@ -183,13 +184,13 @@ function comparator(a: Item, b: Item) {
function injectedRouteToItem(
{ config, cwd }: { config: AstroConfig; cwd?: string },
- { pattern, entryPoint }: InjectedRoute
+ { pattern, entrypoint }: InjectedRoute
): Item {
let resolved: string;
try {
- resolved = require.resolve(entryPoint, { paths: [cwd || fileURLToPath(config.root)] });
+ resolved = require.resolve(entrypoint, { paths: [cwd || fileURLToPath(config.root)] });
} catch (e) {
- resolved = fileURLToPath(new URL(entryPoint, config.root));
+ resolved = fileURLToPath(new URL(entrypoint, config.root));
}
const ext = path.extname(pattern);
@@ -234,8 +235,6 @@ export function createRouteManifest(
const localFs = fsMod ?? nodeFs;
const prerender = getPrerenderDefault(settings.config);
- const foundInvalidFileExtensions = new Set<string>();
-
function walk(
fs: typeof nodeFs,
dir: string,
@@ -259,10 +258,12 @@ export function createRouteManifest(
}
// filter out "foo.astro_tmp" files, etc
if (!isDir && !validPageExtensions.has(ext) && !validEndpointExtensions.has(ext)) {
- if (!foundInvalidFileExtensions.has(ext)) {
- foundInvalidFileExtensions.add(ext);
- logger.warn('astro', `Invalid file extension for Pages: ${ext}`);
- }
+ logger.warn(
+ null,
+ `Unsupported file type ${bold(
+ resolved
+ )} found. Prefix filename with an underscore (\`_\`) to ignore.`
+ );
return;
}
@@ -359,8 +360,7 @@ export function createRouteManifest(
walk(localFs, fileURLToPath(pages), [], []);
} else if (settings.injectedRoutes.length === 0) {
const pagesDirRootRelative = pages.href.slice(settings.config.root.href.length);
-
- logger.warn('astro', `Missing pages directory: ${pagesDirRootRelative}`);
+ logger.warn(null, `Missing pages directory: ${pagesDirRootRelative}`);
}
settings.injectedRoutes
@@ -369,12 +369,12 @@ export function createRouteManifest(
comparator(injectedRouteToItem({ config, cwd }, a), injectedRouteToItem({ config, cwd }, b))
)
.reverse() // prepend to the routes array from lowest to highest priority
- .forEach(({ pattern: name, entryPoint, prerender: prerenderInjected }) => {
+ .forEach(({ pattern: name, entrypoint, prerender: prerenderInjected }) => {
let resolved: string;
try {
- resolved = require.resolve(entryPoint, { paths: [cwd || fileURLToPath(config.root)] });
+ resolved = require.resolve(entrypoint, { paths: [cwd || fileURLToPath(config.root)] });
} catch (e) {
- resolved = fileURLToPath(new URL(entryPoint, config.root));
+ resolved = fileURLToPath(new URL(entrypoint, config.root));
}
const component = slash(path.relative(cwd || fileURLToPath(config.root), resolved));
diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts
index 0261c865a..b68d5f3e8 100644
--- a/packages/astro/src/core/routing/validation.ts
+++ b/packages/astro/src/core/routing/validation.ts
@@ -79,16 +79,16 @@ export function validateGetStaticPathsResult(
for (const [key, val] of Object.entries(pathObject.params)) {
if (!(typeof val === 'undefined' || typeof val === 'string' || typeof val === 'number')) {
logger.warn(
- 'getStaticPaths',
- `invalid path param: ${key}. A string, number or undefined value was expected, but got \`${JSON.stringify(
+ 'router',
+ `getStaticPaths() returned an invalid path param: "${key}". A string, number or undefined value was expected, but got \`${JSON.stringify(
val
)}\`.`
);
}
if (typeof val === 'string' && val === '') {
logger.warn(
- 'getStaticPaths',
- `invalid path param: ${key}. \`undefined\` expected for an optional param, but got empty string.`
+ 'router',
+ `getStaticPaths() returned an invalid path param: "${key}". \`undefined\` expected for an optional param, but got empty string.`
);
}
}
diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts
index 0c7b81c3a..966454845 100644
--- a/packages/astro/src/core/sync/index.ts
+++ b/packages/astro/src/core/sync/index.ts
@@ -117,7 +117,7 @@ export async function syncInternal(
switch (typesResult.reason) {
case 'no-content-dir':
default:
- logger.info('content', 'No content directory found. Skipping type generation.');
+ logger.debug('types', 'No content directory found. Skipping type generation.');
return 0;
}
}
@@ -137,7 +137,7 @@ export async function syncInternal(
await tempViteServer.close();
}
- logger.info('content', `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
+ logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
await setUpEnvTs({ settings, logger, fs: fs ?? fsMod });
return 0;
diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts
index e0269ace0..efd9f1e54 100644
--- a/packages/astro/src/i18n/middleware.ts
+++ b/packages/astro/src/i18n/middleware.ts
@@ -1,5 +1,5 @@
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';
-import type { MiddlewareEndpointHandler, RouteData, SSRManifest } from '../@types/astro.js';
+import type { MiddlewareHandler, RouteData, SSRManifest } from '../@types/astro.js';
import type { PipelineHookFunction } from '../core/pipeline.js';
const routeDataSymbol = Symbol.for('astro.routeData');
@@ -19,7 +19,7 @@ export function createI18nMiddleware(
i18n: SSRManifest['i18n'],
base: SSRManifest['base'],
trailingSlash: SSRManifest['trailingSlash']
-): MiddlewareEndpointHandler | undefined {
+): MiddlewareHandler | undefined {
if (!i18n) {
return undefined;
}
diff --git a/packages/astro/src/i18n/vite-plugin-i18n.ts b/packages/astro/src/i18n/vite-plugin-i18n.ts
index 4aa6ee42e..faba67da3 100644
--- a/packages/astro/src/i18n/vite-plugin-i18n.ts
+++ b/packages/astro/src/i18n/vite-plugin-i18n.ts
@@ -28,7 +28,7 @@ export default function astroInternationalization({
getLocaleAbsoluteUrl as _getLocaleAbsoluteUrl,
getLocaleAbsoluteUrlList as _getLocaleAbsoluteUrlList,
- } from "astro/i18n";
+ } from "astro/virtual-modules/i18n.js";
const base = ${JSON.stringify(settings.config.base)};
const trailingSlash = ${JSON.stringify(settings.config.trailingSlash)};
diff --git a/packages/astro/src/integrations/astroFeaturesValidation.ts b/packages/astro/src/integrations/astroFeaturesValidation.ts
index a26f42afb..4c6b1131b 100644
--- a/packages/astro/src/integrations/astroFeaturesValidation.ts
+++ b/packages/astro/src/integrations/astroFeaturesValidation.ts
@@ -17,17 +17,6 @@ const UNSUPPORTED_ASSETS_FEATURE: AstroAssetsFeature = {
isSharpCompatible: false,
};
-// NOTE: remove for Astro 4.0
-const ALL_UNSUPPORTED: Required<AstroFeatureMap> = {
- serverOutput: UNSUPPORTED,
- staticOutput: UNSUPPORTED,
- hybridOutput: UNSUPPORTED,
- assets: UNSUPPORTED_ASSETS_FEATURE,
- i18n: {
- detectBrowserLanguage: UNSUPPORTED,
- },
-};
-
type ValidationResult = {
[Property in keyof AstroFeatureMap]: boolean;
};
@@ -41,7 +30,7 @@ type ValidationResult = {
*/
export function validateSupportedFeatures(
adapterName: string,
- featureMap: AstroFeatureMap = ALL_UNSUPPORTED,
+ featureMap: AstroFeatureMap,
config: AstroConfig,
logger: Logger
): ValidationResult {
@@ -105,18 +94,21 @@ function validateSupportKind(
}
function featureIsUnsupported(adapterName: string, logger: Logger, featureName: string) {
- logger.error(
- `${adapterName}`,
- `The feature ${featureName} is not supported by the adapter ${adapterName}.`
- );
+ logger.error('config', `The feature ${featureName} is not supported (used by ${adapterName}).`);
}
function featureIsExperimental(adapterName: string, logger: Logger) {
- logger.warn(`${adapterName}`, 'The feature is experimental and subject to issues or changes.');
+ logger.warn(
+ 'config',
+ `The feature is experimental and subject to change (used by ${adapterName}).`
+ );
}
function featureIsDeprecated(adapterName: string, logger: Logger) {
- logger.warn(`${adapterName}`, 'The feature is deprecated and will be moved in the next release.');
+ logger.warn(
+ 'config',
+ `The feature is deprecated and will be removed in the future (used by ${adapterName}).`
+ );
}
const SHARP_SERVICE = 'astro/assets/services/sharp';
@@ -135,7 +127,7 @@ function validateAssetsFeature(
} = assets;
if (config?.image?.service?.entrypoint === SHARP_SERVICE && !isSharpCompatible) {
logger.warn(
- 'astro',
+ null,
`The currently selected adapter \`${adapterName}\` is not compatible with the image service "Sharp".`
);
return false;
@@ -143,7 +135,7 @@ function validateAssetsFeature(
if (config?.image?.service?.entrypoint === SQUOOSH_SERVICE && !isSquooshCompatible) {
logger.warn(
- 'astro',
+ null,
`The currently selected adapter \`${adapterName}\` is not compatible with the image service "Squoosh".`
);
return false;
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index ca2b89b30..18dfa5354 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -1,4 +1,4 @@
-import { bold } from 'kleur/colors';
+import { bold, cyan, underline } from 'kleur/colors';
import fs from 'node:fs';
import type { AddressInfo } from 'node:net';
import { fileURLToPath } from 'node:url';
@@ -24,17 +24,24 @@ import { validateSupportedFeatures } from './astroFeaturesValidation.js';
async function withTakingALongTimeMsg<T>({
name,
+ hookName,
hookResult,
timeoutMs = 3000,
logger,
}: {
name: string;
+ hookName: string;
hookResult: T | Promise<T>;
timeoutMs?: number;
logger: Logger;
}): Promise<T> {
const timeout = setTimeout(() => {
- logger.info('build', `Waiting for the ${bold(name)} integration...`);
+ logger.info(
+ 'build',
+ `Waiting for integration ${bold(JSON.stringify(name))}, hook ${bold(
+ JSON.stringify(hookName)
+ )}...`
+ );
}, timeoutMs);
const result = await hookResult;
clearTimeout(timeout);
@@ -121,6 +128,13 @@ export async function runHookConfigSetup({
return { ...updatedConfig };
},
injectRoute: (injectRoute) => {
+ if (injectRoute.entrypoint == null && 'entryPoint' in injectRoute) {
+ logger.warn(
+ null,
+ `The injected route "${injectRoute.pattern}" by ${integration.name} specifies the entry point with the "entryPoint" property. This property is deprecated, please use "entrypoint" instead.`
+ );
+ injectRoute.entrypoint = injectRoute.entryPoint as string;
+ }
updatedSettings.injectedRoutes.push(injectRoute);
},
addWatchFile: (path) => {
@@ -189,6 +203,7 @@ export async function runHookConfigSetup({
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:config:setup',
hookResult: integration.hooks['astro:config:setup'](hooks),
logger,
});
@@ -220,6 +235,7 @@ export async function runHookConfigDone({
if (integration?.hooks?.['astro:config:done']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:config:done',
hookResult: integration.hooks['astro:config:done']({
config: settings.config,
setAdapter(adapter) {
@@ -229,10 +245,8 @@ export async function runHookConfigDone({
);
}
if (!adapter.supportedAstroFeatures) {
- // NOTE: throw an error in Astro 4.0
- logger.warn(
- 'astro',
- `The adapter ${adapter.name} doesn't provide a feature map. From Astro 3.0, an adapter can provide a feature map. Not providing a feature map will cause an error in Astro 4.0.`
+ throw new Error(
+ `The adapter ${adapter.name} doesn't provide a feature map. It is required in Astro 4.0.`
);
} else {
const validationResult = validateSupportedFeatures(
@@ -248,7 +262,7 @@ export async function runHookConfigDone({
// if we would refactor the validation to support more than boolean, we could still be able to differentiate between the two cases
if (!supported && featureName !== 'assets') {
logger.error(
- 'astro',
+ null,
`The adapter ${adapter.name} doesn't support the feature ${featureName}. Your project won't be built. You should not use it.`
);
}
@@ -277,6 +291,7 @@ export async function runHookServerSetup({
if (integration?.hooks?.['astro:server:setup']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:setup',
hookResult: integration.hooks['astro:server:setup']({
server,
logger: getLogger(integration, logger),
@@ -300,6 +315,7 @@ export async function runHookServerStart({
if (integration?.hooks?.['astro:server:start']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:start',
hookResult: integration.hooks['astro:server:start']({
address,
logger: getLogger(integration, logger),
@@ -321,6 +337,7 @@ export async function runHookServerDone({
if (integration?.hooks?.['astro:server:done']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:server:done',
hookResult: integration.hooks['astro:server:done']({
logger: getLogger(integration, logger),
}),
@@ -343,6 +360,7 @@ export async function runHookBuildStart({
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:start',
hookResult: integration.hooks['astro:build:start']({ logger }),
logger: logging,
});
@@ -369,6 +387,7 @@ export async function runHookBuildSetup({
if (integration?.hooks?.['astro:build:setup']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:setup',
hookResult: integration.hooks['astro:build:setup']({
vite,
pages,
@@ -406,6 +425,7 @@ export async function runHookBuildSsr({
if (integration?.hooks?.['astro:build:ssr']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:ssr',
hookResult: integration.hooks['astro:build:ssr']({
manifest,
entryPoints,
@@ -431,6 +451,7 @@ export async function runHookBuildGenerated({
if (integration?.hooks?.['astro:build:generated']) {
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:generated',
hookResult: integration.hooks['astro:build:generated']({
dir,
logger: getLogger(integration, logger),
@@ -458,6 +479,7 @@ export async function runHookBuildDone({ config, pages, routes, logging }: RunHo
await withTakingALongTimeMsg({
name: integration.name,
+ hookName: 'astro:build:done',
hookResult: integration.hooks['astro:build:done']({
pages: pages.map((p) => ({ pathname: p })),
dir,
diff --git a/packages/astro/src/preferences/README.md b/packages/astro/src/preferences/README.md
new file mode 100644
index 000000000..4234ebac1
--- /dev/null
+++ b/packages/astro/src/preferences/README.md
@@ -0,0 +1,33 @@
+# Preferences
+
+The preferences module implements global and local user preferences for controlling certain Astro behavior. Whereas the `astro.config.mjs` file controls project-specific behavior for every user of a project, preferences are user-specific.
+
+The design of Preferences is inspired by [Git](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration) and [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/settings). Both systems implement similar layering approaches with project-specific and global settings.
+
+## `AstroPreferences`
+
+The `AstroPreferences` interface exposes both a `get` and `set` function.
+
+### Reading a preference
+
+`preferences.get("dot.separated.value")` will read a preference value from multiple sources if needed. Local project preferences are read from `.astro/settings.json`, if it exists. Next, global user preferences are read from `<homedir>/<os-specific-preferences-dir>/astro/settings.json`. If neither of those are found, the default preferences defined in [`./defaults.ts`](./defaults.ts) will apply.
+
+In order to read a preference from a specific location, you can pass the `location: "global" | "project"` option.
+
+```js
+await preferences.get('dot.separated.value', { location: 'global' });
+```
+
+### Writing a preference
+
+`preferences.set("dot.separated.value", true)` will store a preference value. By default, preferences are stored locally in a project.
+
+In order to set a global user preference, you can pass the `location: "global"` option.
+
+```js
+await preferences.set('dot.separated.value', 'value', { location: 'global' });
+```
+
+## Relation to Telemetry
+
+This module evolved from the existing `@astrojs/telemetry` package, but has been generalized for user-facing `astro` preferences. At some point, we'll need to merge the logic in `@astrojs/telemetry` and the logic in this module so that all preferences are stored in the same location.
diff --git a/packages/astro/src/preferences/defaults.ts b/packages/astro/src/preferences/defaults.ts
new file mode 100644
index 000000000..db9cfde87
--- /dev/null
+++ b/packages/astro/src/preferences/defaults.ts
@@ -0,0 +1,8 @@
+export const DEFAULT_PREFERENCES = {
+ devOverlay: {
+ /** Specifies whether the user has the Dev Overlay enabled */
+ enabled: true,
+ },
+};
+
+export type Preferences = typeof DEFAULT_PREFERENCES;
diff --git a/packages/astro/src/preferences/index.ts b/packages/astro/src/preferences/index.ts
new file mode 100644
index 000000000..91b942101
--- /dev/null
+++ b/packages/astro/src/preferences/index.ts
@@ -0,0 +1,104 @@
+import type { AstroConfig } from '../@types/astro.js';
+
+import { fileURLToPath } from 'node:url';
+import os from 'node:os';
+import process from 'node:process';
+import path from 'node:path';
+
+import dget from 'dlv';
+import { DEFAULT_PREFERENCES, type Preferences } from './defaults.js';
+import { PreferenceStore } from './store.js';
+
+type DotKeys<T> = T extends object
+ ? {
+ [K in keyof T]: `${Exclude<K, symbol>}${DotKeys<T[K]> extends never
+ ? ''
+ : `.${DotKeys<T[K]>}`}`;
+ }[keyof T]
+ : never;
+
+export type GetDotKey<
+ T extends Record<string | number, any>,
+ K extends string,
+> = K extends `${infer U}.${infer Rest}` ? GetDotKey<T[U], Rest> : T[K];
+
+export interface PreferenceOptions {
+ location?: 'global' | 'project';
+}
+
+export type PreferenceKey = DotKeys<Preferences>;
+
+export interface AstroPreferences {
+ get<Key extends PreferenceKey>(
+ key: Key,
+ opts?: PreferenceOptions
+ ): Promise<GetDotKey<Preferences, Key>>;
+ set<Key extends PreferenceKey>(
+ key: Key,
+ value: GetDotKey<Preferences, Key>,
+ opts?: PreferenceOptions
+ ): Promise<void>;
+ getAll(opts?: PreferenceOptions): Promise<Record<string, any>>;
+}
+
+export function isValidKey(key: string): key is PreferenceKey {
+ return dget(DEFAULT_PREFERENCES, key) !== undefined;
+}
+export function coerce(key: string, value: unknown) {
+ const type = typeof dget(DEFAULT_PREFERENCES, key);
+ switch (type) {
+ case 'string':
+ return value;
+ case 'number':
+ return Number(value);
+ case 'boolean': {
+ if (value === 'true' || value === 1) return true;
+ if (value === 'false' || value === 0) return false;
+ }
+ }
+ return value as any;
+}
+
+export default function createPreferences(config: AstroConfig): AstroPreferences {
+ const global = new PreferenceStore(getGlobalPreferenceDir());
+ const project = new PreferenceStore(fileURLToPath(new URL('./.astro/', config.root)));
+ const stores = { global, project };
+
+ return {
+ async get(key, { location } = {}) {
+ if (!location) return project.get(key) ?? global.get(key) ?? dget(DEFAULT_PREFERENCES, key);
+ return stores[location].get(key);
+ },
+ async set(key, value, { location = 'project' } = {}) {
+ stores[location].set(key, value);
+ },
+ async getAll({ location } = {}) {
+ if (!location)
+ return Object.assign({}, stores['global'].getAll(), stores['project'].getAll());
+ return stores[location].getAll();
+ },
+ };
+}
+
+// Adapted from https://github.com/sindresorhus/env-paths
+export function getGlobalPreferenceDir() {
+ const name = 'astro';
+ const homedir = os.homedir();
+ const macos = () => path.join(homedir, 'Library', 'Preferences', name);
+ const win = () => {
+ const { APPDATA = path.join(homedir, 'AppData', 'Roaming') } = process.env;
+ return path.join(APPDATA, name, 'Config');
+ };
+ const linux = () => {
+ const { XDG_CONFIG_HOME = path.join(homedir, '.config') } = process.env;
+ return path.join(XDG_CONFIG_HOME, name);
+ };
+ switch (process.platform) {
+ case 'darwin':
+ return macos();
+ case 'win32':
+ return win();
+ default:
+ return linux();
+ }
+}
diff --git a/packages/astro/src/preferences/store.ts b/packages/astro/src/preferences/store.ts
new file mode 100644
index 000000000..1bf9c46c7
--- /dev/null
+++ b/packages/astro/src/preferences/store.ts
@@ -0,0 +1,62 @@
+import dget from 'dlv';
+import { dset } from 'dset';
+import fs from 'node:fs';
+import path from 'node:path';
+
+export class PreferenceStore {
+ private file: string;
+
+ constructor(
+ private dir: string,
+ filename = 'settings.json'
+ ) {
+ this.file = path.join(this.dir, filename);
+ }
+
+ private _store?: Record<string, any>;
+ private get store(): Record<string, any> {
+ if (this._store) return this._store;
+ if (fs.existsSync(this.file)) {
+ try {
+ this._store = JSON.parse(fs.readFileSync(this.file).toString());
+ } catch {}
+ }
+ if (!this._store) {
+ this._store = {};
+ this.write();
+ }
+ return this._store;
+ }
+ private set store(value: Record<string, any>) {
+ this._store = value;
+ this.write();
+ }
+ write() {
+ if (!this._store || Object.keys(this._store).length === 0) return;
+ fs.mkdirSync(this.dir, { recursive: true });
+ fs.writeFileSync(this.file, JSON.stringify(this.store, null, '\t'));
+ }
+ clear(): void {
+ this.store = {};
+ fs.rmSync(this.file, { recursive: true });
+ }
+ delete(key: string): boolean {
+ dset(this.store, key, undefined);
+ this.write();
+ return true;
+ }
+ get(key: string): any {
+ return dget(this.store, key);
+ }
+ has(key: string): boolean {
+ return typeof this.get(key) !== 'undefined';
+ }
+ set(key: string, value: any): void {
+ if (this.get(key) === value) return;
+ dset(this.store, key, value);
+ this.write();
+ }
+ getAll(): Record<string, any> {
+ return this.store;
+ }
+}
diff --git a/packages/astro/src/prefetch/vite-plugin-prefetch.ts b/packages/astro/src/prefetch/vite-plugin-prefetch.ts
index 73ae53f63..69714fd49 100644
--- a/packages/astro/src/prefetch/vite-plugin-prefetch.ts
+++ b/packages/astro/src/prefetch/vite-plugin-prefetch.ts
@@ -4,7 +4,7 @@ import type { AstroSettings } from '../@types/astro.js';
const virtualModuleId = 'astro:prefetch';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
const prefetchInternalModuleFsSubpath = 'astro/dist/prefetch/index.js';
-const prefetchCode = `import { init } from 'astro/prefetch';init()`;
+const prefetchCode = `import { init } from 'astro/virtual-modules/prefetch.js';init()`;
export default function astroPrefetch({ settings }: { settings: AstroSettings }): vite.Plugin {
const prefetchOption = settings.config.prefetch;
@@ -19,7 +19,7 @@ export default function astroPrefetch({ settings }: { settings: AstroSettings })
// Inject prefetch script to all pages
settings.scripts.push({
stage: 'page',
- content: `import { init } from 'astro/prefetch';init()`,
+ content: `import { init } from 'astro/virtual-modules/prefetch.js';init()`,
});
}
@@ -40,7 +40,7 @@ export default function astroPrefetch({ settings }: { settings: AstroSettings })
load(id) {
if (id === resolvedVirtualModuleId) {
if (!prefetch) throwPrefetchNotEnabledError();
- return `export { prefetch } from "astro/prefetch";`;
+ return `export { prefetch } from "astro/virtual-modules/prefetch.js";`;
}
},
transform(code, id) {
diff --git a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
index 65e50c98e..32fca3759 100644
--- a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
@@ -2,7 +2,6 @@ import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@t
import { type AstroDevOverlay, type DevOverlayPlugin } from './overlay.js';
import { settings } from './settings.js';
-import type { Icon } from './ui-library/icons.js';
let overlay: AstroDevOverlay;
@@ -13,12 +12,17 @@ document.addEventListener('DOMContentLoaded', async () => {
{ default: astroAuditPlugin },
{ default: astroXrayPlugin },
{ default: astroSettingsPlugin },
- { AstroDevOverlay, DevOverlayCanvas },
- { DevOverlayCard },
- { DevOverlayHighlight },
- { DevOverlayTooltip },
- { DevOverlayWindow },
- { DevOverlayToggle },
+ { AstroDevOverlay, DevOverlayCanvas, getPluginIcon },
+ {
+ DevOverlayCard,
+ DevOverlayHighlight,
+ DevOverlayTooltip,
+ DevOverlayWindow,
+ DevOverlayToggle,
+ DevOverlayButton,
+ DevOverlayBadge,
+ DevOverlayIcon,
+ },
{ getIconElement, isDefinedIcon },
] = await Promise.all([
// @ts-expect-error
@@ -28,11 +32,7 @@ document.addEventListener('DOMContentLoaded', async () => {
import('./plugins/xray.js'),
import('./plugins/settings.js'),
import('./overlay.js'),
- import('./ui-library/card.js'),
- import('./ui-library/highlight.js'),
- import('./ui-library/tooltip.js'),
- import('./ui-library/window.js'),
- import('./ui-library/toggle.js'),
+ import('./ui-library/index.js'),
import('./ui-library/icons.js'),
]);
@@ -44,6 +44,9 @@ document.addEventListener('DOMContentLoaded', async () => {
customElements.define('astro-dev-overlay-highlight', DevOverlayHighlight);
customElements.define('astro-dev-overlay-card', DevOverlayCard);
customElements.define('astro-dev-overlay-toggle', DevOverlayToggle);
+ customElements.define('astro-dev-overlay-button', DevOverlayButton);
+ customElements.define('astro-dev-overlay-badge', DevOverlayBadge);
+ customElements.define('astro-dev-overlay-icon', DevOverlayIcon);
overlay = document.createElement('astro-dev-overlay');
@@ -84,7 +87,7 @@ document.addEventListener('DOMContentLoaded', async () => {
newState = evt.detail.state ?? true;
}
- await overlay.togglePluginStatus(plugin, newState);
+ await overlay.setPluginStatus(plugin, newState);
});
return plugin;
@@ -109,9 +112,13 @@ document.addEventListener('DOMContentLoaded', async () => {
border: 1px solid rgba(52, 56, 65, 1);
border-radius: 12px;
box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
- width: 180px;
+ width: 192px;
padding: 8px;
z-index: 9999999999;
+ transform: translate(-50%, 0%);
+ position: fixed;
+ bottom: 72px;
+ left: 50%;
}
.notification {
@@ -135,20 +142,19 @@ document.addEventListener('DOMContentLoaded', async () => {
background: transparent;
color: white;
font-family: system-ui, sans-serif;
- font-size: 16px;
- line-height: 1.2;
+ font-size: 14px;
white-space: nowrap;
text-decoration: none;
margin: 0;
display: flex;
- align-items: center;
+ align-items: center;
width: 100%;
padding: 8px;
border-radius: 8px;
}
#dropdown button:hover, #dropdown button:focus-visible {
- background: rgba(27, 30, 36, 1);
+ background: #FFFFFF20;
cursor: pointer;
}
@@ -158,8 +164,9 @@ document.addEventListener('DOMContentLoaded', async () => {
#dropdown .icon {
position: relative;
- height: 24px;
- width: 24px;
+ height: 20px;
+ width: 20px;
+ padding: 1px;
margin-right: 0.5em;
}
@@ -180,8 +187,9 @@ document.addEventListener('DOMContentLoaded', async () => {
button.setAttribute('data-plugin-id', plugin.id);
const iconContainer = document.createElement('div');
- const iconElement = getPluginIcon(plugin.icon);
- iconContainer.append(iconElement);
+ const iconElement = document.createElement('template');
+ iconElement.innerHTML = getPluginIcon(plugin.icon);
+ iconContainer.append(iconElement.content.cloneNode(true));
const notification = document.createElement('div');
notification.classList.add('notification');
@@ -198,9 +206,6 @@ document.addEventListener('DOMContentLoaded', async () => {
dropdown.append(buttonContainer);
- eventTarget.addEventListener('plugin-toggled', positionDropdown);
- window.addEventListener('resize', positionDropdown);
-
plugin.eventTarget.addEventListener('toggle-notification', (evt) => {
if (!(evt instanceof CustomEvent)) return;
@@ -219,29 +224,6 @@ document.addEventListener('DOMContentLoaded', async () => {
}
canvas.append(dropdown);
-
- function getPluginIcon(icon: Icon) {
- if (isDefinedIcon(icon)) {
- return getIconElement(icon)!;
- }
-
- return icon;
- }
-
- function positionDropdown() {
- const moreButtonRect = overlay.shadowRoot
- .querySelector('[data-plugin-id="astro:more"]')
- ?.getBoundingClientRect();
- const dropdownRect = dropdown.getBoundingClientRect();
-
- if (moreButtonRect && dropdownRect) {
- dropdown.style.position = 'absolute';
- dropdown.style.top = `${moreButtonRect.top - dropdownRect.height - 12}px`;
- dropdown.style.left = `${
- moreButtonRect.left + moreButtonRect.width - dropdownRect.width
- }px`;
- }
- }
}
},
} satisfies DevOverlayPluginDefinition;
diff --git a/packages/astro/src/runtime/client/dev-overlay/overlay.ts b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
index 900c3fb0f..2a45e740f 100644
--- a/packages/astro/src/runtime/client/dev-overlay/overlay.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
@@ -1,5 +1,7 @@
/* eslint-disable no-console */
-import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
+import type {
+ DevOverlayPlugin as DevOverlayPluginDefinition,
+} from '../../../@types/astro.js';
import { settings } from './settings.js';
import { getIconElement, isDefinedIcon, type Icon } from './ui-library/icons.js';
@@ -12,17 +14,16 @@ export type DevOverlayPlugin = DevOverlayPluginDefinition & {
};
eventTarget: EventTarget;
};
-
const WS_EVENT_NAME = 'astro-dev-overlay';
+const HOVER_DELAY = 2 * 1000;
export class AstroDevOverlay extends HTMLElement {
shadowRoot: ShadowRoot;
- hoverTimeout: number | undefined;
- isHidden: () => boolean = () => this.devOverlay?.hasAttribute('data-hidden') ?? true;
+ delayedHideTimeout: number | undefined;
devOverlay: HTMLDivElement | undefined;
plugins: DevOverlayPlugin[] = [];
- HOVER_DELAY = 750;
hasBeenInitialized = false;
+ // TODO: This should be dynamic based on the screen size or at least configurable, erika - 2023-11-29
customPluginsToShow = 3;
constructor() {
@@ -30,51 +31,60 @@ export class AstroDevOverlay extends HTMLElement {
this.shadowRoot = this.attachShadow({ mode: 'open' });
}
- // Happens whenever the component is connected to the DOM
- // When view transitions are enabled, this happens every time the view changes
- async connectedCallback() {
- if (!this.hasBeenInitialized) {
- this.shadowRoot.innerHTML = `
- <style>
+ /**
+ * All one-time DOM setup runs through here. Only ever call this once,
+ * in connectedCallback(), and protect it from being called again.
+ */
+ init() {
+ this.shadowRoot.innerHTML = `
+ <style>
:host {
+ /* Important! Reset all inherited styles to initial */
+ all: initial;
z-index: 999999;
view-transition-name: astro-dev-overlay;
display: contents;
}
-
+
::view-transition-old(astro-dev-overlay),
::view-transition-new(astro-dev-overlay) {
- animation: none;
+ animation: none;
}
-
+
#dev-overlay {
position: fixed;
- bottom: 7.5%;
- left: calc(50% + 32px);
+ bottom: 0px;
+ left: 50%;
transform: translate(-50%, 0%);
z-index: 9999999999;
display: flex;
- gap: 8px;
+ flex-direction: column;
align-items: center;
transition: bottom 0.35s cubic-bezier(0.485, -0.050, 0.285, 1.505);
pointer-events: none;
}
-
+
#dev-overlay[data-hidden] {
bottom: -40px;
}
-
- #dev-overlay[data-hidden]:hover, #dev-overlay[data-hidden]:focus-within {
- bottom: -35px;
- cursor: pointer;
+
+ #dev-overlay[data-hidden] #dev-bar .item {
+ opacity: 0;
}
-
- #dev-overlay[data-hidden] #minimize-button {
- visibility: hidden;
+
+ #dev-bar-hitbox-above,
+ #dev-bar-hitbox-below {
+ width: 100%;
+ pointer-events: auto;
}
-
- #dev-bar {
- height: 56px;
+ #dev-bar-hitbox-above {
+ height: 42px;
+ }
+ #dev-bar-hitbox-below {
+ height: 16px;
+ }
+ #dev-bar {
+ height: 40px;
overflow: hidden;
pointer-events: auto;
background: linear-gradient(180deg, #13151A 0%, rgba(19, 21, 26, 0.88) 100%);
@@ -83,11 +93,17 @@ export class AstroDevOverlay extends HTMLElement {
box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
}
+ @media (forced-colors: active) {
+ #dev-bar {
+ background: white;
+ }
+ }
+
#dev-bar .item {
display: flex;
justify-content: center;
align-items: center;
- width: 64px;
+ width: 44px;
border: 0;
background: transparent;
color: white;
@@ -99,39 +115,45 @@ export class AstroDevOverlay extends HTMLElement {
padding: 0;
margin: 0;
overflow: hidden;
+ transition: opacity 0.2s ease-out 0s;
}
-
+
#dev-bar #bar-container .item:hover, #dev-bar #bar-container .item:focus-visible {
- background: rgba(27, 30, 36, 1);
+ background: #FFFFFF20;
cursor: pointer;
outline-offset: -3px;
}
-
+
#dev-bar .item:first-of-type {
border-top-left-radius: 9999px;
border-bottom-left-radius: 9999px;
+ width: 42px;
+ padding-left: 4px;
}
-
+
#dev-bar .item:last-of-type {
border-top-right-radius: 9999px;
border-bottom-right-radius: 9999px;
+ width: 42px;
+ padding-right: 4px;
}
#dev-bar #bar-container .item.active {
background: rgba(71, 78, 94, 1);
}
-
+
#dev-bar .item-tooltip {
background: linear-gradient(0deg, #13151A, #13151A), linear-gradient(0deg, #343841, #343841);
border: 1px solid rgba(52, 56, 65, 1);
border-radius: 4px;
padding: 4px 8px;
position: absolute;
- top: -40px;
+ top: 4px;
+ font-size: 14px;
opacity: 0;
transition: opacity 0.2s ease-in-out 0s;
pointer-events: none;
}
-
+
#dev-bar .item-tooltip::after{
content: '';
position: absolute;
@@ -141,30 +163,43 @@ export class AstroDevOverlay extends HTMLElement {
border-right: 5px solid transparent;
border-top: 5px solid #343841;
}
-
+
#dev-bar .item:hover .item-tooltip, #dev-bar .item:not(.active):focus-visible .item-tooltip {
transition: opacity 0.2s ease-in-out 200ms;
opacity: 1;
}
+ @media (forced-colors: active) {
+ #dev-bar .item:hover .item-tooltip,
+ #dev-bar .item:not(.active):focus-visible .item-tooltip {
+ background: white;
+ }
+ }
+
#dev-bar #bar-container .item.active .notification {
border-color: rgba(71, 78, 94, 1);
}
-
+
#dev-bar .item .icon {
position: relative;
- max-width: 24px;
- max-height: 24px;
+ max-width: 20px;
+ max-height: 20px;
user-select: none;
}
-
+
#dev-bar .item svg {
- width: 24px;
- height: 24px;
+ width: 20px;
+ height: 20px;
display: block;
margin: auto;
}
+ @media (forced-colors: active) {
+ #dev-bar .item svg path[fill="#fff"] {
+ fill: black;
+ }
+ }
+
#dev-bar .item .notification {
display: none;
position: absolute;
@@ -176,67 +211,23 @@ export class AstroDevOverlay extends HTMLElement {
border: 1px solid rgba(19, 21, 26, 1);
background: #B33E66;
}
-
+
#dev-bar .item .notification[data-active] {
display: block;
}
-
+
#dev-bar #bar-container {
height: 100%;
display: flex;
}
-
+
#dev-bar .separator {
background: rgba(52, 56, 65, 1);
width: 1px;
}
-
- #minimize-button {
- width: 32px;
- height: 32px;
- background: rgba(255, 255, 255, 0.75);
- border-radius: 9999px;
- display: flex;
- justify-content: center;
- align-items: center;
- opacity: 0;
- transition: opacity 0.2s ease-in-out;
- pointer-events: auto;
- border: 0;
- color: white;
- font-family: system-ui, sans-serif;
- font-size: 1rem;
- line-height: 1.2;
- white-space: nowrap;
- text-decoration: none;
- padding: 0;
- margin: 0;
- }
-
- #minimize-button:hover, #minimize-button:focus {
- cursor: pointer;
- background: rgba(255, 255, 255, 0.90);
- }
-
- #minimize-button svg {
- width: 16px;
- height: 16px;
- }
-
- .sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- }
- </style>
-
- <div id="dev-overlay">
+ </style>
+ <div id="dev-overlay" data-hidden>
+ <div id="dev-bar-hitbox-above"></div>
<div id="dev-bar">
<div id="bar-container">
${this.plugins
@@ -267,38 +258,45 @@ export class AstroDevOverlay extends HTMLElement {
)}
</div>
</div>
- <button id="minimize-button">${getIconElement('arrow-down')?.outerHTML}</button>
+ <div id="dev-bar-hitbox-below"></div>
</div>`;
- this.devOverlay = this.shadowRoot.querySelector<HTMLDivElement>('#dev-overlay')!;
- this.attachEvents();
- }
+
+ this.devOverlay = this.shadowRoot.querySelector<HTMLDivElement>('#dev-overlay')!;
+ this.attachEvents();
// Create plugin canvases
this.plugins.forEach(async (plugin) => {
- if (!this.hasBeenInitialized) {
- if (settings.config.verbose) console.log(`Creating plugin canvas for ${plugin.id}`);
-
- const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
- pluginCanvas.dataset.pluginId = plugin.id;
- this.shadowRoot?.append(pluginCanvas);
- }
-
- await this.togglePluginStatus(plugin, plugin.active);
+ if (settings.config.verbose) console.log(`Creating plugin canvas for ${plugin.id}`);
+ const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
+ pluginCanvas.dataset.pluginId = plugin.id;
+ this.shadowRoot?.append(pluginCanvas);
});
- // Init plugin lazily - This is safe to do here because only plugins that are not initialized yet will be affected
+ // Init plugin lazily, so that the page can load faster.
+ // Fallback to setTimeout for Safari (sad!)
if ('requestIdleCallback' in window) {
window.requestIdleCallback(async () => {
- await this.initAllPlugins();
+ this.plugins.map((plugin) => this.initPlugin(plugin));
});
} else {
- // Fallback to setTimeout for.. Safari...
setTimeout(async () => {
- await this.initAllPlugins();
- }, 200);
+ this.plugins.map((plugin) => this.initPlugin(plugin));
+ }, 300);
}
+ }
- this.hasBeenInitialized = true;
+ // This is called whenever the component is connected to the DOM.
+ // This happens on first page load, and on each page change when
+ // view transitions are used.
+ connectedCallback() {
+ if (!this.hasBeenInitialized) {
+ this.init();
+ this.hasBeenInitialized = true;
+ }
+ // Run this every time to make sure the correct plugin is open.
+ this.plugins.forEach(async (plugin) => {
+ await this.setPluginStatus(plugin, plugin.active);
+ });
}
attachEvents() {
@@ -307,95 +305,70 @@ export class AstroDevOverlay extends HTMLElement {
item.addEventListener('click', async (e) => {
const target = e.currentTarget;
if (!target || !(target instanceof HTMLElement)) return;
-
const id = target.dataset.pluginId;
if (!id) return;
-
const plugin = this.getPluginById(id);
if (!plugin) return;
-
- if (plugin.status === 'loading') {
- await this.initPlugin(plugin);
- }
-
await this.togglePluginStatus(plugin);
});
});
- const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
- if (minimizeButton && this.devOverlay) {
- minimizeButton.addEventListener('click', () => {
- this.toggleOverlay(false);
- this.toggleMinimizeButton(false);
- });
- }
-
- const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
- if (devBar) {
- // On hover:
- // - If the overlay is hidden, show it after the hover delay
- // - If the overlay is visible, show the minimize button after the hover delay
- (['mouseenter', 'focusin'] as const).forEach((event) => {
- devBar.addEventListener(event, () => {
- if (this.hoverTimeout) {
- window.clearTimeout(this.hoverTimeout);
- }
-
- if (this.isHidden()) {
- this.hoverTimeout = window.setTimeout(() => {
- this.toggleOverlay(true);
- }, this.HOVER_DELAY + 200); // Slightly higher delay here to prevent users opening the overlay by accident
- } else {
- this.hoverTimeout = window.setTimeout(() => {
- this.toggleMinimizeButton(true);
- }, this.HOVER_DELAY);
- }
- });
- });
-
- // On unhover:
- // - Reset every timeout, as to avoid showing the overlay/minimize button when the user didn't really want to hover
- // - If the overlay is visible, hide the minimize button after the hover delay
- devBar.addEventListener('mouseleave', () => {
- if (this.hoverTimeout) {
- window.clearTimeout(this.hoverTimeout);
+ (['mouseenter', 'focusin'] as const).forEach((event) => {
+ this.devOverlay!.addEventListener(event, () => {
+ this.clearDelayedHide();
+ if (this.isHidden()) {
+ this.setOverlayVisible(true);
}
+ });
+ });
- if (!this.isHidden()) {
- this.hoverTimeout = window.setTimeout(() => {
- this.toggleMinimizeButton(false);
- }, this.HOVER_DELAY);
+ (['mouseleave', 'focusout'] as const).forEach((event) => {
+ this.devOverlay!.addEventListener(event, () => {
+ this.clearDelayedHide();
+ if (this.getActivePlugin() || this.isHidden()) {
+ return;
}
+ this.triggerDelayedHide();
});
+ });
- // On click, show the overlay if it's hidden, it's likely the user wants to interact with it
- devBar.addEventListener('click', () => {
- if (!this.isHidden()) return;
- this.toggleOverlay(true);
- });
+ // On click, show the overlay if it's hidden, it's likely the user wants to interact with it
+ this.shadowRoot.addEventListener('click', () => {
+ if (!this.isHidden()) return;
+ this.setOverlayVisible(true);
+ });
- devBar.addEventListener('keyup', (event) => {
- if (event.code === 'Space' || event.code === 'Enter') {
- if (!this.isHidden()) return;
- this.toggleOverlay(true);
- }
- });
- }
- }
+ this.devOverlay!.addEventListener('keyup', (event) => {
+ if (event.code === 'Space' || event.code === 'Enter') {
+ if (!this.isHidden()) return;
+ this.setOverlayVisible(true);
+ }
+ if (event.key === 'Escape') {
+ if (this.isHidden()) return;
+ if (this.getActivePlugin()) return;
+ this.setOverlayVisible(false);
+ }
+ });
- async initAllPlugins() {
- await Promise.all(
- this.plugins
- .filter((plugin) => plugin.status === 'loading')
- .map((plugin) => this.initPlugin(plugin))
- );
+ document.addEventListener('keyup', (event) => {
+ if (event.key !== 'Escape') {
+ return;
+ }
+ if (this.isHidden()) {
+ return;
+ }
+ const activePlugin = this.getActivePlugin();
+ if (activePlugin) {
+ this.setPluginStatus(activePlugin, false);
+ return;
+ }
+ this.setOverlayVisible(false);
+ });
}
async initPlugin(plugin: DevOverlayPlugin) {
- if (plugin.status === 'ready') return;
-
const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;
-
+ plugin.status = 'loading';
try {
if (settings.config.verbose) console.info(`Initializing plugin ${plugin.id}`);
@@ -413,19 +386,11 @@ export class AstroDevOverlay extends HTMLElement {
getPluginTemplate(plugin: DevOverlayPlugin) {
return `<button class="item" data-plugin-id="${plugin.id}">
- <div class="icon">${this.getPluginIcon(plugin.icon)}<div class="notification"></div></div>
+ <div class="icon">${getPluginIcon(plugin.icon)}<div class="notification"></div></div>
<span class="item-tooltip">${plugin.name}</span>
</button>`;
}
- getPluginIcon(icon: Icon) {
- if (isDefinedIcon(icon)) {
- return getIconElement(icon)?.outerHTML;
- }
-
- return icon;
- }
-
getPluginById(id: string) {
return this.plugins.find((plugin) => plugin.id === id);
}
@@ -436,11 +401,23 @@ export class AstroDevOverlay extends HTMLElement {
);
}
- /**
- * @param plugin The plugin to toggle the status of
- * @param newStatus Optionally, force the plugin into a specific state
- */
- async togglePluginStatus(plugin: DevOverlayPlugin, newStatus?: boolean) {
+ async togglePluginStatus(plugin: DevOverlayPlugin) {
+ const activePlugin = this.getActivePlugin();
+ if (activePlugin) {
+ await this.setPluginStatus(activePlugin, false);
+ }
+ // TODO(fks): Handle a plugin that hasn't loaded yet.
+ // Currently, this will just do nothing.
+ if (plugin.status !== 'ready') return;
+ // Open the selected plugin. If the selected plugin was
+ // already the active plugin then the desired outcome
+ // was to close that plugin, so no action needed.
+ if (plugin !== activePlugin) {
+ await this.setPluginStatus(plugin, true);
+ }
+ }
+
+ async setPluginStatus(plugin: DevOverlayPlugin, newStatus: boolean) {
const pluginCanvas = this.getPluginCanvasById(plugin.id);
if (!pluginCanvas) return;
@@ -465,68 +442,58 @@ export class AstroDevOverlay extends HTMLElement {
moreBarButton.classList.toggle('active', plugin.active);
}
- pluginCanvas.style.display = plugin.active ? 'block' : 'none';
-
- window.requestAnimationFrame(() => {
- pluginCanvas.toggleAttribute('data-active', plugin.active);
- plugin.eventTarget.dispatchEvent(
- new CustomEvent('plugin-toggled', {
- detail: {
- state: plugin.active,
- plugin,
- },
- })
- );
- });
+ if (plugin.active) {
+ pluginCanvas.style.display = 'block';
+ pluginCanvas.setAttribute('data-active', '');
+ } else {
+ pluginCanvas.style.display = 'none';
+ pluginCanvas.removeAttribute('data-active');
+ }
+
+ plugin.eventTarget.dispatchEvent(
+ new CustomEvent('plugin-toggled', {
+ detail: {
+ state: plugin.active,
+ plugin,
+ },
+ })
+ );
if (import.meta.hot) {
import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:toggled`, { state: plugin.active });
}
}
-
- /**
- * @param newStatus Optionally, force the minimize button into a specific state
- */
- toggleMinimizeButton(newStatus?: boolean) {
- const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
- if (!minimizeButton) return;
-
- if (newStatus !== undefined) {
- if (newStatus === true) {
- minimizeButton.removeAttribute('inert');
- minimizeButton.style.opacity = '1';
- } else {
- minimizeButton.setAttribute('inert', '');
- minimizeButton.style.opacity = '0';
- }
- } else {
- minimizeButton.toggleAttribute('inert');
- minimizeButton.style.opacity = minimizeButton.hasAttribute('inert') ? '0' : '1';
- }
+ isHidden(): boolean {
+ return this.devOverlay?.hasAttribute('data-hidden') ?? true;
}
-
- toggleOverlay(newStatus?: boolean) {
+ getActivePlugin(): DevOverlayPlugin | undefined {
+ return this.plugins.find((plugin) => plugin.active);
+ }
+ clearDelayedHide() {
+ window.clearTimeout(this.delayedHideTimeout);
+ this.delayedHideTimeout = undefined;
+ }
+ triggerDelayedHide() {
+ this.clearDelayedHide();
+ this.delayedHideTimeout = window.setTimeout(() => {
+ this.setOverlayVisible(false);
+ this.delayedHideTimeout = undefined;
+ }, HOVER_DELAY);
+ }
+ setOverlayVisible(newStatus: boolean) {
const barContainer = this.shadowRoot.querySelector<HTMLDivElement>('#bar-container');
const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
-
- if (newStatus !== undefined) {
- if (newStatus === true) {
- this.devOverlay?.removeAttribute('data-hidden');
- barContainer?.removeAttribute('inert');
- devBar?.removeAttribute('tabindex');
- } else {
- this.devOverlay?.setAttribute('data-hidden', '');
- barContainer?.setAttribute('inert', '');
- devBar?.setAttribute('tabindex', '0');
- }
- } else {
- this.devOverlay?.toggleAttribute('data-hidden');
- barContainer?.toggleAttribute('inert');
- if (this.isHidden()) {
- devBar?.setAttribute('tabindex', '0');
- } else {
- devBar?.removeAttribute('tabindex');
- }
+ if (newStatus === true) {
+ this.devOverlay?.removeAttribute('data-hidden');
+ barContainer?.removeAttribute('inert');
+ devBar?.removeAttribute('tabindex');
+ return;
+ }
+ if (newStatus === false) {
+ this.devOverlay?.setAttribute('data-hidden', '');
+ barContainer?.setAttribute('inert', '');
+ devBar?.setAttribute('tabindex', '0');
+ return;
}
}
}
@@ -550,3 +517,11 @@ export class DevOverlayCanvas extends HTMLElement {
</style>`;
}
}
+
+export function getPluginIcon(icon: Icon) {
+ if (isDefinedIcon(icon)) {
+ return getIconElement(icon).outerHTML;
+ }
+
+ return icon;
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
index 352a018e1..15f7205bd 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
@@ -1,19 +1,85 @@
-import type { DevOverlayPlugin } from '../../../../@types/astro.js';
-import { createWindowWithTransition, waitForTransition } from './utils/window.js';
+import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js';
+import { isDefinedIcon, type Icon } from '../ui-library/icons.js';
+import { colorForIntegration, iconForIntegration } from './utils/icons.js';
+import { createWindowElement } from './utils/window.js';
+
+const astroLogo =
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 99 26" width="100"><path fill="#fff" d="M6.70402 22.1453c-1.17459-1.0737-1.51748-3.3297-1.02811-4.9641.84853 1.0304 2.02424 1.3569 3.24204 1.5411 1.88005.2844 3.72635.178 5.47285-.6813.1998-.0984.3844-.2292.6027-.3617.1639.4755.2065.9554.1493 1.4439-.1392 1.1898-.7313 2.1088-1.673 2.8054-.3765.2787-.775.5278-1.1639.7905-1.1948.8075-1.518 1.7544-1.0691 3.1318.0107.0336.0202.0671.0444.149-.6101-.273-1.0557-.6705-1.39518-1.1931-.3586-.5517-.52921-1.1619-.53819-1.8221-.00449-.3213-.00449-.6455-.0477-.9623-.10551-.7722-.46804-1.118-1.15102-1.1379-.70094-.0205-1.2554.4129-1.40244 1.0953-.01122.0523-.02749.1041-.04377.1649l.00112.0006Z"/><path fill="url(#paint0_linear_386_2739)" d="M6.70402 22.1453c-1.17459-1.0737-1.51748-3.3297-1.02811-4.9641.84853 1.0304 2.02424 1.3569 3.24204 1.5411 1.88005.2844 3.72635.178 5.47285-.6813.1998-.0984.3844-.2292.6027-.3617.1639.4755.2065.9554.1493 1.4439-.1392 1.1898-.7313 2.1088-1.673 2.8054-.3765.2787-.775.5278-1.1639.7905-1.1948.8075-1.518 1.7544-1.0691 3.1318.0107.0336.0202.0671.0444.149-.6101-.273-1.0557-.6705-1.39518-1.1931-.3586-.5517-.52921-1.1619-.53819-1.8221-.00449-.3213-.00449-.6455-.0477-.9623-.10551-.7722-.46804-1.118-1.15102-1.1379-.70094-.0205-1.2554.4129-1.40244 1.0953-.01122.0523-.02749.1041-.04377.1649l.00112.0006Z"/><path fill="#fff" d="M0 16.909s3.47815-1.6944 6.96603-1.6944l2.62973-8.13858c.09846-.39359.38592-.66106.71044-.66106.3246 0 .612.26747.7105.66106l2.6297 8.13858c4.1309 0 6.966 1.6944 6.966 1.6944S14.7045.814589 14.693.782298C14.5234.306461 14.2371 0 13.8512 0H6.76183c-.38593 0-.66063.306461-.84174.782298C5.90733.81398 0 16.909 0 16.909ZM36.671 11.7318c0 1.4262-1.7739 2.2779-4.2302 2.2779-1.5985 0-2.1638-.3962-2.1638-1.2281 0-.8715.7018-1.2875 2.3003-1.2875 1.4426 0 2.6707.0198 4.0937.1981v.0396Zm.0195-1.7629c-.8772-.19808-2.2028-.31693-3.7818-.31693-4.6006 0-6.7644 1.08943-6.7644 3.62483 0 2.6344 1.4815 3.6446 4.9125 3.6446 2.9046 0 4.8735-.7328 5.5947-2.5354h.117c-.0195.4358-.039.8716-.039 1.2083 0 .931.156 1.0102.9162 1.0102h3.5869c-.1949-.5546-.3119-2.1194-.3119-3.4663 0-1.446.0585-2.5355.0585-4.00123 0-2.99098-1.7934-4.89253-7.4077-4.89253-2.4173 0-5.1074.41596-7.1543 1.03.1949.81213.4679 2.45617.6043 3.5258 1.774-.83193 4.2887-1.18847 6.2381-1.18847 2.6902 0 3.4309.61404 3.4309 1.86193v.4952ZM46.5325 12.5637c-.4874.0594-1.1502.0594-1.8325.0594-.7213 0-1.3841-.0198-1.8324-.0792 0 .1585-.0195.3367-.0195.4952 0 2.476 1.618 3.922 7.3102 3.922 5.3609 0 7.0958-1.4262 7.0958-3.9418 0-2.3769-1.1501-3.5456-6.238-3.8031-3.9573-.17827-4.3082-.61404-4.3082-1.10924 0-.57442.5068-.87154 3.158-.87154 2.7487 0 3.4894.37635 3.4894 1.16866v.17827c.3899-.01981 1.0917-.03961 1.813-.03961.6823 0 1.423.0198 1.8519.05942 0-.17827.0195-.33674.0195-.47539 0-2.91175-2.4172-3.86252-7.0958-3.86252-5.2634 0-7.0373 1.2875-7.0373 3.8031 0 2.25805 1.423 3.66445 6.472 3.88235 3.7233.1188 4.1327.5348 4.1327 1.1092 0 .6141-.6043.8914-3.2165.8914-3.0021 0-3.7623-.416-3.7623-1.2677v-.1189ZM63.6883 2.125c-1.423 1.32712-3.9768 2.65425-5.3998 3.01079.0195.73289.0195 2.07982.0195 2.81271l1.3061.01981c-.0195 1.40635-.039 3.10979-.039 4.23889 0 2.6344 1.3841 4.6152 5.6922 4.6152 1.813 0 3.0216-.1981 4.5226-.515-.1559-.9706-.3314-2.4562-.3898-3.5852-.8968.2971-2.0274.4556-3.275.4556-1.735 0-2.4368-.4754-2.4368-1.8422 0-1.1884 0-2.29767.0195-3.32768 2.2223.01981 4.4446.05943 5.7507.09904-.0195-1.03.0195-2.51559.078-3.50598-1.8909.03961-4.0157.05942-5.7702.05942.0195-.87154.039-1.70347.0585-2.5354h-.1365ZM75.3313 7.35427c.0195-1.03001.039-1.90156.0585-2.75329h-3.9183c.0585 1.70347.0585 3.44656.0585 6.00172 0 2.5553-.0195 4.3182-.0585 6.0018h4.4836c-.078-1.1885-.0975-3.189-.0975-4.8925 0-2.69388 1.0917-3.46638 3.5674-3.46638 1.1502 0 1.9689.13865 2.6902.39615.0195-1.01019.2144-2.97117.3314-3.84271-.7408-.21789-1.5595-.35655-2.5537-.35655-2.1249-.0198-3.6844.85174-4.4056 2.93156l-.156-.0198ZM94.8501 10.5235c0 2.1591-1.5595 3.1693-4.0157 3.1693-2.4368 0-3.9963-.9508-3.9963-3.1693 0-2.21846 1.579-3.05039 3.9963-3.05039 2.4367 0 4.0157.89135 4.0157 3.05039Zm4.0743-.099c0-4.29832-3.353-6.21968-8.09-6.21968-4.7566 0-7.9926 1.92136-7.9926 6.21968 0 4.2785 3.0216 6.5762 7.9731 6.5762 4.9904 0 8.1095-2.2977 8.1095-6.5762Z"/><defs><linearGradient id="paint0_linear_386_2739" x1="5.46011" x2="16.8017" y1="25.9999" y2="20.6412" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>';
+
+export interface Integration {
+ name: string;
+ title: string;
+ description: string;
+ image?: string;
+ categories: string[];
+ repoUrl: string;
+ npmUrl: string;
+ homepageUrl: string;
+ official: boolean;
+ featured: number;
+ downloads: number;
+}
+
+interface IntegrationData {
+ data: Integration[];
+}
+
+let integrationData: IntegrationData;
export default {
id: 'astro',
- name: 'Astro',
+ name: 'Menu',
icon: 'astro:logo',
- init(canvas) {
- createWindow();
+ async init(canvas, eventTarget) {
+ createCanvas();
+
+ document.addEventListener('astro:after-swap', createCanvas);
+
+ eventTarget.addEventListener('plugin-toggled', async (event) => {
+ resetDebugButton();
+ if (!(event instanceof CustomEvent)) return;
+
+ if (event.detail.state === true) {
+ if (!integrationData)
+ fetch('https://astro.build/api/v1/dev-overlay/', {
+ cache: 'no-cache',
+ })
+ .then((res) => res.json())
+ .then((data) => {
+ integrationData = data;
+ integrationData.data = integrationData.data.map((integration) => {
+ return integration;
+ });
+ refreshIntegrationList();
+ });
+ }
+ });
- document.addEventListener('astro:after-swap', createWindow);
+ function createCanvas() {
+ const links: { icon: Icon; name: string; link: string }[] = [
+ {
+ icon: 'bug',
+ name: 'Report a Bug',
+ link: 'https://github.com/withastro/astro/issues/new/choose',
+ },
+ {
+ icon: 'lightbulb',
+ name: 'Feedback',
+ link: 'https://github.com/withastro/roadmap/discussions/new/choose',
+ },
+ {
+ icon: 'file-search',
+ name: 'Documentation',
+ link: 'https://docs.astro.build',
+ },
+ {
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 14"><path fill="currentColor" d="M14.3451 1.9072c-1.0375-.47613-2.1323-.81595-3.257-1.010998-.0102-.001716-.0207-.000234-.03.004243s-.017.011728-.022.020757c-.141.249998-.297.576998-.406.832998-1.2124-.18399-2.44561-.18399-3.658 0-.12159-.28518-.25914-.56328-.412-.832998-.00513-.00893-.01285-.016098-.02213-.02056-.00928-.004462-.0197-.00601-.02987-.00444-1.125.193998-2.22.533998-3.257 1.010998-.00888.00339-.0163.00975-.021.018-2.074 3.099-2.643004 6.122-2.364004 9.107.001.014.01.028.021.037 1.207724.8946 2.558594 1.5777 3.995004 2.02.01014.0032.02103.0031.03111-.0003.01007-.0034.01878-.01.02489-.0187.308-.42.582-.863.818-1.329.00491-.0096.0066-.0205.0048-.0312-.00181-.0106-.007-.0204-.0148-.0278-.00517-.0049-.0113-.0086-.018-.011-.43084-.1656-.84811-.3645-1.248-.595-.01117-.0063-.01948-.0167-.0232-.029-.00373-.0123-.00258-.0255.0032-.037.0034-.0074.00854-.014.015-.019.084-.063.168-.129.248-.195.00706-.0057.01554-.0093.02453-.0106.00898-.0012.01813 0 .02647.0036 2.619 1.196 5.454 1.196 8.041 0 .0086-.0037.0181-.0051.0275-.0038.0093.0012.0181.0049.0255.0108.08.066.164.132.248.195.0068.005.0123.0116.0159.0192.0036.0076.0053.016.0049.0244-.0003.0084-.0028.0166-.0072.0238-.0043.0072-.0104.0133-.0176.0176-.399.2326-.8168.4313-1.249.594-.0069.0025-.0132.0065-.0183.0117-.0052.0051-.0092.0114-.0117.0183-.0023.0067-.0032.0138-.0027.0208.0005.0071.0024.0139.0057.0202.24.465.515.909.817 1.329.0061.0087.0148.0153.0249.0187.0101.0034.021.0035.0311.0003 1.4388-.441 2.7919-1.1241 4.001-2.02.0061-.0042.0111-.0097.0147-.0161.0037-.0064.0058-.0135.0063-.0209.334-3.451-.559-6.449-2.366-9.106-.0018-.00439-.0045-.00834-.008-.01162-.0034-.00327-.0075-.00578-.012-.00738Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"/></svg>',
+ name: 'Community',
+ link: 'https://astro.build/chat',
+ },
+ ];
- function createWindow() {
- const window = createWindowWithTransition(
- 'Astro',
- 'astro:logo',
+ const windowComponent = createWindowElement(
`<style>
#buttons-container {
display: flex;
@@ -42,35 +108,332 @@ export default {
#main-container {
display: flex;
flex-direction: column;
- justify-content: space-between;
height: 100%;
+ gap: 24px;
}
p {
margin-top: 0;
}
+
+ header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ header section {
+ display: flex;
+ gap: 0.8em;
+ }
+
+ h2 {
+ color: white;
+ margin: 0;
+ font-size: 18px;
+ }
+
+ a {
+ color: rgba(224, 204, 250, 1);
+ }
+
+ a:hover {
+ color: #f4ecfd;
+ }
+
+ #integration-list-wrapper {
+ position: relative;
+ --offset: 24px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ margin-left: calc(var(--offset) * -1);
+ margin-right: calc(var(--offset) * -1);
+ padding-left: var(--offset);
+ padding-right: var(--offset);
+ height: 210px;
+ }
+
+ /* Pseudo-elements to fade cards as they scroll out of viewport */
+ #integration-list-wrapper::before,
+ #integration-list-wrapper::after {
+ content: '';
+ height: 192px;
+ display: block;
+ position: fixed;
+ width: var(--offset);
+ top: 106px;
+ background: red;
+ }
+
+ #integration-list-wrapper::before {
+ left: -1px;
+ border-left: 1px solid rgba(52, 56, 65, 1);
+ background: linear-gradient(to right, rgba(19, 21, 26, 1), rgba(19, 21, 26, 0));
+ }
+
+ #integration-list-wrapper::after {
+ right: -1px;
+ border-right: 1px solid rgba(52, 56, 65, 1);
+ background: linear-gradient(to left, rgba(19, 21, 26, 1), rgba(19, 21, 26, 0));
+ }
+
+ #integration-list-wrapper::-webkit-scrollbar {
+ width: 5px;
+ height: 8px;
+ background-color: rgba(255, 255, 255, 0.08); /* or add it to the track */
+ border-radius: 4px;
+ }
+
+ /* This is wild but gives us a gap on either side of the container */
+ #integration-list-wrapper::-webkit-scrollbar-button:start:decrement,
+ #integration-list-wrapper::-webkit-scrollbar-button:end:increment {
+ display: block;
+ width: 24px;
+ background-color: #13151A;
+ }
+
+ /* Removes arrows on both sides */
+ #integration-list-wrapper::-webkit-scrollbar-button:horizontal:start:increment,
+ #integration-list-wrapper::-webkit-scrollbar-button:horizontal:end:decrement {
+ display: none;
+ }
+
+ #integration-list-wrapper::-webkit-scrollbar-track-piece {
+ border-radius: 4px;
+ }
+
+ #integration-list-wrapper::-webkit-scrollbar-thumb {
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 4px;
+ }
+
+ #integration-list {
+ margin-top: 1em;
+ display: flex;
+ gap: 16px;
+ padding-bottom: 1em;
+ }
+
+ #integration-list::after {
+ content: " ";
+ display: inline-block;
+ white-space: pre;
+ width: 1px;
+ height: 1px;
+ }
+
+ #integration-list astro-dev-overlay-card, .integration-skeleton {
+ min-width: 240px;
+ height: 160px;
+ }
+
+ .integration-skeleton {
+ animation: pulse 2s calc(var(--i, 0) * 250ms) cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ background-color: rgba(35, 38, 45, 1);
+ border-radius: 8px;
+ }
+
+ @keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: .5;
+ }
+ }
+
+ #integration-list astro-dev-overlay-card .integration-image {
+ width: 40px;
+ height: 40px;
+ background-color: var(--integration-image-background, white);
+ border-radius: 9999px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+
+ #integration-list astro-dev-overlay-card img {
+ width: 24px;
+ height: 24px;
+ }
+
+ #integration-list astro-dev-overlay-card astro-dev-overlay-icon {
+ width: 24px;
+ height: 24px;
+ color: #fff;
+ }
+
+ #links {
+ margin: auto 0;
+ display: flex;
+ justify-content: center;
+ gap: 24px;
+ }
+
+ #links a {
+ text-decoration: none;
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 0.7em;
+ flex: 1;
+ white-space: nowrap;
+ font-weight: 600;
+ color: white;
+ }
+
+ #links a:hover {
+ color: rgba(145, 152, 173, 1);
+ }
+
+ #links astro-dev-overlay-icon {
+ width: 1.5em;
+ height: 1.5em;
+ display: block;
+ }
+
+ #integration-list astro-dev-overlay-card svg {
+ width: 24px;
+ height: 24px;
+ vertical-align: bottom;
+ }
+
+ #integration-list astro-dev-overlay-card h3 {
+ margin: 0;
+ margin-bottom: 8px;
+ color: white;
+ white-space: nowrap;
+ }
+
+ #integration-list astro-dev-overlay-card p {
+ font-size: 14px;
+ }
+
+ @media (forced-colors: active) {
+ svg path[fill="#fff"] {
+ fill: black;
+ }
+ }
</style>
+ <header>
+ <section>
+ ${astroLogo}
+ <astro-dev-overlay-badge badge-style="gray" size="large">${
+ (window as DevOverlayMetadata).__astro_dev_overlay__.version
+ }</astro-dev-overlay-badge>
+ </section>
+ <astro-dev-overlay-button id="copy-debug-button">Copy debug info <astro-dev-overlay-icon icon="copy" /></astro-dev-overlay-button>
+ </header>
+ <hr />
+
<div id="main-container">
<div>
- <p>Welcome to Astro!</p>
- <div id="buttons-container">
- <astro-dev-overlay-card icon="bug" link="https://github.com/withastro/astro/issues/new/choose">Report an issue</astro-dev-overlay-card>
- <astro-dev-overlay-card icon="file-search" link="https://docs.astro.build/en/getting-started/">View Astro Docs</astro-dev-overlay-card>
- </div>
+ <header><h2>Featured integrations</h2><a href="https://astro.build/integrations/" target="_blank">View all</a></header>
+ <div id="integration-list-wrapper">
+ <section id="integration-list">
+ <div class="integration-skeleton" style="--i:0;"></div>
+ <div class="integration-skeleton" style="--i:1;"></div>
+ <div class="integration-skeleton" style="--i:2;"></div>
+ <div class="integration-skeleton" style="--i:3;"></div>
+ <div class="integration-skeleton" style="--i:4;"></div>
+ </section>
+ </div>
</div>
- <footer>
- <a href="https://astro.build/chat" target="_blank">Join us on Discord</a>
- <a href="https://astro.build" target="_blank">Visit the Astro website</a>
- </footer>
+ <section id="links">
+ ${links
+ .map(
+ (link) =>
+ `<a href="${link.link}" target="_blank"><astro-dev-overlay-icon ${
+ isDefinedIcon(link.icon) ? `icon="${link.icon}">` : `>${link.icon}`
+ }</astro-dev-overlay-icon>${link.name}</a>`
+ )
+ .join('')}
+ </section>
</div>
`
);
- canvas.append(window);
+ const copyDebugButton =
+ windowComponent.querySelector<HTMLButtonElement>('#copy-debug-button');
+
+ copyDebugButton?.addEventListener('click', () => {
+ navigator.clipboard.writeText(
+ '```\n' + (window as DevOverlayMetadata).__astro_dev_overlay__.debugInfo + '\n```'
+ );
+ copyDebugButton.textContent = 'Copied to clipboard!';
+
+ setTimeout(() => {
+ resetDebugButton();
+ }, 3500);
+ });
+
+ canvas.append(windowComponent);
+ }
+
+ function resetDebugButton() {
+ const copyDebugButton = canvas.querySelector<HTMLButtonElement>('#copy-debug-button');
+ if (!copyDebugButton) return;
+
+ copyDebugButton.innerHTML = 'Copy debug info <astro-dev-overlay-icon icon="copy" />';
+ }
+
+ function refreshIntegrationList() {
+ const integrationList = canvas.querySelector<HTMLElement>('#integration-list');
+
+ if (!integrationList) return;
+ integrationList.innerHTML = '';
+
+ const fragment = document.createDocumentFragment();
+ for (const integration of integrationData.data) {
+ const integrationComponent = document.createElement('astro-dev-overlay-card');
+ integrationComponent.link = integration.homepageUrl;
+
+ const integrationContainer = document.createElement('div');
+ integrationContainer.className = 'integration-container';
+
+ const integrationImage = document.createElement('div');
+ integrationImage.className = 'integration-image';
+
+ if (integration.image) {
+ const img = document.createElement('img');
+ img.src = integration.image;
+ img.alt = integration.title;
+ integrationImage.append(img);
+ } else {
+ const icon = document.createElement('astro-dev-overlay-icon');
+ icon.icon = iconForIntegration(integration);
+ integrationImage.append(icon);
+ integrationImage.style.setProperty(
+ '--integration-image-background',
+ colorForIntegration()
+ );
+ }
+
+ integrationContainer.append(integrationImage);
+
+ let integrationTitle = document.createElement('h3');
+ integrationTitle.textContent = integration.title;
+ if (integration.official || integration.categories.includes('official')) {
+ integrationTitle.innerHTML +=
+ ' <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 21 20"><rect width="19" height="19" x="1.16602" y=".5" fill="url(#paint0_linear_917_1096)" fill-opacity=".33" rx="9.5"/><path fill="#fff" d="M15.139 6.80657c-.062-.06248-.1357-.11208-.217-.14592-.0812-.03385-.1683-.05127-.2563-.05127-.0881 0-.1752.01742-.2564.05127-.0813.03384-.155.08344-.217.14592L9.22566 11.7799 7.13899 9.68657c-.06435-.06216-.14031-.11103-.22355-.14383-.08323-.03281-.17211-.04889-.26157-.04735-.08945.00155-.17773.0207-.25978.05637a.68120694.68120694 0 0 0-.21843.15148c-.06216.06435-.11104.14031-.14384.22355-.0328.08321-.04889.17211-.04734.26161.00154.0894.0207.1777.05636.2597.03566.0821.08714.1563.15148.2185l2.56 2.56c.06198.0625.13571.1121.21695.1459s.16838.0513.25639.0513c.088 0 .17514-.0175.25638-.0513s.15497-.0834.21695-.1459L15.139 7.78657c.0677-.06242.1217-.13819.1586-.22253.0369-.08433.056-.1754.056-.26747 0-.09206-.0191-.18313-.056-.26747-.0369-.08433-.0909-.1601-.1586-.22253Z"/><rect width="19" height="19" x="1.16602" y=".5" stroke="url(#paint1_linear_917_1096)" rx="9.5"/><defs><linearGradient id="paint0_linear_917_1096" x1="20.666" x2="-3.47548" y1=".00000136" y2="10.1345" gradientUnits="userSpaceOnUse"><stop stop-color="#4AF2C8"/><stop offset="1" stop-color="#2F4CB3"/></linearGradient><linearGradient id="paint1_linear_917_1096" x1="20.666" x2="-3.47548" y1=".00000136" y2="10.1345" gradientUnits="userSpaceOnUse"><stop stop-color="#4AF2C8"/><stop offset="1" stop-color="#2F4CB3"/></linearGradient></defs></svg>';
+ }
+ integrationContainer.append(integrationTitle);
+
+ const integrationDescription = document.createElement('p');
+ integrationDescription.textContent =
+ integration.description.length > 90
+ ? integration.description.slice(0, 90) + '…'
+ : integration.description;
+
+ integrationContainer.append(integrationDescription);
+ integrationComponent.append(integrationContainer);
+
+ fragment.append(integrationComponent);
+ }
+
+ integrationList.append(fragment);
}
- },
- async beforeTogglingOff(canvas) {
- return await waitForTransition(canvas);
},
} satisfies DevOverlayPlugin;
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
index 6961e1227..515b85b71 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
@@ -1,10 +1,10 @@
import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js';
import type { DevOverlayHighlight } from '../ui-library/highlight.js';
-import { getIconElement } from '../ui-library/icons.js';
import { attachTooltipToHighlight, createHighlight, positionHighlight } from './utils/highlight.js';
+import { createWindowElement } from './utils/window.js';
const icon =
- '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 16"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 1 20 16"><path fill="#fff" d="M.6 2A1.1 1.1 0 0 1 1.7.9h16.6a1.1 1.1 0 1 1 0 2.2H1.6A1.1 1.1 0 0 1 .8 2Zm1.1 7.1h6a1.1 1.1 0 0 0 0-2.2h-6a1.1 1.1 0 0 0 0 2.2ZM9.3 13H1.8a1.1 1.1 0 1 0 0 2.2h7.5a1.1 1.1 0 1 0 0-2.2Zm11.3 1.9a1.1 1.1 0 0 1-1.5 0l-1.7-1.7a4.1 4.1 0 1 1 1.6-1.6l1.6 1.7a1.1 1.1 0 0 1 0 1.6Zm-5.3-3.4a1.9 1.9 0 1 0 0-3.8 1.9 1.9 0 0 0 0 3.8Z"/></svg>';
interface AuditRule {
title: string;
@@ -32,8 +32,6 @@ export default {
document.addEventListener('astro:page-load', async () => refreshLintPositions);
async function lint() {
- initStyle();
-
audits.forEach(({ highlightElement }) => {
highlightElement.remove();
});
@@ -65,20 +63,38 @@ export default {
})
);
- const noAuditBlock = document.createElement('div');
- noAuditBlock.id = 'no-audit';
-
- const noAuditIcon = getIconElement('check-circle');
- const text = document.createElement('div');
- text.textContent = 'No issues found!';
-
- if (noAuditIcon) {
- noAuditIcon.style.width = '24px';
- noAuditBlock.append(noAuditIcon);
- }
- noAuditBlock.append(text);
+ const window = createWindowElement(
+ `<style>
+ header {
+ display: flex;
+ }
+
+ h1 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0;
+ font-size: 22px;
+ }
+
+ astro-dev-overlay-icon {
+ width: 1em;
+ height: 1em;
+ padding: 8px;
+ display: block;
+ background: green;
+ border-radius: 9999px;
+ }
+ </style>
+ <header>
+ <h1><astro-dev-overlay-icon icon="check-circle"></astro-dev-overlay-icon>No issues detected.</h1>
+ </header>
+ `
+ );
- canvas.append(noAuditBlock);
+ canvas.append(window);
}
(['scroll', 'resize'] as const).forEach((event) => {
@@ -163,51 +179,5 @@ export default {
return tooltip;
}
-
- function initStyle() {
- const devOverlayRect = document
- .querySelector('astro-dev-overlay')
- ?.shadowRoot.querySelector('#dev-overlay')
- ?.getBoundingClientRect();
-
- const style = document.createElement('style');
- style.textContent = `
- :host {
- opacity: 0;
- transition: opacity 0.1s ease-in-out;
- }
-
- :host([data-active]) {
- opacity: 1;
- }
-
- #no-audit {
- border: 1px solid rgba(113, 24, 226, 1);
- background-color: #310A65;
- box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.30), 0px 1px 2px 0px rgba(0, 0, 0, 0.29), 0px 4px 4px 0px rgba(0, 0, 0, 0.26), 0px 10px 6px 0px rgba(0, 0, 0, 0.15), 0px 17px 7px 0px rgba(0, 0, 0, 0.04), 0px 26px 7px 0px rgba(0, 0, 0, 0.01);
- color: white;
- text-align: center;
- border-radius: 4px;
- padding: 8px;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- position: fixed;
- transform: translate(-50%, 0);
- top: ${(devOverlayRect?.top ?? 0) - (devOverlayRect?.height ?? 0) - 16}px;
- left: calc(50% + 12px);
- width: 200px;
- }
- `;
-
- canvas.append(style);
- }
- },
- async beforeTogglingOff(canvas) {
- canvas.host?.removeAttribute('data-active');
-
- await new Promise((resolve) => {
- canvas.host.addEventListener('transitionend', resolve);
- });
-
- return true;
},
} satisfies DevOverlayPlugin;
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts
index e0d338446..72fb65f97 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts
@@ -1,6 +1,6 @@
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
import { settings, type Settings } from '../settings.js';
-import { createWindowWithTransition, waitForTransition } from './utils/window.js';
+import { createWindowElement } from './utils/window.js';
interface SettingRow {
name: string;
@@ -13,7 +13,7 @@ interface SettingRow {
const settingsRows = [
{
name: 'Disable notifications',
- description: 'Notification bubbles will not be shown when this is enabled.',
+ description: 'Hide notification badges in the toolbar.',
input: 'checkbox',
settingKey: 'disablePluginNotification',
changeEvent: (evt: Event) => {
@@ -37,7 +37,7 @@ const settingsRows = [
export default {
id: 'astro:settings',
- name: 'Overlay settings',
+ name: 'Settings',
icon: 'gear',
init(canvas) {
createSettingsWindow();
@@ -45,10 +45,15 @@ export default {
document.addEventListener('astro:after-swap', createSettingsWindow);
function createSettingsWindow() {
- const window = createWindowWithTransition(
- 'Settings',
- 'gear',
+ const windowElement = createWindowElement(
`<style>
+ :host astro-dev-overlay-window {
+ height: 480px;
+ }
+ header {
+ display: flex;
+ }
+
h2, h3 {
margin-top: 0;
}
@@ -67,18 +72,40 @@ export default {
}
label {
- font-size: 15px;
+ font-size: 14px;
line-height: 1.5rem;
}
+
+ h1 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0;
+ font-size: 22px;
+ }
+
+ astro-dev-overlay-icon {
+ width: 1em;
+ height: 1em;
+ display: block;
+ }
</style>
+ <header>
+ <h1><astro-dev-overlay-icon icon="gear"></astro-dev-overlay-icon> Settings</h1>
+ </header>
+
+ <hr />
+
<h2>General</h2>
- `,
- settingsRows.flatMap((setting) => [
- getElementForSettingAsString(setting),
- document.createElement('hr'),
- ])
+ `
);
- canvas.append(window);
+ for (const settingsRow of settingsRows) {
+ windowElement.append(getElementForSettingAsString(settingsRow));
+ windowElement.append(document.createElement('hr'));
+ }
+ canvas.append(windowElement);
function getElementForSettingAsString(setting: SettingRow) {
const label = document.createElement('label');
@@ -100,7 +127,4 @@ export default {
}
}
},
- async beforeTogglingOff(canvas) {
- return await waitForTransition(canvas);
- },
} satisfies DevOverlayPlugin;
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
index 3af467ecd..e2d74c0ff 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
@@ -15,6 +15,19 @@ export function createHighlight(rect: DOMRect, icon?: Icon) {
return highlight;
}
+export function getHighlightZIndex(el: Element) {
+ let highestZIndex = 0;
+ let current: Element | ParentNode | null = el;
+ while (current instanceof Element) {
+ let zIndex = Number(getComputedStyle(current).zIndex);
+ if (!Number.isNaN(zIndex) && zIndex > highestZIndex) {
+ highestZIndex = zIndex;
+ }
+ current = current.parentNode;
+ }
+ return highestZIndex + 1;
+}
+
export function positionHighlight(highlight: DevOverlayHighlight, rect: DOMRect) {
highlight.style.display = 'block';
// Make an highlight that is 10px bigger than the element on all sides
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts
new file mode 100644
index 000000000..ec5a34ad8
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/icons.ts
@@ -0,0 +1,43 @@
+import type { Integration } from '../astro.js';
+
+function randomFromArray<T>(list: T[]) {
+ return list[Math.floor(Math.random() * list.length)];
+}
+
+const categoryIcons = new Map(
+ Object.entries({
+ frameworks: ['puzzle', 'grid'],
+ adapters: ['puzzle', 'grid', 'compress'],
+ 'css+ui': ['compress', 'grid', 'image', 'resizeImage', 'puzzle'],
+ 'performance+seo': ['approveUser', 'checkCircle', 'compress', 'robot', 'searchFile', 'sitemap'],
+ analytics: ['checkCircle', 'compress', 'searchFile'],
+ accessibility: ['approveUser', 'checkCircle'],
+ other: ['checkCircle', 'grid', 'puzzle', 'sitemap'],
+ })
+);
+
+export function iconForIntegration(integration: Integration) {
+ const icons = integration.categories
+ .filter((category: string) => categoryIcons.has(category))
+ .map((category: string) => categoryIcons.get(category)!)
+ .flat();
+
+ return randomFromArray(icons);
+}
+
+const iconColors = [
+ '#BC52EE',
+ '#6D6AF0',
+ '#52EEBD',
+ '#52B7EE',
+ '#52EE55',
+ '#B7EE52',
+ '#EEBD52',
+ '#EE5552',
+ '#EE52B7',
+ '#858B98',
+];
+
+export function colorForIntegration() {
+ return randomFromArray(iconColors);
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts
index 04f09d6e6..7b152cc55 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/window.ts
@@ -1,56 +1,5 @@
-import type { Icon } from '../../ui-library/icons.js';
-
-export function createWindowWithTransition(
- title: string,
- icon: Icon,
- windowContent: string,
- addedNodes: Node[] = []
-): DocumentFragment {
- const fragment = document.createDocumentFragment();
-
- const style = document.createElement('style');
- style.textContent = `
- :host {
- opacity: 0;
- transition: opacity 0.15s ease-in-out;
- }
-
- :host([data-active]) {
- opacity: 1;
- }
-
- @media screen and (prefers-reduced-motion: no-preference) {
- :host astro-dev-overlay-window {
- transform: translateY(55px) translate(-50%, -50%);
- transition: transform 0.15s ease-in-out;
- transform-origin: center bottom;
- }
-
- :host([data-active]) astro-dev-overlay-window {
- transform: translateY(0) translate(-50%, -50%);
- }
- }
- `;
- fragment.append(style);
-
- const window = document.createElement('astro-dev-overlay-window');
- window.windowTitle = title;
- window.windowIcon = icon;
- window.innerHTML = windowContent;
-
- window.append(...addedNodes);
-
- fragment.append(window);
-
- return fragment;
-}
-
-export async function waitForTransition(canvas: ShadowRoot): Promise<boolean> {
- canvas.host?.removeAttribute('data-active');
-
- await new Promise((resolve) => {
- canvas.host.addEventListener('transitionend', resolve);
- });
-
- return true;
+export function createWindowElement(content: string) {
+ const windowElement = document.createElement('astro-dev-overlay-window');
+ windowElement.innerHTML = content;
+ return windowElement;
}
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts
index ab927bacc..782ede4d2 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts
@@ -1,13 +1,19 @@
import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js';
import type { DevOverlayHighlight } from '../ui-library/highlight.js';
-import { attachTooltipToHighlight, createHighlight, positionHighlight } from './utils/highlight.js';
+import {
+ attachTooltipToHighlight,
+ createHighlight,
+ getHighlightZIndex,
+ positionHighlight,
+} from './utils/highlight.js';
+import { createWindowElement } from './utils/window.js';
const icon =
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#fff" d="M7.9 1.5v-.4a1.1 1.1 0 0 1 2.2 0v.4a1.1 1.1 0 1 1-2.2 0Zm-6.4 8.6a1.1 1.1 0 1 0 0-2.2h-.4a1.1 1.1 0 0 0 0 2.2h.4ZM12 3.7a1.1 1.1 0 0 0 1.4-.7l.4-1.1a1.1 1.1 0 0 0-2.1-.8l-.4 1.2a1.1 1.1 0 0 0 .7 1.4Zm-9.7 7.6-1.2.4a1.1 1.1 0 1 0 .8 2.1l1-.4a1.1 1.1 0 1 0-.6-2ZM20.8 17a1.9 1.9 0 0 1 0 2.6l-1.2 1.2a1.9 1.9 0 0 1-2.6 0l-4.3-4.2-1.6 3.6a1.9 1.9 0 0 1-1.7 1.2A1.9 1.9 0 0 1 7.5 20L2.7 5a1.9 1.9 0 0 1 2.4-2.4l15 5a1.9 1.9 0 0 1 .2 3.4l-3.7 1.6 4.2 4.3ZM19 18.3 14.6 14a1.9 1.9 0 0 1 .6-3l3.2-1.5L5.1 5.1l4.3 13.3 1.5-3.2a1.9 1.9 0 0 1 3-.6l4.4 4.4.7-.7Z"/></svg>';
export default {
id: 'astro:xray',
- name: 'Xray',
+ name: 'Inspect',
icon: icon,
init(canvas) {
let islandsOverlays: { highlightElement: DevOverlayHighlight; island: HTMLElement }[] = [];
@@ -18,7 +24,6 @@ export default {
document.addEventListener('astro:page-load', refreshIslandsOverlayPositions);
function addIslandsOverlay() {
- initStyle();
islandsOverlays.forEach(({ highlightElement }) => {
highlightElement.remove();
});
@@ -26,6 +31,42 @@ export default {
const islands = document.querySelectorAll<HTMLElement>('astro-island');
+ if (islands.length === 0) {
+ const window = createWindowElement(
+ `<style>
+ header {
+ display: flex;
+ }
+
+ h1 {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0;
+ font-size: 22px;
+ }
+
+ astro-dev-overlay-icon {
+ width: 1em;
+ height: 1em;
+ padding: 8px;
+ display: block;
+ background: #5f9ea0;
+ border-radius: 9999px;
+ }
+ </style>
+ <header>
+ <h1><astro-dev-overlay-icon icon="lightbulb"></astro-dev-overlay-icon>No islands detected.</h1>
+ </header>
+ `
+ );
+
+ canvas.append(window);
+ return;
+ }
+
islands.forEach((island) => {
const computedStyle = window.getComputedStyle(island);
const islandElement = (island.children[0] as HTMLElement) || island;
@@ -41,6 +82,10 @@ export default {
const tooltip = buildIslandTooltip(island);
attachTooltipToHighlight(highlight, tooltip, islandElement);
+ // Set the z-index to be 1 higher than the greatest z-index in the stack.
+ const zIndex = getHighlightZIndex(islandElement);
+ tooltip.style.zIndex = highlight.style.zIndex = zIndex + '';
+
canvas.append(highlight);
islandsOverlays.push({ highlightElement: highlight, island: islandElement });
});
@@ -111,30 +156,5 @@ export default {
const [_, value] = prop;
return JSON.stringify(value, null, 2);
}
-
- function initStyle() {
- const style = document.createElement('style');
- style.textContent = `
- :host {
- opacity: 0;
- transition: opacity 0.1s ease-in-out;
- }
-
- :host([data-active]) {
- opacity: 1;
- }
- `;
-
- canvas.append(style);
- }
- },
- async beforeTogglingOff(canvas) {
- canvas.host?.removeAttribute('data-active');
-
- await new Promise((resolve) => {
- canvas.host.addEventListener('transitionend', resolve);
- });
-
- return true;
},
} satisfies DevOverlayPlugin;
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts
new file mode 100644
index 000000000..5a8eea07e
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/badge.ts
@@ -0,0 +1,71 @@
+type BadgeSize = 'small' | 'large';
+type BadgeStyle = 'purple' | 'gray' | 'red' | 'green' | 'yellow';
+
+export class DevOverlayBadge extends HTMLElement {
+ size: BadgeSize = 'small';
+ badgeStyle: BadgeStyle = 'purple';
+
+ shadowRoot: ShadowRoot;
+
+ constructor() {
+ super();
+ this.shadowRoot = this.attachShadow({ mode: 'open' });
+
+ if (this.hasAttribute('size')) this.size = this.getAttribute('size') as BadgeSize;
+
+ if (this.hasAttribute('badge-style'))
+ this.badgeStyle = this.getAttribute('badge-style') as BadgeStyle;
+
+ const classes = [`badge--${this.size}`, `badge--${this.badgeStyle}`];
+ this.shadowRoot.innerHTML = `
+ <style>
+ .badge {
+ box-sizing: border-box;
+ border-radius: 4px;
+ border: 1px solid transparent;
+ padding: 8px;
+ font-size: 12px;
+ color: #fff;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ }
+
+ .badge--large {
+ height: 24px;
+ }
+
+ .badge--gray {
+ color: rgba(191, 193, 201, 1);
+ border-color: rgba(191, 193, 201, 1);
+ }
+
+ .badge--purple {
+ color: rgba(224, 204, 250, 1);
+ border-color: rgba(113, 24, 226, 1);
+ }
+
+ .badge--red {
+ color: rgba(249, 196, 215, 1);
+ border-color: rgba(179, 62, 102, 1);
+ }
+
+ .badge--green {
+ color: rgba(213, 249, 196, 1);
+ border-color: rgba(61, 125, 31, 1);
+ }
+
+ .badge--yellow {
+ color: rgba(249, 233, 196, 1);
+ border-color: rgba(181, 138, 45, 1);
+ }
+ </style>
+
+ <div class="badge ${classes.join(' ')}">
+ <slot></slot>
+ </div>
+ `;
+ }
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts
new file mode 100644
index 000000000..7c39fdc1d
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/button.ts
@@ -0,0 +1,88 @@
+type ButtonSize = 'small' | 'medium' | 'large';
+type ButtonStyle = 'ghost' | 'outline' | 'purple' | 'gray' | 'red';
+
+export class DevOverlayButton extends HTMLElement {
+ size: ButtonSize = 'small';
+ buttonStyle: ButtonStyle = 'purple';
+
+ shadowRoot: ShadowRoot;
+
+ constructor() {
+ super();
+ this.shadowRoot = this.attachShadow({ mode: 'open' });
+
+ if (this.hasAttribute('size')) this.size = this.getAttribute('size') as ButtonSize;
+
+ if (this.hasAttribute('button-style'))
+ this.buttonStyle = this.getAttribute('button-style') as ButtonStyle;
+
+ const classes = [`button--${this.size}`, `button--${this.buttonStyle}`];
+
+ this.shadowRoot.innerHTML = `
+ <style>
+ button {
+ border: 1px solid transparent;
+ color: #fff;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ button:hover {
+ cursor: pointer;
+ }
+
+ .button--small {
+ font-size: 12px;
+ padding: 4px 8px;
+ }
+
+ .button--medium {
+ font-size: 14px;
+ padding: 8px 12px;
+ }
+
+ .button--large {
+ font-size: 16px;
+ padding: 12px 16px;
+ }
+
+ .button--ghost {
+ background: transparent;
+ }
+
+ .button--outline {
+ background: transparent;
+ border-color: #fff;
+ }
+
+ .button--purple {
+ background: rgba(113, 24, 226, 1);
+ border-color: rgba(224, 204, 250, 0.33);
+ }
+
+ .button--gray {
+ background: rgba(52, 56, 65, 1);
+ border-color: rgba(71, 78, 94, 1);
+ }
+
+ .button--red {
+ background: rgba(179, 62, 102, 1);
+ border-color: rgba(249, 196, 215, 0.33);
+ }
+
+ ::slotted(astro-dev-overlay-icon) {
+ display: inline-block;
+ height: 1em;
+ width: 1em;
+ margin-left: 0.5em;
+ }
+ </style>
+
+ <button class="${classes.join(' ')}">
+ <slot></slot>
+ </button>
+ `;
+ }
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts
index 9d7062f8b..90d4739f1 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/card.ts
@@ -1,8 +1,6 @@
-import { getIconElement, isDefinedIcon, type Icon } from './icons.js';
-
export class DevOverlayCard extends HTMLElement {
- icon?: Icon;
link?: string | undefined | null;
+ clickAction?: () => void | (() => Promise<void>);
shadowRoot: ShadowRoot;
constructor() {
@@ -10,26 +8,30 @@ export class DevOverlayCard extends HTMLElement {
this.shadowRoot = this.attachShadow({ mode: 'open' });
this.link = this.getAttribute('link');
- this.icon = this.hasAttribute('icon') ? (this.getAttribute('icon') as Icon) : undefined;
}
connectedCallback() {
- const element = this.link ? 'a' : 'button';
+ const element = this.link ? 'a' : this.clickAction ? 'button' : 'div';
this.shadowRoot.innerHTML = `
<style>
- a, button {
+ :host>a, :host>button, :host>div {
+ box-sizing: border-box;
+ padding: 16px;
display: block;
- padding: 40px 16px;
border-radius: 8px;
border: 1px solid rgba(35, 38, 45, 1);
- color: #fff;
- font-size: 16px;
- font-weight: 600;
- line-height: 19px;
+ color: rgba(191, 193, 201, 1);
text-decoration: none;
background-color: #13151A;
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.10), 0px 4px 4px 0px rgba(0, 0, 0, 0.09), 0px 10px 6px 0px rgba(0, 0, 0, 0.05), 0px 17px 7px 0px rgba(0, 0, 0, 0.01), 0px 26px 7px 0px rgba(0, 0, 0, 0.00);
+ width: 100%;
+ height: 100%;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ color: #fff;
+ font-weight: 600;
}
a:hover, button:hover {
@@ -49,26 +51,15 @@ export class DevOverlayCard extends HTMLElement {
}
</style>
- <${element}${this.link ? ` href="${this.link}" target="_blank"` : ``}>
- ${this.icon ? this.getElementForIcon(this.icon) : ''}
- <span><slot /></span>
+ <${element}${this.link ? ` href="${this.link}" target="_blank"` : ``} id="astro-overlay-card">
+ <slot />
</${element}>
`;
- }
- getElementForIcon(icon: Icon) {
- let iconElement;
- if (isDefinedIcon(icon)) {
- iconElement = getIconElement(icon);
- } else {
- iconElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- iconElement.setAttribute('viewBox', '0 0 16 16');
- iconElement.innerHTML = icon;
+ if (this.clickAction) {
+ this.shadowRoot
+ .getElementById('astro-overlay-card')
+ ?.addEventListener('click', this.clickAction);
}
-
- iconElement?.style.setProperty('height', '24px');
- iconElement?.style.setProperty('width', '24px');
-
- return iconElement?.outerHTML ?? '';
}
}
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts
index 7d91535e0..6a1b914a8 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/highlight.ts
@@ -26,6 +26,7 @@ export class DevOverlayHighlight extends HTMLElement {
.icon {
width: 24px;
height: 24px;
+ color: white;
background: linear-gradient(0deg, #B33E66, #B33E66), linear-gradient(0deg, #351722, #351722);
border: 1px solid rgba(53, 23, 34, 1);
border-radius: 9999px;
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts
new file mode 100644
index 000000000..3211e1857
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/icon.ts
@@ -0,0 +1,51 @@
+import { getIconElement, isDefinedIcon, type Icon } from './icons.js';
+
+export class DevOverlayIcon extends HTMLElement {
+ _icon: Icon | undefined = undefined;
+ shadowRoot: ShadowRoot;
+
+ get icon() {
+ return this._icon;
+ }
+ set icon(name: Icon | undefined) {
+ this._icon = name;
+ this.buildTemplate();
+ }
+
+ constructor() {
+ super();
+
+ this.shadowRoot = this.attachShadow({ mode: 'open' });
+
+ if (this.hasAttribute('icon')) {
+ this.icon = this.getAttribute('icon') as Icon;
+ } else {
+ this.buildTemplate();
+ }
+ }
+
+ getIconHTML(icon: Icon | undefined) {
+ if (icon && isDefinedIcon(icon)) {
+ return getIconElement(icon)?.outerHTML ?? '';
+ }
+
+ // If the icon that was passed isn't one of the predefined one, assume that they're passing it in as a slot
+ return '<slot />';
+ }
+
+ buildTemplate() {
+ this.shadowRoot.innerHTML = `
+ <style>
+ svg {
+ width: 100%;
+ height: 100%;
+ }
+
+ @media (forced-colors: active) {
+ svg path[fill="#fff"] {
+ fill: black;
+ }
+ }
+ </style>\n${this.getIconHTML(this._icon)}`;
+ }
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts
index d9445e44a..28878ef11 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts
@@ -5,10 +5,12 @@ export function isDefinedIcon(icon: Icon): icon is DefinedIcon {
return icon in icons;
}
+export function getIconElement(name: DefinedIcon): SVGElement;
+export function getIconElement(name: string & NonNullable<unknown>): undefined;
export function getIconElement(
- name: keyof typeof icons | (string & NonNullable<unknown>)
+ name: DefinedIcon | (string & NonNullable<unknown>)
): SVGElement | undefined {
- const icon = icons[name as keyof typeof icons];
+ const icon = icons[name as DefinedIcon];
if (!icon) {
return undefined;
@@ -22,15 +24,41 @@ export function getIconElement(
const icons = {
'astro:logo': `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 85 107"><path fill="#fff" d="M27.6 91.1c-4.8-4.4-6.3-13.7-4.2-20.4 3.5 4.2 8.3 5.6 13.3 6.3 7.7 1.2 15.3.8 22.5-2.8l2.5-1.4c.7 2 .9 3.9.6 5.9-.6 4.9-3 8.7-6.9 11.5-1.5 1.2-3.2 2.2-4.8 3.3-4.9 3.3-6.2 7.2-4.4 12.9l.2.6a13 13 0 0 1-5.7-5 13.8 13.8 0 0 1-2.2-7.4c0-1.3 0-2.7-.2-4-.5-3.1-2-4.6-4.8-4.7a5.5 5.5 0 0 0-5.7 4.6l-.2.6Z"/><path fill="url(#a)" d="M27.6 91.1c-4.8-4.4-6.3-13.7-4.2-20.4 3.5 4.2 8.3 5.6 13.3 6.3 7.7 1.2 15.3.8 22.5-2.8l2.5-1.4c.7 2 .9 3.9.6 5.9-.6 4.9-3 8.7-6.9 11.5-1.5 1.2-3.2 2.2-4.8 3.3-4.9 3.3-6.2 7.2-4.4 12.9l.2.6a13 13 0 0 1-5.7-5 13.8 13.8 0 0 1-2.2-7.4c0-1.3 0-2.7-.2-4-.5-3.1-2-4.6-4.8-4.7a5.5 5.5 0 0 0-5.7 4.6l-.2.6Z"/><path fill="#fff" d="M0 69.6s14.3-7 28.7-7l10.8-33.5c.4-1.6 1.6-2.7 3-2.7 1.2 0 2.4 1.1 2.8 2.7l10.9 33.5c17 0 28.6 7 28.6 7L60.5 3.2c-.7-2-2-3.2-3.5-3.2H27.8c-1.6 0-2.7 1.3-3.4 3.2L0 69.6Z"/><defs><linearGradient id="a" x1="22.5" x2="69.1" y1="107" y2="84.9" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>`,
- warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#fff" d="M8 .40625c-1.5019 0-2.97007.445366-4.21886 1.27978C2.53236 2.52044 1.55905 3.70642.984293 5.094.40954 6.48157.259159 8.00842.552165 9.48147.845172 10.9545 1.56841 12.3076 2.63041 13.3696c1.06201 1.062 2.41508 1.7852 3.88813 2.0782 1.47304.293 2.99989.1427 4.38746-.4321 1.3876-.5747 2.5736-1.5481 3.408-2.7968.8344-1.2488 1.2798-2.717 1.2798-4.2189-.0023-2.0133-.8031-3.9435-2.2267-5.36713C11.9435 1.20925 10.0133.408483 8 .40625ZM8 13.9062c-1.16814 0-2.31006-.3463-3.28133-.9953-.97128-.649-1.7283-1.5715-2.17533-2.6507-.44703-1.0792-.56399-2.26675-.3361-3.41245.22789-1.1457.79041-2.1981 1.61641-3.0241.82601-.826 1.8784-1.38852 3.0241-1.61641 1.1457-.2279 2.33325-.11093 3.41245.3361 1.0793.44703 2.0017 1.20405 2.6507 2.17532.649.97128.9954 2.11319.9954 3.28134-.0017 1.56592-.6245 3.0672-1.7318 4.1745S9.56592 13.9046 8 13.9062Zm-.84375-5.62495V4.625c0-.22378.0889-.43839.24713-.59662.15824-.15824.37285-.24713.59662-.24713.22378 0 .43839.08889.59662.24713.15824.15823.24713.37284.24713.59662v3.65625c0 .22378-.08889.43839-.24713.59662C8.43839 9.03611 8.22378 9.125 8 9.125c-.22377 0-.43838-.08889-.59662-.24713-.15823-.15823-.24713-.37284-.24713-.59662ZM9.125 11.0938c0 .2225-.06598.44-.18959.625-.12362.185-.29932.3292-.50489.4143-.20556.0852-.43176.1074-.64999.064-.21823-.0434-.41869-.1505-.57602-.3079-.15734-.1573-.26448-.3577-.30789-.576-.04341-.2182-.02113-.4444.06402-.65.08515-.2055.22934-.3812.41435-.5049.185-.1236.40251-.18955.62501-.18955.29837 0 .58452.11855.7955.32955.21098.2109.3295.4971.3295.7955Z"/></svg>`,
+ warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M8 .40625c-1.5019 0-2.97007.445366-4.21886 1.27978C2.53236 2.52044 1.55905 3.70642.984293 5.094.40954 6.48157.259159 8.00842.552165 9.48147.845172 10.9545 1.56841 12.3076 2.63041 13.3696c1.06201 1.062 2.41508 1.7852 3.88813 2.0782 1.47304.293 2.99989.1427 4.38746-.4321 1.3876-.5747 2.5736-1.5481 3.408-2.7968.8344-1.2488 1.2798-2.717 1.2798-4.2189-.0023-2.0133-.8031-3.9435-2.2267-5.36713C11.9435 1.20925 10.0133.408483 8 .40625ZM8 13.9062c-1.16814 0-2.31006-.3463-3.28133-.9953-.97128-.649-1.7283-1.5715-2.17533-2.6507-.44703-1.0792-.56399-2.26675-.3361-3.41245.22789-1.1457.79041-2.1981 1.61641-3.0241.82601-.826 1.8784-1.38852 3.0241-1.61641 1.1457-.2279 2.33325-.11093 3.41245.3361 1.0793.44703 2.0017 1.20405 2.6507 2.17532.649.97128.9954 2.11319.9954 3.28134-.0017 1.56592-.6245 3.0672-1.7318 4.1745S9.56592 13.9046 8 13.9062Zm-.84375-5.62495V4.625c0-.22378.0889-.43839.24713-.59662.15824-.15824.37285-.24713.59662-.24713.22378 0 .43839.08889.59662.24713.15824.15823.24713.37284.24713.59662v3.65625c0 .22378-.08889.43839-.24713.59662C8.43839 9.03611 8.22378 9.125 8 9.125c-.22377 0-.43838-.08889-.59662-.24713-.15823-.15823-.24713-.37284-.24713-.59662ZM9.125 11.0938c0 .2225-.06598.44-.18959.625-.12362.185-.29932.3292-.50489.4143-.20556.0852-.43176.1074-.64999.064-.21823-.0434-.41869-.1505-.57602-.3079-.15734-.1573-.26448-.3577-.30789-.576-.04341-.2182-.02113-.4444.06402-.65.08515-.2055.22934-.3812.41435-.5049.185-.1236.40251-.18955.62501-.18955.29837 0 .58452.11855.7955.32955.21098.2109.3295.4971.3295.7955Z"/></svg>`,
'arrow-down':
- '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 12 14"><path fill="#13151A" d="m11.0306 8.53063-4.5 4.49997c-.06968.0699-.15247.1254-.24364.1633-.09116.0378-.1889.0573-.28761.0573-.09871 0-.19645-.0195-.28762-.0573-.09116-.0379-.17395-.0934-.24363-.1633L.968098 8.53063c-.140896-.1409-.220051-.332-.220051-.53125 0-.19926.079155-.39036.220051-.53125.140892-.1409.331992-.22006.531252-.22006.19926 0 .39035.07916.53125.22006l3.21937 3.21937V1.5c0-.19891.07902-.38968.21967-.53033C5.61029.829018 5.80106.75 5.99997.75c.19891 0 .38968.079018.53033.21967.14065.14065.21967.33142.21967.53033v9.1875l3.21938-3.22c.14085-.1409.33195-.22005.53125-.22005.1993 0 .3904.07915.5312.22005.1409.1409.2201.33199.2201.53125s-.0792.39035-.2201.53125l-.0012.00063Z"/></svg>',
- bug: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 25 24"><path fill="#CCCED8" d="M13.7916 8.25006c0-.29667.088-.58668.2528-.83335.1648-.24668.3991-.43893.6732-.55247.2741-.11353.5757-.14323.8667-.08536.2909.05788.5582.20074.768.41052s.3526.47706.4105.76803c.0579.29097.0282.59257-.0854.86666-.1135.27409-.3057.50836-.5524.67318-.2467.16482-.5367.25279-.8334.25279-.3978 0-.7793-.15803-1.0606-.43934-.2813-.2813-.4394-.66283-.4394-1.06066Zm-3.75-1.5c-.29665 0-.58666.08798-.83333.2528-.24667.16482-.43893.39909-.55246.67318-.11354.27409-.14324.57569-.08536.86666.05788.29097.20074.55824.41052.76802.20977.20978.47705.35264.76802.41052.29101.05788.59261.02817.86671-.08536.274-.11353.5083-.30579.6731-.55246.1649-.24668.2528-.53668.2528-.83336 0-.39782-.158-.77935-.4393-1.06066-.2813-.2813-.6628-.43934-1.0607-.43934Zm11.25 6.75004c.0003.6512-.0733 1.3003-.2193 1.935l1.7953.7837c.1354.0592.2578.1445.3603.2511.1024.1065.1829.2322.2368.3698.0539.1377.0801.2846.0772.4323-.0028.1478-.0348.2936-.094.429-.0592.1354-.1446.2579-.2511.3603-.1065.1025-.2322.1829-.3698.2368-.1377.0539-.2846.0802-.4323.0773-.1478-.0029-.2936-.0349-.429-.0941l-1.6875-.7359c-.7348 1.3818-1.8317 2.5377-3.1732 3.3437s-2.8771 1.2319-4.4421 1.2319c-1.5651 0-3.10061-.4259-4.44213-1.2319-1.34151-.806-2.43843-1.9619-3.17321-3.3437l-1.6875.7359c-.13542.0592-.28119.0912-.42896.0941-.14778.0029-.29468-.0234-.43232-.0773-.13763-.0539-.2633-.1343-.36984-.2368-.10653-.1024-.19185-.2249-.25106-.3603-.05922-.1354-.09119-.2812-.09407-.429-.00289-.1477.02336-.2946.07725-.4323.05389-.1376.13436-.2633.23681-.3698.10246-.1066.22489-.1919.36032-.2511l1.79531-.7837c-.14354-.635-.21462-1.2841-.21187-1.935v-.375h-1.875c-.29837 0-.58452-.1186-.7955-.3295-.21098-.211-.3295-.4972-.3295-.7955 0-.2984.11852-.5846.3295-.7955.21098-.211.49713-.3295.7955-.3295h1.875v-.375c-.00029-.65126.0733-1.30041.21937-1.93504l-1.79531-.78375c-.27351-.11959-.4883-.34294-.59713-.6209-.10883-.27797-.10278-.58778.01682-.86128.11959-.27351.34294-.4883.6209-.59713.27797-.10883.58778-.10278.86128.01681l1.6875.73594c.73478-1.38183 1.8317-2.53769 3.17321-3.34373 1.34152-.80604 2.87703-1.23187 4.44213-1.23187 1.565 0 3.1006.42583 4.4421 1.23187 1.3415.80604 2.4384 1.9619 3.1732 3.34373l1.6875-.73594c.1354-.05921.2812-.09118.429-.09406.1477-.00289.2946.02336.4323.07725.1376.05389.2633.13435.3698.23681.1065.10245.1919.22489.2511.36032.0592.13542.0912.28118.094.42896.0029.14778-.0233.29468-.0772.43232-.0539.13763-.1344.2633-.2368.36984-.1025.10653-.2249.19185-.3603.25106l-1.7953.78375c.1435.63492.2146 1.28407.2118 1.93504v.375h1.875c.2984 0 .5845.1185.7955.3295.211.2109.3295.4971.3295.7955 0 .2983-.1185.5845-.3295.7955-.211.2109-.4971.3295-.7955.3295h-1.875v.375Zm-14.99997-2.625H19.0416v-.375c0-1.69079-.6716-3.3123-1.8672-4.50784-1.1955-1.19555-2.817-1.8672-4.5078-1.8672-1.6907 0-3.31224.67165-4.50778 1.8672C6.96328 7.1878 6.29163 8.80931 6.29163 10.5001v.375Zm5.24997 8.8987v-6.6487H6.29163v.375c.00211 1.4949.52876 2.9417 1.48816 4.0882.95939 1.1464 2.29071 1.9199 3.76181 2.1855Zm7.5-6.2737v-.375h-5.25v6.6487c1.4712-.2656 2.8025-1.0391 3.7619-2.1855.9594-1.1465 1.486-2.5933 1.4881-4.0882Z"/></svg>',
- 'file-search':
- '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 25 24"><path fill="#CCCED8" d="m20.6293 7.455-5.25-5.25c-.1045-.10461-.2285-.1876-.3651-.24422-.1366-.05662-.283-.08577-.4308-.08578H5.58337c-.49728 0-.97419.19754-1.32582.54917-.35163.35164-.54918.82855-.54918 1.32583v16.5c0 .4973.19755.9742.54918 1.3258.35163.3517.82854.5492 1.32582.5492H19.0834c.4973 0 .9742-.1975 1.3258-.5492.3516-.3516.5492-.8285.5492-1.3258v-12c0-.29813-.1184-.58407-.3291-.795Zm-3.1397.045h-2.1562V5.34375L17.4896 7.5ZM5.95837 19.875V4.125h7.12503v4.5c0 .29837.1185.58452.3295.7955.211.21097.4971.3295.7955.3295h4.5v10.125H5.95837Zm9.04503-4.5459c.3426-.7185.4202-1.5349.2192-2.3051-.2011-.7702-.6679-1.4445-1.3179-1.9038-.65-.4594-1.4415-.6742-2.2346-.6066-.7931.0677-1.5368.4135-2.0996.9763-.56283.5629-.90863 1.3065-.9763 2.0996-.06766.7931.14716 1.5846.60651 2.2346.45936.6501 1.13369 1.1169 1.90389 1.3179.7701.201 1.5866.1234 2.305-.2192l1.125 1.125c.2114.2114.498.3301.7969.3301.2989 0 .5855-.1187.7969-.3301.2113-.2113.3301-.498.3301-.7969 0-.2988-.1188-.5855-.3301-.7968l-1.125-1.125Zm-4.17-1.4541c0-.2225.066-.44.1896-.625.1236-.185.2993-.3292.5049-.4144.2055-.0851.4317-.1074.65-.064.2182.0434.4186.1506.576.3079.1573.1573.2644.3578.3079.576.0434.2183.0211.4445-.0641.65-.0851.2056-.2293.3813-.4143.5049-.185.1236-.4025.1896-.625.1896-.2984 0-.5845-.1185-.7955-.3295-.211-.211-.3295-.4971-.3295-.7955Z"/></svg>',
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 12 14"><path fill="currentColor" d="m11.0306 8.53063-4.5 4.49997c-.06968.0699-.15247.1254-.24364.1633-.09116.0378-.1889.0573-.28761.0573-.09871 0-.19645-.0195-.28762-.0573-.09116-.0379-.17395-.0934-.24363-.1633L.968098 8.53063c-.140896-.1409-.220051-.332-.220051-.53125 0-.19926.079155-.39036.220051-.53125.140892-.1409.331992-.22006.531252-.22006.19926 0 .39035.07916.53125.22006l3.21937 3.21937V1.5c0-.19891.07902-.38968.21967-.53033C5.61029.829018 5.80106.75 5.99997.75c.19891 0 .38968.079018.53033.21967.14065.14065.21967.33142.21967.53033v9.1875l3.21938-3.22c.14085-.1409.33195-.22005.53125-.22005.1993 0 .3904.07915.5312.22005.1409.1409.2201.33199.2201.53125s-.0792.39035-.2201.53125l-.0012.00063Z"/></svg>',
+ bug: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 25 24"><path fill="currentColor" d="M13.7916 8.25006c0-.29667.088-.58668.2528-.83335.1648-.24668.3991-.43893.6732-.55247.2741-.11353.5757-.14323.8667-.08536.2909.05788.5582.20074.768.41052s.3526.47706.4105.76803c.0579.29097.0282.59257-.0854.86666-.1135.27409-.3057.50836-.5524.67318-.2467.16482-.5367.25279-.8334.25279-.3978 0-.7793-.15803-1.0606-.43934-.2813-.2813-.4394-.66283-.4394-1.06066Zm-3.75-1.5c-.29665 0-.58666.08798-.83333.2528-.24667.16482-.43893.39909-.55246.67318-.11354.27409-.14324.57569-.08536.86666.05788.29097.20074.55824.41052.76802.20977.20978.47705.35264.76802.41052.29101.05788.59261.02817.86671-.08536.274-.11353.5083-.30579.6731-.55246.1649-.24668.2528-.53668.2528-.83336 0-.39782-.158-.77935-.4393-1.06066-.2813-.2813-.6628-.43934-1.0607-.43934Zm11.25 6.75004c.0003.6512-.0733 1.3003-.2193 1.935l1.7953.7837c.1354.0592.2578.1445.3603.2511.1024.1065.1829.2322.2368.3698.0539.1377.0801.2846.0772.4323-.0028.1478-.0348.2936-.094.429-.0592.1354-.1446.2579-.2511.3603-.1065.1025-.2322.1829-.3698.2368-.1377.0539-.2846.0802-.4323.0773-.1478-.0029-.2936-.0349-.429-.0941l-1.6875-.7359c-.7348 1.3818-1.8317 2.5377-3.1732 3.3437s-2.8771 1.2319-4.4421 1.2319c-1.5651 0-3.10061-.4259-4.44213-1.2319-1.34151-.806-2.43843-1.9619-3.17321-3.3437l-1.6875.7359c-.13542.0592-.28119.0912-.42896.0941-.14778.0029-.29468-.0234-.43232-.0773-.13763-.0539-.2633-.1343-.36984-.2368-.10653-.1024-.19185-.2249-.25106-.3603-.05922-.1354-.09119-.2812-.09407-.429-.00289-.1477.02336-.2946.07725-.4323.05389-.1376.13436-.2633.23681-.3698.10246-.1066.22489-.1919.36032-.2511l1.79531-.7837c-.14354-.635-.21462-1.2841-.21187-1.935v-.375h-1.875c-.29837 0-.58452-.1186-.7955-.3295-.21098-.211-.3295-.4972-.3295-.7955 0-.2984.11852-.5846.3295-.7955.21098-.211.49713-.3295.7955-.3295h1.875v-.375c-.00029-.65126.0733-1.30041.21937-1.93504l-1.79531-.78375c-.27351-.11959-.4883-.34294-.59713-.6209-.10883-.27797-.10278-.58778.01682-.86128.11959-.27351.34294-.4883.6209-.59713.27797-.10883.58778-.10278.86128.01681l1.6875.73594c.73478-1.38183 1.8317-2.53769 3.17321-3.34373 1.34152-.80604 2.87703-1.23187 4.44213-1.23187 1.565 0 3.1006.42583 4.4421 1.23187 1.3415.80604 2.4384 1.9619 3.1732 3.34373l1.6875-.73594c.1354-.05921.2812-.09118.429-.09406.1477-.00289.2946.02336.4323.07725.1376.05389.2633.13435.3698.23681.1065.10245.1919.22489.2511.36032.0592.13542.0912.28118.094.42896.0029.14778-.0233.29468-.0772.43232-.0539.13763-.1344.2633-.2368.36984-.1025.10653-.2249.19185-.3603.25106l-1.7953.78375c.1435.63492.2146 1.28407.2118 1.93504v.375h1.875c.2984 0 .5845.1185.7955.3295.211.2109.3295.4971.3295.7955 0 .2983-.1185.5845-.3295.7955-.211.2109-.4971.3295-.7955.3295h-1.875v.375Zm-14.99997-2.625H19.0416v-.375c0-1.69079-.6716-3.3123-1.8672-4.50784-1.1955-1.19555-2.817-1.8672-4.5078-1.8672-1.6907 0-3.31224.67165-4.50778 1.8672C6.96328 7.1878 6.29163 8.80931 6.29163 10.5001v.375Zm5.24997 8.8987v-6.6487H6.29163v.375c.00211 1.4949.52876 2.9417 1.48816 4.0882.95939 1.1464 2.29071 1.9199 3.76181 2.1855Zm7.5-6.2737v-.375h-5.25v6.6487c1.4712-.2656 2.8025-1.0391 3.7619-2.1855.9594-1.1465 1.486-2.5933 1.4881-4.0882Z"/></svg>',
+ '': '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 25 24"><path fill="currentColor" d="m20.6293 7.455-5.25-5.25c-.1045-.10461-.2285-.1876-.3651-.24422-.1366-.05662-.283-.08577-.4308-.08578H5.58337c-.49728 0-.97419.19754-1.32582.54917-.35163.35164-.54918.82855-.54918 1.32583v16.5c0 .4973.19755.9742.54918 1.3258.35163.3517.82854.5492 1.32582.5492H19.0834c.4973 0 .9742-.1975 1.3258-.5492.3516-.3516.5492-.8285.5492-1.3258v-12c0-.29813-.1184-.58407-.3291-.795Zm-3.1397.045h-2.1562V5.34375L17.4896 7.5ZM5.95837 19.875V4.125h7.12503v4.5c0 .29837.1185.58452.3295.7955.211.21097.4971.3295.7955.3295h4.5v10.125H5.95837Zm9.04503-4.5459c.3426-.7185.4202-1.5349.2192-2.3051-.2011-.7702-.6679-1.4445-1.3179-1.9038-.65-.4594-1.4415-.6742-2.2346-.6066-.7931.0677-1.5368.4135-2.0996.9763-.56283.5629-.90863 1.3065-.9763 2.0996-.06766.7931.14716 1.5846.60651 2.2346.45936.6501 1.13369 1.1169 1.90389 1.3179.7701.201 1.5866.1234 2.305-.2192l1.125 1.125c.2114.2114.498.3301.7969.3301.2989 0 .5855-.1187.7969-.3301.2113-.2113.3301-.498.3301-.7969 0-.2988-.1188-.5855-.3301-.7968l-1.125-1.125Zm-4.17-1.4541c0-.2225.066-.44.1896-.625.1236-.185.2993-.3292.5049-.4144.2055-.0851.4317-.1074.65-.064.2182.0434.4186.1506.576.3079.1573.1573.2644.3578.3079.576.0434.2183.0211.4445-.0641.65-.0851.2056-.2293.3813-.4143.5049-.185.1236-.4025.1896-.625.1896-.2984 0-.5845-.1185-.7955-.3295-.211-.211-.3295-.4971-.3295-.7955Z"/></svg>',
'check-circle':
- '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"><path fill="#fff" d="M10.0306 4.96938c.0699.06967.1254.15247.1633.24363.0378.09116.0573.1889.0573.28762 0 .09871-.0195.19645-.0573.28761-.0379.09116-.0934.17396-.1633.24364L6.53063 9.53187c-.06968.06992-.15247.1254-.24364.16326-.09116.03785-.1889.05734-.28761.05734-.09871 0-.19645-.01949-.28762-.05734-.09116-.03786-.17395-.09334-.24363-.16326l-1.5-1.5c-.06977-.06976-.12511-.15258-.16286-.24373-.03776-.09116-.05719-.18885-.05719-.28752 0-.09866.01943-.19635.05719-.28751.03775-.09115.09309-.17397.16286-.24373.06976-.06977.15259-.12511.24374-.16287.09115-.03775.18885-.05719.28751-.05719s.19636.01944.28751.05719c.09115.03776.17397.0931.24374.16287L6 7.9375l2.96938-2.97c.06978-.06961.15259-.12478.24371-.16237.09111-.03758.18874-.05683.2873-.05666.09856.00018.19612.01978.28711.05768.09098.0379.1736.09337.2431.16323ZM13.75 7c0 1.33502-.3959 2.64007-1.1376 3.7501-.7417 1.11-1.7959 1.9752-3.02928 2.4861-1.23341.5109-2.5906.6446-3.89998.3841-1.30937-.2605-2.5121-.9033-3.45611-1.8473-.944-.944-1.586877-2.14677-1.847328-3.45614-.26045-1.30937-.126777-2.66657.384114-3.89997C1.27471 3.18349 2.13987 2.12928 3.2499 1.38758 4.35994.645881 5.66498.25 7 .25c1.78961.001985 3.5053.713781 4.7708 1.97922C13.0362 3.49466 13.748 5.2104 13.75 7Zm-1.5 0c0-1.03835-.3079-2.05339-.8848-2.91674-.5769-.86336-1.3968-1.53627-2.35611-1.93363-.95931-.39736-2.01491-.50133-3.03331-.29875-1.0184.20257-1.95386.70258-2.68809 1.43681-.73422.73422-1.23424 1.66969-1.43681 2.68809-.20257 1.0184-.0986 2.074.29876 3.03331.39736.95931 1.07026 1.77921 1.93362 2.35611.86336.5769 1.87839.8848 2.91674.8848 1.39193-.0015 2.72643-.5551 3.7107-1.5393C11.6949 9.72642 12.2485 8.39193 12.25 7Z"/></svg>',
- gear: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22"><path fill="#fff" d="M11 6.12507c-.9642 0-1.90671.28592-2.7084.82159-.80169.53567-1.42653 1.29704-1.79551 2.18783-.36898.89081-.46552 1.87101-.27742 2.81661.18811.9457.6524 1.8143 1.33419 2.4961.68178.6818 1.55042 1.1461 2.49604 1.3342.9457.1881 1.9259.0916 2.8167-.2774s1.6521-.9938 2.1878-1.7955c.5357-.8017.8216-1.7442.8216-2.7084-.0015-1.2925-.5156-2.53161-1.4295-3.44553-.9139-.91392-2.153-1.42801-3.4455-1.4295Zm0 7.50003c-.5192 0-1.02669-.154-1.45837-.4424-.43168-.2885-.76813-.6984-.96681-1.1781-.19868-.4796-.25067-1.0074-.14938-1.5166.10129-.50924.35129-.97697.71841-1.34408.36711-.36712.83484-.61712 1.34405-.71841.5092-.10129 1.037-.0493 1.5166.14938.4797.19868.8897.53513 1.1781.96681.2884.43168.4424.9392.4424 1.4584 0 .6962-.2766 1.3638-.7688 1.8561-.4923.4923-1.16.7689-1.8562.7689Zm8.625-2.551v-.1481l1.3125-1.64155c.1102-.13755.1865-.29905.2228-.4715s.0316-.35102-.0137-.52131c-.2369-.89334-.5909-1.75142-1.0528-2.55188-.089-.15264-.2127-.28218-.3611-.37811-.1484-.09594-.3173-.15557-.493-.17408l-2.0888-.23437-.104-.10406-.2344-2.08969c-.0186-.17556-.0783-.34426-.1743-.49247-.0959-.1482-.2254-.27175-.3779-.36066-.8005-.46341-1.6589-.81869-2.5528-1.056559-.1704-.044683-.349-.048704-.5213-.01174-.1723.036965-.3335.113881-.4706.224549l-1.6415 1.3125h-.1482l-1.64152-1.3125C9.14683.9524 8.98532.87608 8.81288.839767c-.17245-.036314-.35102-.031606-.52132.013744-.89357.238319-1.75165.593909-2.55187 1.057499-.15205.08854-.28121.2115-.37712.35901-.0959.14752-.15586.31547-.17507.49037l-.23437 2.08875-.10407.10406-2.08968.23437c-.17556.01865-.34426.07835-.49247.17428-.14821.09593-.27176.22539-.36066.37791-.46211.80072-.81613 1.65912-1.052812 2.55281-.045195.17016-.049823.34855-.013512.52082.03631.17227.112546.33362.222574.47106L2.375 10.926v.1481l-1.3125 1.6416c-.110173.1375-.186492.299-.222806.4715-.036313.1724-.031605.351.013744.5213.238622.8936.594522 1.7517 1.058442 2.5519.08844.1519.21126.281.3586.3769.14734.0959.3151.1559.48983.1753l2.08875.2325.10407.104.23437 2.0916c.01865.1756.07835.3443.17428.4925.09592.1482.22538.2717.37791.3606.80052.4634 1.65893.8187 2.55281 1.0566.17045.0447.349.0487.52129.0117.17228-.0369.33347-.1139.47059-.2245l1.64152-1.3125h.1482l1.6415 1.3125c.1376.1101.2991.1865.4715.2228.1725.0363.351.0316.5213-.0138.8934-.2368 1.7514-.5908 2.5519-1.0528.1524-.0883.2819-.2112.3782-.3587.0962-.1475.1565-.3156.1759-.4907l.2325-2.0887.104-.1041 2.0897-.239c.1751-.0194.3432-.0797.4907-.1759.1475-.0963.2704-.2258.3587-.3782.4634-.8005.8187-1.6589 1.0566-2.5528.0448-.1699.0493-.3479.013-.5198-.0363-.172-.1124-.333-.2221-.4702l-1.3125-1.6416Zm-2.2612-.4584c.015.256.015.5127 0 .7687-.0168.2784.0704.553.2446.7707l1.2038 1.5047c-.1136.3363-.2492.6648-.406.9834l-1.9153.2128c-.2773.0317-.5329.1654-.7171.375-.1704.1919-.3519.3735-.5438.5438-.2096.1842-.3433.4398-.375.7171l-.2119 1.9144c-.3185.1574-.647.2936-.9834.4078l-1.5047-1.2047c-.1997-.1593-.4477-.2459-.7031-.2456h-.0675c-.2561.015-.5127.015-.7688 0-.2781-.0165-.5525.0703-.7706.2438l-1.50469 1.2047c-.33634-.1137-.66486-.2493-.98343-.406l-.21282-1.9153c-.0317-.2773-.16536-.5329-.375-.7172-.19187-.1703-.37344-.3519-.54375-.5437-.18426-.2097-.43988-.3433-.71718-.375l-1.91438-.2119c-.15734-.3185-.29357-.647-.40781-.9834l1.20375-1.5047c.17424-.2177.26144-.4923.24469-.7707-.01501-.256-.01501-.5127 0-.7687.01675-.2783-.07045-.553-.24469-.77063L3.18781 8.34038c.11364-.33634.24924-.66486.40594-.98343l1.91531-.21281c.27731-.03171.53292-.16537.71719-.375.17031-.19188.35188-.37345.54375-.54375.20964-.18427.3433-.43989.375-.71719l.21188-1.91438c.31852-.15734.64704-.29357.98343-.40781L9.845 4.3907c.2181.17343.4925.26023.7706.24375.2561-.015.5127-.015.7688 0 .2782.01701.5528-.06985.7706-.24375l1.5047-1.20469c.3364.11424.6649.25047.9834.40781l.2128 1.91532c.0317.2773.1654.53292.375.71718.1919.17031.3735.35188.5438.54375.1843.20964.4399.3433.7172.375l1.9143.21188c.1574.31852.2936.64704.4079.98343l-1.2038 1.50469c-.1749.21743-.2628.49203-.2465.77063Z"/></svg>',
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"><path fill="currentColor" d="M10.0306 4.96938c.0699.06967.1254.15247.1633.24363.0378.09116.0573.1889.0573.28762 0 .09871-.0195.19645-.0573.28761-.0379.09116-.0934.17396-.1633.24364L6.53063 9.53187c-.06968.06992-.15247.1254-.24364.16326-.09116.03785-.1889.05734-.28761.05734-.09871 0-.19645-.01949-.28762-.05734-.09116-.03786-.17395-.09334-.24363-.16326l-1.5-1.5c-.06977-.06976-.12511-.15258-.16286-.24373-.03776-.09116-.05719-.18885-.05719-.28752 0-.09866.01943-.19635.05719-.28751.03775-.09115.09309-.17397.16286-.24373.06976-.06977.15259-.12511.24374-.16287.09115-.03775.18885-.05719.28751-.05719s.19636.01944.28751.05719c.09115.03776.17397.0931.24374.16287L6 7.9375l2.96938-2.97c.06978-.06961.15259-.12478.24371-.16237.09111-.03758.18874-.05683.2873-.05666.09856.00018.19612.01978.28711.05768.09098.0379.1736.09337.2431.16323ZM13.75 7c0 1.33502-.3959 2.64007-1.1376 3.7501-.7417 1.11-1.7959 1.9752-3.02928 2.4861-1.23341.5109-2.5906.6446-3.89998.3841-1.30937-.2605-2.5121-.9033-3.45611-1.8473-.944-.944-1.586877-2.14677-1.847328-3.45614-.26045-1.30937-.126777-2.66657.384114-3.89997C1.27471 3.18349 2.13987 2.12928 3.2499 1.38758 4.35994.645881 5.66498.25 7 .25c1.78961.001985 3.5053.713781 4.7708 1.97922C13.0362 3.49466 13.748 5.2104 13.75 7Zm-1.5 0c0-1.03835-.3079-2.05339-.8848-2.91674-.5769-.86336-1.3968-1.53627-2.35611-1.93363-.95931-.39736-2.01491-.50133-3.03331-.29875-1.0184.20257-1.95386.70258-2.68809 1.43681-.73422.73422-1.23424 1.66969-1.43681 2.68809-.20257 1.0184-.0986 2.074.29876 3.03331.39736.95931 1.07026 1.77921 1.93362 2.35611.86336.5769 1.87839.8848 2.91674.8848 1.39193-.0015 2.72643-.5551 3.7107-1.5393C11.6949 9.72642 12.2485 8.39193 12.25 7Z"/></svg>',
+ gear: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22"><path fill="currentColor" d="M11 6.12507c-.9642 0-1.90671.28592-2.7084.82159-.80169.53567-1.42653 1.29704-1.79551 2.18783-.36898.89081-.46552 1.87101-.27742 2.81661.18811.9457.6524 1.8143 1.33419 2.4961.68178.6818 1.55042 1.1461 2.49604 1.3342.9457.1881 1.9259.0916 2.8167-.2774s1.6521-.9938 2.1878-1.7955c.5357-.8017.8216-1.7442.8216-2.7084-.0015-1.2925-.5156-2.53161-1.4295-3.44553-.9139-.91392-2.153-1.42801-3.4455-1.4295Zm0 7.50003c-.5192 0-1.02669-.154-1.45837-.4424-.43168-.2885-.76813-.6984-.96681-1.1781-.19868-.4796-.25067-1.0074-.14938-1.5166.10129-.50924.35129-.97697.71841-1.34408.36711-.36712.83484-.61712 1.34405-.71841.5092-.10129 1.037-.0493 1.5166.14938.4797.19868.8897.53513 1.1781.96681.2884.43168.4424.9392.4424 1.4584 0 .6962-.2766 1.3638-.7688 1.8561-.4923.4923-1.16.7689-1.8562.7689Zm8.625-2.551v-.1481l1.3125-1.64155c.1102-.13755.1865-.29905.2228-.4715s.0316-.35102-.0137-.52131c-.2369-.89334-.5909-1.75142-1.0528-2.55188-.089-.15264-.2127-.28218-.3611-.37811-.1484-.09594-.3173-.15557-.493-.17408l-2.0888-.23437-.104-.10406-.2344-2.08969c-.0186-.17556-.0783-.34426-.1743-.49247-.0959-.1482-.2254-.27175-.3779-.36066-.8005-.46341-1.6589-.81869-2.5528-1.056559-.1704-.044683-.349-.048704-.5213-.01174-.1723.036965-.3335.113881-.4706.224549l-1.6415 1.3125h-.1482l-1.64152-1.3125C9.14683.9524 8.98532.87608 8.81288.839767c-.17245-.036314-.35102-.031606-.52132.013744-.89357.238319-1.75165.593909-2.55187 1.057499-.15205.08854-.28121.2115-.37712.35901-.0959.14752-.15586.31547-.17507.49037l-.23437 2.08875-.10407.10406-2.08968.23437c-.17556.01865-.34426.07835-.49247.17428-.14821.09593-.27176.22539-.36066.37791-.46211.80072-.81613 1.65912-1.052812 2.55281-.045195.17016-.049823.34855-.013512.52082.03631.17227.112546.33362.222574.47106L2.375 10.926v.1481l-1.3125 1.6416c-.110173.1375-.186492.299-.222806.4715-.036313.1724-.031605.351.013744.5213.238622.8936.594522 1.7517 1.058442 2.5519.08844.1519.21126.281.3586.3769.14734.0959.3151.1559.48983.1753l2.08875.2325.10407.104.23437 2.0916c.01865.1756.07835.3443.17428.4925.09592.1482.22538.2717.37791.3606.80052.4634 1.65893.8187 2.55281 1.0566.17045.0447.349.0487.52129.0117.17228-.0369.33347-.1139.47059-.2245l1.64152-1.3125h.1482l1.6415 1.3125c.1376.1101.2991.1865.4715.2228.1725.0363.351.0316.5213-.0138.8934-.2368 1.7514-.5908 2.5519-1.0528.1524-.0883.2819-.2112.3782-.3587.0962-.1475.1565-.3156.1759-.4907l.2325-2.0887.104-.1041 2.0897-.239c.1751-.0194.3432-.0797.4907-.1759.1475-.0963.2704-.2258.3587-.3782.4634-.8005.8187-1.6589 1.0566-2.5528.0448-.1699.0493-.3479.013-.5198-.0363-.172-.1124-.333-.2221-.4702l-1.3125-1.6416Zm-2.2612-.4584c.015.256.015.5127 0 .7687-.0168.2784.0704.553.2446.7707l1.2038 1.5047c-.1136.3363-.2492.6648-.406.9834l-1.9153.2128c-.2773.0317-.5329.1654-.7171.375-.1704.1919-.3519.3735-.5438.5438-.2096.1842-.3433.4398-.375.7171l-.2119 1.9144c-.3185.1574-.647.2936-.9834.4078l-1.5047-1.2047c-.1997-.1593-.4477-.2459-.7031-.2456h-.0675c-.2561.015-.5127.015-.7688 0-.2781-.0165-.5525.0703-.7706.2438l-1.50469 1.2047c-.33634-.1137-.66486-.2493-.98343-.406l-.21282-1.9153c-.0317-.2773-.16536-.5329-.375-.7172-.19187-.1703-.37344-.3519-.54375-.5437-.18426-.2097-.43988-.3433-.71718-.375l-1.91438-.2119c-.15734-.3185-.29357-.647-.40781-.9834l1.20375-1.5047c.17424-.2177.26144-.4923.24469-.7707-.01501-.256-.01501-.5127 0-.7687.01675-.2783-.07045-.553-.24469-.77063L3.18781 8.34038c.11364-.33634.24924-.66486.40594-.98343l1.91531-.21281c.27731-.03171.53292-.16537.71719-.375.17031-.19188.35188-.37345.54375-.54375.20964-.18427.3433-.43989.375-.71719l.21188-1.91438c.31852-.15734.64704-.29357.98343-.40781L9.845 4.3907c.2181.17343.4925.26023.7706.24375.2561-.015.5127-.015.7688 0 .2782.01701.5528-.06985.7706-.24375l1.5047-1.20469c.3364.11424.6649.25047.9834.40781l.2128 1.91532c.0317.2773.1654.53292.375.71718.1919.17031.3735.35188.5438.54375.1843.20964.4399.3433.7172.375l1.9143.21188c.1574.31852.2936.64704.4079.98343l-1.2038 1.50469c-.1749.21743-.2628.49203-.2465.77063Z"/></svg>',
+ lightbulb:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 13 16"><path fill="currentColor" d="M9.84994 14.5002c0 .1989-.07902.3897-.21967.5303-.14066.1407-.33142.2197-.53033.2197h-5c-.19891 0-.38968-.079-.53033-.2197-.14065-.1406-.21967-.3314-.21967-.5303 0-.1989.07902-.3897.21967-.5303.14065-.1407.33142-.2197.53033-.2197h5c.19891 0 .38967.079.53033.2197.14065.1406.21967.3314.21967.5303Zm2.49996-8.00001c.0023.87138-.1945 1.73175-.5755 2.51544-.381.78368-.9359 1.46997-1.6226 2.00647-.093.0708-.16853.162-.22085.2665-.05233.1046-.08004.2197-.08101.3366v.125c0 .3315-.1317.6495-.36612.8839-.23442.2344-.55236.3661-.88388.3661h-4c-.33152 0-.64947-.1317-.88389-.3661-.23442-.2344-.36611-.5524-.36611-.8839v-.125c-.00014-.115-.0267-.2284-.07763-.3314-.05094-.1031-.12488-.193-.21612-.263-.68477-.5334-1.23925-1.2155-1.62148-1.9948-.38223-.77929-.582201-1.63532-.584772-2.50331C.833063 3.41832 3.34994.825195 6.46181.750195c.76665-.018422 1.52923.116696 2.24287.397405.71365.2807 1.36392.70132 1.91262 1.23711.5486.53578.9846 1.1759 1.2821 1.88268.2976.70678.4508 1.46594.4505 2.2328Zm-1.5 0c.0002-.5669-.113-1.12811-.3331-1.65058-.22-.52247-.54226-.99565-.9479-1.39168-.40563-.39602-.8864-.70689-1.414-.91431-.52759-.20741-1.09135-.30718-1.65809-.29343-2.29937.055-4.15937 1.97188-4.14687 4.27375.00214.64152.15011 1.27416.43271 1.85009.2826.57592.69244 1.08006 1.19854 1.47429.25496.19678.46453.44618.61444.73128.14992.285.23665.599.25431.9206h3.50625c.018-.3222.10486-.6368.25472-.9226.14986-.2859.35924-.5362.61403-.73428.50754-.39672.91776-.90412 1.19936-1.4835.2817-.57938.4272-1.21543.4256-1.85963Zm-1.25434-.3325c-.06636-.56119-.28826-1.09265-.64067-1.53441-.35241-.44175-.82128-.7762-1.3537-.96559-.1861-.0608-.38859-.04643-.56423.04006-.17565.08648-.31051.23821-.37579.42278-.06527.18458-.05579.38736.02642.56504.08222.17767.23065.31616.4136.38587.26755.09379.50353.26056.68124.48146.17771.2209.29008.48712.32438.76854.02188.19776.12142.37872.27673.50308.0769.06157.16517.1074.25978.13486.09461.02747.1937.03602.29162.02519.09791-.01083.19274-.04085.27905-.08833.08632-.04748.16244-.1115.22402-.1884.06158-.07689.1074-.16517.13487-.25978.02746-.09461.03602-.1937.02518-.29162l-.0025.00125Z"/></svg>',
+ 'file-search':
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 12 14"><path fill="currentColor" d="M11.5306 3.97 8.03063.47C7.96097.400261 7.87826.344936 7.78721.307186 7.69616.269437 7.59856.250005 7.5.25h-6C1.16848.25.850537.381696.616117.616117.381696.850537.25 1.16848.25 1.5v11c0 .3315.131696.6495.366117.8839.23442.2344.552363.3661.883883.3661h9c.3315 0 .6495-.1317.8839-.3661.2344-.2344.3661-.5524.3661-.8839v-8c0-.19876-.0789-.38938-.2194-.53ZM9.4375 4H8V2.5625L9.4375 4ZM1.75 12.25V1.75H6.5v3c0 .19891.07902.38968.21967.53033.14065.14065.33142.21967.53033.21967h3v6.75h-8.5Zm6.03-3.03063c.2284-.47897.28015-1.02326.14613-1.53671-.13403-.51344-.44521-.96299-.87858-1.26923-.43337-.30623-.96102-.44945-1.48975-.40433-.52872.04511-1.02449.27564-1.39971.65086-.37523.37522-.60576.87099-.65087 1.39972-.04511.52872.0981 1.05638.40434 1.48975.30624.43336.75579.74457 1.26923.87857.51344.134 1.05773.0823 1.53671-.1461l.75.75c.1409.1409.33199.22.53125.22s.39035-.0791.53125-.22c.1409-.1409.22005-.332.22005-.5313 0-.1992-.07915-.3903-.22005-.53123l-.75-.75ZM5 8.25c0-.14834.04399-.29334.1264-.41668.08241-.12333.19954-.21946.33659-.27623.13704-.05676.28784-.07162.43333-.04268.14548.02894.27912.10037.38401.20526.10489.10489.17632.23853.20526.38401.02894.14549.01408.29629-.04268.43333-.05677.13705-.1529.25418-.27623.33659C6.04334 8.95601 5.89834 9 5.75 9c-.19891 0-.38968-.07902-.53033-.21967C5.07902 8.63968 5 8.44891 5 8.25Z"/></svg>',
+ star: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 15 15"><path fill="currentColor" d="M14.5873 6.00333c-.0759-.23292-.2187-.43838-.4105-.59083-.1918-.15245-.4241-.24519-.6682-.26667L9.9461 4.8377 8.55235 1.51645c-.09588-.22586-.25611-.4185-.46072-.553929-.2046-.135425-.44454-.207638-.68991-.207638-.24537 0-.48531.072213-.68991.207638-.20461.135429-.36484.328069-.46071.553929L4.85547 4.8377l-3.5625.30813c-.24538.02032-.479299.11265-.6724.26542-.193101.15276-.336784.35916-.413023.59328-.076238.23412-.081634.48554-.015512.72272s.200817.44954.387185.61045l2.7075 2.3625-.8125 3.515c-.05572.2394-.03965.4898.04619.7201.08585.2303.23767.4301.43648.5746.19881.1444.4358.2271.68132.2376.24553.0105.48871-.0516.69914-.1785l3.0625-1.86 3.06245 1.86c.2105.1267.4536.1886.699.178.2454-.0106.4822-.0933.6809-.2377.1987-.1444.3505-.3442.4363-.5743.0858-.2302.102-.4805.0463-.7198l-.8125-3.515 2.7075-2.3625c.1859-.16149.32-.37429.3853-.61168.0654-.23739.0592-.4888-.0178-.72269Zm-4.1718 2.66375c-.1714.14913-.299.34215-.3689.55831-.0699.21617-.07959.44731-.028.66857l.7119 3.08254-2.68378-1.63c-.1949-.1187-.41869-.1815-.64687-.1815-.22819 0-.45198.0628-.64688.1815l-2.68375 1.63.71188-3.08254c.05158-.22126.04189-.4524-.02803-.66857-.06993-.21616-.19745-.40918-.36885-.55831L2.00359 6.5902l3.13376-.27125c.22692-.01943.44417-.10073.62809-.23507.18393-.13433.32748-.31654.41503-.5268l1.21938-2.90563 1.21937 2.90563c.08755.21026.2311.39247.41503.5268.18392.13434.40117.21564.62809.23507l3.13376.27125-2.3806 2.07688Z"/></svg>',
+ checkmark:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 8"><path fill="#fff" d="M9.47334.806574C9.41136.744088 9.33763.694492 9.25639.660646S9.08802.609375 9.00001.609375 8.82486.6268 8.74362.660646s-.15497.083442-.21695.145928L3.56001 5.77991 1.47334 3.68657c-.06435-.06216-.14031-.11103-.22354-.14383-.08324-.03281-.17212-.04889-.261578-.04735-.089454.00155-.177727.0207-.259779.05637-.082052.03566-.156277.08713-.218436.15148-.062159.06435-.111035.14031-.143837.22355-.032803.08323-.04889.17212-.047342.26157.001547.08945.020699.17773.056361.25978.035663.08205.087137.15627.151485.21843l2.559996 2.56c.06198.06249.13571.11209.21695.14593.08124.03385.16838.05127.25639.05127s.17514-.01742.25638-.05127c.08124-.03384.15498-.08344.21695-.14593l5.44-5.44c.06767-.06242.12168-.13819.15861-.22253.03694-.08433.05601-.1754.05601-.26747 0-.09206-.01907-.18313-.05601-.26747-.03693-.08433-.09094-.160098-.15861-.222526Z"/></svg>',
'dots-three':
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 4"><path fill="#fff" d="M9.5 2c0 .29667-.08797.58668-.2528.83336-.16482.24667-.39908.43893-.67317.55246-.27409.11353-.57569.14324-.86666.08536-.29098-.05788-.55825-.20074-.76803-.41052-.20978-.20978-.35264-.47705-.41052-.76802-.05788-.29098-.02817-.59258.08536-.86666.11353-.27409.30579-.508362.55247-.673184C7.41332.587974 7.70333.5 8 .5c.39783 0 .77936.158036 1.06066.43934C9.34196 1.22064 9.5 1.60218 9.5 2ZM1.625.5c-.29667 0-.58668.087974-.833354.252796-.246674.164822-.438933.399094-.552465.673184-.113531.27408-.1432361.57568-.085358.86666.057878.29097.200739.55824.410518.76802.209778.20978.477049.35264.768029.41052.29097.05788.59257.02817.86666-.08536.27408-.11353.50835-.30579.67318-.55246C3.03703 2.58668 3.125 2.29667 3.125 2c0-.39782-.15803-.77936-.43934-1.06066C2.40436.658036 2.02283.5 1.625.5Zm12.75 0c-.2967 0-.5867.087974-.8334.252796-.2466.164822-.4389.399094-.5524.673184-.1135.27408-.1433.57568-.0854.86666.0579.29097.2008.55824.4105.76802.2098.20978.4771.35264.7681.41052.2909.05788.5925.02817.8666-.08536s.5084-.30579.6732-.55246c.1648-.24668.2528-.53669.2528-.83336 0-.39782-.158-.77936-.4393-1.06066C15.1544.658036 14.7728.5 14.375.5Z"/></svg>',
+ copy: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 11"><path fill="#fff" d="M9.125.8125h-6c-.14918 0-.29226.059263-.39775.164752-.10549.105488-.16475.248568-.16475.397748v1.6875H.875c-.149184 0-.292258.05926-.397748.16475C.371763 3.33274.3125 3.47582.3125 3.625v6c0 .14918.059263.29226.164752.3977.10549.1055.248564.1648.397748.1648h6c.14918 0 .29226-.0593.39775-.1648.10549-.10544.16475-.24852.16475-.3977V7.9375H9.125c.14918 0 .29226-.05926.39775-.16475.10549-.10549.16475-.24857.16475-.39775v-6c0-.14918-.05926-.29226-.16475-.397748C9.41726.871763 9.27418.8125 9.125.8125Zm-2.8125 8.25h-4.875v-4.875h4.875v4.875Zm2.25-2.25h-1.125V3.625c0-.14918-.05926-.29226-.16475-.39775-.10549-.10549-.24857-.16475-.39775-.16475H3.6875v-1.125h4.875v4.875Z"/></svg>',
+ compress:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"><path fill="currentColor" d="M13.84 17.44c-.16-.07-.33-.1-.5-.1H8A1.33 1.33 0 1 0 8 20h2.12l-7.07 7.05a1.33 1.33 0 0 0 .44 2.19 1.33 1.33 0 0 0 1.46-.3L12 21.89V24a1.33 1.33 0 1 0 2.67 0v-5.33a1.37 1.37 0 0 0-.83-1.23Zm-.5-10.77A1.33 1.33 0 0 0 12 8v2.12L4.95 3.05a1.34 1.34 0 1 0-1.9 1.9L10.12 12H8a1.33 1.33 0 0 0 0 2.67h5.33c.18 0 .35-.04.5-.11a1.33 1.33 0 0 0 .84-1.23V8a1.33 1.33 0 0 0-1.34-1.33Zm4.82 7.89c.16.07.33.1.5.1H24A1.33 1.33 0 0 0 24 12h-2.12l7.07-7.05a1.34 1.34 0 1 0-1.9-1.9L20 10.12V8a1.33 1.33 0 0 0-2.67 0v5.33a1.33 1.33 0 0 0 .83 1.23ZM21.88 20H24a1.33 1.33 0 0 0 0-2.67h-5.33c-.18 0-.35.04-.51.11a1.33 1.33 0 0 0-.83 1.23V24A1.33 1.33 0 0 0 20 24v-2.12l7.05 7.07a1.33 1.33 0 0 0 2.19-.44 1.33 1.33 0 0 0-.3-1.46L21.89 20Z"/></svg>',
+ grid: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"><path fill="currentColor" d="M13.33 17.33H4a1.33 1.33 0 0 0-1.33 1.34V28A1.33 1.33 0 0 0 4 29.33h9.33A1.33 1.33 0 0 0 14.67 28v-9.33a1.33 1.33 0 0 0-1.34-1.34ZM12 26.67H5.33V20H12v6.67Zm16-24h-9.33A1.33 1.33 0 0 0 17.33 4v9.33a1.33 1.33 0 0 0 1.34 1.34H28a1.33 1.33 0 0 0 1.33-1.34V4A1.33 1.33 0 0 0 28 2.67ZM26.67 12H20V5.33h6.67V12ZM28 17.33h-9.33a1.33 1.33 0 0 0-1.34 1.34V28a1.33 1.33 0 0 0 1.34 1.33H28A1.33 1.33 0 0 0 29.33 28v-9.33A1.33 1.33 0 0 0 28 17.33Zm-1.33 9.34H20V20h6.67v6.67Zm-13.34-24H4A1.33 1.33 0 0 0 2.67 4v9.33A1.33 1.33 0 0 0 4 14.67h9.33a1.33 1.33 0 0 0 1.34-1.34V4a1.33 1.33 0 0 0-1.34-1.33ZM12 12H5.33V5.33H12V12Z"/></svg>',
+ puzzle:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M23 29.33H7a4 4 0 0 1-4-4V12a4 4 0 0 1 4-4h1.33A5.33 5.33 0 0 1 19 8h4a1.33 1.33 0 0 1 1.33 1.33v4a5.33 5.33 0 0 1 0 10.67v4A1.33 1.33 0 0 1 23 29.33ZM7 10.67A1.33 1.33 0 0 0 5.67 12v13.33A1.33 1.33 0 0 0 7 26.67h14.67v-4.24a1.33 1.33 0 0 1 1.77-1.27 2.36 2.36 0 0 0 2.32-.3A2.67 2.67 0 0 0 27 19.02a2.67 2.67 0 0 0-.64-2.12 2.52 2.52 0 0 0-2.9-.74 1.33 1.33 0 0 1-1.77-1.26v-4.24h-4.26a1.33 1.33 0 0 1-1.34-1.78 2.36 2.36 0 0 0-.3-2.32 2.59 2.59 0 0 0-4-.57A2.67 2.67 0 0 0 11 8c0 .3.06.6.17.9a1.33 1.33 0 0 1-1.26 1.77H7Z"/></svg>',
+ approveUser:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M18.4 16.3a6.56 6.56 0 0 0 2.27-4.97 6.67 6.67 0 1 0-12.74 2.73c.39.86.96 1.62 1.67 2.23A10.67 10.67 0 0 0 3.33 26 1.33 1.33 0 0 0 6 26a8 8 0 0 1 16 0 1.33 1.33 0 0 0 2.67 0 10.67 10.67 0 0 0-6.27-9.7Zm-4.4-.97a4 4 0 1 1 0-8 4 4 0 0 1 0 8Zm15.61-3.16a1.33 1.33 0 0 0-1.9 0l-2.66 2.67-.82-.84a1.33 1.33 0 1 0-1.9 1.88l1.79 1.79a1.33 1.33 0 0 0 1.88 0l3.56-3.56a1.33 1.33 0 0 0 .05-1.94Z"/></svg>',
+ checkCircle:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="m20.3 11.72-5.73 5.73-2.2-2.2a1.33 1.33 0 1 0-1.88 1.88l3.14 3.15a1.33 1.33 0 0 0 1.88 0l6.66-6.67a1.33 1.33 0 1 0-1.88-1.89Zm-3.63-9.05a13.33 13.33 0 1 0 0 26.66 13.33 13.33 0 0 0 0-26.66Zm0 24a10.67 10.67 0 1 1 0-21.34 10.67 10.67 0 0 1 0 21.34Z"/></svg>',
+ resizeImage:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M17.67 14.67H3A1.33 1.33 0 0 0 1.67 16v12A1.33 1.33 0 0 0 3 29.33h14.67A1.33 1.33 0 0 0 19 28V16a1.33 1.33 0 0 0-1.33-1.33Zm-7.42 12 2.58-2.58a.4.4 0 0 1 .66 0l2.56 2.58h-5.8Zm6.08-3.5-.96-.94a3.21 3.21 0 0 0-4.44 0l-4.45 4.44H4.33v-9.34h12v5.84ZM3 5.48a1.33 1.33 0 0 0 1.15-.65 1.4 1.4 0 0 0-.16-1.8A1.33 1.33 0 0 0 3 2.67 1.33 1.33 0 0 0 1.67 4v.13A1.33 1.33 0 0 0 3 5.48Zm10.55-.15h.25a1.33 1.33 0 0 0 0-2.66h-.25a1.33 1.33 0 0 0 0 2.66ZM3 11.71a1.33 1.33 0 0 0 1.33-1.34v-.29a1.33 1.33 0 0 0-2.66 0v.3A1.33 1.33 0 0 0 3 11.7Zm16.12-9.04h-.25a1.33 1.33 0 0 0 0 2.66h.25a1.33 1.33 0 0 0 0-2.66ZM8.22 5.33h.25a1.33 1.33 0 1 0 0-2.66H8.2a1.33 1.33 0 1 0 0 2.66Zm21.45 3.2a1.33 1.33 0 0 0-1.34 1.34v.28a1.33 1.33 0 0 0 2.67 0v-.28a1.33 1.33 0 0 0-1.33-1.34Zm-6.5 18.14h-.33a1.33 1.33 0 1 0 0 2.66h.32a1.33 1.33 0 0 0 0-2.66Zm6.36-24a1.33 1.33 0 1 0 .36 2.64A1.33 1.33 0 0 0 31 4.15V4a1.45 1.45 0 0 0-1.47-1.33Zm.14 11.86a1.33 1.33 0 0 0-1.34 1.34v.29a1.33 1.33 0 1 0 2.67 0v-.3a1.33 1.33 0 0 0-1.33-1.33ZM24.45 2.67h-.25a1.33 1.33 0 1 0 0 2.66h.25a1.33 1.33 0 1 0 0-2.66Zm5.22 24A1.33 1.33 0 0 0 28.34 28a1.33 1.33 0 0 0 1.33 1.33A1.45 1.45 0 0 0 31 27.87a1.33 1.33 0 0 0-1.33-1.2Zm0-6.08a1.33 1.33 0 0 0-1.34 1.33v.3a1.33 1.33 0 0 0 2.67 0v-.35a1.33 1.33 0 0 0-1.33-1.34v.06Z"/></svg>',
+ searchFile:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"><path fill="currentColor" d="M16 26.67H6.67a1.33 1.33 0 0 1-1.34-1.34V6.67a1.33 1.33 0 0 1 1.34-1.34h6.66v4a4 4 0 0 0 4 4h4v1.34a1.33 1.33 0 0 0 2.67 0v-2.75a1.75 1.75 0 0 0-.08-.36v-.12a1.43 1.43 0 0 0-.25-.37l-8-8c-.11-.1-.24-.2-.38-.26h-.12a1.17 1.17 0 0 0-.44-.14H6.67a4 4 0 0 0-4 4v18.66a4 4 0 0 0 4 4H16a1.33 1.33 0 0 0 0-2.66ZM16 7.2l3.45 3.46h-2.12A1.33 1.33 0 0 1 16 9.33V7.21Zm-6.67 3.46a1.33 1.33 0 0 0 0 2.66h1.34a1.33 1.33 0 0 0 0-2.66H9.33Zm19.62 16.38-1.56-1.54a4.59 4.59 0 0 0-.72-5.51 4.65 4.65 0 0 0-8 3.32 4.61 4.61 0 0 0 6.84 4.07l1.54 1.56a1.33 1.33 0 0 0 2.19-.44 1.33 1.33 0 0 0-.3-1.46Zm-4.23-2.33a2.05 2.05 0 0 1-2.81 0 2 2 0 0 1 0-2.81 2 2 0 0 1 1.34-.58 1.96 1.96 0 0 1 1.47 3.39ZM17.33 16h-8a1.33 1.33 0 0 0 0 2.67h8a1.33 1.33 0 1 0 0-2.67Zm-2.66 8a1.33 1.33 0 0 0 0-2.67H9.33a1.33 1.33 0 0 0 0 2.67h5.34Z"/></svg>',
+ image:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M26 2.67H7.33a4 4 0 0 0-4 4v18.66a4 4 0 0 0 4 4H26c.22 0 .44-.02.65-.06l.4-.1h.16l.5-.18.17-.1c.13-.08.28-.14.41-.24.18-.13.35-.27.5-.42l.1-.12c.13-.14.25-.28.36-.43l.12-.17c.1-.15.18-.3.24-.47l.1-.2.16-.5v-.2c.07-.27.12-.54.13-.8V6.66a4 4 0 0 0-4-4Zm-18.67 24A1.33 1.33 0 0 1 6 25.33V19.6l4.39-4.4a1.33 1.33 0 0 1 1.89 0l11.47 11.48H7.33Zm20-1.34c0 .17-.03.33-.1.48a1.33 1.33 0 0 1-.22.35l-7.13-7.13 1.17-1.18a1.33 1.33 0 0 1 1.9 0l4.38 4.4v3.08Zm0-6.85L24.83 16a4.1 4.1 0 0 0-5.66 0L18 17.17l-3.84-3.84a4.1 4.1 0 0 0-5.65 0L6 15.81V6.67a1.33 1.33 0 0 1 1.33-1.34H26a1.33 1.33 0 0 1 1.33 1.34v11.81Z"/></svg>',
+ robot:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M12.67 20a1.33 1.33 0 1 0 0 2.67 1.33 1.33 0 0 0 0-2.67Zm-9.34-1.33A1.33 1.33 0 0 0 2 20v2.67a1.33 1.33 0 1 0 2.67 0V20a1.33 1.33 0 0 0-1.34-1.33Zm26.67 0A1.33 1.33 0 0 0 28.67 20v2.67a1.33 1.33 0 1 0 2.66 0V20A1.33 1.33 0 0 0 30 18.67Zm-6.67-9.34H18v-1.7a2.67 2.67 0 0 0 .55-4.18 2.67 2.67 0 0 0-4.19 3.2c.24.41.57.74.97.98v1.7H10a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h13.33a4 4 0 0 0 4-4v-12a4 4 0 0 0-4-4ZM18.96 12l-.67 2.67h-3.25L14.37 12h4.59Zm5.7 13.33a1.33 1.33 0 0 1-1.33 1.34H10a1.33 1.33 0 0 1-1.33-1.34v-12A1.33 1.33 0 0 1 10 12h1.63l1.04 4.32A1.33 1.33 0 0 0 14 17.33h5.33a1.33 1.33 0 0 0 1.34-1.01L21.7 12h1.62a1.33 1.33 0 0 1 1.34 1.33v12Zm-4-5.33a1.33 1.33 0 1 0 0 2.67 1.33 1.33 0 0 0 0-2.67Z"/></svg>',
+ sitemap:
+ '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path fill="currentColor" d="M29.67 20H27v-4a1.33 1.33 0 0 0-1.33-1.33h-8V12h2.66a1.33 1.33 0 0 0 1.34-1.33v-8a1.33 1.33 0 0 0-1.34-1.34h-8A1.33 1.33 0 0 0 11 2.67v8A1.33 1.33 0 0 0 12.33 12H15v2.67H7A1.33 1.33 0 0 0 5.67 16v4H3a1.33 1.33 0 0 0-1.33 1.33v8A1.33 1.33 0 0 0 3 30.67h8a1.33 1.33 0 0 0 1.33-1.34v-8A1.33 1.33 0 0 0 11 20H8.33v-2.67h16V20h-2.66a1.33 1.33 0 0 0-1.34 1.33v8a1.33 1.33 0 0 0 1.34 1.34h8A1.33 1.33 0 0 0 31 29.33v-8A1.33 1.33 0 0 0 29.67 20Zm-20 2.67V28H4.33v-5.33h5.34Zm4-13.34V4H19v5.33h-5.33ZM28.33 28H23v-5.33h5.33V28Z"/></svg>',
} as const;
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts
new file mode 100644
index 000000000..ba60ecf1e
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/index.ts
@@ -0,0 +1,8 @@
+export { DevOverlayBadge } from './badge.js';
+export { DevOverlayButton } from './button.js';
+export { DevOverlayCard } from './card.js';
+export { DevOverlayHighlight } from './highlight.js';
+export { DevOverlayTooltip } from './tooltip.js';
+export { DevOverlayWindow } from './window.js';
+export { DevOverlayToggle } from './toggle.js';
+export { DevOverlayIcon } from './icon.js';
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts
index 63dcba65e..1fb0b686a 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts
@@ -30,6 +30,14 @@ export class DevOverlayToggle extends HTMLElement {
position: relative;
}
+ @media (forced-colors: active) {
+ input::after {
+ border: 1px solid black;
+ top: 0px;
+ left: 0px;
+ }
+ }
+
input:checked {
border: 1px solid rgba(213, 249, 196, 1);
background-color: rgba(61, 125, 31, 1);
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts
index 18b515429..0873d888d 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/window.ts
@@ -1,18 +1,9 @@
-import { getIconElement, isDefinedIcon, type Icon } from './icons.js';
-
export class DevOverlayWindow extends HTMLElement {
- windowTitle?: string | undefined | null;
- windowIcon?: Icon | undefined | null;
shadowRoot: ShadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: 'open' });
-
- this.windowTitle = this.getAttribute('window-title');
- this.windowIcon = this.hasAttribute('window-icon')
- ? (this.getAttribute('window-icon') as Icon)
- : undefined;
}
async connectedCallback() {
@@ -25,31 +16,34 @@ export class DevOverlayWindow extends HTMLElement {
background: linear-gradient(0deg, #13151A, #13151A), linear-gradient(0deg, #343841, #343841);
border: 1px solid rgba(52, 56, 65, 1);
width: min(640px, 100%);
- height: 480px;
+ max-height: 480px;
border-radius: 12px;
padding: 24px;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- color: rgba(204, 206, 216, 1);
+ color: rgba(191, 193, 201, 1);
position: fixed;
z-index: 999999999;
- top: 55%;
+ bottom: 72px;
left: 50%;
- transform: translate(-50%, -50%);
+ transform: translateX(-50%);
box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
}
- ::slotted(h1), ::slotted(h2), ::slotted(h3), ::slotted(h4), ::slotted(h5) {
- font-weight: 600;
- color: #fff;
+ @media (forced-colors: active) {
+ :host {
+ background: white;
+ }
+ }
+
+ @media (max-width: 640px) {
+ :host {
+ border-radius: 0;
+ }
}
- #window-title {
- display: flex;
- align-items: center;
+ ::slotted(h1), ::slotted(h2), ::slotted(h3), ::slotted(h4), ::slotted(h5) {
font-weight: 600;
color: #fff;
- margin: 0;
- font-size: 22px;
}
::slotted(h1) {
@@ -72,37 +66,13 @@ export class DevOverlayWindow extends HTMLElement {
font-size: 14px;
}
- #window-title svg {
- margin-right: 8px;
- height: 1em;
- }
-
hr, ::slotted(hr) {
border: 1px solid rgba(27, 30, 36, 1);
margin: 1em 0;
}
</style>
- <h1 id="window-title">${this.windowIcon ? this.getElementForIcon(this.windowIcon) : ''}${
- this.windowTitle ?? ''
- }</h1>
- <hr />
<slot />
`;
}
-
- getElementForIcon(icon: Icon) {
- if (isDefinedIcon(icon)) {
- const iconElement = getIconElement(icon);
- iconElement?.style.setProperty('height', '1em');
-
- return iconElement?.outerHTML;
- } else {
- const iconElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- iconElement.setAttribute('viewBox', '0 0 16 16');
- iconElement.innerHTML = icon;
-
- return iconElement.outerHTML;
- }
- }
}
diff --git a/packages/astro/src/runtime/server/endpoint.ts b/packages/astro/src/runtime/server/endpoint.ts
index 7db5f07ee..f8fc1c071 100644
--- a/packages/astro/src/runtime/server/endpoint.ts
+++ b/packages/astro/src/runtime/server/endpoint.ts
@@ -1,35 +1,12 @@
+import { bold } from 'kleur/colors';
import type { APIContext, EndpointHandler, Params } from '../../@types/astro.js';
import type { Logger } from '../../core/logger/core.js';
-function getHandlerFromModule(mod: EndpointHandler, method: string, logger: Logger) {
- const lowerCaseMethod = method.toLowerCase();
-
- // TODO: remove in Astro 4.0
- if (mod[lowerCaseMethod]) {
- logger.warn(
- 'astro',
- `Lower case endpoint names are deprecated and will not be supported in Astro 4.0. Rename the endpoint ${lowerCaseMethod} to ${method}.`
- );
- }
+function getHandlerFromModule(mod: EndpointHandler, method: string) {
// If there was an exact match on `method`, return that function.
if (mod[method]) {
return mod[method];
}
-
- // TODO: remove in Astro 4.0
- if (mod[lowerCaseMethod]) {
- return mod[lowerCaseMethod];
- }
- // TODO: remove in Astro 4.0
- // Handle `del` instead of `delete`, since `delete` is a reserved word in JS.
- if (method === 'delete' && mod['del']) {
- return mod['del'];
- }
- // TODO: remove in Astro 4.0
- // If a single `all` handler was used, return that function.
- if (mod['all']) {
- return mod['all'];
- }
if (mod['ALL']) {
return mod['ALL'];
}
@@ -44,15 +21,17 @@ export async function renderEndpoint(
ssr: boolean,
logger: Logger
) {
- const { request } = context;
+ const { request, url } = context;
const chosenMethod = request.method?.toUpperCase();
- const handler = getHandlerFromModule(mod, chosenMethod, logger);
- // TODO: remove the 'get' check in Astro 4.0
- if (!ssr && ssr === false && chosenMethod && chosenMethod !== 'GET' && chosenMethod !== 'get') {
- // eslint-disable-next-line no-console
- console.warn(`
-${chosenMethod} requests are not available when building a static site. Update your config to \`output: 'server'\` or \`output: 'hybrid'\` with an \`export const prerender = false\` to handle ${chosenMethod} requests.`);
+ const handler = getHandlerFromModule(mod, chosenMethod);
+ if (!ssr && ssr === false && chosenMethod && chosenMethod !== 'GET') {
+ logger.warn(
+ null,
+ `${url.pathname} ${bold(
+ chosenMethod
+ )} requests are not available for a static site. Update your config to \`output: 'server'\` or \`output: 'hybrid'\` to enable.`
+ );
}
if (!handler || typeof handler !== 'function') {
// No handler found, so this should be a 404. Using a custom header
diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts
index e5a5d5e86..4087ef1a7 100644
--- a/packages/astro/src/runtime/server/render/common.ts
+++ b/packages/astro/src/runtime/server/render/common.ts
@@ -67,8 +67,8 @@ function stringifyChunk(
let prescriptType: PrescriptType = needsHydrationScript
? 'both'
: needsDirectiveScript
- ? 'directive'
- : null;
+ ? 'directive'
+ : null;
if (prescriptType) {
let prescripts = getPrescripts(result, prescriptType, hydration.directive);
return markHTMLString(prescripts);
diff --git a/packages/astro/src/runtime/server/transition.ts b/packages/astro/src/runtime/server/transition.ts
index d38a0eac6..0fed017df 100644
--- a/packages/astro/src/runtime/server/transition.ts
+++ b/packages/astro/src/runtime/server/transition.ts
@@ -145,8 +145,8 @@ class ViewTransitionStyleSheet {
direction === 'backwards'
? `[data-astro-transition=back]`
: direction === 'forwards'
- ? ''
- : `[data-astro-transition=${direction}]`;
+ ? ''
+ : `[data-astro-transition=${direction}]`;
this.addRule('modern', `${prefix}::view-transition-${image}(${name}) { ${animation} }`);
this.addRule(
'fallback',
diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts
index 6ff0efc01..92d8bb4a2 100644
--- a/packages/astro/src/transitions/router.ts
+++ b/packages/astro/src/transitions/router.ts
@@ -426,8 +426,8 @@ async function transition(
const navigationType = historyState
? 'traverse'
: options.history === 'replace'
- ? 'replace'
- : 'push';
+ ? 'replace'
+ : 'push';
if (samePage(from, to) && !options.formData /* not yet: && to.hash*/) {
if (navigationType !== 'traverse') {
diff --git a/packages/astro/src/transitions/vite-plugin-transitions.ts b/packages/astro/src/transitions/vite-plugin-transitions.ts
index 247c61e2b..a3d68ade6 100644
--- a/packages/astro/src/transitions/vite-plugin-transitions.ts
+++ b/packages/astro/src/transitions/vite-plugin-transitions.ts
@@ -21,20 +21,20 @@ export default function astroTransitions({ settings }: { settings: AstroSettings
load(id) {
if (id === resolvedVirtualModuleId) {
return `
- export * from "astro/transitions";
+ export * from "astro/virtual-modules/transitions.js";
export { default as ViewTransitions } from "astro/components/ViewTransitions.astro";
`;
}
if (id === resolvedVirtualClientModuleId) {
return `
- export { navigate, supportsViewTransitions, transitionEnabledOnThisPage } from "astro/transitions/router";
- export * from "astro/transitions/types";
+ export { navigate, supportsViewTransitions, transitionEnabledOnThisPage } from "astro/virtual-modules/transitions-router.js";
+ export * from "astro/virtual-modules/transitions-types.js";
export {
TRANSITION_BEFORE_PREPARATION, isTransitionBeforePreparationEvent, TransitionBeforePreparationEvent,
TRANSITION_AFTER_PREPARATION,
TRANSITION_BEFORE_SWAP, isTransitionBeforeSwapEvent, TransitionBeforeSwapEvent,
TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD
- } from "astro/transitions/events";
+ } from "astro/virtual-modules/transitions-events.js";
`;
}
},
diff --git a/packages/astro/src/virtual-modules/README.md b/packages/astro/src/virtual-modules/README.md
new file mode 100644
index 000000000..137e2e16f
--- /dev/null
+++ b/packages/astro/src/virtual-modules/README.md
@@ -0,0 +1,3 @@
+# virtual-modules
+
+This directory contains the entry points for Astro virtual modules. For example, `astro:foobar` would re-export or use `astro/virtual-modules/foobar.js` which maps to the internal file `astro/dist/virtual-modules/foobar.js`.
diff --git a/packages/astro/src/virtual-modules/i18n.ts b/packages/astro/src/virtual-modules/i18n.ts
new file mode 100644
index 000000000..a55c1f6cf
--- /dev/null
+++ b/packages/astro/src/virtual-modules/i18n.ts
@@ -0,0 +1 @@
+export * from '../i18n/index.js';
diff --git a/packages/astro/src/virtual-modules/middleware.ts b/packages/astro/src/virtual-modules/middleware.ts
new file mode 100644
index 000000000..4874c88d0
--- /dev/null
+++ b/packages/astro/src/virtual-modules/middleware.ts
@@ -0,0 +1 @@
+export { defineMiddleware, sequence } from '../core/middleware/index.js';
diff --git a/packages/astro/src/virtual-modules/prefetch.ts b/packages/astro/src/virtual-modules/prefetch.ts
new file mode 100644
index 000000000..72bc23e2d
--- /dev/null
+++ b/packages/astro/src/virtual-modules/prefetch.ts
@@ -0,0 +1 @@
+export * from '../prefetch/index.js';
diff --git a/packages/astro/src/virtual-modules/transitions-events.ts b/packages/astro/src/virtual-modules/transitions-events.ts
new file mode 100644
index 000000000..35ecaf64f
--- /dev/null
+++ b/packages/astro/src/virtual-modules/transitions-events.ts
@@ -0,0 +1 @@
+export * from '../transitions/events.js';
diff --git a/packages/astro/src/virtual-modules/transitions-router.ts b/packages/astro/src/virtual-modules/transitions-router.ts
new file mode 100644
index 000000000..666089f3f
--- /dev/null
+++ b/packages/astro/src/virtual-modules/transitions-router.ts
@@ -0,0 +1 @@
+export * from '../transitions/router.js';
diff --git a/packages/astro/src/virtual-modules/transitions-types.ts b/packages/astro/src/virtual-modules/transitions-types.ts
new file mode 100644
index 000000000..66dfb1d0e
--- /dev/null
+++ b/packages/astro/src/virtual-modules/transitions-types.ts
@@ -0,0 +1 @@
+export * from '../transitions/types.js';
diff --git a/packages/astro/src/virtual-modules/transitions.ts b/packages/astro/src/virtual-modules/transitions.ts
new file mode 100644
index 000000000..84aeb3a2c
--- /dev/null
+++ b/packages/astro/src/virtual-modules/transitions.ts
@@ -0,0 +1 @@
+export * from '../transitions/index.js';
diff --git a/packages/astro/src/vite-plugin-astro-server/base.ts b/packages/astro/src/vite-plugin-astro-server/base.ts
index 7bf57d10a..e757515d7 100644
--- a/packages/astro/src/vite-plugin-astro-server/base.ts
+++ b/packages/astro/src/vite-plugin-astro-server/base.ts
@@ -1,10 +1,10 @@
import type * as vite from 'vite';
import type { AstroSettings } from '../@types/astro.js';
+import { bold } from 'kleur/colors';
import * as fs from 'node:fs';
import type { Logger } from '../core/logger/core.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
-import { log404 } from './common.js';
import { writeHtmlResponse } from './response.js';
export function baseMiddleware(
@@ -28,13 +28,11 @@ export function baseMiddleware(
}
if (pathname === '/' || pathname === '/index.html') {
- log404(logger, pathname);
const html = subpathNotUsedTemplate(devRoot, pathname);
return writeHtmlResponse(res, 404, html);
}
if (req.headers.accept?.includes('text/html')) {
- log404(logger, pathname);
const html = notFoundTemplate({
statusCode: 404,
title: 'Not found',
@@ -45,13 +43,16 @@ export function baseMiddleware(
}
// Check to see if it's in public and if so 404
+ // TODO: Remove redirect, turn this warning into an error in Astro 4.0
const publicPath = new URL('.' + req.url, config.publicDir);
fs.stat(publicPath, (_err, stats) => {
if (stats) {
const expectedLocation = new URL('.' + url, devRootURL).pathname;
logger.warn(
- 'dev',
- `Requests for items in your public folder must also include your base. ${url} should be ${expectedLocation}. Omitting the base will break in production.`
+ 'router',
+ `Request URLs for ${bold(
+ 'public/'
+ )} assets must also include your base. "${expectedLocation}" expected, but received "${url}".`
);
res.writeHead(301, {
Location: expectedLocation,
diff --git a/packages/astro/src/vite-plugin-astro-server/common.ts b/packages/astro/src/vite-plugin-astro-server/common.ts
deleted file mode 100644
index 9e331232c..000000000
--- a/packages/astro/src/vite-plugin-astro-server/common.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Logger } from '../core/logger/core.js';
-import * as msg from '../core/messages.js';
-
-export function log404(logger: Logger, pathname: string) {
- logger.info('serve', msg.req({ url: pathname, statusCode: 404 }));
-}
diff --git a/packages/astro/src/vite-plugin-astro-server/request.ts b/packages/astro/src/vite-plugin-astro-server/request.ts
index 65e97b96f..aa9124fb4 100644
--- a/packages/astro/src/vite-plugin-astro-server/request.ts
+++ b/packages/astro/src/vite-plugin-astro-server/request.ts
@@ -2,7 +2,7 @@ import type http from 'node:http';
import type { ManifestData, SSRManifest } from '../@types/astro.js';
import { collectErrorMetadata } from '../core/errors/dev/index.js';
import { createSafeError } from '../core/errors/index.js';
-import * as msg from '../core/messages.js';
+import { formatErrorMessage } from '../core/messages.js';
import { collapseDuplicateSlashes, removeTrailingForwardSlash } from '../core/path.js';
import { eventError, telemetry } from '../events/index.js';
import { isServerLikeOutput } from '../prerender/utils.js';
@@ -102,7 +102,10 @@ export async function handleRequest({
telemetry.record(eventError({ cmd: 'dev', err: errorWithMetadata, isFatal: false }));
- pipeline.logger.error(null, msg.formatErrorMessage(errorWithMetadata));
+ pipeline.logger.error(
+ null,
+ formatErrorMessage(errorWithMetadata, pipeline.logger.level() === 'debug')
+ );
handle500Response(moduleLoader, incomingResponse, errorWithMetadata);
return err;
diff --git a/packages/astro/src/vite-plugin-astro-server/response.ts b/packages/astro/src/vite-plugin-astro-server/response.ts
index b1c948095..ac36e703b 100644
--- a/packages/astro/src/vite-plugin-astro-server/response.ts
+++ b/packages/astro/src/vite-plugin-astro-server/response.ts
@@ -68,7 +68,7 @@ export async function writeWebResponse(res: http.ServerResponse, webResponse: Re
// Previously, `headers.entries()` would already have these merged, but it seems like this isn't the case anymore.
if (headers.has('set-cookie')) {
if ('getSetCookie' in headers && typeof headers.getSetCookie === 'function') {
- _headers['set-cookie'] = headers.getSetCookie();
+ _headers['set-cookie'] = headers.getSetCookie().toString();
} else {
_headers['set-cookie'] = headers.get('set-cookie')!;
}
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index bf70c1baf..9f64af31f 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -2,13 +2,17 @@ import type http from 'node:http';
import { fileURLToPath } from 'node:url';
import type {
ComponentInstance,
+ DevOverlayMetadata,
ManifestData,
- MiddlewareEndpointHandler,
+ MiddlewareHandler,
RouteData,
SSRElement,
SSRManifest,
} from '../@types/astro.js';
+import { getInfoOutput } from '../cli/info/index.js';
+import { ASTRO_VERSION } from '../core/constants.js';
import { AstroErrorData, isAstroError } from '../core/errors/index.js';
+import { req } from '../core/messages.js';
import { sequence } from '../core/middleware/index.js';
import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
import {
@@ -24,7 +28,6 @@ import { createI18nMiddleware, i18nPipelineHook } from '../i18n/middleware.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js';
import { isServerLikeOutput } from '../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
-import { log404 } from './common.js';
import { getStylesForURL } from './css.js';
import type DevPipeline from './devPipeline.js';
import { preload } from './index.js';
@@ -48,6 +51,10 @@ export interface MatchedRoute {
mod: ComponentInstance;
}
+function isLoggedRequest(url: string) {
+ return url !== '/favicon.ico';
+}
+
function getCustom404Route(manifestData: ManifestData): RouteData | undefined {
const route404 = /^\/404\/?$/;
return manifestData.routes.find((r) => route404.test(r.route));
@@ -108,14 +115,13 @@ export async function matchRoute(
const possibleRoutes = matches.flatMap((route) => route.component);
pipeline.logger.warn(
- 'getStaticPaths',
+ 'router',
`${AstroErrorData.NoMatchingStaticPathFound.message(
pathname
)}\n\n${AstroErrorData.NoMatchingStaticPathFound.hint(possibleRoutes)}`
);
}
- log404(logger, pathname);
const custom404 = getCustom404Route(manifestData);
if (custom404) {
@@ -161,11 +167,15 @@ export async function handleRoute({
incomingResponse,
manifest,
}: HandleRoute): Promise<void> {
+ const timeStart = performance.now();
const env = pipeline.getEnvironment();
const config = pipeline.getConfig();
const moduleLoader = pipeline.getModuleLoader();
const { logger } = env;
if (!matchedRoute && !config.experimental.i18n) {
+ if (isLoggedRequest(pathname)) {
+ logger.info(null, req({ url: pathname, method: incomingRequest.method, statusCode: 404 }));
+ }
return handle404Response(origin, incomingRequest, incomingResponse);
}
@@ -281,7 +291,7 @@ export async function handleRoute({
});
}
- const onRequest = middleware?.onRequest as MiddlewareEndpointHandler | undefined;
+ const onRequest = middleware?.onRequest as MiddlewareHandler | undefined;
if (config.experimental.i18n) {
const i18Middleware = createI18nMiddleware(
config.experimental.i18n,
@@ -304,6 +314,18 @@ export async function handleRoute({
}
let response = await pipeline.renderRoute(renderContext, mod);
+ if (isLoggedRequest(pathname)) {
+ const timeEnd = performance.now();
+ logger.info(
+ null,
+ req({
+ url: pathname,
+ method: incomingRequest.method,
+ statusCode: response.status,
+ reqTime: timeEnd - timeStart,
+ })
+ );
+ }
if (response.status === 404 && has404Route(manifestData)) {
const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline);
if (options && fourOhFourRoute?.route !== options.route)
@@ -362,7 +384,10 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
children: '',
});
- if (settings.config.experimental.devOverlay) {
+ if (
+ settings.config.devOverlay.enabled &&
+ (await settings.preferences.get('devOverlay.enabled'))
+ ) {
scripts.add({
props: {
type: 'module',
@@ -371,12 +396,17 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
children: '',
});
+ const additionalMetadata: DevOverlayMetadata['__astro_dev_overlay__'] = {
+ defaultState: settings.config.devOverlay.defaultState,
+ root: fileURLToPath(settings.config.root),
+ version: ASTRO_VERSION,
+ debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }),
+ };
+
// Additional data for the dev overlay
scripts.add({
props: {},
- children: `window.__astro_dev_overlay__ = {root: ${JSON.stringify(
- fileURLToPath(settings.config.root)
- )}}`,
+ children: `window.__astro_dev_overlay__ = ${JSON.stringify(additionalMetadata)}`,
});
}
}
diff --git a/packages/astro/src/vite-plugin-astro/hmr.ts b/packages/astro/src/vite-plugin-astro/hmr.ts
index ca1c13a95..dc820e9f6 100644
--- a/packages/astro/src/vite-plugin-astro/hmr.ts
+++ b/packages/astro/src/vite-plugin-astro/hmr.ts
@@ -9,7 +9,6 @@ import {
type CompileResult,
} from '../core/compile/index.js';
import type { Logger } from '../core/logger/core.js';
-import * as msg from '../core/messages.js';
import { isAstroScript } from './query.js';
const PKG_PREFIX = fileURLToPath(new URL('../../', import.meta.url));
@@ -92,11 +91,10 @@ export async function handleHotUpdate(
// Bugfix: sometimes style URLs get normalized and end with `lang.css=`
// These will cause full reloads, so filter them out here
const mods = [...filtered].filter((m) => !m.url.endsWith('='));
- const file = ctx.file.replace(slash(fileURLToPath(config.root)), '/');
// If only styles are changed, remove the component file from the update list
if (isStyleOnlyChange) {
- logger.info('astro', msg.hmr({ file, style: true }));
+ logger.debug('watch', 'style-only change');
// Only return the Astro styles that have changed!
return mods.filter((mod) => mod.id?.includes('astro&type=style'));
}
@@ -112,11 +110,8 @@ export async function handleHotUpdate(
// TODO: Svelte files should be marked as `isSelfAccepting` but they don't appear to be
const isSelfAccepting = mods.every((m) => m.isSelfAccepting || m.url.endsWith('.svelte'));
- if (isSelfAccepting) {
- if (/astro\.config\.[cm][jt]s$/.test(file)) return mods;
- logger.info('astro', msg.hmr({ file }));
- } else {
- logger.info('astro', msg.reload({ file }));
+ if (!isSelfAccepting) {
+ logger.debug('watch', 'full page reload triggered');
}
return mods;
diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts
index 1649d8069..631989903 100644
--- a/packages/astro/src/vite-plugin-astro/index.ts
+++ b/packages/astro/src/vite-plugin-astro/index.ts
@@ -4,6 +4,7 @@ import type { AstroSettings } from '../@types/astro.js';
import type { Logger } from '../core/logger/core.js';
import type { PluginMetadata as AstroPluginMetadata } from './types.js';
+import { fileURLToPath } from 'url';
import { normalizePath } from 'vite';
import {
cachedCompilation,
@@ -23,6 +24,28 @@ interface AstroPluginOptions {
logger: Logger;
}
+const PKG_PREFIX = fileURLToPath(new URL('../../', import.meta.url));
+const E2E_PREFIX = fileURLToPath(new URL('../../e2e', import.meta.url));
+const isPkgFile = (id: string | null) => {
+ return id?.startsWith(PKG_PREFIX) && !id.startsWith(E2E_PREFIX);
+};
+
+const dedupeHotUpdateLogsCache = new Map<string, NodeJS.Timeout>();
+
+// TODO(fks): For some reason, we're seeing duplicate handleHotUpdate() calls
+// when hitting save multiple times in a row. This is a temporary workaround
+// to prevent duplicate logging until the (vite?) issue is fixed.
+function dedupeHotUpdateLogs(filename: string) {
+ if (dedupeHotUpdateLogsCache.has(filename)) {
+ return false;
+ }
+ dedupeHotUpdateLogsCache.set(
+ filename,
+ setTimeout(() => dedupeHotUpdateLogsCache.delete(filename), 150)
+ );
+ return true;
+}
+
/** Transform .astro files for Vite */
export default function astro({ settings, logger }: AstroPluginOptions): vite.Plugin[] {
const { config } = settings;
@@ -173,18 +196,27 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl
},
async handleHotUpdate(context) {
if (context.server.config.isProduction) return;
- const compileProps: CompileProps = {
- astroConfig: config,
- viteConfig: resolvedConfig,
- filename: context.file,
- source: await context.read(),
- };
- const compile = () => cachedCompilation(compileProps);
+ const filename = context.file;
+ const isSkipLog =
+ /astro\.config\.[cm][jt]s$/.test(filename) ||
+ /(\/|\\)\.astro(\/|\\)/.test(filename) ||
+ isPkgFile(filename);
+ if (!isSkipLog && dedupeHotUpdateLogs(filename)) {
+ logger.info('watch', filename.replace(config.root.pathname, '/'));
+ }
+ const source = await context.read();
+ const compile = () =>
+ cachedCompilation({
+ astroConfig: config,
+ viteConfig: resolvedConfig,
+ filename,
+ source,
+ });
return handleHotUpdate(context, {
config,
logger,
compile,
- source: compileProps.source,
+ source,
});
},
};
diff --git a/packages/astro/src/vite-plugin-inject-env-ts/index.ts b/packages/astro/src/vite-plugin-inject-env-ts/index.ts
index d884075ab..116b45e3c 100644
--- a/packages/astro/src/vite-plugin-inject-env-ts/index.ts
+++ b/packages/astro/src/vite-plugin-inject-env-ts/index.ts
@@ -57,7 +57,7 @@ export async function setUpEnvTs({
'types="astro/client"'
);
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
- logger.info('assets', `Removed ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Removed ${bold(envTsPathRelativetoRoot)} type declarations`);
}
if (!fs.existsSync(dotAstroDir))
@@ -68,7 +68,7 @@ export async function setUpEnvTs({
if (!typesEnvContents.includes(expectedTypeReference)) {
typesEnvContents = `${expectedTypeReference}\n${typesEnvContents}`;
await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8');
- logger.info('content', `Added ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
}
} else {
// Otherwise, inject the `env.d.ts` file
@@ -81,6 +81,6 @@ export async function setUpEnvTs({
await fs.promises.mkdir(settings.config.srcDir, { recursive: true });
await fs.promises.writeFile(envTsPath, referenceDefs.join('\n'), 'utf-8');
- logger.info('astro', `Added ${bold(envTsPathRelativetoRoot)} types`);
+ logger.info('types', `Added ${bold(envTsPathRelativetoRoot)} type declarations`);
}
}
diff --git a/packages/astro/src/vite-plugin-integrations-container/index.ts b/packages/astro/src/vite-plugin-integrations-container/index.ts
index b18d2e5e9..2125df857 100644
--- a/packages/astro/src/vite-plugin-integrations-container/index.ts
+++ b/packages/astro/src/vite-plugin-integrations-container/index.ts
@@ -34,7 +34,7 @@ async function resolveEntryPoint(
this: PluginContext,
route: InjectedRoute
): Promise<ResolvedInjectedRoute> {
- const resolvedId = await this.resolve(route.entryPoint)
+ const resolvedId = await this.resolve(route.entrypoint)
.then((res) => res?.id)
.catch(() => undefined);
if (!resolvedId) return route;
diff --git a/packages/astro/src/vite-plugin-load-fallback/index.ts b/packages/astro/src/vite-plugin-load-fallback/index.ts
index e11f317ca..80db39edd 100644
--- a/packages/astro/src/vite-plugin-load-fallback/index.ts
+++ b/packages/astro/src/vite-plugin-load-fallback/index.ts
@@ -2,6 +2,7 @@ import nodeFs from 'node:fs';
import npath from 'node:path';
import type * as vite from 'vite';
import { slash } from '../core/path.js';
+import { cleanUrl } from '../vite-plugin-utils/index.js';
type NodeFileSystemModule = typeof nodeFs;
@@ -77,8 +78,3 @@ export default function loadFallbackPlugin({
},
];
}
-
-const queryRE = /\?.*$/s;
-const hashRE = /#.*$/s;
-
-const cleanUrl = (url: string): string => url.replace(hashRE, '').replace(queryRE, '');
diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts
index d48aed203..678f3ac18 100644
--- a/packages/astro/src/vite-plugin-scanner/index.ts
+++ b/packages/astro/src/vite-plugin-scanner/index.ts
@@ -52,12 +52,11 @@ export default function astroScannerPlugin({
// this should only be valid for `.astro`, `.js` and `.ts` files
KNOWN_FILE_EXTENSIONS.includes(extname(filename))
) {
- const reason = ` because \`output: "${settings.config.output}"\` is set`;
logger.warn(
- 'getStaticPaths',
- `The getStaticPaths() statement in ${bold(
+ 'router',
+ `getStaticPaths() ignored in dynamic page ${bold(
rootRelativePath(settings.config.root, fileURL, true)
- )} has been ignored${reason}.\n\nAdd \`export const prerender = true;\` to prerender this page.`
+ )}. Add \`export const prerender = true;\` to prerender the page as static HTML during the build process.`
);
}
diff --git a/packages/astro/src/vite-plugin-scanner/scan.ts b/packages/astro/src/vite-plugin-scanner/scan.ts
index 3eb834ea2..6c277567d 100644
--- a/packages/astro/src/vite-plugin-scanner/scan.ts
+++ b/packages/astro/src/vite-plugin-scanner/scan.ts
@@ -72,7 +72,7 @@ export async function scan(
message: AstroErrorData.InvalidPrerenderExport.message(
prefix,
suffix,
- settings?.config.output === 'hybrid' ?? false
+ settings?.config.output === 'hybrid'
),
location: { file: id },
});
diff --git a/packages/astro/src/vite-plugin-scripts/index.ts b/packages/astro/src/vite-plugin-scripts/index.ts
index 0066b98f5..9b2848923 100644
--- a/packages/astro/src/vite-plugin-scripts/index.ts
+++ b/packages/astro/src/vite-plugin-scripts/index.ts
@@ -50,8 +50,7 @@ export default function astroScriptsPlugin({ settings }: { settings: AstroSettin
},
buildStart() {
const hasHydrationScripts = settings.scripts.some((s) => s.stage === 'before-hydration');
- // @ts-expect-error Vite 5 renamed `ssrBuild` to `isSsrBuild`
- const isSsrBuild = env?.ssrBuild || env?.isSsrBuild;
+ const isSsrBuild = env?.isSsrBuild;
if (hasHydrationScripts && env?.command === 'build' && !isSsrBuild) {
this.emitFile({
type: 'chunk',
diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts
index 51f0e6cc4..7bf9f092f 100644
--- a/packages/astro/src/vite-plugin-utils/index.ts
+++ b/packages/astro/src/vite-plugin-utils/index.ts
@@ -56,3 +56,8 @@ export function normalizeFilename(filename: string, root: URL) {
}
return removeLeadingForwardSlashWindows(filename);
}
+
+const postfixRE = /[?#].*$/s;
+export function cleanUrl(url: string): string {
+ return url.replace(postfixRE, '');
+}