@content-collections/core 0.1.2 → 0.3.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/dist/apidoc.d.ts +186 -0
- package/dist/index.d.ts +39 -9
- package/dist/index.js +177 -42
- package/package.json +2 -1
package/dist/apidoc.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ZodObject } from 'zod';
|
|
3
|
+
import { ZodRawShape } from 'zod';
|
|
4
|
+
import { ZodString } from 'zod';
|
|
5
|
+
import { ZodTypeAny } from 'zod';
|
|
6
|
+
|
|
7
|
+
declare type AddContent<TShape extends ZodRawShape> = TShape extends {
|
|
8
|
+
content: ZodTypeAny;
|
|
9
|
+
} ? TShape : TShape & WithContent;
|
|
10
|
+
|
|
11
|
+
export declare type AnyCollection = Collection<any, ZodRawShape, any, any, any>;
|
|
12
|
+
|
|
13
|
+
export declare type AnyConfiguration = Configuration<Array<AnyCollection>>;
|
|
14
|
+
|
|
15
|
+
export declare type Builder = Awaited<ReturnType<typeof createBuilder>>;
|
|
16
|
+
|
|
17
|
+
export declare type BuilderEvents = {
|
|
18
|
+
"builder:start": {
|
|
19
|
+
startedAt: number;
|
|
20
|
+
};
|
|
21
|
+
"builder:end": {
|
|
22
|
+
startedAt: number;
|
|
23
|
+
endedAt: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export declare class CollectError extends Error {
|
|
28
|
+
type: ErrorType$2;
|
|
29
|
+
constructor(type: ErrorType$2, message: string);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export declare type Collection<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>, "schema"> & {
|
|
33
|
+
typeName: string;
|
|
34
|
+
schema: TShape;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
declare type CollectionByName<TConfiguration extends AnyConfiguration> = {
|
|
38
|
+
[TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
declare type CollectionFile = {
|
|
42
|
+
data: {
|
|
43
|
+
content: string;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
path: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export declare type CollectionRequest<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = {
|
|
50
|
+
/**
|
|
51
|
+
* The name of the collection
|
|
52
|
+
*/
|
|
53
|
+
name: TName;
|
|
54
|
+
/**
|
|
55
|
+
* The name of the generated TypeScript type.
|
|
56
|
+
* If the typeName is undefined the pluralized name of the collection will be used.
|
|
57
|
+
*/
|
|
58
|
+
typeName?: string;
|
|
59
|
+
schema: (z: Z) => TShape;
|
|
60
|
+
transform?: (context: Context, data: TSchema) => TTransformResult;
|
|
61
|
+
directory: string | string[];
|
|
62
|
+
include: string | string[];
|
|
63
|
+
onSuccess?: (documents: Array<TDocument>) => void | Promise<void>;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
declare type CollectorEvents = {
|
|
67
|
+
"collector:read-error": {
|
|
68
|
+
filePath: string;
|
|
69
|
+
error: CollectError;
|
|
70
|
+
};
|
|
71
|
+
"collector:parse-error": {
|
|
72
|
+
filePath: string;
|
|
73
|
+
error: CollectError;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export declare type Configuration<TCollections extends Array<AnyCollection>> = {
|
|
78
|
+
collections: TCollections;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export declare class ConfigurationError extends Error {
|
|
82
|
+
type: ErrorType;
|
|
83
|
+
constructor(type: ErrorType, message: string);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export declare type Context = {
|
|
87
|
+
documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["schema"]>>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export declare function createBuilder(configurationPath: string, options?: Options): Promise<{
|
|
91
|
+
sync: (modification: Modification, filePath: string) => Promise<boolean>;
|
|
92
|
+
build: () => Promise<void>;
|
|
93
|
+
watch: () => Promise<{
|
|
94
|
+
unsubscribe: () => Promise<void>;
|
|
95
|
+
}>;
|
|
96
|
+
on: {
|
|
97
|
+
<TKey extends "builder:start" | "builder:end" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:error" | "watcher:file-changed">(key: TKey, listener: (event: Events[TKey]) => void): void;
|
|
98
|
+
<TKey_1 extends "_error" | "_all">(key: TKey_1, listener: (event: SystemEvents[TKey_1]) => void): void;
|
|
99
|
+
};
|
|
100
|
+
}>;
|
|
101
|
+
|
|
102
|
+
export 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>;
|
|
103
|
+
|
|
104
|
+
export declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
|
|
105
|
+
|
|
106
|
+
declare type ErrorEvent = EventWithError & SystemEvent;
|
|
107
|
+
|
|
108
|
+
declare type ErrorType$1 = "Validation" | "Configuration" | "Transform";
|
|
109
|
+
|
|
110
|
+
declare type ErrorType$2 = "Parse" | "Read";
|
|
111
|
+
|
|
112
|
+
declare type ErrorType = "Read" | "Compile";
|
|
113
|
+
|
|
114
|
+
declare type Events = BuilderEvents & CollectorEvents & TransformerEvents & WatcherEvents;
|
|
115
|
+
|
|
116
|
+
declare type EventWithError = {
|
|
117
|
+
error: Error;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
declare type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, infer TDocument> ? TDocument : never;
|
|
121
|
+
|
|
122
|
+
export declare type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection = CollectionByName<TConfiguration>[TName]> = TCollection extends AnyCollection ? GetDocument<TCollection> : never;
|
|
123
|
+
|
|
124
|
+
export declare type Meta = {
|
|
125
|
+
filePath: string;
|
|
126
|
+
fileName: string;
|
|
127
|
+
directory: string;
|
|
128
|
+
path: string;
|
|
129
|
+
extension: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export declare type Modification = "create" | "update" | "delete";
|
|
133
|
+
|
|
134
|
+
declare type Options$1 = {
|
|
135
|
+
configName: string;
|
|
136
|
+
cacheDir?: string;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
declare type Options = Options$1 & {
|
|
140
|
+
outputDir?: string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export declare type Schema<TShape extends ZodRawShape> = z.infer<ZodObject<AddContent<TShape>>> & {
|
|
144
|
+
_meta: Meta;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
declare type SystemEvent = {
|
|
148
|
+
_event: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
declare type SystemEvents = {
|
|
152
|
+
_error: ErrorEvent;
|
|
153
|
+
_all: SystemEvent;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
declare type TransformerEvents = {
|
|
157
|
+
"transformer:validation-error": {
|
|
158
|
+
collection: AnyCollection;
|
|
159
|
+
file: CollectionFile;
|
|
160
|
+
error: TransformError;
|
|
161
|
+
};
|
|
162
|
+
"transformer:error": {
|
|
163
|
+
collection: AnyCollection;
|
|
164
|
+
error: TransformError;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export declare class TransformError extends Error {
|
|
169
|
+
type: ErrorType$1;
|
|
170
|
+
constructor(type: ErrorType$1, message: string);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
declare type WatcherEvents = {
|
|
174
|
+
"watcher:file-changed": {
|
|
175
|
+
filePath: string;
|
|
176
|
+
modification: Modification;
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
declare type WithContent = {
|
|
181
|
+
content: ZodString;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
declare type Z = typeof z;
|
|
185
|
+
|
|
186
|
+
export { }
|
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
|
|
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?: (
|
|
54
|
+
transform?: (data: TSchema, context: Context) => TTransformResult;
|
|
28
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
|
|
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
|
@@ -11,6 +11,9 @@ function generateTypeName(name) {
|
|
|
11
11
|
function isDefined(value) {
|
|
12
12
|
return value !== void 0 && value !== null;
|
|
13
13
|
}
|
|
14
|
+
function orderByPath(a, b) {
|
|
15
|
+
return a.path.localeCompare(b.path);
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
// src/config.ts
|
|
16
19
|
function defineCollection(collection) {
|
|
@@ -18,9 +21,14 @@ function defineCollection(collection) {
|
|
|
18
21
|
if (!typeName) {
|
|
19
22
|
typeName = generateTypeName(collection.name);
|
|
20
23
|
}
|
|
24
|
+
let parser = collection.parser;
|
|
25
|
+
if (!parser) {
|
|
26
|
+
parser = "frontmatter";
|
|
27
|
+
}
|
|
21
28
|
return {
|
|
22
29
|
...collection,
|
|
23
30
|
typeName,
|
|
31
|
+
parser,
|
|
24
32
|
schema: collection.schema(z)
|
|
25
33
|
};
|
|
26
34
|
}
|
|
@@ -36,7 +44,7 @@ import path from "path";
|
|
|
36
44
|
// package.json
|
|
37
45
|
var package_default = {
|
|
38
46
|
name: "@content-collections/core",
|
|
39
|
-
version: "0.
|
|
47
|
+
version: "0.3.0",
|
|
40
48
|
type: "module",
|
|
41
49
|
main: "dist/index.cjs",
|
|
42
50
|
types: "./dist/index.d.ts",
|
|
@@ -76,12 +84,14 @@ var package_default = {
|
|
|
76
84
|
"gray-matter": "^4.0.3",
|
|
77
85
|
micromatch: "^4.0.5",
|
|
78
86
|
pluralize: "^8.0.0",
|
|
87
|
+
yaml: "^2.3.4",
|
|
79
88
|
zod: "^3.22.4"
|
|
80
89
|
}
|
|
81
90
|
};
|
|
82
91
|
|
|
83
92
|
// src/configurationReader.ts
|
|
84
93
|
import { existsSync } from "fs";
|
|
94
|
+
import { createHash } from "crypto";
|
|
85
95
|
var ConfigurationError = class extends Error {
|
|
86
96
|
type;
|
|
87
97
|
constructor(type, message) {
|
|
@@ -111,7 +121,10 @@ async function compile(configurationPath, outfile) {
|
|
|
111
121
|
}
|
|
112
122
|
await esbuild.build({
|
|
113
123
|
entryPoints: [configurationPath],
|
|
114
|
-
external: [
|
|
124
|
+
external: [
|
|
125
|
+
...Object.keys(package_default.dependencies),
|
|
126
|
+
"@content-collections/*"
|
|
127
|
+
],
|
|
115
128
|
bundle: true,
|
|
116
129
|
platform: "node",
|
|
117
130
|
format: "esm",
|
|
@@ -141,19 +154,56 @@ function createConfigurationReader() {
|
|
|
141
154
|
);
|
|
142
155
|
}
|
|
143
156
|
const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
|
|
157
|
+
const hash = createHash("sha256");
|
|
158
|
+
hash.update(await fs.readFile(outfile, "utf-8"));
|
|
159
|
+
const checksum = hash.digest("hex");
|
|
144
160
|
return {
|
|
145
161
|
...module.default,
|
|
146
162
|
path: configurationPath,
|
|
147
|
-
generateTypes: true
|
|
163
|
+
generateTypes: true,
|
|
164
|
+
checksum
|
|
148
165
|
};
|
|
149
166
|
};
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
// src/collector.ts
|
|
153
|
-
import matter from "gray-matter";
|
|
154
170
|
import fg from "fast-glob";
|
|
155
171
|
import { readFile } from "fs/promises";
|
|
156
172
|
import path2 from "path";
|
|
173
|
+
|
|
174
|
+
// src/parser.ts
|
|
175
|
+
import matter from "gray-matter";
|
|
176
|
+
import { parse, stringify } from "yaml";
|
|
177
|
+
function frontmatterParser(fileContent) {
|
|
178
|
+
const { data, content } = matter(fileContent, {
|
|
179
|
+
engines: {
|
|
180
|
+
yaml: {
|
|
181
|
+
parse,
|
|
182
|
+
stringify
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
...data,
|
|
188
|
+
content: content.trim()
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
var parsers = {
|
|
192
|
+
frontmatter: {
|
|
193
|
+
hasContent: true,
|
|
194
|
+
parse: frontmatterParser
|
|
195
|
+
},
|
|
196
|
+
json: {
|
|
197
|
+
hasContent: false,
|
|
198
|
+
parse: JSON.parse
|
|
199
|
+
},
|
|
200
|
+
yaml: {
|
|
201
|
+
hasContent: false,
|
|
202
|
+
parse
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/collector.ts
|
|
157
207
|
var CollectError = class extends Error {
|
|
158
208
|
type;
|
|
159
209
|
constructor(type, message) {
|
|
@@ -173,27 +223,21 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
173
223
|
return null;
|
|
174
224
|
}
|
|
175
225
|
}
|
|
176
|
-
function
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
...data,
|
|
180
|
-
content
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
async function collectFile(directory, filePath) {
|
|
184
|
-
const file = await read(path2.join(directory, filePath));
|
|
226
|
+
async function collectFile(collection, filePath) {
|
|
227
|
+
const absolutePath = path2.join(baseDirectory, collection.directory, filePath);
|
|
228
|
+
const file = await read(absolutePath);
|
|
185
229
|
if (!file) {
|
|
186
230
|
return null;
|
|
187
231
|
}
|
|
188
232
|
try {
|
|
189
|
-
const data = parse(file);
|
|
233
|
+
const data = parsers[collection.parser].parse(file);
|
|
190
234
|
return {
|
|
191
235
|
data,
|
|
192
236
|
path: filePath
|
|
193
237
|
};
|
|
194
238
|
} catch (error) {
|
|
195
239
|
emitter.emit("collector:parse-error", {
|
|
196
|
-
filePath: path2.join(directory, filePath),
|
|
240
|
+
filePath: path2.join(collection.directory, filePath),
|
|
197
241
|
error: new CollectError("Parse", String(error))
|
|
198
242
|
});
|
|
199
243
|
return null;
|
|
@@ -207,12 +251,12 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
207
251
|
absolute: false
|
|
208
252
|
});
|
|
209
253
|
const promises = filePaths.map(
|
|
210
|
-
(filePath) => collectFile(
|
|
254
|
+
(filePath) => collectFile(collection, filePath)
|
|
211
255
|
);
|
|
212
256
|
const files = await Promise.all(promises);
|
|
213
257
|
return {
|
|
214
258
|
...collection,
|
|
215
|
-
files: files.filter(isDefined)
|
|
259
|
+
files: files.filter(isDefined).sort(orderByPath)
|
|
216
260
|
};
|
|
217
261
|
}
|
|
218
262
|
async function collect(unresolvedCollections) {
|
|
@@ -311,23 +355,27 @@ var TransformError = class extends Error {
|
|
|
311
355
|
this.type = type;
|
|
312
356
|
}
|
|
313
357
|
};
|
|
314
|
-
function createPath(
|
|
315
|
-
let p =
|
|
358
|
+
function createPath(path7, ext) {
|
|
359
|
+
let p = path7.slice(0, -ext.length);
|
|
316
360
|
if (p.endsWith("/index")) {
|
|
317
361
|
p = p.slice(0, -6);
|
|
318
362
|
}
|
|
319
363
|
return p;
|
|
320
364
|
}
|
|
321
|
-
function createTransformer(emitter) {
|
|
322
|
-
function createSchema(schema) {
|
|
365
|
+
function createTransformer(emitter, cacheManager) {
|
|
366
|
+
function createSchema(parserName, schema) {
|
|
367
|
+
const parser = parsers[parserName];
|
|
368
|
+
if (!parser.hasContent) {
|
|
369
|
+
return z2.object(schema);
|
|
370
|
+
}
|
|
323
371
|
return z2.object({
|
|
324
372
|
content: z2.string(),
|
|
325
373
|
...schema
|
|
326
374
|
});
|
|
327
375
|
}
|
|
328
376
|
async function parseFile(collection, file) {
|
|
329
|
-
const { data, path:
|
|
330
|
-
const schema = createSchema(collection.schema);
|
|
377
|
+
const { data, path: path7 } = file;
|
|
378
|
+
const schema = createSchema(collection.parser, collection.schema);
|
|
331
379
|
let parsedData = await schema.safeParseAsync(data);
|
|
332
380
|
if (!parsedData.success) {
|
|
333
381
|
emitter.emit("transformer:validation-error", {
|
|
@@ -337,7 +385,7 @@ function createTransformer(emitter) {
|
|
|
337
385
|
});
|
|
338
386
|
return null;
|
|
339
387
|
}
|
|
340
|
-
const ext = extname(
|
|
388
|
+
const ext = extname(path7);
|
|
341
389
|
let extension = ext;
|
|
342
390
|
if (extension.startsWith(".")) {
|
|
343
391
|
extension = extension.slice(1);
|
|
@@ -345,11 +393,11 @@ function createTransformer(emitter) {
|
|
|
345
393
|
const document = {
|
|
346
394
|
...parsedData.data,
|
|
347
395
|
_meta: {
|
|
348
|
-
filePath:
|
|
349
|
-
fileName: basename(
|
|
350
|
-
directory: dirname(
|
|
396
|
+
filePath: path7,
|
|
397
|
+
fileName: basename(path7),
|
|
398
|
+
directory: dirname(path7),
|
|
351
399
|
extension,
|
|
352
|
-
path: createPath(
|
|
400
|
+
path: createPath(path7, ext)
|
|
353
401
|
}
|
|
354
402
|
};
|
|
355
403
|
return {
|
|
@@ -365,7 +413,7 @@ function createTransformer(emitter) {
|
|
|
365
413
|
documents: (await Promise.all(promises)).filter(isDefined)
|
|
366
414
|
};
|
|
367
415
|
}
|
|
368
|
-
function createContext(collections) {
|
|
416
|
+
function createContext(collections, cache) {
|
|
369
417
|
return {
|
|
370
418
|
documents: (collection) => {
|
|
371
419
|
const resolved = collections.find((c) => c.name === collection.name);
|
|
@@ -376,19 +424,22 @@ function createTransformer(emitter) {
|
|
|
376
424
|
);
|
|
377
425
|
}
|
|
378
426
|
return resolved.documents.map((doc) => doc.document);
|
|
379
|
-
}
|
|
427
|
+
},
|
|
428
|
+
cache: cache.cacheFn
|
|
380
429
|
};
|
|
381
430
|
}
|
|
382
431
|
async function transformCollection(collections, collection) {
|
|
383
432
|
if (collection.transform) {
|
|
384
433
|
const docs = [];
|
|
385
|
-
const context = createContext(collections);
|
|
386
434
|
for (const doc of collection.documents) {
|
|
435
|
+
const cache = cacheManager.cache(collection.name, doc.document._meta.path);
|
|
436
|
+
const context = createContext(collections, cache);
|
|
387
437
|
try {
|
|
388
438
|
docs.push({
|
|
389
439
|
...doc,
|
|
390
|
-
document: await collection.transform(
|
|
440
|
+
document: await collection.transform(doc.document, context)
|
|
391
441
|
});
|
|
442
|
+
await cache.tidyUp();
|
|
392
443
|
} catch (error) {
|
|
393
444
|
if (error instanceof TransformError) {
|
|
394
445
|
emitter.emit("transformer:error", {
|
|
@@ -403,6 +454,7 @@ function createTransformer(emitter) {
|
|
|
403
454
|
}
|
|
404
455
|
}
|
|
405
456
|
}
|
|
457
|
+
await cacheManager.flush();
|
|
406
458
|
return docs;
|
|
407
459
|
}
|
|
408
460
|
return collection.documents;
|
|
@@ -475,15 +527,13 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
475
527
|
const index = collection.files.findIndex(
|
|
476
528
|
(file2) => file2.path === relativePath
|
|
477
529
|
);
|
|
478
|
-
const file = await readCollectionFile(
|
|
479
|
-
path4.join(baseDirectory, collection.directory),
|
|
480
|
-
relativePath
|
|
481
|
-
);
|
|
530
|
+
const file = await readCollectionFile(collection, relativePath);
|
|
482
531
|
if (!file) {
|
|
483
532
|
return false;
|
|
484
533
|
}
|
|
485
534
|
if (index === -1) {
|
|
486
535
|
collection.files.push(file);
|
|
536
|
+
collection.files.sort(orderByPath);
|
|
487
537
|
} else {
|
|
488
538
|
collection.files[index] = file;
|
|
489
539
|
}
|
|
@@ -496,7 +546,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
496
546
|
}
|
|
497
547
|
|
|
498
548
|
// src/builder.ts
|
|
499
|
-
import
|
|
549
|
+
import path6 from "path";
|
|
500
550
|
|
|
501
551
|
// src/watcher.ts
|
|
502
552
|
import * as watcher from "@parcel/watcher";
|
|
@@ -521,7 +571,7 @@ async function createWatcher(emitter, paths, sync, build2) {
|
|
|
521
571
|
}
|
|
522
572
|
};
|
|
523
573
|
const subscriptions = await Promise.all(
|
|
524
|
-
paths.map((
|
|
574
|
+
paths.map((path7) => watcher.subscribe(path7, onChange))
|
|
525
575
|
);
|
|
526
576
|
return {
|
|
527
577
|
unsubscribe: async () => {
|
|
@@ -562,12 +612,96 @@ function createEmitter() {
|
|
|
562
612
|
};
|
|
563
613
|
}
|
|
564
614
|
|
|
615
|
+
// src/cache.ts
|
|
616
|
+
import path5, { join } from "path";
|
|
617
|
+
import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
|
|
618
|
+
import { existsSync as existsSync2 } from "fs";
|
|
619
|
+
import { createHash as createHash2 } from "crypto";
|
|
620
|
+
function createKey(config, input) {
|
|
621
|
+
return createHash2("sha256").update(config).update(JSON.stringify(input)).digest("hex");
|
|
622
|
+
}
|
|
623
|
+
async function createCacheDirectory(directory) {
|
|
624
|
+
const cacheDirectory = path5.join(directory, ".content-collections", "cache");
|
|
625
|
+
if (!existsSync2(cacheDirectory)) {
|
|
626
|
+
await mkdir(cacheDirectory, { recursive: true });
|
|
627
|
+
}
|
|
628
|
+
return cacheDirectory;
|
|
629
|
+
}
|
|
630
|
+
function fileName(input) {
|
|
631
|
+
return input.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
632
|
+
}
|
|
633
|
+
async function createCacheManager(baseDirectory, configChecksum) {
|
|
634
|
+
const cacheDirectory = await createCacheDirectory(baseDirectory);
|
|
635
|
+
let mapping = {};
|
|
636
|
+
const mappingPath = join(cacheDirectory, "mapping.json");
|
|
637
|
+
if (existsSync2(mappingPath)) {
|
|
638
|
+
mapping = JSON.parse(await readFile2(mappingPath, "utf-8"));
|
|
639
|
+
}
|
|
640
|
+
async function flush() {
|
|
641
|
+
await writeFile(mappingPath, JSON.stringify(mapping));
|
|
642
|
+
}
|
|
643
|
+
function cache(collection, file) {
|
|
644
|
+
const directory = join(
|
|
645
|
+
cacheDirectory,
|
|
646
|
+
fileName(collection),
|
|
647
|
+
fileName(file)
|
|
648
|
+
);
|
|
649
|
+
let collectionMapping = mapping[collection];
|
|
650
|
+
if (!collectionMapping) {
|
|
651
|
+
collectionMapping = {};
|
|
652
|
+
mapping[collection] = collectionMapping;
|
|
653
|
+
}
|
|
654
|
+
let fileMapping = collectionMapping[file];
|
|
655
|
+
if (!fileMapping) {
|
|
656
|
+
fileMapping = [];
|
|
657
|
+
collectionMapping[file] = fileMapping;
|
|
658
|
+
}
|
|
659
|
+
let newFileMapping = [];
|
|
660
|
+
const cacheFn = async (input, fn) => {
|
|
661
|
+
const key = createKey(configChecksum, input);
|
|
662
|
+
newFileMapping.push(key);
|
|
663
|
+
const filePath = join(directory, `${key}.cache`);
|
|
664
|
+
if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
|
|
665
|
+
if (existsSync2(filePath)) {
|
|
666
|
+
return JSON.parse(await readFile2(filePath, "utf-8"));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const output = await fn(input);
|
|
670
|
+
if (!existsSync2(directory)) {
|
|
671
|
+
await mkdir(directory, { recursive: true });
|
|
672
|
+
}
|
|
673
|
+
await writeFile(filePath, JSON.stringify(output));
|
|
674
|
+
return output;
|
|
675
|
+
};
|
|
676
|
+
const tidyUp = async () => {
|
|
677
|
+
const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
|
|
678
|
+
for (const key of filesToDelete) {
|
|
679
|
+
const filePath = join(directory, `${key}.cache`);
|
|
680
|
+
if (existsSync2(filePath)) {
|
|
681
|
+
await unlink(filePath);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (collectionMapping) {
|
|
685
|
+
collectionMapping[file] = newFileMapping;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
return {
|
|
689
|
+
cacheFn,
|
|
690
|
+
tidyUp
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
cache,
|
|
695
|
+
flush
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
565
699
|
// src/builder.ts
|
|
566
700
|
function resolveOutputDir(baseDirectory, options) {
|
|
567
701
|
if (options.outputDir) {
|
|
568
702
|
return options.outputDir;
|
|
569
703
|
}
|
|
570
|
-
return
|
|
704
|
+
return path6.join(baseDirectory, ".content-collections", "generated");
|
|
571
705
|
}
|
|
572
706
|
async function createBuilder(configurationPath, options = {
|
|
573
707
|
configName: defaultConfigName
|
|
@@ -575,7 +709,7 @@ async function createBuilder(configurationPath, options = {
|
|
|
575
709
|
const emitter = createEmitter();
|
|
576
710
|
const readConfiguration = createConfigurationReader();
|
|
577
711
|
const configuration = await readConfiguration(configurationPath, options);
|
|
578
|
-
const baseDirectory =
|
|
712
|
+
const baseDirectory = path6.dirname(configurationPath);
|
|
579
713
|
const directory = resolveOutputDir(baseDirectory, options);
|
|
580
714
|
const collector = createCollector(emitter, baseDirectory);
|
|
581
715
|
const writer = await createWriter(directory);
|
|
@@ -589,7 +723,8 @@ async function createBuilder(configurationPath, options = {
|
|
|
589
723
|
resolved,
|
|
590
724
|
baseDirectory
|
|
591
725
|
);
|
|
592
|
-
const
|
|
726
|
+
const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
|
|
727
|
+
const transform = createTransformer(emitter, cacheManager);
|
|
593
728
|
async function sync(modification, filePath) {
|
|
594
729
|
if (modification === "delete") {
|
|
595
730
|
return synchronizer.deleted(filePath);
|
|
@@ -614,7 +749,7 @@ async function createBuilder(configurationPath, options = {
|
|
|
614
749
|
}
|
|
615
750
|
async function watch() {
|
|
616
751
|
const paths = resolved.map(
|
|
617
|
-
(collection) =>
|
|
752
|
+
(collection) => path6.join(baseDirectory, collection.directory)
|
|
618
753
|
);
|
|
619
754
|
const watcher2 = await createWatcher(emitter, paths, sync, build2);
|
|
620
755
|
return watcher2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@content-collections/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -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": {
|