@astrale-os/adapter-cloudflare 0.5.0-beta.8 → 0.5.0-beta.80
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/CHANGELOG.md +983 -0
- package/README.md +20 -2
- package/dist/adapter.d.ts +15 -0
- package/dist/adapter.js +76 -0
- package/dist/build/artifact.d.ts +7 -0
- package/dist/build/artifact.js +165 -0
- package/dist/build/bundle/admit.d.ts +1 -0
- package/dist/build/bundle/admit.js +9 -0
- package/dist/build/closure.d.ts +2 -0
- package/dist/build/closure.js +22 -0
- package/dist/build/codegen/execution.d.ts +3 -0
- package/dist/build/codegen/execution.js +2 -0
- package/dist/build/codegen/merge.d.ts +0 -1
- package/dist/build/codegen/merge.js +0 -1
- package/dist/build/codegen/wrangler.d.ts +13 -1
- package/dist/build/codegen/wrangler.js +35 -18
- package/dist/build/configuration/addressing.d.ts +2 -0
- package/dist/build/configuration/addressing.js +11 -0
- package/dist/build/configuration/invocation-timeout.d.ts +2 -0
- package/dist/build/configuration/invocation-timeout.js +9 -0
- package/dist/build/configuration/request-limit.d.ts +2 -0
- package/dist/build/configuration/request-limit.js +9 -0
- package/dist/build/configuration/router.d.ts +7 -0
- package/dist/build/configuration/router.js +11 -0
- package/dist/build/configuration/worker-name.d.ts +2 -0
- package/dist/build/configuration/worker-name.js +18 -0
- package/dist/build/configuration.d.ts +5 -0
- package/dist/build/configuration.js +5 -0
- package/dist/build/direct.d.ts +4 -0
- package/dist/build/direct.js +93 -0
- package/dist/build/frontend/files.d.ts +7 -0
- package/dist/build/frontend/files.js +60 -0
- package/dist/build/frontend/routes.d.ts +2 -0
- package/dist/build/frontend/routes.js +11 -0
- package/dist/build/index.d.ts +10 -16
- package/dist/build/index.js +9 -16
- package/dist/build/materialize.d.ts +4 -0
- package/dist/build/materialize.js +10 -0
- package/dist/build/metafile.d.ts +5 -0
- package/dist/build/metafile.js +14 -0
- package/dist/build/qualification.d.ts +5 -0
- package/dist/build/qualification.js +7 -0
- package/dist/build/roots.d.ts +2 -0
- package/dist/build/roots.js +34 -0
- package/dist/build/worker/artifact.d.ts +40 -0
- package/dist/build/worker/artifact.js +1 -0
- package/dist/build/worker/artifact.load.d.ts +3 -0
- package/dist/build/worker/artifact.load.js +308 -0
- package/dist/build/worker/artifact.manifest.d.ts +48 -0
- package/dist/build/worker/artifact.manifest.js +87 -0
- package/dist/build/worker/artifact.paths.d.ts +4 -0
- package/dist/build/worker/artifact.paths.js +25 -0
- package/dist/build/worker/codegen/entry.d.ts +2 -0
- package/dist/build/worker/codegen/entry.js +28 -0
- package/dist/build/worker/codegen/service.d.ts +16 -0
- package/dist/build/worker/codegen/service.js +52 -0
- package/dist/build/worker/execution.prepare.d.ts +18 -0
- package/dist/build/worker/execution.prepare.js +109 -0
- package/dist/build/worker/index.d.ts +2 -0
- package/dist/build/worker/index.js +1 -0
- package/dist/build/worker/modules.d.ts +11 -0
- package/dist/build/worker/modules.js +26 -0
- package/dist/build/worker/runtime.d.ts +10 -0
- package/dist/build/worker/runtime.js +35 -0
- package/dist/build/worker/specifier.d.ts +1 -0
- package/dist/build/worker/specifier.js +5 -0
- package/dist/cloudflare.d.ts +1 -34
- package/dist/cloudflare.js +1 -205
- package/dist/deploy/deploy.d.ts +16 -0
- package/dist/deploy/deploy.js +204 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/deploy/output.d.ts +2 -0
- package/dist/deploy/output.js +10 -0
- package/dist/development/develop.d.ts +15 -0
- package/dist/development/develop.js +267 -0
- package/dist/development/frontend.d.ts +11 -0
- package/dist/development/frontend.js +109 -0
- package/dist/development/host.d.ts +2 -0
- package/dist/development/host.js +46 -0
- package/dist/development/port.d.ts +1 -0
- package/dist/development/port.js +12 -0
- package/dist/development/readiness.d.ts +14 -0
- package/dist/development/readiness.js +71 -0
- package/dist/development/resolved-fetch.d.ts +17 -0
- package/dist/development/resolved-fetch.js +80 -0
- package/dist/development/worker-output.d.ts +2 -0
- package/dist/development/worker-output.js +25 -0
- package/dist/development/worker.d.ts +17 -0
- package/dist/development/worker.js +147 -0
- package/dist/identity/binding.d.ts +2 -0
- package/dist/identity/binding.js +2 -0
- package/dist/identity/material.d.ts +7 -0
- package/dist/identity/material.js +42 -0
- package/dist/identity/secret.d.ts +3 -0
- package/dist/identity/secret.js +7 -0
- package/dist/identity/source.d.ts +26 -0
- package/dist/identity/source.js +127 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/params.d.ts +32 -7
- package/dist/params.js +0 -1
- package/dist/release/deployment.d.ts +44 -0
- package/dist/release/deployment.js +141 -0
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/staged-worker-name.d.ts +3 -0
- package/dist/release/staged-worker-name.js +10 -0
- package/dist/worker/entry.d.ts +43 -0
- package/dist/worker/entry.js +181 -0
- package/dist/worker/fetch.d.ts +0 -1
- package/dist/worker/fetch.js +0 -1
- package/dist/worker/identity.d.ts +7 -0
- package/dist/worker/identity.js +23 -0
- package/dist/worker/index.d.ts +2 -61
- package/dist/worker/index.js +2 -212
- package/dist/wrangler/bundle.d.ts +10 -0
- package/dist/wrangler/bundle.js +27 -0
- package/dist/wrangler/deploy.d.ts +24 -0
- package/dist/wrangler/deploy.js +44 -0
- package/dist/wrangler/index.d.ts +5 -0
- package/dist/wrangler/index.js +5 -0
- package/dist/wrangler/process.d.ts +17 -0
- package/dist/wrangler/process.js +115 -0
- package/dist/wrangler/secrets.d.ts +6 -0
- package/dist/wrangler/secrets.js +58 -0
- package/dist/wrangler/status.d.ts +14 -0
- package/dist/wrangler/status.js +56 -0
- package/dist/wrangler/type-generation.d.ts +6 -0
- package/dist/wrangler/type-generation.js +19 -0
- package/package.json +30 -19
- package/template/astrale.config.ts +25 -17
- package/dist/build/codegen/identity.d.ts +0 -10
- package/dist/build/codegen/identity.d.ts.map +0 -1
- package/dist/build/codegen/identity.js +0 -32
- package/dist/build/codegen/identity.js.map +0 -1
- package/dist/build/codegen/merge.d.ts.map +0 -1
- package/dist/build/codegen/merge.js.map +0 -1
- package/dist/build/codegen/worker.d.ts +0 -12
- package/dist/build/codegen/worker.d.ts.map +0 -1
- package/dist/build/codegen/worker.js +0 -40
- package/dist/build/codegen/worker.js.map +0 -1
- package/dist/build/codegen/wrangler.d.ts.map +0 -1
- package/dist/build/codegen/wrangler.js.map +0 -1
- package/dist/build/frontend.d.ts +0 -26
- package/dist/build/frontend.d.ts.map +0 -1
- package/dist/build/frontend.js +0 -263
- package/dist/build/frontend.js.map +0 -1
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js.map +0 -1
- package/dist/build/parse-output.d.ts +0 -27
- package/dist/build/parse-output.d.ts.map +0 -1
- package/dist/build/parse-output.js +0 -58
- package/dist/build/parse-output.js.map +0 -1
- package/dist/build/prepare.d.ts +0 -18
- package/dist/build/prepare.d.ts.map +0 -1
- package/dist/build/prepare.js +0 -117
- package/dist/build/prepare.js.map +0 -1
- package/dist/build/worker.d.ts +0 -15
- package/dist/build/worker.d.ts.map +0 -1
- package/dist/build/worker.js +0 -21
- package/dist/build/worker.js.map +0 -1
- package/dist/build/wrangler-cli.d.ts +0 -88
- package/dist/build/wrangler-cli.d.ts.map +0 -1
- package/dist/build/wrangler-cli.js +0 -283
- package/dist/build/wrangler-cli.js.map +0 -1
- package/dist/cloudflare.d.ts.map +0 -1
- package/dist/cloudflare.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/params.js.map +0 -1
- package/dist/worker/fetch.d.ts.map +0 -1
- package/dist/worker/fetch.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
- package/src/build/codegen/identity.ts +0 -40
- package/src/build/codegen/merge.ts +0 -92
- package/src/build/codegen/worker.ts +0 -53
- package/src/build/codegen/wrangler.ts +0 -127
- package/src/build/frontend.ts +0 -324
- package/src/build/index.ts +0 -29
- package/src/build/parse-output.ts +0 -60
- package/src/build/prepare.ts +0 -161
- package/src/build/worker.ts +0 -37
- package/src/build/wrangler-cli.ts +0 -372
- package/src/cloudflare.ts +0 -251
- package/src/index.ts +0 -21
- package/src/params.ts +0 -92
- package/src/worker/fetch.ts +0 -46
- package/src/worker/index.ts +0 -325
- package/src/worker/tsconfig.json +0 -10
package/dist/worker/index.js
CHANGED
|
@@ -1,212 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
host.get('/meta', (context) => context.json({
|
|
63
|
-
origin: definition.schema.origin,
|
|
64
|
-
issuer: issuerId,
|
|
65
|
-
schemaRevision: definition.domain.$.revision,
|
|
66
|
-
deploymentVersion: version,
|
|
67
|
-
}));
|
|
68
|
-
const serving = createRemoteApplication({
|
|
69
|
-
definition,
|
|
70
|
-
environment,
|
|
71
|
-
identity: {
|
|
72
|
-
issuer: issuerId,
|
|
73
|
-
subject: definition.schema.origin,
|
|
74
|
-
privateKey: typeof config.privateKey === 'function'
|
|
75
|
-
? config.privateKey(bindings)
|
|
76
|
-
: config.privateKey,
|
|
77
|
-
fetch: routedFetch,
|
|
78
|
-
...(new URL(url).protocol === 'http:' ? { allowInsecureHttp: true } : {}),
|
|
79
|
-
},
|
|
80
|
-
deployment: {
|
|
81
|
-
bundleHref: `${url}/.well-known/astrale/domain.bundle.json?${deploymentQuery}`,
|
|
82
|
-
endpoint: { http: `${url}/invoke` },
|
|
83
|
-
...(definition.frontend === undefined
|
|
84
|
-
? {}
|
|
85
|
-
: { frontendBaseHref: url, frontendCacheKey: version }),
|
|
86
|
-
...(config.manifest === undefined ? {} : { manifest: config.manifest }),
|
|
87
|
-
delivery: {
|
|
88
|
-
href: `${url}/install`,
|
|
89
|
-
method: 'POST',
|
|
90
|
-
authentication: { kind: 'public' },
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
app: host,
|
|
94
|
-
server: {
|
|
95
|
-
path: '/invoke',
|
|
96
|
-
health: { probe: { check: () => true } },
|
|
97
|
-
limits: {
|
|
98
|
-
maximumRequestBytes: 1024 * 1024,
|
|
99
|
-
maximumPublicationBytes: 1024 * 1024,
|
|
100
|
-
maximumBundleBytes: 8 * 1024 * 1024,
|
|
101
|
-
maximumDeliveryBytes: 8 * 1024 * 1024,
|
|
102
|
-
invocationTimeoutMs: config.invocationTimeoutMs ?? 30_000,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
const created = Object.freeze({
|
|
107
|
-
origin: new URL(url).origin,
|
|
108
|
-
application: serving,
|
|
109
|
-
environment,
|
|
110
|
-
});
|
|
111
|
-
if (applications.size >= MAXIMUM_CACHED_APPLICATIONS) {
|
|
112
|
-
const oldest = applications.keys().next().value;
|
|
113
|
-
if (oldest !== undefined) {
|
|
114
|
-
const evicted = applications.get(oldest);
|
|
115
|
-
applications.delete(oldest);
|
|
116
|
-
if (evicted !== undefined) {
|
|
117
|
-
try {
|
|
118
|
-
await closeEvictedApplication(evicted.application);
|
|
119
|
-
}
|
|
120
|
-
catch (cause) {
|
|
121
|
-
await serving
|
|
122
|
-
.close({ deadline: Date.now() + APPLICATION_CLOSE_TIMEOUT_MS })
|
|
123
|
-
.catch(() => undefined);
|
|
124
|
-
throw cause;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
applications.set(key, created);
|
|
130
|
-
return created;
|
|
131
|
-
};
|
|
132
|
-
return Object.freeze({
|
|
133
|
-
async fetch(request, bindings, executionContext) {
|
|
134
|
-
const requestUrl = new URL(request.url);
|
|
135
|
-
const definition = await definitionFor(bindings);
|
|
136
|
-
if (definition.frontend !== undefined) {
|
|
137
|
-
const asset = await frontendAsset(definition, bindings, requestUrl, request);
|
|
138
|
-
if (asset !== undefined)
|
|
139
|
-
return asset;
|
|
140
|
-
}
|
|
141
|
-
const publicUrl = canonicalServingUrl(config.resolveUrl?.(bindings, requestOrigin(requestUrl, request)) ??
|
|
142
|
-
bindings.WORKER_URL ??
|
|
143
|
-
requestOrigin(requestUrl, request));
|
|
144
|
-
const serving = await applicationFor(publicUrl, bindings, definition);
|
|
145
|
-
return serving.application.app.fetch(request, serving.environment, executionContext);
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
/** Close an evicted Application before its environment bindings leave the cache. */
|
|
151
|
-
export function closeEvictedApplication(application, now = Date.now) {
|
|
152
|
-
return application.close({ deadline: now() + APPLICATION_CLOSE_TIMEOUT_MS });
|
|
153
|
-
}
|
|
154
|
-
async function frontendAsset(definition, environment, url, request) {
|
|
155
|
-
const frontend = definition.frontend;
|
|
156
|
-
if (frontend === undefined || frontend.source.kind === 'external')
|
|
157
|
-
return undefined;
|
|
158
|
-
const routes = frontend.routes;
|
|
159
|
-
const route = Object.values(routes).find((candidate) => candidate.path === url.pathname);
|
|
160
|
-
const staticAsset = url.pathname.startsWith('/assets/');
|
|
161
|
-
if (route === undefined && !staticAsset)
|
|
162
|
-
return undefined;
|
|
163
|
-
if (request.method !== 'GET' && request.method !== 'HEAD')
|
|
164
|
-
return undefined;
|
|
165
|
-
const binding = environment.ASSETS;
|
|
166
|
-
if (binding === undefined)
|
|
167
|
-
return undefined;
|
|
168
|
-
const dev = environment.FRONTEND_DEV_URL?.replace(/\/+$/u, '');
|
|
169
|
-
if (dev !== undefined && dev.length > 0) {
|
|
170
|
-
return fetch(new Request(`${dev}${url.pathname}${url.search}`, request));
|
|
171
|
-
}
|
|
172
|
-
const path = route === undefined ? `${url.pathname}${url.search}` : `/${route.document}${url.search}`;
|
|
173
|
-
return binding.fetch(new Request(new URL(path, url.origin), request));
|
|
174
|
-
}
|
|
175
|
-
function requestOrigin(url, request) {
|
|
176
|
-
if (url.protocol !== 'http:')
|
|
177
|
-
return url.origin;
|
|
178
|
-
const forwarded = request.headers.get('x-forwarded-proto')?.split(',')[0]?.trim().toLowerCase();
|
|
179
|
-
return forwarded === 'https' ? `https://${url.host}` : url.origin;
|
|
180
|
-
}
|
|
181
|
-
function canonicalServingUrl(input) {
|
|
182
|
-
let url;
|
|
183
|
-
try {
|
|
184
|
-
url = new URL(input);
|
|
185
|
-
}
|
|
186
|
-
catch (cause) {
|
|
187
|
-
throw new TypeError('Cloudflare Worker serving URL is invalid.', { cause });
|
|
188
|
-
}
|
|
189
|
-
if ((url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
190
|
-
url.username !== '' ||
|
|
191
|
-
url.password !== '' ||
|
|
192
|
-
url.pathname !== '/' ||
|
|
193
|
-
url.search !== '' ||
|
|
194
|
-
url.hash !== '') {
|
|
195
|
-
throw new TypeError('Cloudflare Worker serving URL must be one HTTP(S) origin.');
|
|
196
|
-
}
|
|
197
|
-
return url.origin;
|
|
198
|
-
}
|
|
199
|
-
function deploymentVersion(environment) {
|
|
200
|
-
const input = environment.ASTRALE_VERSION_METADATA;
|
|
201
|
-
if (input !== null &&
|
|
202
|
-
typeof input === 'object' &&
|
|
203
|
-
typeof input.id === 'string' &&
|
|
204
|
-
input.id.length > 0) {
|
|
205
|
-
return input.id;
|
|
206
|
-
}
|
|
207
|
-
const fallback = environment.ASTRALE_DEPLOYMENT_VERSION;
|
|
208
|
-
if (typeof fallback === 'string' && fallback.length > 0)
|
|
209
|
-
return fallback;
|
|
210
|
-
throw new TypeError('Cloudflare Worker deployment version is unavailable.');
|
|
211
|
-
}
|
|
212
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export * from './entry.js';
|
|
2
|
+
export { decodeSigningIdentity, runtimeBindings } from './identity.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
'--outdir',
|
|
11
|
+
args.outDir,
|
|
12
|
+
'--metafile',
|
|
13
|
+
'--config',
|
|
14
|
+
args.configPath,
|
|
15
|
+
], args.projectDir, args.onLog, args.signal);
|
|
16
|
+
if (code !== 0)
|
|
17
|
+
throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`);
|
|
18
|
+
const javascript = readdirSync(args.outDir).filter((file) => file.endsWith('.js'));
|
|
19
|
+
if (javascript.length === 0) {
|
|
20
|
+
throw new Error(`wrangler bundle produced no .js in ${args.outDir}`);
|
|
21
|
+
}
|
|
22
|
+
const preferred = javascript.find((file) => file.startsWith('worker')) ?? javascript[0];
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
bundlePath: join(args.outDir, preferred),
|
|
25
|
+
metafilePath: join(args.outDir, 'bundle-meta.json'),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -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,17 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
export declare function wranglerCommand(projectDir: string, runtime?: {
|
|
3
|
+
readonly executable: string;
|
|
4
|
+
readonly bun: boolean;
|
|
5
|
+
}): {
|
|
6
|
+
cmd: string;
|
|
7
|
+
prefix: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Keep Wrangler CLI diagnostic logs inside the generated project state. */
|
|
10
|
+
export declare function wranglerProcessEnv(projectDir: string): NodeJS.ProcessEnv;
|
|
11
|
+
export declare function runCapture(cmd: string, args: string[], cwd: string, onLog?: (line: string) => void, signal?: AbortSignal, redact?: (input: string) => string): Promise<{
|
|
12
|
+
code: number;
|
|
13
|
+
out: string;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function stopChild(child: ChildProcess): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
}
|
|
103
|
+
export async function stopChild(child) {
|
|
104
|
+
if (child.exitCode !== null)
|
|
105
|
+
return;
|
|
106
|
+
await new Promise((resolve) => {
|
|
107
|
+
child.on('exit', () => resolve());
|
|
108
|
+
child.kill('SIGTERM');
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
if (child.exitCode === null)
|
|
111
|
+
child.kill('SIGKILL');
|
|
112
|
+
resolve();
|
|
113
|
+
}, 4000).unref?.();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function withSecretsFile<Result>(secrets: Readonly<Record<string, string>>, operation: (path?: string) => Promise<Result>): Promise<Result>;
|
|
2
|
+
export declare function createSecretEnvironment(secrets: Readonly<Record<string, string>>): Promise<{
|
|
3
|
+
readonly path: string;
|
|
4
|
+
remove(): Promise<void>;
|
|
5
|
+
} | undefined>;
|
|
6
|
+
export declare function secretRedactor(secrets: Readonly<Record<string, string>>): (input: string) => string;
|
|
@@ -0,0 +1,58 @@
|
|
|
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 async function createSecretEnvironment(secrets) {
|
|
18
|
+
if (Object.keys(secrets).length === 0)
|
|
19
|
+
return undefined;
|
|
20
|
+
const source = Object.entries(secrets)
|
|
21
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
22
|
+
.map(([name, value]) => `${secretName(name)}=${dotenvSecret(value)}`)
|
|
23
|
+
.join('\n');
|
|
24
|
+
const directory = await mkdtemp(join(tmpdir(), 'astrale-dev-secrets-'));
|
|
25
|
+
const path = join(directory, '.dev.vars');
|
|
26
|
+
await writeFile(path, `${source}\n`, { mode: 0o600 });
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
path,
|
|
29
|
+
remove: () => rm(directory, { recursive: true, force: true }),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function secretRedactor(secrets) {
|
|
33
|
+
const values = Object.values(secrets)
|
|
34
|
+
.filter((value) => value.length > 0)
|
|
35
|
+
.flatMap((value) => [value, JSON.stringify(value).slice(1, -1)])
|
|
36
|
+
.filter((value, index, all) => all.indexOf(value) === index)
|
|
37
|
+
.sort((left, right) => right.length - left.length);
|
|
38
|
+
return (input) => values.reduce((output, value) => output.replaceAll(value, '[REDACTED]'), input);
|
|
39
|
+
}
|
|
40
|
+
function secretName(input) {
|
|
41
|
+
if (!/^[A-Za-z_]\w*$/u.test(input)) {
|
|
42
|
+
throw new TypeError(`Cloudflare secret name '${input}' is not a Worker binding name.`);
|
|
43
|
+
}
|
|
44
|
+
return input;
|
|
45
|
+
}
|
|
46
|
+
/** Encode without changing a byte under Wrangler's dotenv boundary. */
|
|
47
|
+
function dotenvSecret(input) {
|
|
48
|
+
if (!input.includes("'"))
|
|
49
|
+
return `'${input}'`;
|
|
50
|
+
if (!input.includes('`'))
|
|
51
|
+
return `\`${input}\``;
|
|
52
|
+
if (!input.includes('"') && !input.includes('\\n') && !input.includes('\\r')) {
|
|
53
|
+
return `"${input}"`;
|
|
54
|
+
}
|
|
55
|
+
if (input.trim() === input && !/[#\r\n]/u.test(input))
|
|
56
|
+
return input;
|
|
57
|
+
throw new TypeError('Cloudflare local secret cannot be represented losslessly as dotenv.');
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WranglerDeploymentStatus {
|
|
2
|
+
readonly deployment: string;
|
|
3
|
+
readonly versions: readonly {
|
|
4
|
+
readonly version: string;
|
|
5
|
+
readonly percentage: number;
|
|
6
|
+
}[];
|
|
7
|
+
}
|
|
8
|
+
export declare function runWranglerDeploymentStatus(args: {
|
|
9
|
+
readonly projectDir: string;
|
|
10
|
+
readonly configPath: string;
|
|
11
|
+
readonly workerName: string;
|
|
12
|
+
readonly signal?: AbortSignal;
|
|
13
|
+
}): Promise<WranglerDeploymentStatus>;
|
|
14
|
+
export declare function admitWranglerDeploymentStatus(output: string): WranglerDeploymentStatus;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { runCapture, wranglerCommand } from './process.js';
|
|
2
|
+
export async function runWranglerDeploymentStatus(args) {
|
|
3
|
+
const { cmd, prefix } = wranglerCommand(args.projectDir);
|
|
4
|
+
const result = await runCapture(cmd, [
|
|
5
|
+
...prefix,
|
|
6
|
+
'deployments',
|
|
7
|
+
'status',
|
|
8
|
+
'--config',
|
|
9
|
+
args.configPath,
|
|
10
|
+
'--name',
|
|
11
|
+
args.workerName,
|
|
12
|
+
'--json',
|
|
13
|
+
], args.projectDir, undefined, args.signal);
|
|
14
|
+
if (result.code !== 0) {
|
|
15
|
+
throw new Error(`wrangler deployments status failed (code ${result.code}):\n${result.out.slice(-2000)}`);
|
|
16
|
+
}
|
|
17
|
+
return admitWranglerDeploymentStatus(result.stdout);
|
|
18
|
+
}
|
|
19
|
+
export function admitWranglerDeploymentStatus(output) {
|
|
20
|
+
let decoded;
|
|
21
|
+
try {
|
|
22
|
+
decoded = JSON.parse(output);
|
|
23
|
+
}
|
|
24
|
+
catch (cause) {
|
|
25
|
+
throw new TypeError('wrangler deployments status returned invalid JSON.', { cause });
|
|
26
|
+
}
|
|
27
|
+
if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded))
|
|
28
|
+
invalidStatus();
|
|
29
|
+
const deployment = Reflect.get(decoded, 'id');
|
|
30
|
+
const traffic = Reflect.get(decoded, 'versions');
|
|
31
|
+
if (typeof deployment !== 'string' ||
|
|
32
|
+
deployment.length === 0 ||
|
|
33
|
+
!Array.isArray(traffic) ||
|
|
34
|
+
traffic.length === 0) {
|
|
35
|
+
invalidStatus();
|
|
36
|
+
}
|
|
37
|
+
const versions = traffic.map((entry) => {
|
|
38
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry))
|
|
39
|
+
invalidStatus();
|
|
40
|
+
const version = Reflect.get(entry, 'version_id');
|
|
41
|
+
const percentage = Reflect.get(entry, 'percentage');
|
|
42
|
+
if (typeof version !== 'string' ||
|
|
43
|
+
version.length === 0 ||
|
|
44
|
+
typeof percentage !== 'number' ||
|
|
45
|
+
!Number.isFinite(percentage) ||
|
|
46
|
+
percentage <= 0 ||
|
|
47
|
+
percentage > 100) {
|
|
48
|
+
invalidStatus();
|
|
49
|
+
}
|
|
50
|
+
return Object.freeze({ version, percentage });
|
|
51
|
+
});
|
|
52
|
+
return Object.freeze({ deployment, versions: Object.freeze(versions) });
|
|
53
|
+
}
|
|
54
|
+
function invalidStatus() {
|
|
55
|
+
throw new TypeError('wrangler deployments status returned invalid deployment evidence.');
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { runCapture, wranglerCommand } from './process.js';
|
|
3
|
+
export async function runWranglerTypes(args) {
|
|
4
|
+
const { cmd, prefix } = wranglerCommand(args.projectDir);
|
|
5
|
+
const outputPath = join(dirname(args.configPath), 'worker-configuration.d.ts');
|
|
6
|
+
const { code, out } = await runCapture(cmd, [
|
|
7
|
+
...prefix,
|
|
8
|
+
'types',
|
|
9
|
+
outputPath,
|
|
10
|
+
'--env-interface',
|
|
11
|
+
'CloudflareEnv',
|
|
12
|
+
'--config',
|
|
13
|
+
args.configPath,
|
|
14
|
+
], args.projectDir, args.onLog, args.signal);
|
|
15
|
+
if (code !== 0) {
|
|
16
|
+
throw new Error(`wrangler types failed (code ${code}):\n${out.slice(-2000)}`);
|
|
17
|
+
}
|
|
18
|
+
return outputPath;
|
|
19
|
+
}
|