@content-collections/core 0.6.4 → 0.7.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 +1 -1
- package/dist/index.d.ts +69 -12
- package/dist/index.js +495 -332
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Content Collections offers a variety of adapters that seamlessly integrate with
|
|
|
28
28
|
- [Qwik](https://www.content-collections.dev/docs/quickstart/qwik)
|
|
29
29
|
- [Remix (Vite)](https://www.content-collections.dev/docs/quickstart/remix-vite)
|
|
30
30
|
- [Solid Start](https://www.content-collections.dev/docs/quickstart/solid)
|
|
31
|
-
- [
|
|
31
|
+
- [SvelteKit](https://www.content-collections.dev/docs/quickstart/svelte-kit)
|
|
32
32
|
- [Vite](https://www.content-collections.dev/docs/quickstart/vite)
|
|
33
33
|
|
|
34
34
|
If your framework is not listed, you can still use Content Collections by using the [CLI](https://www.content-collections.dev/docs/quickstart/cli). Please open a ticket if you want to see your framework listed.
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare const parsers: {
|
|
|
14
14
|
};
|
|
15
15
|
readonly json: {
|
|
16
16
|
readonly hasContent: false;
|
|
17
|
-
readonly parse: (text: string, reviver?: (
|
|
17
|
+
readonly parse: (text: string, reviver?: (this: any, key: string, value: any) => any) => any;
|
|
18
18
|
};
|
|
19
19
|
readonly yaml: {
|
|
20
20
|
readonly hasContent: false;
|
|
@@ -54,12 +54,13 @@ type GetShape<TParser extends Parser | undefined, TShape extends ZodRawShape> =
|
|
|
54
54
|
type Schema<TParser extends Parser | undefined, TShape extends ZodRawShape> = z$1.infer<ZodObject<GetShape<TParser, TShape>>> & {
|
|
55
55
|
_meta: Meta;
|
|
56
56
|
};
|
|
57
|
-
type Context = {
|
|
57
|
+
type Context<TSchema = unknown> = {
|
|
58
58
|
documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["parser"], TCollection["schema"]>>;
|
|
59
59
|
cache: CacheFn;
|
|
60
60
|
collection: {
|
|
61
61
|
name: string;
|
|
62
62
|
directory: string;
|
|
63
|
+
documents: () => Promise<Array<TSchema>>;
|
|
63
64
|
};
|
|
64
65
|
};
|
|
65
66
|
type Z = typeof z$1;
|
|
@@ -68,7 +69,7 @@ type CollectionRequest<TName extends string, TShape extends ZodRawShape, TParser
|
|
|
68
69
|
parser?: TParser;
|
|
69
70
|
typeName?: string;
|
|
70
71
|
schema: (z: Z) => TShape;
|
|
71
|
-
transform?: (data: TSchema, context: Context) => TTransformResult;
|
|
72
|
+
transform?: (data: TSchema, context: Context<TSchema>) => TTransformResult;
|
|
72
73
|
directory: string;
|
|
73
74
|
include: string | string[];
|
|
74
75
|
exclude?: string | string[];
|
|
@@ -150,12 +151,31 @@ declare class TransformError extends Error {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
type WatcherEvents = {
|
|
153
|
-
"watcher:
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
"watcher:subscribe-error": {
|
|
155
|
+
paths: Array<string>;
|
|
156
|
+
error: Error;
|
|
157
|
+
};
|
|
158
|
+
"watcher:subscribed": {
|
|
159
|
+
paths: Array<string>;
|
|
156
160
|
};
|
|
161
|
+
"watcher:unsubscribed": {
|
|
162
|
+
paths: Array<string>;
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
type SyncFn = (modification: Modification, path: string) => Promise<unknown>;
|
|
166
|
+
type WatchableCollection = {
|
|
167
|
+
directory: string;
|
|
157
168
|
};
|
|
169
|
+
type WatcherConfiguration = {
|
|
170
|
+
inputPaths: Array<string>;
|
|
171
|
+
collections: Array<WatchableCollection>;
|
|
172
|
+
};
|
|
173
|
+
declare function createWatcher(emitter: Emitter, baseDirectory: string, configuration: WatcherConfiguration, sync: SyncFn): Promise<{
|
|
174
|
+
unsubscribe: () => Promise<void>;
|
|
175
|
+
}>;
|
|
176
|
+
type Watcher = Awaited<ReturnType<typeof createWatcher>>;
|
|
158
177
|
|
|
178
|
+
type EventMap = Record<string, object>;
|
|
159
179
|
type EventWithError = {
|
|
160
180
|
error: Error;
|
|
161
181
|
};
|
|
@@ -168,6 +188,16 @@ type SystemEvents = {
|
|
|
168
188
|
_error: ErrorEvent;
|
|
169
189
|
_all: SystemEvent;
|
|
170
190
|
};
|
|
191
|
+
type Keys<TEvents extends EventMap> = keyof TEvents & string;
|
|
192
|
+
type Listener<TEvent> = (event: TEvent) => void;
|
|
193
|
+
declare function createEmitter<TEvents extends EventMap>(): {
|
|
194
|
+
on: {
|
|
195
|
+
<TKey extends Keys<TEvents>>(key: TKey, listener: Listener<TEvents[TKey]>): void;
|
|
196
|
+
<TKey extends Keys<SystemEvents>>(key: TKey, listener: Listener<SystemEvents[TKey]>): void;
|
|
197
|
+
};
|
|
198
|
+
emit: <TKey extends Keys<TEvents>>(key: TKey, event: TEvents[TKey]) => void;
|
|
199
|
+
};
|
|
200
|
+
type Emitter = ReturnType<typeof createEmitter<Events>>;
|
|
171
201
|
|
|
172
202
|
type ErrorType = "Read" | "Compile";
|
|
173
203
|
declare class ConfigurationError extends Error {
|
|
@@ -179,29 +209,56 @@ type Options$1 = {
|
|
|
179
209
|
cacheDir?: string;
|
|
180
210
|
};
|
|
181
211
|
|
|
182
|
-
type
|
|
212
|
+
type BuildEvents = {
|
|
183
213
|
"builder:start": {
|
|
184
214
|
startedAt: number;
|
|
185
215
|
};
|
|
186
216
|
"builder:end": {
|
|
187
217
|
startedAt: number;
|
|
188
218
|
endedAt: number;
|
|
219
|
+
stats: {
|
|
220
|
+
collections: number;
|
|
221
|
+
documents: number;
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
type BuilderEvents = BuildEvents & {
|
|
227
|
+
"builder:created": {
|
|
228
|
+
createdAt: number;
|
|
229
|
+
configurationPath: string;
|
|
230
|
+
outputDirectory: string;
|
|
231
|
+
};
|
|
232
|
+
"watcher:file-changed": {
|
|
233
|
+
filePath: string;
|
|
234
|
+
modification: Modification;
|
|
235
|
+
};
|
|
236
|
+
"watcher:config-changed": {
|
|
237
|
+
filePath: string;
|
|
238
|
+
modification: Modification;
|
|
239
|
+
};
|
|
240
|
+
"watcher:config-reload-error": {
|
|
241
|
+
error: Error;
|
|
242
|
+
configurationPath: string;
|
|
189
243
|
};
|
|
190
244
|
};
|
|
191
245
|
type Options = Options$1 & {
|
|
192
246
|
outputDir?: string;
|
|
193
247
|
};
|
|
194
|
-
declare
|
|
195
|
-
|
|
248
|
+
declare class ConfigurationReloadError extends Error {
|
|
249
|
+
constructor(message: string);
|
|
250
|
+
}
|
|
251
|
+
declare function createBuilder(configurationPath: string, options?: Options, emitter?: Emitter): Promise<{
|
|
196
252
|
build: () => Promise<void>;
|
|
253
|
+
sync: (modification: Modification, filePath: string) => Promise<boolean>;
|
|
197
254
|
watch: () => Promise<{
|
|
198
255
|
unsubscribe: () => Promise<void>;
|
|
199
256
|
}>;
|
|
200
257
|
on: {
|
|
201
|
-
<TKey extends "builder:start" | "builder:end" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:result-error" | "transformer:error" | "watcher:
|
|
202
|
-
<
|
|
258
|
+
<TKey extends "builder:start" | "builder:end" | "builder:created" | "watcher:file-changed" | "watcher:config-changed" | "watcher:config-reload-error" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:result-error" | "transformer:error" | "watcher:subscribe-error" | "watcher:subscribed" | "watcher:unsubscribed">(key: TKey, listener: (event: Events[TKey]) => void): void;
|
|
259
|
+
<TKey extends "_error" | "_all">(key: TKey, listener: (event: SystemEvents[TKey]) => void): void;
|
|
203
260
|
};
|
|
204
261
|
}>;
|
|
205
262
|
type Builder = Awaited<ReturnType<typeof createBuilder>>;
|
|
206
263
|
|
|
207
|
-
export { type AnyCollection, type AnyConfiguration, type Builder, type BuilderEvents, CollectError, type Collection, type CollectionRequest, type Configuration, ConfigurationError, type Context, type Document, type GetTypeByName, type Meta, type Modification, type Schema, TransformError, createBuilder, defineCollection, defineConfig };
|
|
264
|
+
export { type AnyCollection, type AnyConfiguration, type Builder, type BuilderEvents, CollectError, type Collection, type CollectionRequest, type Configuration, ConfigurationError, ConfigurationReloadError, type Context, type Document, type GetTypeByName, type Meta, type Modification, type Schema, TransformError, type Watcher, createBuilder, defineCollection, defineConfig };
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,20 @@ function isDefined(value) {
|
|
|
14
14
|
function orderByPath(a, b) {
|
|
15
15
|
return a.path.localeCompare(b.path);
|
|
16
16
|
}
|
|
17
|
+
function removeChildPaths(paths) {
|
|
18
|
+
return Array.from(
|
|
19
|
+
new Set(
|
|
20
|
+
paths.filter((path8) => {
|
|
21
|
+
return !paths.some((otherPath) => {
|
|
22
|
+
if (path8 === otherPath) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return path8.startsWith(otherPath);
|
|
26
|
+
});
|
|
27
|
+
})
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
17
31
|
|
|
18
32
|
// src/config.ts
|
|
19
33
|
var InvalidReturnTypeSymbol = Symbol(`InvalidReturnType`);
|
|
@@ -38,59 +52,81 @@ function defineConfig(config) {
|
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
// src/configurationReader.ts
|
|
41
|
-
import * as esbuild from "esbuild";
|
|
42
55
|
import fs from "fs/promises";
|
|
43
56
|
import path from "path";
|
|
44
57
|
import { existsSync } from "fs";
|
|
45
58
|
import { createHash } from "crypto";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
|
|
60
|
+
// src/esbuild.ts
|
|
61
|
+
import { build } from "esbuild";
|
|
62
|
+
import { match, loadTsConfig, tsconfigPathsToRegExp } from "bundle-require";
|
|
63
|
+
import { dirname, join } from "path";
|
|
64
|
+
function tsconfigResolvePaths(configPath) {
|
|
65
|
+
let tsconfig = loadTsConfig(dirname(configPath));
|
|
66
|
+
if (!tsconfig) {
|
|
67
|
+
tsconfig = loadTsConfig();
|
|
68
|
+
}
|
|
69
|
+
return tsconfig?.data?.compilerOptions?.paths || {};
|
|
70
|
+
}
|
|
71
|
+
function createExternalsPlugin(configPath) {
|
|
72
|
+
const resolvedPaths = tsconfigResolvePaths(configPath);
|
|
73
|
+
const resolvePatterns = tsconfigPathsToRegExp(resolvedPaths);
|
|
74
|
+
return {
|
|
75
|
+
name: "external-packages",
|
|
76
|
+
setup: (build3) => {
|
|
77
|
+
const filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/;
|
|
78
|
+
build3.onResolve({ filter }, ({ path: path8, kind }) => {
|
|
79
|
+
if (match(path8, resolvePatterns)) {
|
|
80
|
+
if (kind === "dynamic-import") {
|
|
81
|
+
return { path: path8, external: true };
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
return { path: path8, external: true };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
53
90
|
var importPathPlugin = {
|
|
54
91
|
name: "import-path",
|
|
55
|
-
setup(
|
|
56
|
-
|
|
57
|
-
return { path:
|
|
92
|
+
setup(build3) {
|
|
93
|
+
build3.onResolve({ filter: /^\@content-collections\/core$/ }, () => {
|
|
94
|
+
return { path: join(__dirname, "index.ts"), external: true };
|
|
58
95
|
});
|
|
59
96
|
}
|
|
60
97
|
};
|
|
61
|
-
var defaultConfigName = "content-collection-config.mjs";
|
|
62
|
-
function resolveCacheDir(config, options) {
|
|
63
|
-
if (options.cacheDir) {
|
|
64
|
-
return options.cacheDir;
|
|
65
|
-
}
|
|
66
|
-
return path.join(path.dirname(config), ".content-collections", "cache");
|
|
67
|
-
}
|
|
68
|
-
var externalPackagesPlugin = (configPath) => ({
|
|
69
|
-
name: "external-packages",
|
|
70
|
-
setup: (build2) => {
|
|
71
|
-
const filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/;
|
|
72
|
-
build2.onResolve({ filter }, ({ path: path7 }) => {
|
|
73
|
-
const external = !path7.includes(configPath);
|
|
74
|
-
return { path: path7, external };
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
98
|
async function compile(configurationPath, outfile) {
|
|
79
|
-
const plugins = [
|
|
80
|
-
externalPackagesPlugin(configurationPath)
|
|
81
|
-
];
|
|
99
|
+
const plugins = [createExternalsPlugin(configurationPath)];
|
|
82
100
|
if (process.env.NODE_ENV === "test") {
|
|
83
101
|
plugins.push(importPathPlugin);
|
|
84
102
|
}
|
|
85
|
-
await
|
|
103
|
+
const result = await build({
|
|
86
104
|
entryPoints: [configurationPath],
|
|
87
105
|
packages: "external",
|
|
88
106
|
bundle: true,
|
|
89
107
|
platform: "node",
|
|
90
108
|
format: "esm",
|
|
91
109
|
plugins,
|
|
92
|
-
outfile
|
|
110
|
+
outfile,
|
|
111
|
+
metafile: true
|
|
93
112
|
});
|
|
113
|
+
return Object.keys(result.metafile.inputs);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/configurationReader.ts
|
|
117
|
+
var ConfigurationError = class extends Error {
|
|
118
|
+
type;
|
|
119
|
+
constructor(type, message) {
|
|
120
|
+
super(message);
|
|
121
|
+
this.type = type;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var defaultConfigName = "content-collection-config.mjs";
|
|
125
|
+
function resolveCacheDir(config, options) {
|
|
126
|
+
if (options.cacheDir) {
|
|
127
|
+
return options.cacheDir;
|
|
128
|
+
}
|
|
129
|
+
return path.join(path.dirname(config), ".content-collections", "cache");
|
|
94
130
|
}
|
|
95
131
|
function createConfigurationReader() {
|
|
96
132
|
return async (configurationPath, options = {
|
|
@@ -106,30 +142,103 @@ function createConfigurationReader() {
|
|
|
106
142
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
107
143
|
const outfile = path.join(cacheDir, options.configName);
|
|
108
144
|
try {
|
|
109
|
-
await compile(configurationPath, outfile);
|
|
145
|
+
const configurationPaths = await compile(configurationPath, outfile);
|
|
146
|
+
const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
|
|
147
|
+
const hash = createHash("sha256");
|
|
148
|
+
hash.update(await fs.readFile(outfile, "utf-8"));
|
|
149
|
+
const checksum = hash.digest("hex");
|
|
150
|
+
return {
|
|
151
|
+
...module.default,
|
|
152
|
+
path: configurationPath,
|
|
153
|
+
inputPaths: configurationPaths.map((p) => path.resolve(p)),
|
|
154
|
+
generateTypes: true,
|
|
155
|
+
checksum
|
|
156
|
+
};
|
|
110
157
|
} catch (error) {
|
|
111
158
|
throw new ConfigurationError(
|
|
112
159
|
"Compile",
|
|
113
160
|
`configuration file ${configurationPath} is invalid: ${error}`
|
|
114
161
|
);
|
|
115
162
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/builder.ts
|
|
167
|
+
import path7 from "path";
|
|
168
|
+
|
|
169
|
+
// src/watcher.ts
|
|
170
|
+
import * as watcher from "@parcel/watcher";
|
|
171
|
+
import path2, { dirname as dirname2, resolve } from "path";
|
|
172
|
+
async function createWatcher(emitter, baseDirectory, configuration, sync) {
|
|
173
|
+
const onChange = async (error, events) => {
|
|
174
|
+
if (error) {
|
|
175
|
+
emitter.emit("watcher:subscribe-error", {
|
|
176
|
+
paths,
|
|
177
|
+
error
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (const event of events) {
|
|
182
|
+
await sync(event.type, event.path);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const paths = removeChildPaths([
|
|
186
|
+
...configuration.collections.map((collection) => path2.join(baseDirectory, collection.directory)).map((p) => resolve(p)),
|
|
187
|
+
...configuration.inputPaths.map((p) => dirname2(p))
|
|
188
|
+
]);
|
|
189
|
+
const subscriptions = (await Promise.all(paths.map((path8) => watcher.subscribe(path8, onChange)))).filter(isDefined);
|
|
190
|
+
emitter.emit("watcher:subscribed", {
|
|
191
|
+
paths
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
unsubscribe: async () => {
|
|
195
|
+
if (!subscriptions || subscriptions.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await Promise.all(
|
|
199
|
+
subscriptions.map((subscription) => subscription.unsubscribe())
|
|
200
|
+
);
|
|
201
|
+
emitter.emit("watcher:unsubscribed", {
|
|
202
|
+
paths
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/events.ts
|
|
210
|
+
import { EventEmitter } from "events";
|
|
211
|
+
function isEventWithError(event) {
|
|
212
|
+
return typeof event === "object" && event !== null && "error" in event;
|
|
213
|
+
}
|
|
214
|
+
function createEmitter() {
|
|
215
|
+
const emitter = new EventEmitter();
|
|
216
|
+
function on(key, listener) {
|
|
217
|
+
emitter.on(key, listener);
|
|
218
|
+
}
|
|
219
|
+
function emit(key, event) {
|
|
220
|
+
emitter.emit(key, event);
|
|
221
|
+
if (isEventWithError(event)) {
|
|
222
|
+
emitter.emit("_error", {
|
|
223
|
+
...event,
|
|
224
|
+
_event: key
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
emitter.emit("_all", {
|
|
228
|
+
...event,
|
|
229
|
+
_event: key
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
on,
|
|
234
|
+
emit
|
|
126
235
|
};
|
|
127
236
|
}
|
|
128
237
|
|
|
129
238
|
// src/collector.ts
|
|
130
|
-
import
|
|
239
|
+
import { glob } from "tinyglobby";
|
|
131
240
|
import { readFile } from "fs/promises";
|
|
132
|
-
import
|
|
241
|
+
import path3 from "path";
|
|
133
242
|
|
|
134
243
|
// src/parser.ts
|
|
135
244
|
import matter from "gray-matter";
|
|
@@ -187,7 +296,7 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
187
296
|
}
|
|
188
297
|
}
|
|
189
298
|
async function collectFile(collection, filePath) {
|
|
190
|
-
const absolutePath =
|
|
299
|
+
const absolutePath = path3.join(baseDirectory, collection.directory, filePath);
|
|
191
300
|
const file = await read(absolutePath);
|
|
192
301
|
if (!file) {
|
|
193
302
|
return null;
|
|
@@ -200,7 +309,7 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
200
309
|
};
|
|
201
310
|
} catch (error) {
|
|
202
311
|
emitter.emit("collector:parse-error", {
|
|
203
|
-
filePath:
|
|
312
|
+
filePath: path3.join(collection.directory, filePath),
|
|
204
313
|
error: new CollectError("Parse", String(error))
|
|
205
314
|
});
|
|
206
315
|
return null;
|
|
@@ -217,8 +326,9 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
217
326
|
return void 0;
|
|
218
327
|
}
|
|
219
328
|
async function resolveCollection(collection) {
|
|
220
|
-
const collectionDirectory =
|
|
221
|
-
const
|
|
329
|
+
const collectionDirectory = path3.join(baseDirectory, collection.directory);
|
|
330
|
+
const include = Array.isArray(collection.include) ? collection.include : [collection.include];
|
|
331
|
+
const filePaths = await glob(include, {
|
|
222
332
|
cwd: collectionDirectory,
|
|
223
333
|
onlyFiles: true,
|
|
224
334
|
absolute: false,
|
|
@@ -247,7 +357,7 @@ function createCollector(emitter, baseDirectory = ".") {
|
|
|
247
357
|
|
|
248
358
|
// src/writer.ts
|
|
249
359
|
import fs2 from "fs/promises";
|
|
250
|
-
import
|
|
360
|
+
import path4 from "path";
|
|
251
361
|
import pluralize2 from "pluralize";
|
|
252
362
|
|
|
253
363
|
// src/serializer.ts
|
|
@@ -286,7 +396,7 @@ function createArrayConstName(name) {
|
|
|
286
396
|
return "all" + pluralize2(suffix);
|
|
287
397
|
}
|
|
288
398
|
async function createDataFile(directory, collection) {
|
|
289
|
-
const dataPath =
|
|
399
|
+
const dataPath = path4.join(
|
|
290
400
|
directory,
|
|
291
401
|
`${createArrayConstName(collection.name)}.${extension}`
|
|
292
402
|
);
|
|
@@ -313,11 +423,11 @@ async function createJavaScriptFile(directory, configuration) {
|
|
|
313
423
|
}
|
|
314
424
|
content += "\n";
|
|
315
425
|
content += "export { " + collections.join(", ") + " };\n";
|
|
316
|
-
await fs2.writeFile(
|
|
426
|
+
await fs2.writeFile(path4.join(directory, "index.js"), content, "utf-8");
|
|
317
427
|
}
|
|
318
428
|
function createImportPath(directory, target) {
|
|
319
|
-
let importPath =
|
|
320
|
-
...
|
|
429
|
+
let importPath = path4.posix.join(
|
|
430
|
+
...path4.relative(directory, target).split(path4.sep)
|
|
321
431
|
);
|
|
322
432
|
if (!importPath.startsWith(".")) {
|
|
323
433
|
importPath = "./" + importPath;
|
|
@@ -345,7 +455,7 @@ import { GetTypeByName } from "@content-collections/core";
|
|
|
345
455
|
}
|
|
346
456
|
content += "\n";
|
|
347
457
|
content += "export {};\n";
|
|
348
|
-
await fs2.writeFile(
|
|
458
|
+
await fs2.writeFile(path4.join(directory, "index.d.ts"), content, "utf-8");
|
|
349
459
|
}
|
|
350
460
|
async function createWriter(directory) {
|
|
351
461
|
await fs2.mkdir(directory, { recursive: true });
|
|
@@ -356,179 +466,28 @@ async function createWriter(directory) {
|
|
|
356
466
|
};
|
|
357
467
|
}
|
|
358
468
|
|
|
359
|
-
// src/transformer.ts
|
|
360
|
-
import { basename, dirname, extname } from "path";
|
|
361
|
-
import { z as z3 } from "zod";
|
|
362
|
-
var TransformError = class extends Error {
|
|
363
|
-
type;
|
|
364
|
-
constructor(type, message) {
|
|
365
|
-
super(message);
|
|
366
|
-
this.type = type;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
function createPath(path7, ext) {
|
|
370
|
-
let p = path7.slice(0, -ext.length);
|
|
371
|
-
if (p.endsWith("/index")) {
|
|
372
|
-
p = p.slice(0, -6);
|
|
373
|
-
}
|
|
374
|
-
return p;
|
|
375
|
-
}
|
|
376
|
-
function createTransformer(emitter, cacheManager) {
|
|
377
|
-
function createSchema(parserName, schema2) {
|
|
378
|
-
const parser = parsers[parserName];
|
|
379
|
-
if (!parser.hasContent) {
|
|
380
|
-
return z3.object(schema2);
|
|
381
|
-
}
|
|
382
|
-
return z3.object({
|
|
383
|
-
content: z3.string(),
|
|
384
|
-
...schema2
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
async function parseFile(collection, file) {
|
|
388
|
-
const { data, path: path7 } = file;
|
|
389
|
-
const schema2 = createSchema(collection.parser, collection.schema);
|
|
390
|
-
let parsedData = await schema2.safeParseAsync(data);
|
|
391
|
-
if (!parsedData.success) {
|
|
392
|
-
emitter.emit("transformer:validation-error", {
|
|
393
|
-
collection,
|
|
394
|
-
file,
|
|
395
|
-
error: new TransformError("Validation", parsedData.error.message)
|
|
396
|
-
});
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
const ext = extname(path7);
|
|
400
|
-
let extension2 = ext;
|
|
401
|
-
if (extension2.startsWith(".")) {
|
|
402
|
-
extension2 = extension2.slice(1);
|
|
403
|
-
}
|
|
404
|
-
const document = {
|
|
405
|
-
...parsedData.data,
|
|
406
|
-
_meta: {
|
|
407
|
-
filePath: path7,
|
|
408
|
-
fileName: basename(path7),
|
|
409
|
-
directory: dirname(path7),
|
|
410
|
-
extension: extension2,
|
|
411
|
-
path: createPath(path7, ext)
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
return {
|
|
415
|
-
document
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
async function parseCollection(collection) {
|
|
419
|
-
const promises = collection.files.map(
|
|
420
|
-
(file) => parseFile(collection, file)
|
|
421
|
-
);
|
|
422
|
-
return {
|
|
423
|
-
...collection,
|
|
424
|
-
documents: (await Promise.all(promises)).filter(isDefined)
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
function createContext(collections, collection, cache) {
|
|
428
|
-
return {
|
|
429
|
-
documents: (collection2) => {
|
|
430
|
-
const resolved = collections.find((c) => c.name === collection2.name);
|
|
431
|
-
if (!resolved) {
|
|
432
|
-
throw new TransformError(
|
|
433
|
-
"Configuration",
|
|
434
|
-
`Collection ${collection2.name} not found, do you have registered it in your configuration?`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
return resolved.documents.map((doc) => doc.document);
|
|
438
|
-
},
|
|
439
|
-
collection: {
|
|
440
|
-
name: collection.name,
|
|
441
|
-
directory: collection.directory
|
|
442
|
-
},
|
|
443
|
-
cache: cache.cacheFn
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
async function transformCollection(collections, collection) {
|
|
447
|
-
if (collection.transform) {
|
|
448
|
-
const docs = [];
|
|
449
|
-
for (const doc of collection.documents) {
|
|
450
|
-
const cache = cacheManager.cache(
|
|
451
|
-
collection.name,
|
|
452
|
-
doc.document._meta.path
|
|
453
|
-
);
|
|
454
|
-
const context = createContext(collections, collection, cache);
|
|
455
|
-
try {
|
|
456
|
-
const document = await collection.transform(doc.document, context);
|
|
457
|
-
docs.push({
|
|
458
|
-
...doc,
|
|
459
|
-
document
|
|
460
|
-
});
|
|
461
|
-
await cache.tidyUp();
|
|
462
|
-
} catch (error) {
|
|
463
|
-
if (error instanceof TransformError) {
|
|
464
|
-
emitter.emit("transformer:error", {
|
|
465
|
-
collection,
|
|
466
|
-
error
|
|
467
|
-
});
|
|
468
|
-
} else {
|
|
469
|
-
emitter.emit("transformer:error", {
|
|
470
|
-
collection,
|
|
471
|
-
error: new TransformError("Transform", String(error))
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
await cacheManager.flush();
|
|
477
|
-
return docs;
|
|
478
|
-
}
|
|
479
|
-
return collection.documents;
|
|
480
|
-
}
|
|
481
|
-
async function validateDocuments(collection, documents) {
|
|
482
|
-
const docs = [];
|
|
483
|
-
for (const doc of documents) {
|
|
484
|
-
let parsedData = await serializableSchema.safeParseAsync(doc.document);
|
|
485
|
-
if (parsedData.success) {
|
|
486
|
-
docs.push(doc);
|
|
487
|
-
} else {
|
|
488
|
-
emitter.emit("transformer:result-error", {
|
|
489
|
-
collection,
|
|
490
|
-
document: doc.document,
|
|
491
|
-
error: new TransformError("Result", parsedData.error.message)
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return docs;
|
|
496
|
-
}
|
|
497
|
-
return async (untransformedCollections) => {
|
|
498
|
-
const promises = untransformedCollections.map(
|
|
499
|
-
(collection) => parseCollection(collection)
|
|
500
|
-
);
|
|
501
|
-
const collections = await Promise.all(promises);
|
|
502
|
-
for (const collection of collections) {
|
|
503
|
-
const documents = await transformCollection(collections, collection);
|
|
504
|
-
collection.documents = await validateDocuments(collection, documents);
|
|
505
|
-
}
|
|
506
|
-
return collections;
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
469
|
// src/synchronizer.ts
|
|
511
|
-
import
|
|
512
|
-
import
|
|
470
|
+
import picomatch from "picomatch";
|
|
471
|
+
import path5 from "path";
|
|
513
472
|
function createSynchronizer(readCollectionFile, collections, baseDirectory = ".") {
|
|
514
473
|
function findCollections(filePath) {
|
|
515
|
-
const resolvedFilePath =
|
|
474
|
+
const resolvedFilePath = path5.resolve(filePath);
|
|
516
475
|
return collections.filter((collection) => {
|
|
517
476
|
return resolvedFilePath.startsWith(
|
|
518
|
-
|
|
477
|
+
path5.resolve(baseDirectory, collection.directory)
|
|
519
478
|
);
|
|
520
479
|
});
|
|
521
480
|
}
|
|
522
481
|
function createRelativePath(collectionPath, filePath) {
|
|
523
|
-
const resolvedCollectionPath =
|
|
524
|
-
const resolvedFilePath =
|
|
482
|
+
const resolvedCollectionPath = path5.resolve(baseDirectory, collectionPath);
|
|
483
|
+
const resolvedFilePath = path5.resolve(filePath);
|
|
525
484
|
let relativePath = resolvedFilePath.slice(resolvedCollectionPath.length);
|
|
526
|
-
if (relativePath.startsWith(
|
|
527
|
-
relativePath = relativePath.slice(
|
|
485
|
+
if (relativePath.startsWith(path5.sep)) {
|
|
486
|
+
relativePath = relativePath.slice(path5.sep.length);
|
|
528
487
|
}
|
|
529
488
|
return relativePath;
|
|
530
489
|
}
|
|
531
|
-
function
|
|
490
|
+
function resolve2(filePath) {
|
|
532
491
|
const collections2 = findCollections(filePath);
|
|
533
492
|
return collections2.map((collection) => {
|
|
534
493
|
const relativePath = createRelativePath(collection.directory, filePath);
|
|
@@ -537,13 +496,13 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
537
496
|
relativePath
|
|
538
497
|
};
|
|
539
498
|
}).filter(({ collection, relativePath }) => {
|
|
540
|
-
return
|
|
499
|
+
return picomatch.isMatch(relativePath, collection.include, {
|
|
541
500
|
ignore: collection.exclude
|
|
542
501
|
});
|
|
543
502
|
});
|
|
544
503
|
}
|
|
545
504
|
function deleted(filePath) {
|
|
546
|
-
const resolvedCollections =
|
|
505
|
+
const resolvedCollections = resolve2(filePath);
|
|
547
506
|
if (resolvedCollections.length === 0) {
|
|
548
507
|
return false;
|
|
549
508
|
}
|
|
@@ -560,7 +519,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
560
519
|
return changed2;
|
|
561
520
|
}
|
|
562
521
|
async function changed(filePath) {
|
|
563
|
-
const resolvedCollections =
|
|
522
|
+
const resolvedCollections = resolve2(filePath);
|
|
564
523
|
if (resolvedCollections.length === 0) {
|
|
565
524
|
return false;
|
|
566
525
|
}
|
|
@@ -588,75 +547,8 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
|
|
|
588
547
|
};
|
|
589
548
|
}
|
|
590
549
|
|
|
591
|
-
// src/builder.ts
|
|
592
|
-
import path6 from "path";
|
|
593
|
-
|
|
594
|
-
// src/watcher.ts
|
|
595
|
-
import * as watcher from "@parcel/watcher";
|
|
596
|
-
async function createWatcher(emitter, paths, sync, build2) {
|
|
597
|
-
const onChange = async (error, events) => {
|
|
598
|
-
if (error) {
|
|
599
|
-
console.error(error);
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
let rebuild = false;
|
|
603
|
-
for (const event of events) {
|
|
604
|
-
if (await sync(event.type, event.path)) {
|
|
605
|
-
emitter.emit("watcher:file-changed", {
|
|
606
|
-
filePath: event.path,
|
|
607
|
-
modification: event.type
|
|
608
|
-
});
|
|
609
|
-
rebuild = true;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
if (rebuild) {
|
|
613
|
-
await build2();
|
|
614
|
-
}
|
|
615
|
-
};
|
|
616
|
-
const subscriptions = await Promise.all(
|
|
617
|
-
paths.map((path7) => watcher.subscribe(path7, onChange))
|
|
618
|
-
);
|
|
619
|
-
return {
|
|
620
|
-
unsubscribe: async () => {
|
|
621
|
-
await Promise.all(
|
|
622
|
-
subscriptions.map((subscription) => subscription.unsubscribe())
|
|
623
|
-
);
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// src/events.ts
|
|
630
|
-
import { EventEmitter } from "events";
|
|
631
|
-
function isEventWithError(event) {
|
|
632
|
-
return typeof event === "object" && event !== null && "error" in event;
|
|
633
|
-
}
|
|
634
|
-
function createEmitter() {
|
|
635
|
-
const emitter = new EventEmitter();
|
|
636
|
-
function on(key, listener) {
|
|
637
|
-
emitter.on(key, listener);
|
|
638
|
-
}
|
|
639
|
-
function emit(key, event) {
|
|
640
|
-
emitter.emit(key, event);
|
|
641
|
-
if (isEventWithError(event)) {
|
|
642
|
-
emitter.emit("_error", {
|
|
643
|
-
...event,
|
|
644
|
-
_event: key
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
emitter.emit("_all", {
|
|
648
|
-
...event,
|
|
649
|
-
_event: key
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
return {
|
|
653
|
-
on,
|
|
654
|
-
emit
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
|
|
658
550
|
// src/cache.ts
|
|
659
|
-
import
|
|
551
|
+
import path6, { join as join2 } from "path";
|
|
660
552
|
import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
|
|
661
553
|
import { existsSync as existsSync2 } from "fs";
|
|
662
554
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -664,7 +556,7 @@ function createKey(config, input) {
|
|
|
664
556
|
return createHash2("sha256").update(config).update(JSON.stringify(input)).digest("hex");
|
|
665
557
|
}
|
|
666
558
|
async function createCacheDirectory(directory) {
|
|
667
|
-
const cacheDirectory =
|
|
559
|
+
const cacheDirectory = path6.join(directory, ".content-collections", "cache");
|
|
668
560
|
if (!existsSync2(cacheDirectory)) {
|
|
669
561
|
await mkdir(cacheDirectory, { recursive: true });
|
|
670
562
|
}
|
|
@@ -687,13 +579,13 @@ async function readMapping(mappingPath) {
|
|
|
687
579
|
}
|
|
688
580
|
async function createCacheManager(baseDirectory, configChecksum) {
|
|
689
581
|
const cacheDirectory = await createCacheDirectory(baseDirectory);
|
|
690
|
-
const mappingPath =
|
|
582
|
+
const mappingPath = join2(cacheDirectory, "mapping.json");
|
|
691
583
|
const mapping = await readMapping(mappingPath);
|
|
692
584
|
async function flush() {
|
|
693
585
|
await writeFile(mappingPath, JSON.stringify(mapping));
|
|
694
586
|
}
|
|
695
587
|
function cache(collection, file) {
|
|
696
|
-
const directory =
|
|
588
|
+
const directory = join2(
|
|
697
589
|
cacheDirectory,
|
|
698
590
|
fileName(collection),
|
|
699
591
|
fileName(file)
|
|
@@ -712,7 +604,7 @@ async function createCacheManager(baseDirectory, configChecksum) {
|
|
|
712
604
|
const cacheFn = async (input, fn) => {
|
|
713
605
|
const key = createKey(configChecksum, input);
|
|
714
606
|
newFileMapping.push(key);
|
|
715
|
-
const filePath =
|
|
607
|
+
const filePath = join2(directory, `${key}.cache`);
|
|
716
608
|
if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
|
|
717
609
|
if (existsSync2(filePath)) {
|
|
718
610
|
try {
|
|
@@ -734,7 +626,7 @@ async function createCacheManager(baseDirectory, configChecksum) {
|
|
|
734
626
|
const tidyUp = async () => {
|
|
735
627
|
const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
|
|
736
628
|
for (const key of filesToDelete) {
|
|
737
|
-
const filePath =
|
|
629
|
+
const filePath = join2(directory, `${key}.cache`);
|
|
738
630
|
if (existsSync2(filePath)) {
|
|
739
631
|
await unlink(filePath);
|
|
740
632
|
}
|
|
@@ -754,67 +646,337 @@ async function createCacheManager(baseDirectory, configChecksum) {
|
|
|
754
646
|
};
|
|
755
647
|
}
|
|
756
648
|
|
|
757
|
-
// src/
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
649
|
+
// src/transformer.ts
|
|
650
|
+
import { basename, dirname as dirname3, extname } from "path";
|
|
651
|
+
import { z as z3 } from "zod";
|
|
652
|
+
import os from "os";
|
|
653
|
+
import pLimit from "p-limit";
|
|
654
|
+
var TransformError = class extends Error {
|
|
655
|
+
type;
|
|
656
|
+
constructor(type, message) {
|
|
657
|
+
super(message);
|
|
658
|
+
this.type = type;
|
|
761
659
|
}
|
|
762
|
-
|
|
660
|
+
};
|
|
661
|
+
function createPath(path8, ext) {
|
|
662
|
+
let p = path8.slice(0, -ext.length);
|
|
663
|
+
if (p.endsWith("/index")) {
|
|
664
|
+
p = p.slice(0, -6);
|
|
665
|
+
}
|
|
666
|
+
return p;
|
|
763
667
|
}
|
|
764
|
-
|
|
765
|
-
|
|
668
|
+
function createTransformer(emitter, cacheManager) {
|
|
669
|
+
function createSchema(parserName, schema2) {
|
|
670
|
+
const parser = parsers[parserName];
|
|
671
|
+
if (!parser.hasContent) {
|
|
672
|
+
return z3.object(schema2);
|
|
673
|
+
}
|
|
674
|
+
return z3.object({
|
|
675
|
+
content: z3.string(),
|
|
676
|
+
...schema2
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
async function parseFile(collection, file) {
|
|
680
|
+
const { data, path: path8 } = file;
|
|
681
|
+
const schema2 = createSchema(collection.parser, collection.schema);
|
|
682
|
+
let parsedData = await schema2.safeParseAsync(data);
|
|
683
|
+
if (!parsedData.success) {
|
|
684
|
+
emitter.emit("transformer:validation-error", {
|
|
685
|
+
collection,
|
|
686
|
+
file,
|
|
687
|
+
error: new TransformError("Validation", parsedData.error.message)
|
|
688
|
+
});
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const ext = extname(path8);
|
|
692
|
+
let extension2 = ext;
|
|
693
|
+
if (extension2.startsWith(".")) {
|
|
694
|
+
extension2 = extension2.slice(1);
|
|
695
|
+
}
|
|
696
|
+
const document = {
|
|
697
|
+
...parsedData.data,
|
|
698
|
+
_meta: {
|
|
699
|
+
filePath: path8,
|
|
700
|
+
fileName: basename(path8),
|
|
701
|
+
directory: dirname3(path8),
|
|
702
|
+
extension: extension2,
|
|
703
|
+
path: createPath(path8, ext)
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
return {
|
|
707
|
+
document
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
async function parseCollection(collection) {
|
|
711
|
+
const promises = collection.files.map(
|
|
712
|
+
(file) => parseFile(collection, file)
|
|
713
|
+
);
|
|
714
|
+
return {
|
|
715
|
+
...collection,
|
|
716
|
+
documents: (await Promise.all(promises)).filter(isDefined)
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function createContext(collections, collection, cache) {
|
|
720
|
+
return {
|
|
721
|
+
documents: (collection2) => {
|
|
722
|
+
const resolved = collections.find((c) => c.name === collection2.name);
|
|
723
|
+
if (!resolved) {
|
|
724
|
+
throw new TransformError(
|
|
725
|
+
"Configuration",
|
|
726
|
+
`Collection ${collection2.name} not found, do you have registered it in your configuration?`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
return resolved.documents.map((doc) => doc.document);
|
|
730
|
+
},
|
|
731
|
+
collection: {
|
|
732
|
+
name: collection.name,
|
|
733
|
+
directory: collection.directory,
|
|
734
|
+
documents: async () => {
|
|
735
|
+
return collection.documents.map((doc) => doc.document);
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
cache: cache.cacheFn
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
async function transformDocument(collections, collection, transform, doc) {
|
|
742
|
+
const cache = cacheManager.cache(collection.name, doc.document._meta.path);
|
|
743
|
+
const context = createContext(collections, collection, cache);
|
|
744
|
+
try {
|
|
745
|
+
const document = await transform(doc.document, context);
|
|
746
|
+
await cache.tidyUp();
|
|
747
|
+
return {
|
|
748
|
+
...doc,
|
|
749
|
+
document
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
if (error instanceof TransformError) {
|
|
753
|
+
emitter.emit("transformer:error", {
|
|
754
|
+
collection,
|
|
755
|
+
error
|
|
756
|
+
});
|
|
757
|
+
} else {
|
|
758
|
+
emitter.emit("transformer:error", {
|
|
759
|
+
collection,
|
|
760
|
+
error: new TransformError("Transform", String(error))
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
async function transformCollection(collections, collection) {
|
|
766
|
+
const transform = collection.transform;
|
|
767
|
+
if (transform) {
|
|
768
|
+
const limit = pLimit(os.cpus().length);
|
|
769
|
+
const docs = collection.documents.map(
|
|
770
|
+
(doc) => limit(() => transformDocument(collections, collection, transform, doc))
|
|
771
|
+
);
|
|
772
|
+
const transformed = await Promise.all(docs);
|
|
773
|
+
await cacheManager.flush();
|
|
774
|
+
return transformed.filter(isDefined);
|
|
775
|
+
}
|
|
776
|
+
return collection.documents;
|
|
777
|
+
}
|
|
778
|
+
async function validateDocuments(collection, documents) {
|
|
779
|
+
const docs = [];
|
|
780
|
+
for (const doc of documents) {
|
|
781
|
+
let parsedData = await serializableSchema.safeParseAsync(doc.document);
|
|
782
|
+
if (parsedData.success) {
|
|
783
|
+
docs.push(doc);
|
|
784
|
+
} else {
|
|
785
|
+
emitter.emit("transformer:result-error", {
|
|
786
|
+
collection,
|
|
787
|
+
document: doc.document,
|
|
788
|
+
error: new TransformError("Result", parsedData.error.message)
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return docs;
|
|
793
|
+
}
|
|
794
|
+
return async (untransformedCollections) => {
|
|
795
|
+
const promises = untransformedCollections.map(
|
|
796
|
+
(collection) => parseCollection(collection)
|
|
797
|
+
);
|
|
798
|
+
const collections = await Promise.all(promises);
|
|
799
|
+
for (const collection of collections) {
|
|
800
|
+
const documents = await transformCollection(collections, collection);
|
|
801
|
+
collection.documents = await validateDocuments(collection, documents);
|
|
802
|
+
}
|
|
803
|
+
return collections;
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/build.ts
|
|
808
|
+
async function createBuildContext({
|
|
809
|
+
emitter,
|
|
810
|
+
outputDirectory,
|
|
811
|
+
baseDirectory,
|
|
812
|
+
configuration
|
|
766
813
|
}) {
|
|
767
|
-
const emitter = createEmitter();
|
|
768
|
-
const readConfiguration = createConfigurationReader();
|
|
769
|
-
const configuration = await readConfiguration(configurationPath, options);
|
|
770
|
-
const baseDirectory = path6.dirname(configurationPath);
|
|
771
|
-
const directory = resolveOutputDir(baseDirectory, options);
|
|
772
814
|
const collector = createCollector(emitter, baseDirectory);
|
|
773
|
-
const writer = await
|
|
774
|
-
|
|
815
|
+
const [writer, resolved, cacheManager] = await Promise.all([
|
|
816
|
+
createWriter(outputDirectory),
|
|
775
817
|
collector.collect(configuration.collections),
|
|
776
|
-
|
|
777
|
-
writer.createTypeDefinitionFile(configuration)
|
|
818
|
+
createCacheManager(baseDirectory, configuration.checksum)
|
|
778
819
|
]);
|
|
779
820
|
const synchronizer = createSynchronizer(
|
|
780
821
|
collector.collectFile,
|
|
781
822
|
resolved,
|
|
782
823
|
baseDirectory
|
|
783
824
|
);
|
|
784
|
-
const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
|
|
785
825
|
const transform = createTransformer(emitter, cacheManager);
|
|
826
|
+
return {
|
|
827
|
+
resolved,
|
|
828
|
+
writer,
|
|
829
|
+
synchronizer,
|
|
830
|
+
transform,
|
|
831
|
+
emitter,
|
|
832
|
+
cacheManager,
|
|
833
|
+
configuration
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
async function build2({
|
|
837
|
+
emitter,
|
|
838
|
+
transform,
|
|
839
|
+
resolved,
|
|
840
|
+
writer,
|
|
841
|
+
configuration
|
|
842
|
+
}) {
|
|
843
|
+
const startedAt = Date.now();
|
|
844
|
+
emitter.emit("builder:start", {
|
|
845
|
+
startedAt
|
|
846
|
+
});
|
|
847
|
+
const collections = await transform(resolved);
|
|
848
|
+
await Promise.all([
|
|
849
|
+
writer.createDataFiles(collections),
|
|
850
|
+
writer.createTypeDefinitionFile(configuration),
|
|
851
|
+
writer.createJavaScriptFile(configuration)
|
|
852
|
+
]);
|
|
853
|
+
const pendingOnSuccess = collections.filter((collection) => Boolean(collection.onSuccess)).map(
|
|
854
|
+
(collection) => collection.onSuccess?.(collection.documents.map((doc) => doc.document))
|
|
855
|
+
);
|
|
856
|
+
await Promise.all(pendingOnSuccess.filter(isDefined));
|
|
857
|
+
const stats = collections.reduce(
|
|
858
|
+
(acc, collection) => {
|
|
859
|
+
acc.collections++;
|
|
860
|
+
acc.documents += collection.documents.length;
|
|
861
|
+
return acc;
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
collections: 0,
|
|
865
|
+
documents: 0
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
emitter.emit("builder:end", {
|
|
869
|
+
startedAt,
|
|
870
|
+
endedAt: Date.now(),
|
|
871
|
+
stats
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/builder.ts
|
|
876
|
+
function resolveOutputDir(baseDirectory, options) {
|
|
877
|
+
if (options.outputDir) {
|
|
878
|
+
return options.outputDir;
|
|
879
|
+
}
|
|
880
|
+
return path7.join(baseDirectory, ".content-collections", "generated");
|
|
881
|
+
}
|
|
882
|
+
var ConfigurationReloadError = class extends Error {
|
|
883
|
+
constructor(message) {
|
|
884
|
+
super(message);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
async function createBuilder(configurationPath, options = {
|
|
888
|
+
configName: defaultConfigName
|
|
889
|
+
}, emitter = createEmitter()) {
|
|
890
|
+
const readConfiguration = createConfigurationReader();
|
|
891
|
+
const baseDirectory = path7.dirname(configurationPath);
|
|
892
|
+
const outputDirectory = resolveOutputDir(baseDirectory, options);
|
|
893
|
+
emitter.emit("builder:created", {
|
|
894
|
+
createdAt: Date.now(),
|
|
895
|
+
configurationPath,
|
|
896
|
+
outputDirectory
|
|
897
|
+
});
|
|
898
|
+
let configuration = await readConfiguration(configurationPath, options);
|
|
899
|
+
let watcher2 = null;
|
|
900
|
+
let context = await createBuildContext({
|
|
901
|
+
emitter,
|
|
902
|
+
baseDirectory,
|
|
903
|
+
outputDirectory,
|
|
904
|
+
configuration
|
|
905
|
+
});
|
|
786
906
|
async function sync(modification, filePath) {
|
|
787
|
-
if (
|
|
788
|
-
|
|
907
|
+
if (configuration.inputPaths.includes(filePath)) {
|
|
908
|
+
if (await onConfigurationChange()) {
|
|
909
|
+
emitter.emit("watcher:config-changed", {
|
|
910
|
+
filePath,
|
|
911
|
+
modification
|
|
912
|
+
});
|
|
913
|
+
await build2(context);
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
if (await onFileChange(modification, filePath)) {
|
|
918
|
+
emitter.emit("watcher:file-changed", {
|
|
919
|
+
filePath,
|
|
920
|
+
modification
|
|
921
|
+
});
|
|
922
|
+
await build2(context);
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
789
925
|
}
|
|
790
|
-
return
|
|
926
|
+
return false;
|
|
791
927
|
}
|
|
792
|
-
async function
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
928
|
+
async function onConfigurationChange() {
|
|
929
|
+
try {
|
|
930
|
+
configuration = await readConfiguration(configurationPath, options);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
emitter.emit("watcher:config-reload-error", {
|
|
933
|
+
error: new ConfigurationReloadError(
|
|
934
|
+
`Failed to reload configuration: ${error}`
|
|
935
|
+
),
|
|
936
|
+
configurationPath
|
|
937
|
+
});
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
if (watcher2) {
|
|
941
|
+
await watcher2.unsubscribe();
|
|
942
|
+
}
|
|
943
|
+
context = await createBuildContext({
|
|
944
|
+
emitter,
|
|
945
|
+
baseDirectory,
|
|
946
|
+
outputDirectory,
|
|
947
|
+
configuration
|
|
806
948
|
});
|
|
949
|
+
if (watcher2) {
|
|
950
|
+
watcher2 = await createWatcher(
|
|
951
|
+
emitter,
|
|
952
|
+
baseDirectory,
|
|
953
|
+
configuration,
|
|
954
|
+
sync
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
async function onFileChange(modification, filePath) {
|
|
960
|
+
const { synchronizer } = context;
|
|
961
|
+
if (modification === "delete") {
|
|
962
|
+
return synchronizer.deleted(filePath);
|
|
963
|
+
} else {
|
|
964
|
+
return synchronizer.changed(filePath);
|
|
965
|
+
}
|
|
807
966
|
}
|
|
808
967
|
async function watch() {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
968
|
+
watcher2 = await createWatcher(emitter, baseDirectory, configuration, sync);
|
|
969
|
+
return {
|
|
970
|
+
unsubscribe: async () => {
|
|
971
|
+
if (watcher2) {
|
|
972
|
+
await watcher2.unsubscribe();
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
};
|
|
814
976
|
}
|
|
815
977
|
return {
|
|
978
|
+
build: () => build2(context),
|
|
816
979
|
sync,
|
|
817
|
-
build: build2,
|
|
818
980
|
watch,
|
|
819
981
|
on: emitter.on
|
|
820
982
|
};
|
|
@@ -822,6 +984,7 @@ async function createBuilder(configurationPath, options = {
|
|
|
822
984
|
export {
|
|
823
985
|
CollectError,
|
|
824
986
|
ConfigurationError,
|
|
987
|
+
ConfigurationReloadError,
|
|
825
988
|
TransformError,
|
|
826
989
|
createBuilder,
|
|
827
990
|
defineCollection,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@content-collections/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -19,27 +19,29 @@
|
|
|
19
19
|
"typescript": "^5.0.2"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@types/micromatch": "^4.0.7",
|
|
23
22
|
"@types/node": "^20.14.9",
|
|
23
|
+
"@types/picomatch": "^3.0.1",
|
|
24
24
|
"@types/pluralize": "^0.0.33",
|
|
25
25
|
"@types/serialize-javascript": "^5.0.4",
|
|
26
|
-
"@vitest/coverage-v8": "^
|
|
26
|
+
"@vitest/coverage-v8": "^2.0.5",
|
|
27
27
|
"tsup": "^8.0.2",
|
|
28
28
|
"tsx": "^4.1.1",
|
|
29
|
-
"typescript": "^5.4
|
|
30
|
-
"vitest": "^
|
|
29
|
+
"typescript": "^5.5.4",
|
|
30
|
+
"vitest": "^2.0.5"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@parcel/watcher": "^2.4.1",
|
|
34
|
+
"bundle-require": "^5.0.0",
|
|
34
35
|
"camelcase": "^8.0.0",
|
|
35
36
|
"esbuild": "^0.21.4",
|
|
36
|
-
"fast-glob": "^3.3.2",
|
|
37
37
|
"gray-matter": "^4.0.3",
|
|
38
|
-
"
|
|
38
|
+
"p-limit": "^6.1.0",
|
|
39
|
+
"picomatch": "^4.0.2",
|
|
39
40
|
"pluralize": "^8.0.0",
|
|
40
41
|
"serialize-javascript": "^6.0.2",
|
|
42
|
+
"tinyglobby": "^0.2.5",
|
|
41
43
|
"yaml": "^2.4.5",
|
|
42
|
-
"zod": "^3.
|
|
44
|
+
"zod": "^3.23.8"
|
|
43
45
|
},
|
|
44
46
|
"scripts": {
|
|
45
47
|
"build": "tsup src/index.ts --format esm --dts -d dist",
|