diff --git a/.changeset/happy-boxes-collect.md b/.changeset/happy-boxes-collect.md new file mode 100644 index 000000000000..2792c7a92898 --- /dev/null +++ b/.changeset/happy-boxes-collect.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue with the container APIs where a runtime error was thrown during the build, when using `pnpm` as package manager. diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts index 97b922653cae..53bc33e4b946 100644 --- a/packages/astro/src/container/index.ts +++ b/packages/astro/src/container/index.ts @@ -14,8 +14,8 @@ import type { SSRManifest, SSRResult, } from '../@types/astro.js'; -import { validateConfig } from '../core/config/config.js'; import { ASTRO_CONFIG_DEFAULTS } from '../core/config/schema.js'; +import { validateConfig } from '../core/config/validate.js'; import { Logger } from '../core/logger/core.js'; import { nodeLogDestination } from '../core/logger/node.js'; import { removeLeadingForwardSlash } from '../core/path.js'; @@ -208,7 +208,7 @@ type AstroContainerConstructor = { renderers?: SSRLoadedRenderer[]; manifest?: AstroContainerManifest; resolve?: SSRResult['resolve']; - astroConfig: AstroConfig; + astroConfig?: AstroConfig; }; export class experimental_AstroContainer { @@ -253,10 +253,10 @@ export class experimental_AstroContainer { }); } - async #containerResolve(specifier: string, astroConfig: AstroConfig): Promise { + async #containerResolve(specifier: string, astroConfig?: AstroConfig): Promise { const found = this.#pipeline.manifest.entryModules[specifier]; if (found) { - return new URL(found, astroConfig.build.client).toString(); + return new URL(found, astroConfig?.build.client).toString(); } return found; } diff --git a/packages/astro/src/container/pipeline.ts b/packages/astro/src/container/pipeline.ts index cffd38616587..1ad905bb5910 100644 --- a/packages/astro/src/container/pipeline.ts +++ b/packages/astro/src/container/pipeline.ts @@ -7,13 +7,10 @@ import type { } from '../@types/astro.js'; import { type HeadElements, Pipeline } from '../core/base-pipeline.js'; import type { SinglePageBuiltModule } from '../core/build/types.js'; -import { RouteNotFound } from '../core/errors/errors-data.js'; -import { AstroError } from '../core/errors/index.js'; import { createModuleScriptElement, createStylesheetElementSet, } from '../core/render/ssr-element.js'; -import { DEFAULT_404_ROUTE } from '../core/routing/astro-designed-error-pages.js'; import { findRouteToRewrite } from '../core/routing/rewrite.js'; export class ContainerPipeline extends Pipeline { diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index 5e45a4aaaae0..76634c3ddd25 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -3,12 +3,13 @@ import { extname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import glob from 'fast-glob'; import pLimit from 'p-limit'; -import { type Plugin } from 'vite'; +import type { Plugin } from 'vite'; import type { AstroSettings } from '../@types/astro.js'; import { encodeName } from '../core/build/util.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { appendForwardSlash, removeFileExtension } from '../core/path.js'; -import { isServerLikeOutput, rootRelativePath } from '../core/util.js'; +import { isServerLikeOutput } from '../core/util.js'; +import { rootRelativePath } from '../core/viteUtils.js'; import type { AstroPluginMetadata } from '../vite-plugin-astro/index.js'; import { CONTENT_FLAG, diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 791b33deaeba..339b316bb7b0 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -1,7 +1,7 @@ import { fileURLToPath } from 'node:url'; import glob from 'fast-glob'; import type { OutputChunk } from 'rollup'; -import { type Plugin as VitePlugin } from 'vite'; +import type { Plugin as VitePlugin } from 'vite'; import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js'; import { normalizeTheLocale } from '../../../i18n/index.js'; import { toRoutingStrategy } from '../../../i18n/utils.js'; diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index b6e1f927cab4..c3e6e4bb87a8 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -9,7 +9,7 @@ import type { AstroPreferences } from '../../preferences/index.js'; import type { AstroError } from '../errors/errors.js'; import { AggregateError, CompilerError } from '../errors/errors.js'; import { AstroErrorData } from '../errors/index.js'; -import { resolvePath } from '../util.js'; +import { resolvePath } from '../viteUtils.js'; import { type PartialCompileCssResult, createStylePreprocessor } from './style.js'; import type { CompileCssResult } from './types.js'; diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts index 9a700ab0ebbe..4c0f0aa3d4dd 100644 --- a/packages/astro/src/core/config/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -1,4 +1,9 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as colors from 'kleur/colors'; import type { Arguments as Flags } from 'yargs-parser'; +import { ZodError } from 'zod'; import type { AstroConfig, AstroInlineConfig, @@ -6,49 +11,14 @@ import type { AstroUserConfig, CLIFlags, } from '../../@types/astro.js'; - -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import * as colors from 'kleur/colors'; -import { ZodError } from 'zod'; import { eventConfigError, telemetry } from '../../events/index.js'; import { trackAstroConfigZodError } from '../errors/errors.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { formatConfigErrorMessage } from '../messages.js'; import { mergeConfig } from './merge.js'; -import { createRelativeSchema } from './schema.js'; +import { validateConfig } from './validate.js'; import { loadConfigWithVite } from './vite-load.js'; -/** Turn raw config values into normalized values */ -export async function validateConfig( - userConfig: any, - root: string, - cmd: string -): Promise { - const AstroConfigRelativeSchema = createRelativeSchema(cmd, root); - - // First-Pass Validation - let result: AstroConfig; - try { - result = await AstroConfigRelativeSchema.parseAsync(userConfig); - } catch (e) { - // Improve config zod error messages - if (e instanceof ZodError) { - // Mark this error so the callee can decide to suppress Zod's error if needed. - // We still want to throw the error to signal an error in validation. - trackAstroConfigZodError(e); - // eslint-disable-next-line no-console - console.error(formatConfigErrorMessage(e) + '\n'); - telemetry.record(eventConfigError({ cmd, err: e, isFatal: true })); - } - throw e; - } - - // If successful, return the result as a verified AstroConfig object. - return result; -} - /** Convert the generic "yargs" flag object into our own, custom TypeScript object. */ // NOTE: This function will be removed in a later PR. Use `flagsToAstroInlineConfig` instead. // All CLI related flow should be located in the `packages/astro/src/cli` directory. @@ -197,7 +167,22 @@ export async function resolveConfig( const userConfig = await loadConfig(root, inlineOnlyConfig.configFile, fsMod); const mergedConfig = mergeConfig(userConfig, inlineUserConfig); - const astroConfig = await validateConfig(mergedConfig, root, command); + // First-Pass Validation + let astroConfig: AstroConfig; + try { + astroConfig = await validateConfig(mergedConfig, root, command); + } catch (e) { + // Improve config zod error messages + if (e instanceof ZodError) { + // Mark this error so the callee can decide to suppress Zod's error if needed. + // We still want to throw the error to signal an error in validation. + trackAstroConfigZodError(e); + // eslint-disable-next-line no-console + console.error(formatConfigErrorMessage(e) + '\n'); + telemetry.record(eventConfigError({ cmd: command, err: e, isFatal: true })); + } + throw e; + } return { userConfig: mergedConfig, astroConfig }; } diff --git a/packages/astro/src/core/config/validate.ts b/packages/astro/src/core/config/validate.ts new file mode 100644 index 000000000000..8d1207a85bd1 --- /dev/null +++ b/packages/astro/src/core/config/validate.ts @@ -0,0 +1,14 @@ +import type { AstroConfig } from '../../@types/astro.js'; +import { createRelativeSchema } from './schema.js'; + +/** Turn raw config values into normalized values */ +export async function validateConfig( + userConfig: any, + root: string, + cmd: string +): Promise { + const AstroConfigRelativeSchema = createRelativeSchema(cmd, root); + + // First-Pass Validation + return await AstroConfigRelativeSchema.parseAsync(userConfig); +} diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index d3e7c8662bb3..57fb687834a6 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -1,11 +1,9 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { normalizePath } from 'vite'; import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro.js'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './constants.js'; -import type { ModuleLoader } from './module-loader/index.js'; -import { prependForwardSlash, removeTrailingForwardSlash, slash } from './path.js'; +import { removeTrailingForwardSlash, slash } from './path.js'; /** Returns true if argument is an object of any prototype/class (but not null). */ export function isObject(value: unknown): value is Record { @@ -184,54 +182,10 @@ export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) { return id.slice(slash(fileURLToPath(config.srcDir)).length); } -export function rootRelativePath( - root: URL, - idOrUrl: URL | string, - shouldPrependForwardSlash = true -) { - let id: string; - if (typeof idOrUrl !== 'string') { - id = unwrapId(viteID(idOrUrl)); - } else { - id = idOrUrl; - } - const normalizedRoot = normalizePath(fileURLToPath(root)); - if (id.startsWith(normalizedRoot)) { - id = id.slice(normalizedRoot.length); - } - return shouldPrependForwardSlash ? prependForwardSlash(id) : id; -} - export function emoji(char: string, fallback: string) { return process.platform !== 'win32' ? char : fallback; } -/** - * Simulate Vite's resolve and import analysis so we can import the id as an URL - * through a script tag or a dynamic import as-is. - */ -// NOTE: `/@id/` should only be used when the id is fully resolved -export async function resolveIdToUrl(loader: ModuleLoader, id: string, root?: URL) { - let resultId = await loader.resolveId(id, undefined); - // Try resolve jsx to tsx - if (!resultId && id.endsWith('.jsx')) { - resultId = await loader.resolveId(id.slice(0, -4), undefined); - } - if (!resultId) { - return VALID_ID_PREFIX + id; - } - if (path.isAbsolute(resultId)) { - const normalizedRoot = root && normalizePath(fileURLToPath(root)); - // Convert to root-relative path if path is inside root - if (normalizedRoot && resultId.startsWith(normalizedRoot)) { - return resultId.slice(normalizedRoot.length - 1); - } else { - return '/@fs' + prependForwardSlash(resultId); - } - } - return VALID_ID_PREFIX + resultId; -} - export function resolveJsToTs(filePath: string) { if (filePath.endsWith('.jsx') && !fs.existsSync(filePath)) { const tryPath = filePath.slice(0, -4) + '.tsx'; @@ -242,18 +196,6 @@ export function resolveJsToTs(filePath: string) { return filePath; } -/** - * Resolve the hydration paths so that it can be imported in the client - */ -export function resolvePath(specifier: string, importer: string) { - if (specifier.startsWith('.')) { - const absoluteSpecifier = path.resolve(path.dirname(importer), specifier); - return resolveJsToTs(normalizePath(absoluteSpecifier)); - } else { - return specifier; - } -} - /** * Set a default NODE_ENV so Vite doesn't set an incorrect default when loading the Astro config */ diff --git a/packages/astro/src/core/viteUtils.ts b/packages/astro/src/core/viteUtils.ts new file mode 100644 index 000000000000..61ca26a3dc28 --- /dev/null +++ b/packages/astro/src/core/viteUtils.ts @@ -0,0 +1,62 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { normalizePath } from 'vite'; +import { prependForwardSlash } from '../core/path.js'; +import type { ModuleLoader } from './module-loader/index.js'; +import { VALID_ID_PREFIX, resolveJsToTs, unwrapId, viteID } from './util.js'; + +/** + * Resolve the hydration paths so that it can be imported in the client + */ +export function resolvePath(specifier: string, importer: string) { + if (specifier.startsWith('.')) { + const absoluteSpecifier = path.resolve(path.dirname(importer), specifier); + return resolveJsToTs(normalizePath(absoluteSpecifier)); + } else { + return specifier; + } +} + +export function rootRelativePath( + root: URL, + idOrUrl: URL | string, + shouldPrependForwardSlash = true +) { + let id: string; + if (typeof idOrUrl !== 'string') { + id = unwrapId(viteID(idOrUrl)); + } else { + id = idOrUrl; + } + const normalizedRoot = normalizePath(fileURLToPath(root)); + if (id.startsWith(normalizedRoot)) { + id = id.slice(normalizedRoot.length); + } + return shouldPrependForwardSlash ? prependForwardSlash(id) : id; +} + +/** + * Simulate Vite's resolve and import analysis so we can import the id as an URL + * through a script tag or a dynamic import as-is. + */ +// NOTE: `/@id/` should only be used when the id is fully resolved +export async function resolveIdToUrl(loader: ModuleLoader, id: string, root?: URL) { + let resultId = await loader.resolveId(id, undefined); + // Try resolve jsx to tsx + if (!resultId && id.endsWith('.jsx')) { + resultId = await loader.resolveId(id.slice(0, -4), undefined); + } + if (!resultId) { + return VALID_ID_PREFIX + id; + } + if (path.isAbsolute(resultId)) { + const normalizedRoot = root && normalizePath(fileURLToPath(root)); + // Convert to root-relative path if path is inside root + if (normalizedRoot && resultId.startsWith(normalizedRoot)) { + return resultId.slice(normalizedRoot.length - 1); + } else { + return '/@fs' + prependForwardSlash(resultId); + } + } + return VALID_ID_PREFIX + resultId; +} diff --git a/packages/astro/src/jsx/babel.ts b/packages/astro/src/jsx/babel.ts index d5fc0ccd30b0..bb1a3ce5f266 100644 --- a/packages/astro/src/jsx/babel.ts +++ b/packages/astro/src/jsx/babel.ts @@ -2,7 +2,7 @@ import type { PluginObj } from '@babel/core'; import * as t from '@babel/types'; import { AstroError } from '../core/errors/errors.js'; import { AstroErrorData } from '../core/errors/index.js'; -import { resolvePath } from '../core/util.js'; +import { resolvePath } from '../core/viteUtils.js'; import type { PluginMetadata } from '../vite-plugin-astro/types.js'; const ClientOnlyPlaceholder = 'astro-client-only'; diff --git a/packages/astro/src/jsx/rehype.ts b/packages/astro/src/jsx/rehype.ts index 40a8359cbe5c..7e3229fc5fd1 100644 --- a/packages/astro/src/jsx/rehype.ts +++ b/packages/astro/src/jsx/rehype.ts @@ -9,7 +9,7 @@ import { visit } from 'unist-util-visit'; import type { VFile } from 'vfile'; import { AstroError } from '../core/errors/errors.js'; import { AstroErrorData } from '../core/errors/index.js'; -import { resolvePath } from '../core/util.js'; +import { resolvePath } from '../core/viteUtils.js'; import type { PluginMetadata } from '../vite-plugin-astro/types.js'; // This import includes ambient types for hast to include mdx nodes diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts index 3d80ead1e027..4ad3c48c8ba6 100644 --- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts +++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts @@ -12,18 +12,16 @@ import type { } from '../@types/astro.js'; import { getInfoOutput } from '../cli/info/index.js'; import { type HeadElements } from '../core/base-pipeline.js'; -import { shouldAppendForwardSlash } from '../core/build/util.js'; import { ASTRO_VERSION, DEFAULT_404_COMPONENT } from '../core/constants.js'; import { enhanceViteSSRError } from '../core/errors/dev/index.js'; -import { RewriteEncounteredAnError } from '../core/errors/errors-data.js'; -import { AggregateError, AstroError, CSSError, MarkdownError } from '../core/errors/index.js'; +import { AggregateError, CSSError, MarkdownError } from '../core/errors/index.js'; import type { Logger } from '../core/logger/core.js'; import type { ModuleLoader } from '../core/module-loader/index.js'; -import { prependForwardSlash, removeTrailingForwardSlash } from '../core/path.js'; import { Pipeline, loadRenderer } from '../core/render/index.js'; -import { DEFAULT_404_ROUTE, default404Page } from '../core/routing/astro-designed-error-pages.js'; +import { default404Page } from '../core/routing/astro-designed-error-pages.js'; import { findRouteToRewrite } from '../core/routing/rewrite.js'; -import { isPage, isServerLikeOutput, resolveIdToUrl, viteID } from '../core/util.js'; +import { isPage, isServerLikeOutput, viteID } from '../core/util.js'; +import { resolveIdToUrl } from '../core/viteUtils.js'; import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js'; import { getStylesForURL } from './css.js'; import { getComponentMetadata } from './metadata.js'; diff --git a/packages/astro/src/vite-plugin-astro-server/resolve.ts b/packages/astro/src/vite-plugin-astro-server/resolve.ts index cbeda56b0c87..03b516c95ad4 100644 --- a/packages/astro/src/vite-plugin-astro-server/resolve.ts +++ b/packages/astro/src/vite-plugin-astro-server/resolve.ts @@ -1,5 +1,5 @@ import type { ModuleLoader } from '../core/module-loader/index.js'; -import { resolveIdToUrl } from '../core/util.js'; +import { resolveIdToUrl } from '../core/viteUtils.js'; export function createResolve(loader: ModuleLoader, root: URL) { // Resolves specifiers in the inline hydrated scripts, such as: diff --git a/packages/astro/src/vite-plugin-astro-server/scripts.ts b/packages/astro/src/vite-plugin-astro-server/scripts.ts index 902909753224..4b94d12f57a5 100644 --- a/packages/astro/src/vite-plugin-astro-server/scripts.ts +++ b/packages/astro/src/vite-plugin-astro-server/scripts.ts @@ -1,7 +1,8 @@ import type { SSRElement } from '../@types/astro.js'; import type { ModuleInfo, ModuleLoader } from '../core/module-loader/index.js'; import { createModuleScriptElementWithSrc } from '../core/render/ssr-element.js'; -import { rootRelativePath, viteID } from '../core/util.js'; +import { viteID } from '../core/util.js'; +import { rootRelativePath } from '../core/viteUtils.js'; import type { PluginMetadata as AstroPluginMetadata } from '../vite-plugin-astro/types.js'; import { crawlGraph } from './vite.js'; diff --git a/packages/astro/src/vite-plugin-scanner/index.ts b/packages/astro/src/vite-plugin-scanner/index.ts index 78069b937a3a..180c2b39059f 100644 --- a/packages/astro/src/vite-plugin-scanner/index.ts +++ b/packages/astro/src/vite-plugin-scanner/index.ts @@ -4,7 +4,8 @@ import type { Plugin as VitePlugin } from 'vite'; import { normalizePath } from 'vite'; import type { AstroSettings } from '../@types/astro.js'; import { type Logger } from '../core/logger/core.js'; -import { isEndpoint, isPage, isServerLikeOutput, rootRelativePath } from '../core/util.js'; +import { isEndpoint, isPage, isServerLikeOutput } from '../core/util.js'; +import { rootRelativePath } from '../core/viteUtils.js'; import { getPrerenderDefault } from '../prerender/utils.js'; import { scan } from './scan.js'; diff --git a/packages/astro/test/units/config/config-validate.test.js b/packages/astro/test/units/config/config-validate.test.js index 21d6841c1dfd..13574982939c 100644 --- a/packages/astro/test/units/config/config-validate.test.js +++ b/packages/astro/test/units/config/config-validate.test.js @@ -2,7 +2,7 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import stripAnsi from 'strip-ansi'; import { z } from 'zod'; -import { validateConfig } from '../../../dist/core/config/config.js'; +import { validateConfig } from '../../../dist/core/config/validate.js'; import { formatConfigErrorMessage } from '../../../dist/core/messages.js'; describe('Config Validation', () => { diff --git a/packages/astro/test/units/i18n/astro_i18n.test.js b/packages/astro/test/units/i18n/astro_i18n.test.js index c53d4374871e..9d424f5b16a8 100644 --- a/packages/astro/test/units/i18n/astro_i18n.test.js +++ b/packages/astro/test/units/i18n/astro_i18n.test.js @@ -3,7 +3,7 @@ import { describe, it } from 'node:test'; import { MissingLocale } from '#astro/core/errors/errors-data'; import { AstroError } from '#astro/core/errors/index'; import { toRoutingStrategy } from '#astro/i18n/utils'; -import { validateConfig } from '../../../dist/core/config/config.js'; +import { validateConfig } from '../../../dist/core/config/validate.js'; import { getLocaleAbsoluteUrl, getLocaleAbsoluteUrlList,