@nuasite/cli 0.50.1 → 0.51.0

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/dist/index.js CHANGED
@@ -47,594 +47,6 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
- // src/utils.ts
51
- import { existsSync } from "fs";
52
- import { join } from "path";
53
- function findAstroConfig(cwd = process.cwd()) {
54
- for (const name of CONFIG_NAMES) {
55
- const p = join(cwd, name);
56
- if (existsSync(p))
57
- return p;
58
- }
59
- return null;
60
- }
61
- function findMatchingClose(text, start) {
62
- const open = text[start];
63
- const close = open === "{" ? "}" : open === "[" ? "]" : ")";
64
- let depth = 0;
65
- let inString = false;
66
- for (let i = start;i < text.length; i++) {
67
- const ch = text[i];
68
- if (inString) {
69
- if (ch === "\\") {
70
- i++;
71
- continue;
72
- }
73
- if (ch === inString)
74
- inString = false;
75
- continue;
76
- }
77
- if (ch === "'" || ch === '"' || ch === "`") {
78
- inString = ch;
79
- continue;
80
- }
81
- if (ch === open)
82
- depth++;
83
- if (ch === close) {
84
- depth--;
85
- if (depth === 0)
86
- return i;
87
- }
88
- }
89
- return -1;
90
- }
91
- function extractConfigBody(content) {
92
- const match = content.match(/defineConfig\s*\(\s*\{/);
93
- if (!match || match.index === undefined)
94
- return "";
95
- const openBrace = content.indexOf("{", match.index + "defineConfig".length);
96
- const closeBrace = findMatchingClose(content, openBrace);
97
- if (closeBrace === -1)
98
- return "";
99
- return content.slice(openBrace + 1, closeBrace);
100
- }
101
- function removeProperty(body, propName) {
102
- const regex = new RegExp(`(\\n[ \\t]*)${propName}\\s*:\\s*`);
103
- const match = regex.exec(body);
104
- if (!match || match.index === undefined)
105
- return body;
106
- const propLineStart = match.index + 1;
107
- const afterMatch = match.index + match[0].length;
108
- let i = afterMatch;
109
- while (i < body.length && (body[i] === " " || body[i] === "\t"))
110
- i++;
111
- let valueEnd;
112
- if (body[i] === "{" || body[i] === "[") {
113
- valueEnd = findMatchingClose(body, i);
114
- if (valueEnd === -1)
115
- return body;
116
- valueEnd++;
117
- } else {
118
- while (i < body.length && body[i] !== "," && body[i] !== `
119
- `)
120
- i++;
121
- valueEnd = i;
122
- }
123
- let end = valueEnd;
124
- while (end < body.length && (body[end] === " " || body[end] === "\t"))
125
- end++;
126
- if (end < body.length && body[end] === ",")
127
- end++;
128
- while (end < body.length && (body[end] === " " || body[end] === "\t"))
129
- end++;
130
- if (end < body.length && body[end] === `
131
- `)
132
- end++;
133
- return body.slice(0, propLineStart) + body.slice(end);
134
- }
135
- function assembleConfig(imports, body) {
136
- const bodyLines = body.split(`
137
- `).filter((line) => line.trim() !== "");
138
- let result = imports.join(`
139
- `) + `
140
-
141
- `;
142
- result += `export default defineConfig({
143
- `;
144
- if (bodyLines.length > 0) {
145
- result += bodyLines.join(`
146
- `) + `
147
- `;
148
- }
149
- result += `})
150
- `;
151
- return result;
152
- }
153
- var CONFIG_NAMES;
154
- var init_utils = __esm(() => {
155
- CONFIG_NAMES = [
156
- "astro.config.ts",
157
- "astro.config.mts",
158
- "astro.config.mjs",
159
- "astro.config.js"
160
- ];
161
- });
162
-
163
- // src/init.ts
164
- var exports_init = {};
165
- __export(exports_init, {
166
- transformPackageJson: () => transformPackageJson,
167
- transformConfig: () => transformConfig,
168
- removeCallFromArray: () => removeCallFromArray,
169
- init: () => init,
170
- detectNuaManagedImports: () => detectNuaManagedImports,
171
- cleanEmptyStructures: () => cleanEmptyStructures
172
- });
173
- import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
174
- import { basename, join as join2 } from "path";
175
- function detectNuaManagedImports(content) {
176
- const managed = new Map;
177
- for (const pkg of NUA_MANAGED_PACKAGES) {
178
- const regex = new RegExp(`import\\s+(\\w+)\\s+from\\s+['"]${pkg.replace("/", "\\/")}['"]`);
179
- const match = regex.exec(content);
180
- if (match) {
181
- managed.set(pkg, match[1]);
182
- }
183
- }
184
- return managed;
185
- }
186
- function removeCallFromArray(body, arrayProp, callName) {
187
- const propRegex = new RegExp(`\\b${arrayProp}\\s*:\\s*\\[`);
188
- const propMatch = propRegex.exec(body);
189
- if (!propMatch || propMatch.index === undefined)
190
- return body;
191
- const arrayStart = body.indexOf("[", propMatch.index);
192
- const arrayEnd = findMatchingClose(body, arrayStart);
193
- if (arrayEnd === -1)
194
- return body;
195
- const arrayContent = body.slice(arrayStart + 1, arrayEnd);
196
- const callRegex = new RegExp(`\\b${callName}\\s*\\(`);
197
- const callMatch = callRegex.exec(arrayContent);
198
- if (!callMatch || callMatch.index === undefined)
199
- return body;
200
- const parenStart = arrayContent.indexOf("(", callMatch.index);
201
- const parenEnd = findMatchingClose(arrayContent, parenStart);
202
- if (parenEnd === -1)
203
- return body;
204
- let removeStart = callMatch.index;
205
- let removeEnd = parenEnd + 1;
206
- let after = removeEnd;
207
- while (after < arrayContent.length && (arrayContent[after] === " " || arrayContent[after] === "\t" || arrayContent[after] === `
208
- `)) {
209
- after++;
210
- }
211
- if (after < arrayContent.length && arrayContent[after] === ",") {
212
- removeEnd = after + 1;
213
- while (removeEnd < arrayContent.length && (arrayContent[removeEnd] === " " || arrayContent[removeEnd] === "\t" || arrayContent[removeEnd] === `
214
- `))
215
- removeEnd++;
216
- }
217
- if (removeEnd === parenEnd + 1) {
218
- let before = removeStart - 1;
219
- while (before >= 0 && (arrayContent[before] === " " || arrayContent[before] === "\t" || arrayContent[before] === `
220
- `))
221
- before--;
222
- if (before >= 0 && arrayContent[before] === ",") {
223
- removeStart = before;
224
- }
225
- }
226
- const newArrayContent = arrayContent.slice(0, removeStart) + arrayContent.slice(removeEnd);
227
- return body.slice(0, arrayStart + 1) + newArrayContent + body.slice(arrayEnd);
228
- }
229
- function cleanEmptyStructures(body) {
230
- body = body.replace(/\n[ \t]*sourcemap\s*:\s*true\s*,?[ \t]*/g, `
231
- `);
232
- body = body.replace(/\n[ \t]*build\s*:\s*\{[\s,]*\}\s*,?[ \t]*/g, `
233
- `);
234
- body = body.replace(/\n[ \t]*plugins\s*:\s*\[[\s,]*\]\s*,?[ \t]*/g, `
235
- `);
236
- body = body.replace(/\n[ \t]*integrations\s*:\s*\[[\s,]*\]\s*,?[ \t]*/g, `
237
- `);
238
- body = body.replace(/\n[ \t]*vite\s*:\s*\{[\s,]*\}\s*,?[ \t]*/g, `
239
- `);
240
- return body;
241
- }
242
- function transformConfig(content, managedImports) {
243
- const lines = content.split(`
244
- `);
245
- const newImports = [];
246
- for (const line of lines) {
247
- if (!/^\s*import\s/.test(line))
248
- continue;
249
- if (line.includes("astro/config") && line.includes("defineConfig")) {
250
- newImports.push(`import { defineConfig } from '@nuasite/nua/config'`);
251
- continue;
252
- }
253
- const isManagedImport = [...managedImports.keys()].some((pkg) => line.includes(pkg));
254
- if (isManagedImport)
255
- continue;
256
- newImports.push(line);
257
- }
258
- let body = extractConfigBody(content);
259
- for (const [pkg, localName] of managedImports) {
260
- if (pkg === "@tailwindcss/vite") {
261
- body = removeCallFromArray(body, "plugins", localName);
262
- } else {
263
- body = removeCallFromArray(body, "integrations", localName);
264
- }
265
- }
266
- body = cleanEmptyStructures(body);
267
- return assembleConfig(newImports, body);
268
- }
269
- function resolveNuaVersion() {
270
- const cliPkgPath = new URL("../package.json", import.meta.url);
271
- const cliPkg = JSON.parse(readFileSync(cliPkgPath, "utf-8"));
272
- return `^${cliPkg.version}`;
273
- }
274
- function transformPackageJson(pkg, nuaVersion) {
275
- const result = structuredClone(pkg);
276
- if (result.scripts) {
277
- for (const [key, value] of Object.entries(result.scripts)) {
278
- if (typeof value === "string") {
279
- result.scripts[key] = value.replace(/\bastro build\b/g, "nua build").replace(/\bastro dev\b/g, "nua dev").replace(/\bastro preview\b/g, "nua preview");
280
- }
281
- }
282
- }
283
- for (const field of ["dependencies", "devDependencies"]) {
284
- if (!result[field])
285
- continue;
286
- for (const name of NUA_PROVIDED_PACKAGES) {
287
- delete result[field][name];
288
- }
289
- if (Object.keys(result[field]).length === 0) {
290
- delete result[field];
291
- }
292
- }
293
- if (!result.dependencies)
294
- result.dependencies = {};
295
- if (!result.dependencies["@nuasite/nua"]) {
296
- result.dependencies["@nuasite/nua"] = nuaVersion;
297
- }
298
- result.dependencies = Object.fromEntries(Object.entries(result.dependencies).sort(([a], [b]) => a.localeCompare(b)));
299
- return result;
300
- }
301
- async function init({ cwd = process.cwd(), dryRun = false, yes = false } = {}) {
302
- const configPath = findAstroConfig(cwd);
303
- if (!configPath) {
304
- console.error("No Astro config file found.");
305
- process.exit(1);
306
- }
307
- const configContent = readFileSync(configPath, "utf-8");
308
- if (configContent.includes("@nuasite/nua")) {
309
- console.log("This project already uses @nuasite/nua. Nothing to do.");
310
- return;
311
- }
312
- if (!configContent.includes("defineConfig")) {
313
- console.error("Could not find defineConfig in Astro config.");
314
- process.exit(1);
315
- }
316
- const pkgPath = join2(cwd, "package.json");
317
- if (!existsSync2(pkgPath)) {
318
- console.error("No package.json found.");
319
- process.exit(1);
320
- }
321
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
322
- const managedImports = detectNuaManagedImports(configContent);
323
- const nuaVersion = resolveNuaVersion();
324
- const configName = basename(configPath);
325
- console.log("");
326
- console.log("nua init \u2014 adopt the Nua toolchain");
327
- console.log("");
328
- console.log(` ${configName}`);
329
- console.log(" - Replace astro/config with @nuasite/nua/config");
330
- if (managedImports.size > 0) {
331
- console.log(` - Remove Nua-managed imports: ${[...managedImports.keys()].join(", ")}`);
332
- console.log(" - Remove managed integration/plugin calls");
333
- }
334
- console.log(" - Clean up empty config structures");
335
- console.log("");
336
- console.log(" package.json");
337
- const removable = NUA_PROVIDED_PACKAGES.filter((name) => pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
338
- if (removable.length > 0) {
339
- console.log(` - Remove Nua-provided deps: ${removable.join(", ")}`);
340
- }
341
- console.log(` - Add @nuasite/nua ${nuaVersion}`);
342
- console.log(" - Update scripts: astro \u2192 nua");
343
- if (dryRun) {
344
- console.log("");
345
- console.log(" (--dry-run: no changes made)");
346
- console.log("");
347
- return;
348
- }
349
- if (!yes) {
350
- console.log("");
351
- const answer = prompt("Proceed? [y/N] ");
352
- if (answer?.toLowerCase() !== "y") {
353
- console.log("Cancelled.");
354
- return;
355
- }
356
- }
357
- const newConfig = transformConfig(configContent, managedImports);
358
- writeFileSync(configPath, newConfig);
359
- console.log(` Updated ${configName}`);
360
- const newPkg = transformPackageJson(pkg, nuaVersion);
361
- writeFileSync(pkgPath, JSON.stringify(newPkg, null, "\t") + `
362
- `);
363
- console.log(" Updated package.json");
364
- console.log("");
365
- console.log("Next steps:");
366
- console.log(" 1. bun install");
367
- console.log(" 2. Review the updated config");
368
- console.log(" 3. nua dev");
369
- console.log("");
370
- }
371
- var NUA_PROVIDED_PACKAGES, NUA_MANAGED_PACKAGES;
372
- var init_init = __esm(() => {
373
- init_utils();
374
- NUA_PROVIDED_PACKAGES = [
375
- "@astrojs/mdx",
376
- "@astrojs/sitemap",
377
- "@tailwindcss/vite",
378
- "tailwindcss"
379
- ];
380
- NUA_MANAGED_PACKAGES = [
381
- "@astrojs/mdx",
382
- "@astrojs/sitemap",
383
- "@tailwindcss/vite"
384
- ];
385
- });
386
-
387
- // src/clean.ts
388
- var exports_clean = {};
389
- __export(exports_clean, {
390
- transformPackageJson: () => transformPackageJson2,
391
- transformConfig: () => transformConfig2,
392
- detectDisabledFeatures: () => detectDisabledFeatures,
393
- clean: () => clean
394
- });
395
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
396
- import { basename as basename2, join as join3 } from "path";
397
- function detectDisabledFeatures(content) {
398
- const disabled = new Set;
399
- for (const key of FEATURE_KEYS) {
400
- if (new RegExp(`\\b${key}\\s*:\\s*false\\b`).test(content)) {
401
- disabled.add(key);
402
- }
403
- }
404
- return disabled;
405
- }
406
- function prependToArrayProperty(lines, property, items) {
407
- const pattern = new RegExp(`\\b${property}\\s*:\\s*\\[`);
408
- for (let i = 0;i < lines.length; i++) {
409
- if (pattern.test(lines[i])) {
410
- lines[i] = lines[i].replace(new RegExp(`(\\b${property}\\s*:\\s*\\[)`), `$1${items}, `);
411
- return true;
412
- }
413
- }
414
- return false;
415
- }
416
- function transformConfig2(content, disabled) {
417
- const userImports = content.split(`
418
- `).filter((line) => /^\s*import\s/.test(line)).filter((line) => !line.includes("@nuasite/")).filter((line) => !line.includes("defineConfig"));
419
- let body = extractConfigBody(content);
420
- body = removeProperty(body, "nua");
421
- if (content.includes("@nuasite/nua/integration")) {
422
- body = body.replace(/\bnua\s*\([^)]*\)\s*,?\s*/g, "");
423
- body = body.replace(/\bintegrations\s*:\s*\[\s*,?\s*\]\s*,?/g, "");
424
- }
425
- const imports = [`import { defineConfig } from 'astro/config'`];
426
- if (!disabled.has("tailwindcss"))
427
- imports.push(`import tailwindcss from '@tailwindcss/vite'`);
428
- if (!disabled.has("mdx"))
429
- imports.push(`import mdx from '@astrojs/mdx'`);
430
- if (!disabled.has("sitemap"))
431
- imports.push(`import sitemap from '@astrojs/sitemap'`);
432
- imports.push(...userImports);
433
- const integrationCalls = [];
434
- if (!disabled.has("mdx"))
435
- integrationCalls.push("mdx()");
436
- if (!disabled.has("sitemap"))
437
- integrationCalls.push("sitemap()");
438
- const bodyLines = body.split(`
439
- `).filter((line) => line.trim() !== "");
440
- const hasIntegrations = bodyLines.some((line) => /^\s*integrations\s*:/.test(line));
441
- const hasVite = bodyLines.some((line) => /^\s*vite\s*:/.test(line));
442
- if (hasIntegrations && integrationCalls.length > 0) {
443
- prependToArrayProperty(bodyLines, "integrations", integrationCalls.join(", "));
444
- }
445
- if (!disabled.has("tailwindcss") && hasVite) {
446
- prependToArrayProperty(bodyLines, "plugins", "tailwindcss()");
447
- }
448
- const newPropLines = [];
449
- if (!disabled.has("tailwindcss") && !hasVite) {
450
- newPropLines.push("\tvite: {", "\t\tbuild: {", "\t\t\tsourcemap: true,", "\t\t},", "\t\tplugins: [tailwindcss()],", "\t},");
451
- }
452
- if (!hasIntegrations && integrationCalls.length > 0) {
453
- newPropLines.push(` integrations: [${integrationCalls.join(", ")}],`);
454
- }
455
- const allLines = [...bodyLines, ...newPropLines];
456
- return assembleConfig(imports, allLines.join(`
457
- `));
458
- }
459
- function transformPackageJson2(pkg, disabled, usedRuntimePackages = []) {
460
- const result = structuredClone(pkg);
461
- const nuaVersion = result.dependencies?.["@nuasite/nua"] ?? result.devDependencies?.["@nuasite/nua"];
462
- if (result.scripts) {
463
- for (const [key, value] of Object.entries(result.scripts)) {
464
- if (typeof value === "string") {
465
- result.scripts[key] = value.replace(/\bnua build\b/g, "astro build").replace(/\bnua dev\b/g, "astro dev").replace(/\bnua preview\b/g, "astro preview");
466
- }
467
- }
468
- }
469
- for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
470
- if (!result[field])
471
- continue;
472
- for (const name of NUASITE_TOOLING) {
473
- delete result[field][name];
474
- }
475
- if (Object.keys(result[field]).length === 0) {
476
- delete result[field];
477
- }
478
- }
479
- if (!result.dependencies)
480
- result.dependencies = {};
481
- for (const [name, version] of Object.entries(PACKAGES_TO_ADD)) {
482
- if (result.dependencies[name])
483
- continue;
484
- if (name === "@astrojs/mdx" && disabled.has("mdx"))
485
- continue;
486
- if (name === "@astrojs/sitemap" && disabled.has("sitemap"))
487
- continue;
488
- if (name === "@tailwindcss/vite" && disabled.has("tailwindcss"))
489
- continue;
490
- if (name === "tailwindcss" && disabled.has("tailwindcss"))
491
- continue;
492
- result.dependencies[name] = version;
493
- }
494
- for (const name of usedRuntimePackages) {
495
- if (!result.dependencies[name]) {
496
- result.dependencies[name] = nuaVersion ?? "^0.16.0";
497
- }
498
- }
499
- result.dependencies = Object.fromEntries(Object.entries(result.dependencies).sort(([a], [b]) => a.localeCompare(b)));
500
- return result;
501
- }
502
- function scanForNuasiteUsage(cwd) {
503
- const srcDir = join3(cwd, "src");
504
- if (!existsSync3(srcDir))
505
- return [];
506
- const results = [];
507
- try {
508
- const files = readdirSync(srcDir, { recursive: true });
509
- for (const entry of files) {
510
- const fileName = String(entry);
511
- if (!/\.(astro|ts|tsx|js|jsx)$/.test(fileName))
512
- continue;
513
- try {
514
- const content = readFileSync2(join3(srcDir, fileName), "utf-8");
515
- const matches = content.match(/@nuasite\/[\w-]+/g);
516
- if (matches) {
517
- results.push({
518
- file: join3("src", fileName),
519
- packages: [...new Set(matches)]
520
- });
521
- }
522
- } catch {}
523
- }
524
- } catch {}
525
- return results;
526
- }
527
- async function clean({ cwd = process.cwd(), dryRun = false, yes = false } = {}) {
528
- const configPath = findAstroConfig(cwd);
529
- if (!configPath) {
530
- console.error("No Astro config file found.");
531
- process.exit(1);
532
- }
533
- const configContent = readFileSync2(configPath, "utf-8");
534
- if (!configContent.includes("@nuasite/nua")) {
535
- console.log("This project does not use @nuasite/nua. Nothing to clean.");
536
- return;
537
- }
538
- const pkgPath = join3(cwd, "package.json");
539
- if (!existsSync3(pkgPath)) {
540
- console.error("No package.json found.");
541
- process.exit(1);
542
- }
543
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
544
- const disabled = detectDisabledFeatures(configContent);
545
- const nuasiteUsage = scanForNuasiteUsage(cwd);
546
- const configName = basename2(configPath);
547
- const usedRuntimePackages = new Set;
548
- const toolingUsage = [];
549
- for (const entry of nuasiteUsage) {
550
- const runtime = entry.packages.filter((p) => !NUASITE_TOOLING.includes(p));
551
- const tooling = entry.packages.filter((p) => NUASITE_TOOLING.includes(p));
552
- for (const p of runtime)
553
- usedRuntimePackages.add(p);
554
- if (tooling.length > 0) {
555
- toolingUsage.push({ file: entry.file, packages: tooling });
556
- }
557
- }
558
- console.log("");
559
- console.log("nua clean \u2014 eject to standard Astro project");
560
- console.log("");
561
- console.log(` ${configName}`);
562
- console.log(" - Replace @nuasite/nua with explicit Astro integrations");
563
- console.log(" - Add mdx, sitemap, tailwindcss imports");
564
- if (disabled.size > 0) {
565
- console.log(` - Skipping disabled: ${[...disabled].join(", ")}`);
566
- }
567
- console.log("");
568
- console.log(" package.json");
569
- console.log(" - Remove @nuasite/* tooling dependencies");
570
- if (usedRuntimePackages.size > 0) {
571
- console.log(` - Keep as explicit deps: ${[...usedRuntimePackages].join(", ")}`);
572
- }
573
- console.log(" - Add standard Astro packages");
574
- console.log(" - Update scripts: nua \u2192 astro");
575
- if (toolingUsage.length > 0) {
576
- console.log("");
577
- console.log(" Warning: @nuasite tooling imports found in source files:");
578
- for (const { file, packages } of toolingUsage) {
579
- console.log(` ${file} (${packages.join(", ")})`);
580
- }
581
- console.log(" These will need manual removal.");
582
- }
583
- if (dryRun) {
584
- console.log("");
585
- console.log(" (--dry-run: no changes made)");
586
- console.log("");
587
- return;
588
- }
589
- if (!yes) {
590
- console.log("");
591
- const answer = prompt("Proceed? [y/N] ");
592
- if (answer?.toLowerCase() !== "y") {
593
- console.log("Cancelled.");
594
- return;
595
- }
596
- }
597
- const newConfig = transformConfig2(configContent, disabled);
598
- writeFileSync2(configPath, newConfig);
599
- console.log(` Updated ${configName}`);
600
- const newPkg = transformPackageJson2(pkg, disabled, [...usedRuntimePackages]);
601
- writeFileSync2(pkgPath, JSON.stringify(newPkg, null, "\t") + `
602
- `);
603
- console.log(" Updated package.json");
604
- console.log("");
605
- console.log("Next steps:");
606
- console.log(" 1. bun install");
607
- console.log(" 2. Review the updated config");
608
- console.log(" 3. astro dev");
609
- if (toolingUsage.length > 0) {
610
- console.log(" 4. Remove @nuasite tooling imports from source files");
611
- }
612
- console.log("");
613
- }
614
- var FEATURE_KEYS, NUASITE_TOOLING, PACKAGES_TO_ADD;
615
- var init_clean = __esm(() => {
616
- init_utils();
617
- FEATURE_KEYS = ["cms", "pageMarkdown", "mdx", "sitemap", "tailwindcss", "checks"];
618
- NUASITE_TOOLING = [
619
- "@nuasite/nua",
620
- "@nuasite/core",
621
- "@nuasite/cli",
622
- "@nuasite/cms",
623
- "@nuasite/llm-enhancements",
624
- "@nuasite/checks",
625
- "@nuasite/agent-summary"
626
- ];
627
- PACKAGES_TO_ADD = {
628
- astro: "^6.0.2",
629
- "@astrojs/check": "^0.9.7",
630
- "@astrojs/mdx": "^5.0.0",
631
- "@astrojs/sitemap": "^3.7.1",
632
- "@tailwindcss/vite": "^4.2.1",
633
- tailwindcss: "^4.2.1",
634
- typescript: "^5"
635
- };
636
- });
637
-
638
50
  // ../../node_modules/yaml/dist/nodes/identity.js
639
51
  var require_identity = __commonJS((exports) => {
640
52
  var ALIAS = Symbol.for("yaml.alias");
@@ -11458,13 +10870,13 @@ var require_lib = __commonJS((exports) => {
11458
10870
  flowEnumMemberRaw() {
11459
10871
  const loc = this.state.startLoc;
11460
10872
  const id = this.parseIdentifier(true);
11461
- const init2 = this.eat(29) ? this.flowEnumMemberInit() : {
10873
+ const init = this.eat(29) ? this.flowEnumMemberInit() : {
11462
10874
  type: "none",
11463
10875
  loc
11464
10876
  };
11465
10877
  return {
11466
10878
  id,
11467
- init: init2
10879
+ init
11468
10880
  };
11469
10881
  }
11470
10882
  flowEnumCheckExplicitTypeMismatch(loc, context, expectedType) {
@@ -11498,7 +10910,7 @@ var require_lib = __commonJS((exports) => {
11498
10910
  const memberNode = this.startNode();
11499
10911
  const {
11500
10912
  id,
11501
- init: init2
10913
+ init
11502
10914
  } = this.flowEnumMemberRaw();
11503
10915
  const memberName = id.name;
11504
10916
  if (memberName === "") {
@@ -11524,35 +10936,35 @@ var require_lib = __commonJS((exports) => {
11524
10936
  memberName
11525
10937
  };
11526
10938
  memberNode.id = id;
11527
- switch (init2.type) {
10939
+ switch (init.type) {
11528
10940
  case "boolean": {
11529
- this.flowEnumCheckExplicitTypeMismatch(init2.loc, context, "boolean");
11530
- memberNode.init = init2.value;
10941
+ this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "boolean");
10942
+ memberNode.init = init.value;
11531
10943
  members.booleanMembers.push(this.finishNode(memberNode, "EnumBooleanMember"));
11532
10944
  break;
11533
10945
  }
11534
10946
  case "number": {
11535
- this.flowEnumCheckExplicitTypeMismatch(init2.loc, context, "number");
11536
- memberNode.init = init2.value;
10947
+ this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "number");
10948
+ memberNode.init = init.value;
11537
10949
  members.numberMembers.push(this.finishNode(memberNode, "EnumNumberMember"));
11538
10950
  break;
11539
10951
  }
11540
10952
  case "string": {
11541
- this.flowEnumCheckExplicitTypeMismatch(init2.loc, context, "string");
11542
- memberNode.init = init2.value;
10953
+ this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "string");
10954
+ memberNode.init = init.value;
11543
10955
  members.stringMembers.push(this.finishNode(memberNode, "EnumStringMember"));
11544
10956
  break;
11545
10957
  }
11546
10958
  case "invalid": {
11547
- throw this.flowEnumErrorInvalidMemberInitializer(init2.loc, context);
10959
+ throw this.flowEnumErrorInvalidMemberInitializer(init.loc, context);
11548
10960
  }
11549
10961
  case "none": {
11550
10962
  switch (explicitType) {
11551
10963
  case "boolean":
11552
- this.flowEnumErrorBooleanMemberNotInitialized(init2.loc, context);
10964
+ this.flowEnumErrorBooleanMemberNotInitialized(init.loc, context);
11553
10965
  break;
11554
10966
  case "number":
11555
- this.flowEnumErrorNumberMemberNotInitialized(init2.loc, context);
10967
+ this.flowEnumErrorNumberMemberNotInitialized(init.loc, context);
11556
10968
  break;
11557
10969
  default:
11558
10970
  members.defaultedMembers.push(this.finishNode(memberNode, "EnumDefaultedMember"));
@@ -17177,14 +16589,14 @@ var require_lib = __commonJS((exports) => {
17177
16589
  }
17178
16590
  for (const {
17179
16591
  id,
17180
- init: init2
16592
+ init
17181
16593
  } of declaration.declarations) {
17182
- if (!init2)
16594
+ if (!init)
17183
16595
  continue;
17184
16596
  if (kind === "var" || kind === "let" || !!id.typeAnnotation) {
17185
- this.raise(TSErrors.InitializerNotAllowedInAmbientContext, init2);
17186
- } else if (!isValidAmbientConstInitializer(init2, this.hasPlugin("estree"))) {
17187
- this.raise(TSErrors.ConstInitializerMustBeStringOrNumericLiteralOrLiteralEnumReference, init2);
16597
+ this.raise(TSErrors.InitializerNotAllowedInAmbientContext, init);
16598
+ } else if (!isValidAmbientConstInitializer(init, this.hasPlugin("estree"))) {
16599
+ this.raise(TSErrors.ConstInitializerMustBeStringOrNumericLiteralOrLiteralEnumReference, init);
17188
16600
  }
17189
16601
  }
17190
16602
  return declaration;
@@ -20864,47 +20276,47 @@ var require_lib = __commonJS((exports) => {
20864
20276
  }
20865
20277
  this.next();
20866
20278
  this.parseVar(initNode, true, kind);
20867
- const init3 = this.finishNode(initNode, "VariableDeclaration");
20279
+ const init2 = this.finishNode(initNode, "VariableDeclaration");
20868
20280
  const isForIn = this.match(58);
20869
20281
  if (isForIn && starsWithUsingDeclaration) {
20870
- this.raise(Errors.ForInUsing, init3);
20282
+ this.raise(Errors.ForInUsing, init2);
20871
20283
  }
20872
- if ((isForIn || this.isContextual(102)) && init3.declarations.length === 1) {
20873
- return this.parseForIn(node, init3, awaitAt);
20284
+ if ((isForIn || this.isContextual(102)) && init2.declarations.length === 1) {
20285
+ return this.parseForIn(node, init2, awaitAt);
20874
20286
  }
20875
20287
  if (awaitAt !== null) {
20876
20288
  this.unexpected(awaitAt);
20877
20289
  }
20878
- return this.parseFor(node, init3);
20290
+ return this.parseFor(node, init2);
20879
20291
  }
20880
20292
  }
20881
20293
  const startsWithAsync = this.isContextual(95);
20882
20294
  const refExpressionErrors = new ExpressionErrors;
20883
- const init2 = this.parseExpression(true, refExpressionErrors);
20295
+ const init = this.parseExpression(true, refExpressionErrors);
20884
20296
  const isForOf = this.isContextual(102);
20885
20297
  if (isForOf) {
20886
20298
  if (startsWithLet) {
20887
- this.raise(Errors.ForOfLet, init2);
20299
+ this.raise(Errors.ForOfLet, init);
20888
20300
  }
20889
- if (awaitAt === null && startsWithAsync && init2.type === "Identifier") {
20890
- this.raise(Errors.ForOfAsync, init2);
20301
+ if (awaitAt === null && startsWithAsync && init.type === "Identifier") {
20302
+ this.raise(Errors.ForOfAsync, init);
20891
20303
  }
20892
20304
  }
20893
20305
  if (isForOf || this.match(58)) {
20894
20306
  this.checkDestructuringPrivate(refExpressionErrors);
20895
- this.toAssignable(init2, true);
20307
+ this.toAssignable(init, true);
20896
20308
  const type = isForOf ? "ForOfStatement" : "ForInStatement";
20897
- this.checkLVal(init2, {
20309
+ this.checkLVal(init, {
20898
20310
  type
20899
20311
  });
20900
- return this.parseForIn(node, init2, awaitAt);
20312
+ return this.parseForIn(node, init, awaitAt);
20901
20313
  } else {
20902
20314
  this.checkExpressionErrors(refExpressionErrors, true);
20903
20315
  }
20904
20316
  if (awaitAt !== null) {
20905
20317
  this.unexpected(awaitAt);
20906
20318
  }
20907
- return this.parseFor(node, init2);
20319
+ return this.parseFor(node, init);
20908
20320
  }
20909
20321
  parseFunctionStatement(node, isAsync, isHangingDeclaration) {
20910
20322
  this.next();
@@ -21123,8 +20535,8 @@ var require_lib = __commonJS((exports) => {
21123
20535
  }
21124
20536
  this.next();
21125
20537
  }
21126
- parseFor(node, init2) {
21127
- node.init = init2;
20538
+ parseFor(node, init) {
20539
+ node.init = init;
21128
20540
  this.semicolon(false);
21129
20541
  node.test = this.match(13) ? null : this.parseExpression();
21130
20542
  this.semicolon(false);
@@ -21135,7 +20547,7 @@ var require_lib = __commonJS((exports) => {
21135
20547
  this.state.labels.pop();
21136
20548
  return this.finishNode(node, "ForStatement");
21137
20549
  }
21138
- parseForIn(node, init2, awaitAt) {
20550
+ parseForIn(node, init, awaitAt) {
21139
20551
  const isForIn = this.match(58);
21140
20552
  this.next();
21141
20553
  if (isForIn) {
@@ -21144,19 +20556,19 @@ var require_lib = __commonJS((exports) => {
21144
20556
  } else {
21145
20557
  node.await = awaitAt !== null;
21146
20558
  }
21147
- if (init2.type === "VariableDeclaration" && init2.declarations[0].init != null && (!isForIn || !this.options.annexB || this.state.strict || init2.kind !== "var" || init2.declarations[0].id.type !== "Identifier")) {
21148
- this.raise(Errors.ForInOfLoopInitializer, init2, {
20559
+ if (init.type === "VariableDeclaration" && init.declarations[0].init != null && (!isForIn || !this.options.annexB || this.state.strict || init.kind !== "var" || init.declarations[0].id.type !== "Identifier")) {
20560
+ this.raise(Errors.ForInOfLoopInitializer, init, {
21149
20561
  type: isForIn ? "ForInStatement" : "ForOfStatement"
21150
20562
  });
21151
20563
  }
21152
- if (init2.type === "AssignmentPattern") {
21153
- this.raise(Errors.InvalidLhs, init2, {
20564
+ if (init.type === "AssignmentPattern") {
20565
+ this.raise(Errors.InvalidLhs, init, {
21154
20566
  ancestor: {
21155
20567
  type: "ForStatement"
21156
20568
  }
21157
20569
  });
21158
20570
  }
21159
- node.left = init2;
20571
+ node.left = init;
21160
20572
  node.right = isForIn ? this.parseExpression() : this.parseMaybeAssignAllowIn();
21161
20573
  this.expect(11);
21162
20574
  node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement());
@@ -23047,6 +22459,216 @@ var init_content_config_ast = __esm(() => {
23047
22459
  WRAPPER_METHODS = new Set(["optional", "nullable", "nullish", "default"]);
23048
22460
  });
23049
22461
 
22462
+ // ../cms-core/src/check.ts
22463
+ import path from "path";
22464
+ function isPlainObject(value) {
22465
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
22466
+ }
22467
+ function typeMismatch(field, value) {
22468
+ switch (field.type) {
22469
+ case "number":
22470
+ case "year":
22471
+ case "month":
22472
+ return typeof value === "number" ? null : `expected a number, found ${typeName(value)}`;
22473
+ case "boolean":
22474
+ return typeof value === "boolean" ? null : `expected true/false, found ${typeName(value)}`;
22475
+ case "select": {
22476
+ if (typeof value !== "string")
22477
+ return `expected one of the allowed values, found ${typeName(value)}`;
22478
+ const options = field.options ?? [];
22479
+ if (options.length === 0 || options.includes(value))
22480
+ return null;
22481
+ return `"${value}" is not one of: ${options.join(", ")}`;
22482
+ }
22483
+ case "date":
22484
+ case "datetime":
22485
+ if (value instanceof Date)
22486
+ return Number.isNaN(value.getTime()) ? "is not a valid date" : null;
22487
+ if (typeof value !== "string")
22488
+ return `expected a date, found ${typeName(value)}`;
22489
+ return Number.isNaN(Date.parse(value)) ? `"${value}" is not a valid date` : null;
22490
+ case "array":
22491
+ return Array.isArray(value) ? null : `expected a list, found ${typeName(value)}`;
22492
+ case "object":
22493
+ return isPlainObject(value) ? null : `expected an object, found ${typeName(value)}`;
22494
+ case "text":
22495
+ case "textarea":
22496
+ case "markdown":
22497
+ case "url":
22498
+ case "email":
22499
+ case "tel":
22500
+ case "color":
22501
+ case "time":
22502
+ case "reference":
22503
+ return typeof value === "string" ? null : `expected text, found ${typeName(value)}`;
22504
+ default:
22505
+ return null;
22506
+ }
22507
+ }
22508
+ async function collectEntryFiles(fs, collection, base) {
22509
+ const pattern = collection.loaderPattern ?? "**/*.{md,mdx,json,yaml,yml}";
22510
+ const files = await fs.glob(`${base}/${pattern}`);
22511
+ return files.filter((file) => !path.basename(file).startsWith("_")).sort();
22512
+ }
22513
+ function parseEntry(file, raw) {
22514
+ const extension = path.extname(file);
22515
+ if (DATA_EXTENSIONS.has(extension)) {
22516
+ try {
22517
+ const parsed = extension === ".json" ? JSON.parse(raw) : import_yaml.default.parse(raw);
22518
+ return { frontmatter: isPlainObject(parsed) ? parsed : {} };
22519
+ } catch (error) {
22520
+ return { error: error instanceof Error ? error.message.split(`
22521
+ `)[0] : String(error) };
22522
+ }
22523
+ }
22524
+ const trimmed = raw.trimStart();
22525
+ if (!trimmed.startsWith("---"))
22526
+ return { frontmatter: {} };
22527
+ const lines = trimmed.split(`
22528
+ `);
22529
+ const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
22530
+ if (end === -1)
22531
+ return { error: "frontmatter is never closed (missing the second `---`)" };
22532
+ try {
22533
+ const parsed = import_yaml.default.parse(lines.slice(1, end).join(`
22534
+ `).trim());
22535
+ return { frontmatter: isPlainObject(parsed) ? parsed : {} };
22536
+ } catch (error) {
22537
+ return { error: error instanceof Error ? error.message.split(`
22538
+ `)[0] : String(error) };
22539
+ }
22540
+ }
22541
+ async function checkContent(fs) {
22542
+ const findings = [];
22543
+ const config = await parseContentConfig(fs, new Map);
22544
+ if (config.size === 0) {
22545
+ findings.push({
22546
+ severity: "error",
22547
+ code: "config/no-collections",
22548
+ file: "src/content.config.ts",
22549
+ message: "No collections found \u2014 the content config is missing, unreadable, or declares nothing."
22550
+ });
22551
+ return { findings, collections: 0, entries: 0 };
22552
+ }
22553
+ const bases = new Map;
22554
+ const files = new Map;
22555
+ const stems = new Map;
22556
+ for (const [name, collection] of config) {
22557
+ const base = normalizeBase(collection.loaderBase ?? `src/content/${name}`);
22558
+ bases.set(name, base);
22559
+ if (!await fs.exists(base)) {
22560
+ findings.push({
22561
+ severity: "error",
22562
+ code: "config/missing-dir",
22563
+ file: "src/content.config.ts",
22564
+ field: name,
22565
+ message: `Collection "${name}" loads from ${base}/, which does not exist.`
22566
+ });
22567
+ files.set(name, []);
22568
+ stems.set(name, new Set);
22569
+ continue;
22570
+ }
22571
+ const collectionFiles = await collectEntryFiles(fs, collection, base);
22572
+ files.set(name, collectionFiles);
22573
+ stems.set(name, new Set(collectionFiles.map(entryStem)));
22574
+ if (collectionFiles.length === 0) {
22575
+ findings.push({
22576
+ severity: "warning",
22577
+ code: "config/empty-collection",
22578
+ file: base,
22579
+ field: name,
22580
+ message: `Collection "${name}" matched no entries under ${base}/.`
22581
+ });
22582
+ }
22583
+ }
22584
+ let entries = 0;
22585
+ for (const [name, collection] of config) {
22586
+ const typedFields = collection.fields.filter((field) => field.type !== undefined || field.required || field.reference);
22587
+ for (const file of files.get(name) ?? []) {
22588
+ entries++;
22589
+ const parsed = parseEntry(file, await fs.readFile(file));
22590
+ if ("error" in parsed) {
22591
+ findings.push({ severity: "error", code: "entry/syntax", file, message: `Frontmatter does not parse: ${parsed.error}` });
22592
+ continue;
22593
+ }
22594
+ for (const field of typedFields) {
22595
+ const value = parsed.frontmatter[field.name];
22596
+ if (value === undefined || value === null) {
22597
+ if (field.required) {
22598
+ findings.push({
22599
+ severity: "error",
22600
+ code: "entry/missing-required",
22601
+ file,
22602
+ field: field.name,
22603
+ message: `Required field "${field.name}" is missing.`
22604
+ });
22605
+ }
22606
+ continue;
22607
+ }
22608
+ const mismatch = field.type === undefined ? null : typeMismatch(field, value);
22609
+ if (mismatch) {
22610
+ findings.push({ severity: "error", code: "entry/field-type", file, field: field.name, message: `${field.name}: ${mismatch}` });
22611
+ continue;
22612
+ }
22613
+ const reference = field.reference;
22614
+ if (!reference)
22615
+ continue;
22616
+ const targets = stems.get(reference.target);
22617
+ if (!targets || targets.size === 0)
22618
+ continue;
22619
+ const values = reference.isArray ? Array.isArray(value) ? value : [value] : [value];
22620
+ for (const item of values) {
22621
+ if (typeof item !== "string" || targets.has(item))
22622
+ continue;
22623
+ findings.push({
22624
+ severity: "warning",
22625
+ code: "entry/dangling-reference",
22626
+ file,
22627
+ field: field.name,
22628
+ message: `${field.name}: "${item}" is not an entry of "${reference.target}".`
22629
+ });
22630
+ }
22631
+ }
22632
+ }
22633
+ }
22634
+ return { findings, collections: config.size, entries };
22635
+ }
22636
+ function formatCheckReport(report) {
22637
+ if (report.findings.length === 0)
22638
+ return "";
22639
+ const byFile = new Map;
22640
+ for (const finding of report.findings) {
22641
+ const bucket = byFile.get(finding.file);
22642
+ if (bucket)
22643
+ bucket.push(finding);
22644
+ else
22645
+ byFile.set(finding.file, [finding]);
22646
+ }
22647
+ const lines = [];
22648
+ for (const [file, findings] of byFile) {
22649
+ lines.push(file);
22650
+ for (const finding of findings) {
22651
+ lines.push(` ${finding.severity === "error" ? "error" : "warn "} ${finding.message} [${finding.code}]`);
22652
+ }
22653
+ }
22654
+ return lines.join(`
22655
+ `);
22656
+ }
22657
+ var import_yaml, DATA_EXTENSIONS, normalizeBase = (base) => path.normalize(base).replace(/^\.\//, "").replace(/\/+$/, ""), entryStem = (file) => path.basename(file).replace(/\.(md|mdx|json|ya?ml)$/, ""), typeName = (value) => {
22658
+ if (value === null)
22659
+ return "null";
22660
+ if (Array.isArray(value))
22661
+ return "array";
22662
+ if (value instanceof Date)
22663
+ return "date";
22664
+ return typeof value;
22665
+ };
22666
+ var init_check = __esm(() => {
22667
+ init_content_config_ast();
22668
+ import_yaml = __toESM(require_dist(), 1);
22669
+ DATA_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
22670
+ });
22671
+
23050
22672
  // ../cms-core/src/shared.ts
23051
22673
  function pathSegmentValue(raw) {
23052
22674
  if (typeof raw === "string")
@@ -23069,8 +22691,8 @@ function computePathnameFromSpec(spec, data) {
23069
22691
  return;
23070
22692
  segments.push(seg.map && Object.hasOwn(seg.map, value) ? seg.map[value] : value);
23071
22693
  }
23072
- const path = ("/" + segments.join("/")).replace(/\/{2,}/g, "/").replace(/\/+$/, "");
23073
- return path === "" ? "/" : path;
22694
+ const path2 = ("/" + segments.join("/")).replace(/\/{2,}/g, "/").replace(/\/+$/, "");
22695
+ return path2 === "" ? "/" : path2;
23074
22696
  }
23075
22697
  function resolvePathnameFromSpec(def, data) {
23076
22698
  if (!def.pathname)
@@ -23082,7 +22704,7 @@ function slugifyHref(text) {
23082
22704
  }
23083
22705
 
23084
22706
  // ../cms-core/src/collection-scanner.ts
23085
- import path from "path";
22707
+ import path2 from "path";
23086
22708
  function normalizeFieldName(name) {
23087
22709
  return name.toLowerCase().replace(/[_-]/g, "");
23088
22710
  }
@@ -23106,18 +22728,18 @@ function parseFrontmatter(content) {
23106
22728
  const block = extractFrontmatterBlock(content);
23107
22729
  if (!block)
23108
22730
  return null;
23109
- return import_yaml.parse(block);
22731
+ return import_yaml2.parse(block);
23110
22732
  }
23111
22733
  function parseFieldDirectives(content) {
23112
22734
  const block = extractFrontmatterBlock(content);
23113
22735
  if (!block)
23114
22736
  return {};
23115
- const doc = import_yaml.parseDocument(block);
23116
- if (!import_yaml.isMap(doc.contents))
22737
+ const doc = import_yaml2.parseDocument(block);
22738
+ if (!import_yaml2.isMap(doc.contents))
23117
22739
  return {};
23118
22740
  const result = {};
23119
22741
  for (const pair of doc.contents.items) {
23120
- if (!import_yaml.isPair(pair) || !import_yaml.isScalar(pair.key))
22742
+ if (!import_yaml2.isPair(pair) || !import_yaml2.isScalar(pair.key))
23121
22743
  continue;
23122
22744
  const comment = pair.key.commentBefore;
23123
22745
  if (!comment)
@@ -23191,1016 +22813,1632 @@ function inferFieldType(value, key) {
23191
22813
  `) || value.length > TEXTAREA_MIN_LENGTH) {
23192
22814
  return "textarea";
23193
22815
  }
23194
- return "text";
22816
+ return "text";
22817
+ }
22818
+ return "text";
22819
+ }
22820
+ function mergeFieldObservations(observations, depth = 0) {
22821
+ if (depth >= MAX_NESTED_FIELD_DEPTH)
22822
+ return [];
22823
+ const fields = [];
22824
+ for (const obs of observations) {
22825
+ const nonNullValues = obs.values.filter((v) => v !== null && v !== undefined);
22826
+ if (nonNullValues.length === 0)
22827
+ continue;
22828
+ const typeCounts = new Map;
22829
+ for (const value of nonNullValues) {
22830
+ const type = inferFieldType(value, obs.name);
22831
+ typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
22832
+ }
22833
+ let fieldType = "text";
22834
+ let maxCount = 0;
22835
+ for (const [type, count] of typeCounts) {
22836
+ if (count > maxCount) {
22837
+ maxCount = count;
22838
+ fieldType = type;
22839
+ }
22840
+ }
22841
+ const field = {
22842
+ name: obs.name,
22843
+ type: fieldType,
22844
+ required: obs.presentCount === obs.totalEntries,
22845
+ examples: nonNullValues.slice(0, 3)
22846
+ };
22847
+ if (fieldType === "text" && !FREE_TEXT_FIELD_NAMES.has(normalizeFieldName(obs.name))) {
22848
+ const uniqueValues = [...new Set(nonNullValues.map((v) => String(v)))];
22849
+ const uniqueRatio = uniqueValues.length / nonNullValues.length;
22850
+ if (uniqueValues.length > 0 && uniqueValues.length <= MAX_SELECT_OPTIONS && nonNullValues.length >= 2 && uniqueRatio <= 0.8) {
22851
+ field.type = "select";
22852
+ field.options = uniqueValues.sort();
22853
+ }
22854
+ }
22855
+ if (fieldType === "array") {
22856
+ const allItems = nonNullValues.flatMap((v) => Array.isArray(v) ? v : []);
22857
+ if (allItems.length > 0) {
22858
+ const itemType = inferFieldType(allItems[0], obs.name);
22859
+ field.itemType = itemType;
22860
+ if (itemType === "text") {
22861
+ const uniqueItems = [...new Set(allItems.map((v) => String(v)))];
22862
+ if (uniqueItems.length <= MAX_SELECT_OPTIONS * 2) {
22863
+ field.options = uniqueItems.sort();
22864
+ }
22865
+ }
22866
+ if (itemType === "object") {
22867
+ const objectItems = allItems.filter((v) => typeof v === "object" && v !== null && !Array.isArray(v));
22868
+ if (objectItems.length > 0) {
22869
+ const subFieldMap = new Map;
22870
+ for (const item of objectItems) {
22871
+ collectFieldObservations(subFieldMap, item, objectItems.length);
22872
+ }
22873
+ field.fields = mergeFieldObservations(Array.from(subFieldMap.values()), depth + 1);
22874
+ }
22875
+ }
22876
+ }
22877
+ }
22878
+ if (fieldType === "object") {
22879
+ const objectValues = nonNullValues.filter((v) => typeof v === "object" && v !== null && !Array.isArray(v));
22880
+ if (objectValues.length > 0) {
22881
+ const subFieldMap = new Map;
22882
+ for (const item of objectValues) {
22883
+ collectFieldObservations(subFieldMap, item, objectValues.length);
22884
+ }
22885
+ field.fields = mergeFieldObservations(Array.from(subFieldMap.values()), depth + 1);
22886
+ }
22887
+ }
22888
+ fields.push(field);
22889
+ }
22890
+ return fields;
22891
+ }
22892
+ function collectFieldObservations(fieldMap, data, totalEntries) {
22893
+ for (const [key, value] of Object.entries(data)) {
22894
+ let obs = fieldMap.get(key);
22895
+ if (!obs) {
22896
+ obs = { name: key, values: [], presentCount: 0, totalEntries };
22897
+ fieldMap.set(key, obs);
22898
+ }
22899
+ obs.values.push(value);
22900
+ obs.presentCount++;
22901
+ }
22902
+ }
22903
+ function assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, entryCount, extra) {
22904
+ for (const obs of fieldMap.values()) {
22905
+ obs.totalEntries = entryCount;
22906
+ }
22907
+ entryInfos.sort((a, b) => (a.title ?? a.slug).localeCompare(b.title ?? b.slug));
22908
+ const fields = mergeFieldObservations(Array.from(fieldMap.values()));
22909
+ const label = collectionName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
22910
+ return {
22911
+ name: collectionName,
22912
+ label,
22913
+ path: path2.join(contentDir, collectionName),
22914
+ entryCount,
22915
+ fields,
22916
+ fileExtension: "md",
22917
+ entries: entryInfos,
22918
+ ...extra
22919
+ };
22920
+ }
22921
+ function getCollectionSourceBasePath(basePath, collectionName, contentDir) {
22922
+ const defaultCollectionPath = path2.join(contentDir, collectionName);
22923
+ if (path2.normalize(basePath) === path2.normalize(defaultCollectionPath)) {
22924
+ return path2.join(contentDir, collectionName);
22925
+ }
22926
+ return basePath;
22927
+ }
22928
+ async function buildCollectionDefinition(fs, basePath, sources, collectionName, contentDir) {
22929
+ if (sources.length === 0)
22930
+ return null;
22931
+ const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
22932
+ const hasMd = sources.some((s) => s.relPath.endsWith(".md"));
22933
+ const fileExtension = hasMd ? "md" : "mdx";
22934
+ const fieldMap = new Map;
22935
+ const allDirectives = {};
22936
+ const entryInfos = [];
22937
+ let hasDraft = false;
22938
+ const fileContents = await Promise.all(sources.map((s) => fs.readFile(path2.join(basePath, s.relPath)).catch(() => null)));
22939
+ for (let i = 0;i < sources.length; i++) {
22940
+ const source = sources[i];
22941
+ const content = fileContents[i];
22942
+ if (content === null)
22943
+ continue;
22944
+ const frontmatter = parseFrontmatter(content);
22945
+ const directives = parseFieldDirectives(content);
22946
+ for (const [key, value] of Object.entries(directives)) {
22947
+ if (!allDirectives[key]) {
22948
+ allDirectives[key] = value;
22949
+ }
22950
+ }
22951
+ const entryInfo = {
22952
+ slug: source.slug,
22953
+ sourcePath: path2.join(sourceBasePath, source.relPath)
22954
+ };
22955
+ if (frontmatter) {
22956
+ const title = deriveEntryTitle(frontmatter);
22957
+ if (title !== undefined) {
22958
+ entryInfo.title = title;
22959
+ }
22960
+ if (typeof frontmatter.draft === "boolean" && frontmatter.draft) {
22961
+ entryInfo.draft = true;
22962
+ }
22963
+ entryInfo.data = frontmatter;
22964
+ }
22965
+ entryInfos.push(entryInfo);
22966
+ if (!frontmatter)
22967
+ continue;
22968
+ if (frontmatter.draft === true)
22969
+ hasDraft = true;
22970
+ collectFieldObservations(fieldMap, frontmatter, sources.length);
22971
+ }
22972
+ const def = assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
22973
+ path: sourceBasePath,
22974
+ supportsDraft: hasDraft,
22975
+ fileExtension
22976
+ });
22977
+ assignFieldMetadata(def.fields, allDirectives);
22978
+ return def;
22979
+ }
22980
+ async function buildDataCollectionDefinition(fs, basePath, sources, collectionName, contentDir) {
22981
+ if (sources.length === 0)
22982
+ return null;
22983
+ const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
22984
+ const fieldMap = new Map;
22985
+ const entryInfos = [];
22986
+ const ext = sources.some((s) => s.relPath.endsWith(".json")) ? "json" : sources.some((s) => s.relPath.endsWith(".yaml")) ? "yaml" : "yml";
22987
+ const fileContents = await Promise.all(sources.map((s) => fs.readFile(path2.join(basePath, s.relPath)).catch(() => null)));
22988
+ for (let i = 0;i < sources.length; i++) {
22989
+ const source = sources[i];
22990
+ const raw = fileContents[i];
22991
+ if (raw === null)
22992
+ continue;
22993
+ let data = null;
22994
+ try {
22995
+ data = source.relPath.endsWith(".json") ? JSON.parse(raw) : import_yaml2.parse(raw);
22996
+ } catch {
22997
+ continue;
22998
+ }
22999
+ if (!data || typeof data !== "object")
23000
+ continue;
23001
+ const title = deriveEntryTitle(data);
23002
+ entryInfos.push({
23003
+ slug: source.slug,
23004
+ title,
23005
+ sourcePath: path2.join(sourceBasePath, source.relPath),
23006
+ data
23007
+ });
23008
+ collectFieldObservations(fieldMap, data, sources.length);
23009
+ }
23010
+ return assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23011
+ type: "data",
23012
+ fileExtension: ext
23013
+ });
23014
+ }
23015
+ async function scanCollection(fs, collectionPath, collectionName, contentDir) {
23016
+ const dirEntries = await fs.list(collectionPath);
23017
+ if (dirEntries.length === 0)
23018
+ return null;
23019
+ const sources = [];
23020
+ const takenSlugs = new Set;
23021
+ for (const entry of dirEntries) {
23022
+ if (entry.isDirectory)
23023
+ continue;
23024
+ if (!entry.name.endsWith(".md") && !entry.name.endsWith(".mdx"))
23025
+ continue;
23026
+ const slug = entry.name.replace(/\.(md|mdx)$/, "");
23027
+ sources.push({ slug, relPath: entry.name });
23028
+ takenSlugs.add(slug);
23029
+ }
23030
+ const subdirs = dirEntries.filter((e) => e.isDirectory && !e.name.startsWith("_") && !e.name.startsWith("."));
23031
+ const indexLookups = await Promise.all(subdirs.map(async (dir) => {
23032
+ if (takenSlugs.has(dir.name))
23033
+ return null;
23034
+ for (const ext of ["md", "mdx"]) {
23035
+ const relPath = path2.join(dir.name, `index.${ext}`);
23036
+ if (await fs.exists(path2.join(collectionPath, relPath))) {
23037
+ return { slug: dir.name, relPath };
23038
+ }
23039
+ }
23040
+ return null;
23041
+ }));
23042
+ for (const entry of indexLookups) {
23043
+ if (entry)
23044
+ sources.push(entry);
23045
+ }
23046
+ if (sources.length === 0)
23047
+ return null;
23048
+ return await buildCollectionDefinition(fs, collectionPath, sources, collectionName, contentDir);
23049
+ }
23050
+ async function scanGlobCollection(fs, collectionName, baseRel, pattern, contentDir) {
23051
+ const matches = await fs.glob(path2.join(baseRel, pattern));
23052
+ const rels = matches.map((rel) => path2.relative(baseRel, rel)).filter((relToBase) => !relToBase.split("/").some((seg) => seg.startsWith("_") || seg.startsWith(".")));
23053
+ const mdSources = rels.filter((relToBase) => relToBase.endsWith(".md") || relToBase.endsWith(".mdx")).map((relToBase) => ({ slug: relToBase.replace(/\.(md|mdx)$/, ""), relPath: relToBase }));
23054
+ if (mdSources.length > 0) {
23055
+ return await buildCollectionDefinition(fs, baseRel, mdSources, collectionName, contentDir);
23056
+ }
23057
+ const dataSources = rels.filter((relToBase) => /\.(ya?ml|json)$/.test(relToBase)).map((relToBase) => ({ slug: relToBase.replace(/\.(ya?ml|json)$/, ""), relPath: relToBase }));
23058
+ if (dataSources.length > 0) {
23059
+ return await buildDataCollectionDefinition(fs, baseRel, dataSources, collectionName, contentDir);
23060
+ }
23061
+ return null;
23062
+ }
23063
+ function applyParsedConfig(collections, parsed) {
23064
+ for (const [collectionName, parsedColl] of parsed) {
23065
+ const def = collections[collectionName];
23066
+ if (!def)
23067
+ continue;
23068
+ if (parsedColl.fields.length > 0) {
23069
+ const schemaNames = new Set(parsedColl.fields.map((f) => f.name));
23070
+ def.fields = def.fields.filter((f) => schemaNames.has(f.name));
23071
+ }
23072
+ const fieldsByName = new Map(def.fields.map((f) => [f.name, f]));
23073
+ for (const pf of parsedColl.fields) {
23074
+ const field = fieldsByName.get(pf.name);
23075
+ if (!field)
23076
+ continue;
23077
+ applyParsedFieldOverrides(field, pf);
23078
+ }
23079
+ if (parsedColl.layout)
23080
+ def.layout = parsedColl.layout;
23081
+ if (parsedColl.pathname)
23082
+ def.pathname = parsedColl.pathname;
23083
+ if (parsedColl.fragment) {
23084
+ def.fragment = parsedColl.fragment;
23085
+ if (parsedColl.previewOf)
23086
+ def.previewOf = parsedColl.previewOf;
23087
+ }
23088
+ if (parsedColl.titleField) {
23089
+ def.titleField = parsedColl.titleField;
23090
+ applyCollectionTitleField(def, parsedColl.titleField);
23091
+ }
23092
+ }
23093
+ }
23094
+ function applyCollectionTitleField(def, titleField) {
23095
+ if (!def.entries)
23096
+ return;
23097
+ for (const entry of def.entries) {
23098
+ entry.title = entry.data ? deriveEntryTitle(entry.data, titleField) : undefined;
23099
+ }
23100
+ def.entries.sort((a, b) => (a.title ?? a.slug).localeCompare(b.title ?? b.slug));
23101
+ }
23102
+ function applyParsedFieldLayout(field, pf) {
23103
+ const layout = pf.layout;
23104
+ if (!layout)
23105
+ return;
23106
+ if (layout.label !== undefined)
23107
+ field.label = layout.label;
23108
+ if (layout.help !== undefined)
23109
+ field.help = layout.help;
23110
+ if (layout.group !== undefined)
23111
+ field.group = layout.group;
23112
+ if (layout.width !== undefined)
23113
+ field.width = layout.width;
23114
+ if (layout.order !== undefined)
23115
+ field.order = layout.order;
23116
+ if (layout.sidebar)
23117
+ field.position = "sidebar";
23118
+ if (layout.hidden)
23119
+ field.hidden = true;
23120
+ }
23121
+ function applyParsedFieldOverrides(field, pf) {
23122
+ if (pf.type) {
23123
+ field.type = pf.type;
23124
+ if (pf.options) {
23125
+ field.options = pf.options;
23126
+ field.optionsClosed = true;
23127
+ }
23128
+ }
23129
+ if (pf.itemType)
23130
+ field.itemType = pf.itemType;
23131
+ if (pf.hints)
23132
+ field.hints = pf.hints;
23133
+ if (pf.astroImage)
23134
+ field.astroImage = true;
23135
+ field.required = pf.required;
23136
+ applyParsedFieldLayout(field, pf);
23137
+ if (pf.fields) {
23138
+ const existingByName = new Map((field.fields ?? []).map((f) => [f.name, f]));
23139
+ field.fields = pf.fields.map((subPf) => {
23140
+ const existing = existingByName.get(subPf.name);
23141
+ if (existing) {
23142
+ applyParsedFieldOverrides(existing, subPf);
23143
+ return existing;
23144
+ }
23145
+ return parsedFieldToFieldDefinition(subPf);
23146
+ });
23147
+ }
23148
+ }
23149
+ function parsedFieldToFieldDefinition(pf) {
23150
+ const fd = {
23151
+ name: pf.name,
23152
+ type: pf.type ?? (pf.fields ? "object" : "text"),
23153
+ required: pf.required
23154
+ };
23155
+ if (pf.options) {
23156
+ fd.options = pf.options;
23157
+ fd.optionsClosed = true;
23158
+ }
23159
+ if (pf.itemType)
23160
+ fd.itemType = pf.itemType;
23161
+ if (pf.hints)
23162
+ fd.hints = pf.hints;
23163
+ if (pf.astroImage)
23164
+ fd.astroImage = true;
23165
+ if (pf.fields)
23166
+ fd.fields = pf.fields.map(parsedFieldToFieldDefinition);
23167
+ applyParsedFieldLayout(fd, pf);
23168
+ return fd;
23169
+ }
23170
+ function applyCollectionOrderBy(collections, parsed) {
23171
+ for (const [collectionName, parsedColl] of parsed) {
23172
+ const orderField = parsedColl.fields.find((f) => f.orderBy);
23173
+ if (!orderField?.orderBy)
23174
+ continue;
23175
+ const def = collections[collectionName];
23176
+ if (!def)
23177
+ continue;
23178
+ const fieldName = orderField.name;
23179
+ const direction = orderField.orderBy.direction;
23180
+ def.orderBy = fieldName;
23181
+ def.orderDirection = direction;
23182
+ if (def.entries && def.entries.length > 1) {
23183
+ const dir = direction === "desc" ? -1 : 1;
23184
+ def.entries.sort((a, b) => {
23185
+ const aVal = a.data?.[fieldName];
23186
+ const bVal = b.data?.[fieldName];
23187
+ if (aVal == null && bVal == null)
23188
+ return 0;
23189
+ if (aVal == null)
23190
+ return 1;
23191
+ if (bVal == null)
23192
+ return -1;
23193
+ if (typeof aVal === "number" && typeof bVal === "number")
23194
+ return (aVal - bVal) * dir;
23195
+ if (aVal instanceof Date && bVal instanceof Date)
23196
+ return (aVal.getTime() - bVal.getTime()) * dir;
23197
+ return String(aVal).localeCompare(String(bVal)) * dir;
23198
+ });
23199
+ }
23195
23200
  }
23196
- return "text";
23197
23201
  }
23198
- function mergeFieldObservations(observations, depth = 0) {
23199
- if (depth >= MAX_NESTED_FIELD_DEPTH)
23200
- return [];
23201
- const fields = [];
23202
- for (const obs of observations) {
23203
- const nonNullValues = obs.values.filter((v) => v !== null && v !== undefined);
23204
- if (nonNullValues.length === 0)
23202
+ function detectReferenceFields(collections, parsed) {
23203
+ let appliedAny = false;
23204
+ for (const [collectionName, parsedColl] of parsed) {
23205
+ const def = collections[collectionName];
23206
+ if (!def)
23205
23207
  continue;
23206
- const typeCounts = new Map;
23207
- for (const value of nonNullValues) {
23208
- const type = inferFieldType(value, obs.name);
23209
- typeCounts.set(type, (typeCounts.get(type) || 0) + 1);
23210
- }
23211
- let fieldType = "text";
23212
- let maxCount = 0;
23213
- for (const [type, count] of typeCounts) {
23214
- if (count > maxCount) {
23215
- maxCount = count;
23216
- fieldType = type;
23208
+ for (const pf of parsedColl.fields) {
23209
+ if (!pf.reference)
23210
+ continue;
23211
+ const field = def.fields.find((f) => f.name === pf.name);
23212
+ if (!field)
23213
+ continue;
23214
+ appliedAny = true;
23215
+ if (pf.reference.isArray) {
23216
+ field.type = "array";
23217
+ field.itemType = "reference";
23218
+ } else {
23219
+ field.type = "reference";
23217
23220
  }
23221
+ field.collection = pf.reference.target;
23222
+ field.options = undefined;
23218
23223
  }
23219
- const field = {
23220
- name: obs.name,
23221
- type: fieldType,
23222
- required: obs.presentCount === obs.totalEntries,
23223
- examples: nonNullValues.slice(0, 3)
23224
- };
23225
- if (fieldType === "text" && !FREE_TEXT_FIELD_NAMES.has(normalizeFieldName(obs.name))) {
23226
- const uniqueValues = [...new Set(nonNullValues.map((v) => String(v)))];
23227
- const uniqueRatio = uniqueValues.length / nonNullValues.length;
23228
- if (uniqueValues.length > 0 && uniqueValues.length <= MAX_SELECT_OPTIONS && nonNullValues.length >= 2 && uniqueRatio <= 0.8) {
23229
- field.type = "select";
23230
- field.options = uniqueValues.sort();
23231
- }
23224
+ }
23225
+ if (!appliedAny)
23226
+ detectReferenceFieldsBySlugMatch(collections);
23227
+ }
23228
+ function detectReferenceFieldsBySlugMatch(collections) {
23229
+ const collectionSlugs = new Map;
23230
+ for (const [name, def] of Object.entries(collections)) {
23231
+ if (def.entries && def.entries.length > 0) {
23232
+ collectionSlugs.set(name, new Set(def.entries.map((e) => e.slug)));
23232
23233
  }
23233
- if (fieldType === "array") {
23234
- const allItems = nonNullValues.flatMap((v) => Array.isArray(v) ? v : []);
23235
- if (allItems.length > 0) {
23236
- const itemType = inferFieldType(allItems[0], obs.name);
23237
- field.itemType = itemType;
23238
- if (itemType === "text") {
23239
- const uniqueItems = [...new Set(allItems.map((v) => String(v)))];
23240
- if (uniqueItems.length <= MAX_SELECT_OPTIONS * 2) {
23241
- field.options = uniqueItems.sort();
23234
+ }
23235
+ for (const [collectionName, def] of Object.entries(collections)) {
23236
+ for (const field of def.fields) {
23237
+ if ((field.type === "text" || field.type === "select") && field.examples) {
23238
+ const stringExamples = field.examples.filter((v) => typeof v === "string");
23239
+ if (stringExamples.length === 0)
23240
+ continue;
23241
+ const candidates = [];
23242
+ for (const [targetName, slugs] of collectionSlugs) {
23243
+ if (targetName === collectionName)
23244
+ continue;
23245
+ const matchCount = stringExamples.filter((v) => slugs.has(v)).length;
23246
+ if (matchCount > 0 && matchCount === stringExamples.length) {
23247
+ candidates.push({ name: targetName, slugs });
23242
23248
  }
23243
23249
  }
23244
- if (itemType === "object") {
23245
- const objectItems = allItems.filter((v) => typeof v === "object" && v !== null && !Array.isArray(v));
23246
- if (objectItems.length > 0) {
23247
- const subFieldMap = new Map;
23248
- for (const item of objectItems) {
23249
- collectFieldObservations(subFieldMap, item, objectItems.length);
23250
+ let bestTarget;
23251
+ if (candidates.length === 1) {
23252
+ bestTarget = candidates[0].name;
23253
+ } else if (candidates.length > 1) {
23254
+ const allValues = def.entries?.flatMap((e) => {
23255
+ const v = e.data?.[field.name];
23256
+ return typeof v === "string" ? [v] : [];
23257
+ }) ?? stringExamples;
23258
+ let bestOverlap = 0;
23259
+ for (const c of candidates) {
23260
+ const overlap = allValues.filter((v) => c.slugs.has(v)).length;
23261
+ if (overlap > bestOverlap) {
23262
+ bestOverlap = overlap;
23263
+ bestTarget = c.name;
23264
+ }
23265
+ }
23266
+ }
23267
+ if (bestTarget) {
23268
+ field.type = "reference";
23269
+ field.collection = bestTarget;
23270
+ field.options = undefined;
23271
+ }
23272
+ }
23273
+ if (field.type === "array" && field.itemType === "text" && field.options) {
23274
+ let bestTarget;
23275
+ let bestOverlap = 0;
23276
+ for (const [targetName, slugs] of collectionSlugs) {
23277
+ if (targetName === collectionName)
23278
+ continue;
23279
+ const matchCount = field.options.filter((v) => slugs.has(v)).length;
23280
+ if (matchCount > 0 && matchCount >= field.options.length * 0.5) {
23281
+ if (matchCount > bestOverlap) {
23282
+ bestOverlap = matchCount;
23283
+ bestTarget = targetName;
23250
23284
  }
23251
- field.fields = mergeFieldObservations(Array.from(subFieldMap.values()), depth + 1);
23252
23285
  }
23253
23286
  }
23287
+ if (bestTarget) {
23288
+ field.type = "array";
23289
+ field.itemType = "reference";
23290
+ field.collection = bestTarget;
23291
+ field.options = undefined;
23292
+ }
23254
23293
  }
23255
23294
  }
23256
- if (fieldType === "object") {
23257
- const objectValues = nonNullValues.filter((v) => typeof v === "object" && v !== null && !Array.isArray(v));
23258
- if (objectValues.length > 0) {
23259
- const subFieldMap = new Map;
23260
- for (const item of objectValues) {
23261
- collectFieldObservations(subFieldMap, item, objectValues.length);
23295
+ }
23296
+ }
23297
+ function assignSemanticRoles(collections) {
23298
+ for (const def of Object.values(collections)) {
23299
+ let toggle;
23300
+ let dateByName;
23301
+ let dateByType;
23302
+ for (const field of def.fields) {
23303
+ if (field.hidden || field.role)
23304
+ continue;
23305
+ const normalized = normalizeFieldName(field.name);
23306
+ if (!toggle && field.type === "boolean" && PUBLISH_TOGGLE_NAMES.has(normalized)) {
23307
+ toggle = field;
23308
+ } else if (!dateByName && PUBLISH_DATE_NAMES.has(normalized)) {
23309
+ dateByName = field;
23310
+ } else if (!dateByType && (field.type === "date" || field.type === "datetime")) {
23311
+ dateByType = field;
23312
+ }
23313
+ }
23314
+ if (toggle)
23315
+ toggle.role = "publish-toggle";
23316
+ const date = dateByName ?? dateByType;
23317
+ if (date)
23318
+ date.role = "publish-date";
23319
+ }
23320
+ }
23321
+ function detectDerivedHrefFields(collections) {
23322
+ for (const def of Object.values(collections)) {
23323
+ const fieldsByName = new Map(def.fields.map((f) => [f.name, f]));
23324
+ for (const field of def.fields) {
23325
+ if (field.hidden || field.derivedFrom)
23326
+ continue;
23327
+ const lowerName = field.name.toLowerCase();
23328
+ for (const suffix of HREF_SUFFIXES) {
23329
+ if (!lowerName.endsWith(suffix))
23330
+ continue;
23331
+ const baseName = field.name.slice(0, -suffix.length);
23332
+ if (!baseName)
23333
+ continue;
23334
+ let sourceField = fieldsByName.get(baseName);
23335
+ if (!sourceField) {
23336
+ const lowerBase = baseName.toLowerCase();
23337
+ for (const f of fieldsByName.values()) {
23338
+ if (f.name.toLowerCase() === lowerBase) {
23339
+ sourceField = f;
23340
+ break;
23341
+ }
23342
+ }
23343
+ }
23344
+ if (!sourceField || !sourceField.examples || !field.examples)
23345
+ continue;
23346
+ const sourceExamples = sourceField.examples.filter((v) => typeof v === "string");
23347
+ const derivedExamples = field.examples.filter((v) => typeof v === "string");
23348
+ if (sourceExamples.length === 0 || derivedExamples.length === 0)
23349
+ continue;
23350
+ const expectedHrefs = new Set(sourceExamples.map(slugifyHref));
23351
+ const allMatch = derivedExamples.every((v) => expectedHrefs.has(v));
23352
+ if (allMatch) {
23353
+ field.hidden = true;
23354
+ field.derivedFrom = sourceField.name;
23355
+ break;
23262
23356
  }
23263
- field.fields = mergeFieldObservations(Array.from(subFieldMap.values()), depth + 1);
23264
23357
  }
23265
23358
  }
23266
- fields.push(field);
23267
23359
  }
23268
- return fields;
23269
23360
  }
23270
- function collectFieldObservations(fieldMap, data, totalEntries) {
23271
- for (const [key, value] of Object.entries(data)) {
23272
- let obs = fieldMap.get(key);
23273
- if (!obs) {
23274
- obs = { name: key, values: [], presentCount: 0, totalEntries };
23275
- fieldMap.set(key, obs);
23361
+ async function scanDataCollection(fs, collectionPath, collectionName, contentDir) {
23362
+ const dirEntries = await fs.list(collectionPath);
23363
+ if (dirEntries.length === 0)
23364
+ return null;
23365
+ const sources = [];
23366
+ const takenSlugs = new Set;
23367
+ for (const entry of dirEntries) {
23368
+ if (entry.isDirectory)
23369
+ continue;
23370
+ if (!entry.name.endsWith(".json") && !entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml"))
23371
+ continue;
23372
+ const slug = entry.name.replace(/\.(json|ya?ml)$/, "");
23373
+ sources.push({ slug, relPath: entry.name });
23374
+ takenSlugs.add(slug);
23375
+ }
23376
+ const subdirs = dirEntries.filter((e) => e.isDirectory && !e.name.startsWith("_") && !e.name.startsWith("."));
23377
+ const indexLookups = await Promise.all(subdirs.map(async (dir) => {
23378
+ if (takenSlugs.has(dir.name))
23379
+ return null;
23380
+ for (const indexExt of ["json", "yaml", "yml"]) {
23381
+ const relPath = path2.join(dir.name, `index.${indexExt}`);
23382
+ if (await fs.exists(path2.join(collectionPath, relPath))) {
23383
+ return { slug: dir.name, relPath };
23384
+ }
23276
23385
  }
23277
- obs.values.push(value);
23278
- obs.presentCount++;
23386
+ return null;
23387
+ }));
23388
+ for (const entry of indexLookups) {
23389
+ if (entry)
23390
+ sources.push(entry);
23279
23391
  }
23392
+ return await buildDataCollectionDefinition(fs, collectionPath, sources, collectionName, contentDir);
23280
23393
  }
23281
- function assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, entryCount, extra) {
23282
- for (const obs of fieldMap.values()) {
23283
- obs.totalEntries = entryCount;
23394
+ function normalizeBasePath(basePath) {
23395
+ return path2.normalize(basePath.replace(/^\.[/\\]+/, "")).replace(/[/\\]+$/, "");
23396
+ }
23397
+ function patternCoversWholeCollection(pattern, definition) {
23398
+ const match = pattern.trim().replace(/^\.[/\\]+/, "").match(WHOLE_DIRECTORY_PATTERN);
23399
+ if (!match)
23400
+ return false;
23401
+ const [, braceGroup, singleExtension] = match;
23402
+ if (braceGroup === undefined && singleExtension === undefined)
23403
+ return true;
23404
+ const patternExtensions = new Set((braceGroup !== undefined ? braceGroup.split(",") : [singleExtension]).map((ext) => ext.trim().replace(/^\./, "").toLowerCase()).filter(Boolean));
23405
+ const scannedExtensions = new Set((definition.entries ?? []).map((entry) => path2.extname(entry.sourcePath).slice(1).toLowerCase()).filter(Boolean));
23406
+ if (scannedExtensions.size === 0)
23407
+ return false;
23408
+ for (const ext of scannedExtensions) {
23409
+ if (!patternExtensions.has(ext))
23410
+ return false;
23284
23411
  }
23285
- entryInfos.sort((a, b) => (a.title ?? a.slug).localeCompare(b.title ?? b.slug));
23286
- const fields = mergeFieldObservations(Array.from(fieldMap.values()));
23287
- const label = collectionName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
23288
- return {
23289
- name: collectionName,
23290
- label,
23291
- path: path.join(contentDir, collectionName),
23292
- entryCount,
23293
- fields,
23294
- fileExtension: "md",
23295
- entries: entryInfos,
23296
- ...extra
23297
- };
23412
+ return true;
23298
23413
  }
23299
- function getCollectionSourceBasePath(basePath, collectionName, contentDir) {
23300
- const defaultCollectionPath = path.join(contentDir, collectionName);
23301
- if (path.normalize(basePath) === path.normalize(defaultCollectionPath)) {
23302
- return path.join(contentDir, collectionName);
23414
+ async function scanCollections(fs, contentDir = "src/content", parseCache = new Map) {
23415
+ const collections = {};
23416
+ const entries = await fs.list(contentDir);
23417
+ const scanPromises = entries.filter((entry) => entry.isDirectory && !entry.name.startsWith("_") && !entry.name.startsWith(".")).map(async (entry) => {
23418
+ try {
23419
+ const collectionPath = path2.join(contentDir, entry.name);
23420
+ const definition = await scanCollection(fs, collectionPath, entry.name, contentDir) ?? await scanDataCollection(fs, collectionPath, entry.name, contentDir);
23421
+ if (definition) {
23422
+ collections[entry.name] = definition;
23423
+ }
23424
+ } catch (error) {
23425
+ console.warn(`[cms] Skipping collection "${entry.name}" \u2014 scan failed:`, error);
23426
+ }
23427
+ });
23428
+ await Promise.all(scanPromises);
23429
+ const parsed = await parseContentConfig(fs, parseCache);
23430
+ const scannedNameByBasePath = new Map;
23431
+ for (const [name, definition] of Object.entries(collections)) {
23432
+ scannedNameByBasePath.set(normalizeBasePath(definition.path), name);
23303
23433
  }
23304
- return basePath;
23434
+ const rekeyedScannedNames = new Map;
23435
+ for (const [collectionName, parsedCollection] of parsed) {
23436
+ if (collections[collectionName])
23437
+ continue;
23438
+ if (!parsedCollection.loaderBase || !parsedCollection.loaderPattern)
23439
+ continue;
23440
+ const scannedName = scannedNameByBasePath.get(normalizeBasePath(parsedCollection.loaderBase));
23441
+ const scannedDefinition = scannedName ? collections[scannedName] : undefined;
23442
+ if (scannedName && scannedDefinition && patternCoversWholeCollection(parsedCollection.loaderPattern, scannedDefinition)) {
23443
+ delete collections[scannedName];
23444
+ scannedDefinition.name = collectionName;
23445
+ for (const other of Object.values(collections)) {
23446
+ if (other.parentCollection === scannedName)
23447
+ other.parentCollection = collectionName;
23448
+ }
23449
+ collections[collectionName] = scannedDefinition;
23450
+ rekeyedScannedNames.set(scannedName, collectionName);
23451
+ continue;
23452
+ }
23453
+ let definition;
23454
+ try {
23455
+ definition = await scanGlobCollection(fs, collectionName, parsedCollection.loaderBase, parsedCollection.loaderPattern, contentDir);
23456
+ } catch (error) {
23457
+ console.warn(`[cms] Skipping glob collection "${collectionName}" \u2014 scan failed:`, error);
23458
+ continue;
23459
+ }
23460
+ if (!definition)
23461
+ continue;
23462
+ const baseName = parsedCollection.loaderBase.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
23463
+ const parentName = !baseName ? undefined : collections[baseName] ? baseName : rekeyedScannedNames.get(baseName);
23464
+ if (parentName && parentName !== collectionName) {
23465
+ definition.parentCollection = parentName;
23466
+ }
23467
+ collections[collectionName] = definition;
23468
+ }
23469
+ applyParsedConfig(collections, parsed);
23470
+ detectReferenceFields(collections, parsed);
23471
+ detectDerivedHrefFields(collections);
23472
+ assignSemanticRoles(collections);
23473
+ applyCollectionOrderBy(collections, parsed);
23474
+ warnOnPathnameCollisions(collections);
23475
+ return collections;
23305
23476
  }
23306
- async function buildCollectionDefinition(fs, basePath, sources, collectionName, contentDir) {
23307
- if (sources.length === 0)
23308
- return null;
23309
- const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
23310
- const hasMd = sources.some((s) => s.relPath.endsWith(".md"));
23311
- const fileExtension = hasMd ? "md" : "mdx";
23312
- const fieldMap = new Map;
23313
- const allDirectives = {};
23314
- const entryInfos = [];
23315
- let hasDraft = false;
23316
- const fileContents = await Promise.all(sources.map((s) => fs.readFile(path.join(basePath, s.relPath)).catch(() => null)));
23317
- for (let i = 0;i < sources.length; i++) {
23318
- const source = sources[i];
23319
- const content = fileContents[i];
23320
- if (content === null)
23477
+ function warnOnPathnameCollisions(collections) {
23478
+ for (const def of Object.values(collections)) {
23479
+ if (!def.pathname || !def.entries)
23321
23480
  continue;
23322
- const frontmatter = parseFrontmatter(content);
23323
- const directives = parseFieldDirectives(content);
23324
- for (const [key, value] of Object.entries(directives)) {
23325
- if (!allDirectives[key]) {
23326
- allDirectives[key] = value;
23481
+ const bySlug = new Map;
23482
+ for (const entry of def.entries) {
23483
+ const pathname = resolvePathnameFromSpec(def, entry.data);
23484
+ if (!pathname)
23485
+ continue;
23486
+ const prev = bySlug.get(pathname);
23487
+ if (prev) {
23488
+ console.warn(`[cms] collection "${def.name}": pathname rule maps both "${prev}" and "${entry.slug}" to "${pathname}" \u2014 these entries will collide on one URL`);
23489
+ } else {
23490
+ bySlug.set(pathname, entry.slug);
23327
23491
  }
23328
23492
  }
23329
- const entryInfo = {
23330
- slug: source.slug,
23331
- sourcePath: path.join(sourceBasePath, source.relPath)
23332
- };
23333
- if (frontmatter) {
23334
- const title = deriveEntryTitle(frontmatter);
23335
- if (title !== undefined) {
23336
- entryInfo.title = title;
23493
+ }
23494
+ }
23495
+ var import_yaml2, DATE_PATTERN, URL_PATTERN, IMAGE_EXTENSIONS, MAX_SELECT_OPTIONS = 10, TEXTAREA_MIN_LENGTH = 200, SIDEBAR_FIELD_NAMES, DIRECTIVE_PATTERN, FREE_TEXT_FIELD_NAMES, PUBLISH_TOGGLE_NAMES, PUBLISH_DATE_NAMES, TITLE_FALLBACK_FIELDS, FRONTMATTER_PATTERN, MAX_NESTED_FIELD_DEPTH = 16, HREF_SUFFIXES, WHOLE_DIRECTORY_PATTERN;
23496
+ var init_collection_scanner = __esm(() => {
23497
+ init_content_config_ast();
23498
+ import_yaml2 = __toESM(require_dist(), 1);
23499
+ DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
23500
+ URL_PATTERN = /^(https?:\/\/|\/)/;
23501
+ IMAGE_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|svg|avif)$/i;
23502
+ SIDEBAR_FIELD_NAMES = new Set([
23503
+ "title",
23504
+ "date",
23505
+ "pubdate",
23506
+ "publishdate",
23507
+ "draft",
23508
+ "image",
23509
+ "featuredimage",
23510
+ "cover",
23511
+ "coverimage",
23512
+ "thumbnail",
23513
+ "author"
23514
+ ]);
23515
+ DIRECTIVE_PATTERN = /^\s*@(position|group)\s+(.+)$/;
23516
+ FREE_TEXT_FIELD_NAMES = new Set([
23517
+ "title",
23518
+ "name",
23519
+ "description",
23520
+ "summary",
23521
+ "excerpt",
23522
+ "subtitle",
23523
+ "heading",
23524
+ "headline",
23525
+ "slug",
23526
+ "alt",
23527
+ "caption"
23528
+ ]);
23529
+ PUBLISH_TOGGLE_NAMES = new Set(["draft", "isdraft", "published", "ispublished", "unpublished"]);
23530
+ PUBLISH_DATE_NAMES = new Set([
23531
+ "date",
23532
+ "pubdate",
23533
+ "publishdate",
23534
+ "publisheddate",
23535
+ "publishedate",
23536
+ "publishedat",
23537
+ "datepublished"
23538
+ ]);
23539
+ TITLE_FALLBACK_FIELDS = ["title", "name", "label"];
23540
+ FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
23541
+ HREF_SUFFIXES = ["href", "url", "link", "slug", "path"];
23542
+ WHOLE_DIRECTORY_PATTERN = /^(?:\*\*[/\\])*\*(?:\.(?:\{([^{}/\\]*)\}|([A-Za-z0-9]+)))?$/;
23543
+ });
23544
+
23545
+ // ../cms-core/src/component-registry.ts
23546
+ var import_parser2;
23547
+ var init_component_registry = __esm(() => {
23548
+ import_parser2 = __toESM(require_lib(), 1);
23549
+ });
23550
+
23551
+ // ../cms-core/src/media/local.ts
23552
+ function mimeFromExt(ext) {
23553
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
23554
+ }
23555
+ var MIME_BY_EXT;
23556
+ var init_local = __esm(() => {
23557
+ MIME_BY_EXT = {
23558
+ ".jpg": "image/jpeg",
23559
+ ".jpeg": "image/jpeg",
23560
+ ".png": "image/png",
23561
+ ".gif": "image/gif",
23562
+ ".webp": "image/webp",
23563
+ ".avif": "image/avif",
23564
+ ".svg": "image/svg+xml",
23565
+ ".ico": "image/x-icon",
23566
+ ".mp4": "video/mp4",
23567
+ ".webm": "video/webm",
23568
+ ".pdf": "application/pdf"
23569
+ };
23570
+ });
23571
+
23572
+ // ../cms-core/src/handlers/entry-ops.ts
23573
+ var import_yaml3;
23574
+ var init_entry_ops = __esm(() => {
23575
+ init_collection_scanner();
23576
+ init_content_config_ast();
23577
+ init_local();
23578
+ import_yaml3 = __toESM(require_dist(), 1);
23579
+ });
23580
+
23581
+ // ../cms-core/src/handlers/page-ops.ts
23582
+ var init_page_ops = () => {};
23583
+ // ../cms-core/src/core.ts
23584
+ var init_core = __esm(() => {
23585
+ init_collection_scanner();
23586
+ init_component_registry();
23587
+ init_entry_ops();
23588
+ init_page_ops();
23589
+ });
23590
+
23591
+ // ../cms-core/src/fs/glob.ts
23592
+ function globToRegExp(glob) {
23593
+ let re = "";
23594
+ for (let i = 0;i < glob.length; i++) {
23595
+ const c = glob[i];
23596
+ if (c === "*") {
23597
+ if (glob[i + 1] === "*") {
23598
+ re += ".*";
23599
+ i++;
23600
+ if (glob[i + 1] === "/")
23601
+ i++;
23602
+ } else {
23603
+ re += "[^/]*";
23337
23604
  }
23338
- if (typeof frontmatter.draft === "boolean" && frontmatter.draft) {
23339
- entryInfo.draft = true;
23605
+ } else if (c === "?") {
23606
+ re += "[^/]";
23607
+ } else if (c === "{") {
23608
+ const end = glob.indexOf("}", i);
23609
+ if (end === -1) {
23610
+ re += "\\{";
23611
+ } else {
23612
+ const opts = glob.slice(i + 1, end).split(",").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
23613
+ re += `(?:${opts.join("|")})`;
23614
+ i = end;
23340
23615
  }
23341
- entryInfo.data = frontmatter;
23616
+ } else if (".+^$()|[]\\".includes(c)) {
23617
+ re += `\\${c}`;
23618
+ } else {
23619
+ re += c;
23342
23620
  }
23343
- entryInfos.push(entryInfo);
23344
- if (!frontmatter)
23345
- continue;
23346
- if (frontmatter.draft === true)
23347
- hasDraft = true;
23348
- collectFieldObservations(fieldMap, frontmatter, sources.length);
23349
23621
  }
23350
- const def = assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23351
- path: sourceBasePath,
23352
- supportsDraft: hasDraft,
23353
- fileExtension
23354
- });
23355
- assignFieldMetadata(def.fields, allDirectives);
23356
- return def;
23622
+ return new RegExp(`^${re}$`);
23357
23623
  }
23358
- async function buildDataCollectionDefinition(fs, basePath, sources, collectionName, contentDir) {
23359
- if (sources.length === 0)
23360
- return null;
23361
- const sourceBasePath = getCollectionSourceBasePath(basePath, collectionName, contentDir);
23362
- const fieldMap = new Map;
23363
- const entryInfos = [];
23364
- const ext = sources.some((s) => s.relPath.endsWith(".json")) ? "json" : sources.some((s) => s.relPath.endsWith(".yaml")) ? "yaml" : "yml";
23365
- const fileContents = await Promise.all(sources.map((s) => fs.readFile(path.join(basePath, s.relPath)).catch(() => null)));
23366
- for (let i = 0;i < sources.length; i++) {
23367
- const source = sources[i];
23368
- const raw = fileContents[i];
23369
- if (raw === null)
23370
- continue;
23371
- let data = null;
23372
- try {
23373
- data = source.relPath.endsWith(".json") ? JSON.parse(raw) : import_yaml.parse(raw);
23374
- } catch {
23375
- continue;
23376
- }
23377
- if (!data || typeof data !== "object")
23378
- continue;
23379
- const title = deriveEntryTitle(data);
23380
- entryInfos.push({
23381
- slug: source.slug,
23382
- title,
23383
- sourcePath: path.join(sourceBasePath, source.relPath),
23384
- data
23385
- });
23386
- collectFieldObservations(fieldMap, data, sources.length);
23624
+
23625
+ // ../cms-core/src/fs/node-fs.ts
23626
+ import fs from "fs/promises";
23627
+ import path3 from "path";
23628
+ function resolveWithinRoot(root, filePath) {
23629
+ const resolvedRoot = path3.resolve(root);
23630
+ const isAbsoluteFs = filePath.startsWith(resolvedRoot);
23631
+ const normalizedPath = !isAbsoluteFs && filePath.startsWith("/") ? filePath.slice(1) : filePath;
23632
+ const fullPath = path3.isAbsolute(normalizedPath) ? path3.resolve(normalizedPath) : path3.resolve(resolvedRoot, normalizedPath);
23633
+ if (!fullPath.startsWith(resolvedRoot + path3.sep) && fullPath !== resolvedRoot) {
23634
+ throw new Error(`Path traversal detected: ${filePath}`);
23387
23635
  }
23388
- return assembleCollectionDefinition(collectionName, contentDir, fieldMap, entryInfos, sources.length, {
23389
- type: "data",
23390
- fileExtension: ext
23391
- });
23636
+ return fullPath;
23392
23637
  }
23393
- async function scanCollection(fs, collectionPath, collectionName, contentDir) {
23394
- const dirEntries = await fs.list(collectionPath);
23395
- if (dirEntries.length === 0)
23396
- return null;
23397
- const sources = [];
23398
- const takenSlugs = new Set;
23399
- for (const entry of dirEntries) {
23400
- if (entry.isDirectory)
23401
- continue;
23402
- if (!entry.name.endsWith(".md") && !entry.name.endsWith(".mdx"))
23403
- continue;
23404
- const slug = entry.name.replace(/\.(md|mdx)$/, "");
23405
- sources.push({ slug, relPath: entry.name });
23406
- takenSlugs.add(slug);
23638
+ function isNodeError(error) {
23639
+ return error instanceof Error && "code" in error;
23640
+ }
23641
+ async function walkFiles(absRoot, absDir) {
23642
+ let dirEntries;
23643
+ try {
23644
+ dirEntries = await fs.readdir(absDir, { withFileTypes: true });
23645
+ } catch {
23646
+ return [];
23407
23647
  }
23408
- const subdirs = dirEntries.filter((e) => e.isDirectory && !e.name.startsWith("_") && !e.name.startsWith("."));
23409
- const indexLookups = await Promise.all(subdirs.map(async (dir) => {
23410
- if (takenSlugs.has(dir.name))
23411
- return null;
23412
- for (const ext of ["md", "mdx"]) {
23413
- const relPath = path.join(dir.name, `index.${ext}`);
23414
- if (await fs.exists(path.join(collectionPath, relPath))) {
23415
- return { slug: dir.name, relPath };
23416
- }
23648
+ const out = [];
23649
+ for (const entry of dirEntries) {
23650
+ const abs = path3.join(absDir, entry.name);
23651
+ if (entry.isDirectory()) {
23652
+ out.push(...await walkFiles(absRoot, abs));
23653
+ } else if (entry.isFile()) {
23654
+ out.push(path3.relative(absRoot, abs).split(path3.sep).join("/"));
23417
23655
  }
23418
- return null;
23419
- }));
23420
- for (const entry of indexLookups) {
23421
- if (entry)
23422
- sources.push(entry);
23423
23656
  }
23424
- if (sources.length === 0)
23425
- return null;
23426
- return await buildCollectionDefinition(fs, collectionPath, sources, collectionName, contentDir);
23657
+ return out;
23427
23658
  }
23428
- async function scanGlobCollection(fs, collectionName, baseRel, pattern, contentDir) {
23429
- const matches = await fs.glob(path.join(baseRel, pattern));
23430
- const rels = matches.map((rel) => path.relative(baseRel, rel)).filter((relToBase) => !relToBase.split("/").some((seg) => seg.startsWith("_") || seg.startsWith(".")));
23431
- const mdSources = rels.filter((relToBase) => relToBase.endsWith(".md") || relToBase.endsWith(".mdx")).map((relToBase) => ({ slug: relToBase.replace(/\.(md|mdx)$/, ""), relPath: relToBase }));
23432
- if (mdSources.length > 0) {
23433
- return await buildCollectionDefinition(fs, baseRel, mdSources, collectionName, contentDir);
23659
+ function staticPrefixDir(pattern) {
23660
+ const segments = pattern.split("/");
23661
+ const staticSegments = [];
23662
+ for (const segment of segments) {
23663
+ if (/[*?{}[\]]/.test(segment))
23664
+ break;
23665
+ staticSegments.push(segment);
23434
23666
  }
23435
- const dataSources = rels.filter((relToBase) => /\.(ya?ml|json)$/.test(relToBase)).map((relToBase) => ({ slug: relToBase.replace(/\.(ya?ml|json)$/, ""), relPath: relToBase }));
23436
- if (dataSources.length > 0) {
23437
- return await buildDataCollectionDefinition(fs, baseRel, dataSources, collectionName, contentDir);
23667
+ if (staticSegments.length === segments.length) {
23668
+ staticSegments.pop();
23438
23669
  }
23439
- return null;
23670
+ return staticSegments.join("/");
23440
23671
  }
23441
- function applyParsedConfig(collections, parsed) {
23442
- for (const [collectionName, parsedColl] of parsed) {
23443
- const def = collections[collectionName];
23444
- if (!def)
23445
- continue;
23446
- if (parsedColl.fields.length > 0) {
23447
- const schemaNames = new Set(parsedColl.fields.map((f) => f.name));
23448
- def.fields = def.fields.filter((f) => schemaNames.has(f.name));
23449
- }
23450
- const fieldsByName = new Map(def.fields.map((f) => [f.name, f]));
23451
- for (const pf of parsedColl.fields) {
23452
- const field = fieldsByName.get(pf.name);
23453
- if (!field)
23454
- continue;
23455
- applyParsedFieldOverrides(field, pf);
23456
- }
23457
- if (parsedColl.layout)
23458
- def.layout = parsedColl.layout;
23459
- if (parsedColl.pathname)
23460
- def.pathname = parsedColl.pathname;
23461
- if (parsedColl.fragment) {
23462
- def.fragment = parsedColl.fragment;
23463
- if (parsedColl.previewOf)
23464
- def.previewOf = parsedColl.previewOf;
23465
- }
23466
- if (parsedColl.titleField) {
23467
- def.titleField = parsedColl.titleField;
23468
- applyCollectionTitleField(def, parsedColl.titleField);
23672
+ function createNodeFs(root) {
23673
+ const resolvedRoot = path3.resolve(root);
23674
+ const resolve = (p) => resolveWithinRoot(resolvedRoot, p);
23675
+ return {
23676
+ async readFile(filePath) {
23677
+ return fs.readFile(resolve(filePath), "utf-8");
23678
+ },
23679
+ async readBytes(filePath) {
23680
+ return fs.readFile(resolve(filePath));
23681
+ },
23682
+ async writeFile(filePath, content) {
23683
+ const fullPath = resolve(filePath);
23684
+ await fs.mkdir(path3.dirname(fullPath), { recursive: true });
23685
+ const tempPath = `${fullPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
23686
+ try {
23687
+ await fs.writeFile(tempPath, content, "utf-8");
23688
+ await fs.rename(tempPath, fullPath);
23689
+ } catch (error) {
23690
+ await fs.rm(tempPath, { force: true });
23691
+ throw error;
23692
+ }
23693
+ },
23694
+ async rename(from, to) {
23695
+ const fullTo = resolve(to);
23696
+ await fs.mkdir(path3.dirname(fullTo), { recursive: true });
23697
+ await fs.rename(resolve(from), fullTo);
23698
+ },
23699
+ async remove(filePath) {
23700
+ await fs.rm(resolve(filePath), { force: true });
23701
+ },
23702
+ async exists(filePath) {
23703
+ try {
23704
+ await fs.access(resolve(filePath));
23705
+ return true;
23706
+ } catch {
23707
+ return false;
23708
+ }
23709
+ },
23710
+ async list(dir) {
23711
+ try {
23712
+ const entries = await fs.readdir(resolve(dir), { withFileTypes: true });
23713
+ return entries.map((entry) => ({ name: entry.name, isDirectory: entry.isDirectory() }));
23714
+ } catch (error) {
23715
+ if (isNodeError(error) && error.code === "ENOENT")
23716
+ return [];
23717
+ throw error;
23718
+ }
23719
+ },
23720
+ async glob(pattern) {
23721
+ const baseRel = staticPrefixDir(pattern);
23722
+ const absBase = resolve(baseRel);
23723
+ const matcher = globToRegExp(pattern);
23724
+ const files = await walkFiles(resolvedRoot, absBase);
23725
+ return files.filter((rel) => matcher.test(rel));
23726
+ },
23727
+ async stat(filePath) {
23728
+ const s = await fs.stat(resolve(filePath));
23729
+ return { mtimeMs: s.mtimeMs, size: s.size };
23469
23730
  }
23470
- }
23471
- }
23472
- function applyCollectionTitleField(def, titleField) {
23473
- if (!def.entries)
23474
- return;
23475
- for (const entry of def.entries) {
23476
- entry.title = entry.data ? deriveEntryTitle(entry.data, titleField) : undefined;
23477
- }
23478
- def.entries.sort((a, b) => (a.title ?? a.slug).localeCompare(b.title ?? b.slug));
23731
+ };
23479
23732
  }
23480
- function applyParsedFieldLayout(field, pf) {
23481
- const layout = pf.layout;
23482
- if (!layout)
23733
+ var init_node_fs = () => {};
23734
+ // ../cms-core/src/media/project-images.ts
23735
+ function uploadsDirRelativeToRoot(dir, root) {
23736
+ if (dir === undefined)
23483
23737
  return;
23484
- if (layout.label !== undefined)
23485
- field.label = layout.label;
23486
- if (layout.help !== undefined)
23487
- field.help = layout.help;
23488
- if (layout.group !== undefined)
23489
- field.group = layout.group;
23490
- if (layout.width !== undefined)
23491
- field.width = layout.width;
23492
- if (layout.order !== undefined)
23493
- field.order = layout.order;
23494
- if (layout.sidebar)
23495
- field.position = "sidebar";
23496
- if (layout.hidden)
23497
- field.hidden = true;
23738
+ const base = root.replace(/\/+$/, "");
23739
+ const trimmed = dir.replace(/\/+$/, "");
23740
+ if (trimmed === base)
23741
+ return "";
23742
+ if (trimmed.startsWith(`${base}/`))
23743
+ return trimmed.slice(base.length + 1);
23744
+ return trimmed.replace(/^\.\//, "").replace(/^\/+/, "");
23498
23745
  }
23499
- function applyParsedFieldOverrides(field, pf) {
23500
- if (pf.type) {
23501
- field.type = pf.type;
23502
- if (pf.options) {
23503
- field.options = pf.options;
23504
- field.optionsClosed = true;
23505
- }
23506
- }
23507
- if (pf.itemType)
23508
- field.itemType = pf.itemType;
23509
- if (pf.hints)
23510
- field.hints = pf.hints;
23511
- if (pf.astroImage)
23512
- field.astroImage = true;
23513
- field.required = pf.required;
23514
- applyParsedFieldLayout(field, pf);
23515
- if (pf.fields) {
23516
- const existingByName = new Map((field.fields ?? []).map((f) => [f.name, f]));
23517
- field.fields = pf.fields.map((subPf) => {
23518
- const existing = existingByName.get(subPf.name);
23519
- if (existing) {
23520
- applyParsedFieldOverrides(existing, subPf);
23521
- return existing;
23522
- }
23523
- return parsedFieldToFieldDefinition(subPf);
23524
- });
23525
- }
23746
+ async function listProjectImages(fs2, options) {
23747
+ const normalized = options?.exclude ? uploadsDirRelativeToRoot(options.exclude.dir, options.exclude.root) : options?.excludeDir?.replace(/^\/+/, "").replace(/\/+$/, "");
23748
+ const excludeDir = normalized ? normalized : null;
23749
+ const scanDirs = [
23750
+ { dir: "public", relativeToRoot: false },
23751
+ { dir: "src", relativeToRoot: true }
23752
+ ];
23753
+ const results = await Promise.all(scanDirs.map(async ({ dir, relativeToRoot }) => {
23754
+ const items2 = [];
23755
+ await scanDirectory(fs2, dir, dir, relativeToRoot, excludeDir, items2);
23756
+ return items2;
23757
+ }));
23758
+ const items = results.flat();
23759
+ items.sort((a, b) => a.filename.localeCompare(b.filename) || compareCodeUnits(a.url, b.url));
23760
+ return items;
23526
23761
  }
23527
- function parsedFieldToFieldDefinition(pf) {
23528
- const fd = {
23529
- name: pf.name,
23530
- type: pf.type ?? (pf.fields ? "object" : "text"),
23531
- required: pf.required
23532
- };
23533
- if (pf.options) {
23534
- fd.options = pf.options;
23535
- fd.optionsClosed = true;
23536
- }
23537
- if (pf.itemType)
23538
- fd.itemType = pf.itemType;
23539
- if (pf.hints)
23540
- fd.hints = pf.hints;
23541
- if (pf.astroImage)
23542
- fd.astroImage = true;
23543
- if (pf.fields)
23544
- fd.fields = pf.fields.map(parsedFieldToFieldDefinition);
23545
- applyParsedFieldLayout(fd, pf);
23546
- return fd;
23762
+ function compareCodeUnits(a, b) {
23763
+ return a < b ? -1 : a > b ? 1 : 0;
23547
23764
  }
23548
- function applyCollectionOrderBy(collections, parsed) {
23549
- for (const [collectionName, parsedColl] of parsed) {
23550
- const orderField = parsedColl.fields.find((f) => f.orderBy);
23551
- if (!orderField?.orderBy)
23552
- continue;
23553
- const def = collections[collectionName];
23554
- if (!def)
23765
+ async function scanDirectory(fs2, currentDir, baseDir, relativeToRoot, excludeDir, items) {
23766
+ if (excludeDir !== null && currentDir === excludeDir)
23767
+ return;
23768
+ const entries = await fs2.list(currentDir);
23769
+ const subdirs = [];
23770
+ for (const entry of entries) {
23771
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
23555
23772
  continue;
23556
- const fieldName = orderField.name;
23557
- const direction = orderField.orderBy.direction;
23558
- def.orderBy = fieldName;
23559
- def.orderDirection = direction;
23560
- if (def.entries && def.entries.length > 1) {
23561
- const dir = direction === "desc" ? -1 : 1;
23562
- def.entries.sort((a, b) => {
23563
- const aVal = a.data?.[fieldName];
23564
- const bVal = b.data?.[fieldName];
23565
- if (aVal == null && bVal == null)
23566
- return 0;
23567
- if (aVal == null)
23568
- return 1;
23569
- if (bVal == null)
23570
- return -1;
23571
- if (typeof aVal === "number" && typeof bVal === "number")
23572
- return (aVal - bVal) * dir;
23573
- if (aVal instanceof Date && bVal instanceof Date)
23574
- return (aVal.getTime() - bVal.getTime()) * dir;
23575
- return String(aVal).localeCompare(String(bVal)) * dir;
23773
+ const fullPath = `${currentDir}/${entry.name}`;
23774
+ if (entry.isDirectory) {
23775
+ subdirs.push(scanDirectory(fs2, fullPath, baseDir, relativeToRoot, excludeDir, items));
23776
+ } else {
23777
+ const dotIdx = entry.name.lastIndexOf(".");
23778
+ const ext = dotIdx >= 0 ? entry.name.slice(dotIdx).toLowerCase() : "";
23779
+ if (!IMAGE_EXTENSIONS2.has(ext))
23780
+ continue;
23781
+ const url = relativeToRoot ? `/${fullPath}` : `/${fullPath.slice(baseDir.length + 1)}`;
23782
+ items.push({
23783
+ id: `project:${url}`,
23784
+ url,
23785
+ filename: entry.name,
23786
+ contentType: mimeFromExt(ext)
23576
23787
  });
23577
23788
  }
23578
23789
  }
23790
+ await Promise.all(subdirs);
23579
23791
  }
23580
- function detectReferenceFields(collections, parsed) {
23581
- let appliedAny = false;
23582
- for (const [collectionName, parsedColl] of parsed) {
23583
- const def = collections[collectionName];
23584
- if (!def)
23585
- continue;
23586
- for (const pf of parsedColl.fields) {
23587
- if (!pf.reference)
23588
- continue;
23589
- const field = def.fields.find((f) => f.name === pf.name);
23590
- if (!field)
23591
- continue;
23592
- appliedAny = true;
23593
- if (pf.reference.isArray) {
23594
- field.type = "array";
23595
- field.itemType = "reference";
23596
- } else {
23597
- field.type = "reference";
23598
- }
23599
- field.collection = pf.reference.target;
23600
- field.options = undefined;
23601
- }
23792
+ var IMAGE_EXTENSIONS2;
23793
+ var init_project_images = __esm(() => {
23794
+ init_local();
23795
+ IMAGE_EXTENSIONS2 = new Set(Object.entries(MIME_BY_EXT).filter(([, mime]) => mime.startsWith("image/")).map(([ext]) => ext));
23796
+ });
23797
+
23798
+ // ../cms-core/src/media/s3.ts
23799
+ var init_s3 = __esm(() => {
23800
+ init_local();
23801
+ });
23802
+
23803
+ // ../cms-core/src/media/index.ts
23804
+ var init_media = __esm(() => {
23805
+ init_local();
23806
+ init_project_images();
23807
+ init_s3();
23808
+ });
23809
+
23810
+ // ../cms-core/src/project-config-ast.ts
23811
+ var import_parser3;
23812
+ var init_project_config_ast = __esm(() => {
23813
+ import_parser3 = __toESM(require_lib(), 1);
23814
+ });
23815
+
23816
+ // ../cms-core/src/index.ts
23817
+ var init_src2 = __esm(() => {
23818
+ init_check();
23819
+ init_collection_scanner();
23820
+ init_component_registry();
23821
+ init_content_config_ast();
23822
+ init_core();
23823
+ init_node_fs();
23824
+ init_entry_ops();
23825
+ init_media();
23826
+ init_project_config_ast();
23827
+ });
23828
+
23829
+ // src/check.ts
23830
+ var exports_check = {};
23831
+ __export(exports_check, {
23832
+ check: () => check
23833
+ });
23834
+ async function check(options) {
23835
+ const report = await checkContent(createNodeFs(options.cwd));
23836
+ if (options.json) {
23837
+ console.log(JSON.stringify(report, null, 2));
23838
+ } else {
23839
+ const formatted = formatCheckReport(report);
23840
+ if (formatted)
23841
+ console.log(formatted + `
23842
+ `);
23602
23843
  }
23603
- if (!appliedAny)
23604
- detectReferenceFieldsBySlugMatch(collections);
23844
+ const errors = report.findings.filter((finding) => finding.severity === "error").length;
23845
+ const warnings = report.findings.length - errors;
23846
+ if (!options.json) {
23847
+ const counts = `${report.collections} collection(s), ${report.entries} entries`;
23848
+ console.log(errors === 0 && warnings === 0 ? `${counts} \u2014 no problems found` : `${counts} \u2014 ${errors} error(s), ${warnings} warning(s)`);
23849
+ }
23850
+ return errors > 0 || options.strict && warnings > 0 ? 1 : 0;
23605
23851
  }
23606
- function detectReferenceFieldsBySlugMatch(collections) {
23607
- const collectionSlugs = new Map;
23608
- for (const [name, def] of Object.entries(collections)) {
23609
- if (def.entries && def.entries.length > 0) {
23610
- collectionSlugs.set(name, new Set(def.entries.map((e) => e.slug)));
23611
- }
23852
+ var init_check2 = __esm(() => {
23853
+ init_src2();
23854
+ });
23855
+
23856
+ // src/utils.ts
23857
+ import { existsSync } from "fs";
23858
+ import { join } from "path";
23859
+ function findAstroConfig(cwd = process.cwd()) {
23860
+ for (const name of CONFIG_NAMES) {
23861
+ const p = join(cwd, name);
23862
+ if (existsSync(p))
23863
+ return p;
23612
23864
  }
23613
- for (const [collectionName, def] of Object.entries(collections)) {
23614
- for (const field of def.fields) {
23615
- if ((field.type === "text" || field.type === "select") && field.examples) {
23616
- const stringExamples = field.examples.filter((v) => typeof v === "string");
23617
- if (stringExamples.length === 0)
23618
- continue;
23619
- const candidates = [];
23620
- for (const [targetName, slugs] of collectionSlugs) {
23621
- if (targetName === collectionName)
23622
- continue;
23623
- const matchCount = stringExamples.filter((v) => slugs.has(v)).length;
23624
- if (matchCount > 0 && matchCount === stringExamples.length) {
23625
- candidates.push({ name: targetName, slugs });
23626
- }
23627
- }
23628
- let bestTarget;
23629
- if (candidates.length === 1) {
23630
- bestTarget = candidates[0].name;
23631
- } else if (candidates.length > 1) {
23632
- const allValues = def.entries?.flatMap((e) => {
23633
- const v = e.data?.[field.name];
23634
- return typeof v === "string" ? [v] : [];
23635
- }) ?? stringExamples;
23636
- let bestOverlap = 0;
23637
- for (const c of candidates) {
23638
- const overlap = allValues.filter((v) => c.slugs.has(v)).length;
23639
- if (overlap > bestOverlap) {
23640
- bestOverlap = overlap;
23641
- bestTarget = c.name;
23642
- }
23643
- }
23644
- }
23645
- if (bestTarget) {
23646
- field.type = "reference";
23647
- field.collection = bestTarget;
23648
- field.options = undefined;
23649
- }
23650
- }
23651
- if (field.type === "array" && field.itemType === "text" && field.options) {
23652
- let bestTarget;
23653
- let bestOverlap = 0;
23654
- for (const [targetName, slugs] of collectionSlugs) {
23655
- if (targetName === collectionName)
23656
- continue;
23657
- const matchCount = field.options.filter((v) => slugs.has(v)).length;
23658
- if (matchCount > 0 && matchCount >= field.options.length * 0.5) {
23659
- if (matchCount > bestOverlap) {
23660
- bestOverlap = matchCount;
23661
- bestTarget = targetName;
23662
- }
23663
- }
23664
- }
23665
- if (bestTarget) {
23666
- field.type = "array";
23667
- field.itemType = "reference";
23668
- field.collection = bestTarget;
23669
- field.options = undefined;
23670
- }
23865
+ return null;
23866
+ }
23867
+ function findMatchingClose(text, start) {
23868
+ const open = text[start];
23869
+ const close = open === "{" ? "}" : open === "[" ? "]" : ")";
23870
+ let depth = 0;
23871
+ let inString = false;
23872
+ for (let i = start;i < text.length; i++) {
23873
+ const ch = text[i];
23874
+ if (inString) {
23875
+ if (ch === "\\") {
23876
+ i++;
23877
+ continue;
23671
23878
  }
23879
+ if (ch === inString)
23880
+ inString = false;
23881
+ continue;
23882
+ }
23883
+ if (ch === "'" || ch === '"' || ch === "`") {
23884
+ inString = ch;
23885
+ continue;
23886
+ }
23887
+ if (ch === open)
23888
+ depth++;
23889
+ if (ch === close) {
23890
+ depth--;
23891
+ if (depth === 0)
23892
+ return i;
23672
23893
  }
23673
23894
  }
23895
+ return -1;
23674
23896
  }
23675
- function assignSemanticRoles(collections) {
23676
- for (const def of Object.values(collections)) {
23677
- let toggle;
23678
- let dateByName;
23679
- let dateByType;
23680
- for (const field of def.fields) {
23681
- if (field.hidden || field.role)
23682
- continue;
23683
- const normalized = normalizeFieldName(field.name);
23684
- if (!toggle && field.type === "boolean" && PUBLISH_TOGGLE_NAMES.has(normalized)) {
23685
- toggle = field;
23686
- } else if (!dateByName && PUBLISH_DATE_NAMES.has(normalized)) {
23687
- dateByName = field;
23688
- } else if (!dateByType && (field.type === "date" || field.type === "datetime")) {
23689
- dateByType = field;
23690
- }
23897
+ function extractConfigBody(content) {
23898
+ const match = content.match(/defineConfig\s*\(\s*\{/);
23899
+ if (!match || match.index === undefined)
23900
+ return "";
23901
+ const openBrace = content.indexOf("{", match.index + "defineConfig".length);
23902
+ const closeBrace = findMatchingClose(content, openBrace);
23903
+ if (closeBrace === -1)
23904
+ return "";
23905
+ return content.slice(openBrace + 1, closeBrace);
23906
+ }
23907
+ function removeProperty(body, propName) {
23908
+ const regex = new RegExp(`(\\n[ \\t]*)${propName}\\s*:\\s*`);
23909
+ const match = regex.exec(body);
23910
+ if (!match || match.index === undefined)
23911
+ return body;
23912
+ const propLineStart = match.index + 1;
23913
+ const afterMatch = match.index + match[0].length;
23914
+ let i = afterMatch;
23915
+ while (i < body.length && (body[i] === " " || body[i] === "\t"))
23916
+ i++;
23917
+ let valueEnd;
23918
+ if (body[i] === "{" || body[i] === "[") {
23919
+ valueEnd = findMatchingClose(body, i);
23920
+ if (valueEnd === -1)
23921
+ return body;
23922
+ valueEnd++;
23923
+ } else {
23924
+ while (i < body.length && body[i] !== "," && body[i] !== `
23925
+ `)
23926
+ i++;
23927
+ valueEnd = i;
23928
+ }
23929
+ let end = valueEnd;
23930
+ while (end < body.length && (body[end] === " " || body[end] === "\t"))
23931
+ end++;
23932
+ if (end < body.length && body[end] === ",")
23933
+ end++;
23934
+ while (end < body.length && (body[end] === " " || body[end] === "\t"))
23935
+ end++;
23936
+ if (end < body.length && body[end] === `
23937
+ `)
23938
+ end++;
23939
+ return body.slice(0, propLineStart) + body.slice(end);
23940
+ }
23941
+ function assembleConfig(imports, body) {
23942
+ const bodyLines = body.split(`
23943
+ `).filter((line) => line.trim() !== "");
23944
+ let result = imports.join(`
23945
+ `) + `
23946
+
23947
+ `;
23948
+ result += `export default defineConfig({
23949
+ `;
23950
+ if (bodyLines.length > 0) {
23951
+ result += bodyLines.join(`
23952
+ `) + `
23953
+ `;
23954
+ }
23955
+ result += `})
23956
+ `;
23957
+ return result;
23958
+ }
23959
+ var CONFIG_NAMES;
23960
+ var init_utils = __esm(() => {
23961
+ CONFIG_NAMES = [
23962
+ "astro.config.ts",
23963
+ "astro.config.mts",
23964
+ "astro.config.mjs",
23965
+ "astro.config.js"
23966
+ ];
23967
+ });
23968
+
23969
+ // src/init.ts
23970
+ var exports_init = {};
23971
+ __export(exports_init, {
23972
+ transformPackageJson: () => transformPackageJson,
23973
+ transformConfig: () => transformConfig,
23974
+ removeCallFromArray: () => removeCallFromArray,
23975
+ init: () => init,
23976
+ detectNuaManagedImports: () => detectNuaManagedImports,
23977
+ cleanEmptyStructures: () => cleanEmptyStructures
23978
+ });
23979
+ import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
23980
+ import { basename, join as join2 } from "path";
23981
+ function detectNuaManagedImports(content) {
23982
+ const managed = new Map;
23983
+ for (const pkg of NUA_MANAGED_PACKAGES) {
23984
+ const regex = new RegExp(`import\\s+(\\w+)\\s+from\\s+['"]${pkg.replace("/", "\\/")}['"]`);
23985
+ const match = regex.exec(content);
23986
+ if (match) {
23987
+ managed.set(pkg, match[1]);
23691
23988
  }
23692
- if (toggle)
23693
- toggle.role = "publish-toggle";
23694
- const date = dateByName ?? dateByType;
23695
- if (date)
23696
- date.role = "publish-date";
23697
23989
  }
23990
+ return managed;
23698
23991
  }
23699
- function detectDerivedHrefFields(collections) {
23700
- for (const def of Object.values(collections)) {
23701
- const fieldsByName = new Map(def.fields.map((f) => [f.name, f]));
23702
- for (const field of def.fields) {
23703
- if (field.hidden || field.derivedFrom)
23704
- continue;
23705
- const lowerName = field.name.toLowerCase();
23706
- for (const suffix of HREF_SUFFIXES) {
23707
- if (!lowerName.endsWith(suffix))
23708
- continue;
23709
- const baseName = field.name.slice(0, -suffix.length);
23710
- if (!baseName)
23711
- continue;
23712
- let sourceField = fieldsByName.get(baseName);
23713
- if (!sourceField) {
23714
- const lowerBase = baseName.toLowerCase();
23715
- for (const f of fieldsByName.values()) {
23716
- if (f.name.toLowerCase() === lowerBase) {
23717
- sourceField = f;
23718
- break;
23719
- }
23720
- }
23721
- }
23722
- if (!sourceField || !sourceField.examples || !field.examples)
23723
- continue;
23724
- const sourceExamples = sourceField.examples.filter((v) => typeof v === "string");
23725
- const derivedExamples = field.examples.filter((v) => typeof v === "string");
23726
- if (sourceExamples.length === 0 || derivedExamples.length === 0)
23727
- continue;
23728
- const expectedHrefs = new Set(sourceExamples.map(slugifyHref));
23729
- const allMatch = derivedExamples.every((v) => expectedHrefs.has(v));
23730
- if (allMatch) {
23731
- field.hidden = true;
23732
- field.derivedFrom = sourceField.name;
23733
- break;
23734
- }
23735
- }
23992
+ function removeCallFromArray(body, arrayProp, callName) {
23993
+ const propRegex = new RegExp(`\\b${arrayProp}\\s*:\\s*\\[`);
23994
+ const propMatch = propRegex.exec(body);
23995
+ if (!propMatch || propMatch.index === undefined)
23996
+ return body;
23997
+ const arrayStart = body.indexOf("[", propMatch.index);
23998
+ const arrayEnd = findMatchingClose(body, arrayStart);
23999
+ if (arrayEnd === -1)
24000
+ return body;
24001
+ const arrayContent = body.slice(arrayStart + 1, arrayEnd);
24002
+ const callRegex = new RegExp(`\\b${callName}\\s*\\(`);
24003
+ const callMatch = callRegex.exec(arrayContent);
24004
+ if (!callMatch || callMatch.index === undefined)
24005
+ return body;
24006
+ const parenStart = arrayContent.indexOf("(", callMatch.index);
24007
+ const parenEnd = findMatchingClose(arrayContent, parenStart);
24008
+ if (parenEnd === -1)
24009
+ return body;
24010
+ let removeStart = callMatch.index;
24011
+ let removeEnd = parenEnd + 1;
24012
+ let after = removeEnd;
24013
+ while (after < arrayContent.length && (arrayContent[after] === " " || arrayContent[after] === "\t" || arrayContent[after] === `
24014
+ `)) {
24015
+ after++;
24016
+ }
24017
+ if (after < arrayContent.length && arrayContent[after] === ",") {
24018
+ removeEnd = after + 1;
24019
+ while (removeEnd < arrayContent.length && (arrayContent[removeEnd] === " " || arrayContent[removeEnd] === "\t" || arrayContent[removeEnd] === `
24020
+ `))
24021
+ removeEnd++;
24022
+ }
24023
+ if (removeEnd === parenEnd + 1) {
24024
+ let before = removeStart - 1;
24025
+ while (before >= 0 && (arrayContent[before] === " " || arrayContent[before] === "\t" || arrayContent[before] === `
24026
+ `))
24027
+ before--;
24028
+ if (before >= 0 && arrayContent[before] === ",") {
24029
+ removeStart = before;
23736
24030
  }
23737
24031
  }
24032
+ const newArrayContent = arrayContent.slice(0, removeStart) + arrayContent.slice(removeEnd);
24033
+ return body.slice(0, arrayStart + 1) + newArrayContent + body.slice(arrayEnd);
23738
24034
  }
23739
- async function scanDataCollection(fs, collectionPath, collectionName, contentDir) {
23740
- const dirEntries = await fs.list(collectionPath);
23741
- if (dirEntries.length === 0)
23742
- return null;
23743
- const sources = [];
23744
- const takenSlugs = new Set;
23745
- for (const entry of dirEntries) {
23746
- if (entry.isDirectory)
24035
+ function cleanEmptyStructures(body) {
24036
+ body = body.replace(/\n[ \t]*sourcemap\s*:\s*true\s*,?[ \t]*/g, `
24037
+ `);
24038
+ body = body.replace(/\n[ \t]*build\s*:\s*\{[\s,]*\}\s*,?[ \t]*/g, `
24039
+ `);
24040
+ body = body.replace(/\n[ \t]*plugins\s*:\s*\[[\s,]*\]\s*,?[ \t]*/g, `
24041
+ `);
24042
+ body = body.replace(/\n[ \t]*integrations\s*:\s*\[[\s,]*\]\s*,?[ \t]*/g, `
24043
+ `);
24044
+ body = body.replace(/\n[ \t]*vite\s*:\s*\{[\s,]*\}\s*,?[ \t]*/g, `
24045
+ `);
24046
+ return body;
24047
+ }
24048
+ function transformConfig(content, managedImports) {
24049
+ const lines = content.split(`
24050
+ `);
24051
+ const newImports = [];
24052
+ for (const line of lines) {
24053
+ if (!/^\s*import\s/.test(line))
23747
24054
  continue;
23748
- if (!entry.name.endsWith(".json") && !entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml"))
24055
+ if (line.includes("astro/config") && line.includes("defineConfig")) {
24056
+ newImports.push(`import { defineConfig } from '@nuasite/nua/config'`);
23749
24057
  continue;
23750
- const slug = entry.name.replace(/\.(json|ya?ml)$/, "");
23751
- sources.push({ slug, relPath: entry.name });
23752
- takenSlugs.add(slug);
24058
+ }
24059
+ const isManagedImport = [...managedImports.keys()].some((pkg) => line.includes(pkg));
24060
+ if (isManagedImport)
24061
+ continue;
24062
+ newImports.push(line);
23753
24063
  }
23754
- const subdirs = dirEntries.filter((e) => e.isDirectory && !e.name.startsWith("_") && !e.name.startsWith("."));
23755
- const indexLookups = await Promise.all(subdirs.map(async (dir) => {
23756
- if (takenSlugs.has(dir.name))
23757
- return null;
23758
- for (const indexExt of ["json", "yaml", "yml"]) {
23759
- const relPath = path.join(dir.name, `index.${indexExt}`);
23760
- if (await fs.exists(path.join(collectionPath, relPath))) {
23761
- return { slug: dir.name, relPath };
23762
- }
24064
+ let body = extractConfigBody(content);
24065
+ for (const [pkg, localName] of managedImports) {
24066
+ if (pkg === "@tailwindcss/vite") {
24067
+ body = removeCallFromArray(body, "plugins", localName);
24068
+ } else {
24069
+ body = removeCallFromArray(body, "integrations", localName);
23763
24070
  }
23764
- return null;
23765
- }));
23766
- for (const entry of indexLookups) {
23767
- if (entry)
23768
- sources.push(entry);
23769
24071
  }
23770
- return await buildDataCollectionDefinition(fs, collectionPath, sources, collectionName, contentDir);
23771
- }
23772
- function normalizeBasePath(basePath) {
23773
- return path.normalize(basePath.replace(/^\.[/\\]+/, "")).replace(/[/\\]+$/, "");
24072
+ body = cleanEmptyStructures(body);
24073
+ return assembleConfig(newImports, body);
23774
24074
  }
23775
- function patternCoversWholeCollection(pattern, definition) {
23776
- const match = pattern.trim().replace(/^\.[/\\]+/, "").match(WHOLE_DIRECTORY_PATTERN);
23777
- if (!match)
23778
- return false;
23779
- const [, braceGroup, singleExtension] = match;
23780
- if (braceGroup === undefined && singleExtension === undefined)
23781
- return true;
23782
- const patternExtensions = new Set((braceGroup !== undefined ? braceGroup.split(",") : [singleExtension]).map((ext) => ext.trim().replace(/^\./, "").toLowerCase()).filter(Boolean));
23783
- const scannedExtensions = new Set((definition.entries ?? []).map((entry) => path.extname(entry.sourcePath).slice(1).toLowerCase()).filter(Boolean));
23784
- if (scannedExtensions.size === 0)
23785
- return false;
23786
- for (const ext of scannedExtensions) {
23787
- if (!patternExtensions.has(ext))
23788
- return false;
23789
- }
23790
- return true;
24075
+ function resolveNuaVersion() {
24076
+ const cliPkgPath = new URL("../package.json", import.meta.url);
24077
+ const cliPkg = JSON.parse(readFileSync(cliPkgPath, "utf-8"));
24078
+ return `^${cliPkg.version}`;
23791
24079
  }
23792
- async function scanCollections(fs, contentDir = "src/content", parseCache = new Map) {
23793
- const collections = {};
23794
- const entries = await fs.list(contentDir);
23795
- const scanPromises = entries.filter((entry) => entry.isDirectory && !entry.name.startsWith("_") && !entry.name.startsWith(".")).map(async (entry) => {
23796
- try {
23797
- const collectionPath = path.join(contentDir, entry.name);
23798
- const definition = await scanCollection(fs, collectionPath, entry.name, contentDir) ?? await scanDataCollection(fs, collectionPath, entry.name, contentDir);
23799
- if (definition) {
23800
- collections[entry.name] = definition;
24080
+ function transformPackageJson(pkg, nuaVersion) {
24081
+ const result = structuredClone(pkg);
24082
+ if (result.scripts) {
24083
+ for (const [key, value] of Object.entries(result.scripts)) {
24084
+ if (typeof value === "string") {
24085
+ result.scripts[key] = value.replace(/\bastro build\b/g, "nua build").replace(/\bastro dev\b/g, "nua dev").replace(/\bastro preview\b/g, "nua preview");
23801
24086
  }
23802
- } catch (error) {
23803
- console.warn(`[cms] Skipping collection "${entry.name}" \u2014 scan failed:`, error);
23804
24087
  }
23805
- });
23806
- await Promise.all(scanPromises);
23807
- const parsed = await parseContentConfig(fs, parseCache);
23808
- const scannedNameByBasePath = new Map;
23809
- for (const [name, definition] of Object.entries(collections)) {
23810
- scannedNameByBasePath.set(normalizeBasePath(definition.path), name);
23811
24088
  }
23812
- const rekeyedScannedNames = new Map;
23813
- for (const [collectionName, parsedCollection] of parsed) {
23814
- if (collections[collectionName])
23815
- continue;
23816
- if (!parsedCollection.loaderBase || !parsedCollection.loaderPattern)
23817
- continue;
23818
- const scannedName = scannedNameByBasePath.get(normalizeBasePath(parsedCollection.loaderBase));
23819
- const scannedDefinition = scannedName ? collections[scannedName] : undefined;
23820
- if (scannedName && scannedDefinition && patternCoversWholeCollection(parsedCollection.loaderPattern, scannedDefinition)) {
23821
- delete collections[scannedName];
23822
- scannedDefinition.name = collectionName;
23823
- for (const other of Object.values(collections)) {
23824
- if (other.parentCollection === scannedName)
23825
- other.parentCollection = collectionName;
23826
- }
23827
- collections[collectionName] = scannedDefinition;
23828
- rekeyedScannedNames.set(scannedName, collectionName);
23829
- continue;
23830
- }
23831
- let definition;
23832
- try {
23833
- definition = await scanGlobCollection(fs, collectionName, parsedCollection.loaderBase, parsedCollection.loaderPattern, contentDir);
23834
- } catch (error) {
23835
- console.warn(`[cms] Skipping glob collection "${collectionName}" \u2014 scan failed:`, error);
24089
+ for (const field of ["dependencies", "devDependencies"]) {
24090
+ if (!result[field])
23836
24091
  continue;
24092
+ for (const name of NUA_PROVIDED_PACKAGES) {
24093
+ delete result[field][name];
23837
24094
  }
23838
- if (!definition)
23839
- continue;
23840
- const baseName = parsedCollection.loaderBase.replace(/[/\\]+$/, "").split(/[/\\]/).pop();
23841
- const parentName = !baseName ? undefined : collections[baseName] ? baseName : rekeyedScannedNames.get(baseName);
23842
- if (parentName && parentName !== collectionName) {
23843
- definition.parentCollection = parentName;
24095
+ if (Object.keys(result[field]).length === 0) {
24096
+ delete result[field];
23844
24097
  }
23845
- collections[collectionName] = definition;
23846
24098
  }
23847
- applyParsedConfig(collections, parsed);
23848
- detectReferenceFields(collections, parsed);
23849
- detectDerivedHrefFields(collections);
23850
- assignSemanticRoles(collections);
23851
- applyCollectionOrderBy(collections, parsed);
23852
- warnOnPathnameCollisions(collections);
23853
- return collections;
23854
- }
23855
- function warnOnPathnameCollisions(collections) {
23856
- for (const def of Object.values(collections)) {
23857
- if (!def.pathname || !def.entries)
23858
- continue;
23859
- const bySlug = new Map;
23860
- for (const entry of def.entries) {
23861
- const pathname = resolvePathnameFromSpec(def, entry.data);
23862
- if (!pathname)
23863
- continue;
23864
- const prev = bySlug.get(pathname);
23865
- if (prev) {
23866
- console.warn(`[cms] collection "${def.name}": pathname rule maps both "${prev}" and "${entry.slug}" to "${pathname}" \u2014 these entries will collide on one URL`);
23867
- } else {
23868
- bySlug.set(pathname, entry.slug);
23869
- }
23870
- }
24099
+ if (!result.dependencies)
24100
+ result.dependencies = {};
24101
+ if (!result.dependencies["@nuasite/nua"]) {
24102
+ result.dependencies["@nuasite/nua"] = nuaVersion;
23871
24103
  }
24104
+ result.dependencies = Object.fromEntries(Object.entries(result.dependencies).sort(([a], [b]) => a.localeCompare(b)));
24105
+ return result;
23872
24106
  }
23873
- var import_yaml, DATE_PATTERN, URL_PATTERN, IMAGE_EXTENSIONS, MAX_SELECT_OPTIONS = 10, TEXTAREA_MIN_LENGTH = 200, SIDEBAR_FIELD_NAMES, DIRECTIVE_PATTERN, FREE_TEXT_FIELD_NAMES, PUBLISH_TOGGLE_NAMES, PUBLISH_DATE_NAMES, TITLE_FALLBACK_FIELDS, FRONTMATTER_PATTERN, MAX_NESTED_FIELD_DEPTH = 16, HREF_SUFFIXES, WHOLE_DIRECTORY_PATTERN;
23874
- var init_collection_scanner = __esm(() => {
23875
- init_content_config_ast();
23876
- import_yaml = __toESM(require_dist(), 1);
23877
- DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
23878
- URL_PATTERN = /^(https?:\/\/|\/)/;
23879
- IMAGE_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|svg|avif)$/i;
23880
- SIDEBAR_FIELD_NAMES = new Set([
23881
- "title",
23882
- "date",
23883
- "pubdate",
23884
- "publishdate",
23885
- "draft",
23886
- "image",
23887
- "featuredimage",
23888
- "cover",
23889
- "coverimage",
23890
- "thumbnail",
23891
- "author"
23892
- ]);
23893
- DIRECTIVE_PATTERN = /^\s*@(position|group)\s+(.+)$/;
23894
- FREE_TEXT_FIELD_NAMES = new Set([
23895
- "title",
23896
- "name",
23897
- "description",
23898
- "summary",
23899
- "excerpt",
23900
- "subtitle",
23901
- "heading",
23902
- "headline",
23903
- "slug",
23904
- "alt",
23905
- "caption"
23906
- ]);
23907
- PUBLISH_TOGGLE_NAMES = new Set(["draft", "isdraft", "published", "ispublished", "unpublished"]);
23908
- PUBLISH_DATE_NAMES = new Set([
23909
- "date",
23910
- "pubdate",
23911
- "publishdate",
23912
- "publisheddate",
23913
- "publishedate",
23914
- "publishedat",
23915
- "datepublished"
23916
- ]);
23917
- TITLE_FALLBACK_FIELDS = ["title", "name", "label"];
23918
- FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
23919
- HREF_SUFFIXES = ["href", "url", "link", "slug", "path"];
23920
- WHOLE_DIRECTORY_PATTERN = /^(?:\*\*[/\\])*\*(?:\.(?:\{([^{}/\\]*)\}|([A-Za-z0-9]+)))?$/;
23921
- });
23922
-
23923
- // ../cms-core/src/component-registry.ts
23924
- var import_parser2;
23925
- var init_component_registry = __esm(() => {
23926
- import_parser2 = __toESM(require_lib(), 1);
23927
- });
23928
-
23929
- // ../cms-core/src/media/local.ts
23930
- function mimeFromExt(ext) {
23931
- return MIME_BY_EXT[ext] ?? "application/octet-stream";
23932
- }
23933
- var MIME_BY_EXT;
23934
- var init_local = __esm(() => {
23935
- MIME_BY_EXT = {
23936
- ".jpg": "image/jpeg",
23937
- ".jpeg": "image/jpeg",
23938
- ".png": "image/png",
23939
- ".gif": "image/gif",
23940
- ".webp": "image/webp",
23941
- ".avif": "image/avif",
23942
- ".svg": "image/svg+xml",
23943
- ".ico": "image/x-icon",
23944
- ".mp4": "video/mp4",
23945
- ".webm": "video/webm",
23946
- ".pdf": "application/pdf"
23947
- };
23948
- });
23949
-
23950
- // ../cms-core/src/handlers/entry-ops.ts
23951
- var import_yaml2;
23952
- var init_entry_ops = __esm(() => {
23953
- init_collection_scanner();
23954
- init_content_config_ast();
23955
- init_local();
23956
- import_yaml2 = __toESM(require_dist(), 1);
24107
+ async function init({ cwd = process.cwd(), dryRun = false, yes = false } = {}) {
24108
+ const configPath = findAstroConfig(cwd);
24109
+ if (!configPath) {
24110
+ console.error("No Astro config file found.");
24111
+ process.exit(1);
24112
+ }
24113
+ const configContent = readFileSync(configPath, "utf-8");
24114
+ if (configContent.includes("@nuasite/nua")) {
24115
+ console.log("This project already uses @nuasite/nua. Nothing to do.");
24116
+ return;
24117
+ }
24118
+ if (!configContent.includes("defineConfig")) {
24119
+ console.error("Could not find defineConfig in Astro config.");
24120
+ process.exit(1);
24121
+ }
24122
+ const pkgPath = join2(cwd, "package.json");
24123
+ if (!existsSync2(pkgPath)) {
24124
+ console.error("No package.json found.");
24125
+ process.exit(1);
24126
+ }
24127
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
24128
+ const managedImports = detectNuaManagedImports(configContent);
24129
+ const nuaVersion = resolveNuaVersion();
24130
+ const configName = basename(configPath);
24131
+ console.log("");
24132
+ console.log("nua init \u2014 adopt the Nua toolchain");
24133
+ console.log("");
24134
+ console.log(` ${configName}`);
24135
+ console.log(" - Replace astro/config with @nuasite/nua/config");
24136
+ if (managedImports.size > 0) {
24137
+ console.log(` - Remove Nua-managed imports: ${[...managedImports.keys()].join(", ")}`);
24138
+ console.log(" - Remove managed integration/plugin calls");
24139
+ }
24140
+ console.log(" - Clean up empty config structures");
24141
+ console.log("");
24142
+ console.log(" package.json");
24143
+ const removable = NUA_PROVIDED_PACKAGES.filter((name) => pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
24144
+ if (removable.length > 0) {
24145
+ console.log(` - Remove Nua-provided deps: ${removable.join(", ")}`);
24146
+ }
24147
+ console.log(` - Add @nuasite/nua ${nuaVersion}`);
24148
+ console.log(" - Update scripts: astro \u2192 nua");
24149
+ if (dryRun) {
24150
+ console.log("");
24151
+ console.log(" (--dry-run: no changes made)");
24152
+ console.log("");
24153
+ return;
24154
+ }
24155
+ if (!yes) {
24156
+ console.log("");
24157
+ const answer = prompt("Proceed? [y/N] ");
24158
+ if (answer?.toLowerCase() !== "y") {
24159
+ console.log("Cancelled.");
24160
+ return;
24161
+ }
24162
+ }
24163
+ const newConfig = transformConfig(configContent, managedImports);
24164
+ writeFileSync(configPath, newConfig);
24165
+ console.log(` Updated ${configName}`);
24166
+ const newPkg = transformPackageJson(pkg, nuaVersion);
24167
+ writeFileSync(pkgPath, JSON.stringify(newPkg, null, "\t") + `
24168
+ `);
24169
+ console.log(" Updated package.json");
24170
+ console.log("");
24171
+ console.log("Next steps:");
24172
+ console.log(" 1. bun install");
24173
+ console.log(" 2. Review the updated config");
24174
+ console.log(" 3. nua dev");
24175
+ console.log("");
24176
+ }
24177
+ var NUA_PROVIDED_PACKAGES, NUA_MANAGED_PACKAGES;
24178
+ var init_init = __esm(() => {
24179
+ init_utils();
24180
+ NUA_PROVIDED_PACKAGES = [
24181
+ "@astrojs/mdx",
24182
+ "@astrojs/sitemap",
24183
+ "@tailwindcss/vite",
24184
+ "tailwindcss"
24185
+ ];
24186
+ NUA_MANAGED_PACKAGES = [
24187
+ "@astrojs/mdx",
24188
+ "@astrojs/sitemap",
24189
+ "@tailwindcss/vite"
24190
+ ];
23957
24191
  });
23958
24192
 
23959
- // ../cms-core/src/handlers/page-ops.ts
23960
- var init_page_ops = () => {};
23961
- // ../cms-core/src/core.ts
23962
- var init_core = __esm(() => {
23963
- init_collection_scanner();
23964
- init_component_registry();
23965
- init_entry_ops();
23966
- init_page_ops();
24193
+ // src/clean.ts
24194
+ var exports_clean = {};
24195
+ __export(exports_clean, {
24196
+ transformPackageJson: () => transformPackageJson2,
24197
+ transformConfig: () => transformConfig2,
24198
+ detectDisabledFeatures: () => detectDisabledFeatures,
24199
+ clean: () => clean
23967
24200
  });
23968
-
23969
- // ../cms-core/src/fs/glob.ts
23970
- function globToRegExp(glob) {
23971
- let re = "";
23972
- for (let i = 0;i < glob.length; i++) {
23973
- const c = glob[i];
23974
- if (c === "*") {
23975
- if (glob[i + 1] === "*") {
23976
- re += ".*";
23977
- i++;
23978
- if (glob[i + 1] === "/")
23979
- i++;
23980
- } else {
23981
- re += "[^/]*";
23982
- }
23983
- } else if (c === "?") {
23984
- re += "[^/]";
23985
- } else if (c === "{") {
23986
- const end = glob.indexOf("}", i);
23987
- if (end === -1) {
23988
- re += "\\{";
23989
- } else {
23990
- const opts = glob.slice(i + 1, end).split(",").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&"));
23991
- re += `(?:${opts.join("|")})`;
23992
- i = end;
23993
- }
23994
- } else if (".+^$()|[]\\".includes(c)) {
23995
- re += `\\${c}`;
23996
- } else {
23997
- re += c;
24201
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
24202
+ import { basename as basename2, join as join3 } from "path";
24203
+ function detectDisabledFeatures(content) {
24204
+ const disabled = new Set;
24205
+ for (const key of FEATURE_KEYS) {
24206
+ if (new RegExp(`\\b${key}\\s*:\\s*false\\b`).test(content)) {
24207
+ disabled.add(key);
23998
24208
  }
23999
24209
  }
24000
- return new RegExp(`^${re}$`);
24210
+ return disabled;
24001
24211
  }
24002
-
24003
- // ../cms-core/src/fs/node-fs.ts
24004
- import fs from "fs/promises";
24005
- import path2 from "path";
24006
- function resolveWithinRoot(root, filePath) {
24007
- const resolvedRoot = path2.resolve(root);
24008
- const isAbsoluteFs = filePath.startsWith(resolvedRoot);
24009
- const normalizedPath = !isAbsoluteFs && filePath.startsWith("/") ? filePath.slice(1) : filePath;
24010
- const fullPath = path2.isAbsolute(normalizedPath) ? path2.resolve(normalizedPath) : path2.resolve(resolvedRoot, normalizedPath);
24011
- if (!fullPath.startsWith(resolvedRoot + path2.sep) && fullPath !== resolvedRoot) {
24012
- throw new Error(`Path traversal detected: ${filePath}`);
24212
+ function prependToArrayProperty(lines, property, items) {
24213
+ const pattern = new RegExp(`\\b${property}\\s*:\\s*\\[`);
24214
+ for (let i = 0;i < lines.length; i++) {
24215
+ if (pattern.test(lines[i])) {
24216
+ lines[i] = lines[i].replace(new RegExp(`(\\b${property}\\s*:\\s*\\[)`), `$1${items}, `);
24217
+ return true;
24218
+ }
24013
24219
  }
24014
- return fullPath;
24220
+ return false;
24015
24221
  }
24016
- function isNodeError(error) {
24017
- return error instanceof Error && "code" in error;
24222
+ function transformConfig2(content, disabled) {
24223
+ const userImports = content.split(`
24224
+ `).filter((line) => /^\s*import\s/.test(line)).filter((line) => !line.includes("@nuasite/")).filter((line) => !line.includes("defineConfig"));
24225
+ let body = extractConfigBody(content);
24226
+ body = removeProperty(body, "nua");
24227
+ if (content.includes("@nuasite/nua/integration")) {
24228
+ body = body.replace(/\bnua\s*\([^)]*\)\s*,?\s*/g, "");
24229
+ body = body.replace(/\bintegrations\s*:\s*\[\s*,?\s*\]\s*,?/g, "");
24230
+ }
24231
+ const imports = [`import { defineConfig } from 'astro/config'`];
24232
+ if (!disabled.has("tailwindcss"))
24233
+ imports.push(`import tailwindcss from '@tailwindcss/vite'`);
24234
+ if (!disabled.has("mdx"))
24235
+ imports.push(`import mdx from '@astrojs/mdx'`);
24236
+ if (!disabled.has("sitemap"))
24237
+ imports.push(`import sitemap from '@astrojs/sitemap'`);
24238
+ imports.push(...userImports);
24239
+ const integrationCalls = [];
24240
+ if (!disabled.has("mdx"))
24241
+ integrationCalls.push("mdx()");
24242
+ if (!disabled.has("sitemap"))
24243
+ integrationCalls.push("sitemap()");
24244
+ const bodyLines = body.split(`
24245
+ `).filter((line) => line.trim() !== "");
24246
+ const hasIntegrations = bodyLines.some((line) => /^\s*integrations\s*:/.test(line));
24247
+ const hasVite = bodyLines.some((line) => /^\s*vite\s*:/.test(line));
24248
+ if (hasIntegrations && integrationCalls.length > 0) {
24249
+ prependToArrayProperty(bodyLines, "integrations", integrationCalls.join(", "));
24250
+ }
24251
+ if (!disabled.has("tailwindcss") && hasVite) {
24252
+ prependToArrayProperty(bodyLines, "plugins", "tailwindcss()");
24253
+ }
24254
+ const newPropLines = [];
24255
+ if (!disabled.has("tailwindcss") && !hasVite) {
24256
+ newPropLines.push("\tvite: {", "\t\tbuild: {", "\t\t\tsourcemap: true,", "\t\t},", "\t\tplugins: [tailwindcss()],", "\t},");
24257
+ }
24258
+ if (!hasIntegrations && integrationCalls.length > 0) {
24259
+ newPropLines.push(` integrations: [${integrationCalls.join(", ")}],`);
24260
+ }
24261
+ const allLines = [...bodyLines, ...newPropLines];
24262
+ return assembleConfig(imports, allLines.join(`
24263
+ `));
24018
24264
  }
24019
- async function walkFiles(absRoot, absDir) {
24020
- let dirEntries;
24021
- try {
24022
- dirEntries = await fs.readdir(absDir, { withFileTypes: true });
24023
- } catch {
24024
- return [];
24265
+ function transformPackageJson2(pkg, disabled, usedRuntimePackages = []) {
24266
+ const result = structuredClone(pkg);
24267
+ const nuaVersion = result.dependencies?.["@nuasite/nua"] ?? result.devDependencies?.["@nuasite/nua"];
24268
+ if (result.scripts) {
24269
+ for (const [key, value] of Object.entries(result.scripts)) {
24270
+ if (typeof value === "string") {
24271
+ result.scripts[key] = value.replace(/\bnua build\b/g, "astro build").replace(/\bnua dev\b/g, "astro dev").replace(/\bnua preview\b/g, "astro preview");
24272
+ }
24273
+ }
24025
24274
  }
24026
- const out = [];
24027
- for (const entry of dirEntries) {
24028
- const abs = path2.join(absDir, entry.name);
24029
- if (entry.isDirectory()) {
24030
- out.push(...await walkFiles(absRoot, abs));
24031
- } else if (entry.isFile()) {
24032
- out.push(path2.relative(absRoot, abs).split(path2.sep).join("/"));
24275
+ for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
24276
+ if (!result[field])
24277
+ continue;
24278
+ for (const name of NUASITE_TOOLING) {
24279
+ delete result[field][name];
24280
+ }
24281
+ if (Object.keys(result[field]).length === 0) {
24282
+ delete result[field];
24033
24283
  }
24034
24284
  }
24035
- return out;
24036
- }
24037
- function staticPrefixDir(pattern) {
24038
- const segments = pattern.split("/");
24039
- const staticSegments = [];
24040
- for (const segment of segments) {
24041
- if (/[*?{}[\]]/.test(segment))
24042
- break;
24043
- staticSegments.push(segment);
24285
+ if (!result.dependencies)
24286
+ result.dependencies = {};
24287
+ for (const [name, version] of Object.entries(PACKAGES_TO_ADD)) {
24288
+ if (result.dependencies[name])
24289
+ continue;
24290
+ if (name === "@astrojs/mdx" && disabled.has("mdx"))
24291
+ continue;
24292
+ if (name === "@astrojs/sitemap" && disabled.has("sitemap"))
24293
+ continue;
24294
+ if (name === "@tailwindcss/vite" && disabled.has("tailwindcss"))
24295
+ continue;
24296
+ if (name === "tailwindcss" && disabled.has("tailwindcss"))
24297
+ continue;
24298
+ result.dependencies[name] = version;
24044
24299
  }
24045
- if (staticSegments.length === segments.length) {
24046
- staticSegments.pop();
24300
+ for (const name of usedRuntimePackages) {
24301
+ if (!result.dependencies[name]) {
24302
+ result.dependencies[name] = nuaVersion ?? "^0.16.0";
24303
+ }
24047
24304
  }
24048
- return staticSegments.join("/");
24305
+ result.dependencies = Object.fromEntries(Object.entries(result.dependencies).sort(([a], [b]) => a.localeCompare(b)));
24306
+ return result;
24049
24307
  }
24050
- function createNodeFs(root) {
24051
- const resolvedRoot = path2.resolve(root);
24052
- const resolve = (p) => resolveWithinRoot(resolvedRoot, p);
24053
- return {
24054
- async readFile(filePath) {
24055
- return fs.readFile(resolve(filePath), "utf-8");
24056
- },
24057
- async readBytes(filePath) {
24058
- return fs.readFile(resolve(filePath));
24059
- },
24060
- async writeFile(filePath, content) {
24061
- const fullPath = resolve(filePath);
24062
- await fs.mkdir(path2.dirname(fullPath), { recursive: true });
24063
- const tempPath = `${fullPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
24064
- try {
24065
- await fs.writeFile(tempPath, content, "utf-8");
24066
- await fs.rename(tempPath, fullPath);
24067
- } catch (error) {
24068
- await fs.rm(tempPath, { force: true });
24069
- throw error;
24070
- }
24071
- },
24072
- async rename(from, to) {
24073
- const fullTo = resolve(to);
24074
- await fs.mkdir(path2.dirname(fullTo), { recursive: true });
24075
- await fs.rename(resolve(from), fullTo);
24076
- },
24077
- async remove(filePath) {
24078
- await fs.rm(resolve(filePath), { force: true });
24079
- },
24080
- async exists(filePath) {
24081
- try {
24082
- await fs.access(resolve(filePath));
24083
- return true;
24084
- } catch {
24085
- return false;
24086
- }
24087
- },
24088
- async list(dir) {
24308
+ function scanForNuasiteUsage(cwd) {
24309
+ const srcDir = join3(cwd, "src");
24310
+ if (!existsSync3(srcDir))
24311
+ return [];
24312
+ const results = [];
24313
+ try {
24314
+ const files = readdirSync(srcDir, { recursive: true });
24315
+ for (const entry of files) {
24316
+ const fileName = String(entry);
24317
+ if (!/\.(astro|ts|tsx|js|jsx)$/.test(fileName))
24318
+ continue;
24089
24319
  try {
24090
- const entries = await fs.readdir(resolve(dir), { withFileTypes: true });
24091
- return entries.map((entry) => ({ name: entry.name, isDirectory: entry.isDirectory() }));
24092
- } catch (error) {
24093
- if (isNodeError(error) && error.code === "ENOENT")
24094
- return [];
24095
- throw error;
24096
- }
24097
- },
24098
- async glob(pattern) {
24099
- const baseRel = staticPrefixDir(pattern);
24100
- const absBase = resolve(baseRel);
24101
- const matcher = globToRegExp(pattern);
24102
- const files = await walkFiles(resolvedRoot, absBase);
24103
- return files.filter((rel) => matcher.test(rel));
24104
- },
24105
- async stat(filePath) {
24106
- const s = await fs.stat(resolve(filePath));
24107
- return { mtimeMs: s.mtimeMs, size: s.size };
24320
+ const content = readFileSync2(join3(srcDir, fileName), "utf-8");
24321
+ const matches = content.match(/@nuasite\/[\w-]+/g);
24322
+ if (matches) {
24323
+ results.push({
24324
+ file: join3("src", fileName),
24325
+ packages: [...new Set(matches)]
24326
+ });
24327
+ }
24328
+ } catch {}
24108
24329
  }
24109
- };
24330
+ } catch {}
24331
+ return results;
24110
24332
  }
24111
- var init_node_fs = () => {};
24112
- // ../cms-core/src/media/project-images.ts
24113
- function uploadsDirRelativeToRoot(dir, root) {
24114
- if (dir === undefined)
24333
+ async function clean({ cwd = process.cwd(), dryRun = false, yes = false } = {}) {
24334
+ const configPath = findAstroConfig(cwd);
24335
+ if (!configPath) {
24336
+ console.error("No Astro config file found.");
24337
+ process.exit(1);
24338
+ }
24339
+ const configContent = readFileSync2(configPath, "utf-8");
24340
+ if (!configContent.includes("@nuasite/nua")) {
24341
+ console.log("This project does not use @nuasite/nua. Nothing to clean.");
24115
24342
  return;
24116
- const base = root.replace(/\/+$/, "");
24117
- const trimmed = dir.replace(/\/+$/, "");
24118
- if (trimmed === base)
24119
- return "";
24120
- if (trimmed.startsWith(`${base}/`))
24121
- return trimmed.slice(base.length + 1);
24122
- return trimmed.replace(/^\.\//, "").replace(/^\/+/, "");
24123
- }
24124
- async function listProjectImages(fs2, options) {
24125
- const normalized = options?.exclude ? uploadsDirRelativeToRoot(options.exclude.dir, options.exclude.root) : options?.excludeDir?.replace(/^\/+/, "").replace(/\/+$/, "");
24126
- const excludeDir = normalized ? normalized : null;
24127
- const scanDirs = [
24128
- { dir: "public", relativeToRoot: false },
24129
- { dir: "src", relativeToRoot: true }
24130
- ];
24131
- const results = await Promise.all(scanDirs.map(async ({ dir, relativeToRoot }) => {
24132
- const items2 = [];
24133
- await scanDirectory(fs2, dir, dir, relativeToRoot, excludeDir, items2);
24134
- return items2;
24135
- }));
24136
- const items = results.flat();
24137
- items.sort((a, b) => a.filename.localeCompare(b.filename) || compareCodeUnits(a.url, b.url));
24138
- return items;
24139
- }
24140
- function compareCodeUnits(a, b) {
24141
- return a < b ? -1 : a > b ? 1 : 0;
24142
- }
24143
- async function scanDirectory(fs2, currentDir, baseDir, relativeToRoot, excludeDir, items) {
24144
- if (excludeDir !== null && currentDir === excludeDir)
24343
+ }
24344
+ const pkgPath = join3(cwd, "package.json");
24345
+ if (!existsSync3(pkgPath)) {
24346
+ console.error("No package.json found.");
24347
+ process.exit(1);
24348
+ }
24349
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
24350
+ const disabled = detectDisabledFeatures(configContent);
24351
+ const nuasiteUsage = scanForNuasiteUsage(cwd);
24352
+ const configName = basename2(configPath);
24353
+ const usedRuntimePackages = new Set;
24354
+ const toolingUsage = [];
24355
+ for (const entry of nuasiteUsage) {
24356
+ const runtime = entry.packages.filter((p) => !NUASITE_TOOLING.includes(p));
24357
+ const tooling = entry.packages.filter((p) => NUASITE_TOOLING.includes(p));
24358
+ for (const p of runtime)
24359
+ usedRuntimePackages.add(p);
24360
+ if (tooling.length > 0) {
24361
+ toolingUsage.push({ file: entry.file, packages: tooling });
24362
+ }
24363
+ }
24364
+ console.log("");
24365
+ console.log("nua clean \u2014 eject to standard Astro project");
24366
+ console.log("");
24367
+ console.log(` ${configName}`);
24368
+ console.log(" - Replace @nuasite/nua with explicit Astro integrations");
24369
+ console.log(" - Add mdx, sitemap, tailwindcss imports");
24370
+ if (disabled.size > 0) {
24371
+ console.log(` - Skipping disabled: ${[...disabled].join(", ")}`);
24372
+ }
24373
+ console.log("");
24374
+ console.log(" package.json");
24375
+ console.log(" - Remove @nuasite/* tooling dependencies");
24376
+ if (usedRuntimePackages.size > 0) {
24377
+ console.log(` - Keep as explicit deps: ${[...usedRuntimePackages].join(", ")}`);
24378
+ }
24379
+ console.log(" - Add standard Astro packages");
24380
+ console.log(" - Update scripts: nua \u2192 astro");
24381
+ if (toolingUsage.length > 0) {
24382
+ console.log("");
24383
+ console.log(" Warning: @nuasite tooling imports found in source files:");
24384
+ for (const { file, packages } of toolingUsage) {
24385
+ console.log(` ${file} (${packages.join(", ")})`);
24386
+ }
24387
+ console.log(" These will need manual removal.");
24388
+ }
24389
+ if (dryRun) {
24390
+ console.log("");
24391
+ console.log(" (--dry-run: no changes made)");
24392
+ console.log("");
24145
24393
  return;
24146
- const entries = await fs2.list(currentDir);
24147
- const subdirs = [];
24148
- for (const entry of entries) {
24149
- if (entry.name.startsWith(".") || entry.name === "node_modules")
24150
- continue;
24151
- const fullPath = `${currentDir}/${entry.name}`;
24152
- if (entry.isDirectory) {
24153
- subdirs.push(scanDirectory(fs2, fullPath, baseDir, relativeToRoot, excludeDir, items));
24154
- } else {
24155
- const dotIdx = entry.name.lastIndexOf(".");
24156
- const ext = dotIdx >= 0 ? entry.name.slice(dotIdx).toLowerCase() : "";
24157
- if (!IMAGE_EXTENSIONS2.has(ext))
24158
- continue;
24159
- const url = relativeToRoot ? `/${fullPath}` : `/${fullPath.slice(baseDir.length + 1)}`;
24160
- items.push({
24161
- id: `project:${url}`,
24162
- url,
24163
- filename: entry.name,
24164
- contentType: mimeFromExt(ext)
24165
- });
24394
+ }
24395
+ if (!yes) {
24396
+ console.log("");
24397
+ const answer = prompt("Proceed? [y/N] ");
24398
+ if (answer?.toLowerCase() !== "y") {
24399
+ console.log("Cancelled.");
24400
+ return;
24166
24401
  }
24167
24402
  }
24168
- await Promise.all(subdirs);
24403
+ const newConfig = transformConfig2(configContent, disabled);
24404
+ writeFileSync2(configPath, newConfig);
24405
+ console.log(` Updated ${configName}`);
24406
+ const newPkg = transformPackageJson2(pkg, disabled, [...usedRuntimePackages]);
24407
+ writeFileSync2(pkgPath, JSON.stringify(newPkg, null, "\t") + `
24408
+ `);
24409
+ console.log(" Updated package.json");
24410
+ console.log("");
24411
+ console.log("Next steps:");
24412
+ console.log(" 1. bun install");
24413
+ console.log(" 2. Review the updated config");
24414
+ console.log(" 3. astro dev");
24415
+ if (toolingUsage.length > 0) {
24416
+ console.log(" 4. Remove @nuasite tooling imports from source files");
24417
+ }
24418
+ console.log("");
24169
24419
  }
24170
- var IMAGE_EXTENSIONS2;
24171
- var init_project_images = __esm(() => {
24172
- init_local();
24173
- IMAGE_EXTENSIONS2 = new Set(Object.entries(MIME_BY_EXT).filter(([, mime]) => mime.startsWith("image/")).map(([ext]) => ext));
24174
- });
24175
-
24176
- // ../cms-core/src/media/s3.ts
24177
- var init_s3 = __esm(() => {
24178
- init_local();
24179
- });
24180
-
24181
- // ../cms-core/src/media/index.ts
24182
- var init_media = __esm(() => {
24183
- init_local();
24184
- init_project_images();
24185
- init_s3();
24186
- });
24187
-
24188
- // ../cms-core/src/project-config-ast.ts
24189
- var import_parser3;
24190
- var init_project_config_ast = __esm(() => {
24191
- import_parser3 = __toESM(require_lib(), 1);
24192
- });
24193
-
24194
- // ../cms-core/src/index.ts
24195
- var init_src2 = __esm(() => {
24196
- init_collection_scanner();
24197
- init_component_registry();
24198
- init_content_config_ast();
24199
- init_core();
24200
- init_node_fs();
24201
- init_entry_ops();
24202
- init_media();
24203
- init_project_config_ast();
24420
+ var FEATURE_KEYS, NUASITE_TOOLING, PACKAGES_TO_ADD;
24421
+ var init_clean = __esm(() => {
24422
+ init_utils();
24423
+ FEATURE_KEYS = ["cms", "pageMarkdown", "mdx", "sitemap", "tailwindcss", "checks"];
24424
+ NUASITE_TOOLING = [
24425
+ "@nuasite/nua",
24426
+ "@nuasite/core",
24427
+ "@nuasite/cli",
24428
+ "@nuasite/cms",
24429
+ "@nuasite/llm-enhancements",
24430
+ "@nuasite/checks",
24431
+ "@nuasite/agent-summary"
24432
+ ];
24433
+ PACKAGES_TO_ADD = {
24434
+ astro: "^6.0.2",
24435
+ "@astrojs/check": "^0.9.7",
24436
+ "@astrojs/mdx": "^5.0.0",
24437
+ "@astrojs/sitemap": "^3.7.1",
24438
+ "@tailwindcss/vite": "^4.2.1",
24439
+ tailwindcss: "^4.2.1",
24440
+ typescript: "^5"
24441
+ };
24204
24442
  });
24205
24443
 
24206
24444
  // ../cms/src/config.ts
@@ -24212,7 +24450,7 @@ var init_config = () => {};
24212
24450
 
24213
24451
  // ../cms/src/component-registry.ts
24214
24452
  import fs2 from "fs/promises";
24215
- import path3 from "path";
24453
+ import path4 from "path";
24216
24454
 
24217
24455
  class ComponentRegistry {
24218
24456
  components = new Map;
@@ -24222,7 +24460,7 @@ class ComponentRegistry {
24222
24460
  }
24223
24461
  async scan() {
24224
24462
  for (const dir of this.componentDirs) {
24225
- const fullPath = path3.join(getProjectRoot(), dir);
24463
+ const fullPath = path4.join(getProjectRoot(), dir);
24226
24464
  try {
24227
24465
  await this.scanDirectory(fullPath, dir);
24228
24466
  } catch {}
@@ -24237,8 +24475,8 @@ class ComponentRegistry {
24237
24475
  async scanDirectory(dir, relativePath) {
24238
24476
  const entries = await fs2.readdir(dir, { withFileTypes: true });
24239
24477
  for (const entry of entries) {
24240
- const fullPath = path3.join(dir, entry.name);
24241
- const relPath = path3.join(relativePath, entry.name);
24478
+ const fullPath = path4.join(dir, entry.name);
24479
+ const relPath = path4.join(relativePath, entry.name);
24242
24480
  if (entry.isDirectory()) {
24243
24481
  await this.scanDirectory(fullPath, relPath);
24244
24482
  } else if (entry.isFile() && entry.name.endsWith(".astro")) {
@@ -24249,7 +24487,7 @@ class ComponentRegistry {
24249
24487
  async parseComponent(filePath, relativePath) {
24250
24488
  try {
24251
24489
  const content = await fs2.readFile(filePath, "utf-8");
24252
- const componentName = path3.basename(filePath, ".astro");
24490
+ const componentName = path4.basename(filePath, ".astro");
24253
24491
  const props = await this.extractProps(content);
24254
24492
  const slots = this.extractSlots(content);
24255
24493
  const description = this.extractDescription(content);
@@ -24504,7 +24742,7 @@ function slugify2(text) {
24504
24742
 
24505
24743
  // ../cms/src/utils.ts
24506
24744
  import { createHash } from "crypto";
24507
- import path4 from "path";
24745
+ import path5 from "path";
24508
24746
  function normalizePagePath(url) {
24509
24747
  let pathname;
24510
24748
  try {
@@ -24557,21 +24795,21 @@ function escapeReplacement(str) {
24557
24795
  return str.replace(/\$/g, "$$$$");
24558
24796
  }
24559
24797
  function resolveSourcePath(sourcePath) {
24560
- return path4.isAbsolute(sourcePath) ? sourcePath : path4.join(getProjectRoot(), sourcePath);
24798
+ return path5.isAbsolute(sourcePath) ? sourcePath : path5.join(getProjectRoot(), sourcePath);
24561
24799
  }
24562
24800
  function resolveAndValidatePath(filePath) {
24563
24801
  const projectRoot = getProjectRoot();
24564
- const resolvedRoot = path4.resolve(projectRoot);
24802
+ const resolvedRoot = path5.resolve(projectRoot);
24565
24803
  const isAbsoluteFs = filePath.startsWith(resolvedRoot);
24566
24804
  const normalizedPath = !isAbsoluteFs && filePath.startsWith("/") ? filePath.slice(1) : filePath;
24567
- const fullPath = path4.isAbsolute(normalizedPath) ? path4.resolve(normalizedPath) : path4.resolve(projectRoot, normalizedPath);
24568
- if (!fullPath.startsWith(resolvedRoot + path4.sep) && fullPath !== resolvedRoot) {
24805
+ const fullPath = path5.isAbsolute(normalizedPath) ? path5.resolve(normalizedPath) : path5.resolve(projectRoot, normalizedPath);
24806
+ if (!fullPath.startsWith(resolvedRoot + path5.sep) && fullPath !== resolvedRoot) {
24569
24807
  throw new Error(`Path traversal detected: ${filePath}`);
24570
24808
  }
24571
24809
  return fullPath;
24572
24810
  }
24573
24811
  async function acquireFileLock(filePath) {
24574
- const key = path4.resolve(filePath);
24812
+ const key = path5.resolve(filePath);
24575
24813
  while (fileLocks.has(key)) {
24576
24814
  await fileLocks.get(key);
24577
24815
  }
@@ -24589,8 +24827,8 @@ function escapeHtml2(text) {
24589
24827
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
24590
24828
  }
24591
24829
  function relativeImportPath2(fromFile, toFile) {
24592
- const fromDir = path4.dirname(fromFile);
24593
- let rel = path4.relative(fromDir, toFile).split(path4.sep).join("/");
24830
+ const fromDir = path5.dirname(fromFile);
24831
+ let rel = path5.relative(fromDir, toFile).split(path5.sep).join("/");
24594
24832
  if (!rel.startsWith("."))
24595
24833
  rel = `./${rel}`;
24596
24834
  return rel;
@@ -24603,7 +24841,7 @@ var init_utils2 = __esm(() => {
24603
24841
 
24604
24842
  // ../cms/src/handlers/component-ops.ts
24605
24843
  import fs3 from "fs/promises";
24606
- import path5 from "path";
24844
+ import path6 from "path";
24607
24845
  async function handleInsertComponent(request, manifestWriter) {
24608
24846
  const { position, referenceComponentId, componentName, props, meta } = request;
24609
24847
  if (!meta?.url) {
@@ -24821,7 +25059,7 @@ function getComponentOccurrenceIndex(manifest, referenceComponent) {
24821
25059
  async function findComponentInvocationFile(projectRoot, pageUrl, manifest, referenceComponent) {
24822
25060
  if (referenceComponent.invocationSourcePath) {
24823
25061
  const filePath = normalizeFilePath(referenceComponent.invocationSourcePath);
24824
- const fullPath = path5.resolve(projectRoot, filePath);
25062
+ const fullPath = path6.resolve(projectRoot, filePath);
24825
25063
  try {
24826
25064
  const content = await fs3.readFile(fullPath, "utf-8");
24827
25065
  const lines = content.split(`
@@ -24835,7 +25073,7 @@ async function findComponentInvocationFile(projectRoot, pageUrl, manifest, refer
24835
25073
  const candidates = getPageFileCandidates(pageUrl);
24836
25074
  const occurrenceIndex = getComponentOccurrenceIndex(manifest, referenceComponent);
24837
25075
  for (const candidate of candidates) {
24838
- const fullPath = path5.resolve(projectRoot, candidate);
25076
+ const fullPath = path6.resolve(projectRoot, candidate);
24839
25077
  try {
24840
25078
  const content = await fs3.readFile(fullPath, "utf-8");
24841
25079
  const lines = content.split(`
@@ -24895,7 +25133,7 @@ function getIndentation(line) {
24895
25133
  function normalizeFilePath(p) {
24896
25134
  if (!p.startsWith("/"))
24897
25135
  return p;
24898
- const projectRoot = path5.resolve(getProjectRoot());
25136
+ const projectRoot = path6.resolve(getProjectRoot());
24899
25137
  if (p.startsWith(projectRoot))
24900
25138
  return p;
24901
25139
  return p.slice(1);
@@ -25303,45 +25541,45 @@ var init_array_ops = __esm(() => {
25303
25541
  // ../cms/src/astro-image-paths.ts
25304
25542
  import { createHash as createHash2 } from "crypto";
25305
25543
  import fs5 from "fs/promises";
25306
- import path6 from "path";
25544
+ import path7 from "path";
25307
25545
  function isHugoStyleEntry(entryAbsPath) {
25308
- return HUGO_INDEX_RE.test(path6.basename(entryAbsPath));
25546
+ return HUGO_INDEX_RE.test(path7.basename(entryAbsPath));
25309
25547
  }
25310
25548
  function shortContentHash(buf) {
25311
25549
  return createHash2("sha256").update(buf).digest("hex").slice(0, 8);
25312
25550
  }
25313
25551
  async function pickAstroImageTarget(args) {
25314
- const entryDir = path6.dirname(args.entryAbsPath);
25552
+ const entryDir = path7.dirname(args.entryAbsPath);
25315
25553
  const isHugoStyle = isHugoStyleEntry(args.entryAbsPath);
25316
- const safeFilename = path6.basename(args.originalFilename);
25554
+ const safeFilename = path7.basename(args.originalFilename);
25317
25555
  if (!safeFilename || safeFilename === "." || safeFilename === "..") {
25318
25556
  throw new Error(`Invalid filename: ${args.originalFilename}`);
25319
25557
  }
25320
25558
  const baseName = isHugoStyle ? safeFilename : `${args.slug}-${safeFilename}`;
25321
- const baseAbs = path6.join(entryDir, baseName);
25559
+ const baseAbs = path7.join(entryDir, baseName);
25322
25560
  if (await isFreeOrMatching(baseAbs, args.compareBuffer)) {
25323
25561
  return { absPath: baseAbs, relPath: `./${baseName}` };
25324
25562
  }
25325
25563
  const candidateAbs = await pickHashedSibling(entryDir, baseName, args.compareBuffer);
25326
- return { absPath: candidateAbs, relPath: `./${path6.basename(candidateAbs)}` };
25564
+ return { absPath: candidateAbs, relPath: `./${path7.basename(candidateAbs)}` };
25327
25565
  }
25328
25566
  async function pickSiblingTarget(dir, filename, buf) {
25329
- const safe = path6.basename(filename);
25567
+ const safe = path7.basename(filename);
25330
25568
  if (!safe || safe === "." || safe === "..") {
25331
25569
  throw new Error(`Invalid filename: ${filename}`);
25332
25570
  }
25333
- const baseAbs = path6.join(dir, safe);
25571
+ const baseAbs = path7.join(dir, safe);
25334
25572
  if (await isFreeOrMatching(baseAbs, buf))
25335
25573
  return baseAbs;
25336
25574
  return pickHashedSibling(dir, safe, buf);
25337
25575
  }
25338
25576
  async function pickHashedSibling(dir, baseName, buf) {
25339
25577
  const hash = shortContentHash(buf);
25340
- const ext = path6.extname(baseName);
25341
- const stem = path6.basename(baseName, ext);
25578
+ const ext = path7.extname(baseName);
25579
+ const stem = path7.basename(baseName, ext);
25342
25580
  for (let attempt = 0;attempt < 5; attempt++) {
25343
25581
  const suffix = attempt === 0 ? hash : `${hash}-${attempt}`;
25344
- const candidateAbs = path6.join(dir, `${stem}-${suffix}${ext}`);
25582
+ const candidateAbs = path7.join(dir, `${stem}-${suffix}${ext}`);
25345
25583
  if (await isFreeOrMatching(candidateAbs, buf))
25346
25584
  return candidateAbs;
25347
25585
  }
@@ -25365,7 +25603,7 @@ var init_astro_image_paths = __esm(() => {
25365
25603
 
25366
25604
  // ../cms/src/handlers/astro-image-upload.ts
25367
25605
  import fs6 from "fs/promises";
25368
- import path7 from "path";
25606
+ import path8 from "path";
25369
25607
  async function tryAstroImageUpload(args) {
25370
25608
  const { collection, entry, field } = args.context;
25371
25609
  if (!collection || !entry || !field)
@@ -25387,12 +25625,12 @@ async function tryAstroImageUpload(args) {
25387
25625
  originalFilename: args.originalFilename,
25388
25626
  compareBuffer: args.fileBuffer
25389
25627
  });
25390
- await fs6.mkdir(path7.dirname(target.absPath), { recursive: true });
25628
+ await fs6.mkdir(path8.dirname(target.absPath), { recursive: true });
25391
25629
  await fs6.writeFile(target.absPath, args.fileBuffer);
25392
25630
  return {
25393
25631
  success: true,
25394
25632
  url: target.relPath,
25395
- filename: path7.basename(target.absPath)
25633
+ filename: path8.basename(target.absPath)
25396
25634
  };
25397
25635
  }
25398
25636
  var init_astro_image_upload = __esm(() => {
@@ -31162,14 +31400,14 @@ var init_ast_parser = __esm(() => {
31162
31400
  });
31163
31401
 
31164
31402
  // ../cms/src/source-finder/collection-finder.ts
31165
- var import_yaml3, specPathnameIndexCache;
31403
+ var import_yaml4, specPathnameIndexCache;
31166
31404
  var init_collection_finder = __esm(() => {
31167
31405
  init_src2();
31168
31406
  init_config();
31169
31407
  init_scan_cache();
31170
31408
  init_cache();
31171
31409
  init_snippet_utils();
31172
- import_yaml3 = __toESM(require_dist(), 1);
31410
+ import_yaml4 = __toESM(require_dist(), 1);
31173
31411
  specPathnameIndexCache = new WeakMap;
31174
31412
  });
31175
31413
 
@@ -31228,7 +31466,7 @@ function extractAstroImageOriginalUrl(src) {
31228
31466
  } catch {}
31229
31467
  return;
31230
31468
  }
31231
- var import_yaml4;
31469
+ var import_yaml5;
31232
31470
  var init_snippet_utils = __esm(() => {
31233
31471
  init_astro_image_paths();
31234
31472
  init_config();
@@ -31238,12 +31476,12 @@ var init_snippet_utils = __esm(() => {
31238
31476
  init_cross_file_tracker();
31239
31477
  init_image_finder();
31240
31478
  init_search_index();
31241
- import_yaml4 = __toESM(require_dist(), 1);
31479
+ import_yaml5 = __toESM(require_dist(), 1);
31242
31480
  });
31243
31481
 
31244
31482
  // ../cms/src/handlers/source-writer.ts
31245
31483
  import fs8 from "fs/promises";
31246
- import path8 from "path";
31484
+ import path9 from "path";
31247
31485
  async function handleUpdate(request, manifestWriter) {
31248
31486
  const { changes, meta } = request;
31249
31487
  const errors = [];
@@ -31280,7 +31518,7 @@ async function handleUpdate(request, manifestWriter) {
31280
31518
  }
31281
31519
  if (appliedCount > 0 && newContent !== currentContent) {
31282
31520
  for (const op of fileOps) {
31283
- await fs8.mkdir(path8.dirname(op.target), { recursive: true });
31521
+ await fs8.mkdir(path9.dirname(op.target), { recursive: true });
31284
31522
  await fs8.writeFile(op.target, op.bytes);
31285
31523
  }
31286
31524
  await fs8.writeFile(fullPath, newContent, "utf-8");
@@ -31376,7 +31614,7 @@ async function applyImageChange(content, change, absFilePath, originUrl) {
31376
31614
  const yamlKeyMatch = change.sourceSnippet.match(/^\s*([\w][\w-]*):\s*/);
31377
31615
  if (yamlKeyMatch?.[1]) {
31378
31616
  try {
31379
- const parsed = import_yaml5.parse(change.sourceSnippet);
31617
+ const parsed = import_yaml6.parse(change.sourceSnippet);
31380
31618
  if (parsed && typeof parsed === "object") {
31381
31619
  const value = parsed[yamlKeyMatch[1]];
31382
31620
  if (typeof value === "string" && !srcCandidates.includes(value)) {
@@ -31546,8 +31784,8 @@ async function tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, o
31546
31784
  const resolved = await resolveNewSrcBytes(newSrc, originUrl);
31547
31785
  if (!resolved)
31548
31786
  return null;
31549
- const originalAssetAbs = path8.resolve(path8.dirname(absFilePath), importInfo.source);
31550
- const targetAbs = await pickSiblingTarget(path8.dirname(originalAssetAbs), resolved.filename, resolved.bytes);
31787
+ const originalAssetAbs = path9.resolve(path9.dirname(absFilePath), importInfo.source);
31788
+ const targetAbs = await pickSiblingTarget(path9.dirname(originalAssetAbs), resolved.filename, resolved.bytes);
31551
31789
  const newRelImport = relativeImportPath2(absFilePath, targetAbs);
31552
31790
  const newContent = content.slice(0, importInfo.sourceStart) + newRelImport + content.slice(importInfo.sourceEnd);
31553
31791
  return {
@@ -31557,8 +31795,8 @@ async function tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, o
31557
31795
  };
31558
31796
  }
31559
31797
  async function resolveNewSrcBytes(newSrc, originUrl) {
31560
- const filenameFromPath = (p) => path8.basename(p.split("?")[0] ?? p);
31561
- const diskPath = newSrc.startsWith("/src/") ? path8.join(getProjectRoot(), newSrc.slice(1)) : newSrc.startsWith("/") && !newSrc.startsWith("//") ? path8.join(getProjectRoot(), "public", newSrc.replace(/^\/+/, "")) : null;
31798
+ const filenameFromPath = (p) => path9.basename(p.split("?")[0] ?? p);
31799
+ const diskPath = newSrc.startsWith("/src/") ? path9.join(getProjectRoot(), newSrc.slice(1)) : newSrc.startsWith("/") && !newSrc.startsWith("//") ? path9.join(getProjectRoot(), "public", newSrc.replace(/^\/+/, "")) : null;
31562
31800
  if (diskPath) {
31563
31801
  try {
31564
31802
  return { bytes: await fs8.readFile(diskPath), filename: filenameFromPath(newSrc) };
@@ -31993,7 +32231,7 @@ function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewTex
31993
32231
  if (!keyMatch)
31994
32232
  return null;
31995
32233
  try {
31996
- const parsed = import_yaml5.parse(sourceSnippet);
32234
+ const parsed = import_yaml6.parse(sourceSnippet);
31997
32235
  if (parsed == null || typeof parsed !== "object")
31998
32236
  return null;
31999
32237
  const value = parsed[keyMatch[2]];
@@ -32004,7 +32242,7 @@ function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewTex
32004
32242
  } catch {
32005
32243
  return null;
32006
32244
  }
32007
- const serialized = import_yaml5.stringify(resolvedNewText, { lineWidth: 0 }).trimEnd();
32245
+ const serialized = import_yaml6.stringify(resolvedNewText, { lineWidth: 0 }).trimEnd();
32008
32246
  return `${keyMatch[1]}${serialized}`;
32009
32247
  }
32010
32248
  function tryDataFileValueReplacement(content, sourceSnippet, originalValue, newValue, sourceLine) {
@@ -32092,21 +32330,21 @@ function tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText)
32092
32330
  }
32093
32331
  return result !== sourceSnippet ? result : null;
32094
32332
  }
32095
- var import_node_html_parser, import_yaml5, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS;
32333
+ var import_node_html_parser, import_yaml6, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS;
32096
32334
  var init_source_writer = __esm(() => {
32097
32335
  init_astro_image_paths();
32098
32336
  init_config();
32099
32337
  init_snippet_utils();
32100
32338
  init_utils2();
32101
32339
  import_node_html_parser = __toESM(require_dist2(), 1);
32102
- import_yaml5 = __toESM(require_dist(), 1);
32340
+ import_yaml6 = __toESM(require_dist(), 1);
32103
32341
  ASSET_IMPORT_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|ico|bmp|tiff?)$/i;
32104
32342
  REMOTE_FETCH_MAX_BYTES = 50 * 1024 * 1024;
32105
32343
  QUOTED_LITERAL_DELIMITERS = [`'`, `"`, "`"];
32106
32344
  });
32107
32345
 
32108
32346
  // ../cms/src/handlers/api-routes.ts
32109
- import path9 from "path";
32347
+ import path10 from "path";
32110
32348
  function requireMedia(ctx) {
32111
32349
  if (!ctx.mediaAdapter) {
32112
32350
  sendError(ctx.res, "Media storage not configured", 501);
@@ -32167,7 +32405,7 @@ function lastSlug(sourcePath) {
32167
32405
  const slash = withoutExt.lastIndexOf("/");
32168
32406
  return slash >= 0 ? withoutExt.slice(slash + 1) : withoutExt;
32169
32407
  }
32170
- var ALLOWED_UPLOAD_TYPES, DATA_EXTENSIONS, routeMap;
32408
+ var ALLOWED_UPLOAD_TYPES, DATA_EXTENSIONS2, routeMap;
32171
32409
  var init_api_routes = __esm(() => {
32172
32410
  init_src2();
32173
32411
  init_config();
@@ -32188,7 +32426,7 @@ var init_api_routes = __esm(() => {
32188
32426
  "image/x-icon",
32189
32427
  "application/pdf"
32190
32428
  ]);
32191
- DATA_EXTENSIONS = new Set(["json", "yaml", "yml"]);
32429
+ DATA_EXTENSIONS2 = new Set(["json", "yaml", "yml"]);
32192
32430
  routeMap = new Map([
32193
32431
  post("update", (body, mw) => handleUpdate(body, mw)),
32194
32432
  post("insert-component", (body, mw) => handleInsertComponent(body, mw)),
@@ -32242,7 +32480,7 @@ var init_api_routes = __esm(() => {
32242
32480
  custom("POST", "markdown/create", async ({ req, res, core, manifestWriter, contentDir }) => {
32243
32481
  const body = await parseJsonBody(req);
32244
32482
  const ext = body.fileExtension ?? "md";
32245
- const isData = DATA_EXTENSIONS.has(ext);
32483
+ const isData = DATA_EXTENSIONS2.has(ext);
32246
32484
  const frontmatter = isData ? { ...body.frontmatter ?? {} } : { title: body.title, date: new Date().toISOString().split("T")[0], ...body.frontmatter ?? {} };
32247
32485
  const result = await core.createEntry({
32248
32486
  collection: body.collection,
@@ -32269,7 +32507,7 @@ var init_api_routes = __esm(() => {
32269
32507
  sendJson(res, { success: false, error: `Invalid content path: ${body.filePath}` }, 400);
32270
32508
  return;
32271
32509
  }
32272
- const fullPath = path9.resolve(getProjectRoot(), body.filePath.replace(/^\//, ""));
32510
+ const fullPath = path10.resolve(getProjectRoot(), body.filePath.replace(/^\//, ""));
32273
32511
  expectedDeletions.add(fullPath);
32274
32512
  const result = await core.deleteEntry(entry.collection, entry.slug);
32275
32513
  if (result.success) {
@@ -32376,7 +32614,7 @@ var init_api_routes = __esm(() => {
32376
32614
  const body = await parseJsonBody(req);
32377
32615
  const result = await core.deletePage(body);
32378
32616
  if (result.success && result.filePath) {
32379
- expectedDeletions.add(path9.resolve(getProjectRoot(), result.filePath));
32617
+ expectedDeletions.add(path10.resolve(getProjectRoot(), result.filePath));
32380
32618
  }
32381
32619
  if (result.success && body.createRedirect && body.redirectTo) {
32382
32620
  await core.addRedirect({ source: body.pagePath, destination: body.redirectTo, statusCode: 307 });
@@ -32481,7 +32719,7 @@ var init_color_patterns = __esm(() => {
32481
32719
 
32482
32720
  // ../cms/src/tailwind-colors.ts
32483
32721
  import fs9 from "fs/promises";
32484
- import path10 from "path";
32722
+ import path11 from "path";
32485
32723
  async function parseTailwindConfig(projectRoot = getProjectRoot()) {
32486
32724
  const cssFiles = [
32487
32725
  "src/styles/global.css",
@@ -32495,7 +32733,7 @@ async function parseTailwindConfig(projectRoot = getProjectRoot()) {
32495
32733
  ];
32496
32734
  let customColors = [];
32497
32735
  for (const cssFile of cssFiles) {
32498
- const fullPath = path10.join(projectRoot, cssFile);
32736
+ const fullPath = path11.join(projectRoot, cssFile);
32499
32737
  try {
32500
32738
  const content = await fs9.readFile(fullPath, "utf-8");
32501
32739
  customColors = extractColorsFromCss(content);
@@ -32568,7 +32806,7 @@ async function parseTextStyles(projectRoot = getProjectRoot()) {
32568
32806
  ];
32569
32807
  let customTextStyles = {};
32570
32808
  for (const cssFile of cssFiles) {
32571
- const fullPath = path10.join(projectRoot, cssFile);
32809
+ const fullPath = path11.join(projectRoot, cssFile);
32572
32810
  try {
32573
32811
  const content = await fs9.readFile(fullPath, "utf-8");
32574
32812
  customTextStyles = extractTextStylesFromCss(content);
@@ -33036,7 +33274,7 @@ var init_local_admin = __esm(() => {
33036
33274
 
33037
33275
  // ../cms/src/manifest-writer.ts
33038
33276
  import fs10 from "fs/promises";
33039
- import path11 from "path";
33277
+ import path12 from "path";
33040
33278
 
33041
33279
  class ManifestWriter {
33042
33280
  globalManifest;
@@ -33107,10 +33345,10 @@ class ManifestWriter {
33107
33345
  }
33108
33346
  getPageManifestPath(pagePath) {
33109
33347
  if (pagePath === "/" || pagePath === "") {
33110
- return path11.join(this.outDir, "index.json");
33348
+ return path12.join(this.outDir, "index.json");
33111
33349
  }
33112
33350
  const cleanPath = pagePath.replace(/^\//, "");
33113
- return path11.join(this.outDir, `${cleanPath}.json`);
33351
+ return path12.join(this.outDir, `${cleanPath}.json`);
33114
33352
  }
33115
33353
  addPage(pagePath, entries, components, collection, seo) {
33116
33354
  this.pageManifests.set(pagePath, { entries, components, collection, seo });
@@ -33136,7 +33374,7 @@ class ManifestWriter {
33136
33374
  }
33137
33375
  async writePageManifest(pagePath, entries, components, collection, seo) {
33138
33376
  const manifestPath = this.getPageManifestPath(pagePath);
33139
- const manifestDir = path11.dirname(manifestPath);
33377
+ const manifestDir = path12.dirname(manifestPath);
33140
33378
  await fs10.mkdir(manifestDir, { recursive: true });
33141
33379
  const metadata = {
33142
33380
  version: MANIFEST_VERSION,
@@ -33192,7 +33430,7 @@ class ManifestWriter {
33192
33430
  }
33193
33431
  }
33194
33432
  if (this.outDir) {
33195
- const globalManifestPath = path11.join(this.outDir, this.manifestFile);
33433
+ const globalManifestPath = path12.join(this.outDir, this.manifestFile);
33196
33434
  const globalSettings = {
33197
33435
  componentDefinitions: this.componentDefinitions,
33198
33436
  pages
@@ -33804,7 +34042,7 @@ var init_field_types = __esm(() => {
33804
34042
 
33805
34043
  // ../cms/src/migrate-astro-image.ts
33806
34044
  import fs11 from "fs/promises";
33807
- import path12 from "path";
34045
+ import path13 from "path";
33808
34046
  async function migrateAstroImages(options = {}) {
33809
34047
  const projectRoot = options.projectRoot ?? getProjectRoot();
33810
34048
  const dryRun = options.dryRun ?? false;
@@ -33816,7 +34054,7 @@ async function migrateAstroImages(options = {}) {
33816
34054
  if (astroFields.length === 0 || !def.entries)
33817
34055
  continue;
33818
34056
  for (const entry of def.entries) {
33819
- const entryAbs = path12.isAbsolute(entry.sourcePath) ? entry.sourcePath : path12.join(projectRoot, entry.sourcePath);
34057
+ const entryAbs = path13.isAbsolute(entry.sourcePath) ? entry.sourcePath : path13.join(projectRoot, entry.sourcePath);
33820
34058
  let raw;
33821
34059
  try {
33822
34060
  raw = await fs11.readFile(entryAbs, "utf-8");
@@ -33830,7 +34068,7 @@ async function migrateAstroImages(options = {}) {
33830
34068
  continue;
33831
34069
  }
33832
34070
  const [fullFm, fmStart, yamlBody, fmEnd] = fmMatch;
33833
- const doc2 = import_yaml6.parseDocument(yamlBody);
34071
+ const doc2 = import_yaml7.parseDocument(yamlBody);
33834
34072
  let mutated = false;
33835
34073
  for (const field of astroFields) {
33836
34074
  const current = doc2.get(field.name);
@@ -33841,7 +34079,7 @@ async function migrateAstroImages(options = {}) {
33841
34079
  }
33842
34080
  if (!current.startsWith("/") || current.startsWith("//"))
33843
34081
  continue;
33844
- const sourceAbs = path12.join(projectRoot, "public", current.replace(/^\/+/, ""));
34082
+ const sourceAbs = path13.join(projectRoot, "public", current.replace(/^\/+/, ""));
33845
34083
  let sourceBuf;
33846
34084
  try {
33847
34085
  sourceBuf = await fs11.readFile(sourceAbs);
@@ -33852,11 +34090,11 @@ async function migrateAstroImages(options = {}) {
33852
34090
  const target = await pickAstroImageTarget({
33853
34091
  entryAbsPath: entryAbs,
33854
34092
  slug: entry.slug,
33855
- originalFilename: path12.basename(current),
34093
+ originalFilename: path13.basename(current),
33856
34094
  compareBuffer: sourceBuf
33857
34095
  });
33858
34096
  if (!dryRun) {
33859
- await fs11.mkdir(path12.dirname(target.absPath), { recursive: true });
34097
+ await fs11.mkdir(path13.dirname(target.absPath), { recursive: true });
33860
34098
  await fs11.writeFile(target.absPath, sourceBuf);
33861
34099
  }
33862
34100
  doc2.set(field.name, target.relPath);
@@ -33879,12 +34117,12 @@ async function migrateAstroImages(options = {}) {
33879
34117
  }
33880
34118
  return { migrations, skipped };
33881
34119
  }
33882
- var import_yaml6, FRONTMATTER_RE;
34120
+ var import_yaml7, FRONTMATTER_RE;
33883
34121
  var init_migrate_astro_image = __esm(() => {
33884
34122
  init_src2();
33885
34123
  init_astro_image_paths();
33886
34124
  init_config();
33887
- import_yaml6 = __toESM(require_dist(), 1);
34125
+ import_yaml7 = __toESM(require_dist(), 1);
33888
34126
  FRONTMATTER_RE = /^(---\r?\n)([\s\S]*?)(\r?\n---\r?\n?)/;
33889
34127
  });
33890
34128
 
@@ -33919,7 +34157,7 @@ var exports_migrate = {};
33919
34157
  __export(exports_migrate, {
33920
34158
  migrate: () => migrate
33921
34159
  });
33922
- import path13 from "path";
34160
+ import path14 from "path";
33923
34161
  async function migrate(args) {
33924
34162
  if (args.target !== "astro-image") {
33925
34163
  console.error(`Unknown migrate target: ${args.target}`);
@@ -33938,7 +34176,7 @@ async function migrate(args) {
33938
34176
  for (const m of result.migrations) {
33939
34177
  console.log(` ${m.entrySourcePath}`);
33940
34178
  console.log(` ${m.fieldName}: ${m.originalValue} \u2192 ${m.newValue}`);
33941
- console.log(` copy: ${path13.relative(process.cwd(), m.copiedFrom)} \u2192 ${path13.relative(process.cwd(), m.copiedTo)}`);
34179
+ console.log(` copy: ${path14.relative(process.cwd(), m.copiedFrom)} \u2192 ${path14.relative(process.cwd(), m.copiedTo)}`);
33942
34180
  }
33943
34181
  }
33944
34182
  if (result.skipped.length > 0) {
@@ -33980,6 +34218,7 @@ Commands:`);
33980
34218
  console.log(" build Run astro build with the Nua defaults");
33981
34219
  console.log(" dev Run astro dev with the Nua defaults");
33982
34220
  console.log(" preview Run astro preview with the Nua defaults");
34221
+ console.log(" check Validate the content collections (no build). --json, --strict");
33983
34222
  console.log(" init Convert a standard Astro project to use Nua");
33984
34223
  console.log(" clean Eject to a standard Astro project (remove @nuasite/* deps)");
33985
34224
  console.log(" migrate <target> Run a content migration. Targets: astro-image");
@@ -33992,6 +34231,15 @@ if (command && ["build", "dev", "preview"].includes(command)) {
33992
34231
  proxyToAstroCLI(command, args);
33993
34232
  } else {
33994
34233
  switch (command) {
34234
+ case "check": {
34235
+ const { check: check2 } = await Promise.resolve().then(() => (init_check2(), exports_check));
34236
+ process.exit(await check2({
34237
+ cwd: process.cwd(),
34238
+ json: args.includes("--json"),
34239
+ strict: args.includes("--strict")
34240
+ }));
34241
+ break;
34242
+ }
33995
34243
  case "init": {
33996
34244
  const { init: init2 } = await Promise.resolve().then(() => (init_init(), exports_init));
33997
34245
  await init2({