From 95110f4aec29bc8df5f2d423fc624076db716126 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 25 Jul 2023 12:52:54 +0100 Subject: [PATCH] feat: pass logger to integrations --- packages/astro/src/@types/astro.ts | 126 ++++++++++++++-------- packages/astro/src/core/logger/console.ts | 2 +- packages/astro/src/core/logger/core.ts | 69 ++++++++++-- packages/astro/src/core/logger/node.ts | 14 +-- packages/astro/src/integrations/index.ts | 111 +++++++++++++------ packages/astro/test/static-build.test.js | 2 +- pnpm-lock.yaml | 4 + 7 files changed, 228 insertions(+), 100 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index fb34954ac..4d74125e8 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -22,6 +22,7 @@ import type { AstroCookies } from '../core/cookies'; import type { LogOptions } from '../core/logger/core'; import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server'; import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js'; +import { AstroIntegrationLogger } from '../core/logger/core'; export type { MarkdownHeading, MarkdownMetadata, @@ -1856,56 +1857,87 @@ 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; - }) => void | Promise; - 'astro:config:done'?: (options: { - config: AstroConfig; - setAdapter: (adapter: AstroAdapter) => void; - }) => void | Promise; - 'astro:server:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise; - 'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise; - 'astro:server:done'?: () => 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; - }) => void | Promise; - 'astro:build:start'?: () => void | Promise; - 'astro:build:setup'?: (options: { - vite: vite.InlineConfig; - pages: Map; - target: 'client' | 'server'; - updateConfig: (newConfig: vite.InlineConfig) => void; - }) => void | Promise; - 'astro:build:generated'?: (options: { dir: URL }) => void | Promise; - 'astro:build:done'?: (options: { - pages: { pathname: string }[]; - dir: URL; - routes: RouteData[]; - }) => 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; + // 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; }; } +/** + * 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/console.ts b/packages/astro/src/core/logger/console.ts index dfe732bd7..f39f6b74d 100644 --- a/packages/astro/src/core/logger/console.ts +++ b/packages/astro/src/core/logger/console.ts @@ -15,7 +15,7 @@ export const consoleLogDestination = { function getPrefix() { let prefix = ''; - let type = event.type; + let type = event.label; if (type) { // hide timestamp when type is undefined prefix += dim(dateTimeFormat.format(new Date()) + ' '); diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts index 4f0c281e0..3395b8d1a 100644 --- a/packages/astro/src/core/logger/core.ts +++ b/packages/astro/src/core/logger/core.ts @@ -6,7 +6,6 @@ interface LogWritable { } export type LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'; // same as Pino -export type LoggerEvent = 'info' | 'warn' | 'error'; export interface LogOptions { dest: LogWritable; @@ -29,7 +28,7 @@ export const dateTimeFormat = new Intl.DateTimeFormat([], { }); export interface LogMessage { - type: string | null; + label: string | null; level: LoggerLevel; message: string; } @@ -43,11 +42,11 @@ export const levels: Record = { }; /** Full logging API */ -export function log(opts: LogOptions, level: LoggerLevel, type: string | null, message: string) { +export function log(opts: LogOptions, level: LoggerLevel, label: string | null, message: string) { const logLevel = opts.level; const dest = opts.dest; const event: LogMessage = { - type, + label, level, message, }; @@ -61,18 +60,18 @@ export function log(opts: LogOptions, level: LoggerLevel, type: string | null, m } /** Emit a user-facing message. Useful for UI and other console messages. */ -export function info(opts: LogOptions, type: string | null, message: string) { - return log(opts, 'info', type, message); +export function info(opts: LogOptions, label: string | null, message: string) { + return log(opts, 'info', label, message); } /** Emit a warning message. Useful for high-priority messages that aren't necessarily errors. */ -export function warn(opts: LogOptions, type: string | null, message: string) { - return log(opts, 'warn', type, message); +export function warn(opts: LogOptions, label: string | null, message: string) { + return log(opts, 'warn', label, message); } /** Emit a error message, Useful when Astro can't recover from some error. */ -export function error(opts: LogOptions, type: string | null, message: string) { - return log(opts, 'error', type, message); +export function error(opts: LogOptions, label: string | null, message: string) { + return log(opts, 'error', label, message); } type LogFn = typeof info | typeof warn | typeof error; @@ -127,3 +126,53 @@ export function timerMessage(message: string, startTime: number = Date.now()) { timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`; return `${message} ${dim(timeDisplay)}`; } + +export class Logger { + options: LogOptions; + constructor(options: LogOptions) { + this.options = options; + } + + info(label: string, message: string) { + info(this.options, label, message); + } + warn(label: string, message: string) { + warn(this.options, label, message); + } + error(label: string, message: string) { + error(this.options, label, message); + } + debug(label: string, message: string) { + debug(this.options, label, message); + } +} + +export class AstroIntegrationLogger { + options: LogOptions; + label: string; + + constructor(logging: LogOptions, label: string) { + this.options = logging; + this.label = label; + } + + /** + * Creates a new logger instances with a new label, but the same log options. + */ + fork(label: string): AstroIntegrationLogger { + return new AstroIntegrationLogger(this.options, label); + } + + info(message: string) { + info(this.options, this.label, message); + } + warn(message: string) { + warn(this.options, this.label, message); + } + error(message: string) { + error(this.options, this.label, message); + } + debug(message: string) { + debug(this.options, this.label, message); + } +} diff --git a/packages/astro/src/core/logger/node.ts b/packages/astro/src/core/logger/node.ts index 513ba257e..aeef4bd84 100644 --- a/packages/astro/src/core/logger/node.ts +++ b/packages/astro/src/core/logger/node.ts @@ -21,19 +21,19 @@ export const nodeLogDestination = new Writable({ function getPrefix() { let prefix = ''; - let type = event.type; - if (type) { + let label = event.label; + if (label) { // hide timestamp when type is undefined prefix += dim(dateTimeFormat.format(new Date()) + ' '); if (event.level === 'info') { - type = bold(cyan(`[${type}]`)); + label = bold(cyan(`[${label}]`)); } else if (event.level === 'warn') { - type = bold(yellow(`[${type}]`)); + label = bold(yellow(`[${label}]`)); } else if (event.level === 'error') { - type = bold(red(`[${type}]`)); + label = bold(red(`[${label}]`)); } - prefix += `${type} `; + prefix += `${label} `; } return reset(prefix); } @@ -87,7 +87,7 @@ export const nodeLogOptions: Required = { }; export interface LogMessage { - type: string | null; + label: string | null; level: LoggerLevel; message: string; } diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts index cf50df0e1..c94895948 100644 --- a/packages/astro/src/integrations/index.ts +++ b/packages/astro/src/integrations/index.ts @@ -16,7 +16,7 @@ import type { SerializedSSRManifest } from '../core/app/types'; import type { PageBuildData } from '../core/build/types'; import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js'; import { mergeConfig } from '../core/config/index.js'; -import { info, type LogOptions } from '../core/logger/core.js'; +import { info, type LogOptions, AstroIntegrationLogger } from '../core/logger/core.js'; import { isServerLikeOutput } from '../prerender/utils.js'; async function withTakingALongTimeMsg({ @@ -38,6 +38,19 @@ async function withTakingALongTimeMsg({ return result; } +// Internally used to store instances of loggers. +const Loggers = new Map(); + +function getLogger(adapterName: string, logging: LogOptions) { + if (Loggers.has(adapterName)) { + // SAFETY: we check the existence in the if block + return Loggers.get(adapterName)!; + } + const logger = new AstroIntegrationLogger(logging, adapterName); + Loggers.set(adapterName, logger); + return logger; +} + export async function runHookConfigSetup({ settings, command, @@ -72,6 +85,8 @@ export async function runHookConfigSetup({ * ``` */ if (integration.hooks?.['astro:config:setup']) { + const logger = getLogger(integration.name, logging); + const hooks: HookParameters<'astro:config:setup'> = { config: updatedConfig, command, @@ -144,7 +159,7 @@ export async function runHookConfigSetup({ await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:config:setup'](hooks), + hookResult: integration.hooks['astro:config:setup'](hooks, { logger }), logging, }); @@ -167,20 +182,24 @@ export async function runHookConfigDone({ logging: LogOptions; }) { for (const integration of settings.config.integrations) { + const logger = getLogger(integration.name, 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 } + ), logging, }); } @@ -198,9 +217,10 @@ export async function runHookServerSetup({ }) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:server:setup']) { + const logger = getLogger(integration.name, logging); await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:server:setup']({ server }), + hookResult: integration.hooks['astro:server:setup']({ server }, { logger }), logging, }); } @@ -217,10 +237,12 @@ export async function runHookServerStart({ logging: LogOptions; }) { for (const integration of config.integrations) { + const logger = getLogger(integration.name, logging); + if (integration?.hooks?.['astro:server:start']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:server:start']({ address }), + hookResult: integration.hooks['astro:server:start']({ address }, { logger }), logging, }); } @@ -235,10 +257,12 @@ export async function runHookServerDone({ logging: LogOptions; }) { for (const integration of config.integrations) { + const logger = getLogger(integration.name, logging); + if (integration?.hooks?.['astro:server:done']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:server:done'](), + hookResult: integration.hooks['astro:server:done']({ logger }), logging, }); } @@ -254,9 +278,11 @@ export async function runHookBuildStart({ }) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:start']) { + const logger = getLogger(integration.name, logging); + await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:start'](), + hookResult: integration.hooks['astro:build:start']({ logger }), logging, }); } @@ -280,16 +306,21 @@ export async function runHookBuildSetup({ for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:setup']) { + const logger = getLogger(integration.name, 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 } + ), logging, }); } @@ -315,13 +346,18 @@ export async function runHookBuildSsr({ }: RunHookBuildSsr) { for (const integration of config.integrations) { if (integration?.hooks?.['astro:build:ssr']) { + const logger = getLogger(integration.name, logging); + await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:ssr']({ - manifest, - entryPoints, - middlewareEntryPoint, - }), + hookResult: integration.hooks['astro:build:ssr']( + { + manifest, + entryPoints, + middlewareEntryPoint, + }, + { logger } + ), logging, }); } @@ -338,10 +374,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); + if (integration?.hooks?.['astro:build:generated']) { await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:generated']({ dir }), + hookResult: integration.hooks['astro:build:generated']({ dir }, { logger }), logging, }); } @@ -361,13 +399,18 @@ 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); + await withTakingALongTimeMsg({ name: integration.name, - hookResult: integration.hooks['astro:build:done']({ - pages: pages.map((p) => ({ pathname: p })), - dir, - routes, - }), + hookResult: integration.hooks['astro:build:done']( + { + pages: pages.map((p) => ({ pathname: p })), + dir, + routes, + }, + { logger } + ), logging, }); } diff --git a/packages/astro/test/static-build.test.js b/packages/astro/test/static-build.test.js index d4a687a5d..0552c353f 100644 --- a/packages/astro/test/static-build.test.js +++ b/packages/astro/test/static-build.test.js @@ -175,7 +175,7 @@ describe('Static build', () => { let found = false; for (const log of logs) { if ( - log.type === 'ssg' && + log.label === 'ssg' && /[hH]eaders are not exposed in static \(SSG\) output mode/.test(log.message) ) { found = true; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a929dd1c5..c2da8b51e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18752,21 +18752,25 @@ packages: file:packages/astro/test/fixtures/css-assets/packages/font-awesome: resolution: {directory: packages/astro/test/fixtures/css-assets/packages/font-awesome, type: directory} name: '@test/astro-font-awesome-package' + version: 0.0.1 dev: false file:packages/astro/test/fixtures/multiple-renderers/renderers/one: resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/one, type: directory} name: '@test/astro-renderer-one' + version: 1.0.0 dev: false file:packages/astro/test/fixtures/multiple-renderers/renderers/two: resolution: {directory: packages/astro/test/fixtures/multiple-renderers/renderers/two, type: directory} name: '@test/astro-renderer-two' + version: 1.0.0 dev: false file:packages/astro/test/fixtures/solid-component/deps/solid-jsx-component: resolution: {directory: packages/astro/test/fixtures/solid-component/deps/solid-jsx-component, type: directory} name: '@test/solid-jsx-component' + version: 0.0.0 dependencies: solid-js: 1.7.6 dev: false