@astrale-os/adapter-cloudflare 0.5.0-beta.155 → 0.5.0-beta.158

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
@@ -40,6 +40,25 @@ exact installed Release evidence, and opens
40
40
  the default View when the Application has a frontend. These are Project-tooling behaviors rather
41
41
  than Cloudflare adapter responsibilities.
42
42
 
43
+ ## Frontend files
44
+
45
+ The Worker runs before Workers Static Assets on every request and owns frontend response headers
46
+ and routing, for `cloudflare()` and `astrale()` deployments alike. Frontend preparation therefore
47
+ refuses a built frontend whose root contains `_headers`, `_redirects`, or `.assetsignore`, in any
48
+ letter case, as a file or a directory. Wrangler would apply them as asset configuration in a direct
49
+ deployment, while a managed deployment would serve them as public files. The refusal happens while
50
+ the artifact is prepared, before any provider change. Vite copies its public directory (`public/`
51
+ by default) to that root: remove them there, or stop the plugin that emits them. The same names
52
+ below the root, such as `docs/_headers`, remain ordinary files.
53
+
54
+ The Worker is also the only server-side code, so preparation likewise refuses a root name starting
55
+ with `_worker.js`, the name of a Cloudflare Pages worker script: a `_worker.js` file or directory,
56
+ or a sibling such as `_worker.js.map`. Wrangler refuses to upload these as assets, since they may
57
+ hold server-side code, so a direct deployment would fail at upload, while a managed deployment would
58
+ publish them. Unlike the names above, the match is case-sensitive, as Wrangler's is: `_Worker.js`
59
+ and a nested `docs/_worker.js` remain ordinary files. Remove such a file from the public directory,
60
+ or rename it if it is a browser script.
61
+
43
62
  ## Provider-local staging
44
63
 
45
64
  Managed Cloudflare providers may stage immutable implementation candidates without exposing their
@@ -2,6 +2,28 @@ import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
2
2
  import { readFile, readdir, rm } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { buildViteFrontend } from './vite.js';
5
+ /**
6
+ * Root names that Cloudflare Workers Static Assets reads as configuration rather than serving.
7
+ *
8
+ * Wrangler withholds them from a direct upload and applies them, while the managed upload through
9
+ * Services serves them as ordinary public files, so the same output would behave differently per
10
+ * adapter. The Worker runs first on every request and owns frontend response headers and routing for
11
+ * both, which leaves these files nothing to configure.
12
+ */
13
+ const RESERVED_ROOT_NAMES = new Set([
14
+ '_headers',
15
+ '_redirects',
16
+ '.assetsignore',
17
+ ]);
18
+ /**
19
+ * Name of a Cloudflare Pages worker script, the server-side code of a Pages project.
20
+ *
21
+ * Wrangler refuses to upload a root asset under this name, since that would publish the code, unless
22
+ * an `.assetsignore` exists, and the reserved names above already exclude one. A direct deployment
23
+ * would therefore fail at upload, while the managed upload through Services would publish the code.
24
+ * Neither adapter runs a Pages worker: the Worker is the only server-side code for both.
25
+ */
26
+ const PAGES_WORKER_NAME = '_worker.js';
5
27
  export async function frontendFiles(input) {
6
28
  const frontend = input.frontend;
7
29
  if (frontend === undefined)
@@ -18,11 +40,50 @@ export async function frontendFiles(input) {
18
40
  signal: input.signal,
19
41
  });
20
42
  const files = frontendBuild.files(frontend, await readFiles(input.output));
43
+ refuseReservedNames(files);
44
+ refusePagesWorker(files);
21
45
  // A name the bundler reported but that never reached disk is not a served path; keeping only the
22
46
  // intersection means the evidence describes exactly what this deployment will answer with.
23
47
  const served = new Set(files.map((file) => file.path));
24
48
  return { files, addressed: built.addressed.filter((path) => served.has(path)) };
25
49
  }
50
+ /**
51
+ * Refuse output that Cloudflare would read as asset configuration instead of serving it.
52
+ *
53
+ * Only the root is reserved: a nested `docs/_headers` stays an ordinary file. The match ignores case
54
+ * and covers directories, because Wrangler's upload filter withholds any root entry under these names
55
+ * regardless of case, and a case-insensitive filesystem hands it `_Headers` when it reads `_headers`.
56
+ */
57
+ function refuseReservedNames(files) {
58
+ const reserved = new Set(files
59
+ .map((file) => file.path.split('/')[0])
60
+ .filter((name) => RESERVED_ROOT_NAMES.has(name.toLowerCase())));
61
+ if (reserved.size === 0)
62
+ return;
63
+ const names = [...reserved].join(', ');
64
+ throw new TypeError(`Frontend output contains reserved Cloudflare asset configuration name(s) at its root: ${names}. ` +
65
+ 'A direct cloudflare() deployment would apply them as Workers Static Assets configuration, while a managed astrale() deployment would serve them as public files. ' +
66
+ 'The Worker owns frontend response headers and routing for both adapters: remove them from the Vite public directory, or stop the build from emitting them.');
67
+ }
68
+ /**
69
+ * Refuse output that Wrangler would decline to upload as a Cloudflare Pages worker script.
70
+ *
71
+ * Wrangler tests the relative path with a plain `startsWith`, not through its case-insensitive upload
72
+ * filter, so the match here is a case-sensitive prefix of the root name too. It covers a `_worker.js`
73
+ * file or directory and siblings such as `_worker.js.map`, which Wrangler refuses alike, while both
74
+ * adapters serve `_Worker.js` or a nested `docs/_worker.js` as an ordinary file.
75
+ */
76
+ function refusePagesWorker(files) {
77
+ const workers = new Set(files
78
+ .map((file) => file.path.split('/')[0])
79
+ .filter((name) => name.startsWith(PAGES_WORKER_NAME)));
80
+ if (workers.size === 0)
81
+ return;
82
+ const names = [...workers].join(', ');
83
+ throw new TypeError(`Frontend output contains Cloudflare Pages worker name(s) at its root: ${names}. ` +
84
+ `A direct cloudflare() deployment would fail because Wrangler refuses to upload a root name starting with ${PAGES_WORKER_NAME} as possible server-side code, while a managed astrale() deployment would serve them as public files. ` +
85
+ 'The Worker is the only server-side code for both adapters: remove or rename them in the Vite public directory, or stop the build from emitting them.');
86
+ }
26
87
  async function readFiles(root, current = '') {
27
88
  const entries = await readdir(join(root, current), { withFileTypes: true });
28
89
  const files = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/adapter-cloudflare",
3
- "version": "0.5.0-beta.155",
3
+ "version": "0.5.0-beta.158",
4
4
  "description": "Deploy an Astrale application through Cloudflare Workers",
5
5
  "keywords": [
6
6
  "adapter",
@@ -53,7 +53,7 @@
53
53
  "registry": "https://registry.npmjs.org/"
54
54
  },
55
55
  "dependencies": {
56
- "@astrale-os/sdk": "0.6.0-beta.0",
56
+ "@astrale-os/sdk": "0.6.0-beta.3",
57
57
  "@noble/hashes": "2.3.0",
58
58
  "hono": "^4.6.20",
59
59
  "jose": "^6.1.3",