@astrale-os/adapter-cloudflare 0.5.0-beta.15 → 0.5.0-beta.151

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.
Files changed (195) hide show
  1. package/README.md +46 -2
  2. package/dist/adapter.d.ts +14 -0
  3. package/dist/adapter.js +82 -0
  4. package/dist/build/artifact.d.ts +7 -0
  5. package/dist/build/artifact.js +170 -0
  6. package/dist/build/bundle/admit.d.ts +1 -0
  7. package/dist/build/bundle/admit.js +9 -0
  8. package/dist/build/closure.d.ts +2 -0
  9. package/dist/build/closure.js +22 -0
  10. package/dist/build/codegen/execution.d.ts +3 -0
  11. package/dist/build/codegen/execution.js +2 -0
  12. package/dist/build/codegen/merge.d.ts +0 -1
  13. package/dist/build/codegen/merge.js +0 -1
  14. package/dist/build/codegen/wrangler.d.ts +12 -3
  15. package/dist/build/codegen/wrangler.js +26 -18
  16. package/dist/build/configuration/addressing.d.ts +2 -0
  17. package/dist/build/configuration/addressing.js +17 -0
  18. package/dist/build/configuration/invocation-timeout.d.ts +2 -0
  19. package/dist/build/configuration/invocation-timeout.js +9 -0
  20. package/dist/build/configuration/request-limit.d.ts +2 -0
  21. package/dist/build/configuration/request-limit.js +9 -0
  22. package/dist/build/configuration/router.d.ts +7 -0
  23. package/dist/build/configuration/router.js +11 -0
  24. package/dist/build/configuration/worker-name.d.ts +2 -0
  25. package/dist/build/configuration/worker-name.js +18 -0
  26. package/dist/build/configuration.d.ts +5 -0
  27. package/dist/build/configuration.js +5 -0
  28. package/dist/build/direct.d.ts +4 -0
  29. package/dist/build/direct.js +90 -0
  30. package/dist/build/frontend/files.d.ts +19 -0
  31. package/dist/build/frontend/files.js +38 -0
  32. package/dist/build/frontend/readiness.d.ts +10 -0
  33. package/dist/build/frontend/readiness.js +21 -0
  34. package/dist/build/frontend/revision.d.ts +5 -0
  35. package/dist/build/frontend/revision.js +14 -0
  36. package/dist/build/frontend/vite.d.ts +14 -0
  37. package/dist/build/frontend/vite.js +42 -0
  38. package/dist/build/frontend/vite.process.d.ts +7 -0
  39. package/dist/build/frontend/vite.process.js +65 -0
  40. package/dist/build/frontend/vite.runner.d.ts +1 -0
  41. package/dist/build/frontend/vite.runner.js +155 -0
  42. package/dist/build/index.d.ts +9 -16
  43. package/dist/build/index.js +8 -16
  44. package/dist/build/materialize.d.ts +4 -0
  45. package/dist/build/materialize.js +10 -0
  46. package/dist/build/metafile.d.ts +5 -0
  47. package/dist/build/metafile.js +14 -0
  48. package/dist/build/qualification.d.ts +5 -0
  49. package/dist/build/qualification.js +7 -0
  50. package/dist/build/roots.d.ts +2 -0
  51. package/dist/build/roots.js +34 -0
  52. package/dist/build/worker/artifact.d.ts +40 -0
  53. package/dist/build/worker/artifact.js +1 -0
  54. package/dist/build/worker/artifact.load.d.ts +3 -0
  55. package/dist/build/worker/artifact.load.js +308 -0
  56. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  57. package/dist/build/worker/artifact.manifest.js +87 -0
  58. package/dist/build/worker/artifact.paths.d.ts +4 -0
  59. package/dist/build/worker/artifact.paths.js +25 -0
  60. package/dist/build/worker/codegen/entry.d.ts +2 -0
  61. package/dist/build/worker/codegen/entry.js +28 -0
  62. package/dist/build/worker/codegen/service.d.ts +21 -0
  63. package/dist/build/worker/codegen/service.js +54 -0
  64. package/dist/build/worker/execution.prepare.d.ts +20 -0
  65. package/dist/build/worker/execution.prepare.js +121 -0
  66. package/dist/build/worker/index.d.ts +2 -0
  67. package/dist/build/worker/index.js +1 -0
  68. package/dist/build/worker/modules.d.ts +11 -0
  69. package/dist/build/worker/modules.js +26 -0
  70. package/dist/build/worker/runtime.d.ts +10 -0
  71. package/dist/build/worker/runtime.js +35 -0
  72. package/dist/build/worker/specifier.d.ts +1 -0
  73. package/dist/build/worker/specifier.js +5 -0
  74. package/dist/cloudflare.d.ts +1 -45
  75. package/dist/cloudflare.js +1 -202
  76. package/dist/deploy/deploy.d.ts +17 -0
  77. package/dist/deploy/deploy.js +229 -0
  78. package/dist/deploy/index.d.ts +1 -0
  79. package/dist/deploy/index.js +1 -0
  80. package/dist/deploy/output.d.ts +2 -0
  81. package/dist/deploy/output.js +10 -0
  82. package/dist/deploy/readiness.d.ts +5 -0
  83. package/dist/deploy/readiness.js +21 -0
  84. package/dist/identity/binding.d.ts +2 -0
  85. package/dist/identity/binding.js +2 -0
  86. package/dist/identity/index.d.ts +3 -0
  87. package/dist/identity/index.js +3 -0
  88. package/dist/identity/material.d.ts +7 -0
  89. package/dist/identity/material.js +42 -0
  90. package/dist/identity/secret.d.ts +3 -0
  91. package/dist/identity/secret.js +7 -0
  92. package/dist/identity/source.d.ts +26 -0
  93. package/dist/identity/source.js +127 -0
  94. package/dist/index.d.ts +10 -9
  95. package/dist/index.js +10 -9
  96. package/dist/params.d.ts +43 -36
  97. package/dist/params.js +0 -1
  98. package/dist/release/deployment.d.ts +44 -0
  99. package/dist/release/deployment.js +141 -0
  100. package/dist/release/index.d.ts +2 -0
  101. package/dist/release/index.js +2 -0
  102. package/dist/release/staged-worker-name.d.ts +3 -0
  103. package/dist/release/staged-worker-name.js +10 -0
  104. package/dist/worker/entry.d.ts +48 -0
  105. package/dist/worker/entry.js +159 -0
  106. package/dist/worker/fetch.d.ts +0 -1
  107. package/dist/worker/fetch.js +0 -1
  108. package/dist/worker/frontend.d.ts +4 -0
  109. package/dist/worker/frontend.js +75 -0
  110. package/dist/worker/identity.d.ts +7 -0
  111. package/dist/worker/identity.js +23 -0
  112. package/dist/worker/index.d.ts +2 -61
  113. package/dist/worker/index.js +2 -218
  114. package/dist/wrangler/bundle.d.ts +10 -0
  115. package/dist/wrangler/bundle.js +28 -0
  116. package/dist/wrangler/deploy.d.ts +24 -0
  117. package/dist/wrangler/deploy.js +44 -0
  118. package/dist/wrangler/index.d.ts +5 -0
  119. package/dist/wrangler/index.js +5 -0
  120. package/dist/wrangler/process.d.ts +15 -0
  121. package/dist/wrangler/process.js +102 -0
  122. package/dist/wrangler/rollout.d.ts +27 -0
  123. package/dist/wrangler/rollout.js +75 -0
  124. package/dist/wrangler/secrets.d.ts +2 -0
  125. package/dist/wrangler/secrets.js +24 -0
  126. package/dist/wrangler/status.d.ts +18 -0
  127. package/dist/wrangler/status.js +75 -0
  128. package/dist/wrangler/triggers.d.ts +5 -0
  129. package/dist/wrangler/triggers.js +12 -0
  130. package/dist/wrangler/type-generation.d.ts +6 -0
  131. package/dist/wrangler/type-generation.js +19 -0
  132. package/dist/wrangler/versions.d.ts +13 -0
  133. package/dist/wrangler/versions.js +38 -0
  134. package/package.json +35 -19
  135. package/template/astrale.config.ts +30 -31
  136. package/dist/build/codegen/identity.d.ts +0 -10
  137. package/dist/build/codegen/identity.d.ts.map +0 -1
  138. package/dist/build/codegen/identity.js +0 -32
  139. package/dist/build/codegen/identity.js.map +0 -1
  140. package/dist/build/codegen/merge.d.ts.map +0 -1
  141. package/dist/build/codegen/merge.js.map +0 -1
  142. package/dist/build/codegen/worker.d.ts +0 -12
  143. package/dist/build/codegen/worker.d.ts.map +0 -1
  144. package/dist/build/codegen/worker.js +0 -40
  145. package/dist/build/codegen/worker.js.map +0 -1
  146. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  147. package/dist/build/codegen/wrangler.js.map +0 -1
  148. package/dist/build/frontend.d.ts +0 -27
  149. package/dist/build/frontend.d.ts.map +0 -1
  150. package/dist/build/frontend.js +0 -252
  151. package/dist/build/frontend.js.map +0 -1
  152. package/dist/build/index.d.ts.map +0 -1
  153. package/dist/build/index.js.map +0 -1
  154. package/dist/build/parse-output.d.ts +0 -27
  155. package/dist/build/parse-output.d.ts.map +0 -1
  156. package/dist/build/parse-output.js +0 -58
  157. package/dist/build/parse-output.js.map +0 -1
  158. package/dist/build/prepare.d.ts +0 -18
  159. package/dist/build/prepare.d.ts.map +0 -1
  160. package/dist/build/prepare.js +0 -138
  161. package/dist/build/prepare.js.map +0 -1
  162. package/dist/build/worker.d.ts +0 -15
  163. package/dist/build/worker.d.ts.map +0 -1
  164. package/dist/build/worker.js +0 -21
  165. package/dist/build/worker.js.map +0 -1
  166. package/dist/build/wrangler-cli.d.ts +0 -88
  167. package/dist/build/wrangler-cli.d.ts.map +0 -1
  168. package/dist/build/wrangler-cli.js +0 -283
  169. package/dist/build/wrangler-cli.js.map +0 -1
  170. package/dist/cloudflare.d.ts.map +0 -1
  171. package/dist/cloudflare.js.map +0 -1
  172. package/dist/index.d.ts.map +0 -1
  173. package/dist/index.js.map +0 -1
  174. package/dist/params.d.ts.map +0 -1
  175. package/dist/params.js.map +0 -1
  176. package/dist/worker/fetch.d.ts.map +0 -1
  177. package/dist/worker/fetch.js.map +0 -1
  178. package/dist/worker/index.d.ts.map +0 -1
  179. package/dist/worker/index.js.map +0 -1
  180. package/src/build/codegen/identity.ts +0 -40
  181. package/src/build/codegen/merge.ts +0 -92
  182. package/src/build/codegen/worker.ts +0 -53
  183. package/src/build/codegen/wrangler.ts +0 -128
  184. package/src/build/frontend.ts +0 -298
  185. package/src/build/index.ts +0 -29
  186. package/src/build/parse-output.ts +0 -60
  187. package/src/build/prepare.ts +0 -189
  188. package/src/build/worker.ts +0 -37
  189. package/src/build/wrangler-cli.ts +0 -372
  190. package/src/cloudflare.ts +0 -275
  191. package/src/index.ts +0 -21
  192. package/src/params.ts +0 -92
  193. package/src/worker/fetch.ts +0 -46
  194. package/src/worker/index.ts +0 -332
  195. package/src/worker/tsconfig.json +0 -10
@@ -0,0 +1,90 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { generateWranglerConfig } from './codegen/wrangler.js';
3
+ import { resolveRouter, workerName } from './configuration.js';
4
+ import { workerModules } from './worker/modules.js';
5
+ export function admitDirectPlacement(artifact, worker, parameters, environment = '') {
6
+ const name = workerName(parameters, worker.origin, environment);
7
+ const release = config(artifact, 'wrangler.jsonc');
8
+ const router = resolveRouter(parameters.router);
9
+ const frontend = artifact.build.frontend;
10
+ const hasViteFrontend = frontend?.source.kind === 'vite';
11
+ equal(worker.assets !== undefined, hasViteFrontend);
12
+ if (worker.assets !== undefined) {
13
+ equal(worker.assets.notFound, 'none');
14
+ equal(worker.assets.runWorkerFirst, true);
15
+ }
16
+ equal(release, generated({
17
+ workerName: name,
18
+ main: `./${worker.release.entrypoint}`,
19
+ ...(parameters.route === undefined ? {} : { route: parameters.route }),
20
+ vars: worker.bindings.vars,
21
+ secretBindings: worker.bindings.requiredSecrets,
22
+ modules: directModules(worker.release),
23
+ ...(hasViteFrontend
24
+ ? {
25
+ frontend: {
26
+ directory: './frontend',
27
+ },
28
+ }
29
+ : {}),
30
+ ...(router === undefined
31
+ ? {}
32
+ : { router: { binding: router.binding, service: router.service } }),
33
+ ...(parameters.wrangler === undefined ? {} : { wrangler: parameters.wrangler }),
34
+ selfBinding: false,
35
+ }));
36
+ equal(release.compatibility_date, worker.runtime.compatibilityDate);
37
+ equalStringCollection(release.compatibility_flags, worker.runtime.compatibilityFlags);
38
+ equal(release.services ?? [], worker.bindings.services.map(serviceBinding));
39
+ equal(record(release.assets).html_handling, worker.assets?.htmlHandling);
40
+ }
41
+ function directModules(worker) {
42
+ try {
43
+ return workerModules(worker);
44
+ }
45
+ catch {
46
+ mismatch();
47
+ }
48
+ }
49
+ function generated(input) {
50
+ return JSON.parse(generateWranglerConfig(input));
51
+ }
52
+ function config(artifact, path) {
53
+ const file = artifact.files.find((candidate) => candidate.path === path);
54
+ if (file === undefined)
55
+ throw new TypeError(`Direct Cloudflare artifact is missing ${path}.`);
56
+ try {
57
+ const value = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(file.bytes()));
58
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
59
+ throw new Error();
60
+ return value;
61
+ }
62
+ catch (cause) {
63
+ throw new TypeError(`Direct Cloudflare artifact ${path} is invalid.`, { cause });
64
+ }
65
+ }
66
+ function serviceBinding(binding) {
67
+ return { binding: binding.name, service: binding.service };
68
+ }
69
+ function record(input) {
70
+ return input !== null && typeof input === 'object' && !Array.isArray(input)
71
+ ? input
72
+ : {};
73
+ }
74
+ function equal(actual, expected) {
75
+ if (!isDeepStrictEqual(actual, expected)) {
76
+ throw new TypeError('Direct Cloudflare placement differs from its admitted Worker artifact.', {
77
+ cause: { actual, expected },
78
+ });
79
+ }
80
+ }
81
+ function equalStringCollection(actual, expected) {
82
+ if (!Array.isArray(actual) ||
83
+ actual.some((value) => typeof value !== 'string') ||
84
+ !isDeepStrictEqual([...actual].sort(), [...expected].sort())) {
85
+ mismatch();
86
+ }
87
+ }
88
+ function mismatch() {
89
+ throw new TypeError('Direct Cloudflare placement differs from its admitted Worker artifact.');
90
+ }
@@ -0,0 +1,19 @@
1
+ import type { Build } from '@astrale-os/sdk/deployment/build';
2
+ import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
3
+ /** The served frontend, and which of its files a browser may keep without revalidating. */
4
+ export interface FrontendOutput {
5
+ readonly files: readonly frontendBuild.FrontendFile[];
6
+ /**
7
+ * Served paths the bundler content-addressed, so their bytes can never change under a given name.
8
+ * A frontend Astrale does not build leaves this empty, which is what keeps the absence of evidence
9
+ * indistinguishable from evidence of mutability.
10
+ */
11
+ readonly addressed: readonly string[];
12
+ }
13
+ export declare function frontendFiles(input: {
14
+ readonly frontend: Build['frontend'];
15
+ readonly projectDir: string;
16
+ readonly output: string;
17
+ readonly recordSource?: (path: string) => void;
18
+ readonly signal: AbortSignal;
19
+ }): Promise<FrontendOutput>;
@@ -0,0 +1,38 @@
1
+ import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
2
+ import { readFile, readdir, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { buildViteFrontend } from './vite.js';
5
+ export async function frontendFiles(input) {
6
+ const frontend = input.frontend;
7
+ if (frontend === undefined)
8
+ return { files: [], addressed: [] };
9
+ if (frontend.source.kind === 'external') {
10
+ return { files: frontendBuild.files(frontend, []), addressed: [] };
11
+ }
12
+ await rm(input.output, { recursive: true, force: true });
13
+ const built = await buildViteFrontend({
14
+ projectDir: input.projectDir,
15
+ output: input.output,
16
+ ...(frontend.source.config === undefined ? {} : { config: frontend.source.config }),
17
+ recordSource: input.recordSource,
18
+ signal: input.signal,
19
+ });
20
+ const files = frontendBuild.files(frontend, await readFiles(input.output));
21
+ // A name the bundler reported but that never reached disk is not a served path; keeping only the
22
+ // intersection means the evidence describes exactly what this deployment will answer with.
23
+ const served = new Set(files.map((file) => file.path));
24
+ return { files, addressed: built.addressed.filter((path) => served.has(path)) };
25
+ }
26
+ async function readFiles(root, current = '') {
27
+ const entries = await readdir(join(root, current), { withFileTypes: true });
28
+ const files = [];
29
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
30
+ const path = current === '' ? entry.name : `${current}/${entry.name}`;
31
+ if (entry.isDirectory())
32
+ files.push(...(await readFiles(root, path)));
33
+ else if (entry.isFile()) {
34
+ files.push({ path, content: new Uint8Array(await readFile(join(root, path))) });
35
+ }
36
+ }
37
+ return files;
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { Release } from '@astrale-os/sdk/deployment/release';
2
+ import type { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
3
+ import type { WorkerArtifact } from '../worker/artifact.js';
4
+ type ReadinessFetch = NonNullable<NonNullable<Parameters<typeof verifyReadiness>[1]>['fetch']>;
5
+ /** Publication equality alone cannot prove a frontend-only deployment has propagated. */
6
+ export declare function verifyWorkerFrontend(release: Release, worker: Pick<WorkerArtifact, 'assets'>, options?: {
7
+ readonly fetch?: ReadinessFetch;
8
+ readonly signal?: AbortSignal;
9
+ }): Promise<void>;
10
+ export {};
@@ -0,0 +1,21 @@
1
+ import { frontendRevision } from './revision.js';
2
+ /** Publication equality alone cannot prove a frontend-only deployment has propagated. */
3
+ export async function verifyWorkerFrontend(release, worker, options = {}) {
4
+ const frontend = release.build.frontend;
5
+ if (frontend === undefined || frontend.source.kind === 'external')
6
+ return;
7
+ if (worker.assets === undefined)
8
+ throw new Error('Prepared Worker frontend assets are missing.');
9
+ const expected = frontendRevision(worker.assets.files);
10
+ const fetcher = options.fetch ?? globalThis.fetch;
11
+ for (const route of frontend.routes) {
12
+ const response = await fetcher(new URL(route.path, release.addressing.publicUrl).href, {
13
+ redirect: 'error',
14
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
15
+ });
16
+ const matches = response.status === 200 && response.headers.get('X-Astrale-Frontend') === expected;
17
+ await response.body?.cancel();
18
+ if (!matches)
19
+ throw new Error(`Frontend ${route.path} is not serving the prepared browser build yet.`);
20
+ }
21
+ }
@@ -0,0 +1,5 @@
1
+ /** Bind the generated Worker to its exact browser files, independently of Schema revision. */
2
+ export declare function frontendRevision(files: readonly {
3
+ readonly path: string;
4
+ bytes(): Uint8Array;
5
+ }[]): string;
@@ -0,0 +1,14 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** Bind the generated Worker to its exact browser files, independently of Schema revision. */
3
+ export function frontendRevision(files) {
4
+ const hash = createHash('sha256');
5
+ const ordered = files
6
+ .map((file) => ({ path: file.path.replace(/^\//u, ''), bytes: file.bytes }))
7
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
8
+ for (const file of ordered) {
9
+ const bytes = file.bytes();
10
+ hash.update(JSON.stringify([file.path, bytes.byteLength]));
11
+ hash.update(bytes);
12
+ }
13
+ return `sha256:${hash.digest('hex')}`;
14
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Build a Vite frontend in an isolated process and report its authored inputs, along with the
3
+ * outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
4
+ * the build that named them can say which they are.
5
+ */
6
+ export declare function buildViteFrontend(input: {
7
+ readonly projectDir: string;
8
+ readonly output: string;
9
+ readonly config?: string;
10
+ readonly recordSource?: (path: string) => void;
11
+ readonly signal: AbortSignal;
12
+ }): Promise<{
13
+ readonly addressed: readonly string[];
14
+ }>;
@@ -0,0 +1,42 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { runViteProcess } from './vite.process.js';
6
+ /**
7
+ * Build a Vite frontend in an isolated process and report its authored inputs, along with the
8
+ * outputs the bundler content-addressed. Those are the files a browser may keep forever, and only
9
+ * the build that named them can say which they are.
10
+ */
11
+ export async function buildViteFrontend(input) {
12
+ const parent = dirname(input.output);
13
+ await mkdir(parent, { recursive: true });
14
+ const scratch = await mkdtemp(join(parent, 'vite-run-'));
15
+ const evidence = join(scratch, 'sources.json');
16
+ const addressed = join(scratch, 'addressed.json');
17
+ try {
18
+ await runViteProcess({
19
+ cwd: input.projectDir,
20
+ arguments: [runnerPath(), input.output, evidence, input.config ?? '', addressed],
21
+ signal: input.signal,
22
+ });
23
+ const sources = JSON.parse(await readFile(evidence, 'utf8'));
24
+ if (!Array.isArray(sources) || sources.some((path) => typeof path !== 'string')) {
25
+ throw new TypeError('Vite preparation source evidence is invalid.');
26
+ }
27
+ for (const path of sources)
28
+ input.recordSource?.(path);
29
+ const named = JSON.parse(await readFile(addressed, 'utf8'));
30
+ if (!Array.isArray(named) || named.some((path) => typeof path !== 'string')) {
31
+ throw new TypeError('Vite preparation output evidence is invalid.');
32
+ }
33
+ return { addressed: Object.freeze([...named]) };
34
+ }
35
+ finally {
36
+ await rm(scratch, { recursive: true, force: true });
37
+ }
38
+ }
39
+ function runnerPath() {
40
+ const built = fileURLToPath(new URL('./vite.runner.js', import.meta.url));
41
+ return existsSync(built) ? built : fileURLToPath(new URL('./vite.runner.ts', import.meta.url));
42
+ }
@@ -0,0 +1,7 @@
1
+ /** Run one bounded, cancellable Vite subprocess. */
2
+ export declare function runViteProcess(input: {
3
+ readonly cwd: string;
4
+ readonly arguments: readonly string[];
5
+ readonly signal: AbortSignal;
6
+ readonly timeoutMs?: number;
7
+ }): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { spawn } from 'node:child_process';
2
+ const MAXIMUM_OUTPUT_BYTES = 1024 * 1024;
3
+ const TERMINATION_GRACE_MS = 1_000;
4
+ const VITE_TIMEOUT_MS = 300_000;
5
+ /** Run one bounded, cancellable Vite subprocess. */
6
+ export function runViteProcess(input) {
7
+ if (input.signal.aborted)
8
+ return Promise.reject(abortReason(input.signal));
9
+ return new Promise((resolve, reject) => {
10
+ const child = spawn(process.execPath, input.arguments, {
11
+ cwd: input.cwd,
12
+ stdio: ['ignore', 'pipe', 'pipe'],
13
+ windowsHide: true,
14
+ });
15
+ let output = '';
16
+ let outputBytes = 0;
17
+ let terminating;
18
+ let escalation;
19
+ const timeout = setTimeout(() => terminate(new Error('Vite process timed out.')), input.timeoutMs ?? VITE_TIMEOUT_MS);
20
+ const abort = () => terminate(abortReason(input.signal));
21
+ input.signal.addEventListener('abort', abort, { once: true });
22
+ const cleanup = () => {
23
+ clearTimeout(timeout);
24
+ if (escalation !== undefined)
25
+ clearTimeout(escalation);
26
+ input.signal.removeEventListener('abort', abort);
27
+ };
28
+ function terminate(cause) {
29
+ if (terminating !== undefined)
30
+ return;
31
+ terminating = cause;
32
+ child.kill('SIGTERM');
33
+ escalation = setTimeout(() => {
34
+ if (child.exitCode === null && child.signalCode === null)
35
+ child.kill('SIGKILL');
36
+ }, TERMINATION_GRACE_MS);
37
+ }
38
+ const collect = (buffer) => {
39
+ outputBytes += buffer.byteLength;
40
+ if (outputBytes > MAXIMUM_OUTPUT_BYTES) {
41
+ terminate(new Error('Vite process exceeded its output limit.'));
42
+ return;
43
+ }
44
+ output += buffer.toString();
45
+ };
46
+ child.stdout.on('data', collect);
47
+ child.stderr.on('data', collect);
48
+ child.once('error', (cause) => {
49
+ cleanup();
50
+ reject(terminating ?? cause);
51
+ });
52
+ child.once('close', (code) => {
53
+ cleanup();
54
+ if (terminating !== undefined)
55
+ reject(terminating);
56
+ else if (code === 0)
57
+ resolve();
58
+ else
59
+ reject(new Error(`Vite failed (code ${code ?? 1}):\n${output.slice(-1500)}`));
60
+ });
61
+ });
62
+ }
63
+ function abortReason(signal) {
64
+ return signal.reason instanceof Error ? signal.reason : new Error('Vite process cancelled.');
65
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,155 @@
1
+ import { readdir, writeFile } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
3
+ import { isAbsolute, join, resolve } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ const CONFIG_FILES = Object.freeze([
6
+ 'vite.config.js',
7
+ 'vite.config.mjs',
8
+ 'vite.config.ts',
9
+ 'vite.config.cjs',
10
+ 'vite.config.mts',
11
+ 'vite.config.cts',
12
+ ]);
13
+ await main();
14
+ async function main() {
15
+ const [output, evidence, configured, addressed] = process.argv.slice(2);
16
+ if (output === undefined || evidence === undefined) {
17
+ throw new TypeError('Vite preparation runner arguments are invalid.');
18
+ }
19
+ const projectDir = process.cwd();
20
+ const vite = await projectVite(projectDir);
21
+ const sources = new Set();
22
+ const named = new Set();
23
+ let publicDir = false;
24
+ let environment = {
25
+ directory: false,
26
+ mode: 'production',
27
+ };
28
+ const config = configured === '' || configured === undefined ? undefined : configured;
29
+ await vite.build({
30
+ logLevel: 'silent',
31
+ ...(config === undefined
32
+ ? {}
33
+ : { configFile: isAbsolute(config) ? config : resolve(projectDir, config) }),
34
+ build: { outDir: output, emptyOutDir: true },
35
+ plugins: [
36
+ {
37
+ name: 'astrale:preparation-sources',
38
+ configResolved(value) {
39
+ publicDir = value.publicDir;
40
+ environment = { directory: value.envDir, mode: value.mode };
41
+ for (const path of value.configFileDependencies)
42
+ sources.add(path);
43
+ },
44
+ transform(_source, id) {
45
+ const path = physicalModulePath(id);
46
+ if (path !== undefined)
47
+ sources.add(path);
48
+ },
49
+ // The bundler is the only party that knows which outputs it content-addressed: a served path
50
+ // is safe to retain exactly when its name changes with its bytes. Reading that here, from
51
+ // the build that produced them, is what keeps the answer a fact rather than a guess about
52
+ // directory layout or file naming.
53
+ generateBundle(options, bundle) {
54
+ for (const [fileName, entry] of Object.entries(bundle)) {
55
+ if (addressedName(options, entry))
56
+ named.add(fileName);
57
+ }
58
+ },
59
+ },
60
+ ],
61
+ });
62
+ for (const path of configCandidates(projectDir, config))
63
+ sources.add(path);
64
+ for (const path of environmentCandidates(environment))
65
+ sources.add(path);
66
+ if (publicDir !== false)
67
+ await collectTree(publicDir, sources);
68
+ await writeFile(evidence, JSON.stringify([...sources]));
69
+ if (addressed !== undefined)
70
+ await writeFile(addressed, JSON.stringify([...named].sort()));
71
+ }
72
+ /**
73
+ * Whether this output's name carries a content hash.
74
+ *
75
+ * Two facts are required, and each is necessary. The output must carry a `name`, which the bundler
76
+ * sets only for the outputs it named itself: a plugin emitting an explicit `fileName` bypasses the
77
+ * template entirely, and such a file can sit anywhere, including beside the bundle's own output. And
78
+ * the template that was applied must embed `[hash]`, since a project is free to strip it.
79
+ *
80
+ * Anything else leaves the answer unknown, and unknown must read as "not content-addressed": losing
81
+ * the optimization costs a revalidation, while claiming it wrongly serves a stale file for as long
82
+ * as the header says, with no way to recall it.
83
+ */
84
+ function addressedName(options, entry) {
85
+ if (typeof entry.name !== 'string')
86
+ return false;
87
+ const template = entry.type === 'asset'
88
+ ? options.assetFileNames
89
+ : entry.isEntry === true
90
+ ? options.entryFileNames
91
+ : options.chunkFileNames;
92
+ return typeof template === 'string' && template.includes('[hash]');
93
+ }
94
+ function environmentCandidates(input) {
95
+ const directory = input.directory;
96
+ if (directory === false)
97
+ return [];
98
+ return ['.env', '.env.local', `.env.${input.mode}`, `.env.${input.mode}.local`].map((name) => join(directory, name));
99
+ }
100
+ function physicalModulePath(id) {
101
+ if (id.startsWith('\0'))
102
+ return undefined;
103
+ const path = id.replace(/[?#].*$/u, '');
104
+ if (path.length === 0)
105
+ return undefined;
106
+ if (path.startsWith('file:')) {
107
+ try {
108
+ return fileURLToPath(path);
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ }
114
+ return isAbsolute(path) ? path : undefined;
115
+ }
116
+ async function projectVite(projectDir) {
117
+ try {
118
+ const manifest = createRequire(join(projectDir, 'package.json')).resolve('vite/package.json');
119
+ const entry = createRequire(manifest).resolve('vite');
120
+ return (await import(pathToFileURL(entry).href));
121
+ }
122
+ catch (cause) {
123
+ throw new TypeError('Vite frontend requires a project-local Vite package.', { cause });
124
+ }
125
+ }
126
+ function configCandidates(projectDir, configured) {
127
+ if (configured !== undefined) {
128
+ return [isAbsolute(configured) ? configured : resolve(projectDir, configured)];
129
+ }
130
+ return CONFIG_FILES.map((name) => join(projectDir, name));
131
+ }
132
+ async function collectTree(path, output) {
133
+ output.add(path);
134
+ let entries;
135
+ try {
136
+ entries = await readdir(path, { withFileTypes: true });
137
+ }
138
+ catch (cause) {
139
+ if (errorCode(cause) === 'ENOENT')
140
+ return;
141
+ throw cause;
142
+ }
143
+ for (const entry of entries) {
144
+ const child = join(path, entry.name);
145
+ if (entry.isDirectory())
146
+ await collectTree(child, output);
147
+ else if (entry.isFile())
148
+ output.add(child);
149
+ }
150
+ }
151
+ function errorCode(input) {
152
+ return input !== null && typeof input === 'object' && 'code' in input
153
+ ? String(Reflect.get(input, 'code'))
154
+ : undefined;
155
+ }
@@ -1,16 +1,9 @@
1
- /**
2
- * `@astrale-os/adapter-cloudflare/build` — the shared Cloudflare-Workers build
3
- * toolkit, exposed for sibling deploy adapters (e.g.
4
- * `@astrale-os/adapter-astrale`).
5
- *
6
- * This is the "compile a `defineDomain` into a Cloudflare Workers bundle + run
7
- * local dev" surface that every Cloudflare-Workers deploy strategy shares,
8
- * regardless of WHERE the bundle ultimately ships. It is NOT part of the public
9
- * adapter API (`@astrale-os/adapter-cloudflare`) — it's the seam two adapters
10
- * build on so the codegen/bundling/dev logic lives in exactly one place.
11
- */
12
- export { buildFrontend, FRONTEND_STAGE_DIR, startFrontendDev, type FrontendDevHandle, type ViteBuild, type ViteBuildInput, } from './frontend.js';
13
- export { logTo, prepare } from './prepare.js';
14
- export { buildCloudflareWorker, type CloudflareWorkerBuild } from './worker.js';
15
- export { bulkPutSecrets, runWranglerBundle, runWranglerDeploy, runWranglerDev, runWranglerTypes, } from './wrangler-cli.js';
16
- //# sourceMappingURL=index.d.ts.map
1
+ export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
2
+ export { loadWorkerArtifact } from './worker/index.js';
3
+ export type { WorkerArtifact } from './worker/index.js';
4
+ export { resolveRouter, workerName } from './configuration.js';
5
+ export { frontendFiles } from './frontend/files.js';
6
+ export { verifyWorkerFrontend } from './frontend/readiness.js';
7
+ export { qualifyWorkerBundle } from './qualification.js';
8
+ export { assertSingletonRuntimeRoots } from './roots.js';
9
+ export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
@@ -1,16 +1,8 @@
1
- /**
2
- * `@astrale-os/adapter-cloudflare/build` — the shared Cloudflare-Workers build
3
- * toolkit, exposed for sibling deploy adapters (e.g.
4
- * `@astrale-os/adapter-astrale`).
5
- *
6
- * This is the "compile a `defineDomain` into a Cloudflare Workers bundle + run
7
- * local dev" surface that every Cloudflare-Workers deploy strategy shares,
8
- * regardless of WHERE the bundle ultimately ships. It is NOT part of the public
9
- * adapter API (`@astrale-os/adapter-cloudflare`) — it's the seam two adapters
10
- * build on so the codegen/bundling/dev logic lives in exactly one place.
11
- */
12
- export { buildFrontend, FRONTEND_STAGE_DIR, startFrontendDev, } from './frontend.js';
13
- export { logTo, prepare } from './prepare.js';
14
- export { buildCloudflareWorker } from './worker.js';
15
- export { bulkPutSecrets, runWranglerBundle, runWranglerDeploy, runWranglerDev, runWranglerTypes, } from './wrangler-cli.js';
16
- //# sourceMappingURL=index.js.map
1
+ export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
2
+ export { loadWorkerArtifact } from './worker/index.js';
3
+ export { resolveRouter, workerName } from './configuration.js';
4
+ export { frontendFiles } from './frontend/files.js';
5
+ export { verifyWorkerFrontend } from './frontend/readiness.js';
6
+ export { qualifyWorkerBundle } from './qualification.js';
7
+ export { assertSingletonRuntimeRoots } from './roots.js';
8
+ export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
@@ -0,0 +1,4 @@
1
+ export declare function materialize(root: string, files: readonly {
2
+ readonly path: string;
3
+ bytes(): Uint8Array;
4
+ }[]): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ export async function materialize(root, files) {
4
+ await rm(root, { recursive: true, force: true });
5
+ for (const file of files) {
6
+ const path = join(root, file.path);
7
+ await mkdir(dirname(path), { recursive: true });
8
+ await writeFile(path, file.bytes());
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ export interface WranglerMetafile {
2
+ readonly inputs: Readonly<Record<string, unknown>>;
3
+ }
4
+ /** Decode Wrangler's emitted esbuild graph at the provider-tooling boundary. */
5
+ export declare function readWranglerMetafile(path: string): Promise<WranglerMetafile>;
@@ -0,0 +1,14 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ /** Decode Wrangler's emitted esbuild graph at the provider-tooling boundary. */
3
+ export async function readWranglerMetafile(path) {
4
+ const source = await readFile(path, 'utf8');
5
+ const value = JSON.parse(source);
6
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
7
+ throw new TypeError('Wrangler metafile is not an object.');
8
+ }
9
+ const inputs = Reflect.get(value, 'inputs');
10
+ if (inputs === null || typeof inputs !== 'object' || Array.isArray(inputs)) {
11
+ throw new TypeError('Wrangler metafile has no input graph.');
12
+ }
13
+ return Object.freeze({ inputs: inputs });
14
+ }
@@ -0,0 +1,5 @@
1
+ /** Qualify Wrangler's emitted module, not generated source or an inferred graph. */
2
+ export declare function qualifyWorkerBundle(evidence: {
3
+ readonly metafilePath: string;
4
+ readonly inputRoot: string;
5
+ }): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import { assertRuntimeClosure } from './closure.js';
2
+ import { assertSingletonRuntimeRoots } from './roots.js';
3
+ /** Qualify Wrangler's emitted module, not generated source or an inferred graph. */
4
+ export async function qualifyWorkerBundle(evidence) {
5
+ await assertRuntimeClosure(evidence.metafilePath);
6
+ await assertSingletonRuntimeRoots(evidence.metafilePath, evidence.inputRoot);
7
+ }
@@ -0,0 +1,2 @@
1
+ /** Prove that Wrangler emitted at most one physical root per runtime package. */
2
+ export declare function assertSingletonRuntimeRoots(metafilePath: string, inputRoot: string): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { realpath } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { readWranglerMetafile } from './metafile.js';
4
+ const RUNTIME_PACKAGES = [
5
+ '@astrale-os/adapter-cloudflare',
6
+ '@astrale-os/sdk',
7
+ '@astrale-os/kernel-core',
8
+ '@astrale-os/kernel-dsl',
9
+ '@astrale-os/kernel-ports',
10
+ '@astrale-os/kernel-protocol',
11
+ '@astrale-os/kernel-server',
12
+ '@astrale-os/kernel-client',
13
+ ];
14
+ /** Prove that Wrangler emitted at most one physical root per runtime package. */
15
+ export async function assertSingletonRuntimeRoots(metafilePath, inputRoot) {
16
+ const metafile = await readWranglerMetafile(metafilePath);
17
+ for (const name of RUNTIME_PACKAGES) {
18
+ const roots = new Set();
19
+ for (const input of Object.keys(metafile.inputs)) {
20
+ const root = packageRoot(input, name);
21
+ if (root !== undefined)
22
+ roots.add(await realpath(resolve(inputRoot, root)));
23
+ }
24
+ if (roots.size > 1) {
25
+ throw new Error(`Cloudflare Worker contains duplicate physical ${name} roots: ${[...roots].join(', ')}.`);
26
+ }
27
+ }
28
+ }
29
+ function packageRoot(input, name) {
30
+ const normalized = input.replaceAll('\\', '/');
31
+ const marker = `node_modules/${name}/`;
32
+ const index = normalized.lastIndexOf(marker);
33
+ return index < 0 ? undefined : normalized.slice(0, index + marker.length - 1);
34
+ }