@content-collections/core 0.10.0 → 0.11.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.d.ts CHANGED
@@ -60,7 +60,7 @@ declare const literalSchema: z__default.ZodUnion<[z__default.ZodString, z__defau
60
60
  type Literal = z__default.infer<typeof literalSchema>;
61
61
  type SchemaType = Literal | {
62
62
  [key: string]: SchemaType;
63
- } | SchemaType[];
63
+ } | ReadonlyArray<SchemaType>;
64
64
  type NotSerializableError = `The return type of the transform function must be an object serializable object.
65
65
  See https://www.content-collections.dev/docs/serialization for more information.
66
66
 
@@ -95,6 +95,11 @@ type Prettify<T> = {
95
95
  [K in keyof T]: T[K];
96
96
  } & {};
97
97
  type GetSchema<TCollection extends AnyCollection> = TCollection extends Collection<any, any, any, infer TSchema, any, any> ? Prettify<TSchema> : never;
98
+ declare const skippedSymbol: unique symbol;
99
+ type SkippedSignal = {
100
+ [skippedSymbol]: true;
101
+ reason?: string;
102
+ };
98
103
  type Context<TSchema = unknown> = {
99
104
  documents<TCollection extends AnyCollection>(collection: TCollection): Array<GetSchema<TCollection>>;
100
105
  cache: CacheFn;
@@ -103,6 +108,7 @@ type Context<TSchema = unknown> = {
103
108
  directory: string;
104
109
  documents: () => Promise<Array<TSchema>>;
105
110
  };
111
+ skip: (reason?: string) => SkippedSignal;
106
112
  };
107
113
  type Z = typeof z$1;
108
114
  type CollectionRequest<TName extends string, TShape extends TSchemaProp, TParser, TSchema, TTransformResult, TDocument> = {
@@ -130,7 +136,7 @@ type InvalidReturnType<TMessage extends string, TObject> = {
130
136
  type ResolveImports<TTransformResult> = TTransformResult extends Import<any> ? GetTypeOfImport<TTransformResult> : TTransformResult extends Array<infer U> ? Array<ResolveImports<U>> : TTransformResult extends (...args: any[]) => any ? TTransformResult : TTransformResult extends object ? {
131
137
  [K in keyof TTransformResult]: ResolveImports<TTransformResult[K]>;
132
138
  } : TTransformResult;
133
- declare function defineCollection<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser = "frontmatter", TSchema = Schema<TParser, TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TParser, TShape> : Awaited<TTransformResult>, TResult = TDocument extends Serializable ? Collection<TName, TShape, TParser, TSchema, TTransformResult, ResolveImports<TDocument>> : InvalidReturnType<NotSerializableError, TDocument>>(collection: CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>): TResult;
139
+ declare function defineCollection<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser = "frontmatter", TSchema = Schema<TParser, TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TParser, TShape> : Exclude<Awaited<TTransformResult>, SkippedSignal>, TResult = TDocument extends Serializable ? Collection<TName, TShape, TParser, TSchema, TTransformResult, ResolveImports<TDocument>> : InvalidReturnType<NotSerializableError, TDocument>>(collection: CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>): TResult;
134
140
  type Cache = "memory" | "file" | "none";
135
141
  type Configuration<TCollections extends Array<AnyCollection>> = {
136
142
  collections: TCollections;
@@ -187,6 +193,11 @@ type TransformerEvents = {
187
193
  collection: AnyCollection;
188
194
  error: TransformError;
189
195
  };
196
+ "transformer:document-skipped": {
197
+ collection: AnyCollection;
198
+ filePath: string;
199
+ reason?: string;
200
+ };
190
201
  };
191
202
  type ErrorType$1 = "Validation" | "Configuration" | "Transform" | "Result";
192
203
  declare class TransformError extends Error {
@@ -248,6 +259,13 @@ declare class ConfigurationError extends Error {
248
259
  type: ErrorType;
249
260
  constructor(type: ErrorType, message: string);
250
261
  }
262
+ type InternalConfiguration = {
263
+ collections: Array<AnyCollection>;
264
+ path: string;
265
+ inputPaths: Array<string>;
266
+ checksum: string;
267
+ generateTypes?: boolean;
268
+ };
251
269
  type Options$1 = {
252
270
  configName: string;
253
271
  cacheDir?: string;
@@ -299,7 +317,18 @@ declare function createBuilder(configurationPath: string, options?: Options, emi
299
317
  unsubscribe: () => Promise<void>;
300
318
  }>;
301
319
  on: {
302
- <TKey extends "builder:start" | "builder:end" | "builder:created" | "watcher:file-changed" | "watcher:config-changed" | "watcher:config-reload-error" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:result-error" | "transformer:error" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
320
+ <TKey extends "builder:start" | "builder:end" | "builder:created" | "watcher:file-changed" | "watcher:config-changed" | "watcher:config-reload-error" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:result-error" | "transformer:error" | "transformer:document-skipped" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
321
+ <TKey extends "_error" | "_all">(key: TKey, listener: (event: SystemEvents[TKey]) => void): void;
322
+ };
323
+ }>;
324
+ declare function createInternalBuilder(initialConfiguration: InternalConfiguration, baseDirectory: string, options: Options, emitter: Emitter): Promise<{
325
+ build: () => Promise<void>;
326
+ sync: (modification: Modification, filePath: string) => Promise<boolean>;
327
+ watch: () => Promise<{
328
+ unsubscribe: () => Promise<void>;
329
+ }>;
330
+ on: {
331
+ <TKey extends "builder:start" | "builder:end" | "builder:created" | "watcher:file-changed" | "watcher:config-changed" | "watcher:config-reload-error" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:result-error" | "transformer:error" | "transformer:document-skipped" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
303
332
  <TKey extends "_error" | "_all">(key: TKey, listener: (event: SystemEvents[TKey]) => void): void;
304
333
  };
305
334
  }>;
@@ -309,6 +338,6 @@ declare const deprecations: {
309
338
  legacySchema: string;
310
339
  };
311
340
  type Deprecation = keyof typeof deprecations;
312
- declare function suppressDeprecatedWarnings(...deprecations: Array<Deprecation | "all">): void;
341
+ declare function suppressDeprecatedWarnings(...suppresses: Array<Deprecation | "all">): void;
313
342
 
314
- export { type AnyCollection, type AnyConfiguration, type Builder, type BuilderEvents, CollectError, type Collection, type CollectionRequest, type Configuration, ConfigurationError, ConfigurationReloadError, type Context, type Document, type GetTypeByName, type Meta, type Modification, type Schema, TransformError, type Watcher, createBuilder, createDefaultImport, createNamedImport, defineCollection, defineConfig, defineParser, suppressDeprecatedWarnings };
343
+ export { type AnyCollection, type AnyConfiguration, type Builder, type BuilderEvents, CollectError, type Collection, type CollectionRequest, type Configuration, ConfigurationError, ConfigurationReloadError, type Context, type Document, type GetTypeByName, type Meta, type Modification, type Schema, type SkippedSignal, TransformError, type Watcher, createBuilder, createDefaultImport, createInternalBuilder, createNamedImport, defineCollection, defineConfig, defineParser, skippedSymbol, suppressDeprecatedWarnings };
package/dist/index.js CHANGED
@@ -102,27 +102,331 @@ async function createCacheManager(baseDirectory, configChecksum) {
102
102
 
103
103
  // src/collector.ts
104
104
  import { readFile as readFile2 } from "fs/promises";
105
- import path5 from "node:path";
105
+ import path3 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
+ function getParser(configuredParser) {
154
+ if (typeof configuredParser === "string") {
155
+ return parsers[configuredParser];
156
+ }
157
+ return configuredParser;
158
+ }
159
+ function defineParser(parser) {
160
+ if (typeof parser === "function") {
161
+ return {
162
+ hasContent: false,
163
+ parse: parser
164
+ };
165
+ }
166
+ return parser;
167
+ }
168
+ function isValidParser(parser) {
169
+ if (typeof parser === "string") {
170
+ return parser in parsers;
171
+ }
172
+ return "hasContent" in parser && typeof parser.parse === "function";
173
+ }
174
+
175
+ // src/utils.ts
176
+ import camelcase from "camelcase";
177
+ import pluralize from "pluralize";
178
+ import path2 from "node:path";
179
+ function generateTypeName(name) {
180
+ const singularName = pluralize.singular(name);
181
+ return camelcase(singularName, { pascalCase: true });
182
+ }
183
+ function isDefined(value) {
184
+ return value !== void 0 && value !== null;
185
+ }
186
+ function orderByPath(a, b) {
187
+ return a.path.localeCompare(b.path);
188
+ }
189
+ function removeChildPaths(paths) {
190
+ return Array.from(
191
+ new Set(
192
+ paths.filter((path10) => {
193
+ return !paths.some((otherPath) => {
194
+ if (path10 === otherPath) {
195
+ return false;
196
+ }
197
+ return path10.startsWith(otherPath);
198
+ });
199
+ })
200
+ )
201
+ );
202
+ }
203
+ function posixToNativePath(pathName) {
204
+ if (path2.sep !== path2.posix.sep) {
205
+ return pathName.replaceAll(path2.posix.sep, path2.sep);
206
+ }
207
+ return pathName;
208
+ }
209
+ function toError(error) {
210
+ return error instanceof Error ? error : new Error(String(error));
211
+ }
212
+
213
+ // src/collector.ts
214
+ var CollectError = class extends Error {
215
+ type;
216
+ constructor(type, message) {
217
+ super(message);
218
+ this.type = type;
219
+ }
220
+ };
221
+ function createCollector(emitter, baseDirectory = ".") {
222
+ async function read(filePath) {
223
+ try {
224
+ return await readFile2(filePath, "utf-8");
225
+ } catch (error) {
226
+ emitter.emit("collector:read-error", {
227
+ filePath,
228
+ error: new CollectError("Read", String(error))
229
+ });
230
+ return null;
231
+ }
232
+ }
233
+ async function collectFile(collection, filePath) {
234
+ const absolutePath = path3.join(
235
+ baseDirectory,
236
+ collection.directory,
237
+ filePath
238
+ );
239
+ const file = await read(absolutePath);
240
+ if (!file) {
241
+ return null;
242
+ }
243
+ try {
244
+ const parser = getParser(collection.parser);
245
+ const data = await parser.parse(file);
246
+ return {
247
+ data,
248
+ path: filePath
249
+ };
250
+ } catch (error) {
251
+ emitter.emit("collector:parse-error", {
252
+ filePath: path3.join(collection.directory, filePath),
253
+ error: new CollectError("Parse", String(error))
254
+ });
255
+ return null;
256
+ }
257
+ }
258
+ function createIgnorePattern(collection) {
259
+ if (collection.exclude) {
260
+ if (Array.isArray(collection.exclude)) {
261
+ return collection.exclude;
262
+ } else {
263
+ return [collection.exclude];
264
+ }
265
+ }
266
+ return void 0;
267
+ }
268
+ async function resolveCollection(collection) {
269
+ const collectionDirectory = path3.join(baseDirectory, collection.directory);
270
+ const include = Array.isArray(collection.include) ? collection.include : [collection.include];
271
+ const filePaths = await glob(include, {
272
+ cwd: collectionDirectory,
273
+ onlyFiles: true,
274
+ absolute: false,
275
+ ignore: createIgnorePattern(collection)
276
+ });
277
+ const promises = filePaths.map(
278
+ (filePath) => collectFile(collection, posixToNativePath(filePath))
279
+ );
280
+ const files = await Promise.all(promises);
281
+ return {
282
+ ...collection,
283
+ files: files.filter(isDefined).sort(orderByPath)
284
+ };
285
+ }
286
+ async function collect(unresolvedCollections) {
287
+ const promises = unresolvedCollections.map(
288
+ (collection) => resolveCollection(collection)
289
+ );
290
+ return await Promise.all(promises);
291
+ }
292
+ return {
293
+ collect,
294
+ collectFile
295
+ };
296
+ }
297
+
298
+ // src/synchronizer.ts
299
+ import path4 from "node:path";
300
+ import picomatch from "picomatch";
301
+ function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
302
+ function findCollections(filePath) {
303
+ const resolvedFilePath = path4.resolve(filePath);
304
+ return collections.filter((collection) => {
305
+ return resolvedFilePath.startsWith(
306
+ path4.resolve(baseDirectory, collection.directory)
307
+ );
308
+ });
309
+ }
310
+ function createRelativePath(collectionPath, filePath) {
311
+ const resolvedCollectionPath = path4.resolve(baseDirectory, collectionPath);
312
+ const resolvedFilePath = path4.resolve(filePath);
313
+ let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
314
+ if (relativePath.startsWith(path4.sep)) {
315
+ relativePath = relativePath.slice(path4.sep.length);
316
+ }
317
+ return relativePath;
318
+ }
319
+ function resolve2(filePath) {
320
+ const collections2 = findCollections(filePath);
321
+ return collections2.map((collection) => {
322
+ const relativePath = createRelativePath(collection.directory, filePath);
323
+ return {
324
+ collection,
325
+ relativePath
326
+ };
327
+ }).filter(({ collection, relativePath }) => {
328
+ return picomatch.isMatch(relativePath, collection.include, {
329
+ // @see https://github.com/sdorra/content-collections/issues/602
330
+ windows: process.platform === "win32",
331
+ ignore: collection.exclude
332
+ });
333
+ });
334
+ }
335
+ function deleted(filePath) {
336
+ const resolvedCollections = resolve2(filePath);
337
+ if (resolvedCollections.length === 0) {
338
+ return false;
339
+ }
340
+ let changed2 = false;
341
+ for (const { collection, relativePath } of resolvedCollections) {
342
+ const index = collection.files.findIndex(
343
+ (file) => file.path === relativePath
344
+ );
345
+ const deleted2 = collection.files.splice(index, 1);
346
+ if (deleted2.length > 0) {
347
+ changed2 = true;
348
+ }
349
+ }
350
+ return changed2;
351
+ }
352
+ async function changed(filePath) {
353
+ const resolvedCollections = resolve2(filePath);
354
+ if (resolvedCollections.length === 0) {
355
+ return false;
356
+ }
357
+ let changed2 = false;
358
+ for (const { collection, relativePath } of resolvedCollections) {
359
+ const index = collection.files.findIndex(
360
+ (file2) => file2.path === relativePath
361
+ );
362
+ const file = await readCollectionFile(collection, relativePath);
363
+ if (file) {
364
+ changed2 = true;
365
+ if (index === -1) {
366
+ collection.files.push(file);
367
+ collection.files.sort(orderByPath);
368
+ } else {
369
+ collection.files[index] = file;
370
+ }
371
+ }
372
+ }
373
+ return changed2;
374
+ }
375
+ return {
376
+ deleted,
377
+ changed
378
+ };
379
+ }
380
+
381
+ // src/transformer.ts
382
+ import os from "node:os";
383
+ import { basename, dirname as dirname2, extname, join as join3 } from "node:path";
384
+ import pLimit from "p-limit";
385
+
386
+ // src/config.ts
387
+ import { z } from "zod";
388
+
389
+ // src/warn.ts
390
+ var deprecations = {
391
+ legacySchema: `The use of a function as a schema is deprecated.
392
+ Please use a StandardSchema compliant library directly.
393
+ For more information, see:
394
+ https://content-collections.dev/docs/deprecations/schema-as-function`
395
+ };
396
+ var _suppressDeprecatedWarnings = [];
397
+ function suppressDeprecatedWarnings(...suppresses) {
398
+ for (const deprecation of suppresses) {
399
+ if (deprecation === "all") {
400
+ _suppressDeprecatedWarnings.push(
401
+ ...Object.keys(deprecations)
402
+ );
403
+ return;
404
+ } else {
405
+ _suppressDeprecatedWarnings.push(deprecation);
406
+ }
407
+ }
408
+ }
409
+ function warnDeprecated(deprecation, logger = console.warn) {
410
+ if (_suppressDeprecatedWarnings.includes(deprecation)) {
411
+ return;
412
+ }
413
+ logger(`[CC DEPRECATED]: ${deprecations[deprecation]}`);
414
+ }
111
415
 
112
416
  // src/configurationReader.ts
113
417
  import { createHash as createHash2 } from "node:crypto";
114
418
  import { existsSync as existsSync2 } from "node:fs";
115
419
  import fs2 from "node:fs/promises";
116
- import path3 from "node:path";
420
+ import path6 from "node:path";
117
421
 
118
- // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
422
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.6/node_modules/bundle-require/dist/index.js
119
423
  import {
120
424
  build,
121
425
  context
122
426
  } from "esbuild";
123
427
 
124
428
  // ../../node_modules/.pnpm/load-tsconfig@0.2.5/node_modules/load-tsconfig/dist/index.js
125
- import path2 from "path";
429
+ import path5 from "path";
126
430
  import fs from "fs";
127
431
  import { createRequire } from "module";
128
432
  var singleComment = Symbol("singleComment");
@@ -219,10 +523,10 @@ function jsoncParse(data) {
219
523
  }
220
524
  }
221
525
  var req = true ? createRequire(import.meta.url) : __require;
222
- var findUp = (name, startDir, stopDir = path2.parse(startDir).root) => {
526
+ var findUp = (name, startDir, stopDir = path5.parse(startDir).root) => {
223
527
  let dir = startDir;
224
528
  while (dir !== stopDir) {
225
- const file = path2.join(dir, name);
529
+ const file = path5.join(dir, name);
226
530
  if (fs.existsSync(file))
227
531
  return file;
228
532
  if (!file.endsWith(".json")) {
@@ -230,17 +534,17 @@ var findUp = (name, startDir, stopDir = path2.parse(startDir).root) => {
230
534
  if (fs.existsSync(fileWithExt))
231
535
  return fileWithExt;
232
536
  }
233
- dir = path2.dirname(dir);
537
+ dir = path5.dirname(dir);
234
538
  }
235
539
  return null;
236
540
  };
237
541
  var resolveTsConfigFromFile = (cwd, filename) => {
238
- if (path2.isAbsolute(filename))
542
+ if (path5.isAbsolute(filename))
239
543
  return fs.existsSync(filename) ? filename : null;
240
544
  return findUp(filename, cwd);
241
545
  };
242
546
  var resolveTsConfigFromExtends = (cwd, name) => {
243
- if (path2.isAbsolute(name))
547
+ if (path5.isAbsolute(name))
244
548
  return fs.existsSync(name) ? name : null;
245
549
  if (name.startsWith("."))
246
550
  return findUp(name, cwd);
@@ -249,14 +553,14 @@ var resolveTsConfigFromExtends = (cwd, name) => {
249
553
  };
250
554
  var loadTsConfigInternal = (dir = process.cwd(), name = "tsconfig.json", isExtends = false) => {
251
555
  var _a, _b;
252
- dir = path2.resolve(dir);
556
+ dir = path5.resolve(dir);
253
557
  const id = isExtends ? resolveTsConfigFromExtends(dir, name) : resolveTsConfigFromFile(dir, name);
254
558
  if (!id)
255
559
  return null;
256
560
  const data = jsoncParse(fs.readFileSync(id, "utf-8"));
257
- const configDir = path2.dirname(id);
561
+ const configDir = path5.dirname(id);
258
562
  if ((_a = data.compilerOptions) == null ? void 0 : _a.baseUrl) {
259
- data.compilerOptions.baseUrl = path2.join(
563
+ data.compilerOptions.baseUrl = path5.join(
260
564
  configDir,
261
565
  data.compilerOptions.baseUrl
262
566
  );
@@ -292,7 +596,7 @@ var loadTsConfigInternal = (dir = process.cwd(), name = "tsconfig.json", isExten
292
596
  };
293
597
  var loadTsConfig = (dir, name) => loadTsConfigInternal(dir, name);
294
598
 
295
- // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.0/node_modules/bundle-require/dist/index.js
599
+ // ../../node_modules/.pnpm/bundle-require@5.0.0_esbuild@0.25.6/node_modules/bundle-require/dist/index.js
296
600
  var tsconfigPathsToRegExp = (paths) => {
297
601
  return Object.keys(paths || {}).map((key) => {
298
602
  return new RegExp(`^${key.replace(/\*/, ".*")}$`);
@@ -365,340 +669,96 @@ async function compile(configurationPath, outfile) {
365
669
  plugins,
366
670
  outfile,
367
671
  metafile: true
368
- });
369
- return Object.keys(result.metafile.inputs);
370
- }
371
-
372
- // src/configurationReader.ts
373
- var ConfigurationError = class extends Error {
374
- type;
375
- constructor(type, message) {
376
- super(message);
377
- this.type = type;
378
- }
379
- };
380
- var defaultConfigName = "content-collection-config.mjs";
381
- function resolveCacheDir(config, options) {
382
- if (options.cacheDir) {
383
- return options.cacheDir;
384
- }
385
- return path3.join(path3.dirname(config), ".content-collections", "cache");
386
- }
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
- );
396
- }
397
- const cacheDir = resolveCacheDir(configurationPath, options);
398
- await fs2.mkdir(cacheDir, { recursive: true });
399
- const outfile = path3.join(cacheDir, options.configName);
400
- try {
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");
406
- return {
407
- ...module.default,
408
- path: configurationPath,
409
- inputPaths: configurationPaths.map((p) => path3.resolve(p)),
410
- generateTypes: true,
411
- checksum
412
- };
413
- } catch (error) {
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
432
- }
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
463
- }
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`
472
- );
473
- }
474
- return parser;
475
- }
476
- return configuredParser;
477
- }
478
- function defineParser(parser) {
479
- if (typeof parser === "function") {
480
- return {
481
- hasContent: false,
482
- parse: parser
483
- };
484
- }
485
- return parser;
486
- }
487
-
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 });
495
- }
496
- function isDefined(value) {
497
- return value !== void 0 && value !== null;
498
- }
499
- function orderByPath(a, b) {
500
- return a.path.localeCompare(b.path);
501
- }
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
- )
514
- );
515
- }
516
- function posixToNativePath(pathName) {
517
- if (path4.sep !== path4.posix.sep) {
518
- return pathName.replaceAll(path4.posix.sep, path4.sep);
519
- }
520
- return pathName;
521
- }
522
- function toError(error) {
523
- return error instanceof Error ? error : new Error(String(error));
672
+ });
673
+ return Object.keys(result.metafile.inputs);
524
674
  }
525
675
 
526
- // src/collector.ts
527
- var CollectError = class extends Error {
676
+ // src/configurationReader.ts
677
+ var ConfigurationError = class extends Error {
528
678
  type;
529
679
  constructor(type, message) {
530
680
  super(message);
531
681
  this.type = type;
532
682
  }
533
683
  };
534
- function createCollector(emitter, baseDirectory = ".") {
535
- async function read(filePath) {
536
- try {
537
- return await readFile2(filePath, "utf-8");
538
- } catch (error) {
539
- emitter.emit("collector:read-error", {
540
- filePath,
541
- error: new CollectError("Read", String(error))
542
- });
543
- return null;
544
- }
684
+ var defaultConfigName = "content-collection-config.mjs";
685
+ function resolveCacheDir(config, options) {
686
+ if (options.cacheDir) {
687
+ return options.cacheDir;
545
688
  }
546
- async function collectFile(collection, filePath) {
547
- const absolutePath = path5.join(
548
- baseDirectory,
549
- collection.directory,
550
- filePath
551
- );
552
- const file = await read(absolutePath);
553
- if (!file) {
554
- return null;
689
+ return path6.join(path6.dirname(config), ".content-collections", "cache");
690
+ }
691
+ function createConfigurationReader() {
692
+ return async (configurationPath, options = {
693
+ configName: defaultConfigName
694
+ }) => {
695
+ if (!existsSync2(configurationPath)) {
696
+ throw new ConfigurationError(
697
+ "Read",
698
+ `configuration file ${configurationPath} does not exist`
699
+ );
555
700
  }
701
+ const cacheDir = resolveCacheDir(configurationPath, options);
702
+ await fs2.mkdir(cacheDir, { recursive: true });
703
+ const outfile = path6.join(cacheDir, options.configName);
556
704
  try {
557
- const parser = getParser(collection.parser);
558
- const data = await parser.parse(file);
705
+ const configurationPaths = await compile(configurationPath, outfile);
706
+ const module = await import(`file://${path6.resolve(outfile)}?x=${Date.now()}`);
707
+ const hash = createHash2("sha256");
708
+ hash.update(await fs2.readFile(outfile, "utf-8"));
709
+ const checksum = hash.digest("hex");
559
710
  return {
560
- data,
561
- path: filePath
711
+ ...module.default,
712
+ path: configurationPath,
713
+ inputPaths: configurationPaths.map((p) => path6.resolve(p)),
714
+ generateTypes: true,
715
+ checksum
562
716
  };
563
717
  } catch (error) {
564
- emitter.emit("collector:parse-error", {
565
- filePath: path5.join(collection.directory, filePath),
566
- error: new CollectError("Parse", String(error))
567
- });
568
- return null;
569
- }
570
- }
571
- function createIgnorePattern(collection) {
572
- if (collection.exclude) {
573
- if (Array.isArray(collection.exclude)) {
574
- return collection.exclude;
575
- } else {
576
- return [collection.exclude];
577
- }
718
+ throw new ConfigurationError(
719
+ "Compile",
720
+ `configuration file ${configurationPath} is invalid: ${error}`
721
+ );
578
722
  }
579
- return void 0;
580
- }
581
- async function resolveCollection(collection) {
582
- const collectionDirectory = path5.join(baseDirectory, collection.directory);
583
- const include = Array.isArray(collection.include) ? collection.include : [collection.include];
584
- const filePaths = await glob(include, {
585
- cwd: collectionDirectory,
586
- onlyFiles: true,
587
- absolute: false,
588
- ignore: createIgnorePattern(collection)
589
- });
590
- const promises = filePaths.map(
591
- (filePath) => collectFile(collection, posixToNativePath(filePath))
592
- );
593
- const files = await Promise.all(promises);
594
- return {
595
- ...collection,
596
- files: files.filter(isDefined).sort(orderByPath)
597
- };
598
- }
599
- async function collect(unresolvedCollections) {
600
- const promises = unresolvedCollections.map(
601
- (collection) => resolveCollection(collection)
602
- );
603
- return await Promise.all(promises);
604
- }
605
- return {
606
- collect,
607
- collectFile
608
723
  };
609
724
  }
610
725
 
611
- // src/synchronizer.ts
612
- import path6 from "node:path";
613
- import picomatch from "picomatch";
614
- function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
615
- function findCollections(filePath) {
616
- const resolvedFilePath = path6.resolve(filePath);
617
- return collections.filter((collection) => {
618
- return resolvedFilePath.startsWith(
619
- path6.resolve(baseDirectory, collection.directory)
620
- );
621
- });
622
- }
623
- function createRelativePath(collectionPath, filePath) {
624
- const resolvedCollectionPath = path6.resolve(baseDirectory, collectionPath);
625
- const resolvedFilePath = path6.resolve(filePath);
626
- let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
627
- if (relativePath.startsWith(path6.sep)) {
628
- relativePath = relativePath.slice(path6.sep.length);
629
- }
630
- return relativePath;
631
- }
632
- function resolve2(filePath) {
633
- const collections2 = findCollections(filePath);
634
- return collections2.map((collection) => {
635
- const relativePath = createRelativePath(collection.directory, filePath);
636
- return {
637
- collection,
638
- relativePath
639
- };
640
- }).filter(({ collection, relativePath }) => {
641
- return picomatch.isMatch(relativePath, collection.include, {
642
- // @see https://github.com/sdorra/content-collections/issues/602
643
- windows: process.platform === "win32",
644
- ignore: collection.exclude
645
- });
646
- });
726
+ // src/config.ts
727
+ var skippedSymbol = Symbol("skipped");
728
+ var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
729
+ function defineCollection(collection) {
730
+ let typeName = collection.typeName;
731
+ if (!typeName) {
732
+ typeName = generateTypeName(collection.name);
647
733
  }
648
- function deleted(filePath) {
649
- const resolvedCollections = resolve2(filePath);
650
- if (resolvedCollections.length === 0) {
651
- return false;
652
- }
653
- let changed2 = false;
654
- for (const { collection, relativePath } of resolvedCollections) {
655
- const index = collection.files.findIndex(
656
- (file) => file.path === relativePath
657
- );
658
- const deleted2 = collection.files.splice(index, 1);
659
- if (deleted2.length > 0) {
660
- changed2 = true;
661
- }
662
- }
663
- return changed2;
734
+ let parser = collection.parser;
735
+ if (!parser) {
736
+ parser = "frontmatter";
737
+ } else if (!isValidParser(parser)) {
738
+ throw new ConfigurationError(
739
+ "Read",
740
+ `Parser ${parser} is not valid a parser`
741
+ );
664
742
  }
665
- async function changed(filePath) {
666
- const resolvedCollections = resolve2(filePath);
667
- if (resolvedCollections.length === 0) {
668
- return false;
669
- }
670
- let changed2 = false;
671
- for (const { collection, relativePath } of resolvedCollections) {
672
- const index = collection.files.findIndex(
673
- (file2) => file2.path === relativePath
674
- );
675
- const file = await readCollectionFile(collection, relativePath);
676
- if (file) {
677
- changed2 = true;
678
- if (index === -1) {
679
- collection.files.push(file);
680
- collection.files.sort(orderByPath);
681
- } else {
682
- collection.files[index] = file;
683
- }
684
- }
685
- }
686
- return changed2;
743
+ let schema2 = collection.schema;
744
+ if (!schema2["~standard"]) {
745
+ warnDeprecated("legacySchema");
746
+ schema2 = z.object(schema2(z));
687
747
  }
688
748
  return {
689
- deleted,
690
- changed
749
+ ...collection,
750
+ typeName,
751
+ parser,
752
+ schema: schema2
691
753
  };
692
754
  }
693
-
694
- // src/transformer.ts
695
- import os from "node:os";
696
- import { basename, dirname as dirname2, extname } from "node:path";
697
- import pLimit from "p-limit";
755
+ function defineConfig(config) {
756
+ return config;
757
+ }
698
758
 
699
759
  // src/serializer.ts
700
760
  import serializeJs from "serialize-javascript";
701
- import z from "zod";
761
+ import z2 from "zod";
702
762
 
703
763
  // src/import.ts
704
764
  var importSymbol = Symbol("import");
@@ -720,24 +780,24 @@ function createNamedImport(name, path10) {
720
780
  }
721
781
 
722
782
  // src/serializer.ts
723
- var literalSchema = z.union([
783
+ var literalSchema = z2.union([
724
784
  // json
725
- z.string(),
726
- z.number(),
727
- z.boolean(),
728
- z.null(),
785
+ z2.string(),
786
+ z2.number(),
787
+ z2.boolean(),
788
+ z2.null(),
729
789
  // serializable-javascript
730
- z.undefined(),
731
- z.date(),
732
- z.map(z.unknown(), z.unknown()),
733
- z.set(z.unknown()),
734
- z.bigint()
790
+ z2.undefined(),
791
+ z2.date(),
792
+ z2.map(z2.unknown(), z2.unknown()),
793
+ z2.set(z2.unknown()),
794
+ z2.bigint()
735
795
  ]);
736
- var schema = z.lazy(
737
- () => z.union([literalSchema, z.array(schema), z.record(schema)])
796
+ var schema = z2.lazy(
797
+ () => z2.union([literalSchema, z2.array(schema), z2.record(schema)])
738
798
  );
739
799
  var extension = "js";
740
- var serializableSchema = z.record(schema);
800
+ var serializableSchema = z2.record(schema);
741
801
  function createImport(imp, variableName) {
742
802
  const variableDeclaration = imp.name ? `{ ${imp.name} as ${variableName} }` : variableName;
743
803
  return `import ${variableDeclaration} from "${imp.path}";
@@ -781,6 +841,9 @@ var TransformError = class extends Error {
781
841
  this.type = type;
782
842
  }
783
843
  };
844
+ function isSkippedSignal(signal) {
845
+ return signal[skippedSymbol] === true;
846
+ }
784
847
  function createPath(path10, ext) {
785
848
  let p = path10.slice(0, -ext.length);
786
849
  if (p.endsWith("/index")) {
@@ -872,7 +935,11 @@ function createTransformer(emitter, cacheManager) {
872
935
  return collection.documents.map((doc) => doc.document);
873
936
  }
874
937
  },
875
- cache: cache.cacheFn
938
+ cache: cache.cacheFn,
939
+ skip: (reason) => ({
940
+ [skippedSymbol]: true,
941
+ reason
942
+ })
876
943
  };
877
944
  }
878
945
  async function transformDocument(collections, collection, transform, doc) {
@@ -881,10 +948,18 @@ function createTransformer(emitter, cacheManager) {
881
948
  try {
882
949
  const document = await transform(doc.document, context2);
883
950
  await cache.tidyUp();
884
- return {
885
- ...doc,
886
- document
887
- };
951
+ if (isSkippedSignal(document)) {
952
+ emitter.emit("transformer:document-skipped", {
953
+ collection,
954
+ filePath: join3(collection.directory, doc.document._meta.filePath),
955
+ reason: document.reason
956
+ });
957
+ } else {
958
+ return {
959
+ ...doc,
960
+ document
961
+ };
962
+ }
888
963
  } catch (error) {
889
964
  if (error instanceof TransformError) {
890
965
  emitter.emit("transformer:error", {
@@ -1191,13 +1266,24 @@ async function createBuilder(configurationPath, options = {
1191
1266
  }, emitter = createEmitter()) {
1192
1267
  const readConfiguration = createConfigurationReader();
1193
1268
  const baseDirectory = path9.dirname(configurationPath);
1269
+ const configuration = await readConfiguration(configurationPath, options);
1270
+ return createInternalBuilder(
1271
+ configuration,
1272
+ baseDirectory,
1273
+ options,
1274
+ emitter
1275
+ );
1276
+ }
1277
+ async function createInternalBuilder(initialConfiguration, baseDirectory, options, emitter) {
1278
+ const readConfiguration = createConfigurationReader();
1279
+ const configurationPath = initialConfiguration.path;
1194
1280
  const outputDirectory = resolveOutputDir(baseDirectory, options);
1195
1281
  emitter.emit("builder:created", {
1196
1282
  createdAt: Date.now(),
1197
1283
  configurationPath,
1198
1284
  outputDirectory
1199
1285
  });
1200
- let configuration = await readConfiguration(configurationPath, options);
1286
+ let configuration = initialConfiguration;
1201
1287
  let watcher = null;
1202
1288
  let context2 = await createBuildContext({
1203
1289
  emitter,
@@ -1283,63 +1369,6 @@ async function createBuilder(configurationPath, options = {
1283
1369
  on: emitter.on
1284
1370
  };
1285
1371
  }
1286
-
1287
- // src/config.ts
1288
- import { z as z2 } from "zod";
1289
-
1290
- // src/warn.ts
1291
- var deprecations = {
1292
- legacySchema: `The use of a function as a schema is deprecated.
1293
- Please use a StandardSchema compliant library directly.
1294
- For more information, see:
1295
- https://content-collections.dev/docs/deprecations/schema-as-function`
1296
- };
1297
- var _suppressDeprecatedWarnings = [];
1298
- function suppressDeprecatedWarnings(...deprecations2) {
1299
- for (const deprecation of deprecations2) {
1300
- if (deprecation === "all") {
1301
- _suppressDeprecatedWarnings.push(
1302
- ...Object.keys(deprecations2)
1303
- );
1304
- return;
1305
- } else {
1306
- _suppressDeprecatedWarnings.push(deprecation);
1307
- }
1308
- }
1309
- }
1310
- function warnDeprecated(deprecation, logger = console.warn) {
1311
- if (_suppressDeprecatedWarnings.includes(deprecation)) {
1312
- return;
1313
- }
1314
- logger(`[CC DEPRECATED]: ${deprecations[deprecation]}`);
1315
- }
1316
-
1317
- // src/config.ts
1318
- var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
1319
- function defineCollection(collection) {
1320
- let typeName = collection.typeName;
1321
- if (!typeName) {
1322
- typeName = generateTypeName(collection.name);
1323
- }
1324
- let parser = collection.parser;
1325
- if (!parser) {
1326
- parser = "frontmatter";
1327
- }
1328
- let schema2 = collection.schema;
1329
- if (!schema2["~standard"]) {
1330
- warnDeprecated("legacySchema");
1331
- schema2 = z2.object(schema2(z2));
1332
- }
1333
- return {
1334
- ...collection,
1335
- typeName,
1336
- parser,
1337
- schema: schema2
1338
- };
1339
- }
1340
- function defineConfig(config) {
1341
- return config;
1342
- }
1343
1372
  export {
1344
1373
  CollectError,
1345
1374
  ConfigurationError,
@@ -1347,9 +1376,11 @@ export {
1347
1376
  TransformError,
1348
1377
  createBuilder,
1349
1378
  createDefaultImport,
1379
+ createInternalBuilder,
1350
1380
  createNamedImport,
1351
1381
  defineCollection,
1352
1382
  defineConfig,
1353
1383
  defineParser,
1384
+ skippedSymbol,
1354
1385
  suppressDeprecatedWarnings
1355
1386
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@content-collections/core",
3
3
  "type": "module",
4
- "version": "0.10.0",
4
+ "version": "0.11.1",
5
5
  "description": "Core of Content Collections",
6
6
  "author": "Sebastian Sdorra <s.sdorra@gmail.com>",
7
7
  "license": "MIT",
@@ -33,12 +33,12 @@
33
33
  "@types/picomatch": "^3.0.1",
34
34
  "@types/pluralize": "^0.0.33",
35
35
  "@types/serialize-javascript": "^5.0.4",
36
- "@vitest/coverage-v8": "^3.1.3",
36
+ "@vitest/coverage-v8": "^3.2.4",
37
37
  "bundle-require": "^5.0.0",
38
38
  "tsup": "^8.2.4",
39
39
  "tsx": "^4.19.2",
40
40
  "typescript": "^5.5.4",
41
- "vitest": "^3.1.3"
41
+ "vitest": "^3.2.4"
42
42
  },
43
43
  "dependencies": {
44
44
  "@standard-schema/spec": "^1.0.0",
@@ -57,6 +57,6 @@
57
57
  "scripts": {
58
58
  "build": "tsup src/index.ts --format esm --dts -d dist",
59
59
  "typecheck": "tsc",
60
- "test": "vitest --run --coverage"
60
+ "test": "vitest --run --coverage --typecheck"
61
61
  }
62
62
  }