astro 4.13.4 → 4.14.1
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/components/Code.astro +9 -0
- package/dist/@types/astro.d.ts +249 -1
- package/dist/actions/consts.d.ts +1 -1
- package/dist/actions/consts.js +1 -1
- package/dist/actions/index.js +12 -21
- package/dist/assets/endpoint/node.js +1 -1
- package/dist/assets/utils/resolveImports.d.ts +9 -0
- package/dist/assets/utils/resolveImports.js +22 -0
- package/dist/cli/add/index.d.ts +2 -2
- package/dist/cli/add/index.js +2 -2
- package/dist/cli/build/index.d.ts +2 -2
- package/dist/cli/build/index.js +5 -1
- package/dist/cli/check/index.d.ts +2 -2
- package/dist/cli/check/index.js +5 -2
- package/dist/cli/db/index.d.ts +4 -3
- package/dist/cli/db/index.js +10 -3
- package/dist/cli/dev/index.d.ts +2 -2
- package/dist/cli/dev/index.js +1 -0
- package/dist/cli/docs/index.d.ts +2 -2
- package/dist/cli/flags.d.ts +3 -1
- package/dist/cli/flags.js +2 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +26 -13
- package/dist/cli/info/index.d.ts +2 -2
- package/dist/cli/preferences/index.d.ts +2 -2
- package/dist/cli/preferences/index.js +1 -1
- package/dist/cli/preview/index.d.ts +2 -2
- package/dist/cli/sync/index.d.ts +2 -2
- package/dist/cli/sync/index.js +5 -2
- package/dist/cli/telemetry/index.d.ts +2 -2
- package/dist/content/consts.d.ts +16 -2
- package/dist/content/consts.js +32 -2
- package/dist/content/content-layer.d.ts +40 -0
- package/dist/content/content-layer.js +253 -0
- package/dist/content/data-store.d.ts +54 -0
- package/dist/content/data-store.js +72 -0
- package/dist/content/loaders/file.d.ts +7 -0
- package/dist/content/loaders/file.js +72 -0
- package/dist/content/loaders/glob.d.ts +25 -0
- package/dist/content/loaders/glob.js +218 -0
- package/dist/content/loaders/index.d.ts +3 -0
- package/dist/content/loaders/index.js +7 -0
- package/dist/content/loaders/types.d.ts +36 -0
- package/dist/content/loaders/types.js +0 -0
- package/dist/content/mutable-data-store.d.ts +77 -0
- package/dist/content/mutable-data-store.js +269 -0
- package/dist/content/runtime.d.ts +46 -8
- package/dist/content/runtime.js +225 -31
- package/dist/content/types-generator.js +123 -35
- package/dist/content/utils.d.ts +307 -2
- package/dist/content/utils.js +101 -7
- package/dist/content/vite-plugin-content-assets.js +9 -1
- package/dist/content/vite-plugin-content-virtual-mod.js +94 -2
- package/dist/core/build/index.js +14 -7
- package/dist/core/build/plugins/plugin-ssr.js +32 -4
- package/dist/core/config/config.d.ts +2 -5
- package/dist/core/config/config.js +0 -12
- package/dist/core/config/index.d.ts +1 -1
- package/dist/core/config/index.js +0 -2
- package/dist/core/config/schema.d.ts +34 -0
- package/dist/core/config/schema.js +6 -2
- package/dist/core/config/settings.js +5 -3
- package/dist/core/constants.js +1 -1
- package/dist/core/create-vite.js +1 -1
- package/dist/core/dev/container.js +2 -1
- package/dist/core/dev/dev.js +33 -3
- package/dist/core/dev/restart.js +25 -10
- package/dist/core/errors/errors-data.d.ts +21 -0
- package/dist/core/errors/errors-data.js +13 -0
- package/dist/core/index.js +1 -1
- package/dist/core/logger/vite.js +1 -1
- package/dist/core/messages.js +2 -2
- package/dist/core/preview/static-preview-server.js +1 -1
- package/dist/core/routing/manifest/create.js +1 -1
- package/dist/core/sync/constants.d.ts +1 -0
- package/dist/core/sync/constants.js +4 -0
- package/dist/core/sync/index.d.ts +12 -4
- package/dist/core/sync/index.js +54 -24
- package/dist/core/sync/write-files.d.ts +4 -0
- package/dist/core/sync/write-files.js +69 -0
- package/dist/core/util.js +1 -1
- package/dist/env/sync.js +6 -4
- package/dist/integrations/hooks.d.ts +7 -1
- package/dist/integrations/hooks.js +54 -0
- package/dist/preferences/index.d.ts +1 -1
- package/dist/preferences/index.js +2 -2
- package/dist/runtime/server/render/server-islands.js +6 -4
- package/dist/vite-plugin-astro-server/response.js +1 -1
- package/dist/vite-plugin-env/index.d.ts +3 -1
- package/dist/vite-plugin-env/index.js +11 -1
- package/dist/vite-plugin-markdown/content-entry-type.js +25 -2
- package/dist/vite-plugin-scanner/index.js +15 -5
- package/package.json +10 -5
- package/templates/content/module.mjs +6 -1
- package/templates/content/types.d.ts +18 -5
- package/types/content.d.ts +34 -1
- package/dist/core/sync/setup-env-ts.d.ts +0 -8
- package/dist/core/sync/setup-env-ts.js +0 -79
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
1
2
|
import * as colors from "kleur/colors";
|
|
2
|
-
import yargs from "yargs-parser";
|
|
3
3
|
import { ASTRO_VERSION } from "../core/constants.js";
|
|
4
4
|
async function printAstroHelp() {
|
|
5
5
|
const { printHelp } = await import("../core/messages.js");
|
|
@@ -43,9 +43,9 @@ function printVersion() {
|
|
|
43
43
|
console.log();
|
|
44
44
|
console.log(` ${colors.bgGreen(colors.black(` astro `))} ${colors.green(`v${ASTRO_VERSION}`)}`);
|
|
45
45
|
}
|
|
46
|
-
function resolveCommand(
|
|
47
|
-
const cmd =
|
|
48
|
-
if (
|
|
46
|
+
function resolveCommand(args) {
|
|
47
|
+
const cmd = args.positionals[2];
|
|
48
|
+
if (args.values.version) return "version";
|
|
49
49
|
const supportedCommands = /* @__PURE__ */ new Set([
|
|
50
50
|
"add",
|
|
51
51
|
"sync",
|
|
@@ -68,7 +68,8 @@ function resolveCommand(flags) {
|
|
|
68
68
|
}
|
|
69
69
|
return "help";
|
|
70
70
|
}
|
|
71
|
-
async function runCommand(cmd,
|
|
71
|
+
async function runCommand(cmd, args) {
|
|
72
|
+
const flags = args.values;
|
|
72
73
|
switch (cmd) {
|
|
73
74
|
case "help":
|
|
74
75
|
await printAstroHelp();
|
|
@@ -88,7 +89,7 @@ async function runCommand(cmd, flags) {
|
|
|
88
89
|
}
|
|
89
90
|
case "telemetry": {
|
|
90
91
|
const { update } = await import("./telemetry/index.js");
|
|
91
|
-
const subcommand =
|
|
92
|
+
const subcommand = args.positionals[3];
|
|
92
93
|
await update(subcommand, { flags });
|
|
93
94
|
return;
|
|
94
95
|
}
|
|
@@ -99,7 +100,7 @@ async function runCommand(cmd, flags) {
|
|
|
99
100
|
}
|
|
100
101
|
case "preferences": {
|
|
101
102
|
const { preferences } = await import("./preferences/index.js");
|
|
102
|
-
const [subcommand, key, value] =
|
|
103
|
+
const [subcommand, key, value] = args.positionals.slice(3);
|
|
103
104
|
const exitCode = await preferences(subcommand, key, value, { flags });
|
|
104
105
|
return process.exit(exitCode);
|
|
105
106
|
}
|
|
@@ -113,7 +114,7 @@ async function runCommand(cmd, flags) {
|
|
|
113
114
|
switch (cmd) {
|
|
114
115
|
case "add": {
|
|
115
116
|
const { add } = await import("./add/index.js");
|
|
116
|
-
const packages =
|
|
117
|
+
const packages = args.positionals.slice(3);
|
|
117
118
|
await add(packages, { flags });
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
@@ -123,7 +124,7 @@ async function runCommand(cmd, flags) {
|
|
|
123
124
|
case "link":
|
|
124
125
|
case "init": {
|
|
125
126
|
const { db } = await import("./db/index.js");
|
|
126
|
-
await db({ flags });
|
|
127
|
+
await db({ positionals: args.positionals, flags });
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
case "dev": {
|
|
@@ -161,11 +162,23 @@ async function runCommand(cmd, flags) {
|
|
|
161
162
|
}
|
|
162
163
|
throw new Error(`Error running ${cmd} -- no command found.`);
|
|
163
164
|
}
|
|
164
|
-
async function cli(
|
|
165
|
-
const
|
|
166
|
-
|
|
165
|
+
async function cli(argv) {
|
|
166
|
+
const args = parseArgs({
|
|
167
|
+
args: argv,
|
|
168
|
+
allowPositionals: true,
|
|
169
|
+
strict: false,
|
|
170
|
+
options: {
|
|
171
|
+
global: { type: "boolean", short: "g" },
|
|
172
|
+
host: { type: "string" },
|
|
173
|
+
// Can be boolean too, which is covered by `strict: false`
|
|
174
|
+
open: { type: "string" }
|
|
175
|
+
// Can be boolean too, which is covered by `strict: false`
|
|
176
|
+
// TODO: Add more flags here
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const cmd = resolveCommand(args);
|
|
167
180
|
try {
|
|
168
|
-
await runCommand(cmd,
|
|
181
|
+
await runCommand(cmd, args);
|
|
169
182
|
} catch (err) {
|
|
170
183
|
const { throwAndExit } = await import("./throw-and-exit.js");
|
|
171
184
|
await throwAndExit(cmd, err);
|
package/dist/cli/info/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type yargs from 'yargs-parser';
|
|
2
1
|
import type { AstroConfig, AstroUserConfig } from '../../@types/astro.js';
|
|
2
|
+
import { type Flags } from '../flags.js';
|
|
3
3
|
interface InfoOptions {
|
|
4
|
-
flags:
|
|
4
|
+
flags: Flags;
|
|
5
5
|
}
|
|
6
6
|
export declare function getInfoOutput({ userConfig, print, }: {
|
|
7
7
|
userConfig: AstroUserConfig | AstroConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Flags } from '../flags.js';
|
|
2
2
|
interface PreferencesOptions {
|
|
3
|
-
flags:
|
|
3
|
+
flags: Flags;
|
|
4
4
|
}
|
|
5
5
|
declare const PREFERENCES_SUBCOMMANDS: readonly ["get", "set", "enable", "disable", "delete", "reset", "list"];
|
|
6
6
|
export type Subcommand = (typeof PREFERENCES_SUBCOMMANDS)[number];
|
|
@@ -55,7 +55,7 @@ async function preferences(subcommand, key, value, { flags }) {
|
|
|
55
55
|
const settings = await createSettings(astroConfig, fileURLToPath(astroConfig.root));
|
|
56
56
|
const opts = {
|
|
57
57
|
location: flags.global ? "global" : void 0,
|
|
58
|
-
json: flags.json
|
|
58
|
+
json: !!flags.json
|
|
59
59
|
};
|
|
60
60
|
if (subcommand === "list") {
|
|
61
61
|
return listPreferences(settings, opts);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Flags } from '../flags.js';
|
|
2
2
|
interface PreviewOptions {
|
|
3
|
-
flags:
|
|
3
|
+
flags: Flags;
|
|
4
4
|
}
|
|
5
5
|
export declare function preview({ flags }: PreviewOptions): Promise<import("../../@types/astro.js").PreviewServer | undefined>;
|
|
6
6
|
export {};
|
package/dist/cli/sync/index.d.ts
CHANGED
package/dist/cli/sync/index.js
CHANGED
|
@@ -7,14 +7,17 @@ async function sync({ flags }) {
|
|
|
7
7
|
commandName: "astro sync",
|
|
8
8
|
usage: "[...flags]",
|
|
9
9
|
tables: {
|
|
10
|
-
Flags: [
|
|
10
|
+
Flags: [
|
|
11
|
+
["--force", "Clear the content layer cache, forcing a full rebuild."],
|
|
12
|
+
["--help (-h)", "See all available flags."]
|
|
13
|
+
]
|
|
11
14
|
},
|
|
12
15
|
description: `Generates TypeScript types for all Astro modules.`
|
|
13
16
|
});
|
|
14
17
|
return 0;
|
|
15
18
|
}
|
|
16
19
|
try {
|
|
17
|
-
await _sync(
|
|
20
|
+
await _sync(flagsToAstroInlineConfig(flags), { telemetry: true });
|
|
18
21
|
return 0;
|
|
19
22
|
} catch (_) {
|
|
20
23
|
return 1;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Flags } from '../flags.js';
|
|
2
2
|
interface TelemetryOptions {
|
|
3
|
-
flags:
|
|
3
|
+
flags: Flags;
|
|
4
4
|
}
|
|
5
5
|
export declare function notify(): Promise<void>;
|
|
6
6
|
export declare function update(subcommand: string, { flags }: TelemetryOptions): Promise<void>;
|
package/dist/content/consts.d.ts
CHANGED
|
@@ -2,10 +2,24 @@ export declare const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
|
|
|
2
2
|
export declare const CONTENT_RENDER_FLAG = "astroRenderContent";
|
|
3
3
|
export declare const CONTENT_FLAG = "astroContentCollectionEntry";
|
|
4
4
|
export declare const DATA_FLAG = "astroDataCollectionEntry";
|
|
5
|
+
export declare const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
|
|
6
|
+
export declare const CONTENT_MODULE_FLAG = "astroContentModuleFlag";
|
|
5
7
|
export declare const VIRTUAL_MODULE_ID = "astro:content";
|
|
6
8
|
export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
|
|
9
|
+
export declare const DATA_STORE_VIRTUAL_ID = "astro:data-layer-content";
|
|
10
|
+
export declare const RESOLVED_DATA_STORE_VIRTUAL_ID: string;
|
|
11
|
+
export declare const MODULES_MJS_ID = "astro:content-module-imports";
|
|
12
|
+
export declare const MODULES_MJS_VIRTUAL_ID: string;
|
|
13
|
+
export declare const DEFERRED_MODULE = "astro:content-layer-deferred-module";
|
|
14
|
+
export declare const ASSET_IMPORTS_VIRTUAL_ID = "astro:asset-imports";
|
|
15
|
+
export declare const ASSET_IMPORTS_RESOLVED_STUB_ID: string;
|
|
7
16
|
export declare const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
|
|
8
17
|
export declare const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
|
|
9
18
|
export declare const SCRIPTS_PLACEHOLDER = "@@ASTRO-SCRIPTS@@";
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
19
|
+
export declare const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
|
|
20
|
+
export declare const CONTENT_FLAGS: readonly ["astroContentCollectionEntry", "astroRenderContent", "astroDataCollectionEntry", "astroPropagatedAssets", "astroContentImageFlag", "astroContentModuleFlag"];
|
|
21
|
+
export declare const CONTENT_TYPES_FILE = "astro/content.d.ts";
|
|
22
|
+
export declare const DATA_STORE_FILE = "data-store.json";
|
|
23
|
+
export declare const ASSET_IMPORTS_FILE = "assets.mjs";
|
|
24
|
+
export declare const MODULES_IMPORTS_FILE = "modules.mjs";
|
|
25
|
+
export declare const CONTENT_LAYER_TYPE = "content_layer";
|
package/dist/content/consts.js
CHANGED
|
@@ -2,26 +2,56 @@ const PROPAGATED_ASSET_FLAG = "astroPropagatedAssets";
|
|
|
2
2
|
const CONTENT_RENDER_FLAG = "astroRenderContent";
|
|
3
3
|
const CONTENT_FLAG = "astroContentCollectionEntry";
|
|
4
4
|
const DATA_FLAG = "astroDataCollectionEntry";
|
|
5
|
+
const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
|
|
6
|
+
const CONTENT_MODULE_FLAG = "astroContentModuleFlag";
|
|
5
7
|
const VIRTUAL_MODULE_ID = "astro:content";
|
|
6
8
|
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
9
|
+
const DATA_STORE_VIRTUAL_ID = "astro:data-layer-content";
|
|
10
|
+
const RESOLVED_DATA_STORE_VIRTUAL_ID = "\0" + DATA_STORE_VIRTUAL_ID;
|
|
11
|
+
const MODULES_MJS_ID = "astro:content-module-imports";
|
|
12
|
+
const MODULES_MJS_VIRTUAL_ID = "\0" + MODULES_MJS_ID;
|
|
13
|
+
const DEFERRED_MODULE = "astro:content-layer-deferred-module";
|
|
14
|
+
const ASSET_IMPORTS_VIRTUAL_ID = "astro:asset-imports";
|
|
15
|
+
const ASSET_IMPORTS_RESOLVED_STUB_ID = "\0" + ASSET_IMPORTS_VIRTUAL_ID;
|
|
7
16
|
const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
|
|
8
17
|
const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
|
|
9
18
|
const SCRIPTS_PLACEHOLDER = "@@ASTRO-SCRIPTS@@";
|
|
19
|
+
const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
|
|
10
20
|
const CONTENT_FLAGS = [
|
|
11
21
|
CONTENT_FLAG,
|
|
12
22
|
CONTENT_RENDER_FLAG,
|
|
13
23
|
DATA_FLAG,
|
|
14
|
-
PROPAGATED_ASSET_FLAG
|
|
24
|
+
PROPAGATED_ASSET_FLAG,
|
|
25
|
+
CONTENT_IMAGE_FLAG,
|
|
26
|
+
CONTENT_MODULE_FLAG
|
|
15
27
|
];
|
|
16
|
-
const CONTENT_TYPES_FILE = "
|
|
28
|
+
const CONTENT_TYPES_FILE = "astro/content.d.ts";
|
|
29
|
+
const DATA_STORE_FILE = "data-store.json";
|
|
30
|
+
const ASSET_IMPORTS_FILE = "assets.mjs";
|
|
31
|
+
const MODULES_IMPORTS_FILE = "modules.mjs";
|
|
32
|
+
const CONTENT_LAYER_TYPE = "content_layer";
|
|
17
33
|
export {
|
|
34
|
+
ASSET_IMPORTS_FILE,
|
|
35
|
+
ASSET_IMPORTS_RESOLVED_STUB_ID,
|
|
36
|
+
ASSET_IMPORTS_VIRTUAL_ID,
|
|
18
37
|
CONTENT_FLAG,
|
|
19
38
|
CONTENT_FLAGS,
|
|
39
|
+
CONTENT_IMAGE_FLAG,
|
|
40
|
+
CONTENT_LAYER_TYPE,
|
|
41
|
+
CONTENT_MODULE_FLAG,
|
|
20
42
|
CONTENT_RENDER_FLAG,
|
|
21
43
|
CONTENT_TYPES_FILE,
|
|
22
44
|
DATA_FLAG,
|
|
45
|
+
DATA_STORE_FILE,
|
|
46
|
+
DATA_STORE_VIRTUAL_ID,
|
|
47
|
+
DEFERRED_MODULE,
|
|
48
|
+
IMAGE_IMPORT_PREFIX,
|
|
23
49
|
LINKS_PLACEHOLDER,
|
|
50
|
+
MODULES_IMPORTS_FILE,
|
|
51
|
+
MODULES_MJS_ID,
|
|
52
|
+
MODULES_MJS_VIRTUAL_ID,
|
|
24
53
|
PROPAGATED_ASSET_FLAG,
|
|
54
|
+
RESOLVED_DATA_STORE_VIRTUAL_ID,
|
|
25
55
|
RESOLVED_VIRTUAL_MODULE_ID,
|
|
26
56
|
SCRIPTS_PLACEHOLDER,
|
|
27
57
|
STYLES_PLACEHOLDER,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FSWatcher } from 'vite';
|
|
2
|
+
import type { AstroSettings } from '../@types/astro.js';
|
|
3
|
+
import type { Logger } from '../core/logger/core.js';
|
|
4
|
+
import type { LoaderContext } from './loaders/types.js';
|
|
5
|
+
import type { MutableDataStore } from './mutable-data-store.js';
|
|
6
|
+
export interface ContentLayerOptions {
|
|
7
|
+
store: MutableDataStore;
|
|
8
|
+
settings: AstroSettings;
|
|
9
|
+
logger: Logger;
|
|
10
|
+
watcher?: FSWatcher;
|
|
11
|
+
}
|
|
12
|
+
export declare class ContentLayer {
|
|
13
|
+
#private;
|
|
14
|
+
constructor({ settings, logger, store, watcher }: ContentLayerOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Whether the content layer is currently loading content
|
|
17
|
+
*/
|
|
18
|
+
get loading(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Watch for changes to the content config and trigger a sync when it changes.
|
|
21
|
+
*/
|
|
22
|
+
watchContentConfig(): void;
|
|
23
|
+
unwatchContentConfig(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Run the `load()` method of each collection's loader, which will load the data and save it in the data store.
|
|
26
|
+
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
|
|
27
|
+
* perform an incremental update. After the data is loaded, the data store is written to disk.
|
|
28
|
+
*/
|
|
29
|
+
sync(): Promise<void>;
|
|
30
|
+
regenerateCollectionFileManifest(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare function simpleLoader<TData extends {
|
|
33
|
+
id: string;
|
|
34
|
+
}>(handler: () => Array<TData> | Promise<Array<TData>>, context: LoaderContext): Promise<void>;
|
|
35
|
+
export declare const globalContentLayer: {
|
|
36
|
+
initialized: () => boolean;
|
|
37
|
+
init: (options: ContentLayerOptions) => ContentLayer;
|
|
38
|
+
get: () => ContentLayer;
|
|
39
|
+
dispose: () => void;
|
|
40
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import xxhash from "xxhash-wasm";
|
|
5
|
+
import { AstroUserError } from "../core/errors/errors.js";
|
|
6
|
+
import {
|
|
7
|
+
ASSET_IMPORTS_FILE,
|
|
8
|
+
CONTENT_LAYER_TYPE,
|
|
9
|
+
DATA_STORE_FILE,
|
|
10
|
+
MODULES_IMPORTS_FILE
|
|
11
|
+
} from "./consts.js";
|
|
12
|
+
import { getEntryDataAndImages, globalContentConfigObserver, posixRelative } from "./utils.js";
|
|
13
|
+
class ContentLayer {
|
|
14
|
+
#logger;
|
|
15
|
+
#store;
|
|
16
|
+
#settings;
|
|
17
|
+
#watcher;
|
|
18
|
+
#lastConfigDigest;
|
|
19
|
+
#unsubscribe;
|
|
20
|
+
#generateDigest;
|
|
21
|
+
#loading = false;
|
|
22
|
+
constructor({ settings, logger, store, watcher }) {
|
|
23
|
+
watcher?.setMaxListeners(50);
|
|
24
|
+
this.#logger = logger;
|
|
25
|
+
this.#store = store;
|
|
26
|
+
this.#settings = settings;
|
|
27
|
+
this.#watcher = watcher;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether the content layer is currently loading content
|
|
31
|
+
*/
|
|
32
|
+
get loading() {
|
|
33
|
+
return this.#loading;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Watch for changes to the content config and trigger a sync when it changes.
|
|
37
|
+
*/
|
|
38
|
+
watchContentConfig() {
|
|
39
|
+
this.#unsubscribe?.();
|
|
40
|
+
this.#unsubscribe = globalContentConfigObserver.subscribe(async (ctx) => {
|
|
41
|
+
if (!this.#loading && ctx.status === "loaded" && ctx.config.digest !== this.#lastConfigDigest) {
|
|
42
|
+
this.sync();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
unwatchContentConfig() {
|
|
47
|
+
this.#unsubscribe?.();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run the `load()` method of each collection's loader, which will load the data and save it in the data store.
|
|
51
|
+
* The loader itself is responsible for deciding whether this will clear and reload the full collection, or
|
|
52
|
+
* perform an incremental update. After the data is loaded, the data store is written to disk.
|
|
53
|
+
*/
|
|
54
|
+
async sync() {
|
|
55
|
+
if (this.#loading) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.#loading = true;
|
|
59
|
+
try {
|
|
60
|
+
await this.#doSync();
|
|
61
|
+
} finally {
|
|
62
|
+
this.#loading = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async #getGenerateDigest() {
|
|
66
|
+
if (this.#generateDigest) {
|
|
67
|
+
return this.#generateDigest;
|
|
68
|
+
}
|
|
69
|
+
const { h64ToString } = await xxhash();
|
|
70
|
+
this.#generateDigest = (data) => {
|
|
71
|
+
const dataString = typeof data === "string" ? data : JSON.stringify(data);
|
|
72
|
+
return h64ToString(dataString);
|
|
73
|
+
};
|
|
74
|
+
return this.#generateDigest;
|
|
75
|
+
}
|
|
76
|
+
async #getLoaderContext({
|
|
77
|
+
collectionName,
|
|
78
|
+
loaderName = "content",
|
|
79
|
+
parseData
|
|
80
|
+
}) {
|
|
81
|
+
return {
|
|
82
|
+
collection: collectionName,
|
|
83
|
+
store: this.#store.scopedStore(collectionName),
|
|
84
|
+
meta: this.#store.metaStore(collectionName),
|
|
85
|
+
logger: this.#logger.forkIntegrationLogger(loaderName),
|
|
86
|
+
settings: this.#settings,
|
|
87
|
+
parseData,
|
|
88
|
+
generateDigest: await this.#getGenerateDigest(),
|
|
89
|
+
watcher: this.#watcher
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async #doSync() {
|
|
93
|
+
const contentConfig = globalContentConfigObserver.get();
|
|
94
|
+
const logger = this.#logger.forkIntegrationLogger("content");
|
|
95
|
+
if (contentConfig?.status !== "loaded") {
|
|
96
|
+
logger.debug("Content config not loaded, skipping sync");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!this.#settings.config.experimental.contentLayer) {
|
|
100
|
+
const contentLayerCollections = Object.entries(contentConfig.config.collections).filter(
|
|
101
|
+
([_, collection]) => collection.type === CONTENT_LAYER_TYPE
|
|
102
|
+
);
|
|
103
|
+
if (contentLayerCollections.length > 0) {
|
|
104
|
+
throw new AstroUserError(
|
|
105
|
+
`The following collections have a loader defined, but the content layer is not enabled: ${contentLayerCollections.map(([title]) => title).join(", ")}.`,
|
|
106
|
+
"To enable the Content Layer API, set `experimental: { contentLayer: true }` in your Astro config file."
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
logger.info("Syncing content");
|
|
112
|
+
const { digest: currentConfigDigest } = contentConfig.config;
|
|
113
|
+
this.#lastConfigDigest = currentConfigDigest;
|
|
114
|
+
const previousConfigDigest = await this.#store.metaStore().get("config-digest");
|
|
115
|
+
if (currentConfigDigest && previousConfigDigest !== currentConfigDigest) {
|
|
116
|
+
logger.info("Content config changed, clearing cache");
|
|
117
|
+
this.#store.clearAll();
|
|
118
|
+
await this.#store.metaStore().set("config-digest", currentConfigDigest);
|
|
119
|
+
}
|
|
120
|
+
await Promise.all(
|
|
121
|
+
Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
|
|
122
|
+
if (collection.type !== CONTENT_LAYER_TYPE) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let { schema } = collection;
|
|
126
|
+
if (!schema && typeof collection.loader === "object") {
|
|
127
|
+
schema = collection.loader.schema;
|
|
128
|
+
if (typeof schema === "function") {
|
|
129
|
+
schema = await schema();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const collectionWithResolvedSchema = { ...collection, schema };
|
|
133
|
+
const parseData = async ({ id, data, filePath = "" }) => {
|
|
134
|
+
const { imageImports, data: parsedData } = await getEntryDataAndImages(
|
|
135
|
+
{
|
|
136
|
+
id,
|
|
137
|
+
collection: name,
|
|
138
|
+
unvalidatedData: data,
|
|
139
|
+
_internal: {
|
|
140
|
+
rawData: void 0,
|
|
141
|
+
filePath
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
collectionWithResolvedSchema,
|
|
145
|
+
false
|
|
146
|
+
);
|
|
147
|
+
if (imageImports?.length) {
|
|
148
|
+
this.#store.addAssetImports(
|
|
149
|
+
imageImports,
|
|
150
|
+
// This path may already be relative, if we're re-parsing an existing entry
|
|
151
|
+
isAbsolute(filePath) ? posixRelative(fileURLToPath(this.#settings.config.root), filePath) : filePath
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return parsedData;
|
|
155
|
+
};
|
|
156
|
+
const context = await this.#getLoaderContext({
|
|
157
|
+
collectionName: name,
|
|
158
|
+
parseData,
|
|
159
|
+
loaderName: collection.loader.name
|
|
160
|
+
});
|
|
161
|
+
if (typeof collection.loader === "function") {
|
|
162
|
+
return simpleLoader(collection.loader, context);
|
|
163
|
+
}
|
|
164
|
+
if (!collection.loader.load) {
|
|
165
|
+
throw new Error(`Collection loader for ${name} does not have a load method`);
|
|
166
|
+
}
|
|
167
|
+
return collection.loader.load(context);
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
if (!existsSync(this.#settings.config.cacheDir)) {
|
|
171
|
+
await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
|
|
172
|
+
}
|
|
173
|
+
const cacheFile = new URL(DATA_STORE_FILE, this.#settings.config.cacheDir);
|
|
174
|
+
await this.#store.writeToDisk(cacheFile);
|
|
175
|
+
if (!existsSync(this.#settings.dotAstroDir)) {
|
|
176
|
+
await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
|
|
177
|
+
}
|
|
178
|
+
const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
|
|
179
|
+
await this.#store.writeAssetImports(assetImportsFile);
|
|
180
|
+
const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir);
|
|
181
|
+
await this.#store.writeModuleImports(modulesImportsFile);
|
|
182
|
+
logger.info("Synced content");
|
|
183
|
+
if (this.#settings.config.experimental.contentIntellisense) {
|
|
184
|
+
await this.regenerateCollectionFileManifest();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async regenerateCollectionFileManifest() {
|
|
188
|
+
const collectionsManifest = new URL("collections/collections.json", this.#settings.dotAstroDir);
|
|
189
|
+
this.#logger.debug("content", "Regenerating collection file manifest");
|
|
190
|
+
if (existsSync(collectionsManifest)) {
|
|
191
|
+
try {
|
|
192
|
+
const collections = await fs.readFile(collectionsManifest, "utf-8");
|
|
193
|
+
const collectionsJson = JSON.parse(collections);
|
|
194
|
+
collectionsJson.entries ??= {};
|
|
195
|
+
for (const { hasSchema, name } of collectionsJson.collections) {
|
|
196
|
+
if (!hasSchema) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const entries = this.#store.values(name);
|
|
200
|
+
if (!entries?.[0]?.filePath) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (const { filePath } of entries) {
|
|
204
|
+
if (!filePath) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const key = new URL(filePath, this.#settings.config.root).href.toLowerCase();
|
|
208
|
+
collectionsJson.entries[key] = name;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
await fs.writeFile(collectionsManifest, JSON.stringify(collectionsJson, null, 2));
|
|
212
|
+
} catch {
|
|
213
|
+
this.#logger.error("content", "Failed to regenerate collection file manifest");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.#logger.debug("content", "Regenerated collection file manifest");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function simpleLoader(handler, context) {
|
|
220
|
+
const data = await handler();
|
|
221
|
+
context.store.clear();
|
|
222
|
+
for (const raw of data) {
|
|
223
|
+
const item = await context.parseData({ id: raw.id, data: raw });
|
|
224
|
+
context.store.set({ id: raw.id, data: item });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function contentLayerSingleton() {
|
|
228
|
+
let instance = null;
|
|
229
|
+
return {
|
|
230
|
+
initialized: () => Boolean(instance),
|
|
231
|
+
init: (options) => {
|
|
232
|
+
instance?.unwatchContentConfig();
|
|
233
|
+
instance = new ContentLayer(options);
|
|
234
|
+
return instance;
|
|
235
|
+
},
|
|
236
|
+
get: () => {
|
|
237
|
+
if (!instance) {
|
|
238
|
+
throw new Error("Content layer not initialized");
|
|
239
|
+
}
|
|
240
|
+
return instance;
|
|
241
|
+
},
|
|
242
|
+
dispose: () => {
|
|
243
|
+
instance?.unwatchContentConfig();
|
|
244
|
+
instance = null;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const globalContentLayer = contentLayerSingleton();
|
|
249
|
+
export {
|
|
250
|
+
ContentLayer,
|
|
251
|
+
globalContentLayer,
|
|
252
|
+
simpleLoader
|
|
253
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { MarkdownHeading } from '@astrojs/markdown-remark';
|
|
2
|
+
export interface RenderedContent {
|
|
3
|
+
/** Rendered HTML string. If present then `render(entry)` will return a component that renders this HTML. */
|
|
4
|
+
html: string;
|
|
5
|
+
metadata?: {
|
|
6
|
+
/** Any images that are present in this entry. Relative to the {@link DataEntry} filePath. */
|
|
7
|
+
imagePaths?: Array<string>;
|
|
8
|
+
/** Any headings that are present in this file. */
|
|
9
|
+
headings?: MarkdownHeading[];
|
|
10
|
+
/** Raw frontmatter, parsed parsed from the file. This may include data from remark plugins. */
|
|
11
|
+
frontmatter?: Record<string, any>;
|
|
12
|
+
/** Any other metadata that is present in this file. */
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface DataEntry<TData extends Record<string, unknown> = Record<string, unknown>> {
|
|
17
|
+
/** The ID of the entry. Unique per collection. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** The parsed entry data */
|
|
20
|
+
data: TData;
|
|
21
|
+
/** The file path of the content, if applicable. Relative to the site root. */
|
|
22
|
+
filePath?: string;
|
|
23
|
+
/** The raw body of the content, if applicable. */
|
|
24
|
+
body?: string;
|
|
25
|
+
/** An optional content digest, to check if the content has changed. */
|
|
26
|
+
digest?: number | string;
|
|
27
|
+
/** The rendered content of the entry, if applicable. */
|
|
28
|
+
rendered?: RenderedContent;
|
|
29
|
+
/**
|
|
30
|
+
* If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase when calling `renderEntry`.
|
|
31
|
+
*/
|
|
32
|
+
deferredRender?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A read-only data store for content collections. This is used to retrieve data from the content layer at runtime.
|
|
36
|
+
* To add or modify data, use {@link MutableDataStore} instead.
|
|
37
|
+
*/
|
|
38
|
+
export declare class DataStore {
|
|
39
|
+
protected _collections: Map<string, Map<string, any>>;
|
|
40
|
+
constructor();
|
|
41
|
+
get<T = DataEntry>(collectionName: string, key: string): T | undefined;
|
|
42
|
+
entries<T = DataEntry>(collectionName: string): Array<[id: string, T]>;
|
|
43
|
+
values<T = DataEntry>(collectionName: string): Array<T>;
|
|
44
|
+
keys(collectionName: string): Array<string>;
|
|
45
|
+
has(collectionName: string, key: string): boolean;
|
|
46
|
+
hasCollection(collectionName: string): boolean;
|
|
47
|
+
collections(): Map<string, Map<string, any>>;
|
|
48
|
+
/**
|
|
49
|
+
* Attempts to load a DataStore from the virtual module.
|
|
50
|
+
* This only works in Vite.
|
|
51
|
+
*/
|
|
52
|
+
static fromModule(): Promise<DataStore>;
|
|
53
|
+
static fromMap(data: Map<string, Map<string, any>>): Promise<DataStore>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as devalue from "devalue";
|
|
2
|
+
class DataStore {
|
|
3
|
+
_collections = /* @__PURE__ */ new Map();
|
|
4
|
+
constructor() {
|
|
5
|
+
this._collections = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
get(collectionName, key) {
|
|
8
|
+
return this._collections.get(collectionName)?.get(String(key));
|
|
9
|
+
}
|
|
10
|
+
entries(collectionName) {
|
|
11
|
+
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
|
12
|
+
return [...collection.entries()];
|
|
13
|
+
}
|
|
14
|
+
values(collectionName) {
|
|
15
|
+
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
|
16
|
+
return [...collection.values()];
|
|
17
|
+
}
|
|
18
|
+
keys(collectionName) {
|
|
19
|
+
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
|
20
|
+
return [...collection.keys()];
|
|
21
|
+
}
|
|
22
|
+
has(collectionName, key) {
|
|
23
|
+
const collection = this._collections.get(collectionName);
|
|
24
|
+
if (collection) {
|
|
25
|
+
return collection.has(String(key));
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
hasCollection(collectionName) {
|
|
30
|
+
return this._collections.has(collectionName);
|
|
31
|
+
}
|
|
32
|
+
collections() {
|
|
33
|
+
return this._collections;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Attempts to load a DataStore from the virtual module.
|
|
37
|
+
* This only works in Vite.
|
|
38
|
+
*/
|
|
39
|
+
static async fromModule() {
|
|
40
|
+
try {
|
|
41
|
+
const data = await import("astro:data-layer-content");
|
|
42
|
+
const map = devalue.unflatten(data.default);
|
|
43
|
+
return DataStore.fromMap(map);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
return new DataStore();
|
|
47
|
+
}
|
|
48
|
+
static async fromMap(data) {
|
|
49
|
+
const store = new DataStore();
|
|
50
|
+
store._collections = data;
|
|
51
|
+
return store;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function dataStoreSingleton() {
|
|
55
|
+
let instance = void 0;
|
|
56
|
+
return {
|
|
57
|
+
get: async () => {
|
|
58
|
+
if (!instance) {
|
|
59
|
+
instance = DataStore.fromModule();
|
|
60
|
+
}
|
|
61
|
+
return instance;
|
|
62
|
+
},
|
|
63
|
+
set: (store) => {
|
|
64
|
+
instance = store;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const globalDataStore = dataStoreSingleton();
|
|
69
|
+
export {
|
|
70
|
+
DataStore,
|
|
71
|
+
globalDataStore
|
|
72
|
+
};
|