@astrojs/cloudflare 13.6.0 → 13.7.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.
package/README.md CHANGED
@@ -34,5 +34,5 @@ Copyright (c) 2023–present [Astro][astro]
34
34
  [coc]: https://github.com/withastro/.github/blob/main/CODE_OF_CONDUCT.md
35
35
  [community]: https://github.com/withastro/.github/blob/main/COMMUNITY_GUIDE.md
36
36
  [discord]: https://astro.build/chat/
37
- [issues]: https://github.com/withastro/adapter/issues
37
+ [issues]: https://github.com/withastro/astro/issues
38
38
  [astro-integration]: https://docs.astro.build/en/guides/integrations/
@@ -88,7 +88,7 @@ function serverStart({
88
88
  host,
89
89
  base
90
90
  }) {
91
- const version = "13.6.0";
91
+ const version = "13.7.0";
92
92
  const localPrefix = `${colors.dim("\u2503")} Local `;
93
93
  const networkPrefix = `${colors.dim("\u2503")} Network `;
94
94
  const emptyPrefix = " ".repeat(11);
package/dist/fetch.js CHANGED
@@ -10,9 +10,15 @@ import {
10
10
  createLocals,
11
11
  getClientAddress
12
12
  } from "./utils/cf.js";
13
- setGetEnv(createGetEnv(globalEnv));
14
- const app = createApp();
13
+ let app;
14
+ function ensureInitialized() {
15
+ if (!app) {
16
+ setGetEnv(createGetEnv(globalEnv));
17
+ app = createApp();
18
+ }
19
+ }
15
20
  async function cf(state, env, ctx) {
21
+ ensureInitialized();
16
22
  injectSessionBinding(app.manifest, env);
17
23
  const staticAsset = matchStaticAsset(app.manifest, state.request.url, env);
18
24
  if (staticAsset) return staticAsset;
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
@@ -191,6 +195,8 @@ function createIntegration({
191
195
  "astro > picomatch",
192
196
  "astro/app",
193
197
  "astro/app/fetch/default-handler",
198
+ "astro/fetch",
199
+ "astro/hono",
194
200
  "astro/assets",
195
201
  "astro/assets/runtime",
196
202
  "astro/assets/utils/inferRemoteSize.js",
@@ -376,6 +382,39 @@ function createIntegration({
376
382
  } catch {
377
383
  }
378
384
  }
385
+ if (_config.build.assetsPrefix) {
386
+ logger.debug(
387
+ "Skipping Cache-Control injection for assets \u2014 `build.assetsPrefix` is set, so assets are served from a different origin."
388
+ );
389
+ } else {
390
+ const headersPath = new URL("./_headers", _originalClientDir);
391
+ const result = await buildAssetsHeadersContent(
392
+ {
393
+ assetsDir: _config.build.assets,
394
+ basePrefix: removeTrailingForwardSlash(_config.base),
395
+ headersPath
396
+ },
397
+ (path) => readFile(path, "utf-8")
398
+ );
399
+ if (result === null) {
400
+ logger.debug(
401
+ `Skipping Cache-Control injection \u2014 _headers already sets Cache-Control on a matching rule.`
402
+ );
403
+ } else {
404
+ const tempPath = new URL("./_headers.tmp", _originalClientDir);
405
+ try {
406
+ await writeFile(tempPath, result.content);
407
+ await rename(tempPath, headersPath);
408
+ } catch (err) {
409
+ await unlink(tempPath).catch(() => {
410
+ });
411
+ throw err;
412
+ }
413
+ logger.info(
414
+ `Injected immutable Cache-Control for ${result.assetsPattern} into _headers.`
415
+ );
416
+ }
417
+ }
379
418
  let redirectsExists = false;
380
419
  try {
381
420
  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": "13.6.0",
4
+ "version": "13.7.0",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -49,13 +49,13 @@
49
49
  "wrangler": "^4.83.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@cloudflare/workers-types": "^4.20260228.0",
52
+ "@cloudflare/workers-types": "^4.20260526.1",
53
53
  "@types/node": "^22.10.6",
54
54
  "@types/prismjs": "1.26.6",
55
55
  "cheerio": "1.2.0",
56
- "devalue": "^5.6.3",
56
+ "devalue": "^5.8.1",
57
57
  "prismjs": "^1.30.0",
58
- "astro": "6.4.0",
58
+ "astro": "6.4.5",
59
59
  "astro-scripts": "0.0.14"
60
60
  },
61
61
  "publishConfig": {