@content-collections/core 0.6.4 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,135 +1,109 @@
1
- // src/config.ts
2
- import { z } from "zod";
1
+ // src/builder.ts
2
+ import path8 from "node:path";
3
3
 
4
- // src/utils.ts
5
- import camelcase from "camelcase";
6
- import pluralize from "pluralize";
7
- function generateTypeName(name) {
8
- const singularName = pluralize.singular(name);
9
- return camelcase(singularName, { pascalCase: true });
10
- }
11
- function isDefined(value) {
12
- return value !== void 0 && value !== null;
13
- }
14
- function orderByPath(a, b) {
15
- return a.path.localeCompare(b.path);
4
+ // src/cache.ts
5
+ import { createHash } from "node:crypto";
6
+ import { existsSync } from "node:fs";
7
+ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
8
+ import path, { join } from "node:path";
9
+ function createKey(config, input) {
10
+ return createHash("sha256").update(config).update(JSON.stringify(input)).digest("hex");
16
11
  }
17
-
18
- // src/config.ts
19
- var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
20
- function defineCollection(collection) {
21
- let typeName = collection.typeName;
22
- if (!typeName) {
23
- typeName = generateTypeName(collection.name);
24
- }
25
- let parser = collection.parser;
26
- if (!parser) {
27
- parser = "frontmatter";
12
+ async function createCacheDirectory(directory) {
13
+ const cacheDirectory = path.join(directory, ".content-collections", "cache");
14
+ if (!existsSync(cacheDirectory)) {
15
+ await mkdir(cacheDirectory, { recursive: true });
28
16
  }
29
- return {
30
- ...collection,
31
- typeName,
32
- parser,
33
- schema: collection.schema(z)
34
- };
17
+ return cacheDirectory;
35
18
  }
36
- function defineConfig(config) {
37
- return config;
19
+ function fileName(input) {
20
+ return input.replace(/[^a-z0-9]/gi, "_").toLowerCase();
38
21
  }
39
-
40
- // src/configurationReader.ts
41
- import * as esbuild from "esbuild";
42
- import fs from "fs/promises";
43
- import path from "path";
44
- import { existsSync } from "fs";
45
- import { createHash } from "crypto";
46
- var ConfigurationError = class extends Error {
47
- type;
48
- constructor(type, message) {
49
- super(message);
50
- this.type = type;
51
- }
52
- };
53
- var importPathPlugin = {
54
- name: "import-path",
55
- setup(build2) {
56
- build2.onResolve({ filter: /^\@content-collections\/core$/ }, () => {
57
- return { path: path.join(__dirname, "index.ts"), external: true };
58
- });
59
- }
60
- };
61
- var defaultConfigName = "content-collection-config.mjs";
62
- function resolveCacheDir(config, options) {
63
- if (options.cacheDir) {
64
- return options.cacheDir;
22
+ async function readMapping(mappingPath) {
23
+ if (existsSync(mappingPath)) {
24
+ try {
25
+ return JSON.parse(await readFile(mappingPath, "utf-8"));
26
+ } catch (e) {
27
+ console.error(
28
+ "Failed to parse the cache mapping. We will recreate the cache."
29
+ );
30
+ }
65
31
  }
66
- return path.join(path.dirname(config), ".content-collections", "cache");
32
+ return {};
67
33
  }
68
- var externalPackagesPlugin = (configPath) => ({
69
- name: "external-packages",
70
- setup: (build2) => {
71
- const filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/;
72
- build2.onResolve({ filter }, ({ path: path7 }) => {
73
- const external = !path7.includes(configPath);
74
- return { path: path7, external };
75
- });
76
- }
77
- });
78
- async function compile(configurationPath, outfile) {
79
- const plugins = [
80
- externalPackagesPlugin(configurationPath)
81
- ];
82
- if (process.env.NODE_ENV === "test") {
83
- plugins.push(importPathPlugin);
34
+ async function createCacheManager(baseDirectory, configChecksum) {
35
+ const cacheDirectory = await createCacheDirectory(baseDirectory);
36
+ const mappingPath = join(cacheDirectory, "mapping.json");
37
+ const mapping = await readMapping(mappingPath);
38
+ async function flush() {
39
+ await writeFile(mappingPath, JSON.stringify(mapping));
84
40
  }
85
- await esbuild.build({
86
- entryPoints: [configurationPath],
87
- packages: "external",
88
- bundle: true,
89
- platform: "node",
90
- format: "esm",
91
- plugins,
92
- outfile
93
- });
94
- }
95
- function createConfigurationReader() {
96
- return async (configurationPath, options = {
97
- configName: defaultConfigName
98
- }) => {
99
- if (!existsSync(configurationPath)) {
100
- throw new ConfigurationError(
101
- "Read",
102
- `configuration file ${configurationPath} does not exist`
103
- );
41
+ function cache(collection, file) {
42
+ const directory = join(
43
+ cacheDirectory,
44
+ fileName(collection),
45
+ fileName(file)
46
+ );
47
+ let collectionMapping = mapping[collection];
48
+ if (!collectionMapping) {
49
+ collectionMapping = {};
50
+ mapping[collection] = collectionMapping;
104
51
  }
105
- const cacheDir = resolveCacheDir(configurationPath, options);
106
- await fs.mkdir(cacheDir, { recursive: true });
107
- const outfile = path.join(cacheDir, options.configName);
108
- try {
109
- await compile(configurationPath, outfile);
110
- } catch (error) {
111
- throw new ConfigurationError(
112
- "Compile",
113
- `configuration file ${configurationPath} is invalid: ${error}`
114
- );
52
+ let fileMapping = collectionMapping[file];
53
+ if (!fileMapping) {
54
+ fileMapping = [];
55
+ collectionMapping[file] = fileMapping;
115
56
  }
116
- const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
117
- const hash = createHash("sha256");
118
- hash.update(await fs.readFile(outfile, "utf-8"));
119
- const checksum = hash.digest("hex");
57
+ let newFileMapping = [];
58
+ const cacheFn = async (input, fn) => {
59
+ const key = createKey(configChecksum, input);
60
+ newFileMapping.push(key);
61
+ const filePath = join(directory, `${key}.cache`);
62
+ if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
63
+ if (existsSync(filePath)) {
64
+ try {
65
+ return JSON.parse(await readFile(filePath, "utf-8"));
66
+ } catch (e) {
67
+ console.error(
68
+ "Failed to parse the cache file. We will recompute the value."
69
+ );
70
+ }
71
+ }
72
+ }
73
+ const output = await fn(input);
74
+ if (!existsSync(directory)) {
75
+ await mkdir(directory, { recursive: true });
76
+ }
77
+ await writeFile(filePath, JSON.stringify(output));
78
+ return output;
79
+ };
80
+ const tidyUp = async () => {
81
+ const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
82
+ for (const key of filesToDelete) {
83
+ const filePath = join(directory, `${key}.cache`);
84
+ if (existsSync(filePath)) {
85
+ await unlink(filePath);
86
+ }
87
+ }
88
+ if (collectionMapping) {
89
+ collectionMapping[file] = newFileMapping;
90
+ }
91
+ };
120
92
  return {
121
- ...module.default,
122
- path: configurationPath,
123
- generateTypes: true,
124
- checksum
93
+ cacheFn,
94
+ tidyUp
125
95
  };
96
+ }
97
+ return {
98
+ cache,
99
+ flush
126
100
  };
127
101
  }
128
102
 
129
103
  // src/collector.ts
130
- import fg from "fast-glob";
131
- import { readFile } from "fs/promises";
104
+ import { readFile as readFile2 } from "fs/promises";
132
105
  import path2 from "path";
106
+ import { glob } from "tinyglobby";
133
107
 
134
108
  // src/parser.ts
135
109
  import matter from "gray-matter";
@@ -166,6 +140,34 @@ var parsers = {
166
140
  }
167
141
  };
168
142
 
143
+ // src/utils.ts
144
+ import camelcase from "camelcase";
145
+ import pluralize from "pluralize";
146
+ function generateTypeName(name) {
147
+ const singularName = pluralize.singular(name);
148
+ return camelcase(singularName, { pascalCase: true });
149
+ }
150
+ function isDefined(value) {
151
+ return value !== void 0 && value !== null;
152
+ }
153
+ function orderByPath(a, b) {
154
+ return a.path.localeCompare(b.path);
155
+ }
156
+ function removeChildPaths(paths) {
157
+ return Array.from(
158
+ new Set(
159
+ paths.filter((path9) => {
160
+ return !paths.some((otherPath) => {
161
+ if (path9 === otherPath) {
162
+ return false;
163
+ }
164
+ return path9.startsWith(otherPath);
165
+ });
166
+ })
167
+ )
168
+ );
169
+ }
170
+
169
171
  // src/collector.ts
170
172
  var CollectError = class extends Error {
171
173
  type;
@@ -177,7 +179,7 @@ var CollectError = class extends Error {
177
179
  function createCollector(emitter, baseDirectory = ".") {
178
180
  async function read(filePath) {
179
181
  try {
180
- return await readFile(filePath, "utf-8");
182
+ return await readFile2(filePath, "utf-8");
181
183
  } catch (error) {
182
184
  emitter.emit("collector:read-error", {
183
185
  filePath,
@@ -187,7 +189,11 @@ function createCollector(emitter, baseDirectory = ".") {
187
189
  }
188
190
  }
189
191
  async function collectFile(collection, filePath) {
190
- const absolutePath = path2.join(baseDirectory, collection.directory, filePath);
192
+ const absolutePath = path2.join(
193
+ baseDirectory,
194
+ collection.directory,
195
+ filePath
196
+ );
191
197
  const file = await read(absolutePath);
192
198
  if (!file) {
193
199
  return null;
@@ -218,7 +224,8 @@ function createCollector(emitter, baseDirectory = ".") {
218
224
  }
219
225
  async function resolveCollection(collection) {
220
226
  const collectionDirectory = path2.join(baseDirectory, collection.directory);
221
- const filePaths = await fg(collection.include, {
227
+ const include = Array.isArray(collection.include) ? collection.include : [collection.include];
228
+ const filePaths = await glob(include, {
222
229
  cwd: collectionDirectory,
223
230
  onlyFiles: true,
224
231
  absolute: false,
@@ -245,32 +252,114 @@ function createCollector(emitter, baseDirectory = ".") {
245
252
  };
246
253
  }
247
254
 
248
- // src/writer.ts
249
- import fs2 from "fs/promises";
250
- import path3 from "path";
251
- import pluralize2 from "pluralize";
255
+ // src/synchronizer.ts
256
+ import path3 from "node:path";
257
+ import picomatch from "picomatch";
258
+ function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
259
+ function findCollections(filePath) {
260
+ const resolvedFilePath = path3.resolve(filePath);
261
+ return collections.filter((collection) => {
262
+ return resolvedFilePath.startsWith(
263
+ path3.resolve(baseDirectory, collection.directory)
264
+ );
265
+ });
266
+ }
267
+ function createRelativePath(collectionPath, filePath) {
268
+ const resolvedCollectionPath = path3.resolve(baseDirectory, collectionPath);
269
+ const resolvedFilePath = path3.resolve(filePath);
270
+ let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
271
+ if (relativePath.startsWith(path3.sep)) {
272
+ relativePath = relativePath.slice(path3.sep.length);
273
+ }
274
+ return relativePath;
275
+ }
276
+ function resolve2(filePath) {
277
+ const collections2 = findCollections(filePath);
278
+ return collections2.map((collection) => {
279
+ const relativePath = createRelativePath(collection.directory, filePath);
280
+ return {
281
+ collection,
282
+ relativePath
283
+ };
284
+ }).filter(({ collection, relativePath }) => {
285
+ return picomatch.isMatch(relativePath, collection.include, {
286
+ ignore: collection.exclude
287
+ });
288
+ });
289
+ }
290
+ function deleted(filePath) {
291
+ const resolvedCollections = resolve2(filePath);
292
+ if (resolvedCollections.length === 0) {
293
+ return false;
294
+ }
295
+ let changed2 = false;
296
+ for (const { collection, relativePath } of resolvedCollections) {
297
+ const index = collection.files.findIndex(
298
+ (file) => file.path === relativePath
299
+ );
300
+ const deleted2 = collection.files.splice(index, 1);
301
+ if (deleted2.length > 0) {
302
+ changed2 = true;
303
+ }
304
+ }
305
+ return changed2;
306
+ }
307
+ async function changed(filePath) {
308
+ const resolvedCollections = resolve2(filePath);
309
+ if (resolvedCollections.length === 0) {
310
+ return false;
311
+ }
312
+ let changed2 = false;
313
+ for (const { collection, relativePath } of resolvedCollections) {
314
+ const index = collection.files.findIndex(
315
+ (file2) => file2.path === relativePath
316
+ );
317
+ const file = await readCollectionFile(collection, relativePath);
318
+ if (file) {
319
+ changed2 = true;
320
+ if (index === -1) {
321
+ collection.files.push(file);
322
+ collection.files.sort(orderByPath);
323
+ } else {
324
+ collection.files[index] = file;
325
+ }
326
+ }
327
+ }
328
+ return changed2;
329
+ }
330
+ return {
331
+ deleted,
332
+ changed
333
+ };
334
+ }
335
+
336
+ // src/transformer.ts
337
+ import os from "node:os";
338
+ import { basename, dirname, extname } from "node:path";
339
+ import pLimit from "p-limit";
340
+ import { z as z2 } from "zod";
252
341
 
253
342
  // src/serializer.ts
254
- import z2 from "zod";
255
343
  import serializeJs from "serialize-javascript";
256
- var literalSchema = z2.union([
344
+ import z from "zod";
345
+ var literalSchema = z.union([
257
346
  // json
258
- z2.string(),
259
- z2.number(),
260
- z2.boolean(),
261
- z2.null(),
347
+ z.string(),
348
+ z.number(),
349
+ z.boolean(),
350
+ z.null(),
262
351
  // serializable-javascript
263
- z2.undefined(),
264
- z2.date(),
265
- z2.map(z2.unknown(), z2.unknown()),
266
- z2.set(z2.unknown()),
267
- z2.bigint()
352
+ z.undefined(),
353
+ z.date(),
354
+ z.map(z.unknown(), z.unknown()),
355
+ z.set(z.unknown()),
356
+ z.bigint()
268
357
  ]);
269
- var schema = z2.lazy(
270
- () => z2.union([literalSchema, z2.array(schema), z2.record(schema)])
358
+ var schema = z.lazy(
359
+ () => z.union([literalSchema, z.array(schema), z.record(schema)])
271
360
  );
272
361
  var extension = "js";
273
- var serializableSchema = z2.record(schema);
362
+ var serializableSchema = z.record(schema);
274
363
  function serialize(value) {
275
364
  const serializedValue = serializeJs(value, {
276
365
  space: 2,
@@ -280,85 +369,7 @@ function serialize(value) {
280
369
  return `export default ${serializedValue};`;
281
370
  }
282
371
 
283
- // src/writer.ts
284
- function createArrayConstName(name) {
285
- let suffix = name.charAt(0).toUpperCase() + name.slice(1);
286
- return "all" + pluralize2(suffix);
287
- }
288
- async function createDataFile(directory, collection) {
289
- const dataPath = path3.join(
290
- directory,
291
- `${createArrayConstName(collection.name)}.${extension}`
292
- );
293
- await fs2.writeFile(
294
- dataPath,
295
- serialize(collection.documents.map((doc) => doc.document))
296
- );
297
- }
298
- function createDataFiles(directory, collections) {
299
- return Promise.all(
300
- collections.map((collection) => createDataFile(directory, collection))
301
- );
302
- }
303
- async function createJavaScriptFile(directory, configuration) {
304
- const collections = configuration.collections.map(
305
- ({ name }) => createArrayConstName(name)
306
- );
307
- let content = `// generated by content-collections at ${/* @__PURE__ */ new Date()}
308
-
309
- `;
310
- for (const name of collections) {
311
- content += `import ${name} from "./${name}.${extension}";
312
- `;
313
- }
314
- content += "\n";
315
- content += "export { " + collections.join(", ") + " };\n";
316
- await fs2.writeFile(path3.join(directory, "index.js"), content, "utf-8");
317
- }
318
- function createImportPath(directory, target) {
319
- let importPath = path3.posix.join(
320
- ...path3.relative(directory, target).split(path3.sep)
321
- );
322
- if (!importPath.startsWith(".")) {
323
- importPath = "./" + importPath;
324
- }
325
- return importPath;
326
- }
327
- async function createTypeDefinitionFile(directory, configuration) {
328
- if (!configuration.generateTypes) {
329
- return;
330
- }
331
- const importPath = createImportPath(directory, configuration.path);
332
- let content = `import configuration from "${importPath}";
333
- import { GetTypeByName } from "@content-collections/core";
334
- `;
335
- const collections = configuration.collections;
336
- for (const collection of collections) {
337
- content += `
338
- `;
339
- content += `export type ${collection.typeName} = GetTypeByName<typeof configuration, "${collection.name}">;
340
- `;
341
- content += `export declare const ${createArrayConstName(
342
- collection.name
343
- )}: Array<${collection.typeName}>;
344
- `;
345
- }
346
- content += "\n";
347
- content += "export {};\n";
348
- await fs2.writeFile(path3.join(directory, "index.d.ts"), content, "utf-8");
349
- }
350
- async function createWriter(directory) {
351
- await fs2.mkdir(directory, { recursive: true });
352
- return {
353
- createJavaScriptFile: (configuration) => createJavaScriptFile(directory, configuration),
354
- createTypeDefinitionFile: (configuration) => createTypeDefinitionFile(directory, configuration),
355
- createDataFiles: (collections) => createDataFiles(directory, collections)
356
- };
357
- }
358
-
359
372
  // src/transformer.ts
360
- import { basename, dirname, extname } from "path";
361
- import { z as z3 } from "zod";
362
373
  var TransformError = class extends Error {
363
374
  type;
364
375
  constructor(type, message) {
@@ -366,8 +377,8 @@ var TransformError = class extends Error {
366
377
  this.type = type;
367
378
  }
368
379
  };
369
- function createPath(path7, ext) {
370
- let p = path7.slice(0, -ext.length);
380
+ function createPath(path9, ext) {
381
+ let p = path9.slice(0, -ext.length);
371
382
  if (p.endsWith("/index")) {
372
383
  p = p.slice(0, -6);
373
384
  }
@@ -377,15 +388,15 @@ function createTransformer(emitter, cacheManager) {
377
388
  function createSchema(parserName, schema2) {
378
389
  const parser = parsers[parserName];
379
390
  if (!parser.hasContent) {
380
- return z3.object(schema2);
391
+ return z2.object(schema2);
381
392
  }
382
- return z3.object({
383
- content: z3.string(),
393
+ return z2.object({
394
+ content: z2.string(),
384
395
  ...schema2
385
396
  });
386
397
  }
387
398
  async function parseFile(collection, file) {
388
- const { data, path: path7 } = file;
399
+ const { data, path: path9 } = file;
389
400
  const schema2 = createSchema(collection.parser, collection.schema);
390
401
  let parsedData = await schema2.safeParseAsync(data);
391
402
  if (!parsedData.success) {
@@ -396,7 +407,7 @@ function createTransformer(emitter, cacheManager) {
396
407
  });
397
408
  return null;
398
409
  }
399
- const ext = extname(path7);
410
+ const ext = extname(path9);
400
411
  let extension2 = ext;
401
412
  if (extension2.startsWith(".")) {
402
413
  extension2 = extension2.slice(1);
@@ -404,11 +415,11 @@ function createTransformer(emitter, cacheManager) {
404
415
  const document = {
405
416
  ...parsedData.data,
406
417
  _meta: {
407
- filePath: path7,
408
- fileName: basename(path7),
409
- directory: dirname(path7),
418
+ filePath: path9,
419
+ fileName: basename(path9),
420
+ directory: dirname(path9),
410
421
  extension: extension2,
411
- path: createPath(path7, ext)
422
+ path: createPath(path9, ext)
412
423
  }
413
424
  };
414
425
  return {
@@ -438,43 +449,48 @@ function createTransformer(emitter, cacheManager) {
438
449
  },
439
450
  collection: {
440
451
  name: collection.name,
441
- directory: collection.directory
452
+ directory: collection.directory,
453
+ documents: async () => {
454
+ return collection.documents.map((doc) => doc.document);
455
+ }
442
456
  },
443
457
  cache: cache.cacheFn
444
458
  };
445
459
  }
446
- async function transformCollection(collections, collection) {
447
- if (collection.transform) {
448
- const docs = [];
449
- for (const doc of collection.documents) {
450
- const cache = cacheManager.cache(
451
- collection.name,
452
- doc.document._meta.path
453
- );
454
- const context = createContext(collections, collection, cache);
455
- try {
456
- const document = await collection.transform(doc.document, context);
457
- docs.push({
458
- ...doc,
459
- document
460
- });
461
- await cache.tidyUp();
462
- } catch (error) {
463
- if (error instanceof TransformError) {
464
- emitter.emit("transformer:error", {
465
- collection,
466
- error
467
- });
468
- } else {
469
- emitter.emit("transformer:error", {
470
- collection,
471
- error: new TransformError("Transform", String(error))
472
- });
473
- }
474
- }
460
+ async function transformDocument(collections, collection, transform, doc) {
461
+ const cache = cacheManager.cache(collection.name, doc.document._meta.path);
462
+ const context2 = createContext(collections, collection, cache);
463
+ try {
464
+ const document = await transform(doc.document, context2);
465
+ await cache.tidyUp();
466
+ return {
467
+ ...doc,
468
+ document
469
+ };
470
+ } catch (error) {
471
+ if (error instanceof TransformError) {
472
+ emitter.emit("transformer:error", {
473
+ collection,
474
+ error
475
+ });
476
+ } else {
477
+ emitter.emit("transformer:error", {
478
+ collection,
479
+ error: new TransformError("Transform", String(error))
480
+ });
475
481
  }
482
+ }
483
+ }
484
+ async function transformCollection(collections, collection) {
485
+ const transform = collection.transform;
486
+ if (transform) {
487
+ const limit = pLimit(os.cpus().length);
488
+ const docs = collection.documents.map(
489
+ (doc) => limit(() => transformDocument(collections, collection, transform, doc))
490
+ );
491
+ const transformed = await Promise.all(docs);
476
492
  await cacheManager.flush();
477
- return docs;
493
+ return transformed.filter(isDefined);
478
494
  }
479
495
  return collection.documents;
480
496
  }
@@ -507,127 +523,460 @@ function createTransformer(emitter, cacheManager) {
507
523
  };
508
524
  }
509
525
 
510
- // src/synchronizer.ts
511
- import micromatch from "micromatch";
512
- import path4 from "path";
513
- function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
514
- function findCollections(filePath) {
515
- const resolvedFilePath = path4.resolve(filePath);
516
- return collections.filter((collection) => {
517
- return resolvedFilePath.startsWith(
518
- path4.resolve(baseDirectory, collection.directory)
519
- );
520
- });
526
+ // src/writer.ts
527
+ import fs from "node:fs/promises";
528
+ import path4 from "node:path";
529
+ import pluralize2 from "pluralize";
530
+ function createArrayConstName(name) {
531
+ let suffix = name.charAt(0).toUpperCase() + name.slice(1);
532
+ return "all" + pluralize2(suffix);
533
+ }
534
+ async function createDataFile(directory, collection) {
535
+ const dataPath = path4.join(
536
+ directory,
537
+ `${createArrayConstName(collection.name)}.${extension}`
538
+ );
539
+ await fs.writeFile(
540
+ dataPath,
541
+ serialize(collection.documents.map((doc) => doc.document))
542
+ );
543
+ }
544
+ function createDataFiles(directory, collections) {
545
+ return Promise.all(
546
+ collections.map((collection) => createDataFile(directory, collection))
547
+ );
548
+ }
549
+ async function createJavaScriptFile(directory, configuration) {
550
+ const collections = configuration.collections.map(
551
+ ({ name }) => createArrayConstName(name)
552
+ );
553
+ let content = `// generated by content-collections at ${/* @__PURE__ */ new Date()}
554
+
555
+ `;
556
+ for (const name of collections) {
557
+ content += `import ${name} from "./${name}.${extension}";
558
+ `;
521
559
  }
522
- function createRelativePath(collectionPath, filePath) {
523
- const resolvedCollectionPath = path4.resolve(baseDirectory, collectionPath);
524
- const resolvedFilePath = path4.resolve(filePath);
525
- let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
526
- if (relativePath.startsWith(path4.sep)) {
527
- relativePath = relativePath.slice(path4.sep.length);
528
- }
529
- return relativePath;
560
+ content += "\n";
561
+ content += "export { " + collections.join(", ") + " };\n";
562
+ await fs.writeFile(path4.join(directory, "index.js"), content, "utf-8");
563
+ }
564
+ function createImportPath(directory, target) {
565
+ let importPath = path4.posix.join(
566
+ ...path4.relative(directory, target).split(path4.sep)
567
+ );
568
+ if (!importPath.startsWith(".")) {
569
+ importPath = "./" + importPath;
530
570
  }
531
- function resolve(filePath) {
532
- const collections2 = findCollections(filePath);
533
- return collections2.map((collection) => {
534
- const relativePath = createRelativePath(collection.directory, filePath);
535
- return {
536
- collection,
537
- relativePath
538
- };
539
- }).filter(({ collection, relativePath }) => {
540
- return micromatch.isMatch(relativePath, collection.include, {
541
- ignore: collection.exclude
542
- });
543
- });
571
+ return importPath;
572
+ }
573
+ async function createTypeDefinitionFile(directory, configuration) {
574
+ if (!configuration.generateTypes) {
575
+ return;
544
576
  }
545
- function deleted(filePath) {
546
- const resolvedCollections = resolve(filePath);
547
- if (resolvedCollections.length === 0) {
548
- return false;
577
+ const importPath = createImportPath(directory, configuration.path);
578
+ let content = `import configuration from "${importPath}";
579
+ import { GetTypeByName } from "@content-collections/core";
580
+ `;
581
+ const collections = configuration.collections;
582
+ for (const collection of collections) {
583
+ content += `
584
+ `;
585
+ content += `export type ${collection.typeName} = GetTypeByName<typeof configuration, "${collection.name}">;
586
+ `;
587
+ content += `export declare const ${createArrayConstName(
588
+ collection.name
589
+ )}: Array<${collection.typeName}>;
590
+ `;
591
+ }
592
+ content += "\n";
593
+ content += "export {};\n";
594
+ await fs.writeFile(path4.join(directory, "index.d.ts"), content, "utf-8");
595
+ }
596
+ async function createWriter(directory) {
597
+ await fs.mkdir(directory, { recursive: true });
598
+ return {
599
+ createJavaScriptFile: (configuration) => createJavaScriptFile(directory, configuration),
600
+ createTypeDefinitionFile: (configuration) => createTypeDefinitionFile(directory, configuration),
601
+ createDataFiles: (collections) => createDataFiles(directory, collections)
602
+ };
603
+ }
604
+
605
+ // src/build.ts
606
+ async function createBuildContext({
607
+ emitter,
608
+ outputDirectory,
609
+ baseDirectory,
610
+ configuration
611
+ }) {
612
+ const collector = createCollector(emitter, baseDirectory);
613
+ const [writer, resolved, cacheManager] = await Promise.all([
614
+ createWriter(outputDirectory),
615
+ collector.collect(configuration.collections),
616
+ createCacheManager(baseDirectory, configuration.checksum)
617
+ ]);
618
+ const synchronizer = createSynchronizer(
619
+ collector.collectFile,
620
+ resolved,
621
+ baseDirectory
622
+ );
623
+ const transform = createTransformer(emitter, cacheManager);
624
+ return {
625
+ resolved,
626
+ writer,
627
+ synchronizer,
628
+ transform,
629
+ emitter,
630
+ cacheManager,
631
+ configuration
632
+ };
633
+ }
634
+ async function build({
635
+ emitter,
636
+ transform,
637
+ resolved,
638
+ writer,
639
+ configuration
640
+ }) {
641
+ const startedAt = Date.now();
642
+ emitter.emit("builder:start", {
643
+ startedAt
644
+ });
645
+ const collections = await transform(resolved);
646
+ await Promise.all([
647
+ writer.createDataFiles(collections),
648
+ writer.createTypeDefinitionFile(configuration),
649
+ writer.createJavaScriptFile(configuration)
650
+ ]);
651
+ const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map(
652
+ (collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document))
653
+ );
654
+ await Promise.all(pendingOnSuccess.filter(isDefined));
655
+ const stats = collections.reduce(
656
+ (acc, collection) => {
657
+ acc.collections++;
658
+ acc.documents += collection.documents.length;
659
+ return acc;
660
+ },
661
+ {
662
+ collections: 0,
663
+ documents: 0
549
664
  }
550
- let changed2 = false;
551
- for (const { collection, relativePath } of resolvedCollections) {
552
- const index = collection.files.findIndex(
553
- (file) => file.path === relativePath
554
- );
555
- const deleted2 = collection.files.splice(index, 1);
556
- if (deleted2.length > 0) {
557
- changed2 = true;
665
+ );
666
+ emitter.emit("builder:end", {
667
+ startedAt,
668
+ endedAt: Date.now(),
669
+ stats
670
+ });
671
+ }
672
+
673
+ // src/configurationReader.ts
674
+ import { createHash as createHash2 } from "node:crypto";
675
+ import { existsSync as existsSync2 } from "node:fs";
676
+ import fs3 from "node:fs/promises";
677
+ import path6 from "node:path";
678
+
679
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.21.4/node_modules/bundle-require/dist/index.js
680
+ import {
681
+ build as build2,
682
+ context
683
+ } from "esbuild";
684
+
685
+ // ../../node_modules/.pnpm/load-tsconfig@0.2.5/node_modules/load-tsconfig/dist/index.js
686
+ import path5 from "path";
687
+ import fs2 from "fs";
688
+ import { createRequire } from "module";
689
+ var singleComment = Symbol("singleComment");
690
+ var multiComment = Symbol("multiComment");
691
+ var stripWithoutWhitespace = () => "";
692
+ var stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, " ");
693
+ var isEscaped = (jsonString, quotePosition) => {
694
+ let index = quotePosition - 1;
695
+ let backslashCount = 0;
696
+ while (jsonString[index] === "\\") {
697
+ index -= 1;
698
+ backslashCount += 1;
699
+ }
700
+ return Boolean(backslashCount % 2);
701
+ };
702
+ function stripJsonComments(jsonString, { whitespace = true, trailingCommas = false } = {}) {
703
+ if (typeof jsonString !== "string") {
704
+ throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
705
+ }
706
+ const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
707
+ let isInsideString = false;
708
+ let isInsideComment = false;
709
+ let offset = 0;
710
+ let buffer = "";
711
+ let result = "";
712
+ let commaIndex = -1;
713
+ for (let index = 0; index < jsonString.length; index++) {
714
+ const currentCharacter = jsonString[index];
715
+ const nextCharacter = jsonString[index + 1];
716
+ if (!isInsideComment && currentCharacter === '"') {
717
+ const escaped = isEscaped(jsonString, index);
718
+ if (!escaped) {
719
+ isInsideString = !isInsideString;
558
720
  }
559
721
  }
560
- return changed2;
561
- }
562
- async function changed(filePath) {
563
- const resolvedCollections = resolve(filePath);
564
- if (resolvedCollections.length === 0) {
565
- return false;
722
+ if (isInsideString) {
723
+ continue;
566
724
  }
567
- let changed2 = false;
568
- for (const { collection, relativePath } of resolvedCollections) {
569
- const index = collection.files.findIndex(
570
- (file2) => file2.path === relativePath
571
- );
572
- const file = await readCollectionFile(collection, relativePath);
573
- if (file) {
574
- changed2 = true;
575
- if (index === -1) {
576
- collection.files.push(file);
577
- collection.files.sort(orderByPath);
578
- } else {
579
- collection.files[index] = file;
725
+ if (!isInsideComment && currentCharacter + nextCharacter === "//") {
726
+ buffer += jsonString.slice(offset, index);
727
+ offset = index;
728
+ isInsideComment = singleComment;
729
+ index++;
730
+ } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === "\r\n") {
731
+ index++;
732
+ isInsideComment = false;
733
+ buffer += strip(jsonString, offset, index);
734
+ offset = index;
735
+ continue;
736
+ } else if (isInsideComment === singleComment && currentCharacter === "\n") {
737
+ isInsideComment = false;
738
+ buffer += strip(jsonString, offset, index);
739
+ offset = index;
740
+ } else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
741
+ buffer += jsonString.slice(offset, index);
742
+ offset = index;
743
+ isInsideComment = multiComment;
744
+ index++;
745
+ continue;
746
+ } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === "*/") {
747
+ index++;
748
+ isInsideComment = false;
749
+ buffer += strip(jsonString, offset, index + 1);
750
+ offset = index + 1;
751
+ continue;
752
+ } else if (trailingCommas && !isInsideComment) {
753
+ if (commaIndex !== -1) {
754
+ if (currentCharacter === "}" || currentCharacter === "]") {
755
+ buffer += jsonString.slice(offset, index);
756
+ result += strip(buffer, 0, 1) + buffer.slice(1);
757
+ buffer = "";
758
+ offset = index;
759
+ commaIndex = -1;
760
+ } else if (currentCharacter !== " " && currentCharacter !== " " && currentCharacter !== "\r" && currentCharacter !== "\n") {
761
+ buffer += jsonString.slice(offset, index);
762
+ offset = index;
763
+ commaIndex = -1;
580
764
  }
765
+ } else if (currentCharacter === ",") {
766
+ result += buffer + jsonString.slice(offset, index);
767
+ buffer = "";
768
+ offset = index;
769
+ commaIndex = index;
581
770
  }
582
771
  }
583
- return changed2;
584
772
  }
585
- return {
586
- deleted,
587
- changed
588
- };
773
+ return result + buffer + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
589
774
  }
590
-
591
- // src/builder.ts
592
- import path6 from "path";
593
-
594
- // src/watcher.ts
595
- import * as watcher from "@parcel/watcher";
596
- async function createWatcher(emitter, paths, sync, build2) {
597
- const onChange = async (error, events) => {
598
- if (error) {
599
- console.error(error);
600
- return;
775
+ function jsoncParse(data) {
776
+ try {
777
+ return new Function("return " + stripJsonComments(data).trim())();
778
+ } catch (_) {
779
+ return {};
780
+ }
781
+ }
782
+ var req = true ? createRequire(import.meta.url) : __require;
783
+ var findUp = (name, startDir, stopDir = path5.parse(startDir).root) => {
784
+ let dir = startDir;
785
+ while (dir !== stopDir) {
786
+ const file = path5.join(dir, name);
787
+ if (fs2.existsSync(file))
788
+ return file;
789
+ if (!file.endsWith(".json")) {
790
+ const fileWithExt = file + ".json";
791
+ if (fs2.existsSync(fileWithExt))
792
+ return fileWithExt;
601
793
  }
602
- let rebuild = false;
603
- for (const event of events) {
604
- if (await sync(event.type, event.path)) {
605
- emitter.emit("watcher:file-changed", {
606
- filePath: event.path,
607
- modification: event.type
794
+ dir = path5.dirname(dir);
795
+ }
796
+ return null;
797
+ };
798
+ var resolveTsConfigFromFile = (cwd, filename) => {
799
+ if (path5.isAbsolute(filename))
800
+ return fs2.existsSync(filename) ? filename : null;
801
+ return findUp(filename, cwd);
802
+ };
803
+ var resolveTsConfigFromExtends = (cwd, name) => {
804
+ if (path5.isAbsolute(name))
805
+ return fs2.existsSync(name) ? name : null;
806
+ if (name.startsWith("."))
807
+ return findUp(name, cwd);
808
+ const id = req.resolve(name, { paths: [cwd] });
809
+ return id;
810
+ };
811
+ var loadTsConfigInternal = (dir = process.cwd(), name = "tsconfig.json", isExtends = false) => {
812
+ var _a, _b;
813
+ dir = path5.resolve(dir);
814
+ const id = isExtends ? resolveTsConfigFromExtends(dir, name) : resolveTsConfigFromFile(dir, name);
815
+ if (!id)
816
+ return null;
817
+ const data = jsoncParse(fs2.readFileSync(id, "utf-8"));
818
+ const configDir = path5.dirname(id);
819
+ if ((_a = data.compilerOptions) == null ? void 0 : _a.baseUrl) {
820
+ data.compilerOptions.baseUrl = path5.join(
821
+ configDir,
822
+ data.compilerOptions.baseUrl
823
+ );
824
+ }
825
+ let extendsFiles = [];
826
+ if (data.extends) {
827
+ const extendsList = Array.isArray(data.extends) ? data.extends : [data.extends];
828
+ const extendsData = {};
829
+ for (const name2 of extendsList) {
830
+ const parentConfig = loadTsConfigInternal(configDir, name2, true);
831
+ if (parentConfig) {
832
+ Object.assign(extendsData, {
833
+ ...parentConfig == null ? void 0 : parentConfig.data,
834
+ compilerOptions: {
835
+ ...extendsData.compilerOptions,
836
+ ...(_b = parentConfig == null ? void 0 : parentConfig.data) == null ? void 0 : _b.compilerOptions
837
+ }
608
838
  });
609
- rebuild = true;
839
+ extendsFiles.push(...parentConfig.files);
610
840
  }
611
841
  }
612
- if (rebuild) {
613
- await build2();
842
+ Object.assign(data, {
843
+ ...extendsData,
844
+ ...data,
845
+ compilerOptions: {
846
+ ...extendsData.compilerOptions,
847
+ ...data.compilerOptions
848
+ }
849
+ });
850
+ }
851
+ delete data.extends;
852
+ return { path: id, data, files: [...extendsFiles, id] };
853
+ };
854
+ var loadTsConfig = (dir, name) => loadTsConfigInternal(dir, name);
855
+
856
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.21.4/node_modules/bundle-require/dist/index.js
857
+ var tsconfigPathsToRegExp = (paths) => {
858
+ return Object.keys(paths || {}).map((key) => {
859
+ return new RegExp(`^${key.replace(/\*/, ".*")}$`);
860
+ });
861
+ };
862
+ var match = (id, patterns) => {
863
+ if (!patterns)
864
+ return false;
865
+ return patterns.some((p) => {
866
+ if (p instanceof RegExp) {
867
+ return p.test(id);
614
868
  }
615
- };
616
- const subscriptions = await Promise.all(
617
- paths.map((path7) => watcher.subscribe(path7, onChange))
618
- );
869
+ return id === p || id.startsWith(p + "/");
870
+ });
871
+ };
872
+
873
+ // src/esbuild.ts
874
+ import { build as build3 } from "esbuild";
875
+ import { dirname as dirname2, join as join2 } from "node:path";
876
+ function tsconfigResolvePaths(configPath) {
877
+ let tsconfig = loadTsConfig(dirname2(configPath));
878
+ if (!tsconfig) {
879
+ tsconfig = loadTsConfig();
880
+ }
881
+ return tsconfig?.data?.compilerOptions?.paths || {};
882
+ }
883
+ function createExternalsPlugin(configPath) {
884
+ const resolvedPaths = tsconfigResolvePaths(configPath);
885
+ const resolvePatterns = tsconfigPathsToRegExp(resolvedPaths);
619
886
  return {
620
- unsubscribe: async () => {
621
- await Promise.all(
622
- subscriptions.map((subscription) => subscription.unsubscribe())
887
+ name: "external-packages",
888
+ setup: (build4) => {
889
+ const filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/;
890
+ build4.onResolve({ filter }, ({ path: path9, kind }) => {
891
+ if (match(path9, resolvePatterns)) {
892
+ if (kind === "dynamic-import") {
893
+ return { path: path9, external: true };
894
+ }
895
+ return;
896
+ }
897
+ return { path: path9, external: true };
898
+ });
899
+ }
900
+ };
901
+ }
902
+ var importPathPlugin = {
903
+ name: "import-path",
904
+ setup(build4) {
905
+ build4.onResolve({ filter: /^\@content-collections\/core$/ }, () => {
906
+ return { path: join2(__dirname, "index.ts"), external: true };
907
+ });
908
+ }
909
+ };
910
+ async function compile(configurationPath, outfile) {
911
+ const plugins = [createExternalsPlugin(configurationPath)];
912
+ if (process.env.NODE_ENV === "test") {
913
+ plugins.push(importPathPlugin);
914
+ }
915
+ const result = await build3({
916
+ entryPoints: [configurationPath],
917
+ packages: "external",
918
+ bundle: true,
919
+ platform: "node",
920
+ format: "esm",
921
+ plugins,
922
+ outfile,
923
+ metafile: true
924
+ });
925
+ return Object.keys(result.metafile.inputs);
926
+ }
927
+
928
+ // src/configurationReader.ts
929
+ var ConfigurationError = class extends Error {
930
+ type;
931
+ constructor(type, message) {
932
+ super(message);
933
+ this.type = type;
934
+ }
935
+ };
936
+ var defaultConfigName = "content-collection-config.mjs";
937
+ function resolveCacheDir(config, options) {
938
+ if (options.cacheDir) {
939
+ return options.cacheDir;
940
+ }
941
+ return path6.join(path6.dirname(config), ".content-collections", "cache");
942
+ }
943
+ function createConfigurationReader() {
944
+ return async (configurationPath, options = {
945
+ configName: defaultConfigName
946
+ }) => {
947
+ if (!existsSync2(configurationPath)) {
948
+ throw new ConfigurationError(
949
+ "Read",
950
+ `configuration file ${configurationPath} does not exist`
951
+ );
952
+ }
953
+ const cacheDir = resolveCacheDir(configurationPath, options);
954
+ await fs3.mkdir(cacheDir, { recursive: true });
955
+ const outfile = path6.join(cacheDir, options.configName);
956
+ try {
957
+ const configurationPaths = await compile(configurationPath, outfile);
958
+ const module = await import(`file://${path6.resolve(outfile)}?x=${Date.now()}`);
959
+ const hash = createHash2("sha256");
960
+ hash.update(await fs3.readFile(outfile, "utf-8"));
961
+ const checksum = hash.digest("hex");
962
+ return {
963
+ ...module.default,
964
+ path: configurationPath,
965
+ inputPaths: configurationPaths.map((p) => path6.resolve(p)),
966
+ generateTypes: true,
967
+ checksum
968
+ };
969
+ } catch (error) {
970
+ throw new ConfigurationError(
971
+ "Compile",
972
+ `configuration file ${configurationPath} is invalid: ${error}`
623
973
  );
624
- return;
625
974
  }
626
975
  };
627
976
  }
628
977
 
629
978
  // src/events.ts
630
- import { EventEmitter } from "events";
979
+ import { EventEmitter } from "node:events";
631
980
  function isEventWithError(event) {
632
981
  return typeof event === "object" && event !== null && "error" in event;
633
982
  }
@@ -655,102 +1004,43 @@ function createEmitter() {
655
1004
  };
656
1005
  }
657
1006
 
658
- // src/cache.ts
659
- import path5, { join } from "path";
660
- import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
661
- import { existsSync as existsSync2 } from "fs";
662
- import { createHash as createHash2 } from "crypto";
663
- function createKey(config, input) {
664
- return createHash2("sha256").update(config).update(JSON.stringify(input)).digest("hex");
665
- }
666
- async function createCacheDirectory(directory) {
667
- const cacheDirectory = path5.join(directory, ".content-collections", "cache");
668
- if (!existsSync2(cacheDirectory)) {
669
- await mkdir(cacheDirectory, { recursive: true });
670
- }
671
- return cacheDirectory;
672
- }
673
- function fileName(input) {
674
- return input.replace(/[^a-z0-9]/gi, "_").toLowerCase();
675
- }
676
- async function readMapping(mappingPath) {
677
- if (existsSync2(mappingPath)) {
678
- try {
679
- return JSON.parse(await readFile2(mappingPath, "utf-8"));
680
- } catch (e) {
681
- console.error(
682
- "Failed to parse the cache mapping. We will recreate the cache."
683
- );
684
- }
685
- }
686
- return {};
687
- }
688
- async function createCacheManager(baseDirectory, configChecksum) {
689
- const cacheDirectory = await createCacheDirectory(baseDirectory);
690
- const mappingPath = join(cacheDirectory, "mapping.json");
691
- const mapping = await readMapping(mappingPath);
692
- async function flush() {
693
- await writeFile(mappingPath, JSON.stringify(mapping));
694
- }
695
- function cache(collection, file) {
696
- const directory = join(
697
- cacheDirectory,
698
- fileName(collection),
699
- fileName(file)
700
- );
701
- let collectionMapping = mapping[collection];
702
- if (!collectionMapping) {
703
- collectionMapping = {};
704
- mapping[collection] = collectionMapping;
1007
+ // src/watcher.ts
1008
+ import * as watcher from "@parcel/watcher";
1009
+ import path7, { dirname as dirname3, resolve } from "node:path";
1010
+ async function createWatcher(emitter, baseDirectory, configuration, sync) {
1011
+ const onChange = async (error, events) => {
1012
+ if (error) {
1013
+ emitter.emit("watcher:subscribe-error", {
1014
+ paths,
1015
+ error
1016
+ });
1017
+ return;
705
1018
  }
706
- let fileMapping = collectionMapping[file];
707
- if (!fileMapping) {
708
- fileMapping = [];
709
- collectionMapping[file] = fileMapping;
1019
+ for (const event of events) {
1020
+ await sync(event.type, event.path);
710
1021
  }
711
- let newFileMapping = [];
712
- const cacheFn = async (input, fn) => {
713
- const key = createKey(configChecksum, input);
714
- newFileMapping.push(key);
715
- const filePath = join(directory, `${key}.cache`);
716
- if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
717
- if (existsSync2(filePath)) {
718
- try {
719
- return JSON.parse(await readFile2(filePath, "utf-8"));
720
- } catch (e) {
721
- console.error(
722
- "Failed to parse the cache file. We will recompute the value."
723
- );
724
- }
725
- }
726
- }
727
- const output = await fn(input);
728
- if (!existsSync2(directory)) {
729
- await mkdir(directory, { recursive: true });
730
- }
731
- await writeFile(filePath, JSON.stringify(output));
732
- return output;
733
- };
734
- const tidyUp = async () => {
735
- const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
736
- for (const key of filesToDelete) {
737
- const filePath = join(directory, `${key}.cache`);
738
- if (existsSync2(filePath)) {
739
- await unlink(filePath);
740
- }
741
- }
742
- if (collectionMapping) {
743
- collectionMapping[file] = newFileMapping;
744
- }
745
- };
746
- return {
747
- cacheFn,
748
- tidyUp
749
- };
750
- }
1022
+ };
1023
+ const paths = removeChildPaths([
1024
+ ...configuration.collections.map((collection) => path7.join(baseDirectory, collection.directory)).map((p) => resolve(p)),
1025
+ ...configuration.inputPaths.map((p) => dirname3(p))
1026
+ ]);
1027
+ const subscriptions = (await Promise.all(paths.map((path9) => watcher.subscribe(path9, onChange)))).filter(isDefined);
1028
+ emitter.emit("watcher:subscribed", {
1029
+ paths
1030
+ });
751
1031
  return {
752
- cache,
753
- flush
1032
+ unsubscribe: async () => {
1033
+ if (!subscriptions || subscriptions.length === 0) {
1034
+ return;
1035
+ }
1036
+ await Promise.all(
1037
+ subscriptions.map((subscription) => subscription.unsubscribe())
1038
+ );
1039
+ emitter.emit("watcher:unsubscribed", {
1040
+ paths
1041
+ });
1042
+ return;
1043
+ }
754
1044
  };
755
1045
  }
756
1046
 
@@ -759,69 +1049,137 @@ function resolveOutputDir(baseDirectory, options) {
759
1049
  if (options.outputDir) {
760
1050
  return options.outputDir;
761
1051
  }
762
- return path6.join(baseDirectory, ".content-collections", "generated");
1052
+ return path8.join(baseDirectory, ".content-collections", "generated");
763
1053
  }
1054
+ var ConfigurationReloadError = class extends Error {
1055
+ constructor(message) {
1056
+ super(message);
1057
+ }
1058
+ };
764
1059
  async function createBuilder(configurationPath, options = {
765
1060
  configName: defaultConfigName
766
- }) {
767
- const emitter = createEmitter();
1061
+ }, emitter = createEmitter()) {
768
1062
  const readConfiguration = createConfigurationReader();
769
- const configuration = await readConfiguration(configurationPath, options);
770
- const baseDirectory = path6.dirname(configurationPath);
771
- const directory = resolveOutputDir(baseDirectory, options);
772
- const collector = createCollector(emitter, baseDirectory);
773
- const writer = await createWriter(directory);
774
- const [resolved] = await Promise.all([
775
- collector.collect(configuration.collections),
776
- writer.createJavaScriptFile(configuration),
777
- writer.createTypeDefinitionFile(configuration)
778
- ]);
779
- const synchronizer = createSynchronizer(
780
- collector.collectFile,
781
- resolved,
782
- baseDirectory
783
- );
784
- const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
785
- const transform = createTransformer(emitter, cacheManager);
1063
+ const baseDirectory = path8.dirname(configurationPath);
1064
+ const outputDirectory = resolveOutputDir(baseDirectory, options);
1065
+ emitter.emit("builder:created", {
1066
+ createdAt: Date.now(),
1067
+ configurationPath,
1068
+ outputDirectory
1069
+ });
1070
+ let configuration = await readConfiguration(configurationPath, options);
1071
+ let watcher2 = null;
1072
+ let context2 = await createBuildContext({
1073
+ emitter,
1074
+ baseDirectory,
1075
+ outputDirectory,
1076
+ configuration
1077
+ });
786
1078
  async function sync(modification, filePath) {
787
- if (modification === "delete") {
788
- return synchronizer.deleted(filePath);
1079
+ if (configuration.inputPaths.includes(filePath)) {
1080
+ if (await onConfigurationChange()) {
1081
+ emitter.emit("watcher:config-changed", {
1082
+ filePath,
1083
+ modification
1084
+ });
1085
+ await build(context2);
1086
+ return true;
1087
+ }
1088
+ } else {
1089
+ if (await onFileChange(modification, filePath)) {
1090
+ emitter.emit("watcher:file-changed", {
1091
+ filePath,
1092
+ modification
1093
+ });
1094
+ await build(context2);
1095
+ return true;
1096
+ }
789
1097
  }
790
- return synchronizer.changed(filePath);
1098
+ return false;
791
1099
  }
792
- async function build2() {
793
- const startedAt = Date.now();
794
- emitter.emit("builder:start", {
795
- startedAt
796
- });
797
- const collections = await transform(resolved);
798
- await writer.createDataFiles(collections);
799
- const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map(
800
- (collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document))
801
- );
802
- await Promise.all(pendingOnSuccess.filter(isDefined));
803
- emitter.emit("builder:end", {
804
- startedAt,
805
- endedAt: Date.now()
1100
+ async function onConfigurationChange() {
1101
+ try {
1102
+ configuration = await readConfiguration(configurationPath, options);
1103
+ } catch (error) {
1104
+ emitter.emit("watcher:config-reload-error", {
1105
+ error: new ConfigurationReloadError(
1106
+ `Failed to reload configuration: ${error}`
1107
+ ),
1108
+ configurationPath
1109
+ });
1110
+ return false;
1111
+ }
1112
+ if (watcher2) {
1113
+ await watcher2.unsubscribe();
1114
+ }
1115
+ context2 = await createBuildContext({
1116
+ emitter,
1117
+ baseDirectory,
1118
+ outputDirectory,
1119
+ configuration
806
1120
  });
1121
+ if (watcher2) {
1122
+ watcher2 = await createWatcher(
1123
+ emitter,
1124
+ baseDirectory,
1125
+ configuration,
1126
+ sync
1127
+ );
1128
+ }
1129
+ return true;
1130
+ }
1131
+ async function onFileChange(modification, filePath) {
1132
+ const { synchronizer } = context2;
1133
+ if (modification === "delete") {
1134
+ return synchronizer.deleted(filePath);
1135
+ } else {
1136
+ return synchronizer.changed(filePath);
1137
+ }
807
1138
  }
808
1139
  async function watch() {
809
- const paths = resolved.map(
810
- (collection) => path6.join(baseDirectory, collection.directory)
811
- );
812
- const watcher2 = await createWatcher(emitter, paths, sync, build2);
813
- return watcher2;
1140
+ watcher2 = await createWatcher(emitter, baseDirectory, configuration, sync);
1141
+ return {
1142
+ unsubscribe: async () => {
1143
+ if (watcher2) {
1144
+ await watcher2.unsubscribe();
1145
+ }
1146
+ }
1147
+ };
814
1148
  }
815
1149
  return {
1150
+ build: () => build(context2),
816
1151
  sync,
817
- build: build2,
818
1152
  watch,
819
1153
  on: emitter.on
820
1154
  };
821
1155
  }
1156
+
1157
+ // src/config.ts
1158
+ import { z as z3 } from "zod";
1159
+ var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
1160
+ function defineCollection(collection) {
1161
+ let typeName = collection.typeName;
1162
+ if (!typeName) {
1163
+ typeName = generateTypeName(collection.name);
1164
+ }
1165
+ let parser = collection.parser;
1166
+ if (!parser) {
1167
+ parser = "frontmatter";
1168
+ }
1169
+ return {
1170
+ ...collection,
1171
+ typeName,
1172
+ parser,
1173
+ schema: collection.schema(z3)
1174
+ };
1175
+ }
1176
+ function defineConfig(config) {
1177
+ return config;
1178
+ }
822
1179
  export {
823
1180
  CollectError,
824
1181
  ConfigurationError,
1182
+ ConfigurationReloadError,
825
1183
  TransformError,
826
1184
  createBuilder,
827
1185
  defineCollection,