diff --git a/.changeset/kind-rats-lay.md b/.changeset/kind-rats-lay.md new file mode 100644 index 000000000..9278f6d6e --- /dev/null +++ b/.changeset/kind-rats-lay.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Handle builds with outDir outside of current working directory diff --git a/packages/astro/src/core/build/common.ts b/packages/astro/src/core/build/common.ts index 8b57334d8..94c96a263 100644 --- a/packages/astro/src/core/build/common.ts +++ b/packages/astro/src/core/build/common.ts @@ -1,8 +1,10 @@ import npath from 'path'; +import { fileURLToPath, pathToFileURL } from 'url'; import type { AstroConfig, RouteType } from '../../@types/astro'; import { appendForwardSlash } from '../../core/path.js'; const STATUS_CODE_PAGES = new Set(['/404', '/500']); +const FALLBACK_OUT_DIR_NAME = './.astro/'; function getOutRoot(astroConfig: AstroConfig): URL { return new URL('./', astroConfig.outDir); @@ -59,3 +61,16 @@ export function getOutFile( } } } + +/** + * Ensures the `outDir` is within `process.cwd()`. If not it will fallback to `/.astro`. + * This is used for static `ssrBuild` so the output can access node_modules when we import + * the output files. A hardcoded fallback dir is fine as it would be cleaned up after build. + */ +export function getOutDirWithinCwd(outDir: URL): URL { + if (fileURLToPath(outDir).startsWith(process.cwd())) { + return outDir; + } else { + return new URL(FALLBACK_OUT_DIR_NAME, pathToFileURL(process.cwd() + npath.sep)); + } +} diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index b6ebf4697..378409c5a 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -28,7 +28,7 @@ import { createLinkStylesheetElementSet, createModuleScriptsSet } from '../rende import { createRequest } from '../request.js'; import { matchRoute } from '../routing/match.js'; import { getOutputFilename } from '../util.js'; -import { getOutFile, getOutFolder } from './common.js'; +import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js'; import { eachPageData, getPageDataByComponent, sortedCSS } from './internal.js'; import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types'; import { getTimeStat } from './util.js'; @@ -103,7 +103,7 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn const ssr = opts.astroConfig.output === 'server'; const serverEntry = opts.buildConfig.serverEntry; - const outFolder = ssr ? opts.buildConfig.server : opts.astroConfig.outDir; + const outFolder = ssr ? opts.buildConfig.server : getOutDirWithinCwd(opts.astroConfig.outDir); const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder); const ssrEntry = await import(ssrEntryURL.toString()); const builtPaths = new Set(); diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index e471f6652..0e91dc548 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -10,6 +10,7 @@ import { runHookBuildSetup } from '../../integrations/index.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import type { ViteConfigWithSSR } from '../create-vite'; import { info } from '../logger/core.js'; +import { getOutDirWithinCwd } from './common.js'; import { generatePages } from './generate.js'; import { trackPageData } from './internal.js'; import type { PageBuildData, StaticBuildOptions } from './types'; @@ -110,7 +111,7 @@ Learn more: https://docs.astro.build/en/guides/server-side-rendering/ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set) { const { astroConfig, viteConfig } = opts; const ssr = astroConfig.output === 'server'; - const out = ssr ? opts.buildConfig.server : astroConfig.outDir; + const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(astroConfig.outDir); const viteBuildConfig: ViteConfigWithSSR = { ...viteConfig, @@ -245,13 +246,19 @@ async function clientBuild( } async function cleanSsrOutput(opts: StaticBuildOptions) { + const out = getOutDirWithinCwd(opts.astroConfig.outDir); + // Clean out directly if the outDir is outside of root + if (out.toString() !== opts.astroConfig.outDir.toString()) { + await fs.promises.rm(out, { recursive: true }); + return; + } // The SSR output is all .mjs files, the client output is not. const files = await glob('**/*.mjs', { - cwd: fileURLToPath(opts.astroConfig.outDir), + cwd: fileURLToPath(out), }); await Promise.all( files.map(async (filename) => { - const url = new URL(filename, opts.astroConfig.outDir); + const url = new URL(filename, out); await fs.promises.rm(url); }) );