astro-md-editor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/assets/index-Cc7yKB0o.js +19 -0
  3. package/.output/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  4. package/.output/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  5. package/.output/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  6. package/.output/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  7. package/.output/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  8. package/.output/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  9. package/.output/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  10. package/.output/public/assets/main-DDBjVFnt.js +17 -0
  11. package/.output/public/assets/styles-ggfdUHMo.css +1 -0
  12. package/.output/public/favicon.ico +0 -0
  13. package/.output/public/logo192.png +0 -0
  14. package/.output/public/logo512.png +0 -0
  15. package/.output/public/manifest.json +25 -0
  16. package/.output/public/robots.txt +3 -0
  17. package/.output/server/__root-C09LBXMv.mjs +40 -0
  18. package/.output/server/_chunks/ssr-renderer.mjs +21 -0
  19. package/.output/server/_libs/ajv-formats.mjs +330 -0
  20. package/.output/server/_libs/ajv.mjs +5484 -0
  21. package/.output/server/_libs/base-ui__react.mjs +8712 -0
  22. package/.output/server/_libs/base-ui__utils.mjs +980 -0
  23. package/.output/server/_libs/class-variance-authority.mjs +44 -0
  24. package/.output/server/_libs/clsx.mjs +16 -0
  25. package/.output/server/_libs/cookie-es.mjs +58 -0
  26. package/.output/server/_libs/croner.mjs +1 -0
  27. package/.output/server/_libs/crossws.mjs +1 -0
  28. package/.output/server/_libs/date-fns.mjs +1716 -0
  29. package/.output/server/_libs/date-fns__tz.mjs +217 -0
  30. package/.output/server/_libs/extend-shallow.mjs +35 -0
  31. package/.output/server/_libs/fast-deep-equal.mjs +38 -0
  32. package/.output/server/_libs/fast-uri.mjs +725 -0
  33. package/.output/server/_libs/floating-ui__core.mjs +663 -0
  34. package/.output/server/_libs/floating-ui__dom.mjs +624 -0
  35. package/.output/server/_libs/floating-ui__react-dom.mjs +279 -0
  36. package/.output/server/_libs/floating-ui__utils.mjs +322 -0
  37. package/.output/server/_libs/gray-matter.mjs +393 -0
  38. package/.output/server/_libs/h3-v2.mjs +276 -0
  39. package/.output/server/_libs/h3.mjs +400 -0
  40. package/.output/server/_libs/hookable.mjs +1 -0
  41. package/.output/server/_libs/is-extendable.mjs +13 -0
  42. package/.output/server/_libs/isbot.mjs +20 -0
  43. package/.output/server/_libs/js-yaml.mjs +2822 -0
  44. package/.output/server/_libs/json-schema-traverse.mjs +91 -0
  45. package/.output/server/_libs/kind-of.mjs +125 -0
  46. package/.output/server/_libs/lucide-react.mjs +177 -0
  47. package/.output/server/_libs/ohash.mjs +1 -0
  48. package/.output/server/_libs/react-day-picker.mjs +2216 -0
  49. package/.output/server/_libs/react-dom.mjs +10779 -0
  50. package/.output/server/_libs/react-resizable-panels.mjs +2024 -0
  51. package/.output/server/_libs/react.mjs +513 -0
  52. package/.output/server/_libs/reselect.mjs +326 -0
  53. package/.output/server/_libs/rou3.mjs +8 -0
  54. package/.output/server/_libs/section-matter.mjs +112 -0
  55. package/.output/server/_libs/seroval-plugins.mjs +58 -0
  56. package/.output/server/_libs/seroval.mjs +1765 -0
  57. package/.output/server/_libs/srvx.mjs +736 -0
  58. package/.output/server/_libs/strip-bom-string.mjs +16 -0
  59. package/.output/server/_libs/tabbable.mjs +342 -0
  60. package/.output/server/_libs/tailwind-merge.mjs +3175 -0
  61. package/.output/server/_libs/tanstack__history.mjs +217 -0
  62. package/.output/server/_libs/tanstack__react-router.mjs +1464 -0
  63. package/.output/server/_libs/tanstack__react-store.mjs +1 -0
  64. package/.output/server/_libs/tanstack__router-core.mjs +4912 -0
  65. package/.output/server/_libs/tanstack__store.mjs +1 -0
  66. package/.output/server/_libs/tiny-invariant.mjs +12 -0
  67. package/.output/server/_libs/tiny-warning.mjs +5 -0
  68. package/.output/server/_libs/ufo.mjs +54 -0
  69. package/.output/server/_libs/unctx.mjs +1 -0
  70. package/.output/server/_libs/unstorage.mjs +1 -0
  71. package/.output/server/_libs/use-sync-external-store.mjs +139 -0
  72. package/.output/server/_libs/zod.mjs +3634 -0
  73. package/.output/server/_libs/zustand.mjs +43 -0
  74. package/.output/server/_ssr/RightSidebar-RSY9M7XF.mjs +218 -0
  75. package/.output/server/_ssr/collections.server-D6U2tEsT.mjs +120 -0
  76. package/.output/server/_ssr/createServerRpc-29xaFZcb.mjs +12 -0
  77. package/.output/server/_ssr/index-BaqV4cZC.mjs +2083 -0
  78. package/.output/server/_ssr/index-sQBM6rwN.mjs +115 -0
  79. package/.output/server/_ssr/index.mjs +1448 -0
  80. package/.output/server/_ssr/router-D4G1DGr3.mjs +155 -0
  81. package/.output/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  82. package/.output/server/_tanstack-start-manifest_v-CYEHh_qB.mjs +4 -0
  83. package/.output/server/index.mjs +451 -0
  84. package/README.md +118 -0
  85. package/index.mjs +21 -0
  86. package/package.json +86 -0
  87. package/scripts/bootstrap-collections.mjs +1201 -0
@@ -0,0 +1,1201 @@
1
+ import { access, readFile, readdir, stat } from 'node:fs/promises';
2
+ import { basename, isAbsolute, relative, resolve } from 'node:path';
3
+ import { config as dotenvConfig } from 'dotenv';
4
+ import matter from 'gray-matter';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawn } from 'node:child_process';
7
+ import { z } from 'zod';
8
+
9
+ const ROOT_ENV_KEY = 'APP_ROOT_PATH';
10
+ const DEFAULT_ROOT_PATH = '.';
11
+ const COLLECTIONS_RELATIVE_PATH_PREFIX = '.astro/collections/';
12
+ const CONTENT_RELATIVE_PATH_PREFIX = 'src/content/';
13
+ const CONTENT_CONFIG_CANDIDATES = [
14
+ 'src/content.config.ts',
15
+ 'src/content.config.mjs',
16
+ 'src/content.config.js',
17
+ 'src/content/config.ts',
18
+ 'src/content/config.mjs',
19
+ 'src/content/config.js',
20
+ ];
21
+ const FIELD_OVERRIDES_FILE = 'astro-md-editor.fields.json';
22
+ const ASTRO_DEFINITION_PREFIX = '#/definitions/';
23
+ const CONTENT_FILE_EXTENSIONS = new Set(['.md', '.mdx', '.markdown']);
24
+
25
+ const imageModeSchema = z.enum(['asset', 'public']);
26
+ const fieldOverrideSchema = z.discriminatedUnion('type', [
27
+ z
28
+ .object({
29
+ type: z.literal('image'),
30
+ multiple: z.boolean().optional(),
31
+ mode: imageModeSchema.optional(),
32
+ })
33
+ .strict(),
34
+ z
35
+ .object({
36
+ type: z.literal('color'),
37
+ })
38
+ .strict(),
39
+ ]);
40
+
41
+ function isRecord(value) {
42
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
43
+ }
44
+
45
+ function parseFileUrlPath(value) {
46
+ if (!value.startsWith('file://')) {
47
+ return undefined;
48
+ }
49
+
50
+ try {
51
+ return fileURLToPath(new URL(value));
52
+ } catch {
53
+ return undefined;
54
+ }
55
+ }
56
+
57
+ function extractContentRelativePath(pathLike) {
58
+ const normalized = pathLike.replaceAll('\\', '/');
59
+ const marker = CONTENT_RELATIVE_PATH_PREFIX.toLowerCase();
60
+ const startIndex = normalized.toLowerCase().lastIndexOf(marker);
61
+ if (startIndex === -1) {
62
+ return undefined;
63
+ }
64
+
65
+ const relativePath = normalized
66
+ .slice(startIndex + CONTENT_RELATIVE_PATH_PREFIX.length)
67
+ .replace(/^\/+/, '');
68
+
69
+ return relativePath.length > 0 ? relativePath : undefined;
70
+ }
71
+
72
+ function toPosix(pathLike) {
73
+ return pathLike.replaceAll('\\', '/');
74
+ }
75
+
76
+ function stripExtension(pathLike) {
77
+ return pathLike.replace(/\.[^.]+$/, '');
78
+ }
79
+
80
+ function slugifySegment(value) {
81
+ const normalized = value
82
+ .trim()
83
+ .toLowerCase()
84
+ .replace(/[^a-z0-9]+/g, '-')
85
+ .replace(/^-+/, '')
86
+ .replace(/-+$/, '');
87
+
88
+ return normalized;
89
+ }
90
+
91
+ function buildEntrySlug(rootDir, collectionName, candidatePath, entryKey) {
92
+ const collectionDir = resolve(
93
+ rootDir,
94
+ CONTENT_RELATIVE_PATH_PREFIX + collectionName,
95
+ );
96
+
97
+ const relativeFromCollection = toPosix(
98
+ relative(collectionDir, candidatePath),
99
+ );
100
+ let sourcePath;
101
+ if (
102
+ relativeFromCollection.length > 0 &&
103
+ !relativeFromCollection.startsWith('..')
104
+ ) {
105
+ sourcePath = relativeFromCollection;
106
+ } else {
107
+ sourcePath =
108
+ extractContentRelativePath(candidatePath) ??
109
+ extractContentRelativePath(entryKey) ??
110
+ basename(candidatePath);
111
+ }
112
+
113
+ const withoutExtension = stripExtension(toPosix(sourcePath));
114
+ const segments = withoutExtension
115
+ .split('/')
116
+ .map((segment) => slugifySegment(segment))
117
+ .filter(Boolean);
118
+ const normalizedCollectionSlug = slugifySegment(collectionName);
119
+
120
+ if (segments.length === 0) {
121
+ return 'entry';
122
+ }
123
+
124
+ if (segments[0] === normalizedCollectionSlug && segments.length > 1) {
125
+ segments.shift();
126
+ }
127
+
128
+ return segments.join('-');
129
+ }
130
+
131
+ function makeUniqueSlug(baseSlug, usedSlugs) {
132
+ const seenCount = usedSlugs.get(baseSlug) ?? 0;
133
+ const nextCount = seenCount + 1;
134
+ usedSlugs.set(baseSlug, nextCount);
135
+
136
+ if (nextCount === 1) {
137
+ return baseSlug;
138
+ }
139
+
140
+ return `${baseSlug}-${nextCount}`;
141
+ }
142
+
143
+ function getEntryResolutionCandidates(rootDir, entryKey) {
144
+ const candidates = [];
145
+ const seen = new Set();
146
+ const addCandidate = (candidatePath) => {
147
+ if (!candidatePath || seen.has(candidatePath)) {
148
+ return;
149
+ }
150
+ seen.add(candidatePath);
151
+ candidates.push(candidatePath);
152
+ };
153
+
154
+ const parsedFileUrlPath = parseFileUrlPath(entryKey);
155
+ const entryPath = parsedFileUrlPath ?? entryKey;
156
+ const relativeContentPath =
157
+ extractContentRelativePath(entryPath) ??
158
+ extractContentRelativePath(entryKey);
159
+
160
+ addCandidate(resolve(rootDir, entryPath));
161
+
162
+ if (!isAbsolute(entryPath)) {
163
+ addCandidate(resolve(rootDir, CONTENT_RELATIVE_PATH_PREFIX + entryPath));
164
+ }
165
+
166
+ if (relativeContentPath) {
167
+ addCandidate(
168
+ resolve(rootDir, CONTENT_RELATIVE_PATH_PREFIX + relativeContentPath),
169
+ );
170
+ }
171
+
172
+ return candidates;
173
+ }
174
+
175
+ async function parseCollectionEntry(rootDir, collectionName, entryKey) {
176
+ const candidates = getEntryResolutionCandidates(rootDir, entryKey);
177
+
178
+ for (const candidatePath of candidates) {
179
+ let source;
180
+ try {
181
+ source = await readFile(candidatePath, 'utf8');
182
+ } catch (error) {
183
+ const code =
184
+ typeof error === 'object' && error !== null && 'code' in error
185
+ ? error.code
186
+ : undefined;
187
+ if (code === 'ENOENT') {
188
+ continue;
189
+ }
190
+
191
+ const message = error instanceof Error ? error.message : String(error);
192
+ throw new Error(
193
+ `Collection entry read failed for "${entryKey}" at ${candidatePath}: ${message}`,
194
+ );
195
+ }
196
+
197
+ const parsed = matter(source);
198
+ return {
199
+ slugBase: buildEntrySlug(
200
+ rootDir,
201
+ collectionName,
202
+ candidatePath,
203
+ entryKey,
204
+ ),
205
+ filePath: candidatePath,
206
+ data: parsed.data,
207
+ content: parsed.content,
208
+ };
209
+ }
210
+
211
+ throw new Error(
212
+ `Collection entry file not found for "${entryKey}". Tried: ${candidates.join(', ')}`,
213
+ );
214
+ }
215
+
216
+ function normalizePathInput(value) {
217
+ if (typeof value !== 'string') {
218
+ return undefined;
219
+ }
220
+
221
+ const trimmed = value.trim();
222
+ return trimmed.length > 0 ? trimmed : undefined;
223
+ }
224
+
225
+ function parseModeArg(argv) {
226
+ for (let i = 0; i < argv.length; i += 1) {
227
+ const arg = argv[i];
228
+
229
+ if (arg === '--mode') {
230
+ const value = argv[i + 1];
231
+ return normalizePathInput(value);
232
+ }
233
+
234
+ if (arg.startsWith('--mode=')) {
235
+ return normalizePathInput(arg.slice('--mode='.length));
236
+ }
237
+ }
238
+
239
+ return undefined;
240
+ }
241
+
242
+ function getDotenvCandidates(mode) {
243
+ const files = ['.env.local', '.env'];
244
+
245
+ if (mode) {
246
+ files.unshift(`.env.${mode}.local`);
247
+ files.splice(2, 0, `.env.${mode}`);
248
+ }
249
+
250
+ return files;
251
+ }
252
+
253
+ function loadDotenv(cwd, mode, env) {
254
+ for (const dotenvFile of getDotenvCandidates(mode)) {
255
+ const dotenvPath = resolve(cwd, dotenvFile);
256
+ const result = dotenvConfig({
257
+ path: dotenvPath,
258
+ quiet: true,
259
+ processEnv: env,
260
+ });
261
+ const error = result.error;
262
+
263
+ // Missing dotenv files are optional; any other parse/read error should fail startup.
264
+ if (error && error.code !== 'ENOENT') {
265
+ throw new Error(`Failed to load ${dotenvPath}: ${error.message}`);
266
+ }
267
+ }
268
+ }
269
+
270
+ function parseRootPathArg(argv) {
271
+ let positionalPath;
272
+ let flagPath;
273
+ const forwardArgs = [];
274
+
275
+ let startIndex = 0;
276
+ const firstArg = argv[0];
277
+ if (typeof firstArg === 'string' && !firstArg.startsWith('-')) {
278
+ positionalPath = firstArg;
279
+ startIndex = 1;
280
+ }
281
+
282
+ for (let i = startIndex; i < argv.length; i += 1) {
283
+ const arg = argv[i];
284
+
285
+ if (arg === '--path') {
286
+ const value = argv[i + 1];
287
+ if (!value) {
288
+ throw new Error('Missing value for --path.');
289
+ }
290
+ flagPath = value;
291
+ i += 1;
292
+ continue;
293
+ }
294
+
295
+ if (arg.startsWith('--path=')) {
296
+ const value = arg.slice('--path='.length);
297
+ if (!value) {
298
+ throw new Error('Missing value for --path.');
299
+ }
300
+ flagPath = value;
301
+ continue;
302
+ }
303
+
304
+ forwardArgs.push(arg);
305
+ }
306
+
307
+ return {
308
+ cliPath: positionalPath ?? flagPath,
309
+ forwardArgs,
310
+ };
311
+ }
312
+
313
+ function hasSupportedContentExtension(filePath) {
314
+ const lowercasePath = filePath.toLowerCase();
315
+ for (const extension of CONTENT_FILE_EXTENSIONS) {
316
+ if (lowercasePath.endsWith(extension)) {
317
+ return true;
318
+ }
319
+ }
320
+
321
+ return false;
322
+ }
323
+
324
+ function getPropertyNameText(node, ts) {
325
+ if (!node) {
326
+ return undefined;
327
+ }
328
+
329
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node)) {
330
+ return node.text;
331
+ }
332
+
333
+ return undefined;
334
+ }
335
+
336
+ function getReturnExpression(body, ts) {
337
+ if (!body) {
338
+ return undefined;
339
+ }
340
+
341
+ if (!ts.isBlock(body)) {
342
+ return body;
343
+ }
344
+
345
+ for (const statement of body.statements) {
346
+ if (ts.isReturnStatement(statement)) {
347
+ return statement.expression;
348
+ }
349
+ }
350
+
351
+ return undefined;
352
+ }
353
+
354
+ function unwrapCallWrappers(expression, ts) {
355
+ let current = expression;
356
+ while (
357
+ ts.isCallExpression(current) &&
358
+ ts.isPropertyAccessExpression(current.expression) &&
359
+ ts.isCallExpression(current.expression.expression)
360
+ ) {
361
+ current = current.expression.expression;
362
+ }
363
+
364
+ return current;
365
+ }
366
+
367
+ function isNamedCall(node, names, ts) {
368
+ return ts.isCallExpression(node) && ts.isIdentifier(node.expression)
369
+ ? names.has(node.expression.text)
370
+ : false;
371
+ }
372
+
373
+ function isZArrayCall(node, zNames, ts) {
374
+ if (
375
+ !ts.isCallExpression(node) ||
376
+ !ts.isPropertyAccessExpression(node.expression)
377
+ ) {
378
+ return false;
379
+ }
380
+
381
+ return (
382
+ ts.isIdentifier(node.expression.expression) &&
383
+ zNames.has(node.expression.expression.text) &&
384
+ node.expression.name.text === 'array'
385
+ );
386
+ }
387
+
388
+ function parseSchemaFieldKindsFromFunction(functionNode, ts, zNames) {
389
+ const schemaExpression = getReturnExpression(functionNode.body, ts);
390
+ if (!schemaExpression) {
391
+ return {};
392
+ }
393
+
394
+ const unwrappedSchemaExpression = unwrapCallWrappers(schemaExpression, ts);
395
+ if (
396
+ !ts.isCallExpression(unwrappedSchemaExpression) ||
397
+ !ts.isPropertyAccessExpression(unwrappedSchemaExpression.expression) ||
398
+ !ts.isIdentifier(unwrappedSchemaExpression.expression.expression) ||
399
+ !zNames.has(unwrappedSchemaExpression.expression.expression.text) ||
400
+ unwrappedSchemaExpression.expression.name.text !== 'object'
401
+ ) {
402
+ return {};
403
+ }
404
+
405
+ const objectArgument = unwrappedSchemaExpression.arguments[0];
406
+ if (!objectArgument || !ts.isObjectLiteralExpression(objectArgument)) {
407
+ return {};
408
+ }
409
+
410
+ const imageNames = new Set();
411
+ const [firstParameter] = functionNode.parameters;
412
+ if (firstParameter && ts.isObjectBindingPattern(firstParameter.name)) {
413
+ for (const element of firstParameter.name.elements) {
414
+ const propertyName = getPropertyNameText(element.propertyName, ts);
415
+ const bindingName = ts.isIdentifier(element.name)
416
+ ? element.name.text
417
+ : undefined;
418
+ if (propertyName === 'image' && bindingName) {
419
+ imageNames.add(bindingName);
420
+ }
421
+
422
+ if (!propertyName && bindingName === 'image') {
423
+ imageNames.add(bindingName);
424
+ }
425
+ }
426
+ }
427
+
428
+ if (imageNames.size === 0) {
429
+ imageNames.add('image');
430
+ }
431
+
432
+ const fieldKinds = {};
433
+ for (const property of objectArgument.properties) {
434
+ if (!ts.isPropertyAssignment(property)) {
435
+ continue;
436
+ }
437
+
438
+ const fieldName = getPropertyNameText(property.name, ts);
439
+ if (!fieldName) {
440
+ continue;
441
+ }
442
+
443
+ const unwrappedValue = unwrapCallWrappers(property.initializer, ts);
444
+ if (isNamedCall(unwrappedValue, imageNames, ts)) {
445
+ fieldKinds[fieldName] = 'image';
446
+ continue;
447
+ }
448
+
449
+ if (!isZArrayCall(unwrappedValue, zNames, ts)) {
450
+ continue;
451
+ }
452
+
453
+ const [arrayItemNode] = unwrappedValue.arguments;
454
+ if (!arrayItemNode) {
455
+ continue;
456
+ }
457
+
458
+ const unwrappedArrayItem = unwrapCallWrappers(arrayItemNode, ts);
459
+ if (isNamedCall(unwrappedArrayItem, imageNames, ts)) {
460
+ fieldKinds[fieldName] = 'imageArray';
461
+ }
462
+ }
463
+
464
+ return fieldKinds;
465
+ }
466
+
467
+ function parseCollectionsExportMap(sourceFile, ts, definedCollections) {
468
+ const mapped = {};
469
+
470
+ for (const statement of sourceFile.statements) {
471
+ if (!ts.isVariableStatement(statement)) {
472
+ continue;
473
+ }
474
+
475
+ const hasExportModifier = statement.modifiers?.some(
476
+ (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
477
+ );
478
+ if (!hasExportModifier) {
479
+ continue;
480
+ }
481
+
482
+ for (const declaration of statement.declarationList.declarations) {
483
+ if (
484
+ !ts.isIdentifier(declaration.name) ||
485
+ declaration.name.text !== 'collections' ||
486
+ !declaration.initializer ||
487
+ !ts.isObjectLiteralExpression(declaration.initializer)
488
+ ) {
489
+ continue;
490
+ }
491
+
492
+ for (const property of declaration.initializer.properties) {
493
+ if (ts.isShorthandPropertyAssignment(property)) {
494
+ const variableName = property.name.text;
495
+ const fieldKinds = definedCollections[variableName];
496
+ if (fieldKinds && Object.keys(fieldKinds).length > 0) {
497
+ mapped[variableName] = fieldKinds;
498
+ }
499
+ continue;
500
+ }
501
+
502
+ if (!ts.isPropertyAssignment(property)) {
503
+ continue;
504
+ }
505
+
506
+ const collectionName = getPropertyNameText(property.name, ts);
507
+ if (!collectionName || !ts.isIdentifier(property.initializer)) {
508
+ continue;
509
+ }
510
+
511
+ const fieldKinds = definedCollections[property.initializer.text];
512
+ if (fieldKinds && Object.keys(fieldKinds).length > 0) {
513
+ mapped[collectionName] = fieldKinds;
514
+ }
515
+ }
516
+ }
517
+ }
518
+
519
+ return mapped;
520
+ }
521
+
522
+ function parseImageFieldKindsFromContentConfig({ sourceText, filePath, ts }) {
523
+ const sourceFile = ts.createSourceFile(
524
+ filePath,
525
+ sourceText,
526
+ ts.ScriptTarget.Latest,
527
+ true,
528
+ );
529
+
530
+ const defineCollectionNames = new Set(['defineCollection']);
531
+ const zNames = new Set(['z']);
532
+
533
+ for (const statement of sourceFile.statements) {
534
+ if (!ts.isImportDeclaration(statement)) {
535
+ continue;
536
+ }
537
+
538
+ if (
539
+ !ts.isStringLiteral(statement.moduleSpecifier) ||
540
+ statement.moduleSpecifier.text !== 'astro:content'
541
+ ) {
542
+ continue;
543
+ }
544
+
545
+ const importClause = statement.importClause;
546
+ if (
547
+ !importClause?.namedBindings ||
548
+ !ts.isNamedImports(importClause.namedBindings)
549
+ ) {
550
+ continue;
551
+ }
552
+
553
+ for (const importSpecifier of importClause.namedBindings.elements) {
554
+ const importedName = importSpecifier.propertyName
555
+ ? importSpecifier.propertyName.text
556
+ : importSpecifier.name.text;
557
+ const localName = importSpecifier.name.text;
558
+ if (importedName === 'defineCollection') {
559
+ defineCollectionNames.add(localName);
560
+ }
561
+ if (importedName === 'z') {
562
+ zNames.add(localName);
563
+ }
564
+ }
565
+ }
566
+
567
+ const definedCollections = {};
568
+
569
+ for (const statement of sourceFile.statements) {
570
+ if (!ts.isVariableStatement(statement)) {
571
+ continue;
572
+ }
573
+
574
+ for (const declaration of statement.declarationList.declarations) {
575
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
576
+ continue;
577
+ }
578
+
579
+ if (!isNamedCall(declaration.initializer, defineCollectionNames, ts)) {
580
+ continue;
581
+ }
582
+
583
+ const [configArg] = declaration.initializer.arguments;
584
+ if (!configArg || !ts.isObjectLiteralExpression(configArg)) {
585
+ continue;
586
+ }
587
+
588
+ const schemaProperty = configArg.properties.find((property) => {
589
+ return (
590
+ ts.isPropertyAssignment(property) &&
591
+ getPropertyNameText(property.name, ts) === 'schema'
592
+ );
593
+ });
594
+
595
+ if (
596
+ !schemaProperty ||
597
+ !ts.isPropertyAssignment(schemaProperty) ||
598
+ !(
599
+ ts.isArrowFunction(schemaProperty.initializer) ||
600
+ ts.isFunctionExpression(schemaProperty.initializer)
601
+ )
602
+ ) {
603
+ continue;
604
+ }
605
+
606
+ const fieldKinds = parseSchemaFieldKindsFromFunction(
607
+ schemaProperty.initializer,
608
+ ts,
609
+ zNames,
610
+ );
611
+ if (Object.keys(fieldKinds).length > 0) {
612
+ definedCollections[declaration.name.text] = fieldKinds;
613
+ }
614
+ }
615
+ }
616
+
617
+ const mappedExports = parseCollectionsExportMap(
618
+ sourceFile,
619
+ ts,
620
+ definedCollections,
621
+ );
622
+ if (Object.keys(mappedExports).length > 0) {
623
+ return mappedExports;
624
+ }
625
+
626
+ return definedCollections;
627
+ }
628
+
629
+ async function readOptionalFile(filePath) {
630
+ try {
631
+ const source = await readFile(filePath, 'utf8');
632
+ return source;
633
+ } catch (error) {
634
+ const code =
635
+ typeof error === 'object' && error !== null && 'code' in error
636
+ ? error.code
637
+ : undefined;
638
+ if (code === 'ENOENT') {
639
+ return undefined;
640
+ }
641
+ throw error;
642
+ }
643
+ }
644
+
645
+ async function fileExists(filePath) {
646
+ try {
647
+ await access(filePath);
648
+ return true;
649
+ } catch {
650
+ return false;
651
+ }
652
+ }
653
+
654
+ async function resolveAstroCliPath(rootDir) {
655
+ const candidatePaths =
656
+ process.platform === 'win32'
657
+ ? [
658
+ resolve(rootDir, 'node_modules/.bin/astro.cmd'),
659
+ resolve(rootDir, 'node_modules/.bin/astro'),
660
+ ]
661
+ : [resolve(rootDir, 'node_modules/.bin/astro')];
662
+
663
+ for (const candidatePath of candidatePaths) {
664
+ if (await fileExists(candidatePath)) {
665
+ return candidatePath;
666
+ }
667
+ }
668
+
669
+ return undefined;
670
+ }
671
+
672
+ async function runAstroSync(rootDir, env = process.env) {
673
+ const astroCliPath = await resolveAstroCliPath(rootDir);
674
+ if (!astroCliPath) {
675
+ throw new Error(
676
+ `Collections are missing and Astro CLI was not found at ${resolve(
677
+ rootDir,
678
+ 'node_modules/.bin/astro',
679
+ )}. Install dependencies in ${rootDir} and try again.`,
680
+ );
681
+ }
682
+
683
+ await new Promise((resolveSync, rejectSync) => {
684
+ const child = spawn(astroCliPath, ['sync'], {
685
+ cwd: rootDir,
686
+ stdio: 'inherit',
687
+ env,
688
+ shell: process.platform === 'win32',
689
+ });
690
+
691
+ child.on('error', rejectSync);
692
+ child.on('exit', (code, signal) => {
693
+ if (signal || code !== 0) {
694
+ rejectSync(
695
+ new Error(
696
+ `astro sync failed in ${rootDir} with exit code ${code ?? 'unknown'}.`,
697
+ ),
698
+ );
699
+ return;
700
+ }
701
+
702
+ resolveSync();
703
+ });
704
+ });
705
+ }
706
+
707
+ async function readDirEntriesSafe(dirPath) {
708
+ try {
709
+ return await readdir(dirPath, { withFileTypes: true });
710
+ } catch (error) {
711
+ const code =
712
+ typeof error === 'object' && error !== null && 'code' in error
713
+ ? error.code
714
+ : undefined;
715
+ if (code === 'ENOENT') {
716
+ return [];
717
+ }
718
+ throw error;
719
+ }
720
+ }
721
+
722
+ async function collectCollectionContentFiles(collectionDirPath) {
723
+ const stack = [collectionDirPath];
724
+ const files = [];
725
+
726
+ while (stack.length > 0) {
727
+ const currentDir = stack.pop();
728
+ if (!currentDir) {
729
+ continue;
730
+ }
731
+
732
+ const dirEntries = await readDirEntriesSafe(currentDir);
733
+ for (const entry of dirEntries) {
734
+ const entryPath = resolve(currentDir, entry.name);
735
+ if (entry.isDirectory()) {
736
+ stack.push(entryPath);
737
+ continue;
738
+ }
739
+
740
+ if (entry.isFile() && hasSupportedContentExtension(entry.name)) {
741
+ files.push(entryPath);
742
+ }
743
+ }
744
+ }
745
+
746
+ files.sort();
747
+ return files;
748
+ }
749
+
750
+ async function buildCollectionsFromFilesystem(rootDir) {
751
+ const schemaDirPath = resolve(rootDir, COLLECTIONS_RELATIVE_PATH_PREFIX);
752
+ const contentDirPath = resolve(rootDir, CONTENT_RELATIVE_PATH_PREFIX);
753
+
754
+ const schemaNames = new Set();
755
+ const schemaEntries = await readDirEntriesSafe(schemaDirPath);
756
+ for (const schemaEntry of schemaEntries) {
757
+ if (!schemaEntry.isFile()) {
758
+ continue;
759
+ }
760
+
761
+ if (!schemaEntry.name.endsWith('.schema.json')) {
762
+ continue;
763
+ }
764
+
765
+ const collectionName = schemaEntry.name.slice(0, -'.schema.json'.length);
766
+ if (collectionName.length > 0) {
767
+ schemaNames.add(collectionName);
768
+ }
769
+ }
770
+
771
+ const contentCollectionNames = new Set();
772
+ const contentEntries = await readDirEntriesSafe(contentDirPath);
773
+ for (const contentEntry of contentEntries) {
774
+ if (!contentEntry.isDirectory()) {
775
+ continue;
776
+ }
777
+
778
+ contentCollectionNames.add(contentEntry.name);
779
+ }
780
+
781
+ const collectionNames = [
782
+ ...new Set([...schemaNames, ...contentCollectionNames]),
783
+ ].sort();
784
+ if (collectionNames.length === 0) {
785
+ throw new Error(
786
+ `No collections found. Expected schema files under ${schemaDirPath} or content folders under ${contentDirPath}.`,
787
+ );
788
+ }
789
+
790
+ const entries = {};
791
+ const collections = [];
792
+ for (const collectionName of collectionNames) {
793
+ collections.push({
794
+ name: collectionName,
795
+ hasSchema: schemaNames.has(collectionName),
796
+ });
797
+
798
+ const collectionDirPath = resolve(contentDirPath, collectionName);
799
+ const collectionFiles =
800
+ await collectCollectionContentFiles(collectionDirPath);
801
+ for (const filePath of collectionFiles) {
802
+ entries[filePath] = collectionName;
803
+ }
804
+ }
805
+
806
+ return {
807
+ collections,
808
+ entries,
809
+ };
810
+ }
811
+
812
+ async function resolveContentConfigPath(rootDir) {
813
+ for (const candidate of CONTENT_CONFIG_CANDIDATES) {
814
+ const fullPath = resolve(rootDir, candidate);
815
+ const source = await readOptionalFile(fullPath);
816
+ if (source !== undefined) {
817
+ return {
818
+ filePath: fullPath,
819
+ source,
820
+ };
821
+ }
822
+ }
823
+
824
+ return undefined;
825
+ }
826
+
827
+ async function loadInferredImageFieldKinds(rootDir) {
828
+ const contentConfig = await resolveContentConfigPath(rootDir);
829
+ if (!contentConfig) {
830
+ return {};
831
+ }
832
+
833
+ let ts;
834
+ try {
835
+ const tsModule = await import('typescript');
836
+ ts = tsModule.default ?? tsModule;
837
+ } catch (error) {
838
+ const message = error instanceof Error ? error.message : String(error);
839
+ console.warn(
840
+ `[bootstrap] Unable to load TypeScript for image() inference: ${message}`,
841
+ );
842
+ return {};
843
+ }
844
+
845
+ try {
846
+ return parseImageFieldKindsFromContentConfig({
847
+ sourceText: contentConfig.source,
848
+ filePath: contentConfig.filePath,
849
+ ts,
850
+ });
851
+ } catch (error) {
852
+ const message = error instanceof Error ? error.message : String(error);
853
+ console.warn(
854
+ `[bootstrap] Failed to parse image() fields from ${contentConfig.filePath}: ${message}`,
855
+ );
856
+ return {};
857
+ }
858
+ }
859
+
860
+ async function loadFieldOverrides(rootDir) {
861
+ const configPath = resolve(rootDir, FIELD_OVERRIDES_FILE);
862
+ const source = await readOptionalFile(configPath);
863
+ if (source === undefined) {
864
+ return {};
865
+ }
866
+
867
+ let parsed;
868
+ try {
869
+ parsed = JSON.parse(source);
870
+ } catch (error) {
871
+ const message = error instanceof Error ? error.message : String(error);
872
+ console.warn(`[bootstrap] Invalid JSON in ${configPath}: ${message}`);
873
+ return {};
874
+ }
875
+
876
+ if (!isRecord(parsed)) {
877
+ console.warn(
878
+ `[bootstrap] Ignoring ${FIELD_OVERRIDES_FILE}: expected a top-level object.`,
879
+ );
880
+ return {};
881
+ }
882
+
883
+ const overridesByCollection = {};
884
+
885
+ for (const [collectionName, fieldEntries] of Object.entries(parsed)) {
886
+ if (!isRecord(fieldEntries)) {
887
+ console.warn(
888
+ `[bootstrap] Ignoring ${collectionName} overrides: expected object of fields.`,
889
+ );
890
+ continue;
891
+ }
892
+
893
+ for (const [fieldKey, rawOverride] of Object.entries(fieldEntries)) {
894
+ const parsedOverride = fieldOverrideSchema.safeParse(rawOverride);
895
+ if (!parsedOverride.success) {
896
+ const issueMessage = parsedOverride.error.issues
897
+ .map((issue) => issue.message)
898
+ .join('; ');
899
+ console.warn(
900
+ `[bootstrap] Ignoring override ${collectionName}.${fieldKey}: ${issueMessage}`,
901
+ );
902
+ continue;
903
+ }
904
+
905
+ if (!overridesByCollection[collectionName]) {
906
+ overridesByCollection[collectionName] = {};
907
+ }
908
+ overridesByCollection[collectionName][fieldKey] = parsedOverride.data;
909
+ }
910
+ }
911
+
912
+ return overridesByCollection;
913
+ }
914
+
915
+ function asObjectSchema(value) {
916
+ if (
917
+ !isRecord(value) ||
918
+ value.type !== 'object' ||
919
+ !isRecord(value.properties)
920
+ ) {
921
+ return undefined;
922
+ }
923
+
924
+ return value;
925
+ }
926
+
927
+ function resolveAstroObjectSchema(schema) {
928
+ const direct = asObjectSchema(schema);
929
+ if (direct) {
930
+ return direct;
931
+ }
932
+
933
+ if (!isRecord(schema) || typeof schema.$ref !== 'string') {
934
+ return undefined;
935
+ }
936
+
937
+ if (!schema.$ref.startsWith(ASTRO_DEFINITION_PREFIX)) {
938
+ return undefined;
939
+ }
940
+
941
+ const definitionKey = schema.$ref.slice(ASTRO_DEFINITION_PREFIX.length);
942
+ const definitions = isRecord(schema.definitions)
943
+ ? schema.definitions
944
+ : undefined;
945
+ const resolvedDefinition = definitions?.[definitionKey];
946
+ return asObjectSchema(resolvedDefinition);
947
+ }
948
+
949
+ function resolveSchemaCompatibilityKind(propertySchema, desiredKind) {
950
+ if (!isRecord(propertySchema)) {
951
+ return undefined;
952
+ }
953
+
954
+ if (desiredKind === 'image' && propertySchema.type === 'string') {
955
+ return 'image';
956
+ }
957
+
958
+ if (
959
+ desiredKind === 'imageArray' &&
960
+ propertySchema.type === 'array' &&
961
+ isRecord(propertySchema.items) &&
962
+ propertySchema.items.type === 'string'
963
+ ) {
964
+ return 'imageArray';
965
+ }
966
+
967
+ if (desiredKind === 'color' && propertySchema.type === 'string') {
968
+ return 'color';
969
+ }
970
+
971
+ return undefined;
972
+ }
973
+
974
+ function getOverrideConfig(override) {
975
+ if (!override) {
976
+ return undefined;
977
+ }
978
+
979
+ if (override.type === 'image') {
980
+ return {
981
+ kind: override.multiple ? 'imageArray' : 'image',
982
+ mode: override.mode ?? 'asset',
983
+ };
984
+ }
985
+
986
+ if (override.type === 'color') {
987
+ return {
988
+ kind: 'color',
989
+ };
990
+ }
991
+
992
+ return undefined;
993
+ }
994
+
995
+ function resolveCollectionFieldUiKinds(params) {
996
+ const { collectionName, schema, inferredFieldKinds, overrideEntries } =
997
+ params;
998
+ const objectSchema = resolveAstroObjectSchema(schema);
999
+ if (!objectSchema) {
1000
+ return undefined;
1001
+ }
1002
+
1003
+ const resolvedKinds = {};
1004
+
1005
+ const applyKind = (fieldKey, nextKind, sourceLabel, mode = 'asset') => {
1006
+ if (!nextKind) {
1007
+ return;
1008
+ }
1009
+
1010
+ const propertySchema = objectSchema.properties[fieldKey];
1011
+ const compatibleKind = resolveSchemaCompatibilityKind(
1012
+ propertySchema,
1013
+ nextKind,
1014
+ );
1015
+
1016
+ if (!compatibleKind) {
1017
+ console.warn(
1018
+ `[bootstrap] Ignoring ${sourceLabel} override for ${collectionName}.${fieldKey}: incompatible with schema.`,
1019
+ );
1020
+ return;
1021
+ }
1022
+
1023
+ if (compatibleKind === 'image' || compatibleKind === 'imageArray') {
1024
+ resolvedKinds[fieldKey] = {
1025
+ kind: compatibleKind,
1026
+ mode: mode === 'public' ? 'public' : 'asset',
1027
+ };
1028
+ return;
1029
+ }
1030
+
1031
+ resolvedKinds[fieldKey] = {
1032
+ kind: compatibleKind,
1033
+ };
1034
+ };
1035
+
1036
+ for (const [fieldKey, inferredKind] of Object.entries(
1037
+ inferredFieldKinds ?? {},
1038
+ )) {
1039
+ applyKind(fieldKey, inferredKind, 'inferred', 'asset');
1040
+ }
1041
+
1042
+ for (const [fieldKey, override] of Object.entries(overrideEntries ?? {})) {
1043
+ const overrideConfig = getOverrideConfig(override);
1044
+ applyKind(fieldKey, overrideConfig?.kind, 'custom', overrideConfig?.mode);
1045
+ }
1046
+
1047
+ return Object.keys(resolvedKinds).length > 0 ? resolvedKinds : undefined;
1048
+ }
1049
+
1050
+ export async function bootstrapCollections(argv = [], options = {}) {
1051
+ const { env = process.env, cwd = process.cwd(), mode: defaultMode } = options;
1052
+ const mode =
1053
+ parseModeArg(argv) ??
1054
+ normalizePathInput(env.NODE_ENV) ??
1055
+ normalizePathInput(defaultMode);
1056
+ loadDotenv(cwd, mode, env);
1057
+ const { cliPath, forwardArgs } = parseRootPathArg(argv);
1058
+ const rootInput =
1059
+ normalizePathInput(cliPath) ??
1060
+ normalizePathInput(env[ROOT_ENV_KEY]) ??
1061
+ DEFAULT_ROOT_PATH;
1062
+
1063
+ const rootDir = resolve(cwd, rootInput);
1064
+
1065
+ let rootStats;
1066
+ try {
1067
+ rootStats = await stat(rootDir);
1068
+ } catch {
1069
+ throw new Error(`Root directory not found: ${rootDir}`);
1070
+ }
1071
+
1072
+ if (!rootStats.isDirectory()) {
1073
+ throw new Error(`Root path is not a directory: ${rootDir}`);
1074
+ }
1075
+
1076
+ let collectionsParsed = await buildCollectionsFromFilesystem(rootDir);
1077
+ const hasAnySchema = collectionsParsed.collections.some(
1078
+ (collection) => isRecord(collection) && collection.hasSchema === true,
1079
+ );
1080
+ if (!hasAnySchema && mode === 'development') {
1081
+ const schemaDirPath = resolve(rootDir, COLLECTIONS_RELATIVE_PATH_PREFIX);
1082
+ console.warn(
1083
+ `[bootstrap] No schema files found in ${schemaDirPath}; running "astro sync" in ${rootDir}.`,
1084
+ );
1085
+ await runAstroSync(rootDir, env);
1086
+ collectionsParsed = await buildCollectionsFromFilesystem(rootDir);
1087
+ }
1088
+
1089
+ if (
1090
+ !isRecord(collectionsParsed) ||
1091
+ !Array.isArray(collectionsParsed.collections)
1092
+ ) {
1093
+ const schemaDirPath = resolve(rootDir, COLLECTIONS_RELATIVE_PATH_PREFIX);
1094
+ throw new Error(
1095
+ `Invalid collections shape from filesystem scan. Expected "collections" array from ${schemaDirPath} and ${resolve(rootDir, CONTENT_RELATIVE_PATH_PREFIX)}.`,
1096
+ );
1097
+ }
1098
+
1099
+ const inferredFieldKindsByCollection =
1100
+ await loadInferredImageFieldKinds(rootDir);
1101
+ const fieldOverridesByCollection = await loadFieldOverrides(rootDir);
1102
+
1103
+ for (const collection of collectionsParsed.collections) {
1104
+ if (
1105
+ typeof collection === 'object' &&
1106
+ 'hasSchema' in collection &&
1107
+ collection.hasSchema
1108
+ ) {
1109
+ const schemaFilePath = resolve(
1110
+ rootDir,
1111
+ COLLECTIONS_RELATIVE_PATH_PREFIX + collection.name + '.schema.json',
1112
+ );
1113
+ let schemaRaw;
1114
+ try {
1115
+ schemaRaw = await readFile(schemaFilePath, 'utf8');
1116
+ } catch {
1117
+ throw new Error(
1118
+ `Schema file not found or unreadable: ${schemaFilePath}`,
1119
+ );
1120
+ }
1121
+
1122
+ let schemaParsed;
1123
+ try {
1124
+ schemaParsed = JSON.parse(schemaRaw);
1125
+ } catch (error) {
1126
+ const message = error instanceof Error ? error.message : String(error);
1127
+ throw new Error(
1128
+ `Schema JSON parse failed for ${schemaFilePath}: ${message}`,
1129
+ );
1130
+ }
1131
+
1132
+ collection.schema = schemaParsed;
1133
+ } else {
1134
+ collection.schema = undefined;
1135
+ }
1136
+
1137
+ if (!isRecord(collection) || typeof collection.name !== 'string') {
1138
+ continue;
1139
+ }
1140
+
1141
+ collection.fieldUi = resolveCollectionFieldUiKinds({
1142
+ collectionName: collection.name,
1143
+ schema: collection.schema,
1144
+ inferredFieldKinds: inferredFieldKindsByCollection[collection.name],
1145
+ overrideEntries: fieldOverridesByCollection[collection.name],
1146
+ });
1147
+ }
1148
+
1149
+ const filesByCollection = new Map();
1150
+ const usedSlugsByCollection = new Map();
1151
+ if (isRecord(collectionsParsed.entries)) {
1152
+ for (const [entryKey, collectionName] of Object.entries(
1153
+ collectionsParsed.entries,
1154
+ )) {
1155
+ if (typeof collectionName !== 'string') {
1156
+ continue;
1157
+ }
1158
+
1159
+ const parsedEntry = await parseCollectionEntry(
1160
+ rootDir,
1161
+ collectionName,
1162
+ entryKey,
1163
+ );
1164
+ const { slugBase, ...entryWithoutSlug } = parsedEntry;
1165
+ const existing = filesByCollection.get(collectionName) ?? [];
1166
+ const usedSlugs = usedSlugsByCollection.get(collectionName) ?? new Map();
1167
+ const id = makeUniqueSlug(slugBase, usedSlugs);
1168
+ usedSlugsByCollection.set(collectionName, usedSlugs);
1169
+ existing.push({
1170
+ ...entryWithoutSlug,
1171
+ id,
1172
+ });
1173
+ filesByCollection.set(collectionName, existing);
1174
+ }
1175
+ }
1176
+
1177
+ for (const collection of collectionsParsed.collections) {
1178
+ if (!isRecord(collection) || typeof collection.name !== 'string') {
1179
+ continue;
1180
+ }
1181
+
1182
+ collection.files = filesByCollection.get(collection.name) ?? [];
1183
+ }
1184
+
1185
+ return {
1186
+ rootDir,
1187
+ collectionsParsed,
1188
+ forwardArgs,
1189
+ };
1190
+ }
1191
+
1192
+ export function applyCollectionsEnv(targetEnv, bootstrapResult) {
1193
+ targetEnv.APP_COLLECTIONS_ROOT = bootstrapResult.rootDir;
1194
+ const collectionsJsonWithSchema = JSON.stringify(
1195
+ bootstrapResult.collectionsParsed,
1196
+ );
1197
+ targetEnv.APP_COLLECTIONS_JSON_B64 = Buffer.from(
1198
+ collectionsJsonWithSchema,
1199
+ 'utf8',
1200
+ ).toString('base64');
1201
+ }