@content-collections/core 0.8.2 → 0.9.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +55 -31
  2. package/dist/index.js +920 -846
  3. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -102,948 +102,986 @@ async function createCacheManager(baseDirectory, configChecksum) {
102
102
 
103
103
  // src/collector.ts
104
104
  import { readFile as readFile2 } from "fs/promises";
105
- import path3 from "node:path";
105
+ import path5 from "node:path";
106
106
  import { glob } from "tinyglobby";
107
107
 
108
108
  // src/parser.ts
109
109
  import matter from "gray-matter";
110
110
  import { parse, stringify } from "yaml";
111
- function parseYaml(content) {
112
- return parse(content.trim());
113
- }
114
- function frontmatter(fileContent) {
115
- return matter(fileContent, {
116
- engines: {
117
- yaml: {
118
- parse: parseYaml,
119
- stringify
120
- }
121
- }
122
- });
123
- }
124
- function frontmatterParser(fileContent) {
125
- const { data, content } = frontmatter(fileContent);
126
- return {
127
- ...data,
128
- content: content.trim()
129
- };
130
- }
131
- function frontmatterOnlyParser(fileContent) {
132
- const { data } = frontmatter(fileContent);
133
- return data;
134
- }
135
- var parsers = {
136
- frontmatter: {
137
- hasContent: true,
138
- parse: frontmatterParser
139
- },
140
- ["frontmatter-only"]: {
141
- hasContent: false,
142
- parse: frontmatterOnlyParser
143
- },
144
- json: {
145
- hasContent: false,
146
- parse: JSON.parse
147
- },
148
- yaml: {
149
- hasContent: false,
150
- parse: parseYaml
151
- }
152
- };
153
111
 
154
- // src/utils.ts
155
- import camelcase from "camelcase";
156
- import pluralize from "pluralize";
157
- import path2 from "node:path";
158
- function generateTypeName(name) {
159
- const singularName = pluralize.singular(name);
160
- return camelcase(singularName, { pascalCase: true });
161
- }
162
- function isDefined(value) {
163
- return value !== void 0 && value !== null;
164
- }
165
- function orderByPath(a, b) {
166
- return a.path.localeCompare(b.path);
167
- }
168
- function removeChildPaths(paths) {
169
- return Array.from(
170
- new Set(
171
- paths.filter((path10) => {
172
- return !paths.some((otherPath) => {
173
- if (path10 === otherPath) {
174
- return false;
175
- }
176
- return path10.startsWith(otherPath);
177
- });
178
- })
179
- )
180
- );
181
- }
182
- function posixToNativePath(pathName) {
183
- if (path2.sep !== path2.posix.sep) {
184
- return pathName.replaceAll(path2.posix.sep, path2.sep);
185
- }
186
- return pathName;
187
- }
112
+ // src/configurationReader.ts
113
+ import { createHash as createHash2 } from "node:crypto";
114
+ import { existsSync as existsSync2 } from "node:fs";
115
+ import fs2 from "node:fs/promises";
116
+ import path3 from "node:path";
188
117
 
189
- // src/collector.ts
190
- var CollectError = class extends Error {
191
- type;
192
- constructor(type, message) {
193
- super(message);
194
- this.type = type;
118
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
119
+ import {
120
+ build,
121
+ context
122
+ } from "esbuild";
123
+
124
+ // ../../node_modules/.pnpm/load-tsconfig@0.2.5/node_modules/load-tsconfig/dist/index.js
125
+ import path2 from "path";
126
+ import fs from "fs";
127
+ import { createRequire } from "module";
128
+ var singleComment = Symbol("singleComment");
129
+ var multiComment = Symbol("multiComment");
130
+ var stripWithoutWhitespace = () => "";
131
+ var stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, " ");
132
+ var isEscaped = (jsonString, quotePosition) => {
133
+ let index = quotePosition - 1;
134
+ let backslashCount = 0;
135
+ while (jsonString[index] === "\\") {
136
+ index -= 1;
137
+ backslashCount += 1;
195
138
  }
139
+ return Boolean(backslashCount % 2);
196
140
  };
197
- function createCollector(emitter, baseDirectory = ".") {
198
- async function read(filePath) {
199
- try {
200
- return await readFile2(filePath, "utf-8");
201
- } catch (error) {
202
- emitter.emit("collector:read-error", {
203
- filePath,
204
- error: new CollectError("Read", String(error))
205
- });
206
- return null;
207
- }
141
+ function stripJsonComments(jsonString, { whitespace = true, trailingCommas = false } = {}) {
142
+ if (typeof jsonString !== "string") {
143
+ throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
208
144
  }
209
- async function collectFile(collection, filePath) {
210
- const absolutePath = path3.join(
211
- baseDirectory,
212
- collection.directory,
213
- filePath
214
- );
215
- const file = await read(absolutePath);
216
- if (!file) {
217
- return null;
145
+ const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
146
+ let isInsideString = false;
147
+ let isInsideComment = false;
148
+ let offset = 0;
149
+ let buffer = "";
150
+ let result = "";
151
+ let commaIndex = -1;
152
+ for (let index = 0; index < jsonString.length; index++) {
153
+ const currentCharacter = jsonString[index];
154
+ const nextCharacter = jsonString[index + 1];
155
+ if (!isInsideComment && currentCharacter === '"') {
156
+ const escaped = isEscaped(jsonString, index);
157
+ if (!escaped) {
158
+ isInsideString = !isInsideString;
159
+ }
218
160
  }
219
- try {
220
- const data = parsers[collection.parser].parse(file);
221
- return {
222
- data,
223
- path: filePath
224
- };
225
- } catch (error) {
226
- emitter.emit("collector:parse-error", {
227
- filePath: path3.join(collection.directory, filePath),
228
- error: new CollectError("Parse", String(error))
229
- });
230
- return null;
161
+ if (isInsideString) {
162
+ continue;
231
163
  }
232
- }
233
- function createIgnorePattern(collection) {
234
- if (collection.exclude) {
235
- if (Array.isArray(collection.exclude)) {
236
- return collection.exclude;
237
- } else {
238
- return [collection.exclude];
164
+ if (!isInsideComment && currentCharacter + nextCharacter === "//") {
165
+ buffer += jsonString.slice(offset, index);
166
+ offset = index;
167
+ isInsideComment = singleComment;
168
+ index++;
169
+ } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === "\r\n") {
170
+ index++;
171
+ isInsideComment = false;
172
+ buffer += strip(jsonString, offset, index);
173
+ offset = index;
174
+ continue;
175
+ } else if (isInsideComment === singleComment && currentCharacter === "\n") {
176
+ isInsideComment = false;
177
+ buffer += strip(jsonString, offset, index);
178
+ offset = index;
179
+ } else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
180
+ buffer += jsonString.slice(offset, index);
181
+ offset = index;
182
+ isInsideComment = multiComment;
183
+ index++;
184
+ continue;
185
+ } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === "*/") {
186
+ index++;
187
+ isInsideComment = false;
188
+ buffer += strip(jsonString, offset, index + 1);
189
+ offset = index + 1;
190
+ continue;
191
+ } else if (trailingCommas && !isInsideComment) {
192
+ if (commaIndex !== -1) {
193
+ if (currentCharacter === "}" || currentCharacter === "]") {
194
+ buffer += jsonString.slice(offset, index);
195
+ result += strip(buffer, 0, 1) + buffer.slice(1);
196
+ buffer = "";
197
+ offset = index;
198
+ commaIndex = -1;
199
+ } else if (currentCharacter !== " " && currentCharacter !== " " && currentCharacter !== "\r" && currentCharacter !== "\n") {
200
+ buffer += jsonString.slice(offset, index);
201
+ offset = index;
202
+ commaIndex = -1;
203
+ }
204
+ } else if (currentCharacter === ",") {
205
+ result += buffer + jsonString.slice(offset, index);
206
+ buffer = "";
207
+ offset = index;
208
+ commaIndex = index;
239
209
  }
240
210
  }
241
- return void 0;
242
- }
243
- async function resolveCollection(collection) {
244
- const collectionDirectory = path3.join(baseDirectory, collection.directory);
245
- const include = Array.isArray(collection.include) ? collection.include : [collection.include];
246
- const filePaths = await glob(include, {
247
- cwd: collectionDirectory,
248
- onlyFiles: true,
249
- absolute: false,
250
- ignore: createIgnorePattern(collection)
251
- });
252
- const promises = filePaths.map(
253
- (filePath) => collectFile(collection, posixToNativePath(filePath))
254
- );
255
- const files = await Promise.all(promises);
256
- return {
257
- ...collection,
258
- files: files.filter(isDefined).sort(orderByPath)
259
- };
260
211
  }
261
- async function collect(unresolvedCollections) {
262
- const promises = unresolvedCollections.map(
263
- (collection) => resolveCollection(collection)
264
- );
265
- return await Promise.all(promises);
212
+ return result + buffer + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
213
+ }
214
+ function jsoncParse(data) {
215
+ try {
216
+ return new Function("return " + stripJsonComments(data).trim())();
217
+ } catch (_) {
218
+ return {};
266
219
  }
267
- return {
268
- collect,
269
- collectFile
270
- };
271
220
  }
272
-
273
- // src/synchronizer.ts
274
- import path4 from "node:path";
275
- import picomatch from "picomatch";
276
- function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
277
- function findCollections(filePath) {
278
- const resolvedFilePath = path4.resolve(filePath);
279
- return collections.filter((collection) => {
280
- return resolvedFilePath.startsWith(
281
- path4.resolve(baseDirectory, collection.directory)
282
- );
283
- });
221
+ var req = true ? createRequire(import.meta.url) : __require;
222
+ var findUp = (name, startDir, stopDir = path2.parse(startDir).root) => {
223
+ let dir = startDir;
224
+ while (dir !== stopDir) {
225
+ const file = path2.join(dir, name);
226
+ if (fs.existsSync(file))
227
+ return file;
228
+ if (!file.endsWith(".json")) {
229
+ const fileWithExt = file + ".json";
230
+ if (fs.existsSync(fileWithExt))
231
+ return fileWithExt;
232
+ }
233
+ dir = path2.dirname(dir);
284
234
  }
285
- function createRelativePath(collectionPath, filePath) {
286
- const resolvedCollectionPath = path4.resolve(baseDirectory, collectionPath);
287
- const resolvedFilePath = path4.resolve(filePath);
288
- let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
289
- if (relativePath.startsWith(path4.sep)) {
290
- relativePath = relativePath.slice(path4.sep.length);
291
- }
292
- return relativePath;
293
- }
294
- function resolve2(filePath) {
295
- const collections2 = findCollections(filePath);
296
- return collections2.map((collection) => {
297
- const relativePath = createRelativePath(collection.directory, filePath);
298
- return {
299
- collection,
300
- relativePath
301
- };
302
- }).filter(({ collection, relativePath }) => {
303
- return picomatch.isMatch(relativePath, collection.include, {
304
- ignore: collection.exclude
305
- });
306
- });
235
+ return null;
236
+ };
237
+ var resolveTsConfigFromFile = (cwd, filename) => {
238
+ if (path2.isAbsolute(filename))
239
+ return fs.existsSync(filename) ? filename : null;
240
+ return findUp(filename, cwd);
241
+ };
242
+ var resolveTsConfigFromExtends = (cwd, name) => {
243
+ if (path2.isAbsolute(name))
244
+ return fs.existsSync(name) ? name : null;
245
+ if (name.startsWith("."))
246
+ return findUp(name, cwd);
247
+ const id = req.resolve(name, { paths: [cwd] });
248
+ return id;
249
+ };
250
+ var loadTsConfigInternal = (dir = process.cwd(), name = "tsconfig.json", isExtends = false) => {
251
+ var _a, _b;
252
+ dir = path2.resolve(dir);
253
+ const id = isExtends ? resolveTsConfigFromExtends(dir, name) : resolveTsConfigFromFile(dir, name);
254
+ if (!id)
255
+ return null;
256
+ const data = jsoncParse(fs.readFileSync(id, "utf-8"));
257
+ const configDir = path2.dirname(id);
258
+ if ((_a = data.compilerOptions) == null ? void 0 : _a.baseUrl) {
259
+ data.compilerOptions.baseUrl = path2.join(
260
+ configDir,
261
+ data.compilerOptions.baseUrl
262
+ );
307
263
  }
308
- function deleted(filePath) {
309
- const resolvedCollections = resolve2(filePath);
310
- if (resolvedCollections.length === 0) {
311
- return false;
312
- }
313
- let changed2 = false;
314
- for (const { collection, relativePath } of resolvedCollections) {
315
- const index = collection.files.findIndex(
316
- (file) => file.path === relativePath
317
- );
318
- const deleted2 = collection.files.splice(index, 1);
319
- if (deleted2.length > 0) {
320
- changed2 = true;
264
+ let extendsFiles = [];
265
+ if (data.extends) {
266
+ const extendsList = Array.isArray(data.extends) ? data.extends : [data.extends];
267
+ const extendsData = {};
268
+ for (const name2 of extendsList) {
269
+ const parentConfig = loadTsConfigInternal(configDir, name2, true);
270
+ if (parentConfig) {
271
+ Object.assign(extendsData, {
272
+ ...parentConfig == null ? void 0 : parentConfig.data,
273
+ compilerOptions: {
274
+ ...extendsData.compilerOptions,
275
+ ...(_b = parentConfig == null ? void 0 : parentConfig.data) == null ? void 0 : _b.compilerOptions
276
+ }
277
+ });
278
+ extendsFiles.push(...parentConfig.files);
321
279
  }
322
280
  }
323
- return changed2;
324
- }
325
- async function changed(filePath) {
326
- const resolvedCollections = resolve2(filePath);
327
- if (resolvedCollections.length === 0) {
328
- return false;
329
- }
330
- let changed2 = false;
331
- for (const { collection, relativePath } of resolvedCollections) {
332
- const index = collection.files.findIndex(
333
- (file2) => file2.path === relativePath
334
- );
335
- const file = await readCollectionFile(collection, relativePath);
336
- if (file) {
337
- changed2 = true;
338
- if (index === -1) {
339
- collection.files.push(file);
340
- collection.files.sort(orderByPath);
341
- } else {
342
- collection.files[index] = file;
343
- }
281
+ Object.assign(data, {
282
+ ...extendsData,
283
+ ...data,
284
+ compilerOptions: {
285
+ ...extendsData.compilerOptions,
286
+ ...data.compilerOptions
344
287
  }
345
- }
346
- return changed2;
288
+ });
347
289
  }
348
- return {
349
- deleted,
350
- changed
351
- };
352
- }
353
-
354
- // src/transformer.ts
355
- import os from "node:os";
356
- import { basename, dirname, extname } from "node:path";
357
- import pLimit from "p-limit";
358
- import { z as z2 } from "zod";
290
+ delete data.extends;
291
+ return { path: id, data, files: [...extendsFiles, id] };
292
+ };
293
+ var loadTsConfig = (dir, name) => loadTsConfigInternal(dir, name);
359
294
 
360
- // src/serializer.ts
361
- import serializeJs from "serialize-javascript";
362
- import z from "zod";
295
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
296
+ var tsconfigPathsToRegExp = (paths) => {
297
+ return Object.keys(paths || {}).map((key) => {
298
+ return new RegExp(`^${key.replace(/\*/, ".*")}$`);
299
+ });
300
+ };
301
+ var match = (id, patterns) => {
302
+ if (!patterns)
303
+ return false;
304
+ return patterns.some((p) => {
305
+ if (p instanceof RegExp) {
306
+ return p.test(id);
307
+ }
308
+ return id === p || id.startsWith(p + "/");
309
+ });
310
+ };
363
311
 
364
- // src/import.ts
365
- var importSymbol = Symbol("import");
366
- function isImport(value) {
367
- return value && value[importSymbol];
368
- }
369
- function createDefaultImport(path10) {
370
- return {
371
- [importSymbol]: true,
372
- path: path10
373
- };
312
+ // src/esbuild.ts
313
+ import { build as build2 } from "esbuild";
314
+ import { dirname, join as join2 } from "node:path";
315
+ function tsconfigResolvePaths(configPath) {
316
+ let tsconfig = loadTsConfig(dirname(configPath));
317
+ if (!tsconfig) {
318
+ tsconfig = loadTsConfig();
319
+ }
320
+ return tsconfig?.data?.compilerOptions?.paths || {};
374
321
  }
375
- function createNamedImport(name, path10) {
322
+ var NON_NODE_MODULE_RE = /^[A-Z]:[/\\]|^\.{0,2}\/|^\.{1,2}$/;
323
+ function createExternalsPlugin(configPath) {
324
+ const resolvedPaths = tsconfigResolvePaths(configPath);
325
+ const resolvePatterns = tsconfigPathsToRegExp(resolvedPaths);
376
326
  return {
377
- [importSymbol]: true,
378
- path: path10,
379
- name
380
- };
381
- }
382
-
383
- // src/serializer.ts
384
- var literalSchema = z.union([
385
- // json
386
- z.string(),
387
- z.number(),
388
- z.boolean(),
389
- z.null(),
390
- // serializable-javascript
391
- z.undefined(),
392
- z.date(),
393
- z.map(z.unknown(), z.unknown()),
394
- z.set(z.unknown()),
395
- z.bigint()
396
- ]);
397
- var schema = z.lazy(
398
- () => z.union([literalSchema, z.array(schema), z.record(schema)])
399
- );
400
- var extension = "js";
401
- var serializableSchema = z.record(schema);
402
- function createImport(imp, variableName) {
403
- const variableDeclaration = imp.name ? `{ ${imp.name} as ${variableName} }` : variableName;
404
- return `import ${variableDeclaration} from "${imp.path}";
405
- `;
406
- }
407
- function serialize(value) {
408
- let serializedValue = "";
409
- let counter = 0;
410
- function handleImports(item) {
411
- if (item instanceof Object) {
412
- Object.entries(item).forEach(([key, value2]) => {
413
- if (isImport(value2)) {
414
- counter++;
415
- const variableName = `__v_${counter}`;
416
- serializedValue += createImport(value2, variableName);
417
- item[key] = variableName;
418
- } else if (value2 instanceof Object) {
419
- handleImports(value2);
327
+ name: "external-packages",
328
+ setup: (build4) => {
329
+ build4.onResolve({ filter: /.*/ }, ({ path: path10, kind }) => {
330
+ if (match(path10, resolvePatterns)) {
331
+ if (kind === "dynamic-import") {
332
+ return { path: path10, external: true };
333
+ }
334
+ return;
335
+ }
336
+ if (!NON_NODE_MODULE_RE.test(path10)) {
337
+ return {
338
+ path: path10,
339
+ external: true
340
+ };
420
341
  }
421
342
  });
422
343
  }
344
+ };
345
+ }
346
+ var importPathPlugin = {
347
+ name: "import-path",
348
+ setup(build4) {
349
+ build4.onResolve({ filter: /^\@content-collections\/core$/ }, () => {
350
+ return { path: join2(__dirname, "index.ts"), external: true };
351
+ });
423
352
  }
424
- value.forEach(handleImports);
425
- serializedValue += "\n";
426
- const js = serializeJs(value, {
427
- space: 2,
428
- unsafe: true,
429
- ignoreFunction: true
430
- }).replace(/"__v_(\d+)"/g, (_, index) => {
431
- return `__v_${index}`;
353
+ };
354
+ async function compile(configurationPath, outfile) {
355
+ const plugins = [createExternalsPlugin(configurationPath)];
356
+ if (process.env.NODE_ENV === "test") {
357
+ plugins.push(importPathPlugin);
358
+ }
359
+ const result = await build2({
360
+ entryPoints: [configurationPath],
361
+ packages: "external",
362
+ bundle: true,
363
+ platform: "node",
364
+ format: "esm",
365
+ plugins,
366
+ outfile,
367
+ metafile: true
432
368
  });
433
- serializedValue += "export default " + js;
434
- return serializedValue;
369
+ return Object.keys(result.metafile.inputs);
435
370
  }
436
371
 
437
- // src/transformer.ts
438
- var TransformError = class extends Error {
372
+ // src/configurationReader.ts
373
+ var ConfigurationError = class extends Error {
439
374
  type;
440
375
  constructor(type, message) {
441
376
  super(message);
442
377
  this.type = type;
443
378
  }
444
379
  };
445
- function createPath(path10, ext) {
446
- let p = path10.slice(0, -ext.length);
447
- if (p.endsWith("/index")) {
448
- p = p.slice(0, -6);
380
+ var defaultConfigName = "content-collection-config.mjs";
381
+ function resolveCacheDir(config, options) {
382
+ if (options.cacheDir) {
383
+ return options.cacheDir;
449
384
  }
450
- return p;
385
+ return path3.join(path3.dirname(config), ".content-collections", "cache");
451
386
  }
452
- function createTransformer(emitter, cacheManager) {
453
- function createSchema(parserName, schema2) {
454
- const parser = parsers[parserName];
455
- if (!parser.hasContent) {
456
- return z2.object(schema2);
457
- }
458
- return z2.object({
459
- content: z2.string(),
460
- ...schema2
461
- });
462
- }
463
- async function parseFile(collection, file) {
464
- const { data, path: path10 } = file;
465
- const schema2 = createSchema(collection.parser, collection.schema);
466
- let parsedData = await schema2.safeParseAsync(data);
467
- if (!parsedData.success) {
468
- emitter.emit("transformer:validation-error", {
469
- collection,
470
- file,
471
- error: new TransformError("Validation", parsedData.error.message)
472
- });
473
- return null;
474
- }
475
- const ext = extname(path10);
476
- let extension2 = ext;
477
- if (extension2.startsWith(".")) {
478
- extension2 = extension2.slice(1);
387
+ function createConfigurationReader() {
388
+ return async (configurationPath, options = {
389
+ configName: defaultConfigName
390
+ }) => {
391
+ if (!existsSync2(configurationPath)) {
392
+ throw new ConfigurationError(
393
+ "Read",
394
+ `configuration file ${configurationPath} does not exist`
395
+ );
479
396
  }
480
- const document = {
481
- ...parsedData.data,
482
- _meta: {
483
- filePath: path10,
484
- fileName: basename(path10),
485
- directory: dirname(path10),
486
- extension: extension2,
487
- path: createPath(path10, ext)
488
- }
489
- };
490
- return {
491
- document
492
- };
493
- }
494
- async function parseCollection(collection) {
495
- const promises = collection.files.map(
496
- (file) => parseFile(collection, file)
497
- );
498
- return {
499
- ...collection,
500
- documents: (await Promise.all(promises)).filter(isDefined)
501
- };
502
- }
503
- function createContext(collections, collection, cache) {
504
- return {
505
- documents: (collection2) => {
506
- const resolved = collections.find((c) => c.name === collection2.name);
507
- if (!resolved) {
508
- throw new TransformError(
509
- "Configuration",
510
- `Collection ${collection2.name} not found, do you have registered it in your configuration?`
511
- );
512
- }
513
- return resolved.documents.map((doc) => doc.document);
514
- },
515
- collection: {
516
- name: collection.name,
517
- directory: collection.directory,
518
- documents: async () => {
519
- return collection.documents.map((doc) => doc.document);
520
- }
521
- },
522
- cache: cache.cacheFn
523
- };
524
- }
525
- async function transformDocument(collections, collection, transform, doc) {
526
- const cache = cacheManager.cache(collection.name, doc.document._meta.path);
527
- const context2 = createContext(collections, collection, cache);
397
+ const cacheDir = resolveCacheDir(configurationPath, options);
398
+ await fs2.mkdir(cacheDir, { recursive: true });
399
+ const outfile = path3.join(cacheDir, options.configName);
528
400
  try {
529
- const document = await transform(doc.document, context2);
530
- await cache.tidyUp();
401
+ const configurationPaths = await compile(configurationPath, outfile);
402
+ const module = await import(`file://${path3.resolve(outfile)}?x=${Date.now()}`);
403
+ const hash = createHash2("sha256");
404
+ hash.update(await fs2.readFile(outfile, "utf-8"));
405
+ const checksum = hash.digest("hex");
531
406
  return {
532
- ...doc,
533
- document
407
+ ...module.default,
408
+ path: configurationPath,
409
+ inputPaths: configurationPaths.map((p) => path3.resolve(p)),
410
+ generateTypes: true,
411
+ checksum
534
412
  };
535
413
  } catch (error) {
536
- if (error instanceof TransformError) {
537
- emitter.emit("transformer:error", {
538
- collection,
539
- error
540
- });
541
- } else {
542
- emitter.emit("transformer:error", {
543
- collection,
544
- error: new TransformError("Transform", String(error))
545
- });
414
+ throw new ConfigurationError(
415
+ "Compile",
416
+ `configuration file ${configurationPath} is invalid: ${error}`
417
+ );
418
+ }
419
+ };
420
+ }
421
+
422
+ // src/parser.ts
423
+ function parseYaml(content) {
424
+ return parse(content.trim());
425
+ }
426
+ function frontmatter(fileContent) {
427
+ return matter(fileContent, {
428
+ engines: {
429
+ yaml: {
430
+ parse: parseYaml,
431
+ stringify
546
432
  }
547
433
  }
434
+ });
435
+ }
436
+ function frontmatterParser(fileContent) {
437
+ const { data, content } = frontmatter(fileContent);
438
+ return {
439
+ ...data,
440
+ content: content.trim()
441
+ };
442
+ }
443
+ function frontmatterOnlyParser(fileContent) {
444
+ const { data } = frontmatter(fileContent);
445
+ return data;
446
+ }
447
+ var parsers = {
448
+ frontmatter: {
449
+ hasContent: true,
450
+ parse: frontmatterParser
451
+ },
452
+ ["frontmatter-only"]: {
453
+ hasContent: false,
454
+ parse: frontmatterOnlyParser
455
+ },
456
+ json: {
457
+ hasContent: false,
458
+ parse: JSON.parse
459
+ },
460
+ yaml: {
461
+ hasContent: false,
462
+ parse: parseYaml
548
463
  }
549
- async function transformCollection(collections, collection) {
550
- const transform = collection.transform;
551
- if (transform) {
552
- const limit = pLimit(os.cpus().length);
553
- const docs = collection.documents.map(
554
- (doc) => limit(() => transformDocument(collections, collection, transform, doc))
464
+ };
465
+ function getParser(configuredParser) {
466
+ if (typeof configuredParser === "string") {
467
+ const parser = parsers[configuredParser];
468
+ if (!parser) {
469
+ throw new ConfigurationError(
470
+ "Read",
471
+ `Parser ${configuredParser} does not exist`
555
472
  );
556
- const transformed = await Promise.all(docs);
557
- await cacheManager.flush();
558
- return transformed.filter(isDefined);
559
473
  }
560
- return collection.documents;
474
+ return parser;
561
475
  }
562
- async function validateDocuments(collection, documents) {
563
- const docs = [];
564
- for (const doc of documents) {
565
- let parsedData = await serializableSchema.safeParseAsync(doc.document);
566
- if (parsedData.success) {
567
- docs.push(doc);
568
- } else {
569
- emitter.emit("transformer:result-error", {
570
- collection,
571
- document: doc.document,
572
- error: new TransformError("Result", parsedData.error.message)
573
- });
574
- }
575
- }
576
- return docs;
476
+ return configuredParser;
477
+ }
478
+ function defineParser(parser) {
479
+ if (typeof parser === "function") {
480
+ return {
481
+ hasContent: false,
482
+ parse: parser
483
+ };
577
484
  }
578
- return async (untransformedCollections) => {
579
- const promises = untransformedCollections.map(
580
- (collection) => parseCollection(collection)
581
- );
582
- const collections = await Promise.all(promises);
583
- for (const collection of collections) {
584
- const documents = await transformCollection(collections, collection);
585
- collection.documents = await validateDocuments(collection, documents);
586
- }
587
- return collections;
588
- };
485
+ return parser;
589
486
  }
590
487
 
591
- // src/writer.ts
592
- import fs from "node:fs/promises";
593
- import path5 from "node:path";
594
- import pluralize2 from "pluralize";
595
- function createArrayConstName(name) {
596
- let suffix = name.charAt(0).toUpperCase() + name.slice(1);
597
- return "all" + pluralize2(suffix);
488
+ // src/utils.ts
489
+ import camelcase from "camelcase";
490
+ import pluralize from "pluralize";
491
+ import path4 from "node:path";
492
+ function generateTypeName(name) {
493
+ const singularName = pluralize.singular(name);
494
+ return camelcase(singularName, { pascalCase: true });
598
495
  }
599
- async function createDataFile(directory, collection) {
600
- const dataPath = path5.join(
601
- directory,
602
- `${createArrayConstName(collection.name)}.${extension}`
603
- );
604
- await fs.writeFile(
605
- dataPath,
606
- serialize(collection.documents.map((doc) => doc.document))
607
- );
496
+ function isDefined(value) {
497
+ return value !== void 0 && value !== null;
608
498
  }
609
- function createDataFiles(directory, collections) {
610
- return Promise.all(
611
- collections.map((collection) => createDataFile(directory, collection))
612
- );
499
+ function orderByPath(a, b) {
500
+ return a.path.localeCompare(b.path);
613
501
  }
614
- async function createJavaScriptFile(directory, configuration) {
615
- const collections = configuration.collections.map(
616
- ({ name }) => createArrayConstName(name)
502
+ function removeChildPaths(paths) {
503
+ return Array.from(
504
+ new Set(
505
+ paths.filter((path10) => {
506
+ return !paths.some((otherPath) => {
507
+ if (path10 === otherPath) {
508
+ return false;
509
+ }
510
+ return path10.startsWith(otherPath);
511
+ });
512
+ })
513
+ )
617
514
  );
618
- let content = `// generated by content-collections at ${/* @__PURE__ */ new Date()}
619
-
620
- `;
621
- for (const name of collections) {
622
- content += `import ${name} from "./${name}.${extension}";
623
- `;
624
- }
625
- content += "\n";
626
- content += "export { " + collections.join(", ") + " };\n";
627
- await fs.writeFile(path5.join(directory, "index.js"), content, "utf-8");
628
515
  }
629
- function createImportPath(directory, target) {
630
- let importPath = path5.posix.join(
631
- ...path5.relative(directory, target).split(path5.sep)
632
- );
633
- if (!importPath.startsWith(".")) {
634
- importPath = "./" + importPath;
516
+ function posixToNativePath(pathName) {
517
+ if (path4.sep !== path4.posix.sep) {
518
+ return pathName.replaceAll(path4.posix.sep, path4.sep);
635
519
  }
636
- return importPath;
520
+ return pathName;
637
521
  }
638
- async function createTypeDefinitionFile(directory, configuration) {
639
- if (!configuration.generateTypes) {
640
- return;
522
+
523
+ // src/collector.ts
524
+ var CollectError = class extends Error {
525
+ type;
526
+ constructor(type, message) {
527
+ super(message);
528
+ this.type = type;
641
529
  }
642
- const importPath = createImportPath(directory, configuration.path);
643
- let content = `import configuration from "${importPath}";
644
- import { GetTypeByName } from "@content-collections/core";
645
- `;
646
- const collections = configuration.collections;
647
- for (const collection of collections) {
648
- content += `
649
- `;
650
- content += `export type ${collection.typeName} = GetTypeByName<typeof configuration, "${collection.name}">;
651
- `;
652
- content += `export declare const ${createArrayConstName(
653
- collection.name
654
- )}: Array<${collection.typeName}>;
655
- `;
530
+ };
531
+ function createCollector(emitter, baseDirectory = ".") {
532
+ async function read(filePath) {
533
+ try {
534
+ return await readFile2(filePath, "utf-8");
535
+ } catch (error) {
536
+ emitter.emit("collector:read-error", {
537
+ filePath,
538
+ error: new CollectError("Read", String(error))
539
+ });
540
+ return null;
541
+ }
542
+ }
543
+ async function collectFile(collection, filePath) {
544
+ const absolutePath = path5.join(
545
+ baseDirectory,
546
+ collection.directory,
547
+ filePath
548
+ );
549
+ const file = await read(absolutePath);
550
+ if (!file) {
551
+ return null;
552
+ }
553
+ try {
554
+ const parser = getParser(collection.parser);
555
+ const data = await parser.parse(file);
556
+ return {
557
+ data,
558
+ path: filePath
559
+ };
560
+ } catch (error) {
561
+ emitter.emit("collector:parse-error", {
562
+ filePath: path5.join(collection.directory, filePath),
563
+ error: new CollectError("Parse", String(error))
564
+ });
565
+ return null;
566
+ }
567
+ }
568
+ function createIgnorePattern(collection) {
569
+ if (collection.exclude) {
570
+ if (Array.isArray(collection.exclude)) {
571
+ return collection.exclude;
572
+ } else {
573
+ return [collection.exclude];
574
+ }
575
+ }
576
+ return void 0;
577
+ }
578
+ async function resolveCollection(collection) {
579
+ const collectionDirectory = path5.join(baseDirectory, collection.directory);
580
+ const include = Array.isArray(collection.include) ? collection.include : [collection.include];
581
+ const filePaths = await glob(include, {
582
+ cwd: collectionDirectory,
583
+ onlyFiles: true,
584
+ absolute: false,
585
+ ignore: createIgnorePattern(collection)
586
+ });
587
+ const promises = filePaths.map(
588
+ (filePath) => collectFile(collection, posixToNativePath(filePath))
589
+ );
590
+ const files = await Promise.all(promises);
591
+ return {
592
+ ...collection,
593
+ files: files.filter(isDefined).sort(orderByPath)
594
+ };
595
+ }
596
+ async function collect(unresolvedCollections) {
597
+ const promises = unresolvedCollections.map(
598
+ (collection) => resolveCollection(collection)
599
+ );
600
+ return await Promise.all(promises);
656
601
  }
657
- content += "\n";
658
- content += "export {};\n";
659
- await fs.writeFile(path5.join(directory, "index.d.ts"), content, "utf-8");
660
- }
661
- async function createWriter(directory) {
662
- await fs.mkdir(directory, { recursive: true });
663
602
  return {
664
- createJavaScriptFile: (configuration) => createJavaScriptFile(directory, configuration),
665
- createTypeDefinitionFile: (configuration) => createTypeDefinitionFile(directory, configuration),
666
- createDataFiles: (collections) => createDataFiles(directory, collections)
603
+ collect,
604
+ collectFile
667
605
  };
668
606
  }
669
607
 
670
- // src/build.ts
671
- async function createBuildContext({
672
- emitter,
673
- outputDirectory,
674
- baseDirectory,
675
- configuration
676
- }) {
677
- const collector = createCollector(emitter, baseDirectory);
678
- const [writer, resolved, cacheManager] = await Promise.all([
679
- createWriter(outputDirectory),
680
- collector.collect(configuration.collections),
681
- createCacheManager(baseDirectory, configuration.checksum)
682
- ]);
683
- const synchronizer = createSynchronizer(
684
- collector.collectFile,
685
- resolved,
686
- baseDirectory
687
- );
688
- const transform = createTransformer(emitter, cacheManager);
689
- return {
690
- resolved,
691
- writer,
692
- synchronizer,
693
- transform,
694
- emitter,
695
- cacheManager,
696
- configuration
697
- };
698
- }
699
- async function build({
700
- emitter,
701
- transform,
702
- resolved,
703
- writer,
704
- configuration
705
- }) {
706
- const startedAt = Date.now();
707
- emitter.emit("builder:start", {
708
- startedAt
709
- });
710
- const collections = await transform(resolved);
711
- await Promise.all([
712
- writer.createDataFiles(collections),
713
- writer.createTypeDefinitionFile(configuration),
714
- writer.createJavaScriptFile(configuration)
715
- ]);
716
- const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map(
717
- (collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document))
718
- );
719
- await Promise.all(pendingOnSuccess.filter(isDefined));
720
- const stats = collections.reduce(
721
- (acc, collection) => {
722
- acc.collections++;
723
- acc.documents += collection.documents.length;
724
- return acc;
725
- },
726
- {
727
- collections: 0,
728
- documents: 0
608
+ // src/synchronizer.ts
609
+ import path6 from "node:path";
610
+ import picomatch from "picomatch";
611
+ function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
612
+ function findCollections(filePath) {
613
+ const resolvedFilePath = path6.resolve(filePath);
614
+ return collections.filter((collection) => {
615
+ return resolvedFilePath.startsWith(
616
+ path6.resolve(baseDirectory, collection.directory)
617
+ );
618
+ });
619
+ }
620
+ function createRelativePath(collectionPath, filePath) {
621
+ const resolvedCollectionPath = path6.resolve(baseDirectory, collectionPath);
622
+ const resolvedFilePath = path6.resolve(filePath);
623
+ let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
624
+ if (relativePath.startsWith(path6.sep)) {
625
+ relativePath = relativePath.slice(path6.sep.length);
729
626
  }
730
- );
731
- emitter.emit("builder:end", {
732
- startedAt,
733
- endedAt: Date.now(),
734
- stats
735
- });
736
- }
737
-
738
- // src/configurationReader.ts
739
- import { createHash as createHash2 } from "node:crypto";
740
- import { existsSync as existsSync2 } from "node:fs";
741
- import fs3 from "node:fs/promises";
742
- import path7 from "node:path";
743
-
744
- // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
745
- import {
746
- build as build2,
747
- context
748
- } from "esbuild";
749
-
750
- // ../../node_modules/.pnpm/load-tsconfig@0.2.5/node_modules/load-tsconfig/dist/index.js
751
- import path6 from "path";
752
- import fs2 from "fs";
753
- import { createRequire } from "module";
754
- var singleComment = Symbol("singleComment");
755
- var multiComment = Symbol("multiComment");
756
- var stripWithoutWhitespace = () => "";
757
- var stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, " ");
758
- var isEscaped = (jsonString, quotePosition) => {
759
- let index = quotePosition - 1;
760
- let backslashCount = 0;
761
- while (jsonString[index] === "\\") {
762
- index -= 1;
763
- backslashCount += 1;
627
+ return relativePath;
764
628
  }
765
- return Boolean(backslashCount % 2);
766
- };
767
- function stripJsonComments(jsonString, { whitespace = true, trailingCommas = false } = {}) {
768
- if (typeof jsonString !== "string") {
769
- throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
629
+ function resolve2(filePath) {
630
+ const collections2 = findCollections(filePath);
631
+ return collections2.map((collection) => {
632
+ const relativePath = createRelativePath(collection.directory, filePath);
633
+ return {
634
+ collection,
635
+ relativePath
636
+ };
637
+ }).filter(({ collection, relativePath }) => {
638
+ return picomatch.isMatch(relativePath, collection.include, {
639
+ ignore: collection.exclude
640
+ });
641
+ });
770
642
  }
771
- const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
772
- let isInsideString = false;
773
- let isInsideComment = false;
774
- let offset = 0;
775
- let buffer = "";
776
- let result = "";
777
- let commaIndex = -1;
778
- for (let index = 0; index < jsonString.length; index++) {
779
- const currentCharacter = jsonString[index];
780
- const nextCharacter = jsonString[index + 1];
781
- if (!isInsideComment && currentCharacter === '"') {
782
- const escaped = isEscaped(jsonString, index);
783
- if (!escaped) {
784
- isInsideString = !isInsideString;
643
+ function deleted(filePath) {
644
+ const resolvedCollections = resolve2(filePath);
645
+ if (resolvedCollections.length === 0) {
646
+ return false;
647
+ }
648
+ let changed2 = false;
649
+ for (const { collection, relativePath } of resolvedCollections) {
650
+ const index = collection.files.findIndex(
651
+ (file) => file.path === relativePath
652
+ );
653
+ const deleted2 = collection.files.splice(index, 1);
654
+ if (deleted2.length > 0) {
655
+ changed2 = true;
785
656
  }
786
657
  }
787
- if (isInsideString) {
788
- continue;
658
+ return changed2;
659
+ }
660
+ async function changed(filePath) {
661
+ const resolvedCollections = resolve2(filePath);
662
+ if (resolvedCollections.length === 0) {
663
+ return false;
789
664
  }
790
- if (!isInsideComment && currentCharacter + nextCharacter === "//") {
791
- buffer += jsonString.slice(offset, index);
792
- offset = index;
793
- isInsideComment = singleComment;
794
- index++;
795
- } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === "\r\n") {
796
- index++;
797
- isInsideComment = false;
798
- buffer += strip(jsonString, offset, index);
799
- offset = index;
800
- continue;
801
- } else if (isInsideComment === singleComment && currentCharacter === "\n") {
802
- isInsideComment = false;
803
- buffer += strip(jsonString, offset, index);
804
- offset = index;
805
- } else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
806
- buffer += jsonString.slice(offset, index);
807
- offset = index;
808
- isInsideComment = multiComment;
809
- index++;
810
- continue;
811
- } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === "*/") {
812
- index++;
813
- isInsideComment = false;
814
- buffer += strip(jsonString, offset, index + 1);
815
- offset = index + 1;
816
- continue;
817
- } else if (trailingCommas && !isInsideComment) {
818
- if (commaIndex !== -1) {
819
- if (currentCharacter === "}" || currentCharacter === "]") {
820
- buffer += jsonString.slice(offset, index);
821
- result += strip(buffer, 0, 1) + buffer.slice(1);
822
- buffer = "";
823
- offset = index;
824
- commaIndex = -1;
825
- } else if (currentCharacter !== " " && currentCharacter !== " " && currentCharacter !== "\r" && currentCharacter !== "\n") {
826
- buffer += jsonString.slice(offset, index);
827
- offset = index;
828
- commaIndex = -1;
665
+ let changed2 = false;
666
+ for (const { collection, relativePath } of resolvedCollections) {
667
+ const index = collection.files.findIndex(
668
+ (file2) => file2.path === relativePath
669
+ );
670
+ const file = await readCollectionFile(collection, relativePath);
671
+ if (file) {
672
+ changed2 = true;
673
+ if (index === -1) {
674
+ collection.files.push(file);
675
+ collection.files.sort(orderByPath);
676
+ } else {
677
+ collection.files[index] = file;
678
+ }
679
+ }
680
+ }
681
+ return changed2;
682
+ }
683
+ return {
684
+ deleted,
685
+ changed
686
+ };
687
+ }
688
+
689
+ // src/transformer.ts
690
+ import os from "node:os";
691
+ import { basename, dirname as dirname2, extname } from "node:path";
692
+ import pLimit from "p-limit";
693
+
694
+ // src/serializer.ts
695
+ import serializeJs from "serialize-javascript";
696
+ import z from "zod";
697
+
698
+ // src/import.ts
699
+ var importSymbol = Symbol("import");
700
+ function isImport(value) {
701
+ return value && value[importSymbol];
702
+ }
703
+ function createDefaultImport(path10) {
704
+ return {
705
+ [importSymbol]: true,
706
+ path: path10
707
+ };
708
+ }
709
+ function createNamedImport(name, path10) {
710
+ return {
711
+ [importSymbol]: true,
712
+ path: path10,
713
+ name
714
+ };
715
+ }
716
+
717
+ // src/serializer.ts
718
+ var literalSchema = z.union([
719
+ // json
720
+ z.string(),
721
+ z.number(),
722
+ z.boolean(),
723
+ z.null(),
724
+ // serializable-javascript
725
+ z.undefined(),
726
+ z.date(),
727
+ z.map(z.unknown(), z.unknown()),
728
+ z.set(z.unknown()),
729
+ z.bigint()
730
+ ]);
731
+ var schema = z.lazy(
732
+ () => z.union([literalSchema, z.array(schema), z.record(schema)])
733
+ );
734
+ var extension = "js";
735
+ var serializableSchema = z.record(schema);
736
+ function createImport(imp, variableName) {
737
+ const variableDeclaration = imp.name ? `{ ${imp.name} as ${variableName} }` : variableName;
738
+ return `import ${variableDeclaration} from "${imp.path}";
739
+ `;
740
+ }
741
+ function serialize(value) {
742
+ let serializedValue = "";
743
+ let counter = 0;
744
+ function handleImports(item) {
745
+ if (item instanceof Object) {
746
+ Object.entries(item).forEach(([key, value2]) => {
747
+ if (isImport(value2)) {
748
+ counter++;
749
+ const variableName = `__v_${counter}`;
750
+ serializedValue += createImport(value2, variableName);
751
+ item[key] = variableName;
752
+ } else if (value2 instanceof Object) {
753
+ handleImports(value2);
829
754
  }
830
- } else if (currentCharacter === ",") {
831
- result += buffer + jsonString.slice(offset, index);
832
- buffer = "";
833
- offset = index;
834
- commaIndex = index;
835
- }
755
+ });
836
756
  }
837
757
  }
838
- return result + buffer + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
758
+ value.forEach(handleImports);
759
+ serializedValue += "\n";
760
+ const js = serializeJs(value, {
761
+ space: 2,
762
+ unsafe: true,
763
+ ignoreFunction: true
764
+ }).replace(/"__v_(\d+)"/g, (_, index) => {
765
+ return `__v_${index}`;
766
+ });
767
+ serializedValue += "export default " + js;
768
+ return serializedValue;
839
769
  }
840
- function jsoncParse(data) {
841
- try {
842
- return new Function("return " + stripJsonComments(data).trim())();
843
- } catch (_) {
844
- return {};
770
+
771
+ // src/transformer.ts
772
+ var TransformError = class extends Error {
773
+ type;
774
+ constructor(type, message) {
775
+ super(message);
776
+ this.type = type;
777
+ }
778
+ };
779
+ function createPath(path10, ext) {
780
+ let p = path10.slice(0, -ext.length);
781
+ if (p.endsWith("/index")) {
782
+ p = p.slice(0, -6);
845
783
  }
784
+ return p;
846
785
  }
847
- var req = true ? createRequire(import.meta.url) : __require;
848
- var findUp = (name, startDir, stopDir = path6.parse(startDir).root) => {
849
- let dir = startDir;
850
- while (dir !== stopDir) {
851
- const file = path6.join(dir, name);
852
- if (fs2.existsSync(file))
853
- return file;
854
- if (!file.endsWith(".json")) {
855
- const fileWithExt = file + ".json";
856
- if (fs2.existsSync(fileWithExt))
857
- return fileWithExt;
786
+ function createTransformer(emitter, cacheManager) {
787
+ async function parseFile(collection, file) {
788
+ const { data, path: path10 } = file;
789
+ let parsedData = await collection.schema["~standard"].validate(data);
790
+ if (parsedData.issues) {
791
+ emitter.emit("transformer:validation-error", {
792
+ collection,
793
+ file,
794
+ // TODO: check for better issue formatting
795
+ error: new TransformError(
796
+ "Validation",
797
+ parsedData.issues.map((issue) => issue.message).join(", ")
798
+ )
799
+ });
800
+ return null;
801
+ }
802
+ let values = parsedData.value;
803
+ const parser = getParser(collection.parser);
804
+ if (parser.hasContent) {
805
+ if (typeof data.content !== "string") {
806
+ emitter.emit("transformer:validation-error", {
807
+ collection,
808
+ file,
809
+ error: new TransformError(
810
+ "Validation",
811
+ `The content property is not a string`
812
+ )
813
+ });
814
+ return null;
815
+ }
816
+ values = {
817
+ // @ts-expect-error we can only spread on objects
818
+ ...values,
819
+ content: data.content
820
+ };
821
+ }
822
+ const ext = extname(path10);
823
+ let extension2 = ext;
824
+ if (extension2.startsWith(".")) {
825
+ extension2 = extension2.slice(1);
858
826
  }
859
- dir = path6.dirname(dir);
827
+ const document = {
828
+ // @ts-expect-error we can only spread on objects
829
+ ...values,
830
+ _meta: {
831
+ filePath: path10,
832
+ fileName: basename(path10),
833
+ directory: dirname2(path10),
834
+ extension: extension2,
835
+ path: createPath(path10, ext)
836
+ }
837
+ };
838
+ return {
839
+ document
840
+ };
860
841
  }
861
- return null;
862
- };
863
- var resolveTsConfigFromFile = (cwd, filename) => {
864
- if (path6.isAbsolute(filename))
865
- return fs2.existsSync(filename) ? filename : null;
866
- return findUp(filename, cwd);
867
- };
868
- var resolveTsConfigFromExtends = (cwd, name) => {
869
- if (path6.isAbsolute(name))
870
- return fs2.existsSync(name) ? name : null;
871
- if (name.startsWith("."))
872
- return findUp(name, cwd);
873
- const id = req.resolve(name, { paths: [cwd] });
874
- return id;
875
- };
876
- var loadTsConfigInternal = (dir = process.cwd(), name = "tsconfig.json", isExtends = false) => {
877
- var _a, _b;
878
- dir = path6.resolve(dir);
879
- const id = isExtends ? resolveTsConfigFromExtends(dir, name) : resolveTsConfigFromFile(dir, name);
880
- if (!id)
881
- return null;
882
- const data = jsoncParse(fs2.readFileSync(id, "utf-8"));
883
- const configDir = path6.dirname(id);
884
- if ((_a = data.compilerOptions) == null ? void 0 : _a.baseUrl) {
885
- data.compilerOptions.baseUrl = path6.join(
886
- configDir,
887
- data.compilerOptions.baseUrl
842
+ async function parseCollection(collection) {
843
+ const promises = collection.files.map(
844
+ (file) => parseFile(collection, file)
888
845
  );
846
+ return {
847
+ ...collection,
848
+ documents: (await Promise.all(promises)).filter(isDefined)
849
+ };
889
850
  }
890
- let extendsFiles = [];
891
- if (data.extends) {
892
- const extendsList = Array.isArray(data.extends) ? data.extends : [data.extends];
893
- const extendsData = {};
894
- for (const name2 of extendsList) {
895
- const parentConfig = loadTsConfigInternal(configDir, name2, true);
896
- if (parentConfig) {
897
- Object.assign(extendsData, {
898
- ...parentConfig == null ? void 0 : parentConfig.data,
899
- compilerOptions: {
900
- ...extendsData.compilerOptions,
901
- ...(_b = parentConfig == null ? void 0 : parentConfig.data) == null ? void 0 : _b.compilerOptions
902
- }
851
+ function createContext(collections, collection, cache) {
852
+ return {
853
+ documents: (collection2) => {
854
+ const resolved = collections.find((c) => c.name === collection2.name);
855
+ if (!resolved) {
856
+ throw new TransformError(
857
+ "Configuration",
858
+ `Collection ${collection2.name} not found, do you have registered it in your configuration?`
859
+ );
860
+ }
861
+ return resolved.documents.map((doc) => doc.document);
862
+ },
863
+ collection: {
864
+ name: collection.name,
865
+ directory: collection.directory,
866
+ documents: async () => {
867
+ return collection.documents.map((doc) => doc.document);
868
+ }
869
+ },
870
+ cache: cache.cacheFn
871
+ };
872
+ }
873
+ async function transformDocument(collections, collection, transform, doc) {
874
+ const cache = cacheManager.cache(collection.name, doc.document._meta.path);
875
+ const context2 = createContext(collections, collection, cache);
876
+ try {
877
+ const document = await transform(doc.document, context2);
878
+ await cache.tidyUp();
879
+ return {
880
+ ...doc,
881
+ document
882
+ };
883
+ } catch (error) {
884
+ if (error instanceof TransformError) {
885
+ emitter.emit("transformer:error", {
886
+ collection,
887
+ error
888
+ });
889
+ } else {
890
+ emitter.emit("transformer:error", {
891
+ collection,
892
+ error: new TransformError("Transform", String(error))
903
893
  });
904
- extendsFiles.push(...parentConfig.files);
905
894
  }
906
895
  }
907
- Object.assign(data, {
908
- ...extendsData,
909
- ...data,
910
- compilerOptions: {
911
- ...extendsData.compilerOptions,
912
- ...data.compilerOptions
913
- }
914
- });
915
896
  }
916
- delete data.extends;
917
- return { path: id, data, files: [...extendsFiles, id] };
918
- };
919
- var loadTsConfig = (dir, name) => loadTsConfigInternal(dir, name);
920
-
921
- // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
922
- var tsconfigPathsToRegExp = (paths) => {
923
- return Object.keys(paths || {}).map((key) => {
924
- return new RegExp(`^${key.replace(/\*/, ".*")}$`);
925
- });
926
- };
927
- var match = (id, patterns) => {
928
- if (!patterns)
929
- return false;
930
- return patterns.some((p) => {
931
- if (p instanceof RegExp) {
932
- return p.test(id);
897
+ async function transformCollection(collections, collection) {
898
+ const transform = collection.transform;
899
+ if (transform) {
900
+ const limit = pLimit(os.cpus().length);
901
+ const docs = collection.documents.map(
902
+ (doc) => limit(() => transformDocument(collections, collection, transform, doc))
903
+ );
904
+ const transformed = await Promise.all(docs);
905
+ await cacheManager.flush();
906
+ return transformed.filter(isDefined);
907
+ }
908
+ return collection.documents;
909
+ }
910
+ async function validateDocuments(collection, documents) {
911
+ const docs = [];
912
+ for (const doc of documents) {
913
+ let parsedData = await serializableSchema.safeParseAsync(doc.document);
914
+ if (parsedData.success) {
915
+ docs.push(doc);
916
+ } else {
917
+ emitter.emit("transformer:result-error", {
918
+ collection,
919
+ document: doc.document,
920
+ error: new TransformError("Result", parsedData.error.message)
921
+ });
922
+ }
933
923
  }
934
- return id === p || id.startsWith(p + "/");
935
- });
936
- };
937
-
938
- // src/esbuild.ts
939
- import { build as build3 } from "esbuild";
940
- import { dirname as dirname2, join as join2 } from "node:path";
941
- function tsconfigResolvePaths(configPath) {
942
- let tsconfig = loadTsConfig(dirname2(configPath));
943
- if (!tsconfig) {
944
- tsconfig = loadTsConfig();
924
+ return docs;
945
925
  }
946
- return tsconfig?.data?.compilerOptions?.paths || {};
947
- }
948
- var NON_NODE_MODULE_RE = /^[A-Z]:[/\\]|^\.{0,2}\/|^\.{1,2}$/;
949
- function createExternalsPlugin(configPath) {
950
- const resolvedPaths = tsconfigResolvePaths(configPath);
951
- const resolvePatterns = tsconfigPathsToRegExp(resolvedPaths);
952
- return {
953
- name: "external-packages",
954
- setup: (build4) => {
955
- build4.onResolve({ filter: /.*/ }, ({ path: path10, kind }) => {
956
- if (match(path10, resolvePatterns)) {
957
- if (kind === "dynamic-import") {
958
- return { path: path10, external: true };
959
- }
960
- return;
961
- }
962
- if (!NON_NODE_MODULE_RE.test(path10)) {
963
- return {
964
- path: path10,
965
- external: true
966
- };
967
- }
968
- });
926
+ return async (untransformedCollections) => {
927
+ const promises = untransformedCollections.map(
928
+ (collection) => parseCollection(collection)
929
+ );
930
+ const collections = await Promise.all(promises);
931
+ for (const collection of collections) {
932
+ const documents = await transformCollection(collections, collection);
933
+ collection.documents = await validateDocuments(collection, documents);
969
934
  }
935
+ return collections;
970
936
  };
971
937
  }
972
- var importPathPlugin = {
973
- name: "import-path",
974
- setup(build4) {
975
- build4.onResolve({ filter: /^\@content-collections\/core$/ }, () => {
976
- return { path: join2(__dirname, "index.ts"), external: true };
977
- });
938
+
939
+ // src/writer.ts
940
+ import fs3 from "node:fs/promises";
941
+ import path7 from "node:path";
942
+ import pluralize2 from "pluralize";
943
+ function createArrayConstName(name) {
944
+ let suffix = name.charAt(0).toUpperCase() + name.slice(1);
945
+ return "all" + pluralize2(suffix);
946
+ }
947
+ async function createDataFile(directory, collection) {
948
+ const dataPath = path7.join(
949
+ directory,
950
+ `${createArrayConstName(collection.name)}.${extension}`
951
+ );
952
+ await fs3.writeFile(
953
+ dataPath,
954
+ serialize(collection.documents.map((doc) => doc.document))
955
+ );
956
+ }
957
+ function createDataFiles(directory, collections) {
958
+ return Promise.all(
959
+ collections.map((collection) => createDataFile(directory, collection))
960
+ );
961
+ }
962
+ async function createJavaScriptFile(directory, configuration) {
963
+ const collections = configuration.collections.map(
964
+ ({ name }) => createArrayConstName(name)
965
+ );
966
+ let content = `// generated by content-collections at ${/* @__PURE__ */ new Date()}
967
+
968
+ `;
969
+ for (const name of collections) {
970
+ content += `import ${name} from "./${name}.${extension}";
971
+ `;
978
972
  }
979
- };
980
- async function compile(configurationPath, outfile) {
981
- const plugins = [createExternalsPlugin(configurationPath)];
982
- if (process.env.NODE_ENV === "test") {
983
- plugins.push(importPathPlugin);
973
+ content += "\n";
974
+ content += "export { " + collections.join(", ") + " };\n";
975
+ await fs3.writeFile(path7.join(directory, "index.js"), content, "utf-8");
976
+ }
977
+ function createImportPath(directory, target) {
978
+ let importPath = path7.posix.join(
979
+ ...path7.relative(directory, target).split(path7.sep)
980
+ );
981
+ if (!importPath.startsWith(".")) {
982
+ importPath = "./" + importPath;
984
983
  }
985
- const result = await build3({
986
- entryPoints: [configurationPath],
987
- packages: "external",
988
- bundle: true,
989
- platform: "node",
990
- format: "esm",
991
- plugins,
992
- outfile,
993
- metafile: true
994
- });
995
- return Object.keys(result.metafile.inputs);
984
+ return importPath;
996
985
  }
997
-
998
- // src/configurationReader.ts
999
- var ConfigurationError = class extends Error {
1000
- type;
1001
- constructor(type, message) {
1002
- super(message);
1003
- this.type = type;
986
+ async function createTypeDefinitionFile(directory, configuration) {
987
+ if (!configuration.generateTypes) {
988
+ return;
1004
989
  }
1005
- };
1006
- var defaultConfigName = "content-collection-config.mjs";
1007
- function resolveCacheDir(config, options) {
1008
- if (options.cacheDir) {
1009
- return options.cacheDir;
990
+ const importPath = createImportPath(directory, configuration.path);
991
+ let content = `import configuration from "${importPath}";
992
+ import { GetTypeByName } from "@content-collections/core";
993
+ `;
994
+ const collections = configuration.collections;
995
+ for (const collection of collections) {
996
+ content += `
997
+ `;
998
+ content += `export type ${collection.typeName} = GetTypeByName<typeof configuration, "${collection.name}">;
999
+ `;
1000
+ content += `export declare const ${createArrayConstName(
1001
+ collection.name
1002
+ )}: Array<${collection.typeName}>;
1003
+ `;
1010
1004
  }
1011
- return path7.join(path7.dirname(config), ".content-collections", "cache");
1005
+ content += "\n";
1006
+ content += "export {};\n";
1007
+ await fs3.writeFile(path7.join(directory, "index.d.ts"), content, "utf-8");
1012
1008
  }
1013
- function createConfigurationReader() {
1014
- return async (configurationPath, options = {
1015
- configName: defaultConfigName
1016
- }) => {
1017
- if (!existsSync2(configurationPath)) {
1018
- throw new ConfigurationError(
1019
- "Read",
1020
- `configuration file ${configurationPath} does not exist`
1021
- );
1022
- }
1023
- const cacheDir = resolveCacheDir(configurationPath, options);
1024
- await fs3.mkdir(cacheDir, { recursive: true });
1025
- const outfile = path7.join(cacheDir, options.configName);
1026
- try {
1027
- const configurationPaths = await compile(configurationPath, outfile);
1028
- const module = await import(`file://${path7.resolve(outfile)}?x=${Date.now()}`);
1029
- const hash = createHash2("sha256");
1030
- hash.update(await fs3.readFile(outfile, "utf-8"));
1031
- const checksum = hash.digest("hex");
1032
- return {
1033
- ...module.default,
1034
- path: configurationPath,
1035
- inputPaths: configurationPaths.map((p) => path7.resolve(p)),
1036
- generateTypes: true,
1037
- checksum
1038
- };
1039
- } catch (error) {
1040
- throw new ConfigurationError(
1041
- "Compile",
1042
- `configuration file ${configurationPath} is invalid: ${error}`
1043
- );
1044
- }
1009
+ async function createWriter(directory) {
1010
+ await fs3.mkdir(directory, { recursive: true });
1011
+ return {
1012
+ createJavaScriptFile: (configuration) => createJavaScriptFile(directory, configuration),
1013
+ createTypeDefinitionFile: (configuration) => createTypeDefinitionFile(directory, configuration),
1014
+ createDataFiles: (collections) => createDataFiles(directory, collections)
1015
+ };
1016
+ }
1017
+
1018
+ // src/build.ts
1019
+ async function createBuildContext({
1020
+ emitter,
1021
+ outputDirectory,
1022
+ baseDirectory,
1023
+ configuration
1024
+ }) {
1025
+ const collector = createCollector(emitter, baseDirectory);
1026
+ const [writer, resolved, cacheManager] = await Promise.all([
1027
+ createWriter(outputDirectory),
1028
+ collector.collect(configuration.collections),
1029
+ createCacheManager(baseDirectory, configuration.checksum)
1030
+ ]);
1031
+ const synchronizer = createSynchronizer(
1032
+ collector.collectFile,
1033
+ resolved,
1034
+ baseDirectory
1035
+ );
1036
+ const transform = createTransformer(emitter, cacheManager);
1037
+ return {
1038
+ resolved,
1039
+ writer,
1040
+ synchronizer,
1041
+ transform,
1042
+ emitter,
1043
+ cacheManager,
1044
+ configuration
1045
1045
  };
1046
1046
  }
1047
+ async function build3({
1048
+ emitter,
1049
+ transform,
1050
+ resolved,
1051
+ writer,
1052
+ configuration
1053
+ }) {
1054
+ const startedAt = Date.now();
1055
+ emitter.emit("builder:start", {
1056
+ startedAt
1057
+ });
1058
+ const collections = await transform(resolved);
1059
+ await Promise.all([
1060
+ writer.createDataFiles(collections),
1061
+ writer.createTypeDefinitionFile(configuration),
1062
+ writer.createJavaScriptFile(configuration)
1063
+ ]);
1064
+ const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map(
1065
+ (collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document))
1066
+ );
1067
+ await Promise.all(pendingOnSuccess.filter(isDefined));
1068
+ const stats = collections.reduce(
1069
+ (acc, collection) => {
1070
+ acc.collections++;
1071
+ acc.documents += collection.documents.length;
1072
+ return acc;
1073
+ },
1074
+ {
1075
+ collections: 0,
1076
+ documents: 0
1077
+ }
1078
+ );
1079
+ emitter.emit("builder:end", {
1080
+ startedAt,
1081
+ endedAt: Date.now(),
1082
+ stats
1083
+ });
1084
+ }
1047
1085
 
1048
1086
  // src/events.ts
1049
1087
  import { EventEmitter } from "node:events";
@@ -1152,7 +1190,7 @@ async function createBuilder(configurationPath, options = {
1152
1190
  filePath,
1153
1191
  modification
1154
1192
  });
1155
- await build(context2);
1193
+ await build3(context2);
1156
1194
  return true;
1157
1195
  }
1158
1196
  } else {
@@ -1161,7 +1199,7 @@ async function createBuilder(configurationPath, options = {
1161
1199
  filePath,
1162
1200
  modification
1163
1201
  });
1164
- await build(context2);
1202
+ await build3(context2);
1165
1203
  return true;
1166
1204
  }
1167
1205
  }
@@ -1217,7 +1255,7 @@ async function createBuilder(configurationPath, options = {
1217
1255
  };
1218
1256
  }
1219
1257
  return {
1220
- build: () => build(context2),
1258
+ build: () => build3(context2),
1221
1259
  sync,
1222
1260
  watch,
1223
1261
  on: emitter.on
@@ -1225,7 +1263,36 @@ async function createBuilder(configurationPath, options = {
1225
1263
  }
1226
1264
 
1227
1265
  // src/config.ts
1228
- import { z as z3 } from "zod";
1266
+ import { z as z2 } from "zod";
1267
+
1268
+ // src/warn.ts
1269
+ var deprecations = {
1270
+ legacySchema: `The use of a function as a schema is deprecated.
1271
+ Please use a StandardSchema compliant library directly.
1272
+ For more information, see:
1273
+ https://content-collections.dev/docs/deprecations/schema-as-function`
1274
+ };
1275
+ var _suppressDeprecatedWarnings = [];
1276
+ function suppressDeprecatedWarnings(...deprecations2) {
1277
+ for (const deprecation of deprecations2) {
1278
+ if (deprecation === "all") {
1279
+ _suppressDeprecatedWarnings.push(
1280
+ ...Object.keys(deprecations2)
1281
+ );
1282
+ return;
1283
+ } else {
1284
+ _suppressDeprecatedWarnings.push(deprecation);
1285
+ }
1286
+ }
1287
+ }
1288
+ function warnDeprecated(deprecation, logger = console.warn) {
1289
+ if (_suppressDeprecatedWarnings.includes(deprecation)) {
1290
+ return;
1291
+ }
1292
+ logger(`[CC DEPRECATED]: ${deprecations[deprecation]}`);
1293
+ }
1294
+
1295
+ // src/config.ts
1229
1296
  var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
1230
1297
  function defineCollection(collection) {
1231
1298
  let typeName = collection.typeName;
@@ -1236,11 +1303,16 @@ function defineCollection(collection) {
1236
1303
  if (!parser) {
1237
1304
  parser = "frontmatter";
1238
1305
  }
1306
+ let schema2 = collection.schema;
1307
+ if (!schema2["~standard"]) {
1308
+ warnDeprecated("legacySchema");
1309
+ schema2 = z2.object(schema2(z2));
1310
+ }
1239
1311
  return {
1240
1312
  ...collection,
1241
1313
  typeName,
1242
1314
  parser,
1243
- schema: collection.schema(z3)
1315
+ schema: schema2
1244
1316
  };
1245
1317
  }
1246
1318
  function defineConfig(config) {
@@ -1255,5 +1327,7 @@ export {
1255
1327
  createDefaultImport,
1256
1328
  createNamedImport,
1257
1329
  defineCollection,
1258
- defineConfig
1330
+ defineConfig,
1331
+ defineParser,
1332
+ suppressDeprecatedWarnings
1259
1333
  };