diff --git a/.changeset/odd-books-live.md b/.changeset/odd-books-live.md new file mode 100644 index 000000000000..40f0d7c1762a --- /dev/null +++ b/.changeset/odd-books-live.md @@ -0,0 +1,23 @@ +--- +'astro': minor +--- + +Integrations can now log messages using Astro’s built-in logger. + +The logger is available to all hooks as an additional parameter: + +```ts +import {AstroIntegration} from "./astro"; + +// integration.js +export function myIntegration(): AstroIntegration { + return { + name: "my-integration", + hooks: { + "astro:config:done": ({ logger }) => { + logger.info("Configure integration..."); + } + } + } +} +``` diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 4d74125e8947..b59a57a26429 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1857,87 +1857,70 @@ export interface AstroIntegration { name: string; /** The different hooks available to extend. */ hooks: { - 'astro:config:setup'?: ( - options: { - config: AstroConfig; - command: 'dev' | 'build' | 'preview'; - isRestart: boolean; - updateConfig: (newConfig: Record) => void; - addRenderer: (renderer: AstroRenderer) => void; - addWatchFile: (path: URL | string) => void; - injectScript: (stage: InjectedScriptStage, content: string) => void; - injectRoute: (injectRoute: InjectedRoute) => void; - addClientDirective: (directive: ClientDirectiveConfig) => void; - // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. - // This may require some refactoring of `scripts`, `styles`, and `links` into something - // more generalized. Consider the SSR use-case as well. - // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; - }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:config:done'?: ( - options: { - config: AstroConfig; - setAdapter: (adapter: AstroAdapter) => void; - }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:server:setup'?: ( - options: { server: vite.ViteDevServer }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:server:start'?: ( - options: { address: AddressInfo }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:server:done'?: (bag: AstroIntegrationBag) => void | Promise; - 'astro:build:ssr'?: ( - options: { - manifest: SerializedSSRManifest; - /** - * This maps a {@link RouteData} to an {@link URL}, this URL represents - * the physical file you should import. - */ - entryPoints: Map; - /** - * File path of the emitted middleware - */ - middlewareEntryPoint: URL | undefined; - }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:build:start'?: (bag: AstroIntegrationBag) => void | Promise; - 'astro:build:setup'?: ( - options: { - vite: vite.InlineConfig; - pages: Map; - target: 'client' | 'server'; - updateConfig: (newConfig: vite.InlineConfig) => void; - }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:build:generated'?: ( - options: { dir: URL }, - bag: AstroIntegrationBag - ) => void | Promise; - 'astro:build:done'?: ( - options: { - pages: { pathname: string }[]; - dir: URL; - routes: RouteData[]; - }, - bag: AstroIntegrationBag - ) => void | Promise; + 'astro:config:setup'?: (options: { + config: AstroConfig; + command: 'dev' | 'build' | 'preview'; + isRestart: boolean; + updateConfig: (newConfig: Record) => void; + addRenderer: (renderer: AstroRenderer) => void; + addWatchFile: (path: URL | string) => void; + injectScript: (stage: InjectedScriptStage, content: string) => void; + injectRoute: (injectRoute: InjectedRoute) => void; + addClientDirective: (directive: ClientDirectiveConfig) => void; + logger: AstroIntegrationLogger; + // TODO: Add support for `injectElement()` for full HTML element injection, not just scripts. + // This may require some refactoring of `scripts`, `styles`, and `links` into something + // more generalized. Consider the SSR use-case as well. + // injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void; + }) => void | Promise; + 'astro:config:done'?: (options: { + config: AstroConfig; + setAdapter: (adapter: AstroAdapter) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:setup'?: (options: { + server: vite.ViteDevServer; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:start'?: (options: { + address: AddressInfo; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:server:done'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:ssr'?: (options: { + manifest: SerializedSSRManifest; + /** + * This maps a {@link RouteData} to an {@link URL}, this URL represents + * the physical file you should import. + */ + entryPoints: Map; + /** + * File path of the emitted middleware + */ + middlewareEntryPoint: URL | undefined; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:start'?: (options: { logger: AstroIntegrationLogger }) => void | Promise; + 'astro:build:setup'?: (options: { + vite: vite.InlineConfig; + pages: Map; + target: 'client' | 'server'; + updateConfig: (newConfig: vite.InlineConfig) => void; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:generated'?: (options: { + dir: URL; + logger: AstroIntegrationLogger; + }) => void | Promise; + 'astro:build:done'?: (options: { + pages: { pathname: string }[]; + dir: URL; + routes: RouteData[]; + logger: AstroIntegrationLogger; + }) => void | Promise; }; } -/** - * A set of utilities that are passed at each hook - */ -export type AstroIntegrationBag = { - logger: AstroIntegrationLogger; -}; - export type MiddlewareNext = () => Promise; export type MiddlewareHandler = ( context: APIContext, diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts index 3395b8d1a439..e5d0aee1f0c5 100644 --- a/packages/astro/src/core/logger/core.ts +++ b/packages/astro/src/core/logger/core.ts @@ -157,7 +157,7 @@ export class AstroIntegrationLogger { } /** - * Creates a new logger instances with a new label, but the same log options. + * Creates a new logger instance with a new label, but the same log options. */ fork(label: string): AstroIntegrationLogger { return new AstroIntegrationLogger(this.options, label); diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index c94895948373..d54ac38037c8 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url'; import type { InlineConfig, ViteDevServer } from 'vite'; import type { AstroConfig, + AstroIntegration, AstroRenderer, AstroSettings, ContentEntryType, @@ -38,16 +39,16 @@ async function withTakingALongTimeMsg({ return result; } -// Internally used to store instances of loggers. -const Loggers = new Map(); +// Used internally to store instances of loggers. +const Loggers = new WeakMap(); -function getLogger(adapterName: string, logging: LogOptions) { - if (Loggers.has(adapterName)) { +function getLogger(integration: AstroIntegration, logging: LogOptions) { + if (Loggers.has(integration)) { // SAFETY: we check the existence in the if block - return Loggers.get(adapterName)!; + return Loggers.get(integration)!; } - const logger = new AstroIntegrationLogger(logging, adapterName); - Loggers.set(adapterName, logger); + const logger = new AstroIntegrationLogger(logging, integration.name); + Loggers.set(integration, logger); return logger; } @@ -85,7 +86,7 @@ export async function runHookConfigSetup({ * ``` */ if (integration.hooks?.['astro:config:setup']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); const hooks: HookParameters<'astro:config:setup'> = { config: updatedConfig, @@ -122,6 +123,7 @@ export async function runHookConfigSetup({ } addedClientDirectives.set(name, buildClientDirectiveEntrypoint(name, entrypoint)); }, + logger, }; // --- @@ -159,7 +161,7 @@ export async function runHookConfigSetup({ await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:config:setup'](hooks, { logger }), + hookResult: integration.hooks['astro:config:setup'](hooks), logging, }); @@ -182,24 +184,22 @@ export async function runHookConfigDone({ logging: LogOptions; }) { for (const integration of settings.config.integrations) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); if (integration?.hooks?.['astro:config:done']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:config:done']( - { - config: settings.config, - setAdapter(adapter) { - if (settings.adapter && settings.adapter.name !== adapter.name) { - throw new Error( - `Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.` - ); - } - settings.adapter = adapter; - }, + hookResult: integration.hooks['astro:config:done']({ + config: settings.config, + setAdapter(adapter) { + if (settings.adapter && settings.adapter.name !== adapter.name) { + throw new Error( + `Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.` + ); + } + settings.adapter = adapter; }, - { logger } - ), + logger, + }), logging, }); } @@ -217,10 +217,10 @@ export async function runHookServerSetup({ }) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:server:setup']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:server:setup']({ server }, { logger }), + hookResult: integration.hooks['astro:server:setup']({ server, logger }), logging, }); } @@ -237,12 +237,12 @@ export async function runHookServerStart({ logging: LogOptions; }) { for (const integration of config.integrations) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); if (integration?.hooks?.['astro:server:start']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:server:start']({ address }, { logger }), + hookResult: integration.hooks['astro:server:start']({ address, logger }), logging, }); } @@ -257,7 +257,7 @@ export async function runHookServerDone({ logging: LogOptions; }) { for (const integration of config.integrations) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); if (integration?.hooks?.['astro:server:done']) { await withTakingALongTimeMsg({ @@ -278,7 +278,7 @@ export async function runHookBuildStart({ }) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:start']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); await withTakingALongTimeMsg({ name: integration.name, @@ -306,21 +306,19 @@ export async function runHookBuildSetup({ for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:setup']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:setup']( - { - vite, - pages, - target, - updateConfig: (newConfig) => { - updatedConfig = mergeConfig(updatedConfig, newConfig); - }, + hookResult: integration.hooks['astro:build:setup']({ + vite, + pages, + target, + updateConfig: (newConfig) => { + updatedConfig = mergeConfig(updatedConfig, newConfig); }, - { logger } - ), + logger, + }), logging, }); } @@ -346,18 +344,16 @@ export async function runHookBuildSsr({ }: RunHookBuildSsr) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:ssr']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:ssr']( - { - manifest, - entryPoints, - middlewareEntryPoint, - }, - { logger } - ), + hookResult: integration.hooks['astro:build:ssr']({ + manifest, + entryPoints, + middlewareEntryPoint, + logger, + }), logging, }); } @@ -374,12 +370,12 @@ export async function runHookBuildGenerated({ const dir = isServerLikeOutput(config) ? config.build.client : config.outDir; for (const integration of config.integrations) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); if (integration?.hooks?.['astro:build:generated']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:generated']({ dir }, { logger }), + hookResult: integration.hooks['astro:build:generated']({ dir, logger }), logging, }); } @@ -399,18 +395,16 @@ export async function runHookBuildDone({ config, pages, routes, logging }: RunHo for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:done']) { - const logger = getLogger(integration.name, logging); + const logger = getLogger(integration, logging); await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:done']( - { - pages: pages.map((p) => ({ pathname: p })), - dir, - routes, - }, - { logger } - ), + hookResult: integration.hooks['astro:build:done']({ + pages: pages.map((p) => ({ pathname: p })), + dir, + routes, + logger, + }), logging, }); }