@astrojs/cloudflare 14.0.0-alpha.1 → 14.0.0-beta.3

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.
@@ -88,7 +88,7 @@ function serverStart({
88
88
  host,
89
89
  base
90
90
  }) {
91
- const version = "14.0.0-alpha.1";
91
+ const version = "14.0.0-beta.3";
92
92
  const localPrefix = `${colors.dim("\u2503")} Local `;
93
93
  const networkPrefix = `${colors.dim("\u2503")} Network `;
94
94
  const emptyPrefix = " ".repeat(11);
package/dist/index.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import { createReadStream, existsSync, readFileSync } from "node:fs";
2
- import { appendFile, readFile, rename, stat, writeFile } from "node:fs/promises";
2
+ import { appendFile, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
3
3
  import { relative } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { normalizePath } from "vite";
6
6
  import { createInterface } from "node:readline/promises";
7
- import { removeLeadingForwardSlash } from "@astrojs/internal-helpers/path";
7
+ import {
8
+ removeLeadingForwardSlash,
9
+ removeTrailingForwardSlash
10
+ } from "@astrojs/internal-helpers/path";
8
11
  import { createRedirectsFromAstroRoutes, printAsRedirects } from "@astrojs/underscore-redirects";
9
12
  import { cloudflare as cfVitePlugin } from "@cloudflare/vite-plugin";
10
13
  import { astroFrontmatterScanPlugin } from "./esbuild-plugin-astro-frontmatter.js";
11
14
  import { getParts } from "./utils/generate-routes-json.js";
15
+ import { buildAssetsHeadersContent } from "./utils/headers.js";
12
16
  import {
13
17
  normalizeImageServiceConfig,
14
18
  setImageConfig
@@ -101,6 +105,7 @@ function createIntegration({
101
105
  const needsImagesBindingForDev = isCompile && command === "dev";
102
106
  const usesContentCollections = hasContentCollectionsConfig(config.srcDir);
103
107
  const prebundleContentRuntime = command === "dev" && usesContentCollections;
108
+ const isTypeGenPhase = command === "build" || command === "sync";
104
109
  const adapterPluginConfig = {
105
110
  config: cloudflareConfigCustomizer({
106
111
  needsSessionKVBinding,
@@ -138,6 +143,16 @@ function createIntegration({
138
143
  ];
139
144
  const isAstroPrismPackageInstalled = await getIsAstroPrismInstalled(config.root);
140
145
  const userOptimizeDeps = config.vite?.optimizeDeps;
146
+ const cloudflareVitePlugins = cfVitePlugin({
147
+ ...cfPluginConfig,
148
+ viteEnvironment: { name: "ssr" },
149
+ assetsOnly: () => _buildOutput === "static"
150
+ });
151
+ if (isTypeGenPhase) {
152
+ for (const plugin of cloudflareVitePlugins) {
153
+ plugin.configureServer = void 0;
154
+ }
155
+ }
141
156
  updateConfig({
142
157
  build: {
143
158
  redirects: false
@@ -146,11 +161,7 @@ function createIntegration({
146
161
  vite: {
147
162
  plugins: [
148
163
  ...prerenderEnvironment === "node" && command === "dev" ? [createNodePrerenderPlugin()] : [],
149
- cfVitePlugin({
150
- ...cfPluginConfig,
151
- viteEnvironment: { name: "ssr" },
152
- assetsOnly: () => _buildOutput === "static"
153
- }),
164
+ cloudflareVitePlugins,
154
165
  {
155
166
  name: "@astrojs/cloudflare:cf-imports",
156
167
  enforce: "pre",
@@ -166,6 +177,9 @@ function createIntegration({
166
177
  {
167
178
  name: "@astrojs/cloudflare:environment",
168
179
  configEnvironment(environmentName, _options) {
180
+ if (isTypeGenPhase) {
181
+ return { optimizeDeps: { noDiscovery: true, include: [] } };
182
+ }
169
183
  const isServerEnvironment = ["astro", "ssr", "prerender"].includes(
170
184
  environmentName
171
185
  );
@@ -356,7 +370,7 @@ function createIntegration({
356
370
  },
357
371
  "astro:build:done": async ({ dir, logger, assets }) => {
358
372
  if (_config.base !== "/") {
359
- for (const file of [".assetsignore", "_headers"]) {
373
+ for (const file of [".assetsignore", "_headers", "_redirects"]) {
360
374
  try {
361
375
  await rename(
362
376
  new URL(`./${file}`, _config.build.client),
@@ -378,6 +392,39 @@ function createIntegration({
378
392
  } catch {
379
393
  }
380
394
  }
395
+ if (_config.build.assetsPrefix) {
396
+ logger.debug(
397
+ "Skipping Cache-Control injection for assets \u2014 `build.assetsPrefix` is set, so assets are served from a different origin."
398
+ );
399
+ } else {
400
+ const headersPath = new URL("./_headers", _originalClientDir);
401
+ const result = await buildAssetsHeadersContent(
402
+ {
403
+ assetsDir: _config.build.assets,
404
+ basePrefix: removeTrailingForwardSlash(_config.base),
405
+ headersPath
406
+ },
407
+ (path) => readFile(path, "utf-8")
408
+ );
409
+ if (result === null) {
410
+ logger.debug(
411
+ `Skipping Cache-Control injection \u2014 _headers already sets Cache-Control on a matching rule.`
412
+ );
413
+ } else {
414
+ const tempPath = new URL("./_headers.tmp", _originalClientDir);
415
+ try {
416
+ await writeFile(tempPath, result.content);
417
+ await rename(tempPath, headersPath);
418
+ } catch (err) {
419
+ await unlink(tempPath).catch(() => {
420
+ });
421
+ throw err;
422
+ }
423
+ logger.info(
424
+ `Injected immutable Cache-Control for ${result.assetsPattern} into _headers.`
425
+ );
426
+ }
427
+ }
381
428
  let redirectsExists = false;
382
429
  try {
383
430
  const redirectsStat = await stat(new URL("./_redirects", _originalClientDir));
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Returns true if the given `_headers` content already declares (or detaches)
3
+ * a `Cache-Control` directive on any rule whose URL pattern matches `path`.
4
+ *
5
+ * Used to avoid emitting a second `Cache-Control` rule for hashed assets when
6
+ * the user already has one — Cloudflare merges duplicate header values across
7
+ * matching rules with a comma, which produces contradictory cache directives.
8
+ */
9
+ export declare function headersFileHasCacheControlForPath(content: string, path: string): boolean;
10
+ /**
11
+ * Computes the content to write to `_headers` to inject an immutable
12
+ * Cache-Control rule for the hashed assets directory.
13
+ *
14
+ * Returns `null` when injection should be skipped because the existing
15
+ * `_headers` already declares `Cache-Control` on a rule matching the assets
16
+ * path — Cloudflare merges duplicate header values with a comma, which would
17
+ * produce contradictory directives.
18
+ */
19
+ export declare function buildAssetsHeadersContent(opts: {
20
+ assetsDir: string;
21
+ basePrefix: string;
22
+ headersPath: URL;
23
+ }, readFile: (path: URL) => Promise<string>): Promise<{
24
+ content: string;
25
+ assetsPattern: string;
26
+ } | null>;
@@ -0,0 +1,63 @@
1
+ function cfHeadersPatternToRegex(pattern) {
2
+ let regexStr = "";
3
+ let i = 0;
4
+ while (i < pattern.length) {
5
+ const ch = pattern[i];
6
+ if (ch === "*") {
7
+ regexStr += ".*";
8
+ i++;
9
+ } else if (ch === ":" && /[A-Za-z]/.test(pattern[i + 1] ?? "")) {
10
+ i++;
11
+ while (i < pattern.length && /\w/.test(pattern[i])) i++;
12
+ regexStr += "[^/]+";
13
+ } else {
14
+ regexStr += ch.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
15
+ i++;
16
+ }
17
+ }
18
+ return new RegExp(`^${regexStr}$`);
19
+ }
20
+ function headersFileHasCacheControlForPath(content, path) {
21
+ let matchesCurrentSection = false;
22
+ for (const rawLine of content.split("\n")) {
23
+ const trimmed = rawLine.trim();
24
+ if (!trimmed || trimmed.startsWith("#")) continue;
25
+ const isSectionHeader = !/^\s/.test(rawLine);
26
+ if (isSectionHeader) {
27
+ const pathOnly = trimmed.replace(/^https?:\/\/[^/]+/, "");
28
+ try {
29
+ matchesCurrentSection = cfHeadersPatternToRegex(pathOnly).test(path);
30
+ } catch {
31
+ matchesCurrentSection = false;
32
+ }
33
+ } else if (matchesCurrentSection && // Either `Cache-Control: value` (set) or `! Cache-Control` (detach).
34
+ /^\s+(?:!\s+cache-control\s*$|cache-control\s*:)/i.test(rawLine)) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ async function buildAssetsHeadersContent(opts, readFile) {
41
+ const { assetsDir, basePrefix, headersPath } = opts;
42
+ const assetsPattern = `${basePrefix}/${assetsDir}/*`;
43
+ const probePath = `${basePrefix}/${assetsDir}/probe`;
44
+ let existingHeaders = "";
45
+ try {
46
+ existingHeaders = await readFile(headersPath);
47
+ } catch {
48
+ }
49
+ if (headersFileHasCacheControlForPath(existingHeaders, probePath)) {
50
+ return null;
51
+ }
52
+ const cacheBlock = `${assetsPattern}
53
+ Cache-Control: public, max-age=31536000, immutable
54
+ `;
55
+ const normalizedExisting = existingHeaders && !existingHeaders.endsWith("\n") ? existingHeaders + "\n" : existingHeaders;
56
+ const content = normalizedExisting ? `${cacheBlock}
57
+ ${normalizedExisting}` : cacheBlock;
58
+ return { content, assetsPattern };
59
+ }
60
+ export {
61
+ buildAssetsHeadersContent,
62
+ headersFileHasCacheControlForPath
63
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/cloudflare",
3
3
  "description": "Deploy your site to Cloudflare Workers",
4
- "version": "14.0.0-alpha.1",
4
+ "version": "14.0.0-beta.3",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -55,7 +55,7 @@
55
55
  "cheerio": "1.2.0",
56
56
  "devalue": "^5.8.1",
57
57
  "prismjs": "^1.30.0",
58
- "astro": "7.0.0-alpha.2",
58
+ "astro": "7.0.0-beta.6",
59
59
  "astro-scripts": "0.0.14"
60
60
  },
61
61
  "publishConfig": {