astro 6.4.0 → 6.4.2

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.
@@ -1,6 +1,6 @@
1
1
  class BuildTimeAstroVersionProvider {
2
2
  // Injected during the build through esbuild define
3
- version = "6.4.0";
3
+ version = "6.4.2";
4
4
  }
5
5
  export {
6
6
  BuildTimeAstroVersionProvider
@@ -197,7 +197,7 @@ ${contentConfig.error.message}`
197
197
  logger.info("Content config changed");
198
198
  shouldClear = true;
199
199
  }
200
- if (previousAstroVersion && previousAstroVersion !== "6.4.0") {
200
+ if (previousAstroVersion && previousAstroVersion !== "6.4.2") {
201
201
  logger.info("Astro version changed");
202
202
  shouldClear = true;
203
203
  }
@@ -205,8 +205,8 @@ ${contentConfig.error.message}`
205
205
  logger.info("Clearing content store");
206
206
  this.#store.clearAll();
207
207
  }
208
- if ("6.4.0") {
209
- this.#store.metaStore().set("astro-version", "6.4.0");
208
+ if ("6.4.2") {
209
+ this.#store.metaStore().set("astro-version", "6.4.2");
210
210
  }
211
211
  if (currentConfigDigest) {
212
212
  this.#store.metaStore().set("content-config-digest", currentConfigDigest);
@@ -151,6 +151,10 @@ class BaseApp {
151
151
  return routeData;
152
152
  }
153
153
  if (routeData.prerender) {
154
+ if (routeData.params.length > 0) {
155
+ const allMatches = this.pipeline.matchAllRoutes(decodeURI(pathname));
156
+ return allMatches.find((r) => !r.prerender);
157
+ }
154
158
  return void 0;
155
159
  }
156
160
  return routeData;
@@ -117,6 +117,13 @@ export declare abstract class Pipeline {
117
117
  * routes or check public assets — use `BaseApp.match()` for that.
118
118
  */
119
119
  matchRoute(pathname: string): RouteData | undefined;
120
+ /**
121
+ * Returns all routes matching the given pathname, in priority order.
122
+ * Used when the first match cannot serve the request (e.g. a
123
+ * prerendered dynamic route that doesn't cover this specific path)
124
+ * and the caller needs to try subsequent matches.
125
+ */
126
+ matchAllRoutes(pathname: string): RouteData[];
120
127
  /**
121
128
  * Rebuilds the internal router after routes have been added or
122
129
  * removed (e.g. by the dev server on HMR).
@@ -123,6 +123,15 @@ class Pipeline {
123
123
  if (match.type !== "match") return void 0;
124
124
  return match.route;
125
125
  }
126
+ /**
127
+ * Returns all routes matching the given pathname, in priority order.
128
+ * Used when the first match cannot serve the request (e.g. a
129
+ * prerendered dynamic route that doesn't cover this specific path)
130
+ * and the caller needs to try subsequent matches.
131
+ */
132
+ matchAllRoutes(pathname) {
133
+ return this.#router.matchAll(pathname, { allowWithoutBase: true });
134
+ }
126
135
  /**
127
136
  * Rebuilds the internal router after routes have been added or
128
137
  * removed (e.g. by the dev server on HMR).
@@ -35,6 +35,7 @@ function warnDeprecatedMarkdownOptions(config) {
35
35
  }
36
36
  let didWarnAboutLegacyMarkdownPlugins = false;
37
37
  let didWarnAboutProcessorMismatch = false;
38
+ const migratedLegacyPluginCounts = /* @__PURE__ */ new WeakMap();
38
39
  async function coerceLegacyMarkdownPlugins(config) {
39
40
  const md = config?.markdown;
40
41
  if (!md) return;
@@ -48,18 +49,24 @@ async function coerceLegacyMarkdownPlugins(config) {
48
49
  const current = md.processor;
49
50
  if (!current || isUnifiedProcessor(current)) {
50
51
  const target = current ?? (md.processor = unified());
51
- target.options.remarkPlugins.push(...remarkPlugins);
52
- target.options.rehypePlugins.push(...rehypePlugins);
52
+ const counts = migratedLegacyPluginCounts.get(target.options) ?? { remark: 0, rehype: 0 };
53
+ if (remarkPlugins.length > counts.remark) {
54
+ target.options.remarkPlugins.push(...remarkPlugins.slice(counts.remark));
55
+ }
56
+ if (rehypePlugins.length > counts.rehype) {
57
+ target.options.rehypePlugins.push(...rehypePlugins.slice(counts.rehype));
58
+ }
53
59
  Object.assign(target.options.remarkRehype, remarkRehype);
60
+ migratedLegacyPluginCounts.set(target.options, {
61
+ remark: remarkPlugins.length,
62
+ rehype: rehypePlugins.length
63
+ });
54
64
  if (!didWarnAboutLegacyMarkdownPlugins) {
55
65
  didWarnAboutLegacyMarkdownPlugins = true;
56
66
  console.warn(
57
67
  "[astro] `markdown.remarkPlugins`, `markdown.rehypePlugins`, and `markdown.remarkRehype` are deprecated. Pass them to `unified({...})` from `@astrojs/markdown-remark` directly instead."
58
68
  );
59
69
  }
60
- delete md.remarkPlugins;
61
- delete md.rehypePlugins;
62
- delete md.remarkRehype;
63
70
  } else if (!didWarnAboutProcessorMismatch) {
64
71
  didWarnAboutProcessorMismatch = true;
65
72
  console.warn(
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "6.4.0";
1
+ const ASTRO_VERSION = "6.4.2";
2
2
  const ASTRO_GENERATOR = `Astro v${ASTRO_VERSION}`;
3
3
  const REROUTE_DIRECTIVE_HEADER = "X-Astro-Reroute";
4
4
  const REWRITE_DIRECTIVE_HEADER_KEY = "X-Astro-Rewrite";
@@ -37,7 +37,7 @@ async function dev(inlineConfig) {
37
37
  await telemetry.record([]);
38
38
  const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
39
39
  const logger = restart.container.logger;
40
- const currentVersion = "6.4.0";
40
+ const currentVersion = "6.4.2";
41
41
  const isPrerelease = currentVersion.includes("-");
42
42
  if (!isPrerelease) {
43
43
  try {
@@ -608,7 +608,12 @@ class FetchState {
608
608
  }
609
609
  const matched = pipeline.matchRoute(this.pathname);
610
610
  if (matched && matched.prerender && pipeline.manifest.serverLike) {
611
- this.routeData = void 0;
611
+ if (matched.params.length > 0) {
612
+ const allMatches = pipeline.matchAllRoutes(this.pathname);
613
+ this.routeData = allMatches.find((r) => !r.prerender);
614
+ } else {
615
+ this.routeData = void 0;
616
+ }
612
617
  } else {
613
618
  this.routeData = matched;
614
619
  }
@@ -276,7 +276,7 @@ function printHelp({
276
276
  message.push(
277
277
  linebreak(),
278
278
  ` ${bgGreen(black(` ${commandName} `))} ${green(
279
- `v${"6.4.0"}`
279
+ `v${"6.4.2"}`
280
280
  )} ${headline}`
281
281
  );
282
282
  }
@@ -46,5 +46,13 @@ export declare class Router {
46
46
  match(inputPathname: string, { allowWithoutBase }?: {
47
47
  allowWithoutBase?: boolean;
48
48
  }): RouterMatch;
49
+ /**
50
+ * Returns all routes that match the given pathname, in priority order.
51
+ * Used when the first match (e.g. a prerendered route) cannot serve
52
+ * the request and subsequent matches need to be tried.
53
+ */
54
+ matchAll(inputPathname: string, { allowWithoutBase }?: {
55
+ allowWithoutBase?: boolean;
56
+ }): RouteData[];
49
57
  }
50
58
  export {};
@@ -57,6 +57,34 @@ class Router {
57
57
  const params = getParams(route, pathname);
58
58
  return { type: "match", route, params, pathname };
59
59
  }
60
+ /**
61
+ * Returns all routes that match the given pathname, in priority order.
62
+ * Used when the first match (e.g. a prerendered route) cannot serve
63
+ * the request and subsequent matches need to be tried.
64
+ */
65
+ matchAll(inputPathname, { allowWithoutBase = false } = {}) {
66
+ const normalized = getRedirectForPathname(inputPathname);
67
+ if (normalized.redirect) {
68
+ return [];
69
+ }
70
+ const baseResult = stripBase(
71
+ normalized.pathname,
72
+ this.#base,
73
+ this.#baseWithoutTrailingSlash,
74
+ this.#trailingSlash
75
+ );
76
+ if (!baseResult && !allowWithoutBase) {
77
+ return [];
78
+ }
79
+ let pathname = baseResult ?? normalized.pathname;
80
+ if (this.#buildFormat === "file") {
81
+ pathname = normalizeFileFormatPathname(pathname);
82
+ }
83
+ return this.#routes.filter((candidate) => {
84
+ if (candidate.pattern.test(pathname)) return true;
85
+ return candidate.fallbackRoutes.some((fallbackRoute) => fallbackRoute.pattern.test(pathname));
86
+ });
87
+ }
60
88
  }
61
89
  function normalizeBase(base) {
62
90
  if (!base) return "/";
@@ -0,0 +1,5 @@
1
+ import type { RehypePlugin } from '@astrojs/internal-helpers/markdown';
2
+ import type { VFile } from 'vfile';
3
+ import type { PluginMetadata } from '../vite-plugin-astro/types.js';
4
+ export declare const rehypeAnalyzeAstroMetadata: RehypePlugin;
5
+ export declare function getAstroMetadata(file: VFile): PluginMetadata["astro"] | undefined;
@@ -0,0 +1,241 @@
1
+ import { visit } from "unist-util-visit";
2
+ import { AstroError } from "../core/errors/errors.js";
3
+ import { AstroErrorData } from "../core/errors/index.js";
4
+ import { resolvePath } from "../core/viteUtils.js";
5
+ import { createDefaultAstroMetadata } from "../vite-plugin-astro/metadata.js";
6
+ const ClientOnlyPlaceholder = "astro-client-only";
7
+ const rehypeAnalyzeAstroMetadata = () => {
8
+ return (tree, file) => {
9
+ const metadata = createDefaultAstroMetadata();
10
+ const imports = parseImports(tree.children);
11
+ visit(tree, (node) => {
12
+ if (node.type !== "mdxJsxFlowElement" && node.type !== "mdxJsxTextElement") return;
13
+ const tagName = node.name;
14
+ if (!tagName || !isComponent(tagName) || !(hasClientDirective(node) || hasServerDeferDirective(node)))
15
+ return;
16
+ const matchedImport = findMatchingImport(tagName, imports);
17
+ if (!matchedImport) {
18
+ throw new AstroError({
19
+ ...AstroErrorData.NoMatchingImport,
20
+ message: AstroErrorData.NoMatchingImport.message(node.name)
21
+ });
22
+ }
23
+ if (matchedImport.path.endsWith(".astro")) {
24
+ const clientAttribute = node.attributes.find(
25
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name.startsWith("client:")
26
+ );
27
+ if (clientAttribute) {
28
+ console.warn(
29
+ `You are attempting to render <${node.name} ${clientAttribute.name} />, but ${node.name} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.`
30
+ );
31
+ }
32
+ }
33
+ const resolvedPath = resolvePath(matchedImport.path, file.path);
34
+ if (hasClientOnlyDirective(node)) {
35
+ metadata.clientOnlyComponents.push({
36
+ exportName: matchedImport.name,
37
+ localName: "",
38
+ specifier: tagName,
39
+ resolvedPath
40
+ });
41
+ addClientOnlyMetadata(node, matchedImport, resolvedPath);
42
+ } else if (hasClientDirective(node)) {
43
+ metadata.hydratedComponents.push({
44
+ exportName: "*",
45
+ localName: "",
46
+ specifier: tagName,
47
+ resolvedPath
48
+ });
49
+ addClientMetadata(node, matchedImport, resolvedPath);
50
+ } else if (hasServerDeferDirective(node)) {
51
+ metadata.serverComponents.push({
52
+ exportName: matchedImport.name,
53
+ localName: tagName,
54
+ specifier: matchedImport.path,
55
+ resolvedPath
56
+ });
57
+ addServerDeferMetadata(node, matchedImport, resolvedPath);
58
+ }
59
+ });
60
+ file.data.__astroMetadata = metadata;
61
+ };
62
+ };
63
+ function getAstroMetadata(file) {
64
+ return file.data.__astroMetadata;
65
+ }
66
+ function parseImports(children) {
67
+ const imports = /* @__PURE__ */ new Map();
68
+ for (const child of children) {
69
+ if (child.type !== "mdxjsEsm") continue;
70
+ const body = child.data?.estree?.body;
71
+ if (!body) continue;
72
+ for (const ast of body) {
73
+ if (ast.type !== "ImportDeclaration") continue;
74
+ const source = ast.source.value;
75
+ const specs = ast.specifiers.map((spec) => {
76
+ switch (spec.type) {
77
+ case "ImportDefaultSpecifier":
78
+ return { local: spec.local.name, imported: "default" };
79
+ case "ImportNamespaceSpecifier":
80
+ return { local: spec.local.name, imported: "*" };
81
+ case "ImportSpecifier": {
82
+ return {
83
+ local: spec.local.name,
84
+ imported: spec.imported.type === "Identifier" ? spec.imported.name : String(spec.imported.value)
85
+ };
86
+ }
87
+ default:
88
+ throw new Error("Unknown import declaration specifier: " + spec);
89
+ }
90
+ });
91
+ let specSet = imports.get(source);
92
+ if (!specSet) {
93
+ specSet = /* @__PURE__ */ new Set();
94
+ imports.set(source, specSet);
95
+ }
96
+ for (const spec of specs) {
97
+ specSet.add(spec);
98
+ }
99
+ }
100
+ }
101
+ return imports;
102
+ }
103
+ function isComponent(tagName) {
104
+ return tagName[0] && tagName[0].toLowerCase() !== tagName[0] || tagName.includes(".") || /[^a-zA-Z]/.test(tagName[0]);
105
+ }
106
+ function hasClientDirective(node) {
107
+ return node.attributes.some(
108
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name.startsWith("client:")
109
+ );
110
+ }
111
+ function hasServerDeferDirective(node) {
112
+ return node.attributes.some(
113
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "server:defer"
114
+ );
115
+ }
116
+ function hasClientOnlyDirective(node) {
117
+ return node.attributes.some(
118
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "client:only"
119
+ );
120
+ }
121
+ function findMatchingImport(tagName, imports) {
122
+ const tagSpecifier = tagName.split(".")[0];
123
+ for (const [source, specs] of imports) {
124
+ for (const { imported, local } of specs) {
125
+ if (local === tagSpecifier) {
126
+ if (tagSpecifier !== tagName) {
127
+ switch (imported) {
128
+ // Namespace import: "<buttons.Foo.Bar />" => name: "Foo.Bar"
129
+ case "*": {
130
+ const accessPath = tagName.slice(tagSpecifier.length + 1);
131
+ return { name: accessPath, path: source };
132
+ }
133
+ // Default import: "<buttons.Foo.Bar />" => name: "default.Foo.Bar"
134
+ case "default": {
135
+ const accessPath = tagName.slice(tagSpecifier.length + 1);
136
+ return { name: `default.${accessPath}`, path: source };
137
+ }
138
+ // Named import: "<buttons.Foo.Bar />" => name: "buttons.Foo.Bar"
139
+ default: {
140
+ return { name: tagName, path: source };
141
+ }
142
+ }
143
+ }
144
+ return { name: imported, path: source };
145
+ }
146
+ }
147
+ }
148
+ }
149
+ function addClientMetadata(node, meta, resolvedPath) {
150
+ const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
151
+ if (!attributeNames.includes("client:component-path")) {
152
+ node.attributes.push({
153
+ type: "mdxJsxAttribute",
154
+ name: "client:component-path",
155
+ value: resolvedPath
156
+ });
157
+ }
158
+ if (!attributeNames.includes("client:component-export")) {
159
+ if (meta.name === "*") {
160
+ meta.name = node.name.split(".").slice(1).join(".");
161
+ }
162
+ node.attributes.push({
163
+ type: "mdxJsxAttribute",
164
+ name: "client:component-export",
165
+ value: meta.name
166
+ });
167
+ }
168
+ if (!attributeNames.includes("client:component-hydration")) {
169
+ node.attributes.push({
170
+ type: "mdxJsxAttribute",
171
+ name: "client:component-hydration",
172
+ value: null
173
+ });
174
+ }
175
+ }
176
+ function addClientOnlyMetadata(node, meta, resolvedPath) {
177
+ const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
178
+ if (!attributeNames.includes("client:display-name")) {
179
+ node.attributes.push({
180
+ type: "mdxJsxAttribute",
181
+ name: "client:display-name",
182
+ value: node.name
183
+ });
184
+ }
185
+ if (!attributeNames.includes("client:component-path")) {
186
+ node.attributes.push({
187
+ type: "mdxJsxAttribute",
188
+ name: "client:component-path",
189
+ value: resolvedPath
190
+ });
191
+ }
192
+ if (!attributeNames.includes("client:component-export")) {
193
+ if (meta.name === "*") {
194
+ meta.name = node.name.split(".").slice(1).join(".");
195
+ }
196
+ node.attributes.push({
197
+ type: "mdxJsxAttribute",
198
+ name: "client:component-export",
199
+ value: meta.name
200
+ });
201
+ }
202
+ if (!attributeNames.includes("client:component-hydration")) {
203
+ node.attributes.push({
204
+ type: "mdxJsxAttribute",
205
+ name: "client:component-hydration",
206
+ value: null
207
+ });
208
+ }
209
+ node.name = ClientOnlyPlaceholder;
210
+ }
211
+ function addServerDeferMetadata(node, meta, resolvedPath) {
212
+ const attributeNames = node.attributes.map((attr) => attr.type === "mdxJsxAttribute" ? attr.name : null).filter(Boolean);
213
+ if (!attributeNames.includes("server:component-directive")) {
214
+ node.attributes.push({
215
+ type: "mdxJsxAttribute",
216
+ name: "server:component-directive",
217
+ value: "server:defer"
218
+ });
219
+ }
220
+ if (!attributeNames.includes("server:component-path")) {
221
+ node.attributes.push({
222
+ type: "mdxJsxAttribute",
223
+ name: "server:component-path",
224
+ value: resolvedPath
225
+ });
226
+ }
227
+ if (!attributeNames.includes("server:component-export")) {
228
+ if (meta.name === "*") {
229
+ meta.name = node.name.split(".").slice(1).join(".");
230
+ }
231
+ node.attributes.push({
232
+ type: "mdxJsxAttribute",
233
+ name: "server:component-export",
234
+ value: meta.name
235
+ });
236
+ }
237
+ }
238
+ export {
239
+ getAstroMetadata,
240
+ rehypeAnalyzeAstroMetadata
241
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "6.4.0",
3
+ "version": "6.4.2",
4
4
  "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
5
5
  "type": "module",
6
6
  "author": "withastro",
@@ -39,6 +39,7 @@
39
39
  "./astro-jsx": "./astro-jsx.d.ts",
40
40
  "./tsconfigs/*.json": "./tsconfigs/*",
41
41
  "./tsconfigs/*": "./tsconfigs/*.json",
42
+ "./jsx/rehype.js": "./dist/jsx/rehype.js",
42
43
  "./jsx-runtime": {
43
44
  "types": "./jsx-runtime.d.ts",
44
45
  "default": "./dist/jsx-runtime/index.js"
@@ -188,6 +189,8 @@
188
189
  "expect-type": "^1.3.0",
189
190
  "fs-fixture": "^2.13.0",
190
191
  "hono": "^4.12.14",
192
+ "mdast-util-mdx": "^3.0.0",
193
+ "mdast-util-mdx-jsx": "^3.2.0",
191
194
  "node-mocks-http": "^1.17.2",
192
195
  "parse-srcset": "^1.0.2",
193
196
  "rehype-autolink-headings": "^7.1.0",