astro 5.0.0-beta.2 → 5.0.0-beta.4

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/client.d.ts +1 -1
  2. package/components/viewtransitions.css +10 -0
  3. package/dist/actions/consts.d.ts +1 -1
  4. package/dist/actions/consts.js +1 -1
  5. package/dist/actions/runtime/virtual/get-action.js +1 -1
  6. package/dist/assets/utils/transformToPath.js +5 -1
  7. package/dist/cli/add/index.js +12 -14
  8. package/dist/cli/install-package.js +4 -4
  9. package/dist/content/consts.d.ts +5 -3
  10. package/dist/content/consts.js +7 -3
  11. package/dist/content/content-layer.js +20 -8
  12. package/dist/content/data-store.d.ts +2 -0
  13. package/dist/content/loaders/file.d.ts +9 -2
  14. package/dist/content/loaders/file.js +30 -14
  15. package/dist/content/loaders/glob.d.ts +5 -0
  16. package/dist/content/loaders/glob.js +29 -8
  17. package/dist/content/mutable-data-store.d.ts +2 -19
  18. package/dist/content/mutable-data-store.js +14 -1
  19. package/dist/content/runtime.d.ts +16 -10
  20. package/dist/content/runtime.js +40 -19
  21. package/dist/content/types-generator.js +18 -14
  22. package/dist/content/utils.d.ts +45 -30
  23. package/dist/content/utils.js +98 -5
  24. package/dist/content/vite-plugin-content-virtual-mod.d.ts +1 -3
  25. package/dist/content/vite-plugin-content-virtual-mod.js +8 -72
  26. package/dist/core/app/index.d.ts +1 -1
  27. package/dist/core/app/node.js +2 -1
  28. package/dist/core/app/types.d.ts +1 -1
  29. package/dist/core/base-pipeline.d.ts +2 -2
  30. package/dist/core/base-pipeline.js +4 -1
  31. package/dist/core/build/generate.js +7 -6
  32. package/dist/core/build/index.js +1 -10
  33. package/dist/core/build/internal.d.ts +0 -2
  34. package/dist/core/build/internal.js +0 -2
  35. package/dist/core/build/plugins/index.js +0 -2
  36. package/dist/core/build/plugins/plugin-manifest.js +1 -1
  37. package/dist/core/build/static-build.d.ts +1 -1
  38. package/dist/core/build/static-build.js +8 -44
  39. package/dist/core/config/schema.d.ts +167 -150
  40. package/dist/core/config/schema.js +6 -4
  41. package/dist/core/constants.js +1 -1
  42. package/dist/core/create-vite.js +3 -1
  43. package/dist/core/dev/dev.js +1 -1
  44. package/dist/core/dev/restart.js +7 -3
  45. package/dist/core/errors/dev/utils.d.ts +1 -1
  46. package/dist/core/errors/dev/utils.js +5 -5
  47. package/dist/core/errors/errors-data.d.ts +12 -2
  48. package/dist/core/errors/errors-data.js +7 -1
  49. package/dist/core/fs/index.d.ts +1 -1
  50. package/dist/core/fs/index.js +2 -5
  51. package/dist/core/logger/vite.js +2 -2
  52. package/dist/core/messages.js +2 -2
  53. package/dist/core/render/params-and-props.js +1 -1
  54. package/dist/core/render-context.js +1 -1
  55. package/dist/core/util.d.ts +0 -1
  56. package/dist/core/util.js +1 -6
  57. package/dist/env/constants.d.ts +1 -1
  58. package/dist/env/constants.js +1 -1
  59. package/dist/integrations/hooks.d.ts +2 -2
  60. package/dist/integrations/hooks.js +12 -10
  61. package/dist/jsx/rehype.js +6 -2
  62. package/dist/preferences/constants.d.ts +1 -0
  63. package/dist/preferences/constants.js +4 -0
  64. package/dist/preferences/store.js +2 -1
  65. package/dist/runtime/server/astro-island.js +2 -1
  66. package/dist/runtime/server/astro-island.prebuilt-dev.d.ts +1 -1
  67. package/dist/runtime/server/astro-island.prebuilt-dev.js +1 -1
  68. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  69. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  70. package/dist/runtime/server/endpoint.js +11 -2
  71. package/dist/runtime/server/render/server-islands.js +2 -2
  72. package/dist/runtime/server/serialize.js +11 -4
  73. package/dist/transitions/router.js +4 -3
  74. package/dist/types/public/config.d.ts +44 -20
  75. package/dist/types/public/integrations.d.ts +1 -1
  76. package/dist/vite-plugin-astro/index.js +8 -5
  77. package/dist/vite-plugin-astro-server/plugin.js +2 -1
  78. package/dist/vite-plugin-astro-server/response.js +2 -1
  79. package/dist/vite-plugin-hmr-reload/index.d.ts +7 -0
  80. package/dist/vite-plugin-hmr-reload/index.js +28 -0
  81. package/dist/vite-plugin-markdown/content-entry-type.js +4 -4
  82. package/dist/vite-plugin-markdown/index.js +1 -1
  83. package/package.json +22 -25
  84. package/templates/actions.mjs +5 -1
  85. package/templates/content/module.mjs +8 -6
  86. package/dist/core/build/plugins/plugin-content.d.ts +0 -5
  87. package/dist/core/build/plugins/plugin-content.js +0 -394
package/client.d.ts CHANGED
@@ -19,7 +19,7 @@ interface ImportMeta {
19
19
  * Astro and Vite expose environment variables through `import.meta.env`. For a complete list of the environment variables available, see the two references below.
20
20
  *
21
21
  * - [Astro reference](https://docs.astro.build/en/guides/environment-variables/#default-environment-variables)
22
- * - [Vite reference](https://vitejs.dev/guide/env-and-mode.html#env-variables)
22
+ * - [Vite reference](https://vite.dev/guide/env-and-mode.html#env-variables)
23
23
  */
24
24
  readonly env: ImportMetaEnv;
25
25
  }
@@ -10,12 +10,22 @@
10
10
  @keyframes astroFadeIn {
11
11
  from {
12
12
  opacity: 0;
13
+ mix-blend-mode: plus-lighter;
14
+ }
15
+ to {
16
+ opacity: 1;
17
+ mix-blend-mode: plus-lighter;
13
18
  }
14
19
  }
15
20
 
16
21
  @keyframes astroFadeOut {
22
+ from {
23
+ opacity: 1;
24
+ mix-blend-mode: plus-lighter;
25
+ }
17
26
  to {
18
27
  opacity: 0;
28
+ mix-blend-mode: plus-lighter;
19
29
  }
20
30
  }
21
31
 
@@ -1,6 +1,6 @@
1
1
  export declare const VIRTUAL_MODULE_ID = "astro:actions";
2
2
  export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
3
- export declare const ACTIONS_TYPES_FILE = "astro/actions.d.ts";
3
+ export declare const ACTIONS_TYPES_FILE = "actions.d.ts";
4
4
  export declare const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
5
5
  export declare const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
6
6
  export declare const NOOP_ACTIONS = "\0noop-actions";
@@ -1,6 +1,6 @@
1
1
  const VIRTUAL_MODULE_ID = "astro:actions";
2
2
  const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
3
- const ACTIONS_TYPES_FILE = "astro/actions.d.ts";
3
+ const ACTIONS_TYPES_FILE = "actions.d.ts";
4
4
  const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
5
5
  const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
6
6
  const NOOP_ACTIONS = "\0noop-actions";
@@ -1,7 +1,7 @@
1
1
  import { ActionNotFoundError } from "../../../core/errors/errors-data.js";
2
2
  import { AstroError } from "../../../core/errors/errors.js";
3
3
  async function getAction(path) {
4
- const pathKeys = path.replace("/_actions/", "").split(".");
4
+ const pathKeys = path.replace("/_actions/", "").split(".").map((key) => decodeURIComponent(key));
5
5
  let { server: actionLookup } = await import("astro:internal-actions");
6
6
  if (actionLookup == null || !(typeof actionLookup === "object")) {
7
7
  throw new TypeError(
@@ -6,7 +6,11 @@ import { isESMImportedImage } from "./imageKind.js";
6
6
  function propsToFilename(filePath, transform, hash) {
7
7
  let filename = decodeURIComponent(removeQueryString(filePath));
8
8
  const ext = extname(filename);
9
- filename = basename(filename, ext);
9
+ if (filePath.startsWith("data:")) {
10
+ filename = shorthash(filePath);
11
+ } else {
12
+ filename = basename(filename, ext);
13
+ }
10
14
  const prefixDirname = isESMImportedImage(transform.src) ? dirname(filePath) : "";
11
15
  let outputExt = transform.format ? `.${transform.format}` : ext;
12
16
  return decodeURIComponent(`${prefixDirname}/${filename}_${hash}${outputExt}`);
@@ -6,11 +6,11 @@ import { diffWords } from "diff";
6
6
  import { bold, cyan, dim, green, magenta, red, yellow } from "kleur/colors";
7
7
  import { builders, generateCode, loadFile } from "magicast";
8
8
  import { getDefaultExportOptions } from "magicast/helpers";
9
- import ora from "ora";
10
9
  import preferredPM from "preferred-pm";
11
10
  import prompts from "prompts";
12
11
  import maxSatisfying from "semver/ranges/max-satisfying.js";
13
12
  import { exec } from "tinyexec";
13
+ import yoctoSpinner from "yocto-spinner";
14
14
  import {
15
15
  loadTSConfig,
16
16
  resolveConfig,
@@ -381,9 +381,6 @@ function setAdapter(mod, adapter, exportName) {
381
381
  from: exportName
382
382
  });
383
383
  }
384
- if (!config.output) {
385
- config.output = "server";
386
- }
387
384
  switch (adapter.id) {
388
385
  case "node":
389
386
  config.adapter = builders.functionCall(adapterId, { mode: "standalone" });
@@ -494,7 +491,8 @@ async function resolveRangeToInstallSpecifier(name, range) {
494
491
  const versions = await fetchPackageVersions(name);
495
492
  if (versions instanceof Error) return name;
496
493
  const stableVersions = versions.filter((v) => !v.includes("-"));
497
- const maxStable = maxSatisfying(stableVersions.length !== 0 ? stableVersions : versions, range);
494
+ const maxStable = maxSatisfying(stableVersions, range) ?? maxSatisfying(versions, range);
495
+ if (!maxStable) return name;
498
496
  return `${name}@^${maxStable}`;
499
497
  }
500
498
  const INHERITED_FLAGS = /* @__PURE__ */ new Set([
@@ -545,7 +543,7 @@ ${boxen(coloredOutput, {
545
543
  ${message}`
546
544
  );
547
545
  if (await askToContinue({ flags })) {
548
- const spinner = ora("Installing dependencies...").start();
546
+ const spinner = yoctoSpinner({ text: "Installing dependencies..." }).start();
549
547
  try {
550
548
  await exec(
551
549
  installCommand.pm,
@@ -563,10 +561,10 @@ ${message}`
563
561
  }
564
562
  }
565
563
  );
566
- spinner.succeed();
564
+ spinner.success();
567
565
  return 1 /* updated */;
568
566
  } catch (err) {
569
- spinner.fail();
567
+ spinner.error();
570
568
  logger.debug("add", "Error installing dependencies", err);
571
569
  console.error("\n", err.stdout || err.message, "\n");
572
570
  return 3 /* failure */;
@@ -577,7 +575,7 @@ ${message}`
577
575
  }
578
576
  }
579
577
  async function validateIntegrations(integrations) {
580
- const spinner = ora("Resolving packages...").start();
578
+ const spinner = yoctoSpinner({ text: "Resolving packages..." }).start();
581
579
  try {
582
580
  const integrationEntries = await Promise.all(
583
581
  integrations.map(async (integration) => {
@@ -594,9 +592,9 @@ async function validateIntegrations(integrations) {
594
592
  const firstPartyPkgCheck = await fetchPackageJson("@astrojs", name, tag);
595
593
  if (firstPartyPkgCheck instanceof Error) {
596
594
  if (firstPartyPkgCheck.message) {
597
- spinner.warn(yellow(firstPartyPkgCheck.message));
595
+ spinner.warning(yellow(firstPartyPkgCheck.message));
598
596
  }
599
- spinner.warn(yellow(`${bold(integration)} is not an official Astro package.`));
597
+ spinner.warning(yellow(`${bold(integration)} is not an official Astro package.`));
600
598
  const response = await prompts({
601
599
  type: "confirm",
602
600
  name: "askToContinue",
@@ -621,7 +619,7 @@ async function validateIntegrations(integrations) {
621
619
  const thirdPartyPkgCheck = await fetchPackageJson(scope, name, tag);
622
620
  if (thirdPartyPkgCheck instanceof Error) {
623
621
  if (thirdPartyPkgCheck.message) {
624
- spinner.warn(yellow(thirdPartyPkgCheck.message));
622
+ spinner.warning(yellow(thirdPartyPkgCheck.message));
625
623
  }
626
624
  throw new Error(`Unable to fetch ${bold(integration)}. Does the package exist?`);
627
625
  } else {
@@ -661,11 +659,11 @@ async function validateIntegrations(integrations) {
661
659
  return { id: integration, packageName, dependencies, type: integrationType };
662
660
  })
663
661
  );
664
- spinner.succeed();
662
+ spinner.success();
665
663
  return integrationEntries;
666
664
  } catch (e) {
667
665
  if (e instanceof Error) {
668
- spinner.fail(e.message);
666
+ spinner.error(e.message);
669
667
  process.exit(1);
670
668
  } else {
671
669
  throw e;
@@ -2,11 +2,11 @@ import { createRequire } from "node:module";
2
2
  import boxen from "boxen";
3
3
  import ci from "ci-info";
4
4
  import { bold, cyan, dim, magenta } from "kleur/colors";
5
- import ora from "ora";
6
5
  import preferredPM from "preferred-pm";
7
6
  import prompts from "prompts";
8
7
  import { exec } from "tinyexec";
9
8
  import whichPm from "which-pm";
9
+ import yoctoSpinner from "yocto-spinner";
10
10
  const require2 = createRequire(import.meta.url);
11
11
  async function getPackage(packageName, logger, options, otherDeps = []) {
12
12
  try {
@@ -104,18 +104,18 @@ ${message}`
104
104
  })).askToContinue;
105
105
  }
106
106
  if (Boolean(response)) {
107
- const spinner = ora("Installing dependencies...").start();
107
+ const spinner = yoctoSpinner({ text: "Installing dependencies..." }).start();
108
108
  try {
109
109
  await exec(
110
110
  installCommand.pm,
111
111
  [installCommand.command, ...installCommand.flags, ...installCommand.dependencies],
112
112
  { nodeOptions: { cwd } }
113
113
  );
114
- spinner.succeed();
114
+ spinner.success();
115
115
  return true;
116
116
  } catch (err) {
117
117
  logger.debug("add", "Error installing dependencies", err);
118
- spinner.fail();
118
+ spinner.error();
119
119
  return false;
120
120
  }
121
121
  } else {
@@ -17,8 +17,10 @@ export declare const LINKS_PLACEHOLDER = "@@ASTRO-LINKS@@";
17
17
  export declare const STYLES_PLACEHOLDER = "@@ASTRO-STYLES@@";
18
18
  export declare const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
19
19
  export declare const CONTENT_FLAGS: readonly ["astroContentCollectionEntry", "astroRenderContent", "astroDataCollectionEntry", "astroPropagatedAssets", "astroContentImageFlag", "astroContentModuleFlag"];
20
- export declare const CONTENT_TYPES_FILE = "astro/content.d.ts";
20
+ export declare const CONTENT_TYPES_FILE = "content.d.ts";
21
21
  export declare const DATA_STORE_FILE = "data-store.json";
22
- export declare const ASSET_IMPORTS_FILE = "assets.mjs";
23
- export declare const MODULES_IMPORTS_FILE = "modules.mjs";
22
+ export declare const ASSET_IMPORTS_FILE = "content-assets.mjs";
23
+ export declare const MODULES_IMPORTS_FILE = "content-modules.mjs";
24
+ export declare const COLLECTIONS_MANIFEST_FILE = "collections/collections.json";
25
+ export declare const COLLECTIONS_DIR = "collections/";
24
26
  export declare const CONTENT_LAYER_TYPE = "content_layer";
@@ -24,15 +24,19 @@ const CONTENT_FLAGS = [
24
24
  CONTENT_IMAGE_FLAG,
25
25
  CONTENT_MODULE_FLAG
26
26
  ];
27
- const CONTENT_TYPES_FILE = "astro/content.d.ts";
27
+ const CONTENT_TYPES_FILE = "content.d.ts";
28
28
  const DATA_STORE_FILE = "data-store.json";
29
- const ASSET_IMPORTS_FILE = "assets.mjs";
30
- const MODULES_IMPORTS_FILE = "modules.mjs";
29
+ const ASSET_IMPORTS_FILE = "content-assets.mjs";
30
+ const MODULES_IMPORTS_FILE = "content-modules.mjs";
31
+ const COLLECTIONS_MANIFEST_FILE = "collections/collections.json";
32
+ const COLLECTIONS_DIR = "collections/";
31
33
  const CONTENT_LAYER_TYPE = "content_layer";
32
34
  export {
33
35
  ASSET_IMPORTS_FILE,
34
36
  ASSET_IMPORTS_RESOLVED_STUB_ID,
35
37
  ASSET_IMPORTS_VIRTUAL_ID,
38
+ COLLECTIONS_DIR,
39
+ COLLECTIONS_MANIFEST_FILE,
36
40
  CONTENT_FLAG,
37
41
  CONTENT_FLAGS,
38
42
  CONTENT_IMAGE_FLAG,
@@ -4,6 +4,7 @@ import xxhash from "xxhash-wasm";
4
4
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
5
5
  import {
6
6
  ASSET_IMPORTS_FILE,
7
+ COLLECTIONS_MANIFEST_FILE,
7
8
  CONTENT_LAYER_TYPE,
8
9
  DATA_STORE_FILE,
9
10
  MODULES_IMPORTS_FILE
@@ -106,10 +107,25 @@ class ContentLayer {
106
107
  logger.info("Syncing content");
107
108
  const { digest: currentConfigDigest } = contentConfig.config;
108
109
  this.#lastConfigDigest = currentConfigDigest;
110
+ let shouldClear = false;
109
111
  const previousConfigDigest = await this.#store.metaStore().get("config-digest");
112
+ const previousAstroVersion = await this.#store.metaStore().get("astro-version");
110
113
  if (currentConfigDigest && previousConfigDigest !== currentConfigDigest) {
111
- logger.info("Content config changed, clearing cache");
114
+ logger.info("Content config changed");
115
+ shouldClear = true;
116
+ }
117
+ if (previousAstroVersion !== "5.0.0-beta.4") {
118
+ logger.info("Astro version changed");
119
+ shouldClear = true;
120
+ }
121
+ if (shouldClear) {
122
+ logger.info("Clearing content store");
112
123
  this.#store.clearAll();
124
+ }
125
+ if ("5.0.0-beta.4") {
126
+ await this.#store.metaStore().set("astro-version", "5.0.0-beta.4");
127
+ }
128
+ if (currentConfigDigest) {
113
129
  await this.#store.metaStore().set("config-digest", currentConfigDigest);
114
130
  }
115
131
  await Promise.all(
@@ -159,14 +175,10 @@ class ContentLayer {
159
175
  return collection.loader.load(context);
160
176
  })
161
177
  );
162
- if (!existsSync(this.#settings.config.cacheDir)) {
163
- await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
164
- }
178
+ await fs.mkdir(this.#settings.config.cacheDir, { recursive: true });
179
+ await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
165
180
  const cacheFile = getDataStoreFile(this.#settings);
166
181
  await this.#store.writeToDisk(cacheFile);
167
- if (!existsSync(this.#settings.dotAstroDir)) {
168
- await fs.mkdir(this.#settings.dotAstroDir, { recursive: true });
169
- }
170
182
  const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir);
171
183
  await this.#store.writeAssetImports(assetImportsFile);
172
184
  const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir);
@@ -177,7 +189,7 @@ class ContentLayer {
177
189
  }
178
190
  }
179
191
  async regenerateCollectionFileManifest() {
180
- const collectionsManifest = new URL("collections/collections.json", this.#settings.dotAstroDir);
192
+ const collectionsManifest = new URL(COLLECTIONS_MANIFEST_FILE, this.#settings.dotAstroDir);
181
193
  this.#logger.debug("content", "Regenerating collection file manifest");
182
194
  if (existsSync(collectionsManifest)) {
183
195
  try {
@@ -31,6 +31,8 @@ export interface DataEntry<TData extends Record<string, unknown> = Record<string
31
31
  */
32
32
  deferredRender?: boolean;
33
33
  assetImports?: Array<string>;
34
+ /** @deprecated */
35
+ legacyId?: string;
34
36
  }
35
37
  /**
36
38
  * A read-only data store for content collections. This is used to retrieve data from the content layer at runtime.
@@ -1,7 +1,14 @@
1
1
  import type { Loader } from './types.js';
2
+ export interface FileOptions {
3
+ /**
4
+ * the parsing function to use for this data
5
+ * @default JSON.parse or yaml.load, depending on the extension of the file
6
+ * */
7
+ parser?: (text: string) => Record<string, Record<string, unknown>> | Array<Record<string, unknown>>;
8
+ }
2
9
  /**
3
10
  * 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
11
  * @param fileName The path to the JSON file to load, relative to the content directory.
12
+ * @param options Additional options for the file loader
6
13
  */
7
- export declare function file(fileName: string): Loader;
14
+ export declare function file(fileName: string, options?: FileOptions): Loader;
@@ -1,43 +1,59 @@
1
1
  import { promises as fs, existsSync } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
+ import yaml from "js-yaml";
3
4
  import { posixRelative } from "../utils.js";
4
- function file(fileName) {
5
+ function file(fileName, options) {
5
6
  if (fileName.includes("*")) {
6
7
  throw new Error("Glob patterns are not supported in `file` loader. Use `glob` loader instead.");
7
8
  }
9
+ let parse = null;
10
+ const ext = fileName.split(".").at(-1);
11
+ if (ext === "json") {
12
+ parse = JSON.parse;
13
+ } else if (ext === "yml" || ext === "yaml") {
14
+ parse = (text) => yaml.load(text, {
15
+ filename: fileName
16
+ });
17
+ }
18
+ if (options?.parser) parse = options.parser;
19
+ if (parse === null) {
20
+ throw new Error(
21
+ `No parser found for file '${fileName}'. Try passing a parser to the \`file\` loader.`
22
+ );
23
+ }
8
24
  async function syncData(filePath, { logger, parseData, store, config }) {
9
- let json;
25
+ let data;
10
26
  try {
11
- const data = await fs.readFile(filePath, "utf-8");
12
- json = JSON.parse(data);
27
+ const contents = await fs.readFile(filePath, "utf-8");
28
+ data = parse(contents);
13
29
  } catch (error) {
14
30
  logger.error(`Error reading data from ${fileName}`);
15
31
  logger.debug(error.message);
16
32
  return;
17
33
  }
18
34
  const normalizedFilePath = posixRelative(fileURLToPath(config.root), filePath);
19
- if (Array.isArray(json)) {
20
- if (json.length === 0) {
35
+ if (Array.isArray(data)) {
36
+ if (data.length === 0) {
21
37
  logger.warn(`No items found in ${fileName}`);
22
38
  }
23
- logger.debug(`Found ${json.length} item array in ${fileName}`);
39
+ logger.debug(`Found ${data.length} item array in ${fileName}`);
24
40
  store.clear();
25
- for (const rawItem of json) {
41
+ for (const rawItem of data) {
26
42
  const id = (rawItem.id ?? rawItem.slug)?.toString();
27
43
  if (!id) {
28
44
  logger.error(`Item in ${fileName} is missing an id or slug field.`);
29
45
  continue;
30
46
  }
31
- const data = await parseData({ id, data: rawItem, filePath });
32
- store.set({ id, data, filePath: normalizedFilePath });
47
+ const parsedData = await parseData({ id, data: rawItem, filePath });
48
+ store.set({ id, data: parsedData, filePath: normalizedFilePath });
33
49
  }
34
- } else if (typeof json === "object") {
35
- const entries = Object.entries(json);
50
+ } else if (typeof data === "object") {
51
+ const entries = Object.entries(data);
36
52
  logger.debug(`Found object with ${entries.length} entries in ${fileName}`);
37
53
  store.clear();
38
54
  for (const [id, rawItem] of entries) {
39
- const data = await parseData({ id, data: rawItem, filePath });
40
- store.set({ id, data, filePath: normalizedFilePath });
55
+ const parsedData = await parseData({ id, data: rawItem, filePath });
56
+ store.set({ id, data: parsedData, filePath: normalizedFilePath });
41
57
  }
42
58
  } else {
43
59
  logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
@@ -23,3 +23,8 @@ export interface GlobOptions {
23
23
  * @param pattern A glob pattern to match files, relative to the content directory.
24
24
  */
25
25
  export declare function glob(globOptions: GlobOptions): Loader;
26
+ /** @private */
27
+ export declare function glob(globOptions: GlobOptions & {
28
+ /** @deprecated */
29
+ _legacy?: true;
30
+ }): Loader;
@@ -9,7 +9,7 @@ function generateIdDefault({ entry, base, data }) {
9
9
  if (data.slug) {
10
10
  return data.slug;
11
11
  }
12
- const entryURL = new URL(entry, base);
12
+ const entryURL = new URL(encodeURI(entry), base);
13
13
  const { slug } = getContentEntryIdAndSlug({
14
14
  entry: entryURL,
15
15
  contentDir: base,
@@ -41,17 +41,19 @@ function glob(globOptions) {
41
41
  load: async ({ config, logger, watcher, parseData, store, generateDigest, entryTypes }) => {
42
42
  const renderFunctionByContentType = /* @__PURE__ */ new WeakMap();
43
43
  const untouchedEntries = new Set(store.keys());
44
+ const isLegacy = globOptions._legacy;
45
+ const emulateLegacyCollections = !config.legacy.collections;
44
46
  async function syncData(entry, base, entryType) {
45
47
  if (!entryType) {
46
48
  logger.warn(`No entry type found for ${entry}`);
47
49
  return;
48
50
  }
49
- const fileUrl = new URL(entry, base);
51
+ const fileUrl = new URL(encodeURI(entry), base);
50
52
  const contents = await fs.readFile(fileUrl, "utf-8").catch((err) => {
51
53
  logger.error(`Error reading ${entry}: ${err.message}`);
52
54
  return;
53
55
  });
54
- if (!contents) {
56
+ if (!contents && contents !== "") {
55
57
  logger.warn(`No contents found for ${entry}`);
56
58
  return;
57
59
  }
@@ -60,6 +62,16 @@ function glob(globOptions) {
60
62
  fileUrl
61
63
  });
62
64
  const id = generateId({ entry, base, data });
65
+ let legacyId;
66
+ if (isLegacy) {
67
+ const entryURL = new URL(encodeURI(entry), base);
68
+ const legacyOptions = getContentEntryIdAndSlug({
69
+ entry: entryURL,
70
+ contentDir: base,
71
+ collection: ""
72
+ });
73
+ legacyId = legacyOptions.id;
74
+ }
63
75
  untouchedEntries.delete(id);
64
76
  const existingEntry = store.get(id);
65
77
  const digest = generateDigest(contents);
@@ -80,6 +92,11 @@ function glob(globOptions) {
80
92
  filePath
81
93
  });
82
94
  if (entryType.getRenderFunction) {
95
+ if (isLegacy && data.layout) {
96
+ logger.error(
97
+ `The Markdown "layout" field is not supported in content collections in Astro 5. Ignoring layout for ${JSON.stringify(entry)}. Enable "legacy.collections" if you need to use the layout field.`
98
+ );
99
+ }
83
100
  let render = renderFunctionByContentType.get(entryType);
84
101
  if (!render) {
85
102
  render = await entryType.getRenderFunction(config);
@@ -104,7 +121,8 @@ function glob(globOptions) {
104
121
  filePath: relativePath,
105
122
  digest,
106
123
  rendered,
107
- assetImports: rendered?.metadata?.imagePaths
124
+ assetImports: rendered?.metadata?.imagePaths,
125
+ legacyId
108
126
  });
109
127
  } else if ("contentModuleTypes" in entryType) {
110
128
  store.set({
@@ -113,10 +131,11 @@ function glob(globOptions) {
113
131
  body,
114
132
  filePath: relativePath,
115
133
  digest,
116
- deferredRender: true
134
+ deferredRender: true,
135
+ legacyId
117
136
  });
118
137
  } else {
119
- store.set({ id, data: parsedData, body, filePath: relativePath, digest });
138
+ store.set({ id, data: parsedData, body, filePath: relativePath, digest, legacyId });
120
139
  }
121
140
  fileToIdMap.set(filePath, id);
122
141
  }
@@ -154,7 +173,7 @@ function glob(globOptions) {
154
173
  if (isConfigFile(entry)) {
155
174
  return;
156
175
  }
157
- if (isInContentDir(entry)) {
176
+ if (!emulateLegacyCollections && isInContentDir(entry)) {
158
177
  skippedFiles.push(entry);
159
178
  return;
160
179
  }
@@ -167,7 +186,9 @@ function glob(globOptions) {
167
186
  const skipCount = skippedFiles.length;
168
187
  if (skipCount > 0) {
169
188
  const patternList = Array.isArray(globOptions.pattern) ? globOptions.pattern.join(", ") : globOptions.pattern;
170
- logger.warn(`The glob() loader cannot be used for files in ${bold("src/content")}.`);
189
+ logger.warn(
190
+ `The glob() loader cannot be used for files in ${bold("src/content")} when legacy mode is enabled.`
191
+ );
171
192
  if (skipCount > 10) {
172
193
  logger.warn(
173
194
  `Skipped ${green(skippedFiles.length)} files that matched ${green(patternList)}.`
@@ -1,5 +1,5 @@
1
1
  import { type PathLike } from 'node:fs';
2
- import { type DataEntry, ImmutableDataStore, type RenderedContent } from './data-store.js';
2
+ import { type DataEntry, ImmutableDataStore } from './data-store.js';
3
3
  /**
4
4
  * Extends the DataStore with the ability to change entries and write them to disk.
5
5
  * This is kept as a separate class to avoid needing node builtins at runtime, when read-only access is all that is needed.
@@ -34,24 +34,7 @@ export declare class MutableDataStore extends ImmutableDataStore {
34
34
  export interface DataStore {
35
35
  get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) => DataEntry<TData> | undefined;
36
36
  entries: () => Array<[id: string, DataEntry]>;
37
- set: <TData extends Record<string, unknown>>(opts: {
38
- /** The ID of the entry. Must be unique per collection. */
39
- id: string;
40
- /** The data to store. */
41
- data: TData;
42
- /** The raw body of the content, if applicable. */
43
- body?: string;
44
- /** The file path of the content, if applicable. Relative to the site root. */
45
- filePath?: string;
46
- /** A content digest, to check if the content has changed. */
47
- digest?: number | string;
48
- /** The rendered content, if applicable. */
49
- rendered?: RenderedContent;
50
- /**
51
- * If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase.
52
- */
53
- deferredRender?: boolean;
54
- }) => boolean;
37
+ set: <TData extends Record<string, unknown>>(opts: DataEntry<TData>) => boolean;
55
38
  values: () => Array<DataEntry>;
56
39
  keys: () => Array<string>;
57
40
  delete: (key: string) => void;
@@ -159,7 +159,17 @@ ${lines.join(",\n")}]);
159
159
  entries: () => this.entries(collectionName),
160
160
  values: () => this.values(collectionName),
161
161
  keys: () => this.keys(collectionName),
162
- set: ({ id: key, data, body, filePath, deferredRender, digest, rendered, assetImports }) => {
162
+ set: ({
163
+ id: key,
164
+ data,
165
+ body,
166
+ filePath,
167
+ deferredRender,
168
+ digest,
169
+ rendered,
170
+ assetImports,
171
+ legacyId
172
+ }) => {
163
173
  if (!key) {
164
174
  throw new Error(`ID must be a non-empty string`);
165
175
  }
@@ -200,6 +210,9 @@ ${lines.join(",\n")}]);
200
210
  if (rendered) {
201
211
  entry.rendered = rendered;
202
212
  }
213
+ if (legacyId) {
214
+ entry.legacyId = legacyId;
215
+ }
203
216
  if (deferredRender) {
204
217
  entry.deferredRender = deferredRender;
205
218
  if (filePath) {
@@ -18,10 +18,11 @@ export declare function createGetCollection({ contentCollectionToEntryMap, dataC
18
18
  getRenderEntryImport: GetEntryImport;
19
19
  cacheEntriesByCollection: Map<string, any[]>;
20
20
  }): (collection: string, filter?: (entry: any) => unknown) => Promise<any[]>;
21
- export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, collectionNames, }: {
21
+ export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImport, collectionNames, getEntry, }: {
22
22
  getEntryImport: GetEntryImport;
23
23
  getRenderEntryImport: GetEntryImport;
24
24
  collectionNames: Set<string>;
25
+ getEntry: ReturnType<typeof createGetEntry>;
25
26
  }): (collection: string, slug: string) => Promise<{
26
27
  id: any;
27
28
  slug: any;
@@ -30,10 +31,11 @@ export declare function createGetEntryBySlug({ getEntryImport, getRenderEntryImp
30
31
  data: any;
31
32
  render(): Promise<RenderResult>;
32
33
  } | undefined>;
33
- export declare function createGetDataEntryById({ getEntryImport, collectionNames, }: {
34
+ export declare function createGetDataEntryById({ getEntryImport, collectionNames, getEntry, }: {
34
35
  getEntryImport: GetEntryImport;
35
36
  collectionNames: Set<string>;
36
- }): (collection: string, id: string) => Promise<{
37
+ getEntry: ReturnType<typeof createGetEntry>;
38
+ }): (collection: string, id: string) => Promise<ContentEntryResult | {
37
39
  id: any;
38
40
  collection: any;
39
41
  data: any;
@@ -79,7 +81,11 @@ export declare function renderEntry(entry: DataEntry | {
79
81
  render: () => Promise<{
80
82
  Content: AstroComponentFactory;
81
83
  }>;
82
- }): Promise<{
84
+ } | (DataEntry & {
85
+ render: () => Promise<{
86
+ Content: AstroComponentFactory;
87
+ }>;
88
+ })): Promise<{
83
89
  Content: AstroComponentFactory;
84
90
  }>;
85
91
  export declare function createReference({ lookupMap }: {
@@ -88,20 +94,20 @@ export declare function createReference({ lookupMap }: {
88
94
  id: z.ZodString;
89
95
  collection: z.ZodString;
90
96
  }, "strip", z.ZodTypeAny, {
91
- id: string;
92
97
  collection: string;
93
- }, {
94
98
  id: string;
99
+ }, {
95
100
  collection: string;
101
+ id: string;
96
102
  }>, z.ZodObject<{
97
103
  slug: z.ZodString;
98
104
  collection: z.ZodString;
99
105
  }, "strip", z.ZodTypeAny, {
100
- collection: string;
101
106
  slug: string;
102
- }, {
103
107
  collection: string;
108
+ }, {
104
109
  slug: string;
110
+ collection: string;
105
111
  }>]>, {
106
112
  id: string;
107
113
  collection: string;
@@ -109,10 +115,10 @@ export declare function createReference({ lookupMap }: {
109
115
  slug: string;
110
116
  collection: string;
111
117
  } | undefined, string | {
112
- id: string;
113
118
  collection: string;
119
+ id: string;
114
120
  } | {
115
- collection: string;
116
121
  slug: string;
122
+ collection: string;
117
123
  }>;
118
124
  export {};