@content-collections/core 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.
Files changed (87) hide show
  1. package/.turbo/turbo-build.log +14 -0
  2. package/.turbo/turbo-test.log +47 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/LICENSE +21 -0
  5. package/coverage/.tmp/coverage-0.json +1 -0
  6. package/coverage/.tmp/coverage-1.json +1 -0
  7. package/coverage/.tmp/coverage-2.json +1 -0
  8. package/coverage/.tmp/coverage-3.json +1 -0
  9. package/coverage/.tmp/coverage-4.json +1 -0
  10. package/coverage/.tmp/coverage-5.json +1 -0
  11. package/coverage/.tmp/coverage-6.json +1 -0
  12. package/coverage/.tmp/coverage-7.json +1 -0
  13. package/coverage/.tmp/coverage-8.json +1 -0
  14. package/coverage/.tmp/coverage-9.json +1 -0
  15. package/coverage/base.css +224 -0
  16. package/coverage/block-navigation.js +87 -0
  17. package/coverage/builder.ts.html +424 -0
  18. package/coverage/clover.xml +960 -0
  19. package/coverage/collector.ts.html +427 -0
  20. package/coverage/config.ts.html +403 -0
  21. package/coverage/configurationReader.ts.html +409 -0
  22. package/coverage/coverage-final.json +11 -0
  23. package/coverage/events.ts.html +310 -0
  24. package/coverage/favicon.png +0 -0
  25. package/coverage/index.html +251 -0
  26. package/coverage/index.ts.html +106 -0
  27. package/coverage/prettify.css +1 -0
  28. package/coverage/prettify.js +2 -0
  29. package/coverage/sort-arrow-sprite.png +0 -0
  30. package/coverage/sorter.js +196 -0
  31. package/coverage/synchronizer.ts.html +394 -0
  32. package/coverage/transformer.ts.html +607 -0
  33. package/coverage/utils.ts.html +118 -0
  34. package/coverage/writer.ts.html +424 -0
  35. package/dist/index.cjs +416 -0
  36. package/dist/index.d.cts +59 -0
  37. package/dist/index.d.ts +146 -0
  38. package/dist/index.js +630 -0
  39. package/package.json +39 -0
  40. package/src/__tests__/.content-collections/cache/config.001.ts.js +19 -0
  41. package/src/__tests__/.content-collections/cache/config.002.mjs +16 -0
  42. package/src/__tests__/.content-collections/cache/config.002.ts.js +16 -0
  43. package/src/__tests__/.content-collections/cache/config.003.ts.js +24 -0
  44. package/src/__tests__/.content-collections/cache/config.004.mjs +47 -0
  45. package/src/__tests__/.content-collections/different-cache-dir/different.config.js +19 -0
  46. package/src/__tests__/.content-collections/generated-config.002/allPosts.json +13 -0
  47. package/src/__tests__/.content-collections/generated-config.002/index.d.ts +7 -0
  48. package/src/__tests__/.content-collections/generated-config.002/index.js +5 -0
  49. package/src/__tests__/.content-collections/generated-config.004/allAuthors.json +13 -0
  50. package/src/__tests__/.content-collections/generated-config.004/allPosts.json +13 -0
  51. package/src/__tests__/.content-collections/generated-config.004/index.d.ts +10 -0
  52. package/src/__tests__/.content-collections/generated-config.004/index.js +6 -0
  53. package/src/__tests__/collections/posts.ts +15 -0
  54. package/src/__tests__/config.001.ts +19 -0
  55. package/src/__tests__/config.002.ts +14 -0
  56. package/src/__tests__/config.003.ts +6 -0
  57. package/src/__tests__/config.004.ts +47 -0
  58. package/src/__tests__/invalid +1 -0
  59. package/src/__tests__/sources/authors/trillian.md +8 -0
  60. package/src/__tests__/sources/posts/first.md +6 -0
  61. package/src/__tests__/sources/test/001.md +5 -0
  62. package/src/__tests__/sources/test/002.md +5 -0
  63. package/src/__tests__/sources/test/broken-frontmatter +6 -0
  64. package/src/builder.test.ts +180 -0
  65. package/src/builder.ts +113 -0
  66. package/src/collector.test.ts +157 -0
  67. package/src/collector.ts +114 -0
  68. package/src/config.ts +106 -0
  69. package/src/configurationReader.test.ts +104 -0
  70. package/src/configurationReader.ts +108 -0
  71. package/src/events.test.ts +84 -0
  72. package/src/events.ts +75 -0
  73. package/src/index.ts +7 -0
  74. package/src/synchronizer.test.ts +192 -0
  75. package/src/synchronizer.ts +103 -0
  76. package/src/transformer.test.ts +431 -0
  77. package/src/transformer.ts +174 -0
  78. package/src/types.test.ts +137 -0
  79. package/src/types.ts +33 -0
  80. package/src/utils.test.ts +48 -0
  81. package/src/utils.ts +11 -0
  82. package/src/watcher.test.ts +200 -0
  83. package/src/watcher.ts +56 -0
  84. package/src/writer.test.ts +135 -0
  85. package/src/writer.ts +113 -0
  86. package/tsconfig.json +27 -0
  87. package/vite.config.ts +24 -0
@@ -0,0 +1,16 @@
1
+ // src/__tests__/config.002.ts
2
+ import { defineCollection, defineConfig } from "/Users/sdorra/Projects/sdorra/content-collections/packages/core/src/index.ts";
3
+ var posts = defineCollection({
4
+ name: "posts",
5
+ schema: (z) => ({
6
+ title: z.string()
7
+ }),
8
+ directory: "sources/posts",
9
+ include: "**/*.md(x)?"
10
+ });
11
+ var config_002_default = defineConfig({
12
+ collections: [posts]
13
+ });
14
+ export {
15
+ config_002_default as default
16
+ };
@@ -0,0 +1,24 @@
1
+ // src/__tests__/config.003.ts
2
+ import { defineConfig } from "/Users/sdorra/Projects/sdorra/content-collections/packages/core/src/index.ts";
3
+
4
+ // src/__tests__/collections/posts.ts
5
+ import { defineCollection } from "/Users/sdorra/Projects/sdorra/content-collections/packages/core/src/index.ts";
6
+ var posts_default = defineCollection({
7
+ name: "posts",
8
+ typeName: "Post",
9
+ schema: (z) => ({
10
+ title: z.string().min(5),
11
+ description: z.string().min(10),
12
+ date: z.union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.date()]).transform((val) => new Date(val))
13
+ }),
14
+ directory: "posts",
15
+ include: "**/*.md(x)?"
16
+ });
17
+
18
+ // src/__tests__/config.003.ts
19
+ var config_003_default = defineConfig({
20
+ collections: [posts_default]
21
+ });
22
+ export {
23
+ config_003_default as default
24
+ };
@@ -0,0 +1,47 @@
1
+ // src/__tests__/config.004.ts
2
+ import { defineCollection, defineConfig } from "/Users/sdorra/Projects/sdorra/content-collections/packages/core/src/index.ts";
3
+ import fs from "node:fs";
4
+ function mkdir(directory) {
5
+ if (fs.existsSync(directory)) {
6
+ return;
7
+ }
8
+ fs.mkdirSync(directory);
9
+ }
10
+ var posts = defineCollection({
11
+ name: "posts",
12
+ schema: (z) => ({
13
+ title: z.string()
14
+ }),
15
+ directory: "sources/posts",
16
+ include: "**/*.md(x)?",
17
+ onSuccess: (documents) => {
18
+ mkdir("tmp");
19
+ fs.writeFileSync(
20
+ "tmp/posts.length",
21
+ JSON.stringify(documents.length),
22
+ "utf-8"
23
+ );
24
+ }
25
+ });
26
+ var authors = defineCollection({
27
+ name: "authors",
28
+ schema: (z) => ({
29
+ displayName: z.string()
30
+ }),
31
+ directory: "sources/authors",
32
+ include: "**/*.md(x)?",
33
+ onSuccess: (documents) => {
34
+ mkdir("tmp");
35
+ fs.writeFileSync(
36
+ "tmp/authors.length",
37
+ JSON.stringify(documents.length),
38
+ "utf-8"
39
+ );
40
+ }
41
+ });
42
+ var config_004_default = defineConfig({
43
+ collections: [posts, authors]
44
+ });
45
+ export {
46
+ config_004_default as default
47
+ };
@@ -0,0 +1,19 @@
1
+ // src/__tests__/config.001.ts
2
+ import { defineCollection, defineConfig } from "/Users/sdorra/Projects/sdorra/content-collections/packages/core/src/index.ts";
3
+ var posts = defineCollection({
4
+ name: "posts",
5
+ typeName: "Post",
6
+ schema: (z) => ({
7
+ title: z.string().min(5),
8
+ description: z.string().min(10),
9
+ date: z.union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.date()]).transform((val) => new Date(val))
10
+ }),
11
+ directory: "sources/posts",
12
+ include: "**/*.md(x)?"
13
+ });
14
+ var config_001_default = defineConfig({
15
+ collections: [posts]
16
+ });
17
+ export {
18
+ config_001_default as default
19
+ };
@@ -0,0 +1,13 @@
1
+ [
2
+ {
3
+ "content": "\n# First post",
4
+ "title": "First post",
5
+ "_meta": {
6
+ "filePath": "first.md",
7
+ "fileName": "first.md",
8
+ "directory": ".",
9
+ "extension": "md",
10
+ "path": "first"
11
+ }
12
+ }
13
+ ]
@@ -0,0 +1,7 @@
1
+ import configuration from "../../config.002.ts";
2
+ import { GetTypeByName } from "@content-collections/core";
3
+
4
+ export type Post = GetTypeByName<typeof configuration, "posts">;
5
+ export declare const allPosts: Array<Post>;
6
+
7
+ export {};
@@ -0,0 +1,5 @@
1
+ // generated by content-collections at Mon Jan 15 2024 19:52:01 GMT+0100 (GMT+01:00)
2
+
3
+ import allPosts from "./allPosts.json";
4
+
5
+ export { allPosts };
@@ -0,0 +1,13 @@
1
+ [
2
+ {
3
+ "content": "\nTricia Marie McMillan, also known as Trillian Astra, is a fictional character from Douglas Adams' series The Hitchhiker's Guide to the Galaxy. She is most commonly referred to simply as \"Trillian\", a modification of her birth name, which she adopted because it sounded more \"space-like\". According to the movie version, her middle name is Marie. Physically, she is described as \"a slim, darkish humanoid, with long waves of black hair, a full mouth, an odd little knob of a nose and ridiculously brown eyes,\" looking \"vaguely Arabic.\"\n\nSource: https://en.wikipedia.org/wiki/Trillian_(character)",
4
+ "displayName": "Tricia Marie McMillan",
5
+ "_meta": {
6
+ "filePath": "trillian.md",
7
+ "fileName": "trillian.md",
8
+ "directory": ".",
9
+ "extension": "md",
10
+ "path": "trillian"
11
+ }
12
+ }
13
+ ]
@@ -0,0 +1,13 @@
1
+ [
2
+ {
3
+ "content": "\n# First post",
4
+ "title": "First post",
5
+ "_meta": {
6
+ "filePath": "first.md",
7
+ "fileName": "first.md",
8
+ "directory": ".",
9
+ "extension": "md",
10
+ "path": "first"
11
+ }
12
+ }
13
+ ]
@@ -0,0 +1,10 @@
1
+ import configuration from "../../config.004.ts";
2
+ import { GetTypeByName } from "@content-collections/core";
3
+
4
+ export type Post = GetTypeByName<typeof configuration, "posts">;
5
+ export declare const allPosts: Array<Post>;
6
+
7
+ export type Author = GetTypeByName<typeof configuration, "authors">;
8
+ export declare const allAuthors: Array<Author>;
9
+
10
+ export {};
@@ -0,0 +1,6 @@
1
+ // generated by content-collections at Mon Jan 15 2024 19:52:01 GMT+0100 (GMT+01:00)
2
+
3
+ import allPosts from "./allPosts.json";
4
+ import allAuthors from "./allAuthors.json";
5
+
6
+ export { allPosts, allAuthors };
@@ -0,0 +1,15 @@
1
+ import { defineCollection } from "@content-collections/core";
2
+
3
+ export default defineCollection({
4
+ name: "posts",
5
+ typeName: "Post",
6
+ schema: (z) => ({
7
+ title: z.string().min(5),
8
+ description: z.string().min(10),
9
+ date: z
10
+ .union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.date()])
11
+ .transform((val) => new Date(val)),
12
+ }),
13
+ directory: "posts",
14
+ include: "**/*.md(x)?",
15
+ });
@@ -0,0 +1,19 @@
1
+ import { defineCollection, defineConfig } from "@content-collections/core";
2
+
3
+ const posts = defineCollection({
4
+ name: "posts",
5
+ typeName: "Post",
6
+ schema: (z) => ({
7
+ title: z.string().min(5),
8
+ description: z.string().min(10),
9
+ date: z
10
+ .union([z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.date()])
11
+ .transform((val) => new Date(val)),
12
+ }),
13
+ directory: "sources/posts",
14
+ include: "**/*.md(x)?",
15
+ });
16
+
17
+ export default defineConfig({
18
+ collections: [posts],
19
+ });
@@ -0,0 +1,14 @@
1
+ import { defineCollection, defineConfig } from "@content-collections/core";
2
+
3
+ const posts = defineCollection({
4
+ name: "posts",
5
+ schema: (z) => ({
6
+ title: z.string(),
7
+ }),
8
+ directory: "sources/posts",
9
+ include: "**/*.md(x)?",
10
+ });
11
+
12
+ export default defineConfig({
13
+ collections: [posts],
14
+ });
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "@content-collections/core";
2
+ import posts from "./collections/posts";
3
+
4
+ export default defineConfig({
5
+ collections: [posts],
6
+ });
@@ -0,0 +1,47 @@
1
+ import { defineCollection, defineConfig } from "@content-collections/core";
2
+ import fs from "node:fs";
3
+
4
+ function mkdir(directory: string) {
5
+ if (fs.existsSync(directory)) {
6
+ return;
7
+ }
8
+ fs.mkdirSync(directory);
9
+ }
10
+
11
+ const posts = defineCollection({
12
+ name: "posts",
13
+ schema: (z) => ({
14
+ title: z.string(),
15
+ }),
16
+ directory: "sources/posts",
17
+ include: "**/*.md(x)?",
18
+ onSuccess: (documents) => {
19
+ mkdir("tmp");
20
+ fs.writeFileSync(
21
+ "tmp/posts.length",
22
+ JSON.stringify(documents.length),
23
+ "utf-8"
24
+ );
25
+ },
26
+ });
27
+
28
+ const authors = defineCollection({
29
+ name: "authors",
30
+ schema: (z) => ({
31
+ displayName: z.string(),
32
+ }),
33
+ directory: "sources/authors",
34
+ include: "**/*.md(x)?",
35
+ onSuccess: (documents) => {
36
+ mkdir("tmp");
37
+ fs.writeFileSync(
38
+ "tmp/authors.length",
39
+ JSON.stringify(documents.length),
40
+ "utf-8"
41
+ );
42
+ },
43
+ });
44
+
45
+ export default defineConfig({
46
+ collections: [posts, authors],
47
+ });
@@ -0,0 +1 @@
1
+ This is not valid TypeScript code.
@@ -0,0 +1,8 @@
1
+ ---
2
+ ref: trillian
3
+ displayName: Tricia Marie McMillan
4
+ ---
5
+
6
+ Tricia Marie McMillan, also known as Trillian Astra, is a fictional character from Douglas Adams' series The Hitchhiker's Guide to the Galaxy. She is most commonly referred to simply as "Trillian", a modification of her birth name, which she adopted because it sounded more "space-like". According to the movie version, her middle name is Marie. Physically, she is described as "a slim, darkish humanoid, with long waves of black hair, a full mouth, an odd little knob of a nose and ridiculously brown eyes," looking "vaguely Arabic."
7
+
8
+ Source: https://en.wikipedia.org/wiki/Trillian_(character)
@@ -0,0 +1,6 @@
1
+ ---
2
+ title: First post
3
+ author: trillian
4
+ ---
5
+
6
+ # First post
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: One
3
+ ---
4
+
5
+ # One
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: Two
3
+ ---
4
+
5
+ # Two
@@ -0,0 +1,6 @@
1
+ ---
2
+ t"
3
+ author: trillian
4
+ ---
5
+
6
+ # One
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { createBuilder as origCreateBuilder } from "./builder";
3
+ import path from "node:path";
4
+ import { existsSync } from "node:fs";
5
+ import fs from "node:fs/promises";
6
+ import { Emitter } from "./events";
7
+
8
+ // we mock the watcher module, because it causes problems in some situations
9
+ // we test the watcher module separately
10
+ vi.mock("./watcher", async () => {
11
+ return {
12
+ createWatcher: async (_: Emitter, paths: Array<string>) => {
13
+ return {
14
+ paths,
15
+ unsubscribe: async () => {},
16
+ };
17
+ },
18
+ };
19
+ });
20
+
21
+ describe("builder", () => {
22
+ afterEach(async () => {
23
+ if (existsSync("tmp")) {
24
+ await fs.rm("tmp", { recursive: true });
25
+ }
26
+ });
27
+
28
+ async function createBuilder(name: string) {
29
+ const configPath = path.join(__dirname, "__tests__", name + ".ts");
30
+ const outputDir = path.join(
31
+ __dirname,
32
+ "__tests__",
33
+ ".content-collections",
34
+ "generated-" + name
35
+ );
36
+ const builder = await origCreateBuilder(configPath, {
37
+ configName: name + ".mjs",
38
+ outputDir,
39
+ });
40
+ return {
41
+ builder,
42
+ outputDir,
43
+ };
44
+ }
45
+
46
+ describe("build", () => {
47
+ it("should build", async () => {
48
+ const { builder, outputDir } = await createBuilder("config.002");
49
+ await builder.build();
50
+
51
+ const { allPosts } = await import(path.resolve(outputDir, "index.js"));
52
+ expect(allPosts.length).toBe(1);
53
+ });
54
+
55
+ it("should call onSuccess", async () => {
56
+ const { builder, outputDir } = await createBuilder("config.004");
57
+ await builder.build();
58
+
59
+ const { allPosts, allAuthors } = await import(
60
+ path.resolve(outputDir, "index.js")
61
+ );
62
+
63
+ expect(allPosts.length).toBe(1);
64
+ const postsLength = await fs.readFile(
65
+ path.join("tmp", "posts.length"),
66
+ "utf-8"
67
+ );
68
+ expect(postsLength).toBe("1");
69
+
70
+ expect(allAuthors.length).toBe(1);
71
+ const authorsLength = await fs.readFile(
72
+ path.join("tmp", "authors.length"),
73
+ "utf-8"
74
+ );
75
+ expect(authorsLength).toBe("1");
76
+ });
77
+
78
+ it("should emit build:start and build:end", async () => {
79
+ const { builder } = await createBuilder("config.002");
80
+ const events: Array<string> = [];
81
+
82
+ builder.on("builder:start", (event) => {
83
+ events.push("builder:start");
84
+ expect(event.startedAt).toBeDefined();
85
+ });
86
+ builder.on("builder:end", (event) => {
87
+ events.push("builder:end");
88
+ expect(event.startedAt).toBeDefined();
89
+ expect(event.endedAt).toBeDefined();
90
+ });
91
+
92
+ await builder.build();
93
+
94
+ expect(events).toEqual(["builder:start", "builder:end"]);
95
+ });
96
+ });
97
+
98
+ describe("sync", () => {
99
+ beforeEach(async () => {
100
+ const sourceDir = path.join("tmp", "sources", "posts");
101
+ if (!existsSync(sourceDir)) {
102
+ await fs.mkdir(sourceDir, { recursive: true });
103
+ }
104
+
105
+ const configPath = path.join(__dirname, "__tests__", "config.002.ts");
106
+ await fs.copyFile(configPath, path.join("tmp", "config.ts"));
107
+ await fs.copyFile(
108
+ path.join(__dirname, "__tests__", "sources", "posts", "first.md"),
109
+ path.join(sourceDir, "first.md")
110
+ );
111
+ });
112
+
113
+ it("should sync new files", async () => {
114
+ const builder = await origCreateBuilder(path.join("tmp", "config.ts"), {
115
+ configName: "sync.new.file.ts",
116
+ cacheDir: path.join("tmp", "cache"),
117
+ outputDir: path.join("tmp", "output-new"),
118
+ });
119
+
120
+ await builder.build();
121
+
122
+ const newFile = path.join("tmp", "sources", "posts", "second.md");
123
+ await fs.writeFile(newFile, "---\ntitle: Second\n---\nSecond post");
124
+
125
+ const synced = await builder.sync("create", newFile);
126
+ expect(synced).toBe(true);
127
+
128
+ await builder.build();
129
+
130
+ const { allPosts } = await import(
131
+ path.resolve("tmp", "output-new", "index.js")
132
+ );
133
+
134
+ expect(allPosts.length).toBe(2);
135
+ });
136
+
137
+ it("should sync deleted files", async () => {
138
+ const builder = await origCreateBuilder(path.join("tmp", "config.ts"), {
139
+ configName: "sync.rm.file.ts",
140
+ cacheDir: path.join("tmp", "cache"),
141
+ outputDir: path.join("tmp", "output-rm"),
142
+ });
143
+
144
+ await builder.build();
145
+
146
+ const rmFile = path.join("tmp", "sources", "posts", "first.md");
147
+ await fs.rm(rmFile);
148
+
149
+ const synced = await builder.sync("delete", rmFile);
150
+ expect(synced).toBe(true);
151
+
152
+ await builder.build();
153
+
154
+ const { allPosts } = await import(
155
+ path.resolve("tmp", "output-rm", "index.js")
156
+ );
157
+
158
+ expect(allPosts.length).toBe(0);
159
+ });
160
+ });
161
+
162
+ describe("watch", () => {
163
+ it("should return watcher", async () => {
164
+ const { builder } = await createBuilder("config.002");
165
+
166
+ const watcher = await builder.watch();
167
+ expect(watcher).toBeTruthy();
168
+ });
169
+
170
+ it("should pass the collection directories to the watcher", async () => {
171
+ const { builder } = await createBuilder("config.002");
172
+
173
+ const watcher = await builder.watch();
174
+ // @ts-ignore our mock returns a watcher with a paths property
175
+ expect(watcher.paths).toEqual([
176
+ path.join(__dirname, "__tests__", "sources", "posts"),
177
+ ]);
178
+ })
179
+ });
180
+ });
package/src/builder.ts ADDED
@@ -0,0 +1,113 @@
1
+ import {
2
+ createConfigurationReader,
3
+ Options as ConfigurationOptions,
4
+ defaultConfigName,
5
+ } from "./configurationReader";
6
+ import { createCollector } from "./collector";
7
+ import { Modification } from "./types";
8
+ import { createWriter } from "./writer";
9
+ import { createTransformer } from "./transformer";
10
+ import { isDefined } from "./utils";
11
+ import { createSynchronizer } from "./synchronizer";
12
+ import path from "node:path";
13
+ import { createWatcher } from "./watcher";
14
+ import { Events, createEmitter } from "./events";
15
+
16
+ export type BuilderEvents = {
17
+ "builder:start": {
18
+ startedAt: number;
19
+ };
20
+ "builder:end": {
21
+ startedAt: number;
22
+ endedAt: number;
23
+ };
24
+ };
25
+
26
+ type Options = ConfigurationOptions & {
27
+ outputDir?: string;
28
+ };
29
+
30
+ function resolveOutputDir(baseDirectory: string, options: Options) {
31
+ if (options.outputDir) {
32
+ return options.outputDir;
33
+ }
34
+ return path.join(baseDirectory, ".content-collections", "generated");
35
+ }
36
+
37
+ export async function createBuilder(
38
+ configurationPath: string,
39
+ options: Options = {
40
+ configName: defaultConfigName,
41
+ }
42
+ ) {
43
+ const emitter = createEmitter<Events>();
44
+
45
+ const readConfiguration = createConfigurationReader();
46
+ const configuration = await readConfiguration(configurationPath, options);
47
+ const baseDirectory = path.dirname(configurationPath);
48
+ const directory = resolveOutputDir(baseDirectory, options);
49
+
50
+ // TODO: we should not collect files before the user has a chance to register listeners
51
+ const collector = createCollector(emitter, baseDirectory);
52
+ const writer = await createWriter(directory);
53
+
54
+ const [resolved] = await Promise.all([
55
+ collector.collect(configuration.collections),
56
+ writer.createJavaScriptFile(configuration),
57
+ writer.createTypeDefinitionFile(configuration),
58
+ ]);
59
+
60
+ const synchronizer = createSynchronizer(
61
+ collector.collectFile,
62
+ resolved,
63
+ baseDirectory
64
+ );
65
+ const transform = createTransformer(emitter);
66
+
67
+ async function sync(modification: Modification, filePath: string) {
68
+ if (modification === "delete") {
69
+ return synchronizer.deleted(filePath);
70
+ }
71
+ return synchronizer.changed(filePath);
72
+ }
73
+
74
+ async function build() {
75
+ const startedAt = Date.now();
76
+ emitter.emit("builder:start", {
77
+ startedAt,
78
+ });
79
+
80
+ const collections = await transform(resolved);
81
+ await writer.createDataFiles(collections);
82
+
83
+ const pendingOnSuccess = collections
84
+ .filter((collection) => Boolean(collection.onSuccess))
85
+ .map((collection) =>
86
+ collection.onSuccess?.(collection.documents.map((doc) => doc.document))
87
+ );
88
+
89
+ await Promise.all(pendingOnSuccess.filter(isDefined));
90
+
91
+ emitter.emit("builder:end", {
92
+ startedAt,
93
+ endedAt: Date.now(),
94
+ });
95
+ }
96
+
97
+ async function watch() {
98
+ const paths = resolved.map((collection) =>
99
+ path.join(baseDirectory, collection.directory)
100
+ );
101
+ const watcher = await createWatcher(emitter, paths, sync, build);
102
+ return watcher;
103
+ }
104
+
105
+ return {
106
+ sync,
107
+ build,
108
+ watch,
109
+ on: emitter.on,
110
+ };
111
+ }
112
+
113
+ export type Builder = Awaited<ReturnType<typeof createBuilder>>;