astro 2.4.4 → 2.5.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 (119) hide show
  1. package/astro-jsx.d.ts +5 -1
  2. package/dist/@types/astro.d.ts +130 -6
  3. package/dist/assets/generate.d.ts +22 -0
  4. package/dist/assets/generate.js +90 -0
  5. package/dist/assets/internal.d.ts +0 -21
  6. package/dist/assets/internal.js +0 -86
  7. package/dist/assets/vite-plugin-assets.js +13 -2
  8. package/dist/content/consts.d.ts +3 -1
  9. package/dist/content/consts.js +5 -1
  10. package/dist/content/runtime-assets.d.ts +1 -1
  11. package/dist/content/runtime.d.ts +61 -7
  12. package/dist/content/runtime.js +127 -4
  13. package/dist/content/template/virtual-mod.d.mts +4 -1
  14. package/dist/content/types-generator.js +155 -80
  15. package/dist/content/utils.d.ts +76 -23
  16. package/dist/content/utils.js +129 -65
  17. package/dist/content/vite-plugin-content-imports.js +110 -25
  18. package/dist/content/vite-plugin-content-virtual-mod.d.ts +4 -3
  19. package/dist/content/vite-plugin-content-virtual-mod.js +89 -45
  20. package/dist/core/app/common.js +2 -0
  21. package/dist/core/app/index.js +6 -4
  22. package/dist/core/app/types.d.ts +6 -1
  23. package/dist/core/build/generate.js +8 -9
  24. package/dist/core/build/index.d.ts +1 -1
  25. package/dist/core/build/plugins/plugin-pages.d.ts +0 -2
  26. package/dist/core/build/plugins/plugin-pages.js +3 -4
  27. package/dist/core/build/plugins/plugin-prerender.d.ts +0 -2
  28. package/dist/core/build/plugins/plugin-prerender.js +2 -2
  29. package/dist/core/build/plugins/plugin-ssr.d.ts +1 -4
  30. package/dist/core/build/plugins/plugin-ssr.js +4 -3
  31. package/dist/core/build/static-build.js +9 -7
  32. package/dist/core/client-directive/build.d.ts +4 -0
  33. package/dist/core/client-directive/build.js +28 -0
  34. package/dist/core/client-directive/default.d.ts +1 -0
  35. package/dist/core/client-directive/default.js +17 -0
  36. package/dist/core/client-directive/index.d.ts +2 -0
  37. package/dist/core/client-directive/index.js +6 -0
  38. package/dist/core/compile/compile.js +1 -0
  39. package/dist/core/config/config.js +6 -0
  40. package/dist/core/config/schema.d.ts +68 -12
  41. package/dist/core/config/schema.js +29 -3
  42. package/dist/core/config/settings.js +74 -2
  43. package/dist/core/config/vite-load.js +2 -1
  44. package/dist/core/constants.js +1 -1
  45. package/dist/core/dev/dev.js +1 -1
  46. package/dist/core/endpoint/dev/index.js +1 -1
  47. package/dist/core/endpoint/index.d.ts +1 -1
  48. package/dist/core/endpoint/index.js +16 -16
  49. package/dist/core/errors/errors-data.d.ts +55 -4
  50. package/dist/core/errors/errors-data.js +67 -7
  51. package/dist/core/errors/errors.d.ts +1 -0
  52. package/dist/core/errors/errors.js +5 -1
  53. package/dist/core/errors/index.d.ts +1 -1
  54. package/dist/core/errors/index.js +9 -1
  55. package/dist/core/errors/utils.d.ts +5 -0
  56. package/dist/core/errors/utils.js +14 -0
  57. package/dist/core/messages.js +2 -2
  58. package/dist/core/middleware/callMiddleware.d.ts +2 -1
  59. package/dist/core/middleware/callMiddleware.js +13 -3
  60. package/dist/core/render/core.js +1 -0
  61. package/dist/core/render/dev/environment.js +3 -1
  62. package/dist/core/render/dev/index.js +1 -1
  63. package/dist/core/render/environment.d.ts +1 -0
  64. package/dist/core/render/environment.js +2 -0
  65. package/dist/core/render/result.d.ts +1 -0
  66. package/dist/core/render/result.js +3 -2
  67. package/dist/core/routing/manifest/create.js +9 -2
  68. package/dist/core/sync/index.js +11 -1
  69. package/dist/core/util.js +2 -1
  70. package/dist/integrations/index.js +29 -3
  71. package/dist/jsx/babel.js +1 -2
  72. package/dist/prerender/utils.d.ts +3 -0
  73. package/dist/prerender/utils.js +10 -0
  74. package/dist/runtime/client/idle.d.ts +3 -0
  75. package/dist/runtime/client/idle.js +6 -3
  76. package/dist/runtime/client/idle.prebuilt.d.ts +1 -1
  77. package/dist/runtime/client/idle.prebuilt.js +1 -1
  78. package/dist/runtime/client/load.d.ts +3 -0
  79. package/dist/runtime/client/load.js +7 -6
  80. package/dist/runtime/client/load.prebuilt.d.ts +1 -1
  81. package/dist/runtime/client/load.prebuilt.js +1 -1
  82. package/dist/runtime/client/media.d.ts +6 -0
  83. package/dist/runtime/client/media.js +6 -3
  84. package/dist/runtime/client/media.prebuilt.d.ts +1 -1
  85. package/dist/runtime/client/media.prebuilt.js +1 -1
  86. package/dist/runtime/client/only.d.ts +6 -0
  87. package/dist/runtime/client/only.js +7 -6
  88. package/dist/runtime/client/only.prebuilt.d.ts +1 -1
  89. package/dist/runtime/client/only.prebuilt.js +1 -1
  90. package/dist/runtime/client/visible.d.ts +8 -0
  91. package/dist/runtime/client/visible.js +9 -6
  92. package/dist/runtime/client/visible.prebuilt.d.ts +1 -1
  93. package/dist/runtime/client/visible.prebuilt.js +1 -1
  94. package/dist/runtime/server/astro-island.prebuilt.d.ts +1 -1
  95. package/dist/runtime/server/astro-island.prebuilt.js +1 -1
  96. package/dist/runtime/server/endpoint.js +1 -1
  97. package/dist/runtime/server/hydration.d.ts +1 -2
  98. package/dist/runtime/server/hydration.js +4 -9
  99. package/dist/runtime/server/render/astro/instance.js +1 -2
  100. package/dist/runtime/server/render/astro/render-template.d.ts +1 -1
  101. package/dist/runtime/server/render/astro/render-template.js +10 -2
  102. package/dist/runtime/server/render/common.js +1 -1
  103. package/dist/runtime/server/render/component.js +20 -6
  104. package/dist/runtime/server/render/page.js +5 -0
  105. package/dist/runtime/server/render/util.d.ts +12 -0
  106. package/dist/runtime/server/render/util.js +89 -1
  107. package/dist/runtime/server/scripts.d.ts +1 -2
  108. package/dist/runtime/server/scripts.js +13 -21
  109. package/dist/vite-plugin-astro-server/request.js +2 -1
  110. package/dist/vite-plugin-astro-server/route.js +4 -3
  111. package/dist/vite-plugin-config-alias/index.js +13 -11
  112. package/dist/vite-plugin-jsx/index.js +1 -1
  113. package/dist/vite-plugin-scanner/index.js +6 -1
  114. package/dist/vite-plugin-scanner/scan.d.ts +1 -1
  115. package/dist/vite-plugin-scanner/scan.js +2 -2
  116. package/package.json +5 -3
  117. package/src/content/template/types.d.ts +108 -15
  118. package/src/content/template/virtual-mod.mjs +40 -16
  119. package/types.d.ts +6 -3
@@ -5,32 +5,78 @@ import type { PluginContext } from 'rollup';
5
5
  import { type ViteDevServer } from 'vite';
6
6
  import { z } from 'zod';
7
7
  import type { AstroConfig, AstroSettings, ContentEntryType } from '../@types/astro.js';
8
- export declare const collectionConfigParser: z.ZodObject<{
8
+ import { CONTENT_FLAGS } from './consts.js';
9
+ /**
10
+ * Amap from a collection + slug to the local file path.
11
+ * This is used internally to resolve entry imports when using `getEntry()`.
12
+ * @see `src/content/virtual-mod.mjs`
13
+ */
14
+ export type ContentLookupMap = {
15
+ [collectionName: string]: {
16
+ type: 'content' | 'data';
17
+ entries: {
18
+ [lookupId: string]: string;
19
+ };
20
+ };
21
+ };
22
+ export declare const collectionConfigParser: z.ZodUnion<[z.ZodObject<{
23
+ type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
9
24
  schema: z.ZodOptional<z.ZodAny>;
10
25
  }, "strip", z.ZodTypeAny, {
11
26
  schema?: any;
27
+ type: "content";
12
28
  }, {
29
+ type?: "content" | undefined;
13
30
  schema?: any;
14
- }>;
31
+ }>, z.ZodObject<{
32
+ type: z.ZodLiteral<"data">;
33
+ schema: z.ZodOptional<z.ZodAny>;
34
+ }, "strip", z.ZodTypeAny, {
35
+ schema?: any;
36
+ type: "data";
37
+ }, {
38
+ schema?: any;
39
+ type: "data";
40
+ }>]>;
15
41
  export declare function getDotAstroTypeReference({ root, srcDir }: {
16
42
  root: URL;
17
43
  srcDir: URL;
18
44
  }): string;
19
45
  export declare const contentConfigParser: z.ZodObject<{
20
- collections: z.ZodRecord<z.ZodString, z.ZodObject<{
46
+ collections: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
47
+ type: z.ZodDefault<z.ZodOptional<z.ZodLiteral<"content">>>;
21
48
  schema: z.ZodOptional<z.ZodAny>;
22
49
  }, "strip", z.ZodTypeAny, {
23
50
  schema?: any;
51
+ type: "content";
24
52
  }, {
53
+ type?: "content" | undefined;
25
54
  schema?: any;
26
- }>>;
55
+ }>, z.ZodObject<{
56
+ type: z.ZodLiteral<"data">;
57
+ schema: z.ZodOptional<z.ZodAny>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ schema?: any;
60
+ type: "data";
61
+ }, {
62
+ schema?: any;
63
+ type: "data";
64
+ }>]>>;
27
65
  }, "strip", z.ZodTypeAny, {
28
66
  collections: Record<string, {
29
67
  schema?: any;
68
+ type: "content";
69
+ } | {
70
+ schema?: any;
71
+ type: "data";
30
72
  }>;
31
73
  }, {
32
74
  collections: Record<string, {
75
+ type?: "content" | undefined;
33
76
  schema?: any;
77
+ } | {
78
+ schema?: any;
79
+ type: "data";
34
80
  }>;
35
81
  }>;
36
82
  export type CollectionConfig = z.infer<typeof collectionConfigParser>;
@@ -39,11 +85,6 @@ type EntryInternal = {
39
85
  rawData: string | undefined;
40
86
  filePath: string;
41
87
  };
42
- export type EntryInfo = {
43
- id: string;
44
- slug: string;
45
- collection: string;
46
- };
47
88
  export declare const msg: {
48
89
  collectionConfigMissing: (collection: string) => string;
49
90
  };
@@ -53,26 +94,31 @@ export declare function parseEntrySlug({ id, collection, generatedSlug, frontmat
53
94
  generatedSlug: string;
54
95
  frontmatterSlug?: unknown;
55
96
  }): string;
56
- export declare function getEntryData(entry: EntryInfo & {
97
+ export declare function getEntryData(entry: {
98
+ id: string;
99
+ collection: string;
57
100
  unvalidatedData: Record<string, unknown>;
58
101
  _internal: EntryInternal;
59
- }, collectionConfig: CollectionConfig, pluginContext: PluginContext, settings: AstroSettings): Promise<{
60
- [x: string]: unknown;
61
- }>;
102
+ }, collectionConfig: CollectionConfig, pluginContext: PluginContext, config: AstroConfig): Promise<any>;
62
103
  export declare function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryTypes'>): string[];
104
+ export declare function getDataEntryExts(settings: Pick<AstroSettings, 'dataEntryTypes'>): string[];
63
105
  export declare function getContentEntryConfigByExtMap(settings: Pick<AstroSettings, 'contentEntryTypes'>): Map<string, ContentEntryType>;
64
- export declare class NoCollectionError extends Error {
65
- }
66
- export declare function getEntryInfo(params: Pick<ContentPaths, 'contentDir'> & {
106
+ export declare function getEntryCollectionName({ contentDir, entry, }: Pick<ContentPaths, 'contentDir'> & {
67
107
  entry: string | URL;
68
- allowFilesOutsideCollection?: true;
69
- }): EntryInfo;
70
- export declare function getEntryType(entryPath: string, paths: Pick<ContentPaths, 'config' | 'contentDir'>, contentFileExts: string[], experimentalAssets: boolean): 'content' | 'config' | 'ignored' | 'unsupported';
108
+ }): string | undefined;
109
+ export declare function getDataEntryId({ entry, contentDir, collection, }: Pick<ContentPaths, 'contentDir'> & {
110
+ entry: URL;
111
+ collection: string;
112
+ }): string;
113
+ export declare function getContentEntryIdAndSlug({ entry, contentDir, collection, }: Pick<ContentPaths, 'contentDir'> & {
114
+ entry: URL;
115
+ collection: string;
116
+ }): {
117
+ id: string;
118
+ slug: string;
119
+ };
120
+ export declare function getEntryType(entryPath: string, paths: Pick<ContentPaths, 'config' | 'contentDir'>, contentFileExts: string[], dataFileExts: string[], experimentalAssets?: boolean): 'content' | 'data' | 'config' | 'ignored' | 'unsupported';
71
121
  export declare function hasUnderscoreBelowContentDirectoryPath(fileUrl: URL, contentDir: ContentPaths['contentDir']): boolean;
72
- /**
73
- * Match YAML exception handling from Astro core errors
74
- * @see 'astro/src/core/errors.ts'
75
- */
76
122
  export declare function parseFrontmatter(fileContents: string, filePath: string): matter.GrayMatterFile<string>;
77
123
  /**
78
124
  * The content config is loaded separately from other `src/` files.
@@ -80,11 +126,18 @@ export declare function parseFrontmatter(fileContents: string, filePath: string)
80
126
  * subscribe to changes during dev server updates.
81
127
  */
82
128
  export declare const globalContentConfigObserver: ContentObservable;
129
+ export declare function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean;
83
130
  export declare function loadContentConfig({ fs, settings, viteServer, }: {
84
131
  fs: typeof fsMod;
85
132
  settings: AstroSettings;
86
133
  viteServer: ViteDevServer;
87
134
  }): Promise<ContentConfig | undefined>;
135
+ export declare function reloadContentConfigObserver({ observer, ...loadContentConfigOpts }: {
136
+ fs: typeof fsMod;
137
+ settings: AstroSettings;
138
+ viteServer: ViteDevServer;
139
+ observer?: ContentObservable;
140
+ }): Promise<void>;
88
141
  type ContentCtx = {
89
142
  status: 'init';
90
143
  } | {
@@ -7,12 +7,20 @@ import { normalizePath } from "vite";
7
7
  import { z } from "zod";
8
8
  import { VALID_INPUT_FORMATS } from "../assets/consts.js";
9
9
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
10
+ import { formatYAMLException, isYAMLException } from "../core/errors/utils.js";
10
11
  import { CONTENT_TYPES_FILE } from "./consts.js";
11
12
  import { errorMap } from "./error-map.js";
12
13
  import { createImage } from "./runtime-assets.js";
13
- const collectionConfigParser = z.object({
14
- schema: z.any().optional()
15
- });
14
+ const collectionConfigParser = z.union([
15
+ z.object({
16
+ type: z.literal("content").optional().default("content"),
17
+ schema: z.any().optional()
18
+ }),
19
+ z.object({
20
+ type: z.literal("data"),
21
+ schema: z.any().optional()
22
+ })
23
+ ]);
16
24
  function getDotAstroTypeReference({ root, srcDir }) {
17
25
  const { cacheDir } = getContentPaths({ root, srcDir });
18
26
  const contentTypesRelativeToSrcDir = normalizePath(
@@ -41,48 +49,60 @@ function parseEntrySlug({
41
49
  });
42
50
  }
43
51
  }
44
- async function getEntryData(entry, collectionConfig, pluginContext, settings) {
45
- let { slug, ...data } = entry.unvalidatedData;
52
+ async function getEntryData(entry, collectionConfig, pluginContext, config) {
53
+ let data;
54
+ if (collectionConfig.type === "data") {
55
+ data = entry.unvalidatedData;
56
+ } else {
57
+ const { slug, ...unvalidatedData } = entry.unvalidatedData;
58
+ data = unvalidatedData;
59
+ }
46
60
  let schema = collectionConfig.schema;
47
61
  if (typeof schema === "function") {
48
- if (!settings.config.experimental.assets) {
62
+ if (!config.experimental.assets) {
49
63
  throw new Error(
50
64
  "The function shape for schema can only be used when `experimental.assets` is enabled."
51
65
  );
52
66
  }
53
67
  schema = schema({
54
- image: createImage(settings, pluginContext, entry._internal.filePath)
68
+ image: createImage({ config }, pluginContext, entry._internal.filePath)
55
69
  });
56
70
  }
57
71
  if (schema) {
58
- if (typeof schema === "object" && "shape" in schema && schema.shape.slug) {
72
+ if (collectionConfig.type === "content" && typeof schema === "object" && "shape" in schema && schema.shape.slug) {
59
73
  throw new AstroError({
60
74
  ...AstroErrorData.ContentSchemaContainsSlugError,
61
75
  message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection)
62
76
  });
63
77
  }
78
+ let formattedError;
64
79
  const parsed = await schema.safeParseAsync(entry.unvalidatedData, {
65
- errorMap
80
+ errorMap(error, ctx) {
81
+ var _a, _b;
82
+ if (error.code === "custom" && ((_a = error.params) == null ? void 0 : _a.isHoistedAstroError)) {
83
+ formattedError = (_b = error.params) == null ? void 0 : _b.astroError;
84
+ }
85
+ return errorMap(error, ctx);
86
+ }
66
87
  });
67
88
  if (parsed.success) {
68
89
  data = parsed.data;
69
90
  } else {
70
- const formattedError = new AstroError({
71
- ...AstroErrorData.InvalidContentEntryFrontmatterError,
72
- message: AstroErrorData.InvalidContentEntryFrontmatterError.message(
73
- entry.collection,
74
- entry.id,
75
- parsed.error
76
- ),
77
- location: {
78
- file: entry._internal.filePath,
79
- line: getFrontmatterErrorLine(
80
- entry._internal.rawData,
81
- String(parsed.error.errors[0].path[0])
91
+ if (!formattedError) {
92
+ formattedError = new AstroError({
93
+ ...AstroErrorData.InvalidContentEntryFrontmatterError,
94
+ message: AstroErrorData.InvalidContentEntryFrontmatterError.message(
95
+ entry.collection,
96
+ entry.id,
97
+ parsed.error
82
98
  ),
83
- column: 0
84
- }
85
- });
99
+ location: {
100
+ file: entry._internal.filePath,
101
+ line: getYAMLErrorLine(entry._internal.rawData, String(parsed.error.errors[0].path[0])),
102
+ column: 0
103
+ }
104
+ });
105
+ }
86
106
  throw formattedError;
87
107
  }
88
108
  }
@@ -91,6 +111,9 @@ async function getEntryData(entry, collectionConfig, pluginContext, settings) {
91
111
  function getContentEntryExts(settings) {
92
112
  return settings.contentEntryTypes.map((t) => t.extensions).flat();
93
113
  }
114
+ function getDataEntryExts(settings) {
115
+ return settings.dataEntryTypes.map((t) => t.extensions).flat();
116
+ }
94
117
  function getContentEntryConfigByExtMap(settings) {
95
118
  const map = /* @__PURE__ */ new Map();
96
119
  for (const entryType of settings.contentEntryTypes) {
@@ -100,39 +123,57 @@ function getContentEntryConfigByExtMap(settings) {
100
123
  }
101
124
  return map;
102
125
  }
103
- class NoCollectionError extends Error {
126
+ function getEntryCollectionName({
127
+ contentDir,
128
+ entry
129
+ }) {
130
+ const entryPath = typeof entry === "string" ? entry : fileURLToPath(entry);
131
+ const rawRelativePath = path.relative(fileURLToPath(contentDir), entryPath);
132
+ const collectionName = path.dirname(rawRelativePath).split(path.sep)[0];
133
+ const isOutsideCollection = !collectionName || collectionName === "" || collectionName === ".." || collectionName === ".";
134
+ if (isOutsideCollection) {
135
+ return void 0;
136
+ }
137
+ return collectionName;
104
138
  }
105
- function getEntryInfo({
139
+ function getDataEntryId({
106
140
  entry,
107
141
  contentDir,
108
- allowFilesOutsideCollection = false
142
+ collection
109
143
  }) {
110
- const rawRelativePath = path.relative(
111
- fileURLToPath(contentDir),
112
- typeof entry === "string" ? entry : fileURLToPath(entry)
113
- );
114
- const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
115
- const isOutsideCollection = rawCollection === ".." || rawCollection === ".";
116
- if (!rawCollection || !allowFilesOutsideCollection && isOutsideCollection)
117
- return new NoCollectionError();
118
- const rawId = path.relative(rawCollection, rawRelativePath);
119
- const rawIdWithoutFileExt = rawId.replace(new RegExp(path.extname(rawId) + "$"), "");
120
- const rawSlugSegments = rawIdWithoutFileExt.split(path.sep);
144
+ const relativePath = getRelativeEntryPath(entry, collection, contentDir);
145
+ const withoutFileExt = relativePath.replace(new RegExp(path.extname(relativePath) + "$"), "");
146
+ return withoutFileExt;
147
+ }
148
+ function getContentEntryIdAndSlug({
149
+ entry,
150
+ contentDir,
151
+ collection
152
+ }) {
153
+ const relativePath = getRelativeEntryPath(entry, collection, contentDir);
154
+ const withoutFileExt = relativePath.replace(new RegExp(path.extname(relativePath) + "$"), "");
155
+ const rawSlugSegments = withoutFileExt.split(path.sep);
121
156
  const slug = rawSlugSegments.map((segment) => githubSlug(segment)).join("/").replace(/\/index$/, "");
122
157
  const res = {
123
- id: normalizePath(rawId),
124
- slug,
125
- collection: normalizePath(rawCollection)
158
+ id: normalizePath(relativePath),
159
+ slug
126
160
  };
127
161
  return res;
128
162
  }
129
- function getEntryType(entryPath, paths, contentFileExts, experimentalAssets) {
163
+ function getRelativeEntryPath(entry, collection, contentDir) {
164
+ const relativeToContent = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
165
+ const relativeToCollection = path.relative(collection, relativeToContent);
166
+ return relativeToCollection;
167
+ }
168
+ function getEntryType(entryPath, paths, contentFileExts, dataFileExts, experimentalAssets = false) {
130
169
  const { ext, base } = path.parse(entryPath);
131
170
  const fileUrl = pathToFileURL(entryPath);
132
171
  if (hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir) || isOnIgnoreList(base) || experimentalAssets && isImageAsset(ext)) {
133
172
  return "ignored";
134
173
  } else if (contentFileExts.includes(ext)) {
135
174
  return "content";
175
+ } else if (dataFileExts.includes(ext)) {
176
+ return "data";
136
177
  } else if (fileUrl.href === paths.config.url.href) {
137
178
  return "config";
138
179
  } else {
@@ -153,15 +194,19 @@ function hasUnderscoreBelowContentDirectoryPath(fileUrl, contentDir) {
153
194
  }
154
195
  return false;
155
196
  }
156
- function getFrontmatterErrorLine(rawFrontmatter, frontmatterKey) {
157
- if (!rawFrontmatter)
197
+ function getYAMLErrorLine(rawData, objectKey) {
198
+ if (!rawData)
158
199
  return 0;
159
- const indexOfFrontmatterKey = rawFrontmatter.indexOf(`
160
- ${frontmatterKey}`);
161
- if (indexOfFrontmatterKey === -1)
200
+ const indexOfObjectKey = rawData.search(
201
+ // Match key either at the top of the file or after a newline
202
+ // Ensures matching on top-level object keys only
203
+ new RegExp(`(
204
+ |^)${objectKey}`)
205
+ );
206
+ if (indexOfObjectKey === -1)
162
207
  return 0;
163
- const frontmatterBeforeKey = rawFrontmatter.substring(0, indexOfFrontmatterKey + 1);
164
- const numNewlinesBeforeKey = frontmatterBeforeKey.split("\n").length;
208
+ const dataBeforeKey = rawData.substring(0, indexOfObjectKey + 1);
209
+ const numNewlinesBeforeKey = dataBeforeKey.split("\n").length;
165
210
  return numNewlinesBeforeKey;
166
211
  }
167
212
  function parseFrontmatter(fileContents, filePath) {
@@ -169,18 +214,18 @@ function parseFrontmatter(fileContents, filePath) {
169
214
  matter.clearCache();
170
215
  return matter(fileContents);
171
216
  } catch (e) {
172
- if (e.name === "YAMLException") {
173
- const err = e;
174
- err.id = filePath;
175
- err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column };
176
- err.message = e.reason;
177
- throw err;
217
+ if (isYAMLException(e)) {
218
+ throw formatYAMLException(e);
178
219
  } else {
179
220
  throw e;
180
221
  }
181
222
  }
182
223
  }
183
224
  const globalContentConfigObserver = contentObservable({ status: "init" });
225
+ function hasContentFlag(viteId, flag) {
226
+ const flags = new URLSearchParams(viteId.split("?")[1] ?? "");
227
+ return flags.has(flag);
228
+ }
184
229
  async function loadContentConfig({
185
230
  fs,
186
231
  settings,
@@ -191,12 +236,8 @@ async function loadContentConfig({
191
236
  if (!contentPaths.config.exists) {
192
237
  return void 0;
193
238
  }
194
- try {
195
- const configPathname = fileURLToPath(contentPaths.config.url);
196
- unparsedConfig = await viteServer.ssrLoadModule(configPathname);
197
- } catch (e) {
198
- throw e;
199
- }
239
+ const configPathname = fileURLToPath(contentPaths.config.url);
240
+ unparsedConfig = await viteServer.ssrLoadModule(configPathname);
200
241
  const config = contentConfigParser.safeParse(unparsedConfig);
201
242
  if (config.success) {
202
243
  return config.data;
@@ -204,6 +245,25 @@ async function loadContentConfig({
204
245
  return void 0;
205
246
  }
206
247
  }
248
+ async function reloadContentConfigObserver({
249
+ observer = globalContentConfigObserver,
250
+ ...loadContentConfigOpts
251
+ }) {
252
+ observer.set({ status: "loading" });
253
+ try {
254
+ const config = await loadContentConfig(loadContentConfigOpts);
255
+ if (config) {
256
+ observer.set({ status: "loaded", config });
257
+ } else {
258
+ observer.set({ status: "does-not-exist" });
259
+ }
260
+ } catch (e) {
261
+ observer.set({
262
+ status: "error",
263
+ error: e instanceof Error ? e : new AstroError(AstroErrorData.UnknownContentCollectionError)
264
+ });
265
+ }
266
+ }
207
267
  function contentObservable(initialCtx) {
208
268
  const subscribers = /* @__PURE__ */ new Set();
209
269
  let ctx = initialCtx;
@@ -266,7 +326,7 @@ async function getEntrySlug({
266
326
  }
267
327
  const { slug: frontmatterSlug } = await contentEntryType.getEntryInfo({
268
328
  fileUrl,
269
- contents: await fs.promises.readFile(fileUrl, "utf-8")
329
+ contents
270
330
  });
271
331
  return parseEntrySlug({ generatedSlug, frontmatterSlug, id, collection });
272
332
  }
@@ -277,23 +337,27 @@ function getExtGlob(exts) {
277
337
  ) : `{${exts.join(",")}}`;
278
338
  }
279
339
  export {
280
- NoCollectionError,
281
340
  collectionConfigParser,
282
341
  contentConfigParser,
283
342
  contentObservable,
284
343
  getContentEntryConfigByExtMap,
285
344
  getContentEntryExts,
345
+ getContentEntryIdAndSlug,
286
346
  getContentPaths,
347
+ getDataEntryExts,
348
+ getDataEntryId,
287
349
  getDotAstroTypeReference,
350
+ getEntryCollectionName,
288
351
  getEntryData,
289
- getEntryInfo,
290
352
  getEntrySlug,
291
353
  getEntryType,
292
354
  getExtGlob,
293
355
  globalContentConfigObserver,
356
+ hasContentFlag,
294
357
  hasUnderscoreBelowContentDirectoryPath,
295
358
  loadContentConfig,
296
359
  msg,
297
360
  parseEntrySlug,
298
- parseFrontmatter
361
+ parseFrontmatter,
362
+ reloadContentConfigObserver
299
363
  };
@@ -4,22 +4,22 @@ import { pathToFileURL } from "url";
4
4
  import { AstroErrorData } from "../core/errors/errors-data.js";
5
5
  import { AstroError } from "../core/errors/errors.js";
6
6
  import { escapeViteEnvReferences, getFileInfo } from "../vite-plugin-utils/index.js";
7
- import { CONTENT_FLAG } from "./consts.js";
7
+ import { CONTENT_FLAG, DATA_FLAG } from "./consts.js";
8
8
  import {
9
9
  getContentEntryConfigByExtMap,
10
10
  getContentEntryExts,
11
+ getContentEntryIdAndSlug,
11
12
  getContentPaths,
13
+ getDataEntryExts,
14
+ getDataEntryId,
15
+ getEntryCollectionName,
12
16
  getEntryData,
13
- getEntryInfo,
14
17
  getEntryType,
15
18
  globalContentConfigObserver,
16
- NoCollectionError,
17
- parseEntrySlug
19
+ hasContentFlag,
20
+ parseEntrySlug,
21
+ reloadContentConfigObserver
18
22
  } from "./utils.js";
19
- function isContentFlagImport(viteId) {
20
- const flags = new URLSearchParams(viteId.split("?")[1]);
21
- return flags.has(CONTENT_FLAG);
22
- }
23
23
  function getContentRendererByViteId(viteId, settings) {
24
24
  let ext = viteId.split(".").pop();
25
25
  if (!ext)
@@ -32,18 +32,47 @@ function getContentRendererByViteId(viteId, settings) {
32
32
  return void 0;
33
33
  }
34
34
  const CHOKIDAR_MODIFIED_EVENTS = ["add", "unlink", "change"];
35
+ const COLLECTION_TYPES_TO_INVALIDATE_ON = ["data", "content", "config"];
35
36
  function astroContentImportPlugin({
36
37
  fs,
37
38
  settings
38
39
  }) {
39
40
  const contentPaths = getContentPaths(settings.config, fs);
40
41
  const contentEntryExts = getContentEntryExts(settings);
42
+ const dataEntryExts = getDataEntryExts(settings);
41
43
  const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings);
44
+ const dataEntryExtToParser = /* @__PURE__ */ new Map();
45
+ for (const entryType of settings.dataEntryTypes) {
46
+ for (const ext of entryType.extensions) {
47
+ dataEntryExtToParser.set(ext, entryType.getEntryInfo);
48
+ }
49
+ }
42
50
  const plugins = [
43
51
  {
44
52
  name: "astro:content-imports",
45
53
  async transform(_, viteId) {
46
- if (isContentFlagImport(viteId)) {
54
+ if (hasContentFlag(viteId, DATA_FLAG)) {
55
+ const fileId = viteId.split("?")[0] ?? viteId;
56
+ const { id, data, collection, _internal } = await getDataEntryModule({
57
+ fileId,
58
+ dataEntryExtToParser,
59
+ contentPaths,
60
+ settings,
61
+ fs,
62
+ pluginContext: this
63
+ });
64
+ const code = escapeViteEnvReferences(`
65
+ export const id = ${JSON.stringify(id)};
66
+ export const collection = ${JSON.stringify(collection)};
67
+ export const data = ${devalue.uneval(data)};
68
+ export const _internal = {
69
+ type: 'data',
70
+ filePath: ${JSON.stringify(_internal.filePath)},
71
+ rawData: ${JSON.stringify(_internal.rawData)},
72
+ };
73
+ `);
74
+ return code;
75
+ } else if (hasContentFlag(viteId, CONTENT_FLAG)) {
47
76
  const fileId = viteId.split("?")[0];
48
77
  const { id, slug, collection, body, data, _internal } = await setContentEntryModuleCache({
49
78
  fileId,
@@ -56,6 +85,7 @@ function astroContentImportPlugin({
56
85
  export const body = ${JSON.stringify(body)};
57
86
  export const data = ${devalue.uneval(data)};
58
87
  export const _internal = {
88
+ type: 'content',
59
89
  filePath: ${JSON.stringify(_internal.filePath)},
60
90
  rawData: ${JSON.stringify(_internal.rawData)},
61
91
  };`);
@@ -64,14 +94,21 @@ function astroContentImportPlugin({
64
94
  },
65
95
  configureServer(viteServer) {
66
96
  viteServer.watcher.on("all", async (event, entry) => {
67
- if (CHOKIDAR_MODIFIED_EVENTS.includes(event) && getEntryType(
68
- entry,
69
- contentPaths,
70
- contentEntryExts,
71
- settings.config.experimental.assets
72
- ) === "config") {
97
+ if (CHOKIDAR_MODIFIED_EVENTS.includes(event)) {
98
+ const entryType = getEntryType(
99
+ entry,
100
+ contentPaths,
101
+ contentEntryExts,
102
+ dataEntryExts,
103
+ settings.config.experimental.assets
104
+ );
105
+ if (!COLLECTION_TYPES_TO_INVALIDATE_ON.includes(entryType))
106
+ return;
107
+ if (entryType === "content" || entryType === "data") {
108
+ await reloadContentConfigObserver({ fs, settings, viteServer });
109
+ }
73
110
  for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
74
- if (isContentFlagImport(modUrl) || Boolean(getContentRendererByViteId(modUrl, settings))) {
111
+ if (hasContentFlag(modUrl, CONTENT_FLAG) || hasContentFlag(modUrl, DATA_FLAG) || Boolean(getContentRendererByViteId(modUrl, settings))) {
75
112
  const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
76
113
  if (mod) {
77
114
  viteServer.moduleGraph.invalidateModule(mod);
@@ -145,13 +182,12 @@ function astroContentImportPlugin({
145
182
  fileUrl: pathToFileURL(fileId),
146
183
  contents: rawContents
147
184
  });
148
- const entryInfoResult = getEntryInfo({
149
- entry: pathToFileURL(fileId),
150
- contentDir: contentPaths.contentDir
151
- });
152
- if (entryInfoResult instanceof NoCollectionError)
153
- throw entryInfoResult;
154
- const { id, slug: generatedSlug, collection } = entryInfoResult;
185
+ const entry = pathToFileURL(fileId);
186
+ const { contentDir } = contentPaths;
187
+ const collection = getEntryCollectionName({ entry, contentDir });
188
+ if (collection === void 0)
189
+ throw new AstroError(AstroErrorData.UnknownContentCollectionError);
190
+ const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection });
155
191
  const _internal = { filePath: fileId, rawData };
156
192
  const slug = parseEntrySlug({
157
193
  id,
@@ -161,10 +197,10 @@ function astroContentImportPlugin({
161
197
  });
162
198
  const collectionConfig = contentConfig == null ? void 0 : contentConfig.collections[collection];
163
199
  let data = collectionConfig ? await getEntryData(
164
- { id, collection, slug, _internal, unvalidatedData },
200
+ { id, collection, _internal, unvalidatedData },
165
201
  collectionConfig,
166
202
  pluginContext,
167
- settings
203
+ settings.config
168
204
  ) : unvalidatedData;
169
205
  const contentEntryModule = {
170
206
  id,
@@ -213,6 +249,55 @@ async function getContentConfigFromGlobal() {
213
249
  }
214
250
  return contentConfig;
215
251
  }
252
+ async function getDataEntryModule({
253
+ fileId,
254
+ dataEntryExtToParser,
255
+ contentPaths,
256
+ fs,
257
+ pluginContext,
258
+ settings
259
+ }) {
260
+ const contentConfig = await getContentConfigFromGlobal();
261
+ let rawContents;
262
+ try {
263
+ rawContents = await fs.promises.readFile(fileId, "utf-8");
264
+ } catch (e) {
265
+ throw new AstroError({
266
+ ...AstroErrorData.UnknownContentCollectionError,
267
+ message: `Unexpected error reading entry ${JSON.stringify(fileId)}.`,
268
+ stack: e instanceof Error ? e.stack : void 0
269
+ });
270
+ }
271
+ const fileExt = extname(fileId);
272
+ const dataEntryParser = dataEntryExtToParser.get(fileExt);
273
+ if (!dataEntryParser) {
274
+ throw new AstroError({
275
+ ...AstroErrorData.UnknownContentCollectionError,
276
+ message: `No parser found for data entry ${JSON.stringify(
277
+ fileId
278
+ )}. Did you apply an integration for this file type?`
279
+ });
280
+ }
281
+ const { data: unvalidatedData, rawData = "" } = await dataEntryParser({
282
+ fileUrl: pathToFileURL(fileId),
283
+ contents: rawContents
284
+ });
285
+ const entry = pathToFileURL(fileId);
286
+ const { contentDir } = contentPaths;
287
+ const collection = getEntryCollectionName({ entry, contentDir });
288
+ if (collection === void 0)
289
+ throw new AstroError(AstroErrorData.UnknownContentCollectionError);
290
+ const id = getDataEntryId({ entry, contentDir, collection });
291
+ const _internal = { filePath: fileId, rawData };
292
+ const collectionConfig = contentConfig == null ? void 0 : contentConfig.collections[collection];
293
+ const data = collectionConfig ? await getEntryData(
294
+ { id, collection, _internal, unvalidatedData },
295
+ collectionConfig,
296
+ pluginContext,
297
+ settings.config
298
+ ) : unvalidatedData;
299
+ return { id, collection, data, _internal };
300
+ }
216
301
  export {
217
302
  astroContentImportPlugin
218
303
  };