astro 4.13.3 → 4.14.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 (107) hide show
  1. package/components/Code.astro +9 -0
  2. package/dist/@types/astro.d.ts +251 -2
  3. package/dist/actions/consts.d.ts +1 -1
  4. package/dist/actions/consts.js +1 -1
  5. package/dist/actions/index.js +12 -21
  6. package/dist/actions/runtime/virtual/server.js +9 -3
  7. package/dist/actions/runtime/virtual/shared.js +25 -3
  8. package/dist/assets/utils/resolveImports.d.ts +9 -0
  9. package/dist/assets/utils/resolveImports.js +22 -0
  10. package/dist/cli/add/index.d.ts +2 -2
  11. package/dist/cli/add/index.js +2 -2
  12. package/dist/cli/build/index.d.ts +2 -2
  13. package/dist/cli/build/index.js +5 -1
  14. package/dist/cli/check/index.d.ts +2 -2
  15. package/dist/cli/check/index.js +5 -2
  16. package/dist/cli/db/index.d.ts +4 -3
  17. package/dist/cli/db/index.js +10 -3
  18. package/dist/cli/dev/index.d.ts +2 -2
  19. package/dist/cli/dev/index.js +1 -0
  20. package/dist/cli/docs/index.d.ts +2 -2
  21. package/dist/cli/flags.d.ts +3 -1
  22. package/dist/cli/flags.js +2 -1
  23. package/dist/cli/index.d.ts +1 -1
  24. package/dist/cli/index.js +26 -13
  25. package/dist/cli/info/index.d.ts +2 -2
  26. package/dist/cli/preferences/index.d.ts +2 -2
  27. package/dist/cli/preferences/index.js +1 -1
  28. package/dist/cli/preview/index.d.ts +2 -2
  29. package/dist/cli/sync/index.d.ts +2 -2
  30. package/dist/cli/sync/index.js +5 -2
  31. package/dist/cli/telemetry/index.d.ts +2 -2
  32. package/dist/container/index.js +3 -1
  33. package/dist/content/consts.d.ts +16 -2
  34. package/dist/content/consts.js +32 -2
  35. package/dist/content/content-layer.d.ts +40 -0
  36. package/dist/content/content-layer.js +253 -0
  37. package/dist/content/data-store.d.ts +114 -0
  38. package/dist/content/data-store.js +323 -0
  39. package/dist/content/loaders/file.d.ts +7 -0
  40. package/dist/content/loaders/file.js +72 -0
  41. package/dist/content/loaders/glob.d.ts +25 -0
  42. package/dist/content/loaders/glob.js +218 -0
  43. package/dist/content/loaders/index.d.ts +3 -0
  44. package/dist/content/loaders/index.js +7 -0
  45. package/dist/content/loaders/types.d.ts +36 -0
  46. package/dist/content/loaders/types.js +0 -0
  47. package/dist/content/runtime.d.ts +46 -8
  48. package/dist/content/runtime.js +225 -31
  49. package/dist/content/types-generator.js +125 -35
  50. package/dist/content/utils.d.ts +306 -2
  51. package/dist/content/utils.js +93 -7
  52. package/dist/content/vite-plugin-content-assets.js +9 -1
  53. package/dist/content/vite-plugin-content-virtual-mod.js +94 -2
  54. package/dist/core/app/common.js +4 -1
  55. package/dist/core/app/index.js +5 -3
  56. package/dist/core/app/types.d.ts +3 -1
  57. package/dist/core/build/generate.js +4 -2
  58. package/dist/core/build/index.js +17 -8
  59. package/dist/core/build/plugins/plugin-manifest.js +5 -2
  60. package/dist/core/build/plugins/plugin-ssr.js +35 -3
  61. package/dist/core/build/static-build.js +2 -0
  62. package/dist/core/build/types.d.ts +1 -0
  63. package/dist/core/config/config.d.ts +2 -5
  64. package/dist/core/config/config.js +0 -12
  65. package/dist/core/config/index.d.ts +1 -1
  66. package/dist/core/config/index.js +0 -2
  67. package/dist/core/config/schema.d.ts +34 -0
  68. package/dist/core/config/schema.js +6 -2
  69. package/dist/core/config/settings.js +5 -3
  70. package/dist/core/constants.js +1 -1
  71. package/dist/core/create-vite.js +1 -1
  72. package/dist/core/dev/container.js +2 -1
  73. package/dist/core/dev/dev.js +34 -2
  74. package/dist/core/dev/restart.js +25 -10
  75. package/dist/core/encryption.d.ts +24 -0
  76. package/dist/core/encryption.js +64 -0
  77. package/dist/core/errors/errors-data.d.ts +21 -0
  78. package/dist/core/errors/errors-data.js +13 -0
  79. package/dist/core/index.js +1 -1
  80. package/dist/core/messages.js +2 -2
  81. package/dist/core/render-context.js +1 -0
  82. package/dist/core/server-islands/endpoint.js +5 -1
  83. package/dist/core/sync/constants.d.ts +1 -0
  84. package/dist/core/sync/constants.js +4 -0
  85. package/dist/core/sync/index.d.ts +12 -4
  86. package/dist/core/sync/index.js +56 -25
  87. package/dist/core/sync/write-files.d.ts +4 -0
  88. package/dist/core/sync/write-files.js +69 -0
  89. package/dist/core/util.js +1 -1
  90. package/dist/env/sync.js +6 -4
  91. package/dist/integrations/hooks.d.ts +7 -1
  92. package/dist/integrations/hooks.js +54 -0
  93. package/dist/preferences/index.d.ts +1 -1
  94. package/dist/preferences/index.js +2 -2
  95. package/dist/runtime/server/render/server-islands.js +9 -5
  96. package/dist/vite-plugin-astro-server/plugin.js +2 -0
  97. package/dist/vite-plugin-env/index.d.ts +3 -1
  98. package/dist/vite-plugin-env/index.js +11 -1
  99. package/dist/vite-plugin-markdown/content-entry-type.js +25 -2
  100. package/dist/vite-plugin-scanner/index.js +15 -5
  101. package/dist/vite-plugin-scanner/scan.js +1 -1
  102. package/package.json +15 -9
  103. package/templates/content/module.mjs +6 -1
  104. package/templates/content/types.d.ts +18 -5
  105. package/types/content.d.ts +34 -1
  106. package/dist/core/sync/setup-env-ts.d.ts +0 -8
  107. package/dist/core/sync/setup-env-ts.js +0 -79
@@ -0,0 +1,114 @@
1
+ import { type PathLike } from 'fs';
2
+ import type { MarkdownHeading } from '@astrojs/markdown-remark';
3
+ export interface RenderedContent {
4
+ /** Rendered HTML string. If present then `render(entry)` will return a component that renders this HTML. */
5
+ html: string;
6
+ metadata?: {
7
+ /** Any images that are present in this entry. Relative to the {@link DataEntry} filePath. */
8
+ imagePaths?: Array<string>;
9
+ /** Any headings that are present in this file. */
10
+ headings?: MarkdownHeading[];
11
+ /** Raw frontmatter, parsed parsed from the file. This may include data from remark plugins. */
12
+ frontmatter?: Record<string, any>;
13
+ /** Any other metadata that is present in this file. */
14
+ [key: string]: unknown;
15
+ };
16
+ }
17
+ export interface DataEntry<TData extends Record<string, unknown> = Record<string, unknown>> {
18
+ /** The ID of the entry. Unique per collection. */
19
+ id: string;
20
+ /** The parsed entry data */
21
+ data: TData;
22
+ /** The file path of the content, if applicable. Relative to the site root. */
23
+ filePath?: string;
24
+ /** The raw body of the content, if applicable. */
25
+ body?: string;
26
+ /** An optional content digest, to check if the content has changed. */
27
+ digest?: number | string;
28
+ /** The rendered content of the entry, if applicable. */
29
+ rendered?: RenderedContent;
30
+ /**
31
+ * If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase when calling `renderEntry`.
32
+ */
33
+ deferredRender?: boolean;
34
+ }
35
+ export declare class DataStore {
36
+ #private;
37
+ constructor();
38
+ get<T = DataEntry>(collectionName: string, key: string): T | undefined;
39
+ entries<T = DataEntry>(collectionName: string): Array<[id: string, T]>;
40
+ values<T = DataEntry>(collectionName: string): Array<T>;
41
+ keys(collectionName: string): Array<string>;
42
+ set(collectionName: string, key: string, value: unknown): void;
43
+ delete(collectionName: string, key: string): void;
44
+ clear(collectionName: string): void;
45
+ clearAll(): void;
46
+ has(collectionName: string, key: string): boolean;
47
+ hasCollection(collectionName: string): boolean;
48
+ collections(): Map<string, Map<string, any>>;
49
+ addAssetImport(assetImport: string, filePath: string): void;
50
+ addAssetImports(assets: Array<string>, filePath: string): void;
51
+ addModuleImport(fileName: string): void;
52
+ writeAssetImports(filePath: PathLike): Promise<void>;
53
+ writeModuleImports(filePath: PathLike): Promise<void>;
54
+ scopedStore(collectionName: string): ScopedDataStore;
55
+ /**
56
+ * Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
57
+ */
58
+ metaStore(collectionName?: string): MetaStore;
59
+ toString(): string;
60
+ writeToDisk(filePath: PathLike): Promise<void>;
61
+ /**
62
+ * Attempts to load a DataStore from the virtual module.
63
+ * This only works in Vite.
64
+ */
65
+ static fromModule(): Promise<DataStore>;
66
+ static fromMap(data: Map<string, Map<string, any>>): Promise<DataStore>;
67
+ static fromString(data: string): Promise<DataStore>;
68
+ static fromFile(filePath: string | URL): Promise<DataStore>;
69
+ }
70
+ export interface ScopedDataStore {
71
+ get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) => DataEntry<TData> | undefined;
72
+ entries: () => Array<[id: string, DataEntry]>;
73
+ set: <TData extends Record<string, unknown>>(opts: {
74
+ /** The ID of the entry. Must be unique per collection. */
75
+ id: string;
76
+ /** The data to store. */
77
+ data: TData;
78
+ /** The raw body of the content, if applicable. */
79
+ body?: string;
80
+ /** The file path of the content, if applicable. Relative to the site root. */
81
+ filePath?: string;
82
+ /** A content digest, to check if the content has changed. */
83
+ digest?: number | string;
84
+ /** The rendered content, if applicable. */
85
+ rendered?: RenderedContent;
86
+ /**
87
+ * If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase.
88
+ */
89
+ deferredRender?: boolean;
90
+ }) => boolean;
91
+ values: () => Array<DataEntry>;
92
+ keys: () => Array<string>;
93
+ delete: (key: string) => void;
94
+ clear: () => void;
95
+ has: (key: string) => boolean;
96
+ /**
97
+ * Adds a single asset to the store. This asset will be transformed
98
+ * by Vite, and the URL will be available in the final build.
99
+ * @param fileName
100
+ * @param specifier
101
+ * @returns
102
+ */
103
+ addModuleImport: (fileName: string) => void;
104
+ }
105
+ /**
106
+ * A key-value store for metadata strings. Useful for storing things like sync tokens.
107
+ */
108
+ export interface MetaStore {
109
+ get: (key: string) => string | undefined;
110
+ set: (key: string, value: string) => void;
111
+ has: (key: string) => boolean;
112
+ delete: (key: string) => void;
113
+ }
114
+ export declare function contentModuleToId(fileName: string): string;
@@ -0,0 +1,323 @@
1
+ import { promises as fs, existsSync } from "fs";
2
+ import * as devalue from "devalue";
3
+ import { imageSrcToImportId, importIdToSymbolName } from "../assets/utils/resolveImports.js";
4
+ import { AstroError, AstroErrorData } from "../core/errors/index.js";
5
+ import { CONTENT_MODULE_FLAG, DEFERRED_MODULE } from "./consts.js";
6
+ const SAVE_DEBOUNCE_MS = 500;
7
+ class DataStore {
8
+ #collections = /* @__PURE__ */ new Map();
9
+ #file;
10
+ #assetsFile;
11
+ #modulesFile;
12
+ #saveTimeout;
13
+ #assetsSaveTimeout;
14
+ #modulesSaveTimeout;
15
+ #dirty = false;
16
+ #assetsDirty = false;
17
+ #modulesDirty = false;
18
+ #assetImports = /* @__PURE__ */ new Set();
19
+ #moduleImports = /* @__PURE__ */ new Map();
20
+ constructor() {
21
+ this.#collections = /* @__PURE__ */ new Map();
22
+ }
23
+ get(collectionName, key) {
24
+ return this.#collections.get(collectionName)?.get(String(key));
25
+ }
26
+ entries(collectionName) {
27
+ const collection = this.#collections.get(collectionName) ?? /* @__PURE__ */ new Map();
28
+ return [...collection.entries()];
29
+ }
30
+ values(collectionName) {
31
+ const collection = this.#collections.get(collectionName) ?? /* @__PURE__ */ new Map();
32
+ return [...collection.values()];
33
+ }
34
+ keys(collectionName) {
35
+ const collection = this.#collections.get(collectionName) ?? /* @__PURE__ */ new Map();
36
+ return [...collection.keys()];
37
+ }
38
+ set(collectionName, key, value) {
39
+ const collection = this.#collections.get(collectionName) ?? /* @__PURE__ */ new Map();
40
+ collection.set(String(key), value);
41
+ this.#collections.set(collectionName, collection);
42
+ this.#saveToDiskDebounced();
43
+ }
44
+ delete(collectionName, key) {
45
+ const collection = this.#collections.get(collectionName);
46
+ if (collection) {
47
+ collection.delete(String(key));
48
+ this.#saveToDiskDebounced();
49
+ }
50
+ }
51
+ clear(collectionName) {
52
+ this.#collections.delete(collectionName);
53
+ this.#saveToDiskDebounced();
54
+ }
55
+ clearAll() {
56
+ this.#collections.clear();
57
+ this.#saveToDiskDebounced();
58
+ }
59
+ has(collectionName, key) {
60
+ const collection = this.#collections.get(collectionName);
61
+ if (collection) {
62
+ return collection.has(String(key));
63
+ }
64
+ return false;
65
+ }
66
+ hasCollection(collectionName) {
67
+ return this.#collections.has(collectionName);
68
+ }
69
+ collections() {
70
+ return this.#collections;
71
+ }
72
+ addAssetImport(assetImport, filePath) {
73
+ const id = imageSrcToImportId(assetImport, filePath);
74
+ if (id) {
75
+ this.#assetImports.add(id);
76
+ this.#writeAssetsImportsDebounced();
77
+ }
78
+ }
79
+ addAssetImports(assets, filePath) {
80
+ assets.forEach((asset) => this.addAssetImport(asset, filePath));
81
+ }
82
+ addModuleImport(fileName) {
83
+ const id = contentModuleToId(fileName);
84
+ if (id) {
85
+ this.#moduleImports.set(fileName, id);
86
+ this.#writeModulesImportsDebounced();
87
+ }
88
+ }
89
+ async writeAssetImports(filePath) {
90
+ this.#assetsFile = filePath;
91
+ if (this.#assetImports.size === 0) {
92
+ try {
93
+ await fs.writeFile(filePath, "export default new Map();");
94
+ } catch (err) {
95
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
96
+ }
97
+ }
98
+ if (!this.#assetsDirty && existsSync(filePath)) {
99
+ return;
100
+ }
101
+ const imports = [];
102
+ const exports = [];
103
+ this.#assetImports.forEach((id) => {
104
+ const symbol = importIdToSymbolName(id);
105
+ imports.push(`import ${symbol} from '${id}';`);
106
+ exports.push(`[${JSON.stringify(id)}, ${symbol}]`);
107
+ });
108
+ const code = (
109
+ /* js */
110
+ `
111
+ ${imports.join("\n")}
112
+ export default new Map([${exports.join(", ")}]);
113
+ `
114
+ );
115
+ try {
116
+ await fs.writeFile(filePath, code);
117
+ } catch (err) {
118
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
119
+ }
120
+ this.#assetsDirty = false;
121
+ }
122
+ async writeModuleImports(filePath) {
123
+ this.#modulesFile = filePath;
124
+ if (this.#moduleImports.size === 0) {
125
+ try {
126
+ await fs.writeFile(filePath, "export default new Map();");
127
+ } catch (err) {
128
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
129
+ }
130
+ }
131
+ if (!this.#modulesDirty && existsSync(filePath)) {
132
+ return;
133
+ }
134
+ const lines = [];
135
+ for (const [fileName, specifier] of this.#moduleImports) {
136
+ lines.push(`['${fileName}', () => import('${specifier}')]`);
137
+ }
138
+ const code = `
139
+ export default new Map([
140
+ ${lines.join(",\n")}]);
141
+ `;
142
+ try {
143
+ await fs.writeFile(filePath, code);
144
+ } catch (err) {
145
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
146
+ }
147
+ this.#modulesDirty = false;
148
+ }
149
+ #writeAssetsImportsDebounced() {
150
+ this.#assetsDirty = true;
151
+ if (this.#assetsFile) {
152
+ if (this.#assetsSaveTimeout) {
153
+ clearTimeout(this.#assetsSaveTimeout);
154
+ }
155
+ this.#assetsSaveTimeout = setTimeout(() => {
156
+ this.#assetsSaveTimeout = void 0;
157
+ this.writeAssetImports(this.#assetsFile);
158
+ }, SAVE_DEBOUNCE_MS);
159
+ }
160
+ }
161
+ #writeModulesImportsDebounced() {
162
+ this.#modulesDirty = true;
163
+ if (this.#modulesFile) {
164
+ if (this.#modulesSaveTimeout) {
165
+ clearTimeout(this.#modulesSaveTimeout);
166
+ }
167
+ this.#modulesSaveTimeout = setTimeout(() => {
168
+ this.#modulesSaveTimeout = void 0;
169
+ this.writeModuleImports(this.#modulesFile);
170
+ }, SAVE_DEBOUNCE_MS);
171
+ }
172
+ }
173
+ #saveToDiskDebounced() {
174
+ this.#dirty = true;
175
+ if (this.#file) {
176
+ if (this.#saveTimeout) {
177
+ clearTimeout(this.#saveTimeout);
178
+ }
179
+ this.#saveTimeout = setTimeout(() => {
180
+ this.#saveTimeout = void 0;
181
+ this.writeToDisk(this.#file);
182
+ }, SAVE_DEBOUNCE_MS);
183
+ }
184
+ }
185
+ scopedStore(collectionName) {
186
+ return {
187
+ get: (key) => this.get(collectionName, key),
188
+ entries: () => this.entries(collectionName),
189
+ values: () => this.values(collectionName),
190
+ keys: () => this.keys(collectionName),
191
+ set: ({ id: key, data, body, filePath, deferredRender, digest, rendered }) => {
192
+ if (!key) {
193
+ throw new Error(`ID must be a non-empty string`);
194
+ }
195
+ const id = String(key);
196
+ if (digest) {
197
+ const existing = this.get(collectionName, id);
198
+ if (existing && existing.digest === digest) {
199
+ return false;
200
+ }
201
+ }
202
+ const entry = {
203
+ id,
204
+ data
205
+ };
206
+ if (body) {
207
+ entry.body = body;
208
+ }
209
+ if (filePath) {
210
+ if (filePath.startsWith("/")) {
211
+ throw new Error(`File path must be relative to the site root. Got: ${filePath}`);
212
+ }
213
+ entry.filePath = filePath;
214
+ }
215
+ if (digest) {
216
+ entry.digest = digest;
217
+ }
218
+ if (rendered) {
219
+ entry.rendered = rendered;
220
+ }
221
+ if (deferredRender) {
222
+ entry.deferredRender = deferredRender;
223
+ if (filePath) {
224
+ this.addModuleImport(filePath);
225
+ }
226
+ }
227
+ this.set(collectionName, id, entry);
228
+ return true;
229
+ },
230
+ delete: (key) => this.delete(collectionName, key),
231
+ clear: () => this.clear(collectionName),
232
+ has: (key) => this.has(collectionName, key),
233
+ addAssetImport: (assetImport, fileName) => this.addAssetImport(assetImport, fileName),
234
+ addAssetImports: (assets, fileName) => this.addAssetImports(assets, fileName),
235
+ addModuleImport: (fileName) => this.addModuleImport(fileName)
236
+ };
237
+ }
238
+ /**
239
+ * Returns a MetaStore for a given collection, or if no collection is provided, the default meta collection.
240
+ */
241
+ metaStore(collectionName = ":meta") {
242
+ const collectionKey = `meta:${collectionName}`;
243
+ return {
244
+ get: (key) => this.get(collectionKey, key),
245
+ set: (key, data) => this.set(collectionKey, key, data),
246
+ delete: (key) => this.delete(collectionKey, key),
247
+ has: (key) => this.has(collectionKey, key)
248
+ };
249
+ }
250
+ toString() {
251
+ return devalue.stringify(this.#collections);
252
+ }
253
+ async writeToDisk(filePath) {
254
+ if (!this.#dirty) {
255
+ return;
256
+ }
257
+ try {
258
+ await fs.writeFile(filePath, this.toString());
259
+ this.#file = filePath;
260
+ this.#dirty = false;
261
+ } catch (err) {
262
+ throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
263
+ }
264
+ }
265
+ /**
266
+ * Attempts to load a DataStore from the virtual module.
267
+ * This only works in Vite.
268
+ */
269
+ static async fromModule() {
270
+ try {
271
+ const data = await import("astro:data-layer-content");
272
+ const map = devalue.unflatten(data.default);
273
+ return DataStore.fromMap(map);
274
+ } catch {
275
+ }
276
+ return new DataStore();
277
+ }
278
+ static async fromMap(data) {
279
+ const store = new DataStore();
280
+ store.#collections = data;
281
+ return store;
282
+ }
283
+ static async fromString(data) {
284
+ const map = devalue.parse(data);
285
+ return DataStore.fromMap(map);
286
+ }
287
+ static async fromFile(filePath) {
288
+ try {
289
+ if (existsSync(filePath)) {
290
+ const data = await fs.readFile(filePath, "utf-8");
291
+ return DataStore.fromString(data);
292
+ }
293
+ } catch {
294
+ }
295
+ return new DataStore();
296
+ }
297
+ }
298
+ function dataStoreSingleton() {
299
+ let instance = void 0;
300
+ return {
301
+ get: async () => {
302
+ if (!instance) {
303
+ instance = DataStore.fromModule();
304
+ }
305
+ return instance;
306
+ },
307
+ set: (store) => {
308
+ instance = store;
309
+ }
310
+ };
311
+ }
312
+ function contentModuleToId(fileName) {
313
+ const params = new URLSearchParams(DEFERRED_MODULE);
314
+ params.set("fileName", fileName);
315
+ params.set(CONTENT_MODULE_FLAG, "true");
316
+ return `${DEFERRED_MODULE}?${params.toString()}`;
317
+ }
318
+ const globalDataStore = dataStoreSingleton();
319
+ export {
320
+ DataStore,
321
+ contentModuleToId,
322
+ globalDataStore
323
+ };
@@ -0,0 +1,7 @@
1
+ import type { Loader } from './types.js';
2
+ /**
3
+ * Loads entries from a JSON file. The file must contain an array of objects that contain unique `id` fields, or an object with string keys.
4
+ * @todo Add support for other file types, such as YAML, CSV etc.
5
+ * @param fileName The path to the JSON file to load, relative to the content directory.
6
+ */
7
+ export declare function file(fileName: string): Loader;
@@ -0,0 +1,72 @@
1
+ import { promises as fs, existsSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { posixRelative } from "../utils.js";
4
+ function file(fileName) {
5
+ if (fileName.includes("*")) {
6
+ throw new Error("Glob patterns are not supported in `file` loader. Use `glob` loader instead.");
7
+ }
8
+ async function syncData(filePath, { logger, parseData, store, settings }) {
9
+ let json;
10
+ try {
11
+ const data = await fs.readFile(filePath, "utf-8");
12
+ json = JSON.parse(data);
13
+ } catch (error) {
14
+ logger.error(`Error reading data from ${fileName}`);
15
+ logger.debug(error.message);
16
+ return;
17
+ }
18
+ if (Array.isArray(json)) {
19
+ if (json.length === 0) {
20
+ logger.warn(`No items found in ${fileName}`);
21
+ }
22
+ logger.debug(`Found ${json.length} item array in ${fileName}`);
23
+ store.clear();
24
+ for (const rawItem of json) {
25
+ const id = (rawItem.id ?? rawItem.slug)?.toString();
26
+ if (!id) {
27
+ logger.error(`Item in ${fileName} is missing an id or slug field.`);
28
+ continue;
29
+ }
30
+ const data = await parseData({ id, data: rawItem, filePath });
31
+ store.set({
32
+ id,
33
+ data,
34
+ filePath: posixRelative(fileURLToPath(settings.config.root), filePath)
35
+ });
36
+ }
37
+ } else if (typeof json === "object") {
38
+ const entries = Object.entries(json);
39
+ logger.debug(`Found object with ${entries.length} entries in ${fileName}`);
40
+ store.clear();
41
+ for (const [id, rawItem] of entries) {
42
+ const data = await parseData({ id, data: rawItem, filePath });
43
+ store.set({ id, data });
44
+ }
45
+ } else {
46
+ logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
47
+ }
48
+ }
49
+ return {
50
+ name: "file-loader",
51
+ load: async (options) => {
52
+ const { settings, logger, watcher } = options;
53
+ logger.debug(`Loading data from ${fileName}`);
54
+ const url = new URL(fileName, settings.config.root);
55
+ if (!existsSync(url)) {
56
+ logger.error(`File not found: ${fileName}`);
57
+ return;
58
+ }
59
+ const filePath = fileURLToPath(url);
60
+ await syncData(filePath, options);
61
+ watcher?.on("change", async (changedPath) => {
62
+ if (changedPath === filePath) {
63
+ logger.info(`Reloading data from ${fileName}`);
64
+ await syncData(filePath, options);
65
+ }
66
+ });
67
+ }
68
+ };
69
+ }
70
+ export {
71
+ file
72
+ };
@@ -0,0 +1,25 @@
1
+ import type { Loader } from './types.js';
2
+ export interface GenerateIdOptions {
3
+ /** The path to the entry file, relative to the base directory. */
4
+ entry: string;
5
+ /** The base directory URL. */
6
+ base: URL;
7
+ /** The parsed, unvalidated data of the entry. */
8
+ data: Record<string, unknown>;
9
+ }
10
+ export interface GlobOptions {
11
+ /** The glob pattern to match files, relative to the base directory */
12
+ pattern: string;
13
+ /** The base directory to resolve the glob pattern from. Relative to the root directory, or an absolute file URL. Defaults to `.` */
14
+ base?: string | URL;
15
+ /**
16
+ * Function that generates an ID for an entry. Default implementation generates a slug from the entry path.
17
+ * @returns The ID of the entry. Must be unique per collection.
18
+ **/
19
+ generateId?: (options: GenerateIdOptions) => string;
20
+ }
21
+ /**
22
+ * Loads multiple entries, using a glob pattern to match files.
23
+ * @param pattern A glob pattern to match files, relative to the content directory.
24
+ */
25
+ export declare function glob(globOptions: GlobOptions): Loader;