@content-collections/core 0.2.0 → 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 +40 -10
- package/dist/index.js +186 -100
- 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?: (
|
|
28
|
-
directory: 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
|
|
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,7 +44,7 @@ import path from "path";
|
|
|
39
44
|
// package.json
|
|
40
45
|
var package_default = {
|
|
41
46
|
name: "@content-collections/core",
|
|
42
|
-
version: "0.
|
|
47
|
+
version: "0.3.0",
|
|
43
48
|
type: "module",
|
|
44
49
|
main: "dist/index.cjs",
|
|
45
50
|
types: "./dist/index.d.ts",
|
|
@@ -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,10 @@ async function compile(configurationPath, outfile) {
|
|
|
114
121
|
}
|
|
115
122
|
await esbuild.build({
|
|
116
123
|
entryPoints: [configurationPath],
|
|
117
|
-
external: [
|
|
124
|
+
external: [
|
|
125
|
+
...Object.keys(package_default.dependencies),
|
|
126
|
+
"@content-collections/*"
|
|
127
|
+
],
|
|
118
128
|
bundle: true,
|
|
119
129
|
platform: "node",
|
|
120
130
|
format: "esm",
|
|
@@ -144,19 +154,56 @@ function createConfigurationReader() {
|
|
|
144
154
|
);
|
|
145
155
|
}
|
|
146
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");
|
|
147
160
|
return {
|
|
148
161
|
...module.default,
|
|
149
162
|
path: configurationPath,
|
|
150
|
-
generateTypes: true
|
|
163
|
+
generateTypes: true,
|
|
164
|
+
checksum
|
|
151
165
|
};
|
|
152
166
|
};
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
// src/collector.ts
|
|
156
|
-
import matter from "gray-matter";
|
|
157
170
|
import fg from "fast-glob";
|
|
158
171
|
import { readFile } from "fs/promises";
|
|
159
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
|
|
160
207
|
var CollectError = class extends Error {
|
|
161
208
|
type;
|
|
162
209
|
constructor(type, message) {
|
|
@@ -176,55 +223,37 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
176
223
|
return null;
|
|
177
224
|
}
|
|
178
225
|
}
|
|
179
|
-
function
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
...data,
|
|
183
|
-
content
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
async function collectFile(directory, filePath) {
|
|
187
|
-
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);
|
|
188
229
|
if (!file) {
|
|
189
230
|
return null;
|
|
190
231
|
}
|
|
191
232
|
try {
|
|
192
|
-
const data = parse(file);
|
|
233
|
+
const data = parsers[collection.parser].parse(file);
|
|
193
234
|
return {
|
|
194
235
|
data,
|
|
195
236
|
path: filePath
|
|
196
237
|
};
|
|
197
238
|
} catch (error) {
|
|
198
239
|
emitter.emit("collector:parse-error", {
|
|
199
|
-
filePath: path2.join(directory, filePath),
|
|
240
|
+
filePath: path2.join(collection.directory, filePath),
|
|
200
241
|
error: new CollectError("Parse", String(error))
|
|
201
242
|
});
|
|
202
243
|
return null;
|
|
203
244
|
}
|
|
204
245
|
}
|
|
205
|
-
async function
|
|
206
|
-
const collectionDirectory = path2.join(baseDirectory, directory);
|
|
246
|
+
async function resolveCollection(collection) {
|
|
247
|
+
const collectionDirectory = path2.join(baseDirectory, collection.directory);
|
|
207
248
|
const filePaths = await fg(collection.include, {
|
|
208
249
|
cwd: collectionDirectory,
|
|
209
250
|
onlyFiles: true,
|
|
210
251
|
absolute: false
|
|
211
252
|
});
|
|
212
253
|
const promises = filePaths.map(
|
|
213
|
-
(filePath) => collectFile(
|
|
254
|
+
(filePath) => collectFile(collection, filePath)
|
|
214
255
|
);
|
|
215
|
-
|
|
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
|
-
}
|
|
256
|
+
const files = await Promise.all(promises);
|
|
228
257
|
return {
|
|
229
258
|
...collection,
|
|
230
259
|
files: files.filter(isDefined).sort(orderByPath)
|
|
@@ -326,23 +355,27 @@ var TransformError = class extends Error {
|
|
|
326
355
|
this.type = type;
|
|
327
356
|
}
|
|
328
357
|
};
|
|
329
|
-
function createPath(
|
|
330
|
-
let p =
|
|
358
|
+
function createPath(path7, ext) {
|
|
359
|
+
let p = path7.slice(0, -ext.length);
|
|
331
360
|
if (p.endsWith("/index")) {
|
|
332
361
|
p = p.slice(0, -6);
|
|
333
362
|
}
|
|
334
363
|
return p;
|
|
335
364
|
}
|
|
336
|
-
function createTransformer(emitter) {
|
|
337
|
-
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
|
+
}
|
|
338
371
|
return z2.object({
|
|
339
372
|
content: z2.string(),
|
|
340
373
|
...schema
|
|
341
374
|
});
|
|
342
375
|
}
|
|
343
376
|
async function parseFile(collection, file) {
|
|
344
|
-
const { data, path:
|
|
345
|
-
const schema = createSchema(collection.schema);
|
|
377
|
+
const { data, path: path7 } = file;
|
|
378
|
+
const schema = createSchema(collection.parser, collection.schema);
|
|
346
379
|
let parsedData = await schema.safeParseAsync(data);
|
|
347
380
|
if (!parsedData.success) {
|
|
348
381
|
emitter.emit("transformer:validation-error", {
|
|
@@ -352,7 +385,7 @@ function createTransformer(emitter) {
|
|
|
352
385
|
});
|
|
353
386
|
return null;
|
|
354
387
|
}
|
|
355
|
-
const ext = extname(
|
|
388
|
+
const ext = extname(path7);
|
|
356
389
|
let extension = ext;
|
|
357
390
|
if (extension.startsWith(".")) {
|
|
358
391
|
extension = extension.slice(1);
|
|
@@ -360,11 +393,11 @@ function createTransformer(emitter) {
|
|
|
360
393
|
const document = {
|
|
361
394
|
...parsedData.data,
|
|
362
395
|
_meta: {
|
|
363
|
-
filePath:
|
|
364
|
-
fileName: basename(
|
|
365
|
-
directory: dirname(
|
|
396
|
+
filePath: path7,
|
|
397
|
+
fileName: basename(path7),
|
|
398
|
+
directory: dirname(path7),
|
|
366
399
|
extension,
|
|
367
|
-
path: createPath(
|
|
400
|
+
path: createPath(path7, ext)
|
|
368
401
|
}
|
|
369
402
|
};
|
|
370
403
|
return {
|
|
@@ -380,7 +413,7 @@ function createTransformer(emitter) {
|
|
|
380
413
|
documents: (await Promise.all(promises)).filter(isDefined)
|
|
381
414
|
};
|
|
382
415
|
}
|
|
383
|
-
function createContext(collections) {
|
|
416
|
+
function createContext(collections, cache) {
|
|
384
417
|
return {
|
|
385
418
|
documents: (collection) => {
|
|
386
419
|
const resolved = collections.find((c) => c.name === collection.name);
|
|
@@ -391,19 +424,22 @@ function createTransformer(emitter) {
|
|
|
391
424
|
);
|
|
392
425
|
}
|
|
393
426
|
return resolved.documents.map((doc) => doc.document);
|
|
394
|
-
}
|
|
427
|
+
},
|
|
428
|
+
cache: cache.cacheFn
|
|
395
429
|
};
|
|
396
430
|
}
|
|
397
431
|
async function transformCollection(collections, collection) {
|
|
398
432
|
if (collection.transform) {
|
|
399
433
|
const docs = [];
|
|
400
|
-
const context = createContext(collections);
|
|
401
434
|
for (const doc of collection.documents) {
|
|
435
|
+
const cache = cacheManager.cache(collection.name, doc.document._meta.path);
|
|
436
|
+
const context = createContext(collections, cache);
|
|
402
437
|
try {
|
|
403
438
|
docs.push({
|
|
404
439
|
...doc,
|
|
405
|
-
document: await collection.transform(
|
|
440
|
+
document: await collection.transform(doc.document, context)
|
|
406
441
|
});
|
|
442
|
+
await cache.tidyUp();
|
|
407
443
|
} catch (error) {
|
|
408
444
|
if (error instanceof TransformError) {
|
|
409
445
|
emitter.emit("transformer:error", {
|
|
@@ -418,6 +454,7 @@ function createTransformer(emitter) {
|
|
|
418
454
|
}
|
|
419
455
|
}
|
|
420
456
|
}
|
|
457
|
+
await cacheManager.flush();
|
|
421
458
|
return docs;
|
|
422
459
|
}
|
|
423
460
|
return collection.documents;
|
|
@@ -438,26 +475,13 @@ function createTransformer(emitter) {
|
|
|
438
475
|
import micromatch from "micromatch";
|
|
439
476
|
import path4 from "path";
|
|
440
477
|
function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
|
|
441
|
-
function
|
|
478
|
+
function findCollection(filePath) {
|
|
442
479
|
const resolvedFilePath = path4.resolve(filePath);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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;
|
|
480
|
+
return collections.find((collection) => {
|
|
481
|
+
return resolvedFilePath.startsWith(
|
|
482
|
+
path4.resolve(baseDirectory, collection.directory)
|
|
483
|
+
);
|
|
484
|
+
});
|
|
461
485
|
}
|
|
462
486
|
function createRelativePath(collectionPath, filePath) {
|
|
463
487
|
const resolvedCollectionPath = path4.resolve(baseDirectory, collectionPath);
|
|
@@ -469,12 +493,11 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
469
493
|
return relativePath;
|
|
470
494
|
}
|
|
471
495
|
function resolve(filePath) {
|
|
472
|
-
const
|
|
473
|
-
if (!
|
|
496
|
+
const collection = findCollection(filePath);
|
|
497
|
+
if (!collection) {
|
|
474
498
|
return null;
|
|
475
499
|
}
|
|
476
|
-
const
|
|
477
|
-
const relativePath = createRelativePath(directory, filePath);
|
|
500
|
+
const relativePath = createRelativePath(collection.directory, filePath);
|
|
478
501
|
if (!micromatch.isMatch(relativePath, collection.include)) {
|
|
479
502
|
return null;
|
|
480
503
|
}
|
|
@@ -504,22 +527,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
504
527
|
const index = collection.files.findIndex(
|
|
505
528
|
(file2) => file2.path === relativePath
|
|
506
529
|
);
|
|
507
|
-
const
|
|
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
|
-
}
|
|
530
|
+
const file = await readCollectionFile(collection, relativePath);
|
|
523
531
|
if (!file) {
|
|
524
532
|
return false;
|
|
525
533
|
}
|
|
@@ -538,7 +546,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
538
546
|
}
|
|
539
547
|
|
|
540
548
|
// src/builder.ts
|
|
541
|
-
import
|
|
549
|
+
import path6 from "path";
|
|
542
550
|
|
|
543
551
|
// src/watcher.ts
|
|
544
552
|
import * as watcher from "@parcel/watcher";
|
|
@@ -563,7 +571,7 @@ async function createWatcher(emitter, paths, sync, build2) {
|
|
|
563
571
|
}
|
|
564
572
|
};
|
|
565
573
|
const subscriptions = await Promise.all(
|
|
566
|
-
paths.map((
|
|
574
|
+
paths.map((path7) => watcher.subscribe(path7, onChange))
|
|
567
575
|
);
|
|
568
576
|
return {
|
|
569
577
|
unsubscribe: async () => {
|
|
@@ -604,12 +612,96 @@ function createEmitter() {
|
|
|
604
612
|
};
|
|
605
613
|
}
|
|
606
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
|
+
|
|
607
699
|
// src/builder.ts
|
|
608
700
|
function resolveOutputDir(baseDirectory, options) {
|
|
609
701
|
if (options.outputDir) {
|
|
610
702
|
return options.outputDir;
|
|
611
703
|
}
|
|
612
|
-
return
|
|
704
|
+
return path6.join(baseDirectory, ".content-collections", "generated");
|
|
613
705
|
}
|
|
614
706
|
async function createBuilder(configurationPath, options = {
|
|
615
707
|
configName: defaultConfigName
|
|
@@ -617,7 +709,7 @@ async function createBuilder(configurationPath, options = {
|
|
|
617
709
|
const emitter = createEmitter();
|
|
618
710
|
const readConfiguration = createConfigurationReader();
|
|
619
711
|
const configuration = await readConfiguration(configurationPath, options);
|
|
620
|
-
const baseDirectory =
|
|
712
|
+
const baseDirectory = path6.dirname(configurationPath);
|
|
621
713
|
const directory = resolveOutputDir(baseDirectory, options);
|
|
622
714
|
const collector = createCollector(emitter, baseDirectory);
|
|
623
715
|
const writer = await createWriter(directory);
|
|
@@ -631,7 +723,8 @@ async function createBuilder(configurationPath, options = {
|
|
|
631
723
|
resolved,
|
|
632
724
|
baseDirectory
|
|
633
725
|
);
|
|
634
|
-
const
|
|
726
|
+
const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
|
|
727
|
+
const transform = createTransformer(emitter, cacheManager);
|
|
635
728
|
async function sync(modification, filePath) {
|
|
636
729
|
if (modification === "delete") {
|
|
637
730
|
return synchronizer.deleted(filePath);
|
|
@@ -655,16 +748,9 @@ async function createBuilder(configurationPath, options = {
|
|
|
655
748
|
});
|
|
656
749
|
}
|
|
657
750
|
async function watch() {
|
|
658
|
-
const paths =
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
}
|
|
751
|
+
const paths = resolved.map(
|
|
752
|
+
(collection) => path6.join(baseDirectory, collection.directory)
|
|
753
|
+
);
|
|
668
754
|
const watcher2 = await createWatcher(emitter, paths, sync, build2);
|
|
669
755
|
return watcher2;
|
|
670
756
|
}
|
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": {
|