@content-collections/core 0.2.0 → 0.3.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
@@ -1,4 +1,27 @@
1
1
  import { ZodRawShape, z, ZodObject, ZodTypeAny, ZodString } from 'zod';
2
+ import { parse } from 'yaml';
3
+
4
+ type Parsers = typeof parsers;
5
+ type Parser = keyof typeof parsers;
6
+ declare function frontmatterParser(fileContent: string): {
7
+ content: string;
8
+ };
9
+ declare const parsers: {
10
+ readonly frontmatter: {
11
+ readonly hasContent: true;
12
+ readonly parse: typeof frontmatterParser;
13
+ };
14
+ readonly json: {
15
+ readonly hasContent: false;
16
+ readonly parse: (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any;
17
+ };
18
+ readonly yaml: {
19
+ readonly hasContent: false;
20
+ readonly parse: typeof parse;
21
+ };
22
+ };
23
+
24
+ type CacheFn = <TInput, TOutput>(input: TInput, compute: (input: TInput) => Promise<TOutput> | TOutput) => Promise<TOutput>;
2
25
 
3
26
  type Meta = {
4
27
  filePath: string;
@@ -13,30 +36,37 @@ type WithContent = {
13
36
  type AddContent<TShape extends ZodRawShape> = TShape extends {
14
37
  content: ZodTypeAny;
15
38
  } ? TShape : TShape & WithContent;
16
- type Schema<TShape extends ZodRawShape> = z.infer<ZodObject<AddContent<TShape>>> & {
39
+ type GetParsedShape<TParser extends Parser, TShape extends ZodRawShape> = Parsers[TParser]["hasContent"] extends true ? AddContent<TShape> : TShape;
40
+ type GetShape<TParser extends Parser | undefined, TShape extends ZodRawShape> = TParser extends Parser ? GetParsedShape<TParser, TShape> : AddContent<TShape>;
41
+ type Schema<TParser extends Parser | undefined, TShape extends ZodRawShape> = z.infer<ZodObject<GetShape<TParser, TShape>>> & {
17
42
  _meta: Meta;
18
43
  };
19
44
  type Context = {
20
- documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["schema"]>>;
45
+ documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["parser"], TCollection["schema"]>>;
46
+ cache: CacheFn;
21
47
  };
22
48
  type Z = typeof z;
23
- type CollectionRequest<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = {
49
+ type CollectionRequest<TName extends string, TShape extends ZodRawShape, TParser, TSchema, TTransformResult, TDocument> = {
24
50
  name: TName;
51
+ parser?: TParser;
25
52
  typeName?: string;
26
53
  schema: (z: Z) => TShape;
27
- transform?: (context: Context, data: TSchema) => TTransformResult;
28
- directory: string | string[];
54
+ transform?: (data: TSchema, context: Context) => TTransformResult;
55
+ directory: string;
29
56
  include: string | string[];
30
57
  onSuccess?: (documents: Array<TDocument>) => void | Promise<void>;
31
58
  };
32
- type Collection<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>, "schema"> & {
59
+ type Collection<TName extends string, TShape extends ZodRawShape, TParser extends Parser, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>, "schema"> & {
33
60
  typeName: string;
34
61
  schema: TShape;
62
+ parser: TParser;
35
63
  };
36
- type AnyCollection = Collection<any, ZodRawShape, any, any, any>;
37
- declare function defineCollection<TName extends string, TShape extends ZodRawShape, TSchema = Schema<TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TShape> : Awaited<TTransformResult>>(collection: CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>): Collection<TName, TShape, TSchema, TTransformResult, TDocument>;
64
+ type AnyCollection = Collection<any, ZodRawShape, Parser, any, any, any>;
65
+ declare function defineCollection<TName extends string, TShape extends ZodRawShape, TParser extends Parser = "frontmatter", TSchema = Schema<TParser, TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TParser, TShape> : Awaited<TTransformResult>>(collection: CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>): Collection<TName, TShape, TParser, TSchema, TTransformResult, TDocument>;
66
+ type Cache = "memory" | "file" | "none";
38
67
  type Configuration<TCollections extends Array<AnyCollection>> = {
39
68
  collections: TCollections;
69
+ cache?: Cache;
40
70
  };
41
71
  type AnyConfiguration = Configuration<Array<AnyCollection>>;
42
72
  declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
@@ -44,7 +74,7 @@ declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig)
44
74
  type Modification = "create" | "update" | "delete";
45
75
  type CollectionFile = {
46
76
  data: {
47
- content: string;
77
+ content?: string;
48
78
  [key: string]: unknown;
49
79
  };
50
80
  path: string;
@@ -52,7 +82,7 @@ type CollectionFile = {
52
82
  type CollectionByName<TConfiguration extends AnyConfiguration> = {
53
83
  [TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection;
54
84
  };
55
- type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, infer TDocument> ? TDocument : never;
85
+ type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, any, infer TDocument> ? TDocument : never;
56
86
  type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection = CollectionByName<TConfiguration>[TName]> = TCollection extends AnyCollection ? GetDocument<TCollection> : never;
57
87
 
58
88
  type CollectorEvents = {
package/dist/index.js CHANGED
@@ -21,9 +21,14 @@ function defineCollection(collection) {
21
21
  if (!typeName) {
22
22
  typeName = generateTypeName(collection.name);
23
23
  }
24
+ let parser = collection.parser;
25
+ if (!parser) {
26
+ parser = "frontmatter";
27
+ }
24
28
  return {
25
29
  ...collection,
26
30
  typeName,
31
+ parser,
27
32
  schema: collection.schema(z)
28
33
  };
29
34
  }
@@ -39,9 +44,9 @@ import path from "path";
39
44
  // package.json
40
45
  var package_default = {
41
46
  name: "@content-collections/core",
42
- version: "0.1.2",
47
+ version: "0.3.1",
43
48
  type: "module",
44
- main: "dist/index.cjs",
49
+ main: "dist/index.js",
45
50
  types: "./dist/index.d.ts",
46
51
  exports: {
47
52
  "./package.json": "./package.json",
@@ -79,12 +84,14 @@ var package_default = {
79
84
  "gray-matter": "^4.0.3",
80
85
  micromatch: "^4.0.5",
81
86
  pluralize: "^8.0.0",
87
+ yaml: "^2.3.4",
82
88
  zod: "^3.22.4"
83
89
  }
84
90
  };
85
91
 
86
92
  // src/configurationReader.ts
87
93
  import { existsSync } from "fs";
94
+ import { createHash } from "crypto";
88
95
  var ConfigurationError = class extends Error {
89
96
  type;
90
97
  constructor(type, message) {
@@ -114,7 +121,11 @@ async function compile(configurationPath, outfile) {
114
121
  }
115
122
  await esbuild.build({
116
123
  entryPoints: [configurationPath],
117
- external: [...Object.keys(package_default.dependencies), "@content-collections/*"],
124
+ packages: "external",
125
+ external: [
126
+ ...Object.keys(package_default.dependencies),
127
+ "@content-collections/*"
128
+ ],
118
129
  bundle: true,
119
130
  platform: "node",
120
131
  format: "esm",
@@ -144,19 +155,56 @@ function createConfigurationReader() {
144
155
  );
145
156
  }
146
157
  const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
158
+ const hash = createHash("sha256");
159
+ hash.update(await fs.readFile(outfile, "utf-8"));
160
+ const checksum = hash.digest("hex");
147
161
  return {
148
162
  ...module.default,
149
163
  path: configurationPath,
150
- generateTypes: true
164
+ generateTypes: true,
165
+ checksum
151
166
  };
152
167
  };
153
168
  }
154
169
 
155
170
  // src/collector.ts
156
- import matter from "gray-matter";
157
171
  import fg from "fast-glob";
158
172
  import { readFile } from "fs/promises";
159
173
  import path2 from "path";
174
+
175
+ // src/parser.ts
176
+ import matter from "gray-matter";
177
+ import { parse, stringify } from "yaml";
178
+ function frontmatterParser(fileContent) {
179
+ const { data, content } = matter(fileContent, {
180
+ engines: {
181
+ yaml: {
182
+ parse,
183
+ stringify
184
+ }
185
+ }
186
+ });
187
+ return {
188
+ ...data,
189
+ content: content.trim()
190
+ };
191
+ }
192
+ var parsers = {
193
+ frontmatter: {
194
+ hasContent: true,
195
+ parse: frontmatterParser
196
+ },
197
+ json: {
198
+ hasContent: false,
199
+ parse: JSON.parse
200
+ },
201
+ yaml: {
202
+ hasContent: false,
203
+ parse
204
+ }
205
+ };
206
+
207
+ // src/collector.ts
160
208
  var CollectError = class extends Error {
161
209
  type;
162
210
  constructor(type, message) {
@@ -176,55 +224,37 @@ function createCollector(emitter, baseDirectory = ".") {
176
224
  return null;
177
225
  }
178
226
  }
179
- function parse(file) {
180
- const { data, content } = matter(file);
181
- return {
182
- ...data,
183
- content
184
- };
185
- }
186
- async function collectFile(directory, filePath) {
187
- const file = await read(path2.join(directory, filePath));
227
+ async function collectFile(collection, filePath) {
228
+ const absolutePath = path2.join(baseDirectory, collection.directory, filePath);
229
+ const file = await read(absolutePath);
188
230
  if (!file) {
189
231
  return null;
190
232
  }
191
233
  try {
192
- const data = parse(file);
234
+ const data = parsers[collection.parser].parse(file);
193
235
  return {
194
236
  data,
195
237
  path: filePath
196
238
  };
197
239
  } catch (error) {
198
240
  emitter.emit("collector:parse-error", {
199
- filePath: path2.join(directory, filePath),
241
+ filePath: path2.join(collection.directory, filePath),
200
242
  error: new CollectError("Parse", String(error))
201
243
  });
202
244
  return null;
203
245
  }
204
246
  }
205
- async function resolveDirectory(collection, directory) {
206
- const collectionDirectory = path2.join(baseDirectory, directory);
247
+ async function resolveCollection(collection) {
248
+ const collectionDirectory = path2.join(baseDirectory, collection.directory);
207
249
  const filePaths = await fg(collection.include, {
208
250
  cwd: collectionDirectory,
209
251
  onlyFiles: true,
210
252
  absolute: false
211
253
  });
212
254
  const promises = filePaths.map(
213
- (filePath) => collectFile(collectionDirectory, filePath)
255
+ (filePath) => collectFile(collection, filePath)
214
256
  );
215
- return Promise.all(promises);
216
- }
217
- async function resolveCollection(collection) {
218
- let files = [];
219
- if (typeof collection.directory === "string") {
220
- files = await resolveDirectory(collection, collection.directory);
221
- } else {
222
- const promises = collection.directory.map(
223
- (directory) => resolveDirectory(collection, directory)
224
- );
225
- const resolved = await Promise.all(promises);
226
- files = resolved.flat();
227
- }
257
+ const files = await Promise.all(promises);
228
258
  return {
229
259
  ...collection,
230
260
  files: files.filter(isDefined).sort(orderByPath)
@@ -326,23 +356,27 @@ var TransformError = class extends Error {
326
356
  this.type = type;
327
357
  }
328
358
  };
329
- function createPath(path6, ext) {
330
- let p = path6.slice(0, -ext.length);
359
+ function createPath(path7, ext) {
360
+ let p = path7.slice(0, -ext.length);
331
361
  if (p.endsWith("/index")) {
332
362
  p = p.slice(0, -6);
333
363
  }
334
364
  return p;
335
365
  }
336
- function createTransformer(emitter) {
337
- function createSchema(schema) {
366
+ function createTransformer(emitter, cacheManager) {
367
+ function createSchema(parserName, schema) {
368
+ const parser = parsers[parserName];
369
+ if (!parser.hasContent) {
370
+ return z2.object(schema);
371
+ }
338
372
  return z2.object({
339
373
  content: z2.string(),
340
374
  ...schema
341
375
  });
342
376
  }
343
377
  async function parseFile(collection, file) {
344
- const { data, path: path6 } = file;
345
- const schema = createSchema(collection.schema);
378
+ const { data, path: path7 } = file;
379
+ const schema = createSchema(collection.parser, collection.schema);
346
380
  let parsedData = await schema.safeParseAsync(data);
347
381
  if (!parsedData.success) {
348
382
  emitter.emit("transformer:validation-error", {
@@ -352,7 +386,7 @@ function createTransformer(emitter) {
352
386
  });
353
387
  return null;
354
388
  }
355
- const ext = extname(path6);
389
+ const ext = extname(path7);
356
390
  let extension = ext;
357
391
  if (extension.startsWith(".")) {
358
392
  extension = extension.slice(1);
@@ -360,11 +394,11 @@ function createTransformer(emitter) {
360
394
  const document = {
361
395
  ...parsedData.data,
362
396
  _meta: {
363
- filePath: path6,
364
- fileName: basename(path6),
365
- directory: dirname(path6),
397
+ filePath: path7,
398
+ fileName: basename(path7),
399
+ directory: dirname(path7),
366
400
  extension,
367
- path: createPath(path6, ext)
401
+ path: createPath(path7, ext)
368
402
  }
369
403
  };
370
404
  return {
@@ -380,7 +414,7 @@ function createTransformer(emitter) {
380
414
  documents: (await Promise.all(promises)).filter(isDefined)
381
415
  };
382
416
  }
383
- function createContext(collections) {
417
+ function createContext(collections, cache) {
384
418
  return {
385
419
  documents: (collection) => {
386
420
  const resolved = collections.find((c) => c.name === collection.name);
@@ -391,19 +425,22 @@ function createTransformer(emitter) {
391
425
  );
392
426
  }
393
427
  return resolved.documents.map((doc) => doc.document);
394
- }
428
+ },
429
+ cache: cache.cacheFn
395
430
  };
396
431
  }
397
432
  async function transformCollection(collections, collection) {
398
433
  if (collection.transform) {
399
434
  const docs = [];
400
- const context = createContext(collections);
401
435
  for (const doc of collection.documents) {
436
+ const cache = cacheManager.cache(collection.name, doc.document._meta.path);
437
+ const context = createContext(collections, cache);
402
438
  try {
403
439
  docs.push({
404
440
  ...doc,
405
- document: await collection.transform(context, doc.document)
441
+ document: await collection.transform(doc.document, context)
406
442
  });
443
+ await cache.tidyUp();
407
444
  } catch (error) {
408
445
  if (error instanceof TransformError) {
409
446
  emitter.emit("transformer:error", {
@@ -418,6 +455,7 @@ function createTransformer(emitter) {
418
455
  }
419
456
  }
420
457
  }
458
+ await cacheManager.flush();
421
459
  return docs;
422
460
  }
423
461
  return collection.documents;
@@ -438,26 +476,13 @@ function createTransformer(emitter) {
438
476
  import micromatch from "micromatch";
439
477
  import path4 from "path";
440
478
  function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
441
- function findCollectionAndDirectory(filePath) {
479
+ function findCollection(filePath) {
442
480
  const resolvedFilePath = path4.resolve(filePath);
443
- for (const collection of collections) {
444
- const directories = [];
445
- if (typeof collection.directory === "string") {
446
- directories.push(collection.directory);
447
- } else {
448
- directories.push(...collection.directory);
449
- }
450
- for (const directory of directories) {
451
- const resolvedDirectory = path4.resolve(baseDirectory, directory);
452
- if (resolvedFilePath.startsWith(resolvedDirectory)) {
453
- return {
454
- collection,
455
- directory
456
- };
457
- }
458
- }
459
- }
460
- return null;
481
+ return collections.find((collection) => {
482
+ return resolvedFilePath.startsWith(
483
+ path4.resolve(baseDirectory, collection.directory)
484
+ );
485
+ });
461
486
  }
462
487
  function createRelativePath(collectionPath, filePath) {
463
488
  const resolvedCollectionPath = path4.resolve(baseDirectory, collectionPath);
@@ -469,12 +494,11 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
469
494
  return relativePath;
470
495
  }
471
496
  function resolve(filePath) {
472
- const collectionAndDirectory = findCollectionAndDirectory(filePath);
473
- if (!collectionAndDirectory) {
497
+ const collection = findCollection(filePath);
498
+ if (!collection) {
474
499
  return null;
475
500
  }
476
- const { collection, directory } = collectionAndDirectory;
477
- const relativePath = createRelativePath(directory, filePath);
501
+ const relativePath = createRelativePath(collection.directory, filePath);
478
502
  if (!micromatch.isMatch(relativePath, collection.include)) {
479
503
  return null;
480
504
  }
@@ -504,22 +528,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
504
528
  const index = collection.files.findIndex(
505
529
  (file2) => file2.path === relativePath
506
530
  );
507
- const directories = [];
508
- if (typeof collection.directory === "string") {
509
- directories.push(collection.directory);
510
- } else {
511
- directories.push(...collection.directory);
512
- }
513
- let file = null;
514
- for (const directory of directories) {
515
- file = await readCollectionFile(
516
- path4.join(baseDirectory, directory),
517
- relativePath
518
- );
519
- if (file) {
520
- break;
521
- }
522
- }
531
+ const file = await readCollectionFile(collection, relativePath);
523
532
  if (!file) {
524
533
  return false;
525
534
  }
@@ -538,7 +547,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
538
547
  }
539
548
 
540
549
  // src/builder.ts
541
- import path5 from "path";
550
+ import path6 from "path";
542
551
 
543
552
  // src/watcher.ts
544
553
  import * as watcher from "@parcel/watcher";
@@ -563,7 +572,7 @@ async function createWatcher(emitter, paths, sync, build2) {
563
572
  }
564
573
  };
565
574
  const subscriptions = await Promise.all(
566
- paths.map((path6) => watcher.subscribe(path6, onChange))
575
+ paths.map((path7) => watcher.subscribe(path7, onChange))
567
576
  );
568
577
  return {
569
578
  unsubscribe: async () => {
@@ -604,12 +613,96 @@ function createEmitter() {
604
613
  };
605
614
  }
606
615
 
616
+ // src/cache.ts
617
+ import path5, { join } from "path";
618
+ import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
619
+ import { existsSync as existsSync2 } from "fs";
620
+ import { createHash as createHash2 } from "crypto";
621
+ function createKey(config, input) {
622
+ return createHash2("sha256").update(config).update(JSON.stringify(input)).digest("hex");
623
+ }
624
+ async function createCacheDirectory(directory) {
625
+ const cacheDirectory = path5.join(directory, ".content-collections", "cache");
626
+ if (!existsSync2(cacheDirectory)) {
627
+ await mkdir(cacheDirectory, { recursive: true });
628
+ }
629
+ return cacheDirectory;
630
+ }
631
+ function fileName(input) {
632
+ return input.replace(/[^a-z0-9]/gi, "_").toLowerCase();
633
+ }
634
+ async function createCacheManager(baseDirectory, configChecksum) {
635
+ const cacheDirectory = await createCacheDirectory(baseDirectory);
636
+ let mapping = {};
637
+ const mappingPath = join(cacheDirectory, "mapping.json");
638
+ if (existsSync2(mappingPath)) {
639
+ mapping = JSON.parse(await readFile2(mappingPath, "utf-8"));
640
+ }
641
+ async function flush() {
642
+ await writeFile(mappingPath, JSON.stringify(mapping));
643
+ }
644
+ function cache(collection, file) {
645
+ const directory = join(
646
+ cacheDirectory,
647
+ fileName(collection),
648
+ fileName(file)
649
+ );
650
+ let collectionMapping = mapping[collection];
651
+ if (!collectionMapping) {
652
+ collectionMapping = {};
653
+ mapping[collection] = collectionMapping;
654
+ }
655
+ let fileMapping = collectionMapping[file];
656
+ if (!fileMapping) {
657
+ fileMapping = [];
658
+ collectionMapping[file] = fileMapping;
659
+ }
660
+ let newFileMapping = [];
661
+ const cacheFn = async (input, fn) => {
662
+ const key = createKey(configChecksum, input);
663
+ newFileMapping.push(key);
664
+ const filePath = join(directory, `${key}.cache`);
665
+ if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
666
+ if (existsSync2(filePath)) {
667
+ return JSON.parse(await readFile2(filePath, "utf-8"));
668
+ }
669
+ }
670
+ const output = await fn(input);
671
+ if (!existsSync2(directory)) {
672
+ await mkdir(directory, { recursive: true });
673
+ }
674
+ await writeFile(filePath, JSON.stringify(output));
675
+ return output;
676
+ };
677
+ const tidyUp = async () => {
678
+ const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
679
+ for (const key of filesToDelete) {
680
+ const filePath = join(directory, `${key}.cache`);
681
+ if (existsSync2(filePath)) {
682
+ await unlink(filePath);
683
+ }
684
+ }
685
+ if (collectionMapping) {
686
+ collectionMapping[file] = newFileMapping;
687
+ }
688
+ };
689
+ return {
690
+ cacheFn,
691
+ tidyUp
692
+ };
693
+ }
694
+ return {
695
+ cache,
696
+ flush
697
+ };
698
+ }
699
+
607
700
  // src/builder.ts
608
701
  function resolveOutputDir(baseDirectory, options) {
609
702
  if (options.outputDir) {
610
703
  return options.outputDir;
611
704
  }
612
- return path5.join(baseDirectory, ".content-collections", "generated");
705
+ return path6.join(baseDirectory, ".content-collections", "generated");
613
706
  }
614
707
  async function createBuilder(configurationPath, options = {
615
708
  configName: defaultConfigName
@@ -617,7 +710,7 @@ async function createBuilder(configurationPath, options = {
617
710
  const emitter = createEmitter();
618
711
  const readConfiguration = createConfigurationReader();
619
712
  const configuration = await readConfiguration(configurationPath, options);
620
- const baseDirectory = path5.dirname(configurationPath);
713
+ const baseDirectory = path6.dirname(configurationPath);
621
714
  const directory = resolveOutputDir(baseDirectory, options);
622
715
  const collector = createCollector(emitter, baseDirectory);
623
716
  const writer = await createWriter(directory);
@@ -631,7 +724,8 @@ async function createBuilder(configurationPath, options = {
631
724
  resolved,
632
725
  baseDirectory
633
726
  );
634
- const transform = createTransformer(emitter);
727
+ const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
728
+ const transform = createTransformer(emitter, cacheManager);
635
729
  async function sync(modification, filePath) {
636
730
  if (modification === "delete") {
637
731
  return synchronizer.deleted(filePath);
@@ -655,16 +749,9 @@ async function createBuilder(configurationPath, options = {
655
749
  });
656
750
  }
657
751
  async function watch() {
658
- const paths = [];
659
- for (const collection of resolved) {
660
- if (typeof collection.directory === "string") {
661
- paths.push(path5.join(baseDirectory, collection.directory));
662
- } else {
663
- paths.push(
664
- ...collection.directory.map((dir) => path5.join(baseDirectory, dir))
665
- );
666
- }
667
- }
752
+ const paths = resolved.map(
753
+ (collection) => path6.join(baseDirectory, collection.directory)
754
+ );
668
755
  const watcher2 = await createWatcher(emitter, paths, sync, build2);
669
756
  return watcher2;
670
757
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@content-collections/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
- "main": "dist/index.cjs",
5
+ "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "exports": {
8
8
  "./package.json": "./package.json",
@@ -33,6 +33,7 @@
33
33
  "gray-matter": "^4.0.3",
34
34
  "micromatch": "^4.0.5",
35
35
  "pluralize": "^8.0.0",
36
+ "yaml": "^2.3.4",
36
37
  "zod": "^3.22.4"
37
38
  },
38
39
  "scripts": {
package/dist/index.cjs DELETED
@@ -1,416 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- applyConfig: () => applyConfig,
34
- build: () => build2,
35
- createRunner: () => createRunner,
36
- defineCollection: () => defineCollection,
37
- defineConfig: () => defineConfig
38
- });
39
- module.exports = __toCommonJS(src_exports);
40
- var import_path2 = __toESM(require("path"), 1);
41
-
42
- // src/applyConfig.ts
43
- var esbuild = __toESM(require("esbuild"), 1);
44
- var import_promises = __toESM(require("fs/promises"), 1);
45
- var import_node_path = __toESM(require("path"), 1);
46
-
47
- // package.json
48
- var package_default = {
49
- name: "@mdx-collections/core",
50
- version: "1.0.0",
51
- type: "module",
52
- main: "dist/index.cjs",
53
- exports: {
54
- "./package.json": "./package.json",
55
- ".": {
56
- import: "./dist/index.js",
57
- require: "./dist/index.cjs"
58
- }
59
- },
60
- types: "./dist/index.d.ts",
61
- scripts: {
62
- build: "tsup src/index.ts --format esm,cjs --dts -d dist",
63
- typecheck: "tsc",
64
- test: "vitest --run --coverage"
65
- },
66
- devDependencies: {
67
- "@types/micromatch": "^4.0.6",
68
- "@types/node": "^20.9.0",
69
- "@types/pluralize": "^0.0.33",
70
- "@vitest/coverage-v8": "^0.34.6",
71
- tsup: "^7.2.0",
72
- tsx: "^4.1.1",
73
- typescript: "^5.3.2",
74
- vitest: "^0.34.6"
75
- },
76
- dependencies: {
77
- camelcase: "^8.0.0",
78
- esbuild: "^0.19.5",
79
- "fast-glob": "^3.3.2",
80
- "gray-matter": "^4.0.3",
81
- micromatch: "^4.0.5",
82
- pluralize: "^8.0.0",
83
- zod: "^3.22.4"
84
- }
85
- };
86
-
87
- // src/applyConfig.ts
88
- var importPathPlugin = {
89
- name: "import-path",
90
- setup(build3) {
91
- build3.onResolve({ filter: /^\@mdx-collections\/core$/ }, () => {
92
- return { path: import_node_path.default.join(__dirname, "index.ts"), external: true };
93
- });
94
- }
95
- };
96
- function resolveCacheDir(config, options) {
97
- if (options.cacheDir) {
98
- return options.cacheDir;
99
- }
100
- return import_node_path.default.join(import_node_path.default.dirname(config), ".mdx-collections", "cache");
101
- }
102
- async function applyConfig(config, options = {
103
- configName: "mdx-collection-config.mjs"
104
- }) {
105
- const cacheDir = resolveCacheDir(config, options);
106
- await import_promises.default.mkdir(cacheDir, { recursive: true });
107
- const outfile = import_node_path.default.join(cacheDir, options.configName);
108
- const plugins = [];
109
- if (process.env.NODE_ENV === "test") {
110
- plugins.push(importPathPlugin);
111
- }
112
- await esbuild.build({
113
- entryPoints: [config],
114
- external: [...Object.keys(package_default.dependencies), "@mdx-collections/*"],
115
- bundle: true,
116
- platform: "node",
117
- format: "esm",
118
- plugins,
119
- outfile
120
- });
121
- const module2 = await import(`file://${import_node_path.default.resolve(outfile)}?x=${Date.now()}`);
122
- return {
123
- ...module2.default,
124
- path: config,
125
- generateTypes: true
126
- };
127
- }
128
-
129
- // src/run.ts
130
- var import_promises3 = __toESM(require("fs/promises"), 1);
131
- var import_node_path2 = __toESM(require("path"), 1);
132
- var import_pluralize = __toESM(require("pluralize"), 1);
133
-
134
- // src/collect.ts
135
- var import_gray_matter = __toESM(require("gray-matter"), 1);
136
- var import_fast_glob = __toESM(require("fast-glob"), 1);
137
- var import_promises2 = require("fs/promises");
138
- var import_path = __toESM(require("path"), 1);
139
- var import_micromatch = __toESM(require("micromatch"), 1);
140
- async function collectFile(directory, filePath) {
141
- const file = await (0, import_promises2.readFile)(import_path.default.join(directory, filePath), "utf-8");
142
- const { data, content: body } = (0, import_gray_matter.default)(file);
143
- return {
144
- data,
145
- body,
146
- path: filePath
147
- };
148
- }
149
- async function resolveCollection(collection) {
150
- const filePaths = await (0, import_fast_glob.default)(collection.include, {
151
- cwd: collection.directory,
152
- onlyFiles: true,
153
- absolute: false
154
- });
155
- const promises = filePaths.map(
156
- (filePath) => collectFile(collection.directory, filePath)
157
- );
158
- return {
159
- ...collection,
160
- files: await Promise.all(promises)
161
- };
162
- }
163
- async function collect(unresolvedCollections) {
164
- const promises = unresolvedCollections.map(
165
- (collection) => resolveCollection(collection)
166
- );
167
- return await Promise.all(promises);
168
- }
169
- function createRelativePath(directory, filePath) {
170
- if (!filePath.startsWith(directory)) {
171
- throw new Error("Path is not in collection directory");
172
- }
173
- let relativePath = filePath.slice(directory.length);
174
- if (relativePath.startsWith("/")) {
175
- relativePath = relativePath.slice(1);
176
- }
177
- return relativePath;
178
- }
179
- function isIncluded(collection, path5) {
180
- if (path5.startsWith(collection.directory)) {
181
- const relativePath = createRelativePath(collection.directory, path5);
182
- return import_micromatch.default.isMatch(relativePath, collection.include);
183
- }
184
- return false;
185
- }
186
- async function syncFile(collection, modification, path5) {
187
- if ("added" === modification) {
188
- const file = await collectFile(collection.directory, path5);
189
- collection.files.push(file);
190
- } else if ("changed" === modification) {
191
- const file = await collectFile(collection.directory, path5);
192
- const index = collection.files.findIndex((file2) => file2.path === path5);
193
- collection.files[index] = file;
194
- } else if ("removed" === modification) {
195
- const index = collection.files.findIndex((file) => file.path === path5);
196
- collection.files.splice(index, 1);
197
- }
198
- }
199
- function sync(collection, modification, path5) {
200
- const relativePath = createRelativePath(collection.directory, path5);
201
- return syncFile(collection, modification, relativePath);
202
- }
203
-
204
- // src/transformer.ts
205
- var TransformError = class extends Error {
206
- type;
207
- constructor(type, message) {
208
- super(message);
209
- this.type = type;
210
- }
211
- };
212
- var throwingErrorHandler = (error) => {
213
- throw error;
214
- };
215
- function isDefined(value) {
216
- return value !== void 0 && value !== null;
217
- }
218
- async function transform(untransformedCollections, errorHandler = throwingErrorHandler) {
219
- async function parseFile(collection, file) {
220
- const { data, body, path: path5 } = file;
221
- let parsedData = await collection.schema.safeParseAsync(data);
222
- if (!parsedData.success) {
223
- errorHandler(new TransformError("Validation", parsedData.error.message));
224
- return null;
225
- }
226
- const document = {
227
- ...parsedData.data,
228
- _meta: {
229
- path: path5
230
- }
231
- };
232
- return {
233
- document,
234
- content: body
235
- };
236
- }
237
- async function parseCollection(collection) {
238
- const promises2 = collection.files.map(
239
- (file) => parseFile(collection, file)
240
- );
241
- return {
242
- ...collection,
243
- documents: (await Promise.all(promises2)).filter(isDefined)
244
- };
245
- }
246
- function createContext(collections2, file) {
247
- return {
248
- content: async () => file.content,
249
- documents: (collection) => {
250
- const resolved = collections2.find((c) => c.name === collection.name);
251
- if (!resolved) {
252
- throw new TransformError(
253
- "Configuration",
254
- `Collection ${collection.name} not found, do you have registered it in your configuration?`
255
- );
256
- }
257
- return resolved.documents.map((doc) => doc.document);
258
- }
259
- };
260
- }
261
- async function transformCollection(collections2, collection) {
262
- if (collection.transform) {
263
- const docs = [];
264
- for (const doc of collection.documents) {
265
- const context = createContext(collections2, doc);
266
- try {
267
- docs.push({
268
- ...doc,
269
- document: await collection.transform(context, doc.document)
270
- });
271
- } catch (error) {
272
- if (error instanceof TransformError) {
273
- errorHandler(error);
274
- } else {
275
- errorHandler(new TransformError("Transform", String(error)));
276
- }
277
- }
278
- }
279
- return docs;
280
- }
281
- return collection.documents;
282
- }
283
- const promises = untransformedCollections.map(
284
- (collection) => parseCollection(collection)
285
- );
286
- const collections = await Promise.all(promises);
287
- for (const collection of collections) {
288
- collection.documents = await transformCollection(collections, collection);
289
- }
290
- return collections;
291
- }
292
-
293
- // src/run.ts
294
- function createArrayConstName(name) {
295
- let suffix = name.charAt(0).toUpperCase() + name.slice(1);
296
- return "all" + (0, import_pluralize.default)(suffix);
297
- }
298
- async function createDataFiles(collections, directory) {
299
- for (const collection of collections) {
300
- const dataPath = import_node_path2.default.join(
301
- directory,
302
- `${createArrayConstName(collection.name)}.json`
303
- );
304
- await import_promises3.default.writeFile(
305
- dataPath,
306
- JSON.stringify(
307
- collection.documents.map((doc) => doc.document),
308
- null,
309
- 2
310
- )
311
- );
312
- }
313
- }
314
- async function createJavaScriptFile(configuration, directory) {
315
- const collections = configuration.collections.map(
316
- ({ name }) => createArrayConstName(name)
317
- );
318
- let content = "";
319
- for (const name of collections) {
320
- content += `import ${name} from "./${name}.json";
321
- `;
322
- }
323
- content += "\n";
324
- content += "export { " + collections.join(", ") + " };\n";
325
- await import_promises3.default.writeFile(import_node_path2.default.join(directory, "index.js"), content, "utf-8");
326
- }
327
- async function createTypeDefinitionFile(configuration, directory) {
328
- const importPath = import_node_path2.default.relative(directory, configuration.path);
329
- let content = `import mdxConfiguration from "${importPath}";
330
- import { GetTypeByName } from "@mdx-collections/core";
331
- `;
332
- const collections = configuration.collections;
333
- for (const collection of collections) {
334
- content += `
335
- `;
336
- content += `export type ${collection.typeName} = GetTypeByName<typeof mdxConfiguration, "${collection.name}">;
337
- `;
338
- content += `export declare const ${createArrayConstName(
339
- collection.name
340
- )}: Array<${collection.typeName}>;
341
- `;
342
- }
343
- content += "\n";
344
- content += "export {};\n";
345
- await import_promises3.default.writeFile(import_node_path2.default.join(directory, "index.d.ts"), content, "utf-8");
346
- }
347
- async function createRunner(configuration, directory) {
348
- await import_promises3.default.mkdir(directory, { recursive: true });
349
- const resolved = await collect(configuration.collections);
350
- await createJavaScriptFile(configuration, directory);
351
- if (configuration.generateTypes) {
352
- await createTypeDefinitionFile(configuration, directory);
353
- }
354
- async function run() {
355
- const collections = await transform(resolved);
356
- await createDataFiles(collections, directory);
357
- for (const collection of collections) {
358
- if (collection.onSuccess) {
359
- await collection.onSuccess(
360
- collection.documents.map((doc) => doc.document)
361
- );
362
- }
363
- }
364
- }
365
- return {
366
- run,
367
- sync: async (event, path5) => {
368
- for (const collection of resolved) {
369
- if (isIncluded(collection, path5)) {
370
- await sync(collection, event, path5);
371
- await run();
372
- }
373
- }
374
- }
375
- };
376
- }
377
-
378
- // src/utils.ts
379
- var import_camelcase = __toESM(require("camelcase"), 1);
380
- var import_pluralize2 = __toESM(require("pluralize"), 1);
381
- function generateTypeName(name) {
382
- const singularName = import_pluralize2.default.singular(name);
383
- return (0, import_camelcase.default)(singularName, { pascalCase: true });
384
- }
385
-
386
- // src/config.ts
387
- function defineCollection(collection) {
388
- let typeName = collection.typeName;
389
- if (!typeName) {
390
- typeName = generateTypeName(collection.name);
391
- }
392
- return {
393
- ...collection,
394
- typeName
395
- };
396
- }
397
- function defineConfig(config) {
398
- return config;
399
- }
400
-
401
- // src/index.ts
402
- async function build2(config) {
403
- const configuration = await applyConfig(config);
404
- const baseDirectory = import_path2.default.dirname(config);
405
- const directory = import_path2.default.join(baseDirectory, ".mdx-collections", "generated");
406
- const runner = await createRunner(configuration, directory);
407
- await runner.run();
408
- }
409
- // Annotate the CommonJS export names for ESM import in node:
410
- 0 && (module.exports = {
411
- applyConfig,
412
- build,
413
- createRunner,
414
- defineCollection,
415
- defineConfig
416
- });
package/dist/index.d.cts DELETED
@@ -1,59 +0,0 @@
1
- import { ZodTypeAny, z } from 'zod';
2
-
3
- type Meta = {
4
- path: string;
5
- };
6
- type Document<TSchema extends ZodTypeAny> = z.infer<TSchema> & {
7
- _meta: Meta;
8
- };
9
- type Context = {
10
- content(): Promise<string>;
11
- documents<TCollection extends AnyCollection>(collection: TCollection): Array<Document<TCollection["schema"]>>;
12
- };
13
- type TransformFn<TSchema extends ZodTypeAny> = ((context: Context, data: Document<TSchema>) => any) | undefined;
14
- type CollectionRequest<TSchema extends ZodTypeAny, TName extends string, TTransform extends TransformFn<TSchema>, TDocument> = {
15
- name: TName;
16
- typeName?: string;
17
- schema: TSchema;
18
- transform?: TTransform;
19
- directory: string;
20
- include: string | string[];
21
- onSuccess?: (documents: Array<TDocument>) => void | Promise<void>;
22
- };
23
- type Collection<TSchema extends ZodTypeAny, TName extends string, TTransform extends TransformFn<TSchema>, TDocument> = CollectionRequest<TSchema, TName, TTransform, TDocument> & {
24
- typeName: string;
25
- };
26
- type AnyCollection = Collection<ZodTypeAny, any, any, any>;
27
- declare function defineCollection<TSchema extends ZodTypeAny, TName extends string, TTransform extends TransformFn<TSchema>, TDocument = [TTransform] extends [(...args: any) => any] ? Awaited<ReturnType<TTransform>> : Document<TSchema>>(collection: CollectionRequest<TSchema, TName, TTransform, TDocument>): Collection<TSchema, TName, TTransform, TDocument>;
28
- type Configuration<TCollections extends Array<AnyCollection>> = {
29
- collections: TCollections;
30
- };
31
- type AnyConfiguration = Configuration<Array<AnyCollection>>;
32
- declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
33
-
34
- type InternalConfiguration = {
35
- collections: Array<AnyCollection>;
36
- path: string;
37
- generateTypes?: boolean;
38
- };
39
- type Options = {
40
- configName: string;
41
- cacheDir?: string;
42
- };
43
- declare function applyConfig(config: string, options?: Options): Promise<InternalConfiguration>;
44
-
45
- type Modification = "added" | "changed" | "removed";
46
- type CollectionByName<TConfiguration extends AnyConfiguration> = {
47
- [TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection;
48
- };
49
- type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<ZodTypeAny, any, any, infer TDocument> ? TDocument : never;
50
- type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection = CollectionByName<TConfiguration>[TName]> = TCollection extends AnyCollection ? GetDocument<TCollection> : never;
51
-
52
- declare function createRunner(configuration: InternalConfiguration, directory: string): Promise<{
53
- run: () => Promise<void>;
54
- sync: (event: Modification, path: string) => Promise<void>;
55
- }>;
56
-
57
- declare function build(config: string): Promise<void>;
58
-
59
- export { AnyCollection, AnyConfiguration, Collection, CollectionRequest, Configuration, Context, Document, GetTypeByName, Meta, Modification, applyConfig, build, createRunner, defineCollection, defineConfig };