@acdh-oeaw/content-lib 0.0.1 → 0.1.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/index.d.ts +43 -11
- package/dist/index.js +69 -33
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,44 +15,76 @@ interface CollectionItem {
|
|
|
15
15
|
timestamp: number;
|
|
16
16
|
}
|
|
17
17
|
interface TransformContext {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
collections: Array<Collection>;
|
|
19
|
+
createImportDeclaration: <T>(path: string) => ImportDeclaration<T>;
|
|
20
|
+
createJavaScriptImport: <T>(content: string) => JavaScriptImport<T>;
|
|
21
|
+
createJsonImport: <T>(content: string) => JsonImport<T>;
|
|
21
22
|
}
|
|
22
|
-
interface CollectionConfig<TCollectionItemContent = any, TCollectionDocument = any> {
|
|
23
|
-
name:
|
|
23
|
+
interface CollectionConfig<TCollectionName extends string = string, TCollectionItemContent = any, TCollectionDocument = any> {
|
|
24
|
+
name: TCollectionName;
|
|
24
25
|
directory: string;
|
|
25
26
|
include: NonEmptyReadonlyArray<GlobString>;
|
|
26
27
|
exclude?: ReadonlyArray<GlobString>;
|
|
27
28
|
read: (item: CollectionItem) => MaybePromise<TCollectionItemContent>;
|
|
28
29
|
transform: (content: TCollectionItemContent, item: CollectionItem, context: TransformContext) => MaybePromise<TCollectionDocument>;
|
|
29
30
|
}
|
|
30
|
-
declare function createCollection<TCollectionItemContent, TCollectionDocument>(config: CollectionConfig<TCollectionItemContent, TCollectionDocument>): CollectionConfig<TCollectionItemContent, TCollectionDocument>;
|
|
31
|
+
declare function createCollection<TCollectionName extends string, TCollectionItemContent, TCollectionDocument>(config: CollectionConfig<TCollectionName, TCollectionItemContent, TCollectionDocument>): CollectionConfig<TCollectionName, TCollectionItemContent, TCollectionDocument>;
|
|
31
32
|
interface ContentConfig {
|
|
32
33
|
collections: Array<CollectionConfig>;
|
|
33
34
|
}
|
|
34
35
|
declare function createConfig<T extends ContentConfig>(config: T): T;
|
|
35
|
-
declare class ImportDeclaration {
|
|
36
|
+
declare class ImportDeclaration<T> {
|
|
37
|
+
private __brand;
|
|
38
|
+
value: T;
|
|
36
39
|
path: string;
|
|
37
40
|
constructor(path: string);
|
|
38
41
|
}
|
|
39
|
-
declare class JavaScriptImport {
|
|
42
|
+
declare class JavaScriptImport<T> {
|
|
43
|
+
private __brand;
|
|
44
|
+
value: T;
|
|
40
45
|
content: string;
|
|
41
46
|
constructor(content: string);
|
|
42
47
|
}
|
|
43
|
-
declare class JsonImport {
|
|
48
|
+
declare class JsonImport<T> {
|
|
49
|
+
private __brand;
|
|
50
|
+
value: T;
|
|
44
51
|
content: string;
|
|
45
52
|
constructor(content: string);
|
|
46
53
|
}
|
|
54
|
+
type GetCollection<TConfig extends ContentConfig, TName extends string> = Extract<TConfig["collections"][number], {
|
|
55
|
+
name: TName;
|
|
56
|
+
}>;
|
|
57
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
58
|
+
type Replace<T> = { [K in keyof T]: T[K] extends JavaScriptImport<infer U> ? U : T[K] extends JsonImport<infer U> ? U : T[K] extends ImportDeclaration<infer U> ? U : T[K] extends object ? Simplify<Replace<T[K]>> : T[K] };
|
|
59
|
+
type CollectionEntry<TCollection extends Collection> = Simplify<{
|
|
60
|
+
item: {
|
|
61
|
+
id: string;
|
|
62
|
+
};
|
|
63
|
+
content: Simplify<Awaited<ReturnType<TCollection["read"]>>>;
|
|
64
|
+
document: Simplify<Replace<Awaited<ReturnType<TCollection["transform"]>>>>;
|
|
65
|
+
}>;
|
|
66
|
+
interface Collection extends CollectionConfig {
|
|
67
|
+
absoluteDirectoryPath: string;
|
|
68
|
+
data: Map<CollectionItem["id"], {
|
|
69
|
+
item: CollectionItem;
|
|
70
|
+
content: any;
|
|
71
|
+
document: any;
|
|
72
|
+
}>;
|
|
73
|
+
outputDirectoryPath: string;
|
|
74
|
+
}
|
|
47
75
|
interface BuildStats {
|
|
48
76
|
collections: number;
|
|
49
77
|
documents: number;
|
|
50
78
|
}
|
|
79
|
+
interface ContentProcessorConfig {
|
|
80
|
+
/** Path to config file, relative to `process.cwd()`, which provides a named export `config`. */
|
|
81
|
+
configFilePath: string;
|
|
82
|
+
}
|
|
51
83
|
interface ContentProcessor {
|
|
52
84
|
build: () => Promise<BuildStats>;
|
|
53
85
|
watch: () => Promise<Set<watcher.AsyncSubscription>>;
|
|
54
86
|
}
|
|
55
|
-
declare function createContentProcessor(
|
|
87
|
+
declare function createContentProcessor(contentProcessorConfig: ContentProcessorConfig): Promise<ContentProcessor>;
|
|
56
88
|
//#endregion
|
|
57
|
-
export { CollectionConfig, ContentConfig, ContentProcessor, createCollection, createConfig, createContentProcessor };
|
|
89
|
+
export { CollectionConfig, CollectionEntry, ContentConfig, ContentProcessor, ContentProcessorConfig, GetCollection, createCollection, createConfig, createContentProcessor };
|
|
58
90
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import { availableParallelism } from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
4
6
|
import { debuglog } from "node:util";
|
|
5
|
-
import { addTrailingSlash, log } from "@acdh-oeaw/lib";
|
|
7
|
+
import { addTrailingSlash, capitalize, log } from "@acdh-oeaw/lib";
|
|
6
8
|
import * as watcher from "@parcel/watcher";
|
|
9
|
+
import plimit from "p-limit";
|
|
10
|
+
import pluralize from "pluralize";
|
|
7
11
|
|
|
8
12
|
//#region src/index.ts
|
|
9
13
|
const debug = debuglog("content-lib");
|
|
@@ -22,6 +26,8 @@ function createConfig(config) {
|
|
|
22
26
|
return config;
|
|
23
27
|
}
|
|
24
28
|
var ImportDeclaration = class {
|
|
29
|
+
__brand;
|
|
30
|
+
value;
|
|
25
31
|
path;
|
|
26
32
|
constructor(path$1) {
|
|
27
33
|
this.path = path$1;
|
|
@@ -31,6 +37,8 @@ function createImportDeclaration(path$1) {
|
|
|
31
37
|
return new ImportDeclaration(path$1);
|
|
32
38
|
}
|
|
33
39
|
var JavaScriptImport = class {
|
|
40
|
+
__brand;
|
|
41
|
+
value;
|
|
34
42
|
content;
|
|
35
43
|
constructor(content) {
|
|
36
44
|
this.content = content;
|
|
@@ -40,6 +48,8 @@ function createJavaScriptImport(content) {
|
|
|
40
48
|
return new JavaScriptImport(content);
|
|
41
49
|
}
|
|
42
50
|
var JsonImport = class {
|
|
51
|
+
__brand;
|
|
52
|
+
value;
|
|
43
53
|
content;
|
|
44
54
|
constructor(content) {
|
|
45
55
|
this.content = content;
|
|
@@ -50,7 +60,7 @@ function createJsonImport(content) {
|
|
|
50
60
|
}
|
|
51
61
|
const prefix = "__i__";
|
|
52
62
|
const re = new RegExp(`"(${prefix}\\d+)"`, "g");
|
|
53
|
-
function serialize(value) {
|
|
63
|
+
function serialize(value, contentProcessorConfigFilePath, collectionName) {
|
|
54
64
|
debug("Serializing...\n");
|
|
55
65
|
const imports = [];
|
|
56
66
|
function addImport(filePath, type = "js") {
|
|
@@ -74,7 +84,7 @@ function serialize(value) {
|
|
|
74
84
|
const filePath = `./${hash}.js`;
|
|
75
85
|
debug(`Adding javascript import for "${filePath}".`);
|
|
76
86
|
const identifier = addImport(filePath);
|
|
77
|
-
addFiles(filePath, value$1.content);
|
|
87
|
+
addFiles(filePath, `// @ts-nocheck\n${value$1.content}`);
|
|
78
88
|
return identifier;
|
|
79
89
|
}
|
|
80
90
|
if (value$1 instanceof JsonImport) {
|
|
@@ -92,27 +102,46 @@ function serialize(value) {
|
|
|
92
102
|
result += imports.join("\n");
|
|
93
103
|
result += "\n\n";
|
|
94
104
|
}
|
|
95
|
-
result += `const items = new Map(${json})
|
|
96
|
-
|
|
105
|
+
result += [`const items = new Map(${json});`, "export default items;"].join("\n\n");
|
|
106
|
+
files.set("index.js", result);
|
|
107
|
+
const typeName = capitalize(pluralize.singular(collectionName));
|
|
108
|
+
files.set("index.d.ts", [
|
|
109
|
+
`import type { GetCollection, CollectionEntry } from "@acdh-oeaw/content-lib";`,
|
|
110
|
+
"",
|
|
111
|
+
`import type { config } from "${contentProcessorConfigFilePath}";`,
|
|
112
|
+
"",
|
|
113
|
+
`type Collection = GetCollection<typeof config, "${collectionName}">;`,
|
|
114
|
+
`type ${typeName} = CollectionEntry<Collection>;`,
|
|
115
|
+
"",
|
|
116
|
+
`declare const items: Map<string, ${typeName}>;`,
|
|
117
|
+
`export { type ${typeName}, items as default };`
|
|
118
|
+
].join("\n"));
|
|
119
|
+
return files;
|
|
97
120
|
}
|
|
98
|
-
async function createContentProcessor(
|
|
121
|
+
async function createContentProcessor(contentProcessorConfig) {
|
|
99
122
|
debug("Creating content processor...\n");
|
|
123
|
+
debug("Reading config file...");
|
|
124
|
+
const contentProcessorConfigUrl = pathToFileURL(path.resolve(contentProcessorConfig.configFilePath));
|
|
125
|
+
contentProcessorConfigUrl.searchParams.set("cache-key", String(Date.now()));
|
|
126
|
+
const contentProcessorConfigFilePath = String(contentProcessorConfigUrl);
|
|
127
|
+
const { config } = await import(contentProcessorConfigFilePath);
|
|
128
|
+
debug(`Done reading config file "${contentProcessorConfigFilePath}".`);
|
|
129
|
+
const concurrency = availableParallelism();
|
|
130
|
+
const limit = plimit(concurrency);
|
|
131
|
+
debug(`Concurrency: ${String(concurrency)}.\n`);
|
|
100
132
|
const outputDirectoryBasePath = path.join(process.cwd(), ".content", "generated");
|
|
101
|
-
debug("Clearing output directory...\n");
|
|
102
|
-
await fs.rm(outputDirectoryBasePath, {
|
|
103
|
-
force: true,
|
|
104
|
-
recursive: true
|
|
105
|
-
});
|
|
106
133
|
const collections = [];
|
|
107
134
|
const context = {
|
|
135
|
+
collections,
|
|
108
136
|
createImportDeclaration,
|
|
109
137
|
createJavaScriptImport,
|
|
110
138
|
createJsonImport
|
|
111
139
|
};
|
|
112
140
|
for (const collection of config.collections) {
|
|
113
141
|
const absoluteDirectoryPath = addTrailingSlash(path.resolve(collection.directory));
|
|
114
|
-
|
|
115
|
-
await fs.mkdir(
|
|
142
|
+
/** Ensure directory exists, which is expected by `@parcel/watcher`. */
|
|
143
|
+
await fs.mkdir(absoluteDirectoryPath, { recursive: true });
|
|
144
|
+
const outputDirectoryPath = path.join(outputDirectoryBasePath, collection.name.toLowerCase().replaceAll(/[^a-z0-9_-]/g, "-"));
|
|
116
145
|
collections.push({
|
|
117
146
|
...collection,
|
|
118
147
|
absoluteDirectoryPath,
|
|
@@ -124,43 +153,50 @@ async function createContentProcessor(config) {
|
|
|
124
153
|
debug("Generating...\n");
|
|
125
154
|
for (const collection of collections) {
|
|
126
155
|
debug(`Reading collection "${collection.name}"...`);
|
|
127
|
-
|
|
156
|
+
await limit.map(Array.from(collection.data), async ([id, { item }]) => {
|
|
128
157
|
if (signal?.aborted === true) {
|
|
129
|
-
debug("Aborted
|
|
158
|
+
debug("Aborted reading collections.");
|
|
159
|
+
limit.clearQueue();
|
|
130
160
|
return;
|
|
131
161
|
}
|
|
132
162
|
const content = await collection.read(item);
|
|
133
163
|
collection.data.get(id).content = content;
|
|
134
|
-
debug(`- Read item "${id}"
|
|
135
|
-
}
|
|
164
|
+
debug(`- Read item "${id}".`);
|
|
165
|
+
});
|
|
136
166
|
debug(`Done reading ${String(collection.data.size)} item(s) in collection "${collection.name}".\n`);
|
|
137
167
|
}
|
|
138
168
|
for (const collection of collections) {
|
|
139
169
|
debug(`Transforming collection "${collection.name}"...`);
|
|
140
|
-
|
|
170
|
+
await limit.map(Array.from(collection.data), async ([id, { content, item }]) => {
|
|
141
171
|
if (signal?.aborted === true) {
|
|
142
|
-
debug("Aborted transforming collections
|
|
172
|
+
debug("Aborted transforming collections.");
|
|
173
|
+
limit.clearQueue();
|
|
143
174
|
return;
|
|
144
175
|
}
|
|
145
176
|
const document = await collection.transform(content, item, context);
|
|
146
177
|
collection.data.get(id).document = document;
|
|
147
|
-
debug(`- Transformed item "${id}"
|
|
148
|
-
}
|
|
178
|
+
debug(`- Transformed item "${id}".`);
|
|
179
|
+
});
|
|
149
180
|
debug(`Done transforming ${String(collection.data.size)} item(s) in collection "${collection.name}".\n`);
|
|
150
181
|
}
|
|
151
182
|
if (signal?.aborted === true) {
|
|
152
|
-
debug("Aborted writing collections
|
|
183
|
+
debug("Aborted writing collections.");
|
|
153
184
|
return;
|
|
154
185
|
}
|
|
186
|
+
debug("Clearing output directory...\n");
|
|
187
|
+
await fs.rm(outputDirectoryBasePath, {
|
|
188
|
+
force: true,
|
|
189
|
+
recursive: true
|
|
190
|
+
});
|
|
155
191
|
for (const collection of collections) {
|
|
156
|
-
debug(`Writing collection "${collection.name}"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const outputFilePath
|
|
162
|
-
await fs.writeFile(outputFilePath
|
|
163
|
-
}
|
|
192
|
+
debug(`Writing collection "${collection.name}".`);
|
|
193
|
+
debug(`Creating output directory for "${collection.name}".`);
|
|
194
|
+
await fs.mkdir(collection.outputDirectoryPath, { recursive: true });
|
|
195
|
+
const files = serialize(collection.data, path.relative(collection.outputDirectoryPath, contentProcessorConfig.configFilePath), collection.name);
|
|
196
|
+
await limit.map(Array.from(files), async ([filePath, fileContent]) => {
|
|
197
|
+
const outputFilePath = path.join(collection.outputDirectoryPath, filePath);
|
|
198
|
+
await fs.writeFile(outputFilePath, fileContent, { encoding: "utf-8" });
|
|
199
|
+
});
|
|
164
200
|
}
|
|
165
201
|
}
|
|
166
202
|
async function build() {
|
|
@@ -190,7 +226,7 @@ async function createContentProcessor(config) {
|
|
|
190
226
|
content: null,
|
|
191
227
|
document: null
|
|
192
228
|
});
|
|
193
|
-
debug(`- Added item "${id}" (path: "${filePath}")
|
|
229
|
+
debug(`- Added item "${id}" (path: "${filePath}").`);
|
|
194
230
|
}
|
|
195
231
|
debug(`Done adding ${String(collection.data.size)} item(s) to collection "${collection.name}".\n`);
|
|
196
232
|
}
|
|
@@ -228,7 +264,7 @@ async function createContentProcessor(config) {
|
|
|
228
264
|
log.error(error);
|
|
229
265
|
return;
|
|
230
266
|
}
|
|
231
|
-
debug(`- ${String(events.length)} events in collection "${collection.name}"
|
|
267
|
+
debug(`- ${String(events.length)} events in collection "${collection.name}".`);
|
|
232
268
|
for (const event of events) {
|
|
233
269
|
const relativeFilePath = event.path.slice(collection.absoluteDirectoryPath.length);
|
|
234
270
|
if (collection.include.some((pattern) => {
|
|
@@ -287,7 +323,7 @@ async function createContentProcessor(config) {
|
|
|
287
323
|
subscriptions.add(subscription);
|
|
288
324
|
}
|
|
289
325
|
async function unsubscribe() {
|
|
290
|
-
debug("Cleaning up
|
|
326
|
+
debug("Cleaning up...");
|
|
291
327
|
if (timer != null) clearTimeout(timer);
|
|
292
328
|
timer = null;
|
|
293
329
|
if (controller != null) controller.abort();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["value: string","filePath: string","config: CollectionConfig<TCollectionItemContent, TCollectionDocument>","config: T","path: string","path","content: string","value: Map<string, any>","imports: Array<string>","type: \"js\" | \"json\"","value","config: ContentConfig","collections: Array<Collection>","context: TransformContext","signal?: AbortSignal","outputFilePath","error: unknown","item: CollectionItem","timer: ReturnType<typeof setTimeout> | null","controller: AbortController | null","events","error"],"sources":["../src/index.ts"],"sourcesContent":["import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { debuglog } from \"node:util\";\n\nimport { addTrailingSlash, log } from \"@acdh-oeaw/lib\";\nimport * as watcher from \"@parcel/watcher\";\n\n//\n\nconst debug = debuglog(\"content-lib\");\n\n//\n\nfunction createContentHash(value: string): string {\n\treturn crypto.createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction createIdFromFilePath(filePath: string): string {\n\tconst parsed = path.parse(filePath);\n\n\tif (parsed.name.toLowerCase() === \"index\") {\n\t\treturn path.basename(parsed.dir);\n\t}\n\n\treturn parsed.name;\n}\n\n//\n\ntype MaybePromise<T> = T | Promise<T>;\n\ntype NonEmptyReadonlyArray<T> = readonly [T, ...Array<T>];\n\ntype GlobString = string;\n\ninterface CollectionItem {\n\t/** Unique identifier. */\n\tid: string;\n\t/** File path relative to colleciton directory. */\n\tfilePath: string;\n\t/** File path relative to current working directory. */\n\tabsoluteFilePath: string;\n\t/** File modification timestamp. */\n\ttimestamp: number;\n}\n\ninterface TransformContext {\n\tcreateImportDeclaration: (path: string) => ImportDeclaration;\n\tcreateJavaScriptImport: (content: string) => JavaScriptImport;\n\tcreateJsonImport: (content: string) => JsonImport;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface CollectionConfig<TCollectionItemContent = any, TCollectionDocument = any> {\n\tname: string;\n\tdirectory: string;\n\tinclude: NonEmptyReadonlyArray<GlobString>;\n\texclude?: ReadonlyArray<GlobString>;\n\tread: (item: CollectionItem) => MaybePromise<TCollectionItemContent>;\n\ttransform: (\n\t\tcontent: TCollectionItemContent,\n\t\titem: CollectionItem,\n\t\tcontext: TransformContext,\n\t) => MaybePromise<TCollectionDocument>;\n}\n\nexport function createCollection<TCollectionItemContent, TCollectionDocument>(\n\tconfig: CollectionConfig<TCollectionItemContent, TCollectionDocument>,\n): CollectionConfig<TCollectionItemContent, TCollectionDocument> {\n\treturn config;\n}\n\nexport interface ContentConfig {\n\tcollections: Array<CollectionConfig>;\n}\n\nexport function createConfig<T extends ContentConfig>(config: T): T {\n\treturn config;\n}\n\n//\n\n// function createItemCacheKey(item: CollectionItem): string {\n// \treturn String(item.timestamp);\n// }\n\n//\n\nclass ImportDeclaration {\n\tpath: string;\n\n\tconstructor(path: string) {\n\t\tthis.path = path;\n\t}\n}\n\nfunction createImportDeclaration(path: string): ImportDeclaration {\n\treturn new ImportDeclaration(path);\n}\n\nclass JavaScriptImport {\n\tcontent: string;\n\n\tconstructor(content: string) {\n\t\tthis.content = content;\n\t}\n}\n\nfunction createJavaScriptImport(content: string): JavaScriptImport {\n\treturn new JavaScriptImport(content);\n}\n\nclass JsonImport {\n\tcontent: string;\n\n\tconstructor(content: string) {\n\t\tthis.content = content;\n\t}\n}\n\nfunction createJsonImport(content: string): JsonImport {\n\treturn new JsonImport(content);\n}\n\n//\n\nconst prefix = \"__i__\";\nconst re = new RegExp(`\"(${prefix}\\\\d+)\"`, \"g\");\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction serialize(value: Map<string, any>): [string, Map<string, string>] {\n\tdebug(\"Serializing...\\n\");\n\n\tconst imports: Array<string> = [];\n\n\tfunction addImport(filePath: string, type: \"js\" | \"json\" = \"js\"): string {\n\t\tconst identifier = [prefix, imports.length].join(\"\");\n\t\timports.push(\n\t\t\t`import ${identifier} from \"${filePath}\"${type !== \"js\" ? ` with { type: \"${type}\" }` : \"\"};`,\n\t\t);\n\n\t\treturn identifier;\n\t}\n\n\tconst files = new Map<string, string>();\n\n\tfunction addFiles(filePath: string, content: string): void {\n\t\tfiles.set(filePath, content);\n\t}\n\n\tconst json = JSON.stringify(\n\t\tArray.from(value),\n\t\t// TODO: Should we support (multiple) named imports?\n\t\t(_key, value) => {\n\t\t\tif (value instanceof ImportDeclaration) {\n\t\t\t\tconst filePath = value.path;\n\n\t\t\t\tdebug(`Adding import declaration for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\tif (value instanceof JavaScriptImport) {\n\t\t\t\tconst hash = createContentHash(value.content);\n\t\t\t\tconst filePath = `./${hash}.js`;\n\n\t\t\t\tdebug(`Adding javascript import for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath);\n\t\t\t\taddFiles(filePath, value.content);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\tif (value instanceof JsonImport) {\n\t\t\t\tconst hash = createContentHash(value.content);\n\t\t\t\tconst filePath = `./${hash}.json`;\n\n\t\t\t\tdebug(`Adding json import for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath, \"json\");\n\t\t\t\taddFiles(filePath, value.content);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\treturn value;\n\t\t},\n\t\t2,\n\t)\n\t\t/** Remove quotes from import identifiers. */\n\t\t.replaceAll(re, \"$1\");\n\n\tlet result = \"\";\n\n\tif (imports.length > 0) {\n\t\tresult += imports.join(\"\\n\");\n\t\tresult += \"\\n\\n\";\n\t}\n\n\tresult += `const items = new Map(${json});\\n\\nexport default items;`;\n\n\treturn [result, files];\n}\n\n//\n\ninterface Collection extends CollectionConfig {\n\tabsoluteDirectoryPath: string;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tdata: Map<CollectionItem[\"id\"], { item: CollectionItem; content: any; document: any }>; // TODO: revisit\n\toutputDirectoryPath: string;\n}\n\ninterface BuildStats {\n\tcollections: number;\n\tdocuments: number;\n}\n\nexport interface ContentProcessor {\n\tbuild: () => Promise<BuildStats>;\n\twatch: () => Promise<Set<watcher.AsyncSubscription>>;\n}\n\nexport async function createContentProcessor(config: ContentConfig): Promise<ContentProcessor> {\n\tdebug(\"Creating content processor...\\n\");\n\n\tconst outputDirectoryBasePath = path.join(process.cwd(), \".content\", \"generated\");\n\n\tdebug(\"Clearing output directory...\\n\");\n\tawait fs.rm(outputDirectoryBasePath, { force: true, recursive: true });\n\n\tconst collections: Array<Collection> = [];\n\n\tconst context: TransformContext = {\n\t\tcreateImportDeclaration,\n\t\tcreateJavaScriptImport,\n\t\tcreateJsonImport,\n\t};\n\n\tfor (const collection of config.collections) {\n\t\tconst absoluteDirectoryPath = addTrailingSlash(path.resolve(collection.directory));\n\n\t\tconst outputDirectoryPath = path.join(outputDirectoryBasePath, collection.name);\n\t\tawait fs.mkdir(outputDirectoryPath, { recursive: true });\n\n\t\tcollections.push({\n\t\t\t...collection,\n\t\t\tabsoluteDirectoryPath,\n\t\t\tdata: new Map(),\n\t\t\toutputDirectoryPath,\n\t\t});\n\t}\n\n\tasync function generate(signal?: AbortSignal): Promise<void> {\n\t\tdebug(\"Generating...\\n\");\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Reading collection \"${collection.name}\"...`);\n\n\t\t\tfor (const [id, { item }] of collection.data) {\n\t\t\t\tif (signal?.aborted === true) {\n\t\t\t\t\tdebug(\"Aborted reeading collections...\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// FIXME: race condition: what if file has been deleted in the meantime\n\t\t\t\t// TODO: skip item when `read()` returns `null`?\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst content = await collection.read(item);\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tcollection.data.get(id)!.content = content;\n\n\t\t\t\tdebug(`- Read item \"${id}\"...`);\n\t\t\t}\n\n\t\t\tdebug(\n\t\t\t\t`Done reading ${String(collection.data.size)} item(s) in collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Transforming collection \"${collection.name}\"...`);\n\n\t\t\tfor (const [id, { content, item }] of collection.data) {\n\t\t\t\tif (signal?.aborted === true) {\n\t\t\t\t\tdebug(\"Aborted transforming collections...\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst document = await collection.transform(content, item, context);\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tcollection.data.get(id)!.document = document;\n\n\t\t\t\tdebug(`- Transformed item \"${id}\"...`);\n\t\t\t}\n\n\t\t\tdebug(\n\t\t\t\t`Done transforming ${String(collection.data.size)} item(s) in collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tif (signal?.aborted === true) {\n\t\t\tdebug(\"Aborted writing collections...\");\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Writing collection \"${collection.name}\"...`);\n\n\t\t\t// TODO: Consider combining serializing and writing to disk in one function.\n\t\t\tconst outputFilePath = path.join(collection.outputDirectoryPath, \"index.js\");\n\t\t\tconst [serialized, files] = serialize(collection.data);\n\t\t\tawait fs.writeFile(outputFilePath, serialized, { encoding: \"utf-8\" });\n\t\t\tfor (const [filePath, fileContent] of files) {\n\t\t\t\tconst outputFilePath = path.join(collection.outputDirectoryPath, filePath);\n\t\t\t\tawait fs.writeFile(outputFilePath, fileContent, { encoding: \"utf-8\" });\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function build(): Promise<BuildStats> {\n\t\tdebug(\"Building...\\n\");\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Building collection \"${collection.name}\"...`);\n\n\t\t\t// eslint-disable-next-line n/no-unsupported-features/node-builtins\n\t\t\tfor await (const filePath of fs.glob(collection.include, {\n\t\t\t\tcwd: collection.directory,\n\t\t\t\texclude: collection.exclude,\n\t\t\t})) {\n\t\t\t\tconst absoluteFilePath = path.join(collection.directory, filePath);\n\t\t\t\tconst id = createIdFromFilePath(filePath);\n\n\t\t\t\tconst stats = await fs.stat(absoluteFilePath).catch((error: unknown) => {\n\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t});\n\t\t\t\tif (stats == null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst { mtimeMs: timestamp } = stats;\n\n\t\t\t\tconst item: CollectionItem = { id, filePath, absoluteFilePath, timestamp };\n\n\t\t\t\tcollection.data.set(id, { item, content: null, document: null });\n\n\t\t\t\tdebug(`- Added item \"${id}\" (path: \"${filePath}\")...`);\n\t\t\t}\n\n\t\t\tdebug(\n\t\t\t\t`Done adding ${String(collection.data.size)} item(s) to collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tawait generate();\n\n\t\treturn {\n\t\t\tcollections: collections.length,\n\t\t\tdocuments: collections.reduce((acc, collection) => {\n\t\t\t\treturn acc + collection.data.size;\n\t\t\t}, 0),\n\t\t};\n\t}\n\n\tasync function watch(): Promise<Set<watcher.AsyncSubscription>> {\n\t\tdebug(\"Watching...\\n\");\n\n\t\tconst subscriptions = new Set<watcher.AsyncSubscription>();\n\n\t\tconst debounceDelayMs = 150;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tlet controller: AbortController | null = null;\n\t\tlet batch = new Map<watcher.Event[\"path\"], watcher.Event & { relativeFilePath: string }>();\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Watching collection \"${collection.name}\"...`);\n\n\t\t\t/**\n\t\t\t * Ideally, we could just add `include` as a negative ignore pattern.\n\t\t\t *\n\t\t\t * This is currently not supported by `@parcel/watcher`. Simple patterns like \"!*.md\" do seem\n\t\t\t * to work, but others like \"!*\\/index.md\" do not.\n\t\t\t *\n\t\t\t * Therefore we need to filter out matching events in the javascript main thread\n\t\t\t * (see `path.matchesGlob` below).\n\t\t\t *\n\t\t\t * @see https://github.com/parcel-bundler/watcher/issues/166\n\t\t\t */\n\t\t\t// const ignore = [\n\t\t\t// \t...collection.include.map((glob) => {\n\t\t\t// \t\treturn `!${glob}`;\n\t\t\t// \t}),\n\t\t\t// \t...(collection.exclude ?? []),\n\t\t\t// ];\n\t\t\tconst ignore = (collection.exclude ?? []) as Array<string>;\n\n\t\t\tconst subscription = await watcher.subscribe(\n\t\t\t\tcollection.directory,\n\t\t\t\t(error, events) => {\n\t\t\t\t\tif (error != null) {\n\t\t\t\t\t\tlog.error(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tdebug(`- ${String(events.length)} events in collection \"${collection.name}\"...`);\n\n\t\t\t\t\tfor (const event of events) {\n\t\t\t\t\t\t// const relativeFilePath = path.relative(collection.directory, event.path);\n\t\t\t\t\t\tconst relativeFilePath = event.path.slice(collection.absoluteDirectoryPath.length);\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcollection.include.some((pattern) => {\n\t\t\t\t\t\t\t\t// eslint-disable-next-line n/no-unsupported-features/node-builtins\n\t\t\t\t\t\t\t\treturn path.matchesGlob(relativeFilePath, pattern);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t(event as watcher.Event & { relativeFilePath: string }).relativeFilePath =\n\t\t\t\t\t\t\t\trelativeFilePath;\n\t\t\t\t\t\t\tbatch.set(event.path, event as watcher.Event & { relativeFilePath: string });\n\n\t\t\t\t\t\t\tdebug(`- Added \"${event.type}\" event for \"${relativeFilePath}\" to queue.`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdebug(`- Discarded \"${event.type}\" event for \"${relativeFilePath}\".`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (timer != null) {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t}\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\t\t\t\ttimer = setTimeout(async () => {\n\t\t\t\t\t\tif (controller != null) {\n\t\t\t\t\t\t\tcontroller.abort();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcontroller = new AbortController();\n\n\t\t\t\t\t\tconst events = batch;\n\t\t\t\t\t\tbatch = new Map();\n\t\t\t\t\t\ttimer = null;\n\n\t\t\t\t\t\tlet isCollectionChanged = false;\n\n\t\t\t\t\t\tfor (const event of events.values()) {\n\t\t\t\t\t\t\tconst filePath = event.relativeFilePath;\n\t\t\t\t\t\t\tconst id = createIdFromFilePath(filePath);\n\n\t\t\t\t\t\t\tdebug(`Processing \"${event.type}\" event for \"${id}\".`);\n\n\t\t\t\t\t\t\tswitch (event.type) {\n\t\t\t\t\t\t\t\tcase \"create\":\n\t\t\t\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\t\t\t\tisCollectionChanged ||= event.type === \"create\" || collection.data.has(id);\n\n\t\t\t\t\t\t\t\t\tconst absoluteFilePath = path.join(collection.directory, filePath);\n\n\t\t\t\t\t\t\t\t\tconst stats = await fs.stat(absoluteFilePath).catch((error: unknown) => {\n\t\t\t\t\t\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tif (stats == null) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst { mtimeMs: timestamp } = stats;\n\n\t\t\t\t\t\t\t\t\tconst item: CollectionItem = { id, filePath, absoluteFilePath, timestamp };\n\n\t\t\t\t\t\t\t\t\tcollection.data.set(id, { item, content: null, document: null });\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase \"delete\": {\n\t\t\t\t\t\t\t\t\tisCollectionChanged ||= collection.data.has(id);\n\n\t\t\t\t\t\t\t\t\tcollection.data.delete(id);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (isCollectionChanged) {\n\t\t\t\t\t\t\tawait generate(controller.signal);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, debounceDelayMs);\n\t\t\t\t},\n\t\t\t\t{ ignore },\n\t\t\t);\n\n\t\t\tsubscriptions.add(subscription);\n\t\t}\n\n\t\tasync function unsubscribe() {\n\t\t\tdebug(\"Cleaning up.\");\n\n\t\t\tif (timer != null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\ttimer = null;\n\n\t\t\tif (controller != null) {\n\t\t\t\tcontroller.abort();\n\t\t\t}\n\t\t\tcontroller = null;\n\n\t\t\tfor (const subscription of subscriptions) {\n\t\t\t\tawait subscription.unsubscribe();\n\t\t\t}\n\t\t\tsubscriptions.clear();\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\tprocess.once(\"SIGINT\", unsubscribe);\n\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\tprocess.once(\"SIGTERM\", unsubscribe);\n\n\t\treturn subscriptions;\n\t}\n\n\treturn {\n\t\tbuild,\n\t\twatch,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["path","imports: Array<string>","value","collections: Array<Collection>","context: TransformContext","item: CollectionItem","timer: ReturnType<typeof setTimeout> | null","controller: AbortController | null","events","error"],"sources":["../src/index.ts"],"sourcesContent":["import * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs/promises\";\nimport { availableParallelism } from \"node:os\";\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { debuglog } from \"node:util\";\n\nimport { addTrailingSlash, capitalize, log } from \"@acdh-oeaw/lib\";\nimport * as watcher from \"@parcel/watcher\";\nimport plimit from \"p-limit\";\nimport pluralize from \"pluralize\";\n\n//\n\nconst debug = debuglog(\"content-lib\");\n\n//\n\nfunction createContentHash(value: string): string {\n\treturn crypto.createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction createIdFromFilePath(filePath: string): string {\n\tconst parsed = path.parse(filePath);\n\n\tif (parsed.name.toLowerCase() === \"index\") {\n\t\treturn path.basename(parsed.dir);\n\t}\n\n\treturn parsed.name;\n}\n\n//\n\ntype MaybePromise<T> = T | Promise<T>;\n\ntype NonEmptyReadonlyArray<T> = readonly [T, ...Array<T>];\n\ntype GlobString = string;\n\ninterface CollectionItem {\n\t/** Unique identifier. */\n\tid: string;\n\t/** File path relative to colleciton directory. */\n\tfilePath: string;\n\t/** File path relative to current working directory. */\n\tabsoluteFilePath: string;\n\t/** File modification timestamp. */\n\ttimestamp: number;\n}\n\ninterface TransformContext {\n\tcollections: Array<Collection>;\n\tcreateImportDeclaration: <T>(path: string) => ImportDeclaration<T>;\n\tcreateJavaScriptImport: <T>(content: string) => JavaScriptImport<T>;\n\tcreateJsonImport: <T>(content: string) => JsonImport<T>;\n}\n\nexport interface CollectionConfig<\n\tTCollectionName extends string = string,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tTCollectionItemContent = any,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tTCollectionDocument = any,\n> {\n\tname: TCollectionName;\n\tdirectory: string;\n\tinclude: NonEmptyReadonlyArray<GlobString>;\n\texclude?: ReadonlyArray<GlobString>;\n\tread: (item: CollectionItem) => MaybePromise<TCollectionItemContent>;\n\ttransform: (\n\t\tcontent: TCollectionItemContent,\n\t\titem: CollectionItem,\n\t\tcontext: TransformContext,\n\t) => MaybePromise<TCollectionDocument>;\n}\n\nexport function createCollection<\n\tTCollectionName extends string,\n\tTCollectionItemContent,\n\tTCollectionDocument,\n>(\n\tconfig: CollectionConfig<TCollectionName, TCollectionItemContent, TCollectionDocument>,\n): CollectionConfig<TCollectionName, TCollectionItemContent, TCollectionDocument> {\n\treturn config;\n}\n\nexport interface ContentConfig {\n\tcollections: Array<CollectionConfig>;\n}\n\nexport function createConfig<T extends ContentConfig>(config: T): T {\n\treturn config;\n}\n\n//\n\n// function createItemCacheKey(item: CollectionItem): string {\n// \treturn String(item.timestamp);\n// }\n\n//\n\nclass ImportDeclaration<T> {\n\tprivate __brand!: never;\n\tvalue!: T;\n\tpath: string;\n\n\tconstructor(path: string) {\n\t\tthis.path = path;\n\t}\n}\n\nfunction createImportDeclaration<T>(path: string): ImportDeclaration<T> {\n\treturn new ImportDeclaration<T>(path);\n}\n\nclass JavaScriptImport<T> {\n\tprivate __brand!: never;\n\tvalue!: T;\n\tcontent: string;\n\n\tconstructor(content: string) {\n\t\tthis.content = content;\n\t}\n}\n\nfunction createJavaScriptImport<T>(content: string): JavaScriptImport<T> {\n\treturn new JavaScriptImport<T>(content);\n}\n\nclass JsonImport<T> {\n\tprivate __brand!: never;\n\tvalue!: T;\n\tcontent: string;\n\n\tconstructor(content: string) {\n\t\tthis.content = content;\n\t}\n}\n\nfunction createJsonImport<T>(content: string): JsonImport<T> {\n\treturn new JsonImport<T>(content);\n}\n\n//\n\nexport type GetCollection<TConfig extends ContentConfig, TName extends string> = Extract<\n\tTConfig[\"collections\"][number],\n\t{ name: TName }\n>;\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\n\ntype Replace<T> = {\n\t[K in keyof T]: T[K] extends JavaScriptImport<infer U>\n\t\t? U\n\t\t: T[K] extends JsonImport<infer U>\n\t\t\t? U\n\t\t\t: T[K] extends ImportDeclaration<infer U>\n\t\t\t\t? U\n\t\t\t\t: T[K] extends object\n\t\t\t\t\t? Simplify<Replace<T[K]>>\n\t\t\t\t\t: T[K];\n};\n\nexport type CollectionEntry<TCollection extends Collection> = Simplify<{\n\titem: { id: string };\n\tcontent: Simplify<Awaited<ReturnType<TCollection[\"read\"]>>>;\n\tdocument: Simplify<Replace<Awaited<ReturnType<TCollection[\"transform\"]>>>>;\n}>;\n\n//\n\nconst prefix = \"__i__\";\nconst re = new RegExp(`\"(${prefix}\\\\d+)\"`, \"g\");\n\nfunction serialize(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tvalue: Map<string, any>,\n\tcontentProcessorConfigFilePath: string,\n\tcollectionName: string,\n): Map<string, string> {\n\tdebug(\"Serializing...\\n\");\n\n\tconst imports: Array<string> = [];\n\n\tfunction addImport(filePath: string, type: \"js\" | \"json\" = \"js\"): string {\n\t\tconst identifier = [prefix, imports.length].join(\"\");\n\t\timports.push(\n\t\t\t`import ${identifier} from \"${filePath}\"${type !== \"js\" ? ` with { type: \"${type}\" }` : \"\"};`,\n\t\t);\n\n\t\treturn identifier;\n\t}\n\n\tconst files = new Map<string, string>();\n\n\tfunction addFiles(filePath: string, content: string): void {\n\t\tfiles.set(filePath, content);\n\t}\n\n\tconst json = JSON.stringify(\n\t\tArray.from(value),\n\t\t// TODO: Should we support (multiple) named imports?\n\t\t(_key, value) => {\n\t\t\tif (value instanceof ImportDeclaration) {\n\t\t\t\tconst filePath = value.path;\n\n\t\t\t\tdebug(`Adding import declaration for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\tif (value instanceof JavaScriptImport) {\n\t\t\t\tconst hash = createContentHash(value.content);\n\t\t\t\tconst filePath = `./${hash}.js`;\n\n\t\t\t\tdebug(`Adding javascript import for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath);\n\t\t\t\taddFiles(filePath, `// @ts-nocheck\\n${value.content}`);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\tif (value instanceof JsonImport) {\n\t\t\t\tconst hash = createContentHash(value.content);\n\t\t\t\tconst filePath = `./${hash}.json`;\n\n\t\t\t\tdebug(`Adding json import for \"${filePath}\".`);\n\t\t\t\tconst identifier = addImport(filePath, \"json\");\n\t\t\t\taddFiles(filePath, value.content);\n\n\t\t\t\treturn identifier;\n\t\t\t}\n\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\treturn value;\n\t\t},\n\t\t2,\n\t)\n\t\t/** Remove quotes from import identifiers. */\n\t\t.replaceAll(re, \"$1\");\n\n\tlet result = \"\";\n\n\tif (imports.length > 0) {\n\t\tresult += imports.join(\"\\n\");\n\t\tresult += \"\\n\\n\";\n\t}\n\n\tresult += [`const items = new Map(${json});`, \"export default items;\"].join(\"\\n\\n\");\n\n\tfiles.set(\"index.js\", result);\n\n\t// eslint-disable-next-line import-x/no-named-as-default-member\n\tconst typeName = capitalize(pluralize.singular(collectionName));\n\n\tfiles.set(\n\t\t\"index.d.ts\",\n\t\t[\n\t\t\t`import type { GetCollection, CollectionEntry } from \"@acdh-oeaw/content-lib\";`,\n\t\t\t\"\",\n\t\t\t`import type { config } from \"${contentProcessorConfigFilePath}\";`,\n\t\t\t\"\",\n\t\t\t`type Collection = GetCollection<typeof config, \"${collectionName}\">;`,\n\t\t\t`type ${typeName} = CollectionEntry<Collection>;`,\n\t\t\t\"\",\n\t\t\t`declare const items: Map<string, ${typeName}>;`,\n\t\t\t`export { type ${typeName}, items as default };`,\n\t\t].join(\"\\n\"),\n\t);\n\n\treturn files;\n}\n\n//\n\ninterface Collection extends CollectionConfig {\n\tabsoluteDirectoryPath: string;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tdata: Map<CollectionItem[\"id\"], { item: CollectionItem; content: any; document: any }>; // TODO: revisit\n\toutputDirectoryPath: string;\n}\n\ninterface BuildStats {\n\tcollections: number;\n\tdocuments: number;\n}\n\nexport interface ContentProcessorConfig {\n\t/** Path to config file, relative to `process.cwd()`, which provides a named export `config`. */\n\tconfigFilePath: string;\n}\n\nexport interface ContentProcessor {\n\tbuild: () => Promise<BuildStats>;\n\twatch: () => Promise<Set<watcher.AsyncSubscription>>;\n}\n\nexport async function createContentProcessor(\n\tcontentProcessorConfig: ContentProcessorConfig,\n): Promise<ContentProcessor> {\n\tdebug(\"Creating content processor...\\n\");\n\n\tdebug(\"Reading config file...\");\n\tconst contentProcessorConfigUrl = pathToFileURL(\n\t\tpath.resolve(contentProcessorConfig.configFilePath),\n\t);\n\tcontentProcessorConfigUrl.searchParams.set(\"cache-key\", String(Date.now()));\n\tconst contentProcessorConfigFilePath = String(contentProcessorConfigUrl);\n\tconst { config } = (await import(contentProcessorConfigFilePath)) as { config: ContentConfig }; // TODO: validate\n\tdebug(`Done reading config file \"${contentProcessorConfigFilePath}\".`);\n\n\tconst concurrency = availableParallelism();\n\tconst limit = plimit(concurrency);\n\tdebug(`Concurrency: ${String(concurrency)}.\\n`);\n\n\tconst outputDirectoryBasePath = path.join(process.cwd(), \".content\", \"generated\");\n\n\tconst collections: Array<Collection> = [];\n\n\tconst context: TransformContext = {\n\t\tcollections,\n\t\tcreateImportDeclaration,\n\t\tcreateJavaScriptImport,\n\t\tcreateJsonImport,\n\t};\n\n\tfor (const collection of config.collections) {\n\t\tconst absoluteDirectoryPath = addTrailingSlash(path.resolve(collection.directory));\n\n\t\t/** Ensure directory exists, which is expected by `@parcel/watcher`. */\n\t\tawait fs.mkdir(absoluteDirectoryPath, { recursive: true });\n\n\t\tconst outputDirectoryPath = path.join(\n\t\t\toutputDirectoryBasePath,\n\t\t\tcollection.name.toLowerCase().replaceAll(/[^a-z0-9_-]/g, \"-\"),\n\t\t);\n\n\t\tcollections.push({\n\t\t\t...collection,\n\t\t\tabsoluteDirectoryPath,\n\t\t\tdata: new Map(),\n\t\t\toutputDirectoryPath,\n\t\t});\n\t}\n\n\tasync function generate(signal?: AbortSignal): Promise<void> {\n\t\tdebug(\"Generating...\\n\");\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Reading collection \"${collection.name}\"...`);\n\n\t\t\tawait limit.map(Array.from(collection.data), async ([id, { item }]) => {\n\t\t\t\tif (signal?.aborted === true) {\n\t\t\t\t\tdebug(\"Aborted reading collections.\");\n\t\t\t\t\tlimit.clearQueue();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// FIXME: race condition: what if file has been deleted in the meantime\n\t\t\t\t// TODO: skip item when `read()` returns `null`?\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst content = await collection.read(item);\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tcollection.data.get(id)!.content = content;\n\n\t\t\t\tdebug(`- Read item \"${id}\".`);\n\t\t\t});\n\n\t\t\tdebug(\n\t\t\t\t`Done reading ${String(collection.data.size)} item(s) in collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Transforming collection \"${collection.name}\"...`);\n\n\t\t\tawait limit.map(Array.from(collection.data), async ([id, { content, item }]) => {\n\t\t\t\tif (signal?.aborted === true) {\n\t\t\t\t\tdebug(\"Aborted transforming collections.\");\n\t\t\t\t\tlimit.clearQueue();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tconst document = await collection.transform(content, item, context);\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\t\tcollection.data.get(id)!.document = document;\n\n\t\t\t\tdebug(`- Transformed item \"${id}\".`);\n\t\t\t});\n\n\t\t\tdebug(\n\t\t\t\t`Done transforming ${String(collection.data.size)} item(s) in collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tif (signal?.aborted === true) {\n\t\t\tdebug(\"Aborted writing collections.\");\n\t\t\treturn;\n\t\t}\n\n\t\tdebug(\"Clearing output directory...\\n\");\n\t\tawait fs.rm(outputDirectoryBasePath, { force: true, recursive: true });\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Writing collection \"${collection.name}\".`);\n\n\t\t\tdebug(`Creating output directory for \"${collection.name}\".`);\n\t\t\tawait fs.mkdir(collection.outputDirectoryPath, { recursive: true });\n\n\t\t\tconst files = serialize(\n\t\t\t\tcollection.data,\n\t\t\t\tpath.relative(collection.outputDirectoryPath, contentProcessorConfig.configFilePath),\n\t\t\t\tcollection.name,\n\t\t\t);\n\n\t\t\tawait limit.map(Array.from(files), async ([filePath, fileContent]) => {\n\t\t\t\tconst outputFilePath = path.join(collection.outputDirectoryPath, filePath);\n\t\t\t\tawait fs.writeFile(outputFilePath, fileContent, { encoding: \"utf-8\" });\n\t\t\t});\n\t\t}\n\t}\n\n\tasync function build(): Promise<BuildStats> {\n\t\tdebug(\"Building...\\n\");\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Building collection \"${collection.name}\"...`);\n\n\t\t\t// eslint-disable-next-line n/no-unsupported-features/node-builtins\n\t\t\tfor await (const filePath of fs.glob(collection.include, {\n\t\t\t\tcwd: collection.directory,\n\t\t\t\texclude: collection.exclude,\n\t\t\t})) {\n\t\t\t\tconst absoluteFilePath = path.join(collection.directory, filePath);\n\t\t\t\tconst id = createIdFromFilePath(filePath);\n\n\t\t\t\tconst stats = await fs.stat(absoluteFilePath).catch((error: unknown) => {\n\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t});\n\t\t\t\tif (stats == null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst { mtimeMs: timestamp } = stats;\n\n\t\t\t\tconst item: CollectionItem = { id, filePath, absoluteFilePath, timestamp };\n\n\t\t\t\tcollection.data.set(id, { item, content: null, document: null });\n\n\t\t\t\tdebug(`- Added item \"${id}\" (path: \"${filePath}\").`);\n\t\t\t}\n\n\t\t\tdebug(\n\t\t\t\t`Done adding ${String(collection.data.size)} item(s) to collection \"${collection.name}\".\\n`,\n\t\t\t);\n\t\t}\n\n\t\tawait generate();\n\n\t\treturn {\n\t\t\tcollections: collections.length,\n\t\t\tdocuments: collections.reduce((acc, collection) => {\n\t\t\t\treturn acc + collection.data.size;\n\t\t\t}, 0),\n\t\t};\n\t}\n\n\tasync function watch(): Promise<Set<watcher.AsyncSubscription>> {\n\t\tdebug(\"Watching...\\n\");\n\n\t\tconst subscriptions = new Set<watcher.AsyncSubscription>();\n\n\t\tconst debounceDelayMs = 150;\n\t\tlet timer: ReturnType<typeof setTimeout> | null = null;\n\t\tlet controller: AbortController | null = null;\n\t\tlet batch = new Map<watcher.Event[\"path\"], watcher.Event & { relativeFilePath: string }>();\n\n\t\tfor (const collection of collections) {\n\t\t\tdebug(`Watching collection \"${collection.name}\"...`);\n\n\t\t\t/**\n\t\t\t * Ideally, we could just add `include` as a negative ignore pattern.\n\t\t\t *\n\t\t\t * This is currently not supported by `@parcel/watcher`. Simple patterns like \"!*.md\" do seem\n\t\t\t * to work, but others like \"!*\\/index.md\" do not.\n\t\t\t *\n\t\t\t * Therefore we need to filter out matching events in the javascript main thread\n\t\t\t * (see `path.matchesGlob` below).\n\t\t\t *\n\t\t\t * @see https://github.com/parcel-bundler/watcher/issues/166\n\t\t\t */\n\t\t\t// const ignore = [\n\t\t\t// \t...collection.include.map((glob) => {\n\t\t\t// \t\treturn `!${glob}`;\n\t\t\t// \t}),\n\t\t\t// \t...(collection.exclude ?? []),\n\t\t\t// ];\n\t\t\tconst ignore = (collection.exclude ?? []) as Array<string>;\n\n\t\t\tconst subscription = await watcher.subscribe(\n\t\t\t\tcollection.directory,\n\t\t\t\t(error, events) => {\n\t\t\t\t\tif (error != null) {\n\t\t\t\t\t\tlog.error(error);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tdebug(`- ${String(events.length)} events in collection \"${collection.name}\".`);\n\n\t\t\t\t\tfor (const event of events) {\n\t\t\t\t\t\t// const relativeFilePath = path.relative(collection.directory, event.path);\n\t\t\t\t\t\tconst relativeFilePath = event.path.slice(collection.absoluteDirectoryPath.length);\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tcollection.include.some((pattern) => {\n\t\t\t\t\t\t\t\t// eslint-disable-next-line n/no-unsupported-features/node-builtins\n\t\t\t\t\t\t\t\treturn path.matchesGlob(relativeFilePath, pattern);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t(event as watcher.Event & { relativeFilePath: string }).relativeFilePath =\n\t\t\t\t\t\t\t\trelativeFilePath;\n\t\t\t\t\t\t\tbatch.set(event.path, event as watcher.Event & { relativeFilePath: string });\n\n\t\t\t\t\t\t\tdebug(`- Added \"${event.type}\" event for \"${relativeFilePath}\" to queue.`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdebug(`- Discarded \"${event.type}\" event for \"${relativeFilePath}\".`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (timer != null) {\n\t\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\t}\n\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\t\t\t\ttimer = setTimeout(async () => {\n\t\t\t\t\t\tif (controller != null) {\n\t\t\t\t\t\t\tcontroller.abort();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcontroller = new AbortController();\n\n\t\t\t\t\t\tconst events = batch;\n\t\t\t\t\t\tbatch = new Map();\n\t\t\t\t\t\ttimer = null;\n\n\t\t\t\t\t\tlet isCollectionChanged = false;\n\n\t\t\t\t\t\tfor (const event of events.values()) {\n\t\t\t\t\t\t\tconst filePath = event.relativeFilePath;\n\t\t\t\t\t\t\tconst id = createIdFromFilePath(filePath);\n\n\t\t\t\t\t\t\tdebug(`Processing \"${event.type}\" event for \"${id}\".`);\n\n\t\t\t\t\t\t\tswitch (event.type) {\n\t\t\t\t\t\t\t\tcase \"create\":\n\t\t\t\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\t\t\t\tisCollectionChanged ||= event.type === \"create\" || collection.data.has(id);\n\n\t\t\t\t\t\t\t\t\tconst absoluteFilePath = path.join(collection.directory, filePath);\n\n\t\t\t\t\t\t\t\t\tconst stats = await fs.stat(absoluteFilePath).catch((error: unknown) => {\n\t\t\t\t\t\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tif (stats == null) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst { mtimeMs: timestamp } = stats;\n\n\t\t\t\t\t\t\t\t\tconst item: CollectionItem = { id, filePath, absoluteFilePath, timestamp };\n\n\t\t\t\t\t\t\t\t\tcollection.data.set(id, { item, content: null, document: null });\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase \"delete\": {\n\t\t\t\t\t\t\t\t\tisCollectionChanged ||= collection.data.has(id);\n\n\t\t\t\t\t\t\t\t\tcollection.data.delete(id);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (isCollectionChanged) {\n\t\t\t\t\t\t\tawait generate(controller.signal);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, debounceDelayMs);\n\t\t\t\t},\n\t\t\t\t{ ignore },\n\t\t\t);\n\n\t\t\tsubscriptions.add(subscription);\n\t\t}\n\n\t\tasync function unsubscribe() {\n\t\t\tdebug(\"Cleaning up...\");\n\n\t\t\tif (timer != null) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\ttimer = null;\n\n\t\t\tif (controller != null) {\n\t\t\t\tcontroller.abort();\n\t\t\t}\n\t\t\tcontroller = null;\n\n\t\t\tfor (const subscription of subscriptions) {\n\t\t\t\tawait subscription.unsubscribe();\n\t\t\t}\n\t\t\tsubscriptions.clear();\n\t\t}\n\n\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\tprocess.once(\"SIGINT\", unsubscribe);\n\t\t// eslint-disable-next-line @typescript-eslint/no-misused-promises\n\t\tprocess.once(\"SIGTERM\", unsubscribe);\n\n\t\treturn subscriptions;\n\t}\n\n\treturn {\n\t\tbuild,\n\t\twatch,\n\t};\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acdh-oeaw/content-lib",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@acdh-oeaw/lib": "0.3.4",
|
|
25
|
-
"@parcel/watcher": "^2.5.1"
|
|
25
|
+
"@parcel/watcher": "^2.5.1",
|
|
26
|
+
"p-limit": "^7.1.0",
|
|
27
|
+
"pluralize": "^8.0.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
30
|
"@acdh-oeaw/eslint-config": "2.0.9",
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
"@acdh-oeaw/tsconfig": "^1.5.1",
|
|
32
34
|
"@acdh-oeaw/tsconfig-lib": "^1.3.0",
|
|
33
35
|
"@types/node": "^22.17.2",
|
|
36
|
+
"@types/pluralize": "^0.0.33",
|
|
34
37
|
"eslint": "9.33.0",
|
|
35
38
|
"eslint-config-flat-gitignore": "2.1.0",
|
|
36
39
|
"globals": "16.3.0",
|