@content-collections/core 0.13.1 → 0.14.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.
package/README.md CHANGED
@@ -52,7 +52,7 @@ If your framework is not listed, you can still use Content Collections by using
52
52
  });
53
53
 
54
54
  export default defineConfig({
55
- collections: [posts],
55
+ content: [posts],
56
56
  });
57
57
  ```
58
58
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,45 @@
1
1
  import { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
+ //#region src/parser.d.ts
4
+ type ParseFn = (content: string) => Record<string, unknown> | Promise<Record<string, unknown>>;
5
+ type Parser = {
6
+ hasContent: boolean;
7
+ parse: ParseFn;
8
+ };
9
+ type PredefinedParsers = typeof parsers;
10
+ type PredefinedParser = keyof typeof parsers;
11
+ type ConfiguredParser = PredefinedParser | Parser;
12
+ declare function parseYaml(content: string): any;
13
+ declare function frontmatterParser(fileContent: string): {
14
+ content: string;
15
+ };
16
+ declare function frontmatterOnlyParser(fileContent: string): {
17
+ [key: string]: any;
18
+ };
19
+ declare const parsers: {
20
+ frontmatter: {
21
+ hasContent: true;
22
+ parse: typeof frontmatterParser;
23
+ };
24
+ "frontmatter-only": {
25
+ hasContent: false;
26
+ parse: typeof frontmatterOnlyParser;
27
+ };
28
+ json: {
29
+ hasContent: false;
30
+ parse: (text: string, reviver?: (this: any, key: string, value: any) => any) => any;
31
+ };
32
+ yaml: {
33
+ hasContent: false;
34
+ parse: typeof parseYaml;
35
+ };
36
+ };
37
+ type DefineParserResult<TArgument extends Parser | ParseFn> = TArgument extends Function ? {
38
+ hasContent: false;
39
+ parse: ParseFn;
40
+ } : TArgument extends infer Parser ? Parser : never;
41
+ declare function defineParser<TArgument extends Parser | ParseFn>(parser: TArgument): DefineParserResult<TArgument>;
42
+ //#endregion
3
43
  //#region src/cache.d.ts
4
44
  type Options$2 = {
5
45
  key: string;
@@ -673,46 +713,6 @@ type GetTypeOfImport<T$1> = T$1 extends Import<infer U> ? U : never;
673
713
  declare function createDefaultImport<T$1>(path: string): Import<T$1>;
674
714
  declare function createNamedImport<T$1>(name: string, path: string): Import<T$1>;
675
715
  //#endregion
676
- //#region src/parser.d.ts
677
- type ParseFn = (content: string) => Record<string, unknown> | Promise<Record<string, unknown>>;
678
- type Parser = {
679
- hasContent: boolean;
680
- parse: ParseFn;
681
- };
682
- type PredefinedParsers = typeof parsers;
683
- type PredefinedParser = keyof typeof parsers;
684
- type ConfiguredParser = PredefinedParser | Parser;
685
- declare function parseYaml(content: string): any;
686
- declare function frontmatterParser(fileContent: string): {
687
- content: string;
688
- };
689
- declare function frontmatterOnlyParser(fileContent: string): {
690
- [key: string]: any;
691
- };
692
- declare const parsers: {
693
- frontmatter: {
694
- hasContent: true;
695
- parse: typeof frontmatterParser;
696
- };
697
- "frontmatter-only": {
698
- hasContent: false;
699
- parse: typeof frontmatterOnlyParser;
700
- };
701
- json: {
702
- hasContent: false;
703
- parse: (text: string, reviver?: (this: any, key: string, value: any) => any) => any;
704
- };
705
- yaml: {
706
- hasContent: false;
707
- parse: typeof parseYaml;
708
- };
709
- };
710
- type DefineParserResult<TArgument extends Parser | ParseFn> = TArgument extends Function ? {
711
- hasContent: false;
712
- parse: ParseFn;
713
- } : TArgument extends infer Parser ? Parser : never;
714
- declare function defineParser<TArgument extends Parser | ParseFn>(parser: TArgument): DefineParserResult<TArgument>;
715
- //#endregion
716
716
  //#region src/serializer.d.ts
717
717
  declare const literalSchema: ZodMiniUnion<readonly [ZodMiniString<string>, ZodMiniNumber<number>, ZodMiniBoolean<boolean>, ZodMiniNull, ZodMiniUndefined, ZodMiniDate<Date>, ZodMiniMap<ZodMiniUnknown, ZodMiniUnknown>, ZodMiniSet<ZodMiniUnknown>, ZodMiniBigInt<bigint>, ZodMiniObject<{
718
718
  __cc_import: ZodMiniLiteral<true>;
@@ -753,15 +753,18 @@ type Schema<TParser extends ConfiguredParser, TShape extends TSchemaProp> = GetO
753
753
  _meta: Meta;
754
754
  };
755
755
  type Prettify<T$1> = { [K in keyof T$1]: T$1[K] } & {};
756
- type GetSchema<TCollection$1 extends AnyCollection> = TCollection$1 extends Collection<any, any, any, infer TSchema, any, any> ? Prettify<TSchema> : never;
756
+ type GetSchema<TSource extends AnyContent> = TSource extends Collection<any, any, any, infer TSchema, any, any> ? Prettify<TSchema> : TSource extends Singleton<any, any, any, infer TSchema, any, any> ? Prettify<TSchema> : never;
757
757
  declare const skippedSymbol: unique symbol;
758
758
  type SkippedSignal = {
759
759
  [skippedSymbol]: true;
760
760
  reason?: string;
761
761
  };
762
762
  type Context<TSchema$1 = unknown> = {
763
- documents<TCollection$1 extends AnyCollection>(collection: TCollection$1): Array<GetSchema<TCollection$1>>;
763
+ documents<TSource extends AnyContent>(source: TSource): Array<GetSchema<TSource>>;
764
764
  cache: CacheFn;
765
+ /**
766
+ * @deprecated Use `CollectionContext["collection"]` in collection transforms.
767
+ */
765
768
  collection: {
766
769
  name: string;
767
770
  directory: string;
@@ -769,23 +772,59 @@ type Context<TSchema$1 = unknown> = {
769
772
  };
770
773
  skip: (reason?: string) => SkippedSignal;
771
774
  };
775
+ type CollectionContext<TSchema$1 = unknown> = Context<TSchema$1> & {
776
+ collection: {
777
+ name: string;
778
+ directory: string;
779
+ documents: () => Promise<Array<TSchema$1>>;
780
+ };
781
+ };
782
+ type SingletonContext<TSchema$1 = unknown> = Context<TSchema$1> & {
783
+ singleton: {
784
+ name: string;
785
+ filePath: string;
786
+ directory: string;
787
+ document: () => Promise<TSchema$1 | undefined>;
788
+ };
789
+ };
790
+ type ContentType = "collection" | "singleton";
772
791
  type CollectionRequest<TName extends string, TShape extends TSchemaProp, TParser, TSchema$1, TTransformResult, TDocument$1> = {
773
792
  name: TName;
774
793
  parser?: TParser;
775
794
  typeName?: string;
776
795
  schema: TShape;
777
- transform?: (data: TSchema$1, context: Context<TSchema$1>) => TTransformResult;
796
+ transform?: (data: TSchema$1, context: CollectionContext<TSchema$1>) => TTransformResult;
778
797
  directory: string;
779
798
  include: string | string[];
780
799
  exclude?: string | string[];
781
800
  onSuccess?: (documents: Array<TDocument$1>) => void | Promise<void>;
782
801
  };
783
802
  type Collection<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser, TSchema$1, TTransformResult, TDocument$1> = Omit<CollectionRequest<TName, TShape, TParser, TSchema$1, TTransformResult, TDocument$1>, "schema"> & {
803
+ type: "collection";
784
804
  typeName: string;
785
805
  schema: StandardSchemaV1;
786
806
  parser: TParser;
787
807
  };
788
808
  type AnyCollection = Collection<any, TSchemaProp, ConfiguredParser, any, any, any>;
809
+ type SingletonRequest<TName extends string, TShape extends TSchemaProp, TParser, TSchema$1, TTransformResult, TDocument$1> = {
810
+ name: TName;
811
+ filePath: string;
812
+ parser?: TParser;
813
+ typeName?: string;
814
+ schema: TShape;
815
+ optional?: boolean;
816
+ transform?: (data: TSchema$1, context: SingletonContext<TSchema$1>) => TTransformResult;
817
+ onSuccess?: (document: TDocument$1 | undefined) => void | Promise<void>;
818
+ };
819
+ type Singleton<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser, TSchema$1, TTransformResult, TDocument$1> = Omit<SingletonRequest<TName, TShape, TParser, TSchema$1, TTransformResult, TDocument$1>, "schema"> & {
820
+ type: "singleton";
821
+ typeName: string;
822
+ schema: StandardSchemaV1;
823
+ parser: TParser;
824
+ };
825
+ type AnySingleton = Singleton<any, TSchemaProp, ConfiguredParser, any, any, any>;
826
+ type AnyContent = AnyCollection | AnySingleton;
827
+ declare function isSingleton(source: AnyContent): source is AnySingleton;
789
828
  declare const InvalidReturnTypeSymbol: unique symbol;
790
829
  type InvalidReturnType<TMessage extends string, TObject> = {
791
830
  [InvalidReturnTypeSymbol]: TMessage;
@@ -793,13 +832,37 @@ type InvalidReturnType<TMessage extends string, TObject> = {
793
832
  };
794
833
  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 ? { [K in keyof TTransformResult]: ResolveImports<TTransformResult[K]> } : TTransformResult;
795
834
  declare function defineCollection<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser = "frontmatter", TSchema$1 = Schema<TParser, TShape>, TTransformResult = never, TDocument$1 = ([TTransformResult] extends [never] ? Schema<TParser, TShape> : Exclude<Awaited<TTransformResult>, SkippedSignal>), TResult = (TDocument$1 extends Serializable ? Collection<TName, TShape, TParser, TSchema$1, TTransformResult, ResolveImports<TDocument$1>> : InvalidReturnType<NotSerializableError, TDocument$1>)>(collection: CollectionRequest<TName, TShape, TParser, TSchema$1, TTransformResult, TDocument$1>): TResult;
835
+ declare function defineSingleton<TName extends string, TShape extends TSchemaProp, TParser extends ConfiguredParser = "frontmatter", TSchema$1 = Schema<TParser, TShape>, TTransformResult = never, TDocument$1 = ([TTransformResult] extends [never] ? Schema<TParser, TShape> : Exclude<Awaited<TTransformResult>, SkippedSignal>), TResult = (TDocument$1 extends Serializable ? Singleton<TName, TShape, TParser, TSchema$1, TTransformResult, ResolveImports<TDocument$1>> : InvalidReturnType<NotSerializableError, TDocument$1>)>(singleton: SingletonRequest<TName, TShape, TParser, TSchema$1, TTransformResult, TDocument$1>): TResult;
796
836
  type Cache = "memory" | "file" | "none";
797
- type Configuration<TCollections extends Array<AnyCollection>> = {
837
+ type ConfigurationWithContent<TCollections extends Array<AnyContent>> = {
838
+ content: TCollections;
839
+ /**
840
+ * @deprecated Use `content` instead.
841
+ */
842
+ collections?: TCollections;
843
+ cache?: Cache;
844
+ };
845
+ type ConfigurationWithCollections<TCollections extends Array<AnyContent>> = {
846
+ /**
847
+ * @deprecated Use `content` instead.
848
+ */
798
849
  collections: TCollections;
850
+ content?: TCollections;
799
851
  cache?: Cache;
800
852
  };
801
- type AnyConfiguration = Configuration<Array<AnyCollection>>;
802
- declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
853
+ type Configuration<TCollections extends Array<AnyContent>> = ConfigurationWithContent<TCollections> | ConfigurationWithCollections<TCollections>;
854
+ type AnyConfiguration = Configuration<Array<AnyContent>>;
855
+ declare function defineConfig<TCollections extends Array<AnyContent>>(config: {
856
+ content: TCollections;
857
+ cache?: Cache;
858
+ }): ConfigurationWithContent<TCollections>;
859
+ declare function defineConfig<TCollections extends Array<AnyContent>>(config: {
860
+ /**
861
+ * @deprecated Use `content` instead.
862
+ */
863
+ collections: TCollections;
864
+ cache?: Cache;
865
+ }): ConfigurationWithCollections<TCollections>;
803
866
  //#endregion
804
867
  //#region src/types.d.ts
805
868
  type Modification = "create" | "update" | "delete";
@@ -813,9 +876,14 @@ type CollectionFile = {
813
876
  };
814
877
  path: string;
815
878
  };
816
- type CollectionByName<TConfiguration extends AnyConfiguration> = { [TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection };
817
- type GetDocument<TCollection$1 extends AnyCollection> = TCollection$1 extends Collection<any, any, any, any, any, infer TDocument> ? TDocument : never;
818
- type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection$1 = CollectionByName<TConfiguration>[TName]> = TCollection$1 extends AnyCollection ? GetDocument<TCollection$1> : never;
879
+ type ConfigurationSources<TConfiguration extends AnyConfiguration> = TConfiguration extends {
880
+ content: infer TSources extends Array<AnyContent>;
881
+ } ? TSources : TConfiguration extends {
882
+ collections: infer TSources extends Array<AnyContent>;
883
+ } ? TSources : never;
884
+ type CollectionByName<TConfiguration extends AnyConfiguration> = { [TCollection in ConfigurationSources<TConfiguration>[number] as TCollection["name"]]: TCollection };
885
+ type GetDocument<TSource extends AnyContent> = TSource extends Collection<any, any, any, any, any, infer TDocument> ? TDocument : TSource extends Singleton<any, any, any, any, any, infer TDocument> ? TDocument : never;
886
+ type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection$1 = CollectionByName<TConfiguration>[TName]> = TCollection$1 extends AnyContent ? GetDocument<TCollection$1> : never;
819
887
  //#endregion
820
888
  //#region src/collector.d.ts
821
889
  type CollectorEvents = {
@@ -837,21 +905,24 @@ declare class CollectError extends Error {
837
905
  //#region src/transformer.d.ts
838
906
  type TransformerEvents = {
839
907
  "transformer:validation-error": {
840
- collection: AnyCollection;
908
+ collection: AnyContent;
841
909
  file: CollectionFile;
842
910
  error: TransformError;
843
911
  };
912
+ "transformer:singleton-warning": {
913
+ collection: AnyContent;
914
+ };
844
915
  "transformer:result-error": {
845
- collection: AnyCollection;
916
+ collection: AnyContent;
846
917
  document: any;
847
918
  error: TransformError;
848
919
  };
849
920
  "transformer:error": {
850
- collection: AnyCollection;
921
+ collection: AnyContent;
851
922
  error: TransformError;
852
923
  };
853
924
  "transformer:document-skipped": {
854
- collection: AnyCollection;
925
+ collection: AnyContent;
855
926
  filePath: string;
856
927
  reason?: string;
857
928
  };
@@ -881,7 +952,9 @@ type WatchableCollection = {
881
952
  };
882
953
  type WatcherConfiguration = {
883
954
  inputPaths: Array<string>;
884
- collections: Array<WatchableCollection>;
955
+ collections: Array<WatchableCollection | {
956
+ filePath: string;
957
+ }>;
885
958
  };
886
959
  declare function createWatcher(emitter: Emitter, baseDirectory: string, configuration: WatcherConfiguration, sync: SyncFn): Promise<{
887
960
  unsubscribe: () => Promise<void>;
@@ -920,7 +993,7 @@ declare class ConfigurationError extends Error {
920
993
  constructor(type: ErrorType, message: string);
921
994
  }
922
995
  type InternalConfiguration = {
923
- collections: Array<AnyCollection>;
996
+ collections: Array<AnyContent>;
924
997
  path: string;
925
998
  inputPaths: Array<string>;
926
999
  checksum: string;
@@ -979,7 +1052,7 @@ declare function createBuilder(configurationPath: string, options?: Options$1, e
979
1052
  unsubscribe: () => Promise<void>;
980
1053
  }>;
981
1054
  on: {
982
- <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;
1055
+ <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:singleton-warning" | "transformer:result-error" | "transformer:error" | "transformer:document-skipped" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
983
1056
  <TKey extends "_error" | "_all">(key: TKey, listener: (event: SystemEvents[TKey]) => void): void;
984
1057
  };
985
1058
  }>;
@@ -990,7 +1063,7 @@ declare function createInternalBuilder(initialConfiguration: InternalConfigurati
990
1063
  unsubscribe: () => Promise<void>;
991
1064
  }>;
992
1065
  on: {
993
- <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;
1066
+ <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:singleton-warning" | "transformer:result-error" | "transformer:error" | "transformer:document-skipped" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
994
1067
  <TKey extends "_error" | "_all">(key: TKey, listener: (event: SystemEvents[TKey]) => void): void;
995
1068
  };
996
1069
  }>;
@@ -999,8 +1072,9 @@ type Builder = Awaited<ReturnType<typeof createBuilder>>;
999
1072
  //#region src/features.d.ts
1000
1073
  declare const deprecations: {
1001
1074
  implicitContentProperty: string;
1075
+ collectionsConfigProperty: string;
1002
1076
  };
1003
1077
  type Deprecation = keyof typeof deprecations;
1004
1078
  declare function suppressDeprecatedWarnings(...suppresses: Array<Deprecation | "all">): void;
1005
1079
  //#endregion
1006
- export { AnyCollection, AnyConfiguration, Builder, BuilderEvents, CollectError, Collection, CollectionRequest, Configuration, ConfigurationError, ConfigurationReloadError, Context, type Document, type GetTypeByName, Meta, type Modification, Schema, SkippedSignal, TransformError, type Watcher, createBuilder, createDefaultImport, createInternalBuilder, createNamedImport, defineCollection, defineConfig, defineParser, skippedSymbol, suppressDeprecatedWarnings };
1080
+ export { AnyCollection, AnyConfiguration, AnyContent, AnySingleton, Builder, BuilderEvents, CollectError, Collection, CollectionContext, CollectionRequest, Configuration, ConfigurationError, ConfigurationReloadError, ConfigurationWithCollections, ConfigurationWithContent, ContentType, Context, type Document, type GetTypeByName, Meta, type Modification, Schema, Singleton, SingletonContext, SingletonRequest, SkippedSignal, TransformError, type Watcher, createBuilder, createDefaultImport, createInternalBuilder, createNamedImport, defineCollection, defineConfig, defineParser, defineSingleton, isSingleton, skippedSymbol, suppressDeprecatedWarnings };
package/dist/index.js CHANGED
@@ -4,6 +4,10 @@ import { existsSync } from "node:fs";
4
4
  import fs, { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
5
5
  import { readFile as readFile$1 } from "fs/promises";
6
6
  import { glob } from "tinyglobby";
7
+ import fs2 from "fs";
8
+ import path2 from "path";
9
+ import { build } from "esbuild";
10
+ import { createRequire } from "module";
7
11
  import matter from "gray-matter";
8
12
  import { parse, stringify } from "yaml";
9
13
  import camelcase from "camelcase";
@@ -11,10 +15,6 @@ import pluralize from "pluralize";
11
15
  import picomatch from "picomatch";
12
16
  import os from "node:os";
13
17
  import pLimit from "p-limit";
14
- import fs2 from "fs";
15
- import path2 from "path";
16
- import { build } from "esbuild";
17
- import { createRequire } from "module";
18
18
  import serializeJs from "serialize-javascript";
19
19
  import { EventEmitter } from "node:events";
20
20
  import chokidar from "chokidar";
@@ -94,215 +94,6 @@ async function createCacheManager(baseDirectory, configChecksum) {
94
94
  };
95
95
  }
96
96
 
97
- //#endregion
98
- //#region src/parser.ts
99
- function parseYaml(content) {
100
- return parse(content.trim());
101
- }
102
- function frontmatter(fileContent) {
103
- return matter(fileContent, { engines: { yaml: {
104
- parse: parseYaml,
105
- stringify
106
- } } });
107
- }
108
- function frontmatterParser(fileContent) {
109
- const { data, content } = frontmatter(fileContent);
110
- return {
111
- ...data,
112
- content: content.trim()
113
- };
114
- }
115
- function frontmatterOnlyParser(fileContent) {
116
- const { data } = frontmatter(fileContent);
117
- return data;
118
- }
119
- const parsers = {
120
- frontmatter: {
121
- hasContent: true,
122
- parse: frontmatterParser
123
- },
124
- ["frontmatter-only"]: {
125
- hasContent: false,
126
- parse: frontmatterOnlyParser
127
- },
128
- json: {
129
- hasContent: false,
130
- parse: JSON.parse
131
- },
132
- yaml: {
133
- hasContent: false,
134
- parse: parseYaml
135
- }
136
- };
137
- function getParser(configuredParser) {
138
- if (typeof configuredParser === "string") return parsers[configuredParser];
139
- return configuredParser;
140
- }
141
- function defineParser(parser) {
142
- if (typeof parser === "function") return {
143
- hasContent: false,
144
- parse: parser
145
- };
146
- return parser;
147
- }
148
- function isValidParser(parser) {
149
- if (typeof parser === "string") return parser in parsers;
150
- return "hasContent" in parser && typeof parser.parse === "function";
151
- }
152
-
153
- //#endregion
154
- //#region src/utils.ts
155
- function generateTypeName(name) {
156
- return camelcase(pluralize.singular(name), { pascalCase: true });
157
- }
158
- function isDefined(value) {
159
- return value !== void 0 && value !== null;
160
- }
161
- function orderByPath(a, b) {
162
- return a.path.localeCompare(b.path);
163
- }
164
- function removeChildPaths(paths) {
165
- return Array.from(new Set(paths.filter((path$1) => {
166
- return !paths.some((otherPath) => {
167
- if (path$1 === otherPath) return false;
168
- return path$1.startsWith(otherPath);
169
- });
170
- })));
171
- }
172
- function posixToNativePath(pathName) {
173
- if (path.sep !== path.posix.sep) return pathName.replaceAll(path.posix.sep, path.sep);
174
- return pathName;
175
- }
176
- function toError(error) {
177
- return error instanceof Error ? error : new Error(String(error));
178
- }
179
-
180
- //#endregion
181
- //#region src/collector.ts
182
- var CollectError = class extends Error {
183
- type;
184
- constructor(type, message) {
185
- super(message);
186
- this.type = type;
187
- }
188
- };
189
- function createCollector(emitter, baseDirectory = ".") {
190
- async function read(filePath) {
191
- try {
192
- return await readFile$1(filePath, "utf-8");
193
- } catch (error) {
194
- emitter.emit("collector:read-error", {
195
- filePath,
196
- error: new CollectError("Read", String(error))
197
- });
198
- return null;
199
- }
200
- }
201
- async function collectFile(collection, filePath) {
202
- const file = await read(path.join(baseDirectory, collection.directory, filePath));
203
- if (!file) return null;
204
- try {
205
- return {
206
- data: await getParser(collection.parser).parse(file),
207
- path: filePath
208
- };
209
- } catch (error) {
210
- emitter.emit("collector:parse-error", {
211
- filePath: path.join(collection.directory, filePath),
212
- error: new CollectError("Parse", String(error))
213
- });
214
- return null;
215
- }
216
- }
217
- function createIgnorePattern(collection) {
218
- if (collection.exclude) if (Array.isArray(collection.exclude)) return collection.exclude;
219
- else return [collection.exclude];
220
- }
221
- async function resolveCollection(collection) {
222
- const collectionDirectory = path.join(baseDirectory, collection.directory);
223
- const promises = (await glob(Array.isArray(collection.include) ? collection.include : [collection.include], {
224
- cwd: collectionDirectory,
225
- onlyFiles: true,
226
- absolute: false,
227
- ignore: createIgnorePattern(collection)
228
- })).map((filePath) => collectFile(collection, posixToNativePath(filePath)));
229
- const files = await Promise.all(promises);
230
- return {
231
- ...collection,
232
- files: files.filter(isDefined).sort(orderByPath)
233
- };
234
- }
235
- async function collect(unresolvedCollections) {
236
- const promises = unresolvedCollections.map((collection) => resolveCollection(collection));
237
- return await Promise.all(promises);
238
- }
239
- return {
240
- collect,
241
- collectFile
242
- };
243
- }
244
-
245
- //#endregion
246
- //#region src/synchronizer.ts
247
- function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
248
- function findCollections(filePath) {
249
- const resolvedFilePath = path.resolve(filePath);
250
- return collections.filter((collection) => {
251
- return resolvedFilePath.startsWith(path.resolve(baseDirectory, collection.directory));
252
- });
253
- }
254
- function createRelativePath(collectionPath, filePath) {
255
- const resolvedCollectionPath = path.resolve(baseDirectory, collectionPath);
256
- let relativePath = path.resolve(filePath).slice(resolvedCollectionPath.length);
257
- if (relativePath.startsWith(path.sep)) relativePath = relativePath.slice(path.sep.length);
258
- return relativePath;
259
- }
260
- function resolve$1(filePath) {
261
- return findCollections(filePath).map((collection) => {
262
- return {
263
- collection,
264
- relativePath: createRelativePath(collection.directory, filePath)
265
- };
266
- }).filter(({ collection, relativePath }) => {
267
- return picomatch.isMatch(relativePath, collection.include, {
268
- windows: process.platform === "win32",
269
- ignore: collection.exclude
270
- });
271
- });
272
- }
273
- function deleted(filePath) {
274
- const resolvedCollections = resolve$1(filePath);
275
- if (resolvedCollections.length === 0) return false;
276
- let changed$1 = false;
277
- for (const { collection, relativePath } of resolvedCollections) {
278
- const index = collection.files.findIndex((file) => file.path === relativePath);
279
- if (collection.files.splice(index, 1).length > 0) changed$1 = true;
280
- }
281
- return changed$1;
282
- }
283
- async function changed(filePath) {
284
- const resolvedCollections = resolve$1(filePath);
285
- if (resolvedCollections.length === 0) return false;
286
- let changed$1 = false;
287
- for (const { collection, relativePath } of resolvedCollections) {
288
- const index = collection.files.findIndex((file$1) => file$1.path === relativePath);
289
- const file = await readCollectionFile(collection, relativePath);
290
- if (file) {
291
- changed$1 = true;
292
- if (index === -1) {
293
- collection.files.push(file);
294
- collection.files.sort(orderByPath);
295
- } else collection.files[index] = file;
296
- }
297
- }
298
- return changed$1;
299
- }
300
- return {
301
- deleted,
302
- changed
303
- };
304
- }
305
-
306
97
  //#endregion
307
98
  //#region ../../node_modules/.pnpm/load-tsconfig@0.2.5/node_modules/load-tsconfig/dist/index.js
308
99
  var singleComment = Symbol("singleComment");
@@ -522,6 +313,46 @@ async function compile(configurationPath, outfile) {
522
313
  return Object.keys(result.metafile.inputs);
523
314
  }
524
315
 
316
+ //#endregion
317
+ //#region src/features.ts
318
+ const deprecations = {
319
+ implicitContentProperty: `The implicit addition of a content property to schemas is deprecated.
320
+ Please add an explicit content property to your schema.
321
+ For more information, see:
322
+ https://content-collections.dev/docs/deprecations/implicit-content-property`,
323
+ collectionsConfigProperty: `The configuration property "collections" is deprecated.
324
+ Please use "content" instead.
325
+ For more information, see:
326
+ https://content-collections.dev/docs/deprecations/config-collections-property`
327
+ };
328
+ const _suppressDeprecatedWarnings = [];
329
+ function suppressDeprecatedWarnings(...suppresses) {
330
+ for (const deprecation of suppresses) if (deprecation === "all") {
331
+ _suppressDeprecatedWarnings.push(...Object.keys(deprecations));
332
+ return;
333
+ } else _suppressDeprecatedWarnings.push(deprecation);
334
+ }
335
+ function deprecated(deprecation, logger = console.warn) {
336
+ if (_suppressDeprecatedWarnings.includes(deprecation)) return;
337
+ logger(`[CC DEPRECATED]: ${deprecations[deprecation]}`);
338
+ }
339
+ const retiredFeatures = { legacySchema: `The use of a function as a schema is retired.
340
+ Please use a StandardSchema compliant library directly.
341
+ For more information, see:
342
+ https://content-collections.dev/docs/deprecations/schema-as-function` };
343
+ var RetiredFeatureError = class RetiredFeatureError extends Error {
344
+ feature;
345
+ constructor(feature) {
346
+ super(`This feature has been removed:\n${retiredFeatures[feature]}`);
347
+ this.feature = feature;
348
+ this.name = "RetiredFeatureError";
349
+ Object.setPrototypeOf(this, RetiredFeatureError.prototype);
350
+ }
351
+ };
352
+ function retired(feature) {
353
+ throw new RetiredFeatureError(feature);
354
+ }
355
+
525
356
  //#endregion
526
357
  //#region src/configurationReader.ts
527
358
  var ConfigurationError = class extends Error {
@@ -544,12 +375,14 @@ function createConfigurationReader() {
544
375
  const outfile = path.join(cacheDir, options.configName);
545
376
  try {
546
377
  const configurationPaths = await compile(configurationPath, outfile);
547
- const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
378
+ const { content, collections,...rest } = (await import(`file://${path.resolve(outfile)}?x=${Date.now()}`)).default;
379
+ if (!content && collections) deprecated("collectionsConfigProperty");
548
380
  const hash = createHash("sha256");
549
381
  hash.update(await fs.readFile(outfile, "utf-8"));
550
382
  const checksum = hash.digest("hex");
551
383
  return {
552
- ...module.default,
384
+ ...rest,
385
+ collections: content ?? collections ?? [],
553
386
  path: configurationPath,
554
387
  inputPaths: configurationPaths.map((p) => path.resolve(p)),
555
388
  generateTypes: true,
@@ -562,52 +395,127 @@ function createConfigurationReader() {
562
395
  }
563
396
 
564
397
  //#endregion
565
- //#region src/features.ts
566
- const deprecations = { implicitContentProperty: `The implicit addition of a content property to schemas is deprecated.
567
- Please add an explicit content property to your schema.
568
- For more information, see:
569
- https://content-collections.dev/docs/deprecations/implicit-content-property` };
570
- const _suppressDeprecatedWarnings = [];
571
- function suppressDeprecatedWarnings(...suppresses) {
572
- for (const deprecation of suppresses) if (deprecation === "all") {
573
- _suppressDeprecatedWarnings.push(...Object.keys(deprecations));
574
- return;
575
- } else _suppressDeprecatedWarnings.push(deprecation);
398
+ //#region src/parser.ts
399
+ function parseYaml(content) {
400
+ return parse(content.trim());
576
401
  }
577
- function deprecated(deprecation, logger = console.warn) {
578
- if (_suppressDeprecatedWarnings.includes(deprecation)) return;
579
- logger(`[CC DEPRECATED]: ${deprecations[deprecation]}`);
402
+ function frontmatter(fileContent) {
403
+ return matter(fileContent, { engines: { yaml: {
404
+ parse: parseYaml,
405
+ stringify
406
+ } } });
580
407
  }
581
- const retiredFeatures = { legacySchema: `The use of a function as a schema is retired.
582
- Please use a StandardSchema compliant library directly.
583
- For more information, see:
584
- https://content-collections.dev/docs/deprecations/schema-as-function` };
585
- var RetiredFeatureError = class RetiredFeatureError extends Error {
586
- feature;
587
- constructor(feature) {
588
- super(`This feature has been removed:\n${retiredFeatures[feature]}`);
589
- this.feature = feature;
590
- this.name = "RetiredFeatureError";
591
- Object.setPrototypeOf(this, RetiredFeatureError.prototype);
408
+ function frontmatterParser(fileContent) {
409
+ const { data, content } = frontmatter(fileContent);
410
+ return {
411
+ ...data,
412
+ content: content.trim()
413
+ };
414
+ }
415
+ function frontmatterOnlyParser(fileContent) {
416
+ const { data } = frontmatter(fileContent);
417
+ return data;
418
+ }
419
+ const parsers = {
420
+ frontmatter: {
421
+ hasContent: true,
422
+ parse: frontmatterParser
423
+ },
424
+ ["frontmatter-only"]: {
425
+ hasContent: false,
426
+ parse: frontmatterOnlyParser
427
+ },
428
+ json: {
429
+ hasContent: false,
430
+ parse: JSON.parse
431
+ },
432
+ yaml: {
433
+ hasContent: false,
434
+ parse: parseYaml
592
435
  }
593
436
  };
594
- function retired(feature) {
595
- throw new RetiredFeatureError(feature);
437
+ function getParser(configuredParser) {
438
+ if (typeof configuredParser === "string") return parsers[configuredParser];
439
+ return configuredParser;
440
+ }
441
+ function defineParser(parser) {
442
+ if (typeof parser === "function") return {
443
+ hasContent: false,
444
+ parse: parser
445
+ };
446
+ return parser;
447
+ }
448
+ function isValidParser(parser) {
449
+ if (typeof parser === "string") return parser in parsers;
450
+ return "hasContent" in parser && typeof parser.parse === "function";
451
+ }
452
+
453
+ //#endregion
454
+ //#region src/utils.ts
455
+ function generateTypeName(name) {
456
+ return camelcase(pluralize.singular(name), { pascalCase: true });
457
+ }
458
+ function generateArrayConstName(name) {
459
+ return "all" + pluralize(name.charAt(0).toUpperCase() + name.slice(1));
460
+ }
461
+ function generateSingletonConstName(typeName) {
462
+ return camelcase(typeName, { pascalCase: false });
463
+ }
464
+ function isDefined(value) {
465
+ return value !== void 0 && value !== null;
466
+ }
467
+ function orderByPath(a, b) {
468
+ return a.path.localeCompare(b.path);
469
+ }
470
+ function removeChildPaths(paths) {
471
+ return Array.from(new Set(paths.filter((path$1) => {
472
+ return !paths.some((otherPath) => {
473
+ if (path$1 === otherPath) return false;
474
+ return path$1.startsWith(otherPath);
475
+ });
476
+ })));
477
+ }
478
+ function posixToNativePath(pathName) {
479
+ if (path.sep !== path.posix.sep) return pathName.replaceAll(path.posix.sep, path.sep);
480
+ return pathName;
481
+ }
482
+ function toError(error) {
483
+ return error instanceof Error ? error : new Error(String(error));
596
484
  }
597
485
 
598
486
  //#endregion
599
487
  //#region src/config.ts
600
488
  const skippedSymbol = Symbol("skipped");
489
+ function isSingleton(source) {
490
+ return source.type === "singleton";
491
+ }
601
492
  function defineCollection(collection) {
602
493
  let typeName = collection.typeName;
603
494
  if (!typeName) typeName = generateTypeName(collection.name);
604
495
  let parser = collection.parser;
605
496
  if (!parser) parser = "frontmatter";
606
- else if (!isValidParser(parser)) throw new ConfigurationError("Read", `Parser ${parser} is not valid a parser`);
497
+ else if (!isValidParser(parser)) throw new ConfigurationError("Read", `Parser ${parser} is not a valid parser`);
607
498
  let schema$1 = collection.schema;
608
499
  if (!schema$1["~standard"]) retired("legacySchema");
609
500
  return {
610
501
  ...collection,
502
+ type: "collection",
503
+ typeName,
504
+ parser,
505
+ schema: schema$1
506
+ };
507
+ }
508
+ function defineSingleton(singleton) {
509
+ let typeName = singleton.typeName;
510
+ if (!typeName) typeName = generateTypeName(singleton.name);
511
+ let parser = singleton.parser;
512
+ if (!parser) parser = "frontmatter";
513
+ else if (!isValidParser(parser)) throw new ConfigurationError("Read", `Parser ${parser} is not a valid parser`);
514
+ let schema$1 = singleton.schema;
515
+ if (!schema$1["~standard"]) retired("legacySchema");
516
+ return {
517
+ ...singleton,
518
+ type: "singleton",
611
519
  typeName,
612
520
  parser,
613
521
  schema: schema$1
@@ -617,6 +525,175 @@ function defineConfig(config$1) {
617
525
  return config$1;
618
526
  }
619
527
 
528
+ //#endregion
529
+ //#region src/collector.ts
530
+ var CollectError = class extends Error {
531
+ type;
532
+ constructor(type, message) {
533
+ super(message);
534
+ this.type = type;
535
+ }
536
+ };
537
+ function createCollector(emitter, baseDirectory = ".") {
538
+ async function read(filePath) {
539
+ try {
540
+ return await readFile$1(filePath, "utf-8");
541
+ } catch (error) {
542
+ emitter.emit("collector:read-error", {
543
+ filePath,
544
+ error: new CollectError("Read", String(error))
545
+ });
546
+ return null;
547
+ }
548
+ }
549
+ async function collectCollectionFile(collection, filePath) {
550
+ const file = await read(path.join(baseDirectory, collection.directory, filePath));
551
+ if (!file) return null;
552
+ try {
553
+ return {
554
+ data: await getParser(collection.parser).parse(file),
555
+ path: filePath
556
+ };
557
+ } catch (error) {
558
+ emitter.emit("collector:parse-error", {
559
+ filePath: path.join(collection.directory, filePath),
560
+ error: new CollectError("Parse", String(error))
561
+ });
562
+ return null;
563
+ }
564
+ }
565
+ async function collectSingletonFile(singleton) {
566
+ const file = await read(path.join(baseDirectory, singleton.filePath));
567
+ if (!file) {
568
+ if (singleton.optional) return null;
569
+ throw new CollectError("Read", `Singleton file not found at path: ${singleton.filePath}`);
570
+ }
571
+ try {
572
+ return {
573
+ data: await getParser(singleton.parser).parse(file),
574
+ path: path.basename(singleton.filePath)
575
+ };
576
+ } catch (error) {
577
+ emitter.emit("collector:parse-error", {
578
+ filePath: singleton.filePath,
579
+ error: new CollectError("Parse", String(error))
580
+ });
581
+ return null;
582
+ }
583
+ }
584
+ async function collectFile(source, filePath) {
585
+ if (isSingleton(source)) return collectSingletonFile(source);
586
+ return collectCollectionFile(source, filePath);
587
+ }
588
+ function createIgnorePattern(collection) {
589
+ if (collection.exclude) if (Array.isArray(collection.exclude)) return collection.exclude;
590
+ else return [collection.exclude];
591
+ }
592
+ async function resolveCollection(collection) {
593
+ const collectionDirectory = path.join(baseDirectory, collection.directory);
594
+ const promises = (await glob(Array.isArray(collection.include) ? collection.include : [collection.include], {
595
+ cwd: collectionDirectory,
596
+ onlyFiles: true,
597
+ absolute: false,
598
+ ignore: createIgnorePattern(collection)
599
+ })).map((filePath) => collectCollectionFile(collection, posixToNativePath(filePath)));
600
+ const files = await Promise.all(promises);
601
+ return {
602
+ ...collection,
603
+ files: files.filter(isDefined).sort(orderByPath)
604
+ };
605
+ }
606
+ async function resolveSingleton(singleton) {
607
+ const file = await collectSingletonFile(singleton);
608
+ return {
609
+ ...singleton,
610
+ files: file ? [file] : []
611
+ };
612
+ }
613
+ async function collect(unresolvedCollections) {
614
+ const promises = unresolvedCollections.map((collection) => {
615
+ if (isSingleton(collection)) return resolveSingleton(collection);
616
+ return resolveCollection(collection);
617
+ });
618
+ return await Promise.all(promises);
619
+ }
620
+ return {
621
+ collect,
622
+ collectFile
623
+ };
624
+ }
625
+
626
+ //#endregion
627
+ //#region src/synchronizer.ts
628
+ function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
629
+ function findCollections(filePath) {
630
+ const resolvedFilePath = path.resolve(filePath);
631
+ return collections.filter((collection) => {
632
+ if (isSingleton(collection)) return resolvedFilePath === path.resolve(baseDirectory, collection.filePath);
633
+ return resolvedFilePath.startsWith(path.resolve(baseDirectory, collection.directory));
634
+ });
635
+ }
636
+ function createRelativePath(collectionPath, filePath) {
637
+ const resolvedCollectionPath = path.resolve(baseDirectory, collectionPath);
638
+ let relativePath = path.resolve(filePath).slice(resolvedCollectionPath.length);
639
+ if (relativePath.startsWith(path.sep)) relativePath = relativePath.slice(path.sep.length);
640
+ return relativePath;
641
+ }
642
+ function resolve$1(filePath) {
643
+ return findCollections(filePath).map((collection) => {
644
+ return {
645
+ collection,
646
+ relativePath: isSingleton(collection) ? path.basename(collection.filePath) : createRelativePath(collection.directory, filePath)
647
+ };
648
+ }).filter(({ collection, relativePath }) => {
649
+ if (isSingleton(collection)) return true;
650
+ return picomatch.isMatch(relativePath, collection.include, {
651
+ windows: process.platform === "win32",
652
+ ignore: collection.exclude
653
+ });
654
+ });
655
+ }
656
+ function deleted(filePath) {
657
+ const resolvedCollections = resolve$1(filePath);
658
+ if (resolvedCollections.length === 0) return false;
659
+ let changed$1 = false;
660
+ for (const { collection, relativePath } of resolvedCollections) {
661
+ if (isSingleton(collection)) {
662
+ if (collection.files.length > 0) {
663
+ collection.files = [];
664
+ changed$1 = true;
665
+ }
666
+ continue;
667
+ }
668
+ const index = collection.files.findIndex((file) => file.path === relativePath);
669
+ if (collection.files.splice(index, 1).length > 0) changed$1 = true;
670
+ }
671
+ return changed$1;
672
+ }
673
+ async function changed(filePath) {
674
+ const resolvedCollections = resolve$1(filePath);
675
+ if (resolvedCollections.length === 0) return false;
676
+ let changed$1 = false;
677
+ for (const { collection, relativePath } of resolvedCollections) {
678
+ const index = collection.files.findIndex((file$1) => file$1.path === relativePath);
679
+ const file = await readCollectionFile(collection, relativePath);
680
+ if (file) {
681
+ changed$1 = true;
682
+ if (isSingleton(collection)) collection.files = [file];
683
+ else if (index === -1) {
684
+ collection.files.push(file);
685
+ collection.files.sort(orderByPath);
686
+ } else collection.files[index] = file;
687
+ }
688
+ }
689
+ return changed$1;
690
+ }
691
+ return {
692
+ deleted,
693
+ changed
694
+ };
695
+ }
696
+
620
697
  //#endregion
621
698
  //#region ../../node_modules/.pnpm/zod@4.1.11/node_modules/zod/v4/core/core.js
622
699
  /** A special constant with type `never` */
@@ -1857,16 +1934,21 @@ function serialize(value) {
1857
1934
  let serializedValue = "";
1858
1935
  let counter = 0;
1859
1936
  function handleImports(item) {
1860
- if (item instanceof Object) Object.entries(item).forEach(([key, value$1]) => {
1937
+ if (!item || typeof item !== "object") return;
1938
+ if (Array.isArray(item)) {
1939
+ item.forEach(handleImports);
1940
+ return;
1941
+ }
1942
+ Object.entries(item).forEach(([key, value$1]) => {
1861
1943
  if (isImport(value$1)) {
1862
1944
  counter++;
1863
1945
  const variableName = `__v_${counter}`;
1864
1946
  serializedValue += createImport(value$1, variableName);
1865
1947
  item[key] = variableName;
1866
- } else if (value$1 instanceof Object) handleImports(value$1);
1948
+ } else handleImports(value$1);
1867
1949
  });
1868
1950
  }
1869
- value.forEach(handleImports);
1951
+ handleImports(value);
1870
1952
  serializedValue += "\n";
1871
1953
  const js = serializeJs(value, {
1872
1954
  space: 2,
@@ -1959,35 +2041,46 @@ function createTransformer(emitter, cacheManager) {
1959
2041
  };
1960
2042
  }
1961
2043
  function createContext(collections, collection, cache) {
1962
- return {
1963
- documents: (collection$1) => {
1964
- const resolved = collections.find((c) => c.name === collection$1.name);
1965
- if (!resolved) throw new TransformError("Configuration", `Collection ${collection$1.name} not found, do you have registered it in your configuration?`);
2044
+ const sourceDirectory = isSingleton(collection) ? dirname(collection.filePath) : collection.directory;
2045
+ const base = {
2046
+ documents: (source) => {
2047
+ const resolved = collections.find((c) => c.name === source.name);
2048
+ if (!resolved) throw new TransformError("Configuration", `Collection ${source.name} not found, do you have registered it in your configuration?`);
1966
2049
  return resolved.documents.map((doc) => doc.document);
1967
2050
  },
2051
+ cache: cache.cacheFn,
1968
2052
  collection: {
1969
2053
  name: collection.name,
1970
- directory: collection.directory,
1971
- documents: async () => {
1972
- return collection.documents.map((doc) => doc.document);
1973
- }
2054
+ directory: sourceDirectory,
2055
+ documents: async () => collection.documents.map((doc) => doc.document)
1974
2056
  },
1975
- cache: cache.cacheFn,
1976
2057
  skip: (reason) => ({
1977
2058
  [skippedSymbol]: true,
1978
2059
  reason
1979
2060
  })
1980
2061
  };
2062
+ if (isSingleton(collection)) return {
2063
+ ...base,
2064
+ singleton: {
2065
+ name: collection.name,
2066
+ filePath: collection.filePath,
2067
+ directory: sourceDirectory,
2068
+ document: async () => {
2069
+ return collection.documents[0]?.document;
2070
+ }
2071
+ }
2072
+ };
2073
+ return base;
1981
2074
  }
1982
2075
  async function transformDocument(collections, collection, transform, doc) {
1983
2076
  const cache = cacheManager.cache(collection.name, doc.document._meta.path);
1984
2077
  const context = createContext(collections, collection, cache);
1985
2078
  try {
1986
- const document = await transform(doc.document, context);
2079
+ const document = isSingleton(collection) ? await transform(doc.document, context) : await transform(doc.document, context);
1987
2080
  await cache.tidyUp();
1988
2081
  if (isSkippedSignal(document)) emitter.emit("transformer:document-skipped", {
1989
2082
  collection,
1990
- filePath: join(collection.directory, doc.document._meta.filePath),
2083
+ filePath: join(isSingleton(collection) ? dirname(collection.filePath) : collection.directory, doc.document._meta.filePath),
1991
2084
  reason: document.reason
1992
2085
  });
1993
2086
  else return {
@@ -2009,7 +2102,8 @@ function createTransformer(emitter, cacheManager) {
2009
2102
  const transform = collection.transform;
2010
2103
  if (transform) {
2011
2104
  const limit = pLimit(os.cpus().length);
2012
- const docs = collection.documents.map((doc) => limit(() => transformDocument(collections, collection, transform, doc)));
2105
+ const typedTransform = transform;
2106
+ const docs = collection.documents.map((doc) => limit(() => transformDocument(collections, collection, typedTransform, doc)));
2013
2107
  const transformed = await Promise.all(docs);
2014
2108
  await cacheManager.flush();
2015
2109
  return transformed.filter(isDefined);
@@ -2032,29 +2126,33 @@ function createTransformer(emitter, cacheManager) {
2032
2126
  return async (untransformedCollections) => {
2033
2127
  const promises = untransformedCollections.map((collection) => parseCollection(collection));
2034
2128
  const collections = await Promise.all(promises);
2035
- for (const collection of collections) collection.documents = await validateDocuments(collection, await transformCollection(collections, collection));
2129
+ for (const collection of collections) {
2130
+ collection.documents = await validateDocuments(collection, await transformCollection(collections, collection));
2131
+ if (isSingleton(collection)) {
2132
+ if (collection.documents.length === 0) emitter.emit("transformer:singleton-warning", { collection });
2133
+ }
2134
+ }
2036
2135
  return collections;
2037
2136
  };
2038
2137
  }
2039
2138
 
2040
2139
  //#endregion
2041
2140
  //#region src/writer.ts
2042
- function createArrayConstName(name) {
2043
- return "all" + pluralize(name.charAt(0).toUpperCase() + name.slice(1));
2044
- }
2045
2141
  async function createDataFile(directory, collection) {
2046
- const dataPath = path.join(directory, `${createArrayConstName(collection.name)}.${extension}`);
2047
- await fs.writeFile(dataPath, serialize(collection.documents.map((doc) => doc.document)));
2142
+ const constName = isSingleton(collection) ? generateSingletonConstName(collection.typeName) : generateArrayConstName(collection.name);
2143
+ const dataPath = path.join(directory, `${constName}.${extension}`);
2144
+ const value = isSingleton(collection) ? collection.documents[0]?.document : collection.documents.map((doc) => doc.document);
2145
+ await fs.writeFile(dataPath, serialize(value));
2048
2146
  }
2049
2147
  function createDataFiles(directory, collections) {
2050
2148
  return Promise.all(collections.map((collection) => createDataFile(directory, collection)));
2051
2149
  }
2052
2150
  async function createJavaScriptFile(directory, configuration) {
2053
- const collections = configuration.collections.map(({ name }) => createArrayConstName(name));
2151
+ const exports = configuration.collections.map((collection) => isSingleton(collection) ? generateSingletonConstName(collection.typeName) : generateArrayConstName(collection.name));
2054
2152
  let content = `// generated by content-collections at ${/* @__PURE__ */ new Date()}\n\n`;
2055
- for (const name of collections) content += `import ${name} from "./${name}.${extension}";\n`;
2153
+ for (const name of exports) content += `import ${name} from "./${name}.${extension}";\n`;
2056
2154
  content += "\n";
2057
- content += "export { " + collections.join(", ") + " };\n";
2155
+ content += "export { " + exports.join(", ") + " };\n";
2058
2156
  await fs.writeFile(path.join(directory, "index.js"), content, "utf-8");
2059
2157
  }
2060
2158
  function createImportPath(directory, target) {
@@ -2071,7 +2169,8 @@ import { GetTypeByName } from "@content-collections/core";
2071
2169
  for (const collection of collections) {
2072
2170
  content += `\n`;
2073
2171
  content += `export type ${collection.typeName} = GetTypeByName<typeof configuration, "${collection.name}">;\n`;
2074
- content += `export declare const ${createArrayConstName(collection.name)}: Array<${collection.typeName}>;\n`;
2172
+ if (isSingleton(collection)) content += `export declare const ${generateSingletonConstName(collection.typeName)}: ${collection.typeName}${collection.optional ? " | undefined" : ""};\n`;
2173
+ else content += `export declare const ${generateArrayConstName(collection.name)}: Array<${collection.typeName}>;\n`;
2075
2174
  }
2076
2175
  content += "\n";
2077
2176
  content += "export {};\n";
@@ -2114,7 +2213,7 @@ async function build$1({ emitter, transform, resolved, writer, configuration })
2114
2213
  writer.createTypeDefinitionFile(configuration),
2115
2214
  writer.createJavaScriptFile(configuration)
2116
2215
  ]);
2117
- const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map((collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document)));
2216
+ const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map((collection) => isSingleton(collection) ? collection.onSuccess?.(collection.documents[0]?.document) : collection.onSuccess?.(collection.documents.map((doc) => doc.document)));
2118
2217
  await Promise.all(pendingOnSuccess.filter(isDefined));
2119
2218
  const stats = collections.reduce((acc, collection) => {
2120
2219
  acc.collections++;
@@ -2161,7 +2260,10 @@ function createEmitter() {
2161
2260
  //#endregion
2162
2261
  //#region src/watcher.ts
2163
2262
  async function createWatcher(emitter, baseDirectory, configuration, sync) {
2164
- const paths = removeChildPaths([...configuration.collections.map((collection) => path.join(baseDirectory, collection.directory)).map((p) => resolve(p)), ...configuration.inputPaths.map((p) => dirname(p))]);
2263
+ const paths = removeChildPaths([...configuration.collections.map((collection) => {
2264
+ const directory = isSingleton(collection) ? path.dirname(collection.filePath) : collection.directory;
2265
+ return path.join(baseDirectory, directory);
2266
+ }).map((p) => resolve(p)), ...configuration.inputPaths.map((p) => dirname(p))]);
2165
2267
  const watcher = chokidar.watch(paths, {
2166
2268
  ignored: [/(^|[\/\\])\../, /(^|[\/\\])node_modules([\/\\]|$)/],
2167
2269
  persistent: true,
@@ -2292,4 +2394,4 @@ async function createInternalBuilder(initialConfiguration, baseDirectory, option
2292
2394
  }
2293
2395
 
2294
2396
  //#endregion
2295
- export { CollectError, ConfigurationError, ConfigurationReloadError, TransformError, createBuilder, createDefaultImport, createInternalBuilder, createNamedImport, defineCollection, defineConfig, defineParser, skippedSymbol, suppressDeprecatedWarnings };
2397
+ export { CollectError, ConfigurationError, ConfigurationReloadError, TransformError, createBuilder, createDefaultImport, createInternalBuilder, createNamedImport, defineCollection, defineConfig, defineParser, defineSingleton, isSingleton, skippedSymbol, suppressDeprecatedWarnings };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@content-collections/core",
3
3
  "type": "module",
4
- "version": "0.13.1",
4
+ "version": "0.14.0",
5
5
  "description": "Core of Content Collections",
6
6
  "author": "Sebastian Sdorra <s.sdorra@gmail.com>",
7
7
  "license": "MIT",