@astrale-os/adapter-cloudflare 0.5.0-beta.13 → 0.5.0-beta.131

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 (196) hide show
  1. package/CHANGELOG.md +1515 -0
  2. package/README.md +46 -2
  3. package/dist/adapter.d.ts +14 -0
  4. package/dist/adapter.js +82 -0
  5. package/dist/build/artifact.d.ts +7 -0
  6. package/dist/build/artifact.js +165 -0
  7. package/dist/build/bundle/admit.d.ts +1 -0
  8. package/dist/build/bundle/admit.js +9 -0
  9. package/dist/build/closure.d.ts +2 -0
  10. package/dist/build/closure.js +22 -0
  11. package/dist/build/codegen/execution.d.ts +3 -0
  12. package/dist/build/codegen/execution.js +2 -0
  13. package/dist/build/codegen/merge.d.ts +0 -1
  14. package/dist/build/codegen/merge.js +0 -1
  15. package/dist/build/codegen/wrangler.d.ts +12 -3
  16. package/dist/build/codegen/wrangler.js +26 -18
  17. package/dist/build/configuration/addressing.d.ts +2 -0
  18. package/dist/build/configuration/addressing.js +11 -0
  19. package/dist/build/configuration/invocation-timeout.d.ts +2 -0
  20. package/dist/build/configuration/invocation-timeout.js +9 -0
  21. package/dist/build/configuration/request-limit.d.ts +2 -0
  22. package/dist/build/configuration/request-limit.js +9 -0
  23. package/dist/build/configuration/router.d.ts +7 -0
  24. package/dist/build/configuration/router.js +11 -0
  25. package/dist/build/configuration/worker-name.d.ts +2 -0
  26. package/dist/build/configuration/worker-name.js +18 -0
  27. package/dist/build/configuration.d.ts +5 -0
  28. package/dist/build/configuration.js +5 -0
  29. package/dist/build/direct.d.ts +4 -0
  30. package/dist/build/direct.js +90 -0
  31. package/dist/build/frontend/files.d.ts +9 -0
  32. package/dist/build/frontend/files.js +33 -0
  33. package/dist/build/frontend/readiness.d.ts +10 -0
  34. package/dist/build/frontend/readiness.js +21 -0
  35. package/dist/build/frontend/revision.d.ts +5 -0
  36. package/dist/build/frontend/revision.js +14 -0
  37. package/dist/build/frontend/vite.d.ts +8 -0
  38. package/dist/build/frontend/vite.js +32 -0
  39. package/dist/build/frontend/vite.process.d.ts +7 -0
  40. package/dist/build/frontend/vite.process.js +65 -0
  41. package/dist/build/frontend/vite.runner.d.ts +1 -0
  42. package/dist/build/frontend/vite.runner.js +120 -0
  43. package/dist/build/index.d.ts +9 -16
  44. package/dist/build/index.js +8 -16
  45. package/dist/build/materialize.d.ts +4 -0
  46. package/dist/build/materialize.js +10 -0
  47. package/dist/build/metafile.d.ts +5 -0
  48. package/dist/build/metafile.js +14 -0
  49. package/dist/build/qualification.d.ts +5 -0
  50. package/dist/build/qualification.js +7 -0
  51. package/dist/build/roots.d.ts +2 -0
  52. package/dist/build/roots.js +34 -0
  53. package/dist/build/worker/artifact.d.ts +40 -0
  54. package/dist/build/worker/artifact.js +1 -0
  55. package/dist/build/worker/artifact.load.d.ts +3 -0
  56. package/dist/build/worker/artifact.load.js +308 -0
  57. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  58. package/dist/build/worker/artifact.manifest.js +87 -0
  59. package/dist/build/worker/artifact.paths.d.ts +4 -0
  60. package/dist/build/worker/artifact.paths.js +25 -0
  61. package/dist/build/worker/codegen/entry.d.ts +2 -0
  62. package/dist/build/worker/codegen/entry.js +28 -0
  63. package/dist/build/worker/codegen/service.d.ts +19 -0
  64. package/dist/build/worker/codegen/service.js +53 -0
  65. package/dist/build/worker/execution.prepare.d.ts +19 -0
  66. package/dist/build/worker/execution.prepare.js +115 -0
  67. package/dist/build/worker/index.d.ts +2 -0
  68. package/dist/build/worker/index.js +1 -0
  69. package/dist/build/worker/modules.d.ts +11 -0
  70. package/dist/build/worker/modules.js +26 -0
  71. package/dist/build/worker/runtime.d.ts +10 -0
  72. package/dist/build/worker/runtime.js +35 -0
  73. package/dist/build/worker/specifier.d.ts +1 -0
  74. package/dist/build/worker/specifier.js +5 -0
  75. package/dist/cloudflare.d.ts +1 -45
  76. package/dist/cloudflare.js +1 -202
  77. package/dist/deploy/deploy.d.ts +17 -0
  78. package/dist/deploy/deploy.js +221 -0
  79. package/dist/deploy/index.d.ts +1 -0
  80. package/dist/deploy/index.js +1 -0
  81. package/dist/deploy/output.d.ts +2 -0
  82. package/dist/deploy/output.js +10 -0
  83. package/dist/deploy/readiness.d.ts +5 -0
  84. package/dist/deploy/readiness.js +18 -0
  85. package/dist/identity/binding.d.ts +2 -0
  86. package/dist/identity/binding.js +2 -0
  87. package/dist/identity/index.d.ts +3 -0
  88. package/dist/identity/index.js +3 -0
  89. package/dist/identity/material.d.ts +7 -0
  90. package/dist/identity/material.js +42 -0
  91. package/dist/identity/secret.d.ts +3 -0
  92. package/dist/identity/secret.js +7 -0
  93. package/dist/identity/source.d.ts +26 -0
  94. package/dist/identity/source.js +127 -0
  95. package/dist/index.d.ts +10 -9
  96. package/dist/index.js +10 -9
  97. package/dist/params.d.ts +40 -36
  98. package/dist/params.js +0 -1
  99. package/dist/release/deployment.d.ts +44 -0
  100. package/dist/release/deployment.js +141 -0
  101. package/dist/release/index.d.ts +2 -0
  102. package/dist/release/index.js +2 -0
  103. package/dist/release/staged-worker-name.d.ts +3 -0
  104. package/dist/release/staged-worker-name.js +10 -0
  105. package/dist/worker/entry.d.ts +48 -0
  106. package/dist/worker/entry.js +157 -0
  107. package/dist/worker/fetch.d.ts +0 -1
  108. package/dist/worker/fetch.js +0 -1
  109. package/dist/worker/frontend.d.ts +4 -0
  110. package/dist/worker/frontend.js +46 -0
  111. package/dist/worker/identity.d.ts +7 -0
  112. package/dist/worker/identity.js +23 -0
  113. package/dist/worker/index.d.ts +2 -61
  114. package/dist/worker/index.js +2 -218
  115. package/dist/wrangler/bundle.d.ts +10 -0
  116. package/dist/wrangler/bundle.js +28 -0
  117. package/dist/wrangler/deploy.d.ts +24 -0
  118. package/dist/wrangler/deploy.js +44 -0
  119. package/dist/wrangler/index.d.ts +5 -0
  120. package/dist/wrangler/index.js +5 -0
  121. package/dist/wrangler/process.d.ts +15 -0
  122. package/dist/wrangler/process.js +102 -0
  123. package/dist/wrangler/rollout.d.ts +27 -0
  124. package/dist/wrangler/rollout.js +75 -0
  125. package/dist/wrangler/secrets.d.ts +2 -0
  126. package/dist/wrangler/secrets.js +24 -0
  127. package/dist/wrangler/status.d.ts +18 -0
  128. package/dist/wrangler/status.js +75 -0
  129. package/dist/wrangler/triggers.d.ts +5 -0
  130. package/dist/wrangler/triggers.js +12 -0
  131. package/dist/wrangler/type-generation.d.ts +6 -0
  132. package/dist/wrangler/type-generation.js +19 -0
  133. package/dist/wrangler/versions.d.ts +13 -0
  134. package/dist/wrangler/versions.js +38 -0
  135. package/package.json +34 -19
  136. package/template/astrale.config.ts +30 -31
  137. package/dist/build/codegen/identity.d.ts +0 -10
  138. package/dist/build/codegen/identity.d.ts.map +0 -1
  139. package/dist/build/codegen/identity.js +0 -32
  140. package/dist/build/codegen/identity.js.map +0 -1
  141. package/dist/build/codegen/merge.d.ts.map +0 -1
  142. package/dist/build/codegen/merge.js.map +0 -1
  143. package/dist/build/codegen/worker.d.ts +0 -12
  144. package/dist/build/codegen/worker.d.ts.map +0 -1
  145. package/dist/build/codegen/worker.js +0 -40
  146. package/dist/build/codegen/worker.js.map +0 -1
  147. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  148. package/dist/build/codegen/wrangler.js.map +0 -1
  149. package/dist/build/frontend.d.ts +0 -27
  150. package/dist/build/frontend.d.ts.map +0 -1
  151. package/dist/build/frontend.js +0 -252
  152. package/dist/build/frontend.js.map +0 -1
  153. package/dist/build/index.d.ts.map +0 -1
  154. package/dist/build/index.js.map +0 -1
  155. package/dist/build/parse-output.d.ts +0 -27
  156. package/dist/build/parse-output.d.ts.map +0 -1
  157. package/dist/build/parse-output.js +0 -58
  158. package/dist/build/parse-output.js.map +0 -1
  159. package/dist/build/prepare.d.ts +0 -18
  160. package/dist/build/prepare.d.ts.map +0 -1
  161. package/dist/build/prepare.js +0 -138
  162. package/dist/build/prepare.js.map +0 -1
  163. package/dist/build/worker.d.ts +0 -15
  164. package/dist/build/worker.d.ts.map +0 -1
  165. package/dist/build/worker.js +0 -21
  166. package/dist/build/worker.js.map +0 -1
  167. package/dist/build/wrangler-cli.d.ts +0 -88
  168. package/dist/build/wrangler-cli.d.ts.map +0 -1
  169. package/dist/build/wrangler-cli.js +0 -283
  170. package/dist/build/wrangler-cli.js.map +0 -1
  171. package/dist/cloudflare.d.ts.map +0 -1
  172. package/dist/cloudflare.js.map +0 -1
  173. package/dist/index.d.ts.map +0 -1
  174. package/dist/index.js.map +0 -1
  175. package/dist/params.d.ts.map +0 -1
  176. package/dist/params.js.map +0 -1
  177. package/dist/worker/fetch.d.ts.map +0 -1
  178. package/dist/worker/fetch.js.map +0 -1
  179. package/dist/worker/index.d.ts.map +0 -1
  180. package/dist/worker/index.js.map +0 -1
  181. package/src/build/codegen/identity.ts +0 -40
  182. package/src/build/codegen/merge.ts +0 -92
  183. package/src/build/codegen/worker.ts +0 -53
  184. package/src/build/codegen/wrangler.ts +0 -128
  185. package/src/build/frontend.ts +0 -298
  186. package/src/build/index.ts +0 -29
  187. package/src/build/parse-output.ts +0 -60
  188. package/src/build/prepare.ts +0 -189
  189. package/src/build/worker.ts +0 -37
  190. package/src/build/wrangler-cli.ts +0 -372
  191. package/src/cloudflare.ts +0 -275
  192. package/src/index.ts +0 -21
  193. package/src/params.ts +0 -92
  194. package/src/worker/fetch.ts +0 -46
  195. package/src/worker/index.ts +0 -332
  196. package/src/worker/tsconfig.json +0 -10
@@ -1,61 +1,2 @@
1
- import type { schema } from '@astrale-os/kernel-dsl/v1';
2
- import type { DomainDefinition } from '@astrale-os/sdk';
3
- import type { Application, RemoteDeploymentInput, RemoteIdentityInput } from '@astrale-os/sdk/server';
4
- import type { ExecutionContext } from 'hono';
5
- /** SDK-owned Publication manifest passed through without adapter interpretation. */
6
- export type CloudflareManifest = NonNullable<RemoteDeploymentInput['manifest']>;
7
- export interface Fetcher {
8
- fetch(request: Request): Response | Promise<Response>;
9
- }
10
- /** Adapter-owned bindings generated into every Cloudflare deployment. */
11
- export interface CloudflareAdapterBindings {
12
- /** Native Cloudflare deployment metadata (direct Wrangler deployments). */
13
- readonly ASTRALE_VERSION_METADATA?: {
14
- readonly id: string;
15
- readonly tag: string;
16
- readonly timestamp: string;
17
- };
18
- /** Content-derived generation supplied by providers without version metadata. */
19
- readonly ASTRALE_DEPLOYMENT_VERSION?: string;
20
- readonly WORKER_URL?: string;
21
- readonly ASSETS?: Fetcher;
22
- readonly SELF?: Fetcher;
23
- readonly FRONTEND_DEV_URL?: string;
24
- readonly DISPATCHER?: {
25
- get(name: string): Fetcher;
26
- };
27
- }
28
- interface CloudflareWorkerSharedConfig<Bindings extends CloudflareAdapterBindings, Environment> {
29
- readonly privateKey: RemoteIdentityInput['privateKey'] | ((bindings: Bindings) => RemoteIdentityInput['privateKey']);
30
- /** Compose provider bindings into the exact Domain environment once per deployment generation. */
31
- readonly resolveEnvironment?: (bindings: Bindings) => Environment | Promise<Environment>;
32
- readonly manifest?: CloudflareManifest;
33
- /** Maximum time allowed for one Domain invocation. Defaults to 30 seconds. */
34
- readonly invocationTimeoutMs?: number;
35
- readonly resolveUrl?: (bindings: Bindings, requestOrigin: string) => string;
36
- readonly selfBinding?: (bindings: Bindings) => Fetcher | null | undefined;
37
- readonly routeSubrequest?: (url: URL, bindings: Bindings) => Fetcher | null | undefined;
38
- }
39
- /**
40
- * Resolve a Domain lazily when its module graph is too expensive for provider startup admission.
41
- * The adapter resolves at most once concurrently and retries after a rejected resolution.
42
- */
43
- export type CloudflareWorkerConfig<S extends schema.DomainSchema, Bindings extends CloudflareAdapterBindings, Environment> = CloudflareWorkerSharedConfig<Bindings, Environment> & ({
44
- readonly definition: DomainDefinition<S>;
45
- readonly resolveDefinition?: never;
46
- } | {
47
- readonly definition?: never;
48
- readonly resolveDefinition: (bindings: Bindings) => DomainDefinition<S> | Promise<DomainDefinition<S>>;
49
- });
50
- export interface CloudflareWorker<Environment> {
51
- fetch(request: Request, environment: Environment, executionContext?: ExecutionContext): Response | Promise<Response>;
52
- }
53
- /**
54
- * Compose one Cloudflare module Worker without request-global mutation.
55
- * Environment bindings are captured once per immutable serving URL/version generation.
56
- */
57
- export declare function cloudflareWorkerEntry<Bindings extends CloudflareAdapterBindings, Environment = Bindings>(): <const S extends schema.DomainSchema>(config: CloudflareWorkerConfig<S, Bindings, Environment>) => CloudflareWorker<Bindings>;
58
- /** Close an evicted Application before its environment bindings leave the cache. */
59
- export declare function closeEvictedApplication(application: Pick<Application, 'close'>, now?: () => number): Promise<void>;
60
- export {};
61
- //# sourceMappingURL=index.d.ts.map
1
+ export * from './entry.js';
2
+ export { decodeSigningIdentity, runtimeBindings } from './identity.js';
@@ -1,218 +1,2 @@
1
- import { issuer } from '@astrale-os/kernel-core/auth';
2
- import { createRemoteApplication } from '@astrale-os/sdk/server';
3
- import { Hono as HonoApplication } from 'hono';
4
- import { dispatchCloudflareRequest, prepareCloudflareRequest } from './fetch.js';
5
- const MAXIMUM_CACHED_APPLICATIONS = 4;
6
- const APPLICATION_CLOSE_TIMEOUT_MS = 5_000;
7
- /**
8
- * Compose one Cloudflare module Worker without request-global mutation.
9
- * Environment bindings are captured once per immutable serving URL/version generation.
10
- */
11
- export function cloudflareWorkerEntry() {
12
- return function (config) {
13
- const applications = new Map();
14
- let resolvedDefinition = config.definition;
15
- let pendingDefinition;
16
- const definitionFor = async (bindings) => {
17
- if (resolvedDefinition !== undefined)
18
- return resolvedDefinition;
19
- if (pendingDefinition !== undefined)
20
- return pendingDefinition;
21
- const resolve = config.resolveDefinition;
22
- if (resolve === undefined)
23
- throw new TypeError('Cloudflare Worker Domain definition is unavailable.');
24
- const current = Promise.resolve().then(() => resolve(bindings));
25
- pendingDefinition = current;
26
- try {
27
- const definition = await current;
28
- resolvedDefinition = definition;
29
- return definition;
30
- }
31
- finally {
32
- if (pendingDefinition === current)
33
- pendingDefinition = undefined;
34
- }
35
- };
36
- const applicationFor = async (url, bindings, definition) => {
37
- const version = deploymentVersion(bindings);
38
- const key = `${url}\u0000${version}`;
39
- const cached = applications.get(key);
40
- if (cached !== undefined)
41
- return cached;
42
- const environment = config.resolveEnvironment === undefined
43
- ? bindings
44
- : await config.resolveEnvironment(bindings);
45
- const routedFetch = async (input, init) => {
46
- const { request, rejectRedirect } = prepareCloudflareRequest(input, init);
47
- const destination = new URL(request.url);
48
- if (destination.origin === new URL(url).origin) {
49
- const self = config.selfBinding?.(bindings);
50
- return dispatchCloudflareRequest(request, rejectRedirect, (transportRequest) => self !== null && self !== undefined
51
- ? self.fetch(transportRequest)
52
- : serving.app.fetch(transportRequest, environment));
53
- }
54
- const route = config.routeSubrequest?.(destination, bindings);
55
- return dispatchCloudflareRequest(request, rejectRedirect, (transportRequest) => route === null || route === undefined
56
- ? fetch(transportRequest)
57
- : route.fetch(transportRequest));
58
- };
59
- const deploymentQuery = `deployment=${encodeURIComponent(version)}`;
60
- const issuerId = issuer.accept(url);
61
- const host = new HonoApplication();
62
- const serving = createRemoteApplication({
63
- definition,
64
- environment,
65
- identity: {
66
- issuer: issuerId,
67
- subject: definition.schema.origin,
68
- privateKey: typeof config.privateKey === 'function'
69
- ? config.privateKey(bindings)
70
- : config.privateKey,
71
- fetch: routedFetch,
72
- ...(new URL(url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
73
- },
74
- deployment: {
75
- bundleHref: `${url}/.well-known/astrale/domain.bundle.json?${deploymentQuery}`,
76
- endpoint: { http: `${url}/invoke` },
77
- ...(definition.frontend === undefined
78
- ? {}
79
- : { frontendBaseHref: url, frontendCacheKey: version }),
80
- ...(config.manifest === undefined ? {} : { manifest: config.manifest }),
81
- delivery: {
82
- href: `${url}/install`,
83
- method: 'POST',
84
- authentication: { kind: 'public' },
85
- },
86
- },
87
- app: host,
88
- server: {
89
- path: '/invoke',
90
- health: { probe: { check: () => true } },
91
- limits: {
92
- maximumRequestBytes: 1024 * 1024,
93
- maximumPublicationBytes: 1024 * 1024,
94
- maximumBundleBytes: 8 * 1024 * 1024,
95
- maximumDeliveryBytes: 8 * 1024 * 1024,
96
- invocationTimeoutMs: config.invocationTimeoutMs ?? 30_000,
97
- },
98
- },
99
- });
100
- const created = Object.freeze({
101
- origin: new URL(url).origin,
102
- application: serving,
103
- environment,
104
- });
105
- if (applications.size >= MAXIMUM_CACHED_APPLICATIONS) {
106
- const oldest = applications.keys().next().value;
107
- if (oldest !== undefined) {
108
- const evicted = applications.get(oldest);
109
- applications.delete(oldest);
110
- if (evicted !== undefined) {
111
- try {
112
- await closeEvictedApplication(evicted.application);
113
- }
114
- catch (cause) {
115
- await serving
116
- .close({ deadline: Date.now() + APPLICATION_CLOSE_TIMEOUT_MS })
117
- .catch(() => undefined);
118
- throw cause;
119
- }
120
- }
121
- }
122
- }
123
- applications.set(key, created);
124
- return created;
125
- };
126
- return Object.freeze({
127
- async fetch(request, bindings, executionContext) {
128
- const requestUrl = new URL(request.url);
129
- const definition = await definitionFor(bindings);
130
- if (definition.frontend !== undefined) {
131
- const asset = await frontendAsset(definition, bindings, requestUrl, request);
132
- if (asset !== undefined)
133
- return asset;
134
- }
135
- const publicUrl = canonicalServingUrl(config.resolveUrl?.(bindings, requestOrigin(requestUrl, request)) ??
136
- bindings.WORKER_URL ??
137
- requestOrigin(requestUrl, request));
138
- const serving = await applicationFor(publicUrl, bindings, definition);
139
- return serving.application.app.fetch(request, serving.environment, executionContext);
140
- },
141
- });
142
- };
143
- }
144
- /** Close an evicted Application before its environment bindings leave the cache. */
145
- export function closeEvictedApplication(application, now = Date.now) {
146
- return application.close({ deadline: now() + APPLICATION_CLOSE_TIMEOUT_MS });
147
- }
148
- async function frontendAsset(definition, environment, url, request) {
149
- const frontend = definition.frontend;
150
- if (frontend === undefined || frontend.source.kind === 'external')
151
- return undefined;
152
- if (isReservedServicePath(url.pathname))
153
- return undefined;
154
- if (request.method !== 'GET' && request.method !== 'HEAD')
155
- return undefined;
156
- const dev = environment.FRONTEND_DEV_URL?.replace(/\/+$/u, '');
157
- if (dev !== undefined && dev.length > 0) {
158
- return fetch(new Request(new URL(`${url.pathname}${url.search}`, `${dev}/`), request));
159
- }
160
- const routes = frontend.routes;
161
- const spa = frontend.source.kind === 'vite' && frontend.source.spa;
162
- const route = Object.values(routes)
163
- .filter((candidate) => candidate.path === url.pathname ||
164
- (spa && (candidate.path === '/' || url.pathname.startsWith(`${candidate.path}/`))))
165
- .sort((left, right) => right.path.length - left.path.length)[0];
166
- const staticAsset = url.pathname.startsWith('/assets/');
167
- if (route === undefined && !staticAsset)
168
- return undefined;
169
- const binding = environment.ASSETS;
170
- if (binding === undefined)
171
- return undefined;
172
- const asset = await binding.fetch(request);
173
- if (asset.status !== 404 || route === undefined)
174
- return asset;
175
- await asset.body?.cancel();
176
- return binding.fetch(new Request(new URL(`/${route.document}${url.search}`, url.origin), request));
177
- }
178
- function isReservedServicePath(path) {
179
- return ['/invoke', '/install', '/health', '/meta', '/.well-known'].some((reserved) => path === reserved || path.startsWith(`${reserved}/`));
180
- }
181
- function requestOrigin(url, request) {
182
- if (url.protocol !== 'http:')
183
- return url.origin;
184
- const forwarded = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim().toLowerCase();
185
- return forwarded === 'https' ? `https://${url.host}` : url.origin;
186
- }
187
- function canonicalServingUrl(input) {
188
- let url;
189
- try {
190
- url = new URL(input);
191
- }
192
- catch (cause) {
193
- throw new TypeError('Cloudflare Worker serving URL is invalid.', { cause });
194
- }
195
- if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
196
- url.username !== '' ||
197
- url.password !== '' ||
198
- url.pathname !== '/' ||
199
- url.search !== '' ||
200
- url.hash !== '') {
201
- throw new TypeError('Cloudflare Worker serving URL must be one HTTP(S) origin.');
202
- }
203
- return url.origin;
204
- }
205
- function deploymentVersion(environment) {
206
- const input = environment.ASTRALE_VERSION_METADATA;
207
- if (input !== null &&
208
- typeof input === 'object' &&
209
- typeof input.id === 'string' &&
210
- input.id.length > 0) {
211
- return input.id;
212
- }
213
- const fallback = environment.ASTRALE_DEPLOYMENT_VERSION;
214
- if (typeof fallback === 'string' && fallback.length > 0)
215
- return fallback;
216
- throw new TypeError('Cloudflare Worker deployment version is unavailable.');
217
- }
218
- //# sourceMappingURL=index.js.map
1
+ export * from './entry.js';
2
+ export { decodeSigningIdentity, runtimeBindings } from './identity.js';
@@ -0,0 +1,10 @@
1
+ export declare function runWranglerBundle(args: {
2
+ projectDir: string;
3
+ configPath: string;
4
+ outDir: string;
5
+ onLog?: (line: string) => void;
6
+ signal?: AbortSignal;
7
+ }): Promise<{
8
+ readonly bundlePath: string;
9
+ readonly metafilePath: string;
10
+ }>;
@@ -0,0 +1,28 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { runCapture, wranglerCommand } from './process.js';
4
+ export async function runWranglerBundle(args) {
5
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
6
+ const { code, out } = await runCapture(cmd, [
7
+ ...prefix,
8
+ 'deploy',
9
+ '--dry-run',
10
+ '--minify',
11
+ '--outdir',
12
+ args.outDir,
13
+ '--metafile',
14
+ '--config',
15
+ args.configPath,
16
+ ], args.projectDir, args.onLog, args.signal);
17
+ if (code !== 0)
18
+ throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`);
19
+ const javascript = readdirSync(args.outDir).filter((file) => file.endsWith('.js'));
20
+ if (javascript.length === 0) {
21
+ throw new Error(`wrangler bundle produced no .js in ${args.outDir}`);
22
+ }
23
+ const preferred = javascript.find((file) => file.startsWith('worker')) ?? javascript[0];
24
+ return Object.freeze({
25
+ bundlePath: join(args.outDir, preferred),
26
+ metafilePath: join(args.outDir, 'bundle-meta.json'),
27
+ });
28
+ }
@@ -0,0 +1,24 @@
1
+ export interface DeployArgs {
2
+ configPath: string;
3
+ secrets?: Readonly<Record<string, string>>;
4
+ route?: string;
5
+ onLog?: (line: string) => void;
6
+ signal?: AbortSignal;
7
+ }
8
+ export declare class WranglerDeployError extends Error {
9
+ readonly applied?: {
10
+ readonly url?: string;
11
+ readonly deploymentVersion?: string;
12
+ };
13
+ constructor(message: string, applied?: {
14
+ readonly url?: string;
15
+ readonly deploymentVersion?: string;
16
+ });
17
+ }
18
+ export declare function runWranglerDeploy(args: DeployArgs & {
19
+ projectDir: string;
20
+ }): Promise<{
21
+ url?: string;
22
+ raw: string;
23
+ deploymentVersion: string;
24
+ }>;
@@ -0,0 +1,44 @@
1
+ import { parseDeployUrl, parseDeployVersionId } from '../deploy/output.js';
2
+ import { runCapture, wranglerCommand } from './process.js';
3
+ import { secretRedactor, withSecretsFile } from './secrets.js';
4
+ import { runWranglerTypes } from './type-generation.js';
5
+ export class WranglerDeployError extends Error {
6
+ applied;
7
+ constructor(message, applied) {
8
+ super(message);
9
+ this.name = 'WranglerDeployError';
10
+ if (applied !== undefined)
11
+ this.applied = Object.freeze({ ...applied });
12
+ }
13
+ }
14
+ export async function runWranglerDeploy(args) {
15
+ await runWranglerTypes(args);
16
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
17
+ const secrets = args.secrets ?? {};
18
+ const execute = (secretsFile) => runCapture(cmd, [
19
+ ...prefix,
20
+ 'deploy',
21
+ '--config',
22
+ args.configPath,
23
+ ...(secretsFile === undefined ? [] : ['--secrets-file', secretsFile]),
24
+ ], args.projectDir, args.onLog, args.signal, secretRedactor(secrets));
25
+ const { code, out } = await withSecretsFile(secrets, execute);
26
+ const url = parseDeployUrl(out, args.route);
27
+ const deploymentVersion = parseDeployVersionId(out);
28
+ if (code !== 0) {
29
+ throw new WranglerDeployError(`wrangler deploy failed (code ${code}):\n${out.slice(-2000)}`, url === undefined && deploymentVersion === undefined
30
+ ? undefined
31
+ : {
32
+ ...(url === undefined ? {} : { url }),
33
+ ...(deploymentVersion === undefined ? {} : { deploymentVersion }),
34
+ });
35
+ }
36
+ if (deploymentVersion === undefined) {
37
+ throw new WranglerDeployError('wrangler deploy did not report its Version ID.', url === undefined ? undefined : { url });
38
+ }
39
+ return Object.freeze({
40
+ raw: out,
41
+ ...(url === undefined ? {} : { url }),
42
+ deploymentVersion,
43
+ });
44
+ }
@@ -0,0 +1,5 @@
1
+ export { runWranglerBundle } from './bundle.js';
2
+ export { runWranglerDeploy, WranglerDeployError, type DeployArgs } from './deploy.js';
3
+ export { wranglerProcessEnv } from './process.js';
4
+ export { admitWranglerDeploymentStatus, runWranglerDeploymentStatus, type WranglerDeploymentStatus, } from './status.js';
5
+ export { runWranglerTypes } from './type-generation.js';
@@ -0,0 +1,5 @@
1
+ export { runWranglerBundle } from './bundle.js';
2
+ export { runWranglerDeploy, WranglerDeployError } from './deploy.js';
3
+ export { wranglerProcessEnv } from './process.js';
4
+ export { admitWranglerDeploymentStatus, runWranglerDeploymentStatus, } from './status.js';
5
+ export { runWranglerTypes } from './type-generation.js';
@@ -0,0 +1,15 @@
1
+ export declare function wranglerCommand(projectDir: string, runtime?: {
2
+ readonly executable: string;
3
+ readonly bun: boolean;
4
+ }): {
5
+ cmd: string;
6
+ prefix: string[];
7
+ };
8
+ /** Keep Wrangler CLI diagnostic logs inside the generated project state. */
9
+ export declare function wranglerProcessEnv(projectDir: string): NodeJS.ProcessEnv;
10
+ export declare function runCapture(cmd: string, args: string[], cwd: string, onLog?: (line: string) => void, signal?: AbortSignal, redact?: (input: string) => string): Promise<{
11
+ code: number;
12
+ out: string;
13
+ stdout: string;
14
+ stderr: string;
15
+ }>;
@@ -0,0 +1,102 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ export function wranglerCommand(projectDir, runtime = {
6
+ executable: process.execPath,
7
+ bun: process.versions.bun !== undefined,
8
+ }) {
9
+ const local = join(projectDir, 'node_modules', '.bin', 'wrangler');
10
+ if (existsSync(local))
11
+ return { cmd: local, prefix: [] };
12
+ const require = createRequire(import.meta.url);
13
+ const packagePath = require.resolve('wrangler/package.json');
14
+ const manifest = JSON.parse(readFileSync(packagePath, 'utf8'));
15
+ const executable = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.wrangler;
16
+ if (!executable)
17
+ throw new TypeError('Installed Wrangler package has no wrangler executable.');
18
+ return {
19
+ cmd: runtime.bun ? 'node' : runtime.executable,
20
+ prefix: [resolve(dirname(packagePath), executable)],
21
+ };
22
+ }
23
+ /** Keep Wrangler CLI diagnostic logs inside the generated project state. */
24
+ export function wranglerProcessEnv(projectDir) {
25
+ return {
26
+ ...process.env,
27
+ WRANGLER_SEND_METRICS: 'false',
28
+ WRANGLER_LOG_PATH: process.env.WRANGLER_LOG_PATH || resolve(projectDir, '.astrale', 'logs', 'wrangler'),
29
+ };
30
+ }
31
+ export function runCapture(cmd, args, cwd, onLog, signal, redact = (input) => input) {
32
+ if (signal?.aborted)
33
+ return Promise.reject(new Error('Wrangler process cancelled.'));
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn(cmd, args, {
36
+ cwd,
37
+ stdio: ['ignore', 'pipe', 'pipe'],
38
+ env: wranglerProcessEnv(cwd),
39
+ });
40
+ let out = '';
41
+ let stdout = '';
42
+ let stderr = '';
43
+ let outputBytes = 0;
44
+ let terminating;
45
+ const timeout = setTimeout(() => terminate(new Error('Wrangler process timed out.')), 300_000);
46
+ const abort = () => terminate(new Error('Wrangler process cancelled.'));
47
+ signal?.addEventListener('abort', abort, { once: true });
48
+ let escalation;
49
+ const cleanup = () => {
50
+ clearTimeout(timeout);
51
+ if (escalation !== undefined)
52
+ clearTimeout(escalation);
53
+ signal?.removeEventListener('abort', abort);
54
+ };
55
+ function terminate(cause) {
56
+ if (terminating !== undefined)
57
+ return;
58
+ terminating = cause;
59
+ child.kill('SIGTERM');
60
+ escalation = setTimeout(() => {
61
+ if (child.exitCode === null && child.signalCode === null)
62
+ child.kill('SIGKILL');
63
+ }, 1_000);
64
+ }
65
+ const onData = (buffer, stream) => {
66
+ outputBytes += buffer.byteLength;
67
+ if (outputBytes > 1024 * 1024) {
68
+ terminate(new Error('Wrangler process exceeded its output limit.'));
69
+ return;
70
+ }
71
+ const chunk = buffer.toString();
72
+ out += chunk;
73
+ if (stream === 'stdout')
74
+ stdout += chunk;
75
+ else
76
+ stderr += chunk;
77
+ };
78
+ child.stdout?.on('data', (buffer) => onData(buffer, 'stdout'));
79
+ child.stderr?.on('data', (buffer) => onData(buffer, 'stderr'));
80
+ child.on('exit', (code) => {
81
+ cleanup();
82
+ if (terminating !== undefined)
83
+ reject(terminating);
84
+ else {
85
+ const safe = redact(out);
86
+ for (const line of safe.split('\n'))
87
+ if (line.trim())
88
+ onLog?.(line);
89
+ resolve({
90
+ code: code ?? 1,
91
+ out: safe,
92
+ stdout: redact(stdout),
93
+ stderr: redact(stderr),
94
+ });
95
+ }
96
+ });
97
+ child.on('error', (cause) => {
98
+ cleanup();
99
+ reject(terminating ?? cause);
100
+ });
101
+ });
102
+ }
@@ -0,0 +1,27 @@
1
+ import { runWranglerDeploy, WranglerDeployError, type DeployArgs } from './deploy.js';
2
+ import { observeWranglerDeployment, runWranglerDeploymentStatus } from './status.js';
3
+ import { deployWranglerTriggers } from './triggers.js';
4
+ import { deployWranglerVersions, uploadWranglerVersion } from './versions.js';
5
+ export interface WorkerCandidate {
6
+ readonly deploymentVersion: string;
7
+ readonly url?: string;
8
+ }
9
+ export declare class WranglerRolloutError extends WranglerDeployError {
10
+ readonly phase: 'provider' | 'readiness';
11
+ constructor(phase: 'provider' | 'readiness', cause: unknown, candidate?: WorkerCandidate);
12
+ }
13
+ declare const operations: {
14
+ observe: typeof observeWranglerDeployment;
15
+ status: typeof runWranglerDeploymentStatus;
16
+ initial: typeof runWranglerDeploy;
17
+ upload: typeof uploadWranglerVersion;
18
+ triggers: typeof deployWranglerTriggers;
19
+ traffic: typeof deployWranglerVersions;
20
+ };
21
+ /** HTTP candidates receive no normal traffic until the caller proves their exact readiness. */
22
+ export declare function runWranglerRollout(args: DeployArgs & {
23
+ readonly projectDir: string;
24
+ readonly workerName: string;
25
+ readonly verifyCandidate: (candidate: WorkerCandidate) => Promise<void>;
26
+ }, dependencies?: typeof operations): Promise<Awaited<ReturnType<typeof runWranglerDeploy>>>;
27
+ export {};
@@ -0,0 +1,75 @@
1
+ import { runWranglerDeploy, WranglerDeployError } from './deploy.js';
2
+ import { observeWranglerDeployment, runWranglerDeploymentStatus, } from './status.js';
3
+ import { deployWranglerTriggers } from './triggers.js';
4
+ import { deployWranglerVersions, uploadWranglerVersion } from './versions.js';
5
+ export class WranglerRolloutError extends WranglerDeployError {
6
+ phase;
7
+ constructor(phase, cause, candidate) {
8
+ super(`Cloudflare Worker rollout failed during ${phase}: ${cause instanceof Error ? cause.message : 'unexpected provider failure'}`, candidate);
9
+ this.phase = phase;
10
+ this.name = 'WranglerRolloutError';
11
+ this.cause = cause;
12
+ }
13
+ }
14
+ const operations = {
15
+ observe: observeWranglerDeployment,
16
+ status: runWranglerDeploymentStatus,
17
+ initial: runWranglerDeploy,
18
+ upload: uploadWranglerVersion,
19
+ triggers: deployWranglerTriggers,
20
+ traffic: deployWranglerVersions,
21
+ };
22
+ /** HTTP candidates receive no normal traffic until the caller proves their exact readiness. */
23
+ export async function runWranglerRollout(args, dependencies = operations) {
24
+ const previous = await dependencies.observe(args);
25
+ if (previous === undefined)
26
+ return dependencies.initial(args);
27
+ const serving = previous.versions.find(({ percentage }) => percentage === 100);
28
+ if (serving === undefined) {
29
+ throw new Error('Cloudflare staging requires one serving version at 100%; coordinate an existing gradual rollout first.');
30
+ }
31
+ let candidate;
32
+ let phase = 'provider';
33
+ try {
34
+ const deploymentVersion = await dependencies.upload(args);
35
+ candidate = { deploymentVersion };
36
+ requireUnchanged(await dependencies.status(args), previous);
37
+ const url = await dependencies.triggers(args);
38
+ candidate = { deploymentVersion, ...(url === undefined ? {} : { url }) };
39
+ requireUnchanged(await dependencies.status(args), previous);
40
+ const versions = [
41
+ { version: serving.version, percentage: 100 },
42
+ { version: deploymentVersion, percentage: 0 },
43
+ ];
44
+ await dependencies.traffic({ ...args, versions });
45
+ const staged = await dependencies.status(args);
46
+ requireTraffic(staged, versions);
47
+ phase = 'readiness';
48
+ await args.verifyCandidate(candidate);
49
+ phase = 'provider';
50
+ requireUnchanged(await dependencies.status(args), staged);
51
+ await dependencies.traffic({
52
+ ...args,
53
+ versions: [{ version: deploymentVersion, percentage: 100 }],
54
+ });
55
+ return { ...candidate, raw: '' };
56
+ }
57
+ catch (cause) {
58
+ // A failed/cancelled probe leaves the previous version at 100%. Do not perform an unguarded
59
+ // rollback after an unknown provider outcome or overwrite another operator's deployment.
60
+ if (candidate === undefined && cause instanceof WranglerDeployError)
61
+ throw cause;
62
+ throw new WranglerRolloutError(phase, cause, candidate);
63
+ }
64
+ }
65
+ function requireUnchanged(actual, expected) {
66
+ if (actual.deployment !== expected.deployment)
67
+ throw new Error('Cloudflare deployment changed during staging; refusing to overwrite it.');
68
+ requireTraffic(actual, expected.versions);
69
+ }
70
+ function requireTraffic(actual, expected) {
71
+ if (actual.versions.length !== expected.length ||
72
+ expected.some((entry) => !actual.versions.some(({ version, percentage }) => version === entry.version && percentage === entry.percentage))) {
73
+ throw new Error('Cloudflare deployment differs from the expected staged traffic allocation.');
74
+ }
75
+ }
@@ -0,0 +1,2 @@
1
+ export declare function withSecretsFile<Result>(secrets: Readonly<Record<string, string>>, operation: (path?: string) => Promise<Result>): Promise<Result>;
2
+ export declare function secretRedactor(secrets: Readonly<Record<string, string>>): (input: string) => string;
@@ -0,0 +1,24 @@
1
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export async function withSecretsFile(secrets, operation) {
5
+ if (Object.keys(secrets).length === 0)
6
+ return operation();
7
+ const directory = await mkdtemp(join(tmpdir(), 'astrale-secrets-'));
8
+ const path = join(directory, 'secrets.json');
9
+ try {
10
+ await writeFile(path, JSON.stringify(secrets), { mode: 0o600 });
11
+ return await operation(path);
12
+ }
13
+ finally {
14
+ await rm(directory, { recursive: true, force: true });
15
+ }
16
+ }
17
+ export function secretRedactor(secrets) {
18
+ const values = Object.values(secrets)
19
+ .filter((value) => value.length > 0)
20
+ .flatMap((value) => [value, JSON.stringify(value).slice(1, -1)])
21
+ .filter((value, index, all) => all.indexOf(value) === index)
22
+ .sort((left, right) => right.length - left.length);
23
+ return (input) => values.reduce((output, value) => output.replaceAll(value, '[REDACTED]'), input);
24
+ }