@knitli/astro-docs-template 0.2.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +25 -20
- package/scaffolding/README.md +1 -1
- package/src/config.ts +123 -61
- package/src/index.test.ts +3 -3
- package/src/index.ts +1 -1
- package/src/integration.test.ts +95 -0
- /package/scaffolding/{wrangler.jsonc → wrangler.json} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knitli/astro-docs-template",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Opinionated Astro + Starlight docs site template with Knitli branding",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"knitli",
|
|
@@ -38,22 +38,33 @@
|
|
|
38
38
|
"clean": "rm -rf dist",
|
|
39
39
|
"deploy": "bun publish --tag latest",
|
|
40
40
|
"prepack": "bun run build",
|
|
41
|
-
"test": "vitest run",
|
|
41
|
+
"test": "vitest run src/index.test.ts",
|
|
42
|
+
"test:integration": "vitest run src/integration.test.ts",
|
|
43
|
+
"test:unit": "vitest run src/index.test.ts",
|
|
42
44
|
"test:watch": "vitest"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
|
-
"@astrojs/
|
|
47
|
+
"@astrojs/check": "^0.9.6",
|
|
48
|
+
"@astrojs/cloudflare": "^13.1.7",
|
|
49
|
+
"@astrojs/compiler-rs": "^0.1.6",
|
|
46
50
|
"@astrojs/markdoc": "^1.0.0",
|
|
47
|
-
"@astrojs/mdx": "^5.0.
|
|
48
|
-
"@astrojs/sitemap": "^3.
|
|
49
|
-
"@astrojs/starlight": "^0.38.
|
|
51
|
+
"@astrojs/mdx": "^5.0.3",
|
|
52
|
+
"@astrojs/sitemap": "^3.7.2",
|
|
53
|
+
"@astrojs/starlight": "^0.38.2",
|
|
54
|
+
"@biomejs/biome": "^2.4.2",
|
|
50
55
|
"@knitli/docs-components": "*",
|
|
51
|
-
"@
|
|
52
|
-
"
|
|
56
|
+
"@knitli/tsconfig": "*",
|
|
57
|
+
"@nuasite/llm-enhancements": "^0.19.1",
|
|
58
|
+
"@types/bun": "^1.3.4",
|
|
59
|
+
"@types/node": "^24.0.2",
|
|
60
|
+
"astro": "^6.1.4",
|
|
53
61
|
"astro-cloudflare-pages-headers": "^1.7.7",
|
|
54
62
|
"astro-d2": "^0.10.0",
|
|
55
63
|
"astro-favicons": "3.1.6",
|
|
64
|
+
"bun": "^1.3.9",
|
|
65
|
+
"lightningcss": "^1.30.2",
|
|
56
66
|
"rehype-external-links": "^3.0.0",
|
|
67
|
+
"sharp": "^0.34.5",
|
|
57
68
|
"starlight-announcement": ">=0.1.1",
|
|
58
69
|
"starlight-changelogs": " >=0.1.0",
|
|
59
70
|
"starlight-heading-badges": ">=0.6.1",
|
|
@@ -64,18 +75,9 @@
|
|
|
64
75
|
"starlight-scroll-to-top": ">=0.4.0",
|
|
65
76
|
"starlight-sidebar-topics": ">=0.7.1",
|
|
66
77
|
"starlight-tags": ">=0.4.0",
|
|
67
|
-
"vite-tsconfig-paths": "6.1.1",
|
|
68
|
-
"vite": "^7.0.0",
|
|
69
|
-
"@astrojs/compiler-rs": "^0.1.4",
|
|
70
|
-
"@biomejs/biome": "^2.4.2",
|
|
71
|
-
"@knitli/tsconfig": "*",
|
|
72
|
-
"@types/bun": "^1.3.4",
|
|
73
|
-
"@types/node": "^24.0.2",
|
|
74
|
-
"bun": "^1.3.9",
|
|
75
|
-
"lightningcss": "^1.30.2",
|
|
76
|
-
"sharp": "^0.34.5",
|
|
77
78
|
"svgo": "^4.0.0",
|
|
78
|
-
"
|
|
79
|
+
"vite": "^7.3.1",
|
|
80
|
+
"vite-tsconfig-paths": "6.1.1"
|
|
79
81
|
},
|
|
80
82
|
"devDependencies": {
|
|
81
83
|
"typescript": "^5.9.3",
|
|
@@ -93,5 +95,8 @@
|
|
|
93
95
|
"provenance": false,
|
|
94
96
|
"registry": "https://registry.npmjs.org/",
|
|
95
97
|
"tag": "latest"
|
|
96
|
-
}
|
|
98
|
+
},
|
|
99
|
+
"trustedDependencies": [
|
|
100
|
+
"bun"
|
|
101
|
+
]
|
|
97
102
|
}
|
package/scaffolding/README.md
CHANGED
|
@@ -26,7 +26,7 @@ bun run deploy
|
|
|
26
26
|
│ ├── styles/ # Site-specific CSS overrides
|
|
27
27
|
│ └── content.config.ts
|
|
28
28
|
├── astro.config.ts # Uses @knitli/astro-docs-template createConfig
|
|
29
|
-
├── wrangler.
|
|
29
|
+
├── wrangler.json # Cloudflare Workers configuration
|
|
30
30
|
└── package.json
|
|
31
31
|
```
|
|
32
32
|
|
package/src/config.ts
CHANGED
|
@@ -10,8 +10,10 @@ import markdoc from "@astrojs/markdoc";
|
|
|
10
10
|
import mdx from "@astrojs/mdx";
|
|
11
11
|
import sitemap from "@astrojs/sitemap";
|
|
12
12
|
import starlight from "@astrojs/starlight";
|
|
13
|
+
import type { StarlightPlugin } from "@astrojs/starlight/types";
|
|
13
14
|
import { DocsAssets } from "@knitli/docs-components";
|
|
14
15
|
import llmEnhancements from "@nuasite/llm-enhancements";
|
|
16
|
+
import type { AstroIntegration } from "astro";
|
|
15
17
|
import { defineConfig, fontProviders } from "astro/config";
|
|
16
18
|
import cloudflarePagesHeaders from "astro-cloudflare-pages-headers";
|
|
17
19
|
import astroD2 from "astro-d2";
|
|
@@ -39,6 +41,7 @@ type defaultIntegration =
|
|
|
39
41
|
| "markdoc"
|
|
40
42
|
| "mdx"
|
|
41
43
|
| "favicons"
|
|
44
|
+
| "starlightIconsIntegration"
|
|
42
45
|
| "cloudflare-pages-headers";
|
|
43
46
|
|
|
44
47
|
function nonNullable<T>(value: T): value is NonNullable<T> {
|
|
@@ -194,6 +197,40 @@ const getIntegrations = (options: IntegrationOptions) => {
|
|
|
194
197
|
integrationConfigs,
|
|
195
198
|
unwantedIntegrations = [],
|
|
196
199
|
} = options;
|
|
200
|
+
// Registry maps config keys (used in unwantedIntegrations and integrationConfigs)
|
|
201
|
+
// to actual runtime .name values and factory functions for overrides.
|
|
202
|
+
const integrationRegistry: Record<
|
|
203
|
+
string,
|
|
204
|
+
{
|
|
205
|
+
runtimeNames: string[];
|
|
206
|
+
// biome-ignore lint/suspicious/noExplicitAny: integration configs are opaque
|
|
207
|
+
create: (cfg: any) => AstroIntegration;
|
|
208
|
+
}
|
|
209
|
+
> = {
|
|
210
|
+
astroD2: { runtimeNames: ["astro-d2-integration"], create: astroD2 },
|
|
211
|
+
markdoc: { runtimeNames: ["@astrojs/markdoc"], create: markdoc },
|
|
212
|
+
mdx: { runtimeNames: ["@astrojs/mdx"], create: mdx },
|
|
213
|
+
favicons: { runtimeNames: ["astro-favicons"], create: favicons },
|
|
214
|
+
sitemap: { runtimeNames: ["@astrojs/sitemap"], create: sitemap },
|
|
215
|
+
starlightIconsIntegration: {
|
|
216
|
+
runtimeNames: ["starlight-plugin-icons"],
|
|
217
|
+
create: starlightIconsIntegration,
|
|
218
|
+
},
|
|
219
|
+
"cloudflare-pages-headers": {
|
|
220
|
+
runtimeNames: ["astroCloudflarePagesHeaders"],
|
|
221
|
+
create: cloudflarePagesHeaders,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Resolve unwantedIntegrations config keys to runtime .name values
|
|
226
|
+
const unwantedRuntimeNames = new Set<string>();
|
|
227
|
+
for (const key of unwantedIntegrations) {
|
|
228
|
+
const entry = integrationRegistry[key];
|
|
229
|
+
if (entry) {
|
|
230
|
+
for (const n of entry.runtimeNames) unwantedRuntimeNames.add(n);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
197
234
|
const defaultIntegrations = [
|
|
198
235
|
astroD2({ skipGeneration: true }),
|
|
199
236
|
markdoc(),
|
|
@@ -220,33 +257,33 @@ const getIntegrations = (options: IntegrationOptions) => {
|
|
|
220
257
|
}),
|
|
221
258
|
];
|
|
222
259
|
|
|
223
|
-
const filtered = defaultIntegrations.filter(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
260
|
+
const filtered = defaultIntegrations.filter(
|
|
261
|
+
(integration) =>
|
|
262
|
+
!integration?.name || !unwantedRuntimeNames.has(integration.name),
|
|
263
|
+
);
|
|
227
264
|
|
|
228
265
|
if (!integrationConfigs) return filtered;
|
|
229
266
|
|
|
267
|
+
// Collect runtime names that will be replaced by overrides
|
|
268
|
+
const replacedIntegrationNames = new Set<string>();
|
|
269
|
+
for (const key of Object.keys(integrationConfigs)) {
|
|
270
|
+
const entry = integrationRegistry[key];
|
|
271
|
+
if (entry) {
|
|
272
|
+
for (const n of entry.runtimeNames) replacedIntegrationNames.add(n);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Remove defaults that will be replaced by overrides
|
|
277
|
+
const base = filtered.filter(
|
|
278
|
+
(integration) =>
|
|
279
|
+
!integration?.name || !replacedIntegrationNames.has(integration.name),
|
|
280
|
+
);
|
|
281
|
+
|
|
230
282
|
const overrides = Object.entries(integrationConfigs)
|
|
231
|
-
.map(([
|
|
232
|
-
switch (integrationName) {
|
|
233
|
-
case "astroD2":
|
|
234
|
-
return astroD2(config);
|
|
235
|
-
case "markdoc":
|
|
236
|
-
return markdoc(config);
|
|
237
|
-
case "mdx":
|
|
238
|
-
return mdx(config);
|
|
239
|
-
case "favicons":
|
|
240
|
-
return favicons(config);
|
|
241
|
-
case "sitemap":
|
|
242
|
-
return sitemap(config);
|
|
243
|
-
default:
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
})
|
|
283
|
+
.map(([key, config]) => integrationRegistry[key]?.create(config) ?? null)
|
|
247
284
|
.filter(nonNullable);
|
|
248
285
|
|
|
249
|
-
return [...
|
|
286
|
+
return [...base, ...overrides];
|
|
250
287
|
};
|
|
251
288
|
|
|
252
289
|
export type PluginOptions = Pick<
|
|
@@ -293,30 +330,59 @@ const get_plugins = (options: PluginOptions) => {
|
|
|
293
330
|
|
|
294
331
|
if (!pluginConfigs) return filtered;
|
|
295
332
|
|
|
333
|
+
// Map config keys to factory functions and the actual plugin .name values.
|
|
334
|
+
// Note: starlightIconsIntegration is an Astro *integration* (in getIntegrations),
|
|
335
|
+
// not a Starlight plugin — it was incorrectly in the old plugin override switch.
|
|
336
|
+
const pluginRegistry: Record<
|
|
337
|
+
string,
|
|
338
|
+
{
|
|
339
|
+
pluginNames: string[];
|
|
340
|
+
// biome-ignore lint/suspicious/noExplicitAny: plugin configs are opaque
|
|
341
|
+
create: (cfg: any) => StarlightPlugin;
|
|
342
|
+
}
|
|
343
|
+
> = {
|
|
344
|
+
starlightAnnouncement: {
|
|
345
|
+
pluginNames: ["starlight-announcement"],
|
|
346
|
+
create: starlightAnnouncement,
|
|
347
|
+
},
|
|
348
|
+
starlightIconsPlugin: {
|
|
349
|
+
pluginNames: ["starlight-plugin-icons"],
|
|
350
|
+
create: starlightIconsPlugin,
|
|
351
|
+
},
|
|
352
|
+
starlightLinksValidator: {
|
|
353
|
+
pluginNames: ["starlight-links-validator-plugin"],
|
|
354
|
+
create: starlightLinksValidator,
|
|
355
|
+
},
|
|
356
|
+
starlightPageActions: {
|
|
357
|
+
pluginNames: ["starlight-page-actions"],
|
|
358
|
+
create: starlightPageActions,
|
|
359
|
+
},
|
|
360
|
+
starlightTags: { pluginNames: ["starlight-tags"], create: starlightTags },
|
|
361
|
+
starlightScrollToTop: {
|
|
362
|
+
pluginNames: ["starlight-scroll-to-top-plugin"],
|
|
363
|
+
create: starlightScrollToTop,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Collect the actual plugin .name values that will be replaced
|
|
368
|
+
const replacedPluginNames = new Set<string>();
|
|
369
|
+
for (const key of Object.keys(pluginConfigs)) {
|
|
370
|
+
const entry = pluginRegistry[key];
|
|
371
|
+
if (entry) {
|
|
372
|
+
for (const n of entry.pluginNames) replacedPluginNames.add(n);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Remove defaults that will be replaced by overrides
|
|
377
|
+
const base = filtered.filter(
|
|
378
|
+
(plugin) => !replacedPluginNames.has(plugin.name),
|
|
379
|
+
);
|
|
380
|
+
|
|
296
381
|
const overrides = Object.entries(pluginConfigs)
|
|
297
|
-
.map(([
|
|
298
|
-
switch (pluginName) {
|
|
299
|
-
case "starlightAnnouncement":
|
|
300
|
-
return starlightAnnouncement(config);
|
|
301
|
-
case "starlightIconsIntegration":
|
|
302
|
-
return starlightIconsIntegration(config);
|
|
303
|
-
case "starlightIconsPlugin":
|
|
304
|
-
return starlightIconsPlugin(config);
|
|
305
|
-
case "starlightLinksValidator":
|
|
306
|
-
return starlightLinksValidator(config);
|
|
307
|
-
case "starlightPageActions":
|
|
308
|
-
return starlightPageActions(config);
|
|
309
|
-
case "starlightTags":
|
|
310
|
-
return starlightTags(config);
|
|
311
|
-
case "starlightScrollToTop":
|
|
312
|
-
return starlightScrollToTop(config);
|
|
313
|
-
default:
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
})
|
|
382
|
+
.map(([key, config]) => pluginRegistry[key]?.create(config) ?? null)
|
|
317
383
|
.filter(nonNullable);
|
|
318
384
|
|
|
319
|
-
return [...
|
|
385
|
+
return [...base, ...overrides];
|
|
320
386
|
};
|
|
321
387
|
|
|
322
388
|
const defaultHeadersConfig: OutgoingHttpHeaders = {
|
|
@@ -354,11 +420,7 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
354
420
|
site: "https://docs.knitli.com",
|
|
355
421
|
base: `/${appName.toLowerCase()}/`,
|
|
356
422
|
adapter: cloudflare({
|
|
357
|
-
|
|
358
|
-
experimental: {
|
|
359
|
-
headersAndRedirectsDevModeSupport: true,
|
|
360
|
-
},
|
|
361
|
-
configPath: `${rootDir}/wrangler.jsonc`,
|
|
423
|
+
configPath: `${rootDir}/wrangler.json`,
|
|
362
424
|
imageService: "compile",
|
|
363
425
|
}),
|
|
364
426
|
// Image optimization
|
|
@@ -383,10 +445,10 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
383
445
|
Object.entries(shikiConfig).filter(([key]) => key !== "bundledLangs"),
|
|
384
446
|
),
|
|
385
447
|
rehypePlugins: [
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
rel: ["nofollow"],
|
|
389
|
-
|
|
448
|
+
[
|
|
449
|
+
rehypeExternalLinks,
|
|
450
|
+
{ content: { type: "text", value: " ↗" }, rel: ["nofollow"] },
|
|
451
|
+
],
|
|
390
452
|
],
|
|
391
453
|
},
|
|
392
454
|
server: {
|
|
@@ -413,7 +475,7 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
413
475
|
"import.meta.env.PUBLIC_DOCS_PRODUCT": JSON.stringify(appName),
|
|
414
476
|
},
|
|
415
477
|
build: {
|
|
416
|
-
cssMinify: "
|
|
478
|
+
cssMinify: "esbuild",
|
|
417
479
|
minify: "esbuild",
|
|
418
480
|
cssCodeSplit: true,
|
|
419
481
|
rollupOptions: {
|
|
@@ -445,9 +507,7 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
445
507
|
},
|
|
446
508
|
ssr: false,
|
|
447
509
|
},
|
|
448
|
-
css: {
|
|
449
|
-
lightningcss: {},
|
|
450
|
-
},
|
|
510
|
+
css: {},
|
|
451
511
|
},
|
|
452
512
|
prefetch: {
|
|
453
513
|
defaultStrategy: "viewport",
|
|
@@ -479,12 +539,8 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
479
539
|
// Static site generation for Cloudflare
|
|
480
540
|
output: "static",
|
|
481
541
|
integrations: [
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
sitemapFilter,
|
|
485
|
-
integrationConfigs,
|
|
486
|
-
unwantedIntegrations,
|
|
487
|
-
}),
|
|
542
|
+
// Starlight must come before mdx() because it injects astro-expressive-code
|
|
543
|
+
// which requires being ordered before mdx in the integration array.
|
|
488
544
|
starlight({
|
|
489
545
|
title: `${appName} Docs`,
|
|
490
546
|
pagefind: true,
|
|
@@ -558,6 +614,12 @@ export default function createConfig(options: DocsTemplateOptions) {
|
|
|
558
614
|
},
|
|
559
615
|
],
|
|
560
616
|
}),
|
|
617
|
+
...getIntegrations({
|
|
618
|
+
appName,
|
|
619
|
+
sitemapFilter,
|
|
620
|
+
integrationConfigs,
|
|
621
|
+
unwantedIntegrations,
|
|
622
|
+
}),
|
|
561
623
|
],
|
|
562
624
|
});
|
|
563
625
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -175,7 +175,7 @@ describe("initDocsTemplate", () => {
|
|
|
175
175
|
|
|
176
176
|
it("substitutes {{appName}} in text files", () => {
|
|
177
177
|
initDocsTemplate(tmpDir, BASE_OPTIONS);
|
|
178
|
-
const wrangler = readFileSync(join(tmpDir, "wrangler.
|
|
178
|
+
const wrangler = readFileSync(join(tmpDir, "wrangler.json"), "utf-8");
|
|
179
179
|
expect(wrangler).toContain(BASE_OPTIONS.workerName);
|
|
180
180
|
expect(wrangler).not.toContain("{{workerName}}");
|
|
181
181
|
});
|
|
@@ -195,7 +195,7 @@ describe("initDocsTemplate", () => {
|
|
|
195
195
|
it("creates all expected piece files", () => {
|
|
196
196
|
initDocsTemplate(tmpDir, BASE_OPTIONS);
|
|
197
197
|
expect(existsSync(join(tmpDir, "astro.config.ts"))).toBe(true);
|
|
198
|
-
expect(existsSync(join(tmpDir, "wrangler.
|
|
198
|
+
expect(existsSync(join(tmpDir, "wrangler.json"))).toBe(true);
|
|
199
199
|
expect(existsSync(join(tmpDir, "tsconfig.json"))).toBe(true);
|
|
200
200
|
expect(existsSync(join(tmpDir, "package.json"))).toBe(true);
|
|
201
201
|
expect(existsSync(join(tmpDir, "mise.toml"))).toBe(true);
|
|
@@ -258,7 +258,7 @@ describe("addPieces", () => {
|
|
|
258
258
|
|
|
259
259
|
it("applies placeholder substitutions to added files", () => {
|
|
260
260
|
addPieces(tmpDir, { ...BASE_OPTIONS, pieces: ["wrangler"] });
|
|
261
|
-
const content = readFileSync(join(tmpDir, "wrangler.
|
|
261
|
+
const content = readFileSync(join(tmpDir, "wrangler.json"), "utf-8");
|
|
262
262
|
expect(content).not.toContain("{{workerName}}");
|
|
263
263
|
expect(content).toContain(BASE_OPTIONS.workerName);
|
|
264
264
|
});
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Knitli Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import { execFileSync, type SpawnSyncReturns } from "node:child_process";
|
|
6
|
+
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
const FIXTURE_DIR = join(import.meta.dirname, "../fixture");
|
|
11
|
+
const DIST_DIR = join(FIXTURE_DIR, "dist");
|
|
12
|
+
|
|
13
|
+
const CONFIG_VARIANTS = [
|
|
14
|
+
"astro.config.default.ts",
|
|
15
|
+
"astro.config.codeweaver.ts",
|
|
16
|
+
"astro.config.stripped.ts",
|
|
17
|
+
"astro.config.overrides.ts",
|
|
18
|
+
"astro.config.custom.ts",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Known non-fatal error from the Cloudflare adapter's post-build step.
|
|
23
|
+
* The adapter tries to read a wrangler.json from the prerender output
|
|
24
|
+
* directory, which doesn't exist in local builds. The Vite build phases
|
|
25
|
+
* all complete successfully — this error occurs after bundling is done.
|
|
26
|
+
*/
|
|
27
|
+
const KNOWN_WRANGLER_PRERENDER_ERROR = "Could not read file:" as const;
|
|
28
|
+
const KNOWN_WRANGLER_PRERENDER_PATH =
|
|
29
|
+
"dist/server/.prerender/wrangler.json" as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Run `astro build` for a given config file. Returns stdout+stderr.
|
|
33
|
+
*
|
|
34
|
+
* Tolerates the known wrangler prerender error (see above) but re-throws
|
|
35
|
+
* any other build failure.
|
|
36
|
+
*/
|
|
37
|
+
function astroBuild(configFile: string): { stdout: string; stderr: string } {
|
|
38
|
+
try {
|
|
39
|
+
const stdout = execFileSync(
|
|
40
|
+
"bunx",
|
|
41
|
+
["astro", "build", "--config", configFile],
|
|
42
|
+
{
|
|
43
|
+
cwd: FIXTURE_DIR,
|
|
44
|
+
timeout: 120_000,
|
|
45
|
+
stdio: "pipe",
|
|
46
|
+
encoding: "utf-8",
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
return { stdout, stderr: "" };
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const e = err as SpawnSyncReturns<string>;
|
|
52
|
+
const stderr = e.stderr ?? "";
|
|
53
|
+
const stdout = e.stdout ?? "";
|
|
54
|
+
const isKnownWranglerError =
|
|
55
|
+
stderr.includes(KNOWN_WRANGLER_PRERENDER_ERROR) &&
|
|
56
|
+
stderr.includes(KNOWN_WRANGLER_PRERENDER_PATH);
|
|
57
|
+
// Also check that Vite itself didn't report a build failure alongside
|
|
58
|
+
// the wrangler error — "Build failed" in stderr means a real problem.
|
|
59
|
+
const hasViteBuildFailure = stderr.includes("Build failed");
|
|
60
|
+
if (!isKnownWranglerError || hasViteBuildFailure) {
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
console.warn(
|
|
64
|
+
`[${configFile}] tolerated known wrangler prerender error (non-fatal)`,
|
|
65
|
+
);
|
|
66
|
+
return { stdout, stderr };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe("createConfig integration", { timeout: 180_000 }, () => {
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
rmSync(DIST_DIR, { recursive: true, force: true });
|
|
73
|
+
// Clean up .astro cache between variants
|
|
74
|
+
rmSync(join(FIXTURE_DIR, ".astro"), { recursive: true, force: true });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
for (const config of CONFIG_VARIANTS) {
|
|
78
|
+
const variant = config.replace("astro.config.", "").replace(".ts", "");
|
|
79
|
+
|
|
80
|
+
it(`builds successfully with ${variant} config`, () => {
|
|
81
|
+
astroBuild(config);
|
|
82
|
+
|
|
83
|
+
expect(existsSync(DIST_DIR)).toBe(true);
|
|
84
|
+
expect(existsSync(join(DIST_DIR, "_astro"))).toBe(true);
|
|
85
|
+
|
|
86
|
+
// The Cloudflare adapter places all output — including server entries
|
|
87
|
+
// and public/ files — inside dist/_astro/ (not dist/ root). This is
|
|
88
|
+
// specific to the @astrojs/cloudflare adapter's asset handling.
|
|
89
|
+
const astroDir = join(DIST_DIR, "_astro");
|
|
90
|
+
const files = readdirSync(astroDir);
|
|
91
|
+
expect(files).toContain("entry.mjs");
|
|
92
|
+
expect(files).toContain("robots.txt");
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
File without changes
|