astro 1.7.2 → 1.9.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.
@@ -1,7 +1,9 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
+ /// <reference types="node" />
3
4
  import type { MarkdownHeading, MarkdownMetadata, MarkdownRenderingResult, RehypePlugins, RemarkPlugins, RemarkRehype, ShikiConfig } from '@astrojs/markdown-remark';
4
5
  import type * as babel from '@babel/core';
6
+ import type { OutgoingHttpHeaders } from 'http';
5
7
  import type { AddressInfo } from 'net';
6
8
  import type { TsConfigJson } from 'tsconfig-resolver';
7
9
  import type * as vite from 'vite';
@@ -287,6 +289,15 @@ declare type ServerConfig = {
287
289
  * If the given port is already in use, Astro will automatically try the next available port.
288
290
  */
289
291
  port?: number;
292
+ /**
293
+ * @name server.headers
294
+ * @typeraw {OutgoingHttpHeaders}
295
+ * @default `{}`
296
+ * @version 1.7.0
297
+ * @description
298
+ * Set custom HTTP response headers to be sent in `astro dev` and `astro preview`.
299
+ */
300
+ headers?: OutgoingHttpHeaders;
290
301
  };
291
302
  export interface ViteUserConfig extends vite.UserConfig {
292
303
  ssr?: vite.SSROptions;
@@ -618,6 +629,15 @@ export interface AstroUserConfig {
618
629
  * }
619
630
  * ```
620
631
  */
632
+ /**
633
+ * @docs
634
+ * @name server.headers
635
+ * @typeraw {OutgoingHttpHeaders}
636
+ * @default `{}`
637
+ * @version 1.7.0
638
+ * @description
639
+ * Set custom HTTP response headers to be sent in `astro dev` and `astro preview`.
640
+ */
621
641
  server?: ServerConfig | ((options: {
622
642
  command: 'dev' | 'preview';
623
643
  }) => ServerConfig);
package/dist/cli/index.js CHANGED
@@ -31,6 +31,7 @@ function printAstroHelp() {
31
31
  ["dev", "Start the development server."],
32
32
  ["docs", "Open documentation in your web browser."],
33
33
  ["preview", "Preview your build locally."],
34
+ ["sync", "Generate content collection types."],
34
35
  ["telemetry", "Configure telemetry settings."]
35
36
  ],
36
37
  "Global Flags": [
@@ -54,6 +55,8 @@ function resolveCommand(flags) {
54
55
  const cmd = flags._[2];
55
56
  if (cmd === "add")
56
57
  return "add";
58
+ if (cmd === "sync")
59
+ return "sync";
57
60
  if (cmd === "telemetry")
58
61
  return "telemetry";
59
62
  if (flags.version)
@@ -153,6 +156,11 @@ async function runCommand(cmd, flags) {
153
156
  const ret = await check(settings);
154
157
  return process.exit(ret);
155
158
  }
159
+ case "sync": {
160
+ const { sync } = await import("./sync/index.js");
161
+ const ret = await sync(settings, { logging, fs });
162
+ return process.exit(ret);
163
+ }
156
164
  case "preview": {
157
165
  const { default: preview } = await import("../core/preview/index.js");
158
166
  const server = await preview(settings, { logging, telemetry });
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ import type fsMod from 'node:fs';
3
+ import type { AstroSettings } from '../../@types/astro';
4
+ import { LogOptions } from '../../core/logger/core.js';
5
+ export declare function sync(settings: AstroSettings, { logging, fs }: {
6
+ logging: LogOptions;
7
+ fs: typeof fsMod;
8
+ }): Promise<0 | 1>;
@@ -0,0 +1,25 @@
1
+ import { dim } from "kleur/colors";
2
+ import { performance } from "node:perf_hooks";
3
+ import { contentObservable, createContentTypesGenerator } from "../../content/index.js";
4
+ import { getTimeStat } from "../../core/build/util.js";
5
+ import { AstroError, AstroErrorData } from "../../core/errors/index.js";
6
+ import { info } from "../../core/logger/core.js";
7
+ async function sync(settings, { logging, fs }) {
8
+ const timerStart = performance.now();
9
+ try {
10
+ const contentTypesGenerator = await createContentTypesGenerator({
11
+ contentConfigObserver: contentObservable({ status: "loading" }),
12
+ logging,
13
+ fs,
14
+ settings
15
+ });
16
+ await contentTypesGenerator.init();
17
+ } catch (e) {
18
+ throw new AstroError(AstroErrorData.GenerateContentTypesError);
19
+ }
20
+ info(logging, "content", `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
21
+ return 0;
22
+ }
23
+ export {
24
+ sync
25
+ };
@@ -1,3 +1,5 @@
1
+ export { createContentTypesGenerator } from './types-generator.js';
2
+ export { contentObservable, getContentPaths } from './utils.js';
1
3
  export { astroBundleDelayedAssetPlugin, astroDelayedAssetPlugin, } from './vite-plugin-content-assets.js';
2
4
  export { astroContentServerPlugin } from './vite-plugin-content-server.js';
3
5
  export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
@@ -1,3 +1,5 @@
1
+ import { createContentTypesGenerator } from "./types-generator.js";
2
+ import { contentObservable, getContentPaths } from "./utils.js";
1
3
  import {
2
4
  astroBundleDelayedAssetPlugin,
3
5
  astroDelayedAssetPlugin
@@ -8,5 +10,8 @@ export {
8
10
  astroBundleDelayedAssetPlugin,
9
11
  astroContentServerPlugin,
10
12
  astroContentVirtualModPlugin,
11
- astroDelayedAssetPlugin
13
+ astroDelayedAssetPlugin,
14
+ contentObservable,
15
+ createContentTypesGenerator,
16
+ getContentPaths
12
17
  };
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import fsMod from 'node:fs';
2
+ import type fsMod from 'node:fs';
3
3
  import type { AstroSettings } from '../@types/astro.js';
4
4
  import { LogOptions } from '../core/logger/core.js';
5
5
  import { ContentObservable, ContentPaths } from './utils.js';
@@ -8,25 +8,16 @@ declare type RawContentEvent = {
8
8
  name: ChokidarEvent;
9
9
  entry: string;
10
10
  };
11
- declare type EntryInfo = {
12
- id: string;
13
- slug: string;
14
- collection: string;
15
- };
16
11
  export declare type GenerateContentTypes = {
17
12
  init(): Promise<void>;
18
13
  queueEvent(event: RawContentEvent): void;
19
14
  };
20
15
  declare type CreateContentGeneratorParams = {
21
- contentPaths: ContentPaths;
22
16
  contentConfigObserver: ContentObservable;
23
17
  logging: LogOptions;
24
18
  settings: AstroSettings;
25
19
  fs: typeof fsMod;
26
20
  };
27
- export declare function createContentTypesGenerator({ contentPaths, contentConfigObserver, fs, logging, settings, }: CreateContentGeneratorParams): Promise<GenerateContentTypes>;
28
- export declare function getEntryInfo({ entry, contentDir, }: Pick<ContentPaths, 'contentDir'> & {
29
- entry: URL;
30
- }): EntryInfo | Error;
21
+ export declare function createContentTypesGenerator({ contentConfigObserver, fs, logging, settings, }: CreateContentGeneratorParams): Promise<GenerateContentTypes>;
31
22
  export declare function getEntryType(entryPath: string, paths: ContentPaths): 'content' | 'config' | 'unknown' | 'generated-types';
32
23
  export {};
@@ -1,26 +1,30 @@
1
1
  import glob from "fast-glob";
2
2
  import { cyan } from "kleur/colors";
3
- import fsMod from "node:fs";
4
3
  import * as path from "node:path";
5
4
  import { fileURLToPath, pathToFileURL } from "node:url";
6
5
  import { normalizePath } from "vite";
7
6
  import { info, warn } from "../core/logger/core.js";
8
7
  import { appendForwardSlash, isRelativePath } from "../core/path.js";
9
8
  import { contentFileExts, CONTENT_TYPES_FILE } from "./consts.js";
10
- import { loadContentConfig } from "./utils.js";
9
+ import {
10
+ getContentPaths,
11
+ getEntryInfo,
12
+ loadContentConfig,
13
+ NoCollectionError
14
+ } from "./utils.js";
11
15
  class UnsupportedFileTypeError extends Error {
12
16
  }
13
17
  async function createContentTypesGenerator({
14
- contentPaths,
15
18
  contentConfigObserver,
16
19
  fs,
17
20
  logging,
18
21
  settings
19
22
  }) {
20
23
  const contentTypes = {};
24
+ const contentPaths = getContentPaths({ srcDir: settings.config.srcDir });
21
25
  let events = [];
22
26
  let debounceTimeout;
23
- const contentTypesBase = await fsMod.promises.readFile(
27
+ const contentTypesBase = await fs.promises.readFile(
24
28
  new URL(CONTENT_TYPES_FILE, contentPaths.generatedInputDir),
25
29
  "utf-8"
26
30
  );
@@ -77,23 +81,26 @@ async function createContentTypesGenerator({
77
81
  }
78
82
  return { shouldGenerateTypes: true };
79
83
  }
80
- const entryInfo = getEntryInfo({
81
- entry: event.entry,
82
- contentDir: contentPaths.contentDir
83
- });
84
- if (entryInfo instanceof Error)
85
- return { shouldGenerateTypes: false };
86
84
  if (fileType === "unknown") {
87
- if (entryInfo.id.startsWith("_") && (event.name === "add" || event.name === "change")) {
85
+ const entryInfo2 = getEntryInfo({
86
+ entry: event.entry,
87
+ contentDir: contentPaths.contentDir,
88
+ allowFilesOutsideCollection: true
89
+ });
90
+ if (entryInfo2.id.startsWith("_") && (event.name === "add" || event.name === "change")) {
88
91
  return { shouldGenerateTypes: false };
89
92
  } else {
90
93
  return {
91
94
  shouldGenerateTypes: false,
92
- error: new UnsupportedFileTypeError(entryInfo.id)
95
+ error: new UnsupportedFileTypeError(entryInfo2.id)
93
96
  };
94
97
  }
95
98
  }
96
- if (entryInfo.collection === ".") {
99
+ const entryInfo = getEntryInfo({
100
+ entry: event.entry,
101
+ contentDir: contentPaths.contentDir
102
+ });
103
+ if (entryInfo instanceof NoCollectionError) {
97
104
  if (["info", "warn"].includes(logLevel)) {
98
105
  warn(
99
106
  logging,
@@ -194,23 +201,6 @@ function addEntry(contentTypes, collectionKey, entryKey, slug) {
194
201
  function removeEntry(contentTypes, collectionKey, entryKey) {
195
202
  delete contentTypes[collectionKey][entryKey];
196
203
  }
197
- function getEntryInfo({
198
- entry,
199
- contentDir
200
- }) {
201
- const rawRelativePath = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
202
- const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
203
- if (!rawCollection)
204
- return new Error();
205
- const rawId = path.relative(rawCollection, rawRelativePath);
206
- const rawSlug = rawId.replace(path.extname(rawId), "");
207
- const res = {
208
- id: normalizePath(rawId),
209
- slug: normalizePath(rawSlug),
210
- collection: normalizePath(rawCollection)
211
- };
212
- return res;
213
- }
214
204
  function getEntryType(entryPath, paths) {
215
205
  const { dir: rawDir, ext, name, base } = path.parse(entryPath);
216
206
  const dir = appendForwardSlash(pathToFileURL(rawDir).href);
@@ -285,6 +275,5 @@ function warnNonexistentCollections({
285
275
  }
286
276
  export {
287
277
  createContentTypesGenerator,
288
- getEntryInfo,
289
278
  getEntryType
290
279
  };
@@ -120,11 +120,22 @@ declare type Entry = {
120
120
  filePath: string;
121
121
  };
122
122
  };
123
+ export declare type EntryInfo = {
124
+ id: string;
125
+ slug: string;
126
+ collection: string;
127
+ };
123
128
  export declare const msg: {
124
129
  collectionConfigMissing: (collection: string) => string;
125
130
  };
126
131
  export declare function getEntrySlug(entry: Entry, collectionConfig: CollectionConfig): Promise<string>;
127
132
  export declare function getEntryData(entry: Entry, collectionConfig: CollectionConfig): Promise<any>;
133
+ export declare class NoCollectionError extends Error {
134
+ }
135
+ export declare function getEntryInfo(params: Pick<ContentPaths, 'contentDir'> & {
136
+ entry: URL;
137
+ allowFilesOutsideCollection?: true;
138
+ }): EntryInfo;
128
139
  /**
129
140
  * Match YAML exception handling from Astro core errors
130
141
  * @see 'astro/src/core/errors.ts'
@@ -1,6 +1,8 @@
1
+ import { slug as githubSlug } from "github-slugger";
1
2
  import matter from "gray-matter";
3
+ import path from "node:path";
2
4
  import { fileURLToPath } from "node:url";
3
- import { createServer } from "vite";
5
+ import { createServer, normalizePath } from "vite";
4
6
  import { z } from "zod";
5
7
  import { AstroError, AstroErrorData } from "../core/errors/index.js";
6
8
  import { astroContentVirtualModPlugin } from "./vite-plugin-content-virtual-mod.js";
@@ -60,10 +62,33 @@ async function getEntryData(entry, collectionConfig) {
60
62
  }
61
63
  return data;
62
64
  }
63
- const flattenPath = (path) => path.join(".");
65
+ class NoCollectionError extends Error {
66
+ }
67
+ function getEntryInfo({
68
+ entry,
69
+ contentDir,
70
+ allowFilesOutsideCollection = false
71
+ }) {
72
+ const rawRelativePath = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
73
+ const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
74
+ const isOutsideCollection = rawCollection === ".." || rawCollection === ".";
75
+ if (!rawCollection || !allowFilesOutsideCollection && isOutsideCollection)
76
+ return new NoCollectionError();
77
+ const rawId = path.relative(rawCollection, rawRelativePath);
78
+ const rawIdWithoutFileExt = rawId.replace(new RegExp(path.extname(rawId) + "$"), "");
79
+ const rawSlugSegments = rawIdWithoutFileExt.split(path.sep);
80
+ const slug = rawSlugSegments.map((segment) => githubSlug(segment)).join("/").replace(/\/index$/, "");
81
+ const res = {
82
+ id: normalizePath(rawId),
83
+ slug,
84
+ collection: normalizePath(rawCollection)
85
+ };
86
+ return res;
87
+ }
88
+ const flattenErrorPath = (errorPath) => errorPath.join(".");
64
89
  const errorMap = (error, ctx) => {
65
90
  if (error.code === "invalid_type") {
66
- const badKeyPath = JSON.stringify(flattenPath(error.path));
91
+ const badKeyPath = JSON.stringify(flattenErrorPath(error.path));
67
92
  if (error.received === "undefined") {
68
93
  return { message: `${badKeyPath} is required.` };
69
94
  } else {
@@ -106,6 +131,7 @@ async function loadContentConfig({
106
131
  settings
107
132
  }) {
108
133
  const contentPaths = getContentPaths({ srcDir: settings.config.srcDir });
134
+ const nodeEnv = process.env.NODE_ENV;
109
135
  const tempConfigServer = await createServer({
110
136
  root: fileURLToPath(settings.config.root),
111
137
  server: { middlewareMode: true, hmr: false },
@@ -122,6 +148,7 @@ async function loadContentConfig({
122
148
  return new NotFoundError("Failed to resolve content config.");
123
149
  } finally {
124
150
  await tempConfigServer.close();
151
+ process.env.NODE_ENV = nodeEnv;
125
152
  }
126
153
  const config = contentConfigParser.safeParse(unparsedConfig);
127
154
  if (config.success) {
@@ -161,6 +188,7 @@ function getContentPaths({ srcDir }) {
161
188
  };
162
189
  }
163
190
  export {
191
+ NoCollectionError,
164
192
  NotFoundError,
165
193
  ZodParseError,
166
194
  collectionConfigParser,
@@ -168,6 +196,7 @@ export {
168
196
  contentObservable,
169
197
  getContentPaths,
170
198
  getEntryData,
199
+ getEntryInfo,
171
200
  getEntrySlug,
172
201
  loadContentConfig,
173
202
  msg,
@@ -9,7 +9,8 @@ import {
9
9
  LINKS_PLACEHOLDER,
10
10
  STYLES_PLACEHOLDER
11
11
  } from "./consts.js";
12
- function isDelayedAsset(url) {
12
+ function isDelayedAsset(viteId) {
13
+ const url = new URL(viteId, "file://");
13
14
  return url.searchParams.has(DELAYED_ASSET_FLAG) && contentFileExts.some((ext) => url.pathname.endsWith(ext));
14
15
  }
15
16
  function astroDelayedAssetPlugin({ mode }) {
@@ -23,10 +24,10 @@ function astroDelayedAssetPlugin({ mode }) {
23
24
  }
24
25
  },
25
26
  load(id) {
26
- const url = new URL(id, "file://");
27
- if (isDelayedAsset(url)) {
27
+ if (isDelayedAsset(id)) {
28
+ const basePath = id.split("?")[0];
28
29
  const code = `
29
- export { Content, getHeadings, _internal } from ${JSON.stringify(url.pathname)};
30
+ export { Content, getHeadings, _internal } from ${JSON.stringify(basePath)};
30
31
  export const collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)};
31
32
  export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)};
32
33
  `;
@@ -37,14 +38,13 @@ function astroDelayedAssetPlugin({ mode }) {
37
38
  var _a;
38
39
  if (!(options == null ? void 0 : options.ssr))
39
40
  return;
40
- const url = new URL(id, "file://");
41
- if (devModuleLoader && isDelayedAsset(url)) {
42
- const { pathname } = url;
43
- if (!((_a = devModuleLoader.getModuleById(pathname)) == null ? void 0 : _a.ssrModule)) {
44
- await devModuleLoader.import(pathname);
41
+ if (devModuleLoader && isDelayedAsset(id)) {
42
+ const basePath = id.split("?")[0];
43
+ if (!((_a = devModuleLoader.getModuleById(basePath)) == null ? void 0 : _a.ssrModule)) {
44
+ await devModuleLoader.import(basePath);
45
45
  }
46
46
  const { stylesMap, urls } = await getStylesForURL(
47
- pathToFileURL(pathname),
47
+ pathToFileURL(basePath),
48
48
  devModuleLoader,
49
49
  "development"
50
50
  );
@@ -2,18 +2,17 @@ import * as devalue from "devalue";
2
2
  import { cyan } from "kleur/colors";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { info } from "../core/logger/core.js";
5
- import { prependForwardSlash } from "../core/path.js";
6
- import { escapeViteEnvReferences } from "../vite-plugin-utils/index.js";
5
+ import { escapeViteEnvReferences, getFileInfo } from "../vite-plugin-utils/index.js";
7
6
  import { contentFileExts, CONTENT_FLAG } from "./consts.js";
8
7
  import {
9
8
  createContentTypesGenerator,
10
- getEntryInfo,
11
9
  getEntryType
12
10
  } from "./types-generator.js";
13
11
  import {
14
12
  contentObservable,
15
13
  getContentPaths,
16
14
  getEntryData,
15
+ getEntryInfo,
17
16
  getEntrySlug,
18
17
  parseFrontmatter
19
18
  } from "./utils.js";
@@ -43,8 +42,7 @@ function astroContentServerPlugin({
43
42
  fs,
44
43
  settings,
45
44
  logging,
46
- contentConfigObserver,
47
- contentPaths
45
+ contentConfigObserver
48
46
  });
49
47
  await contentGenerator.init();
50
48
  info(logging, "content", "Types generated");
@@ -96,8 +94,8 @@ function astroContentServerPlugin({
96
94
  {
97
95
  name: "astro-content-flag-plugin",
98
96
  async load(id) {
99
- const fileUrl = new URL(prependForwardSlash(id), "file://");
100
- if (isContentFlagImport(fileUrl)) {
97
+ const { fileId } = getFileInfo(id, settings.config);
98
+ if (isContentFlagImport(id)) {
101
99
  const observable = contentConfigObserver.get();
102
100
  let contentConfig = observable.status === "loaded" ? observable.config : void 0;
103
101
  if (observable.status === "loading") {
@@ -113,19 +111,19 @@ function astroContentServerPlugin({
113
111
  });
114
112
  });
115
113
  }
116
- const rawContents = await fs.promises.readFile(fileUrl, "utf-8");
114
+ const rawContents = await fs.promises.readFile(fileId, "utf-8");
117
115
  const {
118
116
  content: body,
119
117
  data: unparsedData,
120
118
  matter: rawData = ""
121
- } = parseFrontmatter(rawContents, fileUrl.pathname);
119
+ } = parseFrontmatter(rawContents, fileId);
122
120
  const entryInfo = getEntryInfo({
123
- entry: fileUrl,
121
+ entry: pathToFileURL(fileId),
124
122
  contentDir: contentPaths.contentDir
125
123
  });
126
124
  if (entryInfo instanceof Error)
127
125
  return;
128
- const _internal = { filePath: fileUrl.pathname, rawData };
126
+ const _internal = { filePath: fileId, rawData };
129
127
  const partialEntry = { data: unparsedData, body, _internal, ...entryInfo };
130
128
  const collectionConfig = contentConfig == null ? void 0 : contentConfig.collections[entryInfo.collection];
131
129
  const data = collectionConfig ? await getEntryData(partialEntry, collectionConfig) : unparsedData;
@@ -137,7 +135,7 @@ export const slug = ${JSON.stringify(slug)};
137
135
  export const body = ${JSON.stringify(body)};
138
136
  export const data = ${devalue.uneval(data)};
139
137
  export const _internal = {
140
- filePath: ${JSON.stringify(fileUrl.pathname)},
138
+ filePath: ${JSON.stringify(fileId)},
141
139
  rawData: ${JSON.stringify(rawData)},
142
140
  };
143
141
  `);
@@ -148,7 +146,7 @@ export const _internal = {
148
146
  viteServer.watcher.on("all", async (event, entry) => {
149
147
  if (["add", "unlink", "change"].includes(event) && getEntryType(entry, contentPaths) === "config") {
150
148
  for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
151
- if (isContentFlagImport(new URL(modUrl, "file://"))) {
149
+ if (isContentFlagImport(modUrl)) {
152
150
  const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
153
151
  if (mod) {
154
152
  viteServer.moduleGraph.invalidateModule(mod);
@@ -159,14 +157,15 @@ export const _internal = {
159
157
  });
160
158
  },
161
159
  async transform(code, id) {
162
- if (isContentFlagImport(new URL(id, "file://"))) {
160
+ if (isContentFlagImport(id)) {
163
161
  return { code: escapeViteEnvReferences(code) };
164
162
  }
165
163
  }
166
164
  }
167
165
  ];
168
166
  }
169
- function isContentFlagImport({ searchParams, pathname }) {
167
+ function isContentFlagImport(viteId) {
168
+ const { pathname, searchParams } = new URL(viteId, "file://");
170
169
  return searchParams.has(CONTENT_FLAG) && contentFileExts.some((ext) => pathname.endsWith(ext));
171
170
  }
172
171
  export {
@@ -3,6 +3,7 @@ import * as colors from "kleur/colors";
3
3
  import { bgGreen, black, cyan, dim, green, magenta } from "kleur/colors";
4
4
  import npath from "path";
5
5
  import { fileURLToPath } from "url";
6
+ import { getContentPaths } from "../../content/index.js";
6
7
  import { hasPrerenderedPages } from "../../core/build/internal.js";
7
8
  import {
8
9
  prependForwardSlash,
@@ -237,7 +238,9 @@ async function generatePath(pathname, opts, gopts) {
237
238
  logging,
238
239
  markdown: {
239
240
  ...settings.config.markdown,
240
- isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown
241
+ isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
242
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
243
+ contentDir: getContentPaths(settings.config).contentDir
241
244
  },
242
245
  mode: opts.mode,
243
246
  renderers,
@@ -21,8 +21,8 @@ function vitePluginPages(opts, internals) {
21
21
  let i = 0;
22
22
  for (const pageData of eachPageData(internals)) {
23
23
  const variable = `_page${i}`;
24
- imports.push(`import * as ${variable} from '${pageData.moduleSpecifier}';`);
25
- importMap += `['${pageData.component}', ${variable}],`;
24
+ imports.push(`import * as ${variable} from ${JSON.stringify(pageData.moduleSpecifier)};`);
25
+ importMap += `[${JSON.stringify(pageData.component)}, ${variable}],`;
26
26
  i++;
27
27
  }
28
28
  i = 0;
@@ -1,6 +1,7 @@
1
1
  import glob from "fast-glob";
2
2
  import * as fs from "fs";
3
3
  import { fileURLToPath } from "url";
4
+ import { getContentPaths } from "../../content/index.js";
4
5
  import { runHookBuildSsr } from "../../integrations/index.js";
5
6
  import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from "../../vite-plugin-scripts/index.js";
6
7
  import { pagesVirtualModuleId } from "../app/index.js";
@@ -35,9 +36,7 @@ const _manifest = Object.assign(_deserializeManifest('${manifestReplace}'), {
35
36
  renderers: _main.renderers
36
37
  });
37
38
  const _args = ${adapter.args ? JSON.stringify(adapter.args) : "undefined"};
38
-
39
39
  export * from '${pagesVirtualModuleId}';
40
-
41
40
  ${adapter.exports ? `const _exports = adapter.createExports(_manifest, _args);
42
41
  ${adapter.exports.map((name) => {
43
42
  if (name === "default") {
@@ -134,9 +133,11 @@ function buildManifest(opts, internals, staticFiles) {
134
133
  for (const pageData of eachServerPageData(internals)) {
135
134
  const scripts = [];
136
135
  if (pageData.hoistedScript) {
136
+ const hoistedValue = pageData.hoistedScript.value;
137
+ const value = hoistedValue.endsWith(".js") ? joinBase(hoistedValue) : hoistedValue;
137
138
  scripts.unshift(
138
139
  Object.assign({}, pageData.hoistedScript, {
139
- value: joinBase(pageData.hoistedScript.value)
140
+ value
140
141
  })
141
142
  );
142
143
  }
@@ -168,7 +169,9 @@ function buildManifest(opts, internals, staticFiles) {
168
169
  base: settings.config.base,
169
170
  markdown: {
170
171
  ...settings.config.markdown,
171
- isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown
172
+ isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
173
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
174
+ contentDir: getContentPaths(settings.config).contentDir
172
175
  },
173
176
  pageMap: null,
174
177
  renderers: [],
@@ -13,7 +13,13 @@ async function createViteLoader(root, fs) {
13
13
  clearScreen: false,
14
14
  appType: "custom",
15
15
  ssr: {
16
- external: ["@astrojs/tailwind", "@astrojs/mdx", "@astrojs/react"]
16
+ external: [
17
+ "@astrojs/tailwind",
18
+ "@astrojs/mdx",
19
+ "@astrojs/react",
20
+ "@astrojs/preact",
21
+ "@astrojs/sitemap"
22
+ ]
17
23
  },
18
24
  plugins: [loadFallbackPlugin({ fs, root: pathToFileURL(root) })]
19
25
  });
@@ -1,4 +1,4 @@
1
- const ASTRO_VERSION = "1.7.2";
1
+ const ASTRO_VERSION = "1.9.0";
2
2
  const SUPPORTED_MARKDOWN_FILE_EXTENSIONS = [
3
3
  ".markdown",
4
4
  ".mdown",
@@ -93,7 +93,7 @@ async function createVite(commandConfig, { settings, logging, mode, fs = nodeFs
93
93
  root: fileURLToPath(settings.config.root),
94
94
  envPrefix: "PUBLIC_",
95
95
  define: {
96
- "import.meta.env.SITE": settings.config.site ? `'${settings.config.site}'` : "undefined"
96
+ "import.meta.env.SITE": settings.config.site ? JSON.stringify(settings.config.site) : "undefined"
97
97
  },
98
98
  server: {
99
99
  hmr: process.env.NODE_ENV === "test" || process.env.NODE_ENV === "production" ? false : void 0,
@@ -40,7 +40,7 @@ async function createContainer(params = {}) {
40
40
  include: rendererClientEntries
41
41
  },
42
42
  define: {
43
- "import.meta.env.BASE_URL": settings.config.base ? `'${settings.config.base}'` : "undefined"
43
+ "import.meta.env.BASE_URL": settings.config.base ? JSON.stringify(settings.config.base) : "undefined"
44
44
  }
45
45
  },
46
46
  { settings, logging, mode: "dev", fs }
@@ -30,7 +30,7 @@ async function dev(settings, options) {
30
30
  isRestart: options.isRestart
31
31
  })
32
32
  );
33
- const currentVersion = "1.7.2";
33
+ const currentVersion = "1.9.0";
34
34
  if (currentVersion.includes("-")) {
35
35
  warn(options.logging, null, msg.prerelease({ currentVersion }));
36
36
  }
@@ -479,6 +479,28 @@ export declare const AstroErrorData: {
479
479
  readonly message: (legacyConfigKey: string) => string;
480
480
  readonly hint: "Please update your configuration to the new format.\nSee https://astro.build/config for more information.";
481
481
  };
482
+ /**
483
+ * @docs
484
+ * @kind heading
485
+ * @name CLI Errors
486
+ */
487
+ readonly UnknownCLIError: {
488
+ readonly title: "Unknown CLI Error.";
489
+ readonly code: 8000;
490
+ };
491
+ /**
492
+ * @docs
493
+ * @description
494
+ * `astro sync` command failed to generate content collection types.
495
+ * @see
496
+ * - [Content collections documentation](https://docs.astro.build/en/guides/content-collections/)
497
+ */
498
+ readonly GenerateContentTypesError: {
499
+ readonly title: "Failed to generate content types.";
500
+ readonly code: 8001;
501
+ readonly message: "`astro sync` command failed to generate content collection types.";
502
+ readonly hint: "Check your `src/content/config.*` file for typos.";
503
+ };
482
504
  readonly UnknownError: {
483
505
  readonly title: "Unknown Error.";
484
506
  readonly code: 99999;
@@ -190,6 +190,16 @@ Expected \`true\` value but got \`${suffix}\`.`;
190
190
  message: (legacyConfigKey) => `Legacy configuration detected: \`${legacyConfigKey}\`.`,
191
191
  hint: "Please update your configuration to the new format.\nSee https://astro.build/config for more information."
192
192
  },
193
+ UnknownCLIError: {
194
+ title: "Unknown CLI Error.",
195
+ code: 8e3
196
+ },
197
+ GenerateContentTypesError: {
198
+ title: "Failed to generate content types.",
199
+ code: 8001,
200
+ message: "`astro sync` command failed to generate content collection types.",
201
+ hint: "Check your `src/content/config.*` file for typos."
202
+ },
193
203
  UnknownError: {
194
204
  title: "Unknown Error.",
195
205
  code: 99999
@@ -151,6 +151,14 @@ export declare function getErrorDataByCode(code: AstroErrorCodes | DiagnosticCod
151
151
  readonly code: 7002;
152
152
  readonly message: (legacyConfigKey: string) => string;
153
153
  readonly hint: "Please update your configuration to the new format.\nSee https://astro.build/config for more information.";
154
+ } | {
155
+ readonly title: "Unknown CLI Error.";
156
+ readonly code: 8000;
157
+ } | {
158
+ readonly title: "Failed to generate content types.";
159
+ readonly code: 8001;
160
+ readonly message: "`astro sync` command failed to generate content collection types.";
161
+ readonly hint: "Check your `src/content/config.*` file for typos.";
154
162
  } | {
155
163
  readonly title: "Unknown Error.";
156
164
  readonly code: 99999;
@@ -49,7 +49,7 @@ function serverStart({
49
49
  site,
50
50
  isRestart = false
51
51
  }) {
52
- const version = "1.7.2";
52
+ const version = "1.9.0";
53
53
  const rootPath = site ? site.pathname : "/";
54
54
  const localPrefix = `${dim("\u2503")} Local `;
55
55
  const networkPrefix = `${dim("\u2503")} Network `;
@@ -272,7 +272,7 @@ function printHelp({
272
272
  message.push(
273
273
  linebreak(),
274
274
  ` ${bgGreen(black(` ${commandName} `))} ${green(
275
- `v${"1.7.2"}`
275
+ `v${"1.9.0"}`
276
276
  )} ${headline}`
277
277
  );
278
278
  }
@@ -1,3 +1,4 @@
1
+ import { getContentPaths } from "../../../content/index.js";
1
2
  import { createEnvironment } from "../index.js";
2
3
  import { RouteCache } from "../route-cache.js";
3
4
  import { createResolve } from "./resolve.js";
@@ -9,7 +10,9 @@ function createDevelopmentEnvironment(settings, logging, loader) {
9
10
  logging,
10
11
  markdown: {
11
12
  ...settings.config.markdown,
12
- isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown
13
+ isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
14
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
15
+ contentDir: getContentPaths(settings.config).contentDir
13
16
  },
14
17
  mode,
15
18
  renderers: [],
@@ -6,7 +6,10 @@ function createBasicEnvironment(options) {
6
6
  const mode = options.mode ?? "development";
7
7
  return createEnvironment({
8
8
  ...options,
9
- markdown: options.markdown ?? {},
9
+ markdown: {
10
+ ...options.markdown ?? {},
11
+ contentDir: new URL("file:///src/content/")
12
+ },
10
13
  mode,
11
14
  renderers: options.renderers ?? [],
12
15
  resolve: options.resolve ?? ((s) => Promise.resolve(s)),
@@ -1,5 +1,6 @@
1
- import { parse as babelParser } from "@babel/parser";
2
- import { parse, print, types, visit } from "recast";
1
+ import { parse } from "acorn";
2
+ import { walk } from "estree-walker";
3
+ import MagicString from "magic-string";
3
4
  import { isMarkdownFile } from "../core/util.js";
4
5
  const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/;
5
6
  function astro(_opts) {
@@ -12,43 +13,36 @@ function astro(_opts) {
12
13
  if (!ASTRO_GLOB_REGEX.test(code)) {
13
14
  return null;
14
15
  }
16
+ let s;
15
17
  const ast = parse(code, {
16
- parser: { parse: babelParser }
18
+ ecmaVersion: "latest",
19
+ sourceType: "module"
17
20
  });
18
- visit(ast, {
19
- visitCallExpression: function(path) {
20
- if (!types.namedTypes.MemberExpression.check(path.node.callee) || !types.namedTypes.Identifier.check(path.node.callee.property) || !(path.node.callee.property.name === "glob") || !types.namedTypes.Identifier.check(path.node.callee.object) || !(path.node.callee.object.name === "Astro" || path.node.callee.object.name === "Astro2")) {
21
- this.traverse(path);
22
- return;
23
- }
24
- const argsPath = path.get("arguments", 0);
25
- const args = argsPath.value;
26
- argsPath.replace(
27
- {
28
- type: "CallExpression",
29
- callee: {
30
- type: "MemberExpression",
31
- object: {
32
- type: "MetaProperty",
33
- meta: { type: "Identifier", name: "import" },
34
- property: { type: "Identifier", name: "meta" }
35
- },
36
- property: { type: "Identifier", name: "glob" },
37
- computed: false
38
- },
39
- arguments: [args]
40
- },
41
- {
42
- type: "ArrowFunctionExpression",
43
- body: args,
44
- params: []
21
+ walk(ast, {
22
+ enter(node) {
23
+ if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.property.name === "glob" && (node.callee.object.name === "Astro" || node.callee.object.name === "Astro2") && node.arguments.length) {
24
+ const firstArgStart = node.arguments[0].start;
25
+ const firstArgEnd = node.arguments[0].end;
26
+ const lastArgEnd = node.arguments[node.arguments.length - 1].end;
27
+ let firstArg = code.slice(firstArgStart, firstArgEnd);
28
+ if (firstArg.startsWith("`") && firstArg.endsWith("`") && !firstArg.includes("${")) {
29
+ firstArg = JSON.stringify(firstArg.slice(1, -1));
45
30
  }
46
- );
47
- return false;
31
+ s ?? (s = new MagicString(code));
32
+ s.overwrite(
33
+ firstArgStart,
34
+ lastArgEnd,
35
+ `import.meta.glob(${firstArg}), () => ${firstArg}`
36
+ );
37
+ }
48
38
  }
49
39
  });
50
- const result = print(ast);
51
- return { code: result.code, map: result.map };
40
+ if (s) {
41
+ return {
42
+ code: s.toString(),
43
+ map: s.generateMap()
44
+ };
45
+ }
52
46
  }
53
47
  };
54
48
  }
@@ -21,9 +21,9 @@ function getPrivateEnv(viteConfig, astroConfig) {
21
21
  }
22
22
  }
23
23
  }
24
- privateEnv.SITE = astroConfig.site ? `'${astroConfig.site}'` : "undefined";
24
+ privateEnv.SITE = astroConfig.site ? JSON.stringify(astroConfig.site) : "undefined";
25
25
  privateEnv.SSR = JSON.stringify(true);
26
- privateEnv.BASE_URL = astroConfig.base ? `'${astroConfig.base}'` : "undefined";
26
+ privateEnv.BASE_URL = astroConfig.base ? JSON.stringify(astroConfig.base) : "undefined";
27
27
  return privateEnv;
28
28
  }
29
29
  function getReferencedPrivateKeys(source, privateEnv) {
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import matter from "gray-matter";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { normalizePath } from "vite";
6
+ import { getContentPaths } from "../content/index.js";
6
7
  import { AstroErrorData, MarkdownError } from "../core/errors/index.js";
7
8
  import { warn } from "../core/logger/core.js";
8
9
  import { isMarkdownFile } from "../core/util.js";
@@ -50,7 +51,9 @@ function markdown({ settings, logging }) {
50
51
  const renderResult = await renderMarkdown(raw.content, {
51
52
  ...settings.config.markdown,
52
53
  fileURL: new URL(`file://${fileId}`),
53
- isAstroFlavoredMd: false
54
+ isAstroFlavoredMd: false,
55
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
56
+ contentDir: getContentPaths(settings.config).contentDir
54
57
  });
55
58
  const html = renderResult.code;
56
59
  const { headings } = renderResult.metadata;
@@ -68,7 +71,7 @@ function markdown({ settings, logging }) {
68
71
  );
69
72
  }
70
73
  const code = escapeViteEnvReferences(`
71
- import { Fragment, jsx as h } from '${astroJsxRuntimeModulePath}';
74
+ import { Fragment, jsx as h } from ${JSON.stringify(astroJsxRuntimeModulePath)};
72
75
  ${layout ? `import Layout from ${JSON.stringify(layout)};` : ""}
73
76
 
74
77
  const html = ${JSON.stringify(html)};
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import matter from "gray-matter";
4
4
  import { fileURLToPath } from "url";
5
5
  import { transformWithEsbuild } from "vite";
6
+ import { getContentPaths } from "../content/index.js";
6
7
  import { pagesVirtualModuleId } from "../core/app/index.js";
7
8
  import { cachedCompilation } from "../core/compile/index.js";
8
9
  import { AstroErrorData, MarkdownError } from "../core/errors/index.js";
@@ -115,7 +116,9 @@ function markdown({ settings }) {
115
116
  let renderResult = await renderMarkdown(markdownContent, {
116
117
  ...renderOpts,
117
118
  fileURL: fileUrl,
118
- isAstroFlavoredMd: true
119
+ isAstroFlavoredMd: true,
120
+ isExperimentalContentCollections: settings.config.experimental.contentCollections,
121
+ contentDir: getContentPaths(settings.config).contentDir
119
122
  });
120
123
  let { code: astroResult, metadata } = renderResult;
121
124
  const { layout = "", components = "", setup = "", ...content } = frontmatter;
@@ -124,8 +127,8 @@ function markdown({ settings }) {
124
127
  content.file = filename;
125
128
  const prelude = `---
126
129
  import Slugger from 'github-slugger';
127
- ${layout ? `import Layout from '${layout}';` : ""}
128
- ${components ? `import * from '${components}';` : ""}
130
+ ${layout ? `import Layout from ${JSON.stringify(layout)};` : ""}
131
+ ${components ? `import * from ${JSON.stringify(components)};` : ""}
129
132
  ${setup}
130
133
 
131
134
  const slugger = new Slugger();
@@ -142,7 +145,7 @@ Object.defineProperty($$content.astro, 'headers', {
142
145
  }
143
146
  });
144
147
  ---`;
145
- const imports = `${layout ? `import Layout from '${layout}';` : ""}
148
+ const imports = `${layout ? `import Layout from ${JSON.stringify(layout)};` : ""}
146
149
  ${setup}`.trim();
147
150
  if (/\bLayout\b/.test(imports)) {
148
151
  astroResult = `${prelude}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro",
3
- "version": "1.7.2",
3
+ "version": "1.9.0",
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",
@@ -97,7 +97,7 @@
97
97
  "dependencies": {
98
98
  "@astrojs/compiler": "^0.31.0",
99
99
  "@astrojs/language-server": "^0.28.3",
100
- "@astrojs/markdown-remark": "^1.1.3",
100
+ "@astrojs/markdown-remark": "^1.2.0",
101
101
  "@astrojs/telemetry": "^1.0.1",
102
102
  "@astrojs/webapi": "^1.1.1",
103
103
  "@babel/core": "^7.18.2",
@@ -111,6 +111,7 @@
111
111
  "@types/babel__core": "^7.1.19",
112
112
  "@types/html-escaper": "^3.0.0",
113
113
  "@types/yargs-parser": "^21.0.0",
114
+ "acorn": "^8.8.1",
114
115
  "boxen": "^6.2.1",
115
116
  "ci-info": "^3.3.1",
116
117
  "common-ancestor-path": "^1.0.1",
@@ -120,9 +121,10 @@
120
121
  "devalue": "^4.2.0",
121
122
  "diff": "^5.1.0",
122
123
  "es-module-lexer": "^1.1.0",
124
+ "estree-walker": "^3.0.1",
123
125
  "execa": "^6.1.0",
124
126
  "fast-glob": "^3.2.11",
125
- "github-slugger": "^1.4.0",
127
+ "github-slugger": "^2.0.0",
126
128
  "gray-matter": "^4.0.3",
127
129
  "html-entities": "^2.3.3",
128
130
  "html-escaper": "^3.0.3",
@@ -179,7 +181,6 @@
179
181
  "@types/rimraf": "^3.0.2",
180
182
  "@types/send": "^0.17.1",
181
183
  "@types/unist": "^2.0.6",
182
- "ast-types": "^0.14.2",
183
184
  "astro-scripts": "0.0.9",
184
185
  "chai": "^4.3.6",
185
186
  "cheerio": "^1.0.0-rc.11",
@@ -208,6 +209,7 @@
208
209
  "postbuild": "astro-scripts copy \"src/**/*.astro\"",
209
210
  "benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
210
211
  "test:unit": "mocha --exit --timeout 30000 ./test/units/**/*.test.js",
212
+ "test:unit:match": "mocha --exit --timeout 30000 ./test/units/**/*.test.js -g",
211
213
  "test": "pnpm run test:unit && mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
212
214
  "test:match": "mocha --timeout 20000 -g",
213
215
  "test:e2e": "playwright test",