@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.
- package/CHANGELOG.md +1515 -0
- package/README.md +46 -2
- package/dist/adapter.d.ts +14 -0
- package/dist/adapter.js +82 -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 +12 -3
- package/dist/build/codegen/wrangler.js +26 -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 +90 -0
- package/dist/build/frontend/files.d.ts +9 -0
- package/dist/build/frontend/files.js +33 -0
- package/dist/build/frontend/readiness.d.ts +10 -0
- package/dist/build/frontend/readiness.js +21 -0
- package/dist/build/frontend/revision.d.ts +5 -0
- package/dist/build/frontend/revision.js +14 -0
- package/dist/build/frontend/vite.d.ts +8 -0
- package/dist/build/frontend/vite.js +32 -0
- package/dist/build/frontend/vite.process.d.ts +7 -0
- package/dist/build/frontend/vite.process.js +65 -0
- package/dist/build/frontend/vite.runner.d.ts +1 -0
- package/dist/build/frontend/vite.runner.js +120 -0
- package/dist/build/index.d.ts +9 -16
- package/dist/build/index.js +8 -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 +19 -0
- package/dist/build/worker/codegen/service.js +53 -0
- package/dist/build/worker/execution.prepare.d.ts +19 -0
- package/dist/build/worker/execution.prepare.js +115 -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 -45
- package/dist/cloudflare.js +1 -202
- package/dist/deploy/deploy.d.ts +17 -0
- package/dist/deploy/deploy.js +221 -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/deploy/readiness.d.ts +5 -0
- package/dist/deploy/readiness.js +18 -0
- package/dist/identity/binding.d.ts +2 -0
- package/dist/identity/binding.js +2 -0
- package/dist/identity/index.d.ts +3 -0
- package/dist/identity/index.js +3 -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 +10 -9
- package/dist/index.js +10 -9
- package/dist/params.d.ts +40 -36
- 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 +48 -0
- package/dist/worker/entry.js +157 -0
- package/dist/worker/fetch.d.ts +0 -1
- package/dist/worker/fetch.js +0 -1
- package/dist/worker/frontend.d.ts +4 -0
- package/dist/worker/frontend.js +46 -0
- 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 -218
- package/dist/wrangler/bundle.d.ts +10 -0
- package/dist/wrangler/bundle.js +28 -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 +15 -0
- package/dist/wrangler/process.js +102 -0
- package/dist/wrangler/rollout.d.ts +27 -0
- package/dist/wrangler/rollout.js +75 -0
- package/dist/wrangler/secrets.d.ts +2 -0
- package/dist/wrangler/secrets.js +24 -0
- package/dist/wrangler/status.d.ts +18 -0
- package/dist/wrangler/status.js +75 -0
- package/dist/wrangler/triggers.d.ts +5 -0
- package/dist/wrangler/triggers.js +12 -0
- package/dist/wrangler/type-generation.d.ts +6 -0
- package/dist/wrangler/type-generation.js +19 -0
- package/dist/wrangler/versions.d.ts +13 -0
- package/dist/wrangler/versions.js +38 -0
- package/package.json +34 -19
- package/template/astrale.config.ts +30 -31
- 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 -27
- package/dist/build/frontend.d.ts.map +0 -1
- package/dist/build/frontend.js +0 -252
- 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 -138
- 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 -128
- package/src/build/frontend.ts +0 -298
- package/src/build/index.ts +0 -29
- package/src/build/parse-output.ts +0 -60
- package/src/build/prepare.ts +0 -189
- package/src/build/worker.ts +0 -37
- package/src/build/wrangler-cli.ts +0 -372
- package/src/cloudflare.ts +0 -275
- 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 -332
- package/src/worker/tsconfig.json +0 -10
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { addressing, assemble } from '@astrale-os/sdk/deployment/release';
|
|
2
|
+
import { mkdir, mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { workerName } from '../build/configuration.js';
|
|
5
|
+
import { admitDirectPlacement } from '../build/direct.js';
|
|
6
|
+
import { loadWorkerArtifact } from '../build/worker/index.js';
|
|
7
|
+
import { signingIdentitySecrets } from '../identity/secret.js';
|
|
8
|
+
import { WranglerDeployError } from '../wrangler/deploy.js';
|
|
9
|
+
import { WranglerRolloutError } from '../wrangler/rollout.js';
|
|
10
|
+
const NONE = Object.freeze({ kind: 'none' });
|
|
11
|
+
/** Deploy one stable Worker target and verify its exact serving implementation. */
|
|
12
|
+
export async function deployCloudflare(parameters, context, dependencies) {
|
|
13
|
+
let secrets;
|
|
14
|
+
try {
|
|
15
|
+
const identity = await dependencies.signingIdentity({
|
|
16
|
+
projectDir: context.projectDir,
|
|
17
|
+
source: parameters.signingIdentity,
|
|
18
|
+
origin: context.artifact.build.schema.compiled.root.origin,
|
|
19
|
+
mode: 'deploy',
|
|
20
|
+
});
|
|
21
|
+
secrets = signingIdentitySecrets(context.secrets, identity);
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
return failed('prerequisite', progress({}), cause);
|
|
25
|
+
}
|
|
26
|
+
let worker;
|
|
27
|
+
try {
|
|
28
|
+
worker = loadWorkerArtifact(context.artifact);
|
|
29
|
+
for (const name of worker.bindings.requiredSecrets) {
|
|
30
|
+
if (!Object.hasOwn(secrets, name)) {
|
|
31
|
+
throw new TypeError(`Cloudflare Worker requires secret ${name}.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
admitDirectPlacement(context.artifact, worker, parameters, context.environment);
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return failed('prerequisite', progress({}), cause);
|
|
38
|
+
}
|
|
39
|
+
const stateDirectory = join(context.projectDir, '.astrale');
|
|
40
|
+
await mkdir(stateDirectory, { recursive: true });
|
|
41
|
+
const directory = await mkdtemp(join(stateDirectory, 'deploy-'));
|
|
42
|
+
try {
|
|
43
|
+
await dependencies.materialize(directory, context.artifact.files);
|
|
44
|
+
const name = workerName(parameters, context.artifact.build.schema.compiled.root.origin, context.environment);
|
|
45
|
+
const configPath = join(directory, 'wrangler.jsonc');
|
|
46
|
+
let deployed;
|
|
47
|
+
try {
|
|
48
|
+
deployed = await dependencies.runWranglerRollout({
|
|
49
|
+
projectDir: context.projectDir,
|
|
50
|
+
configPath,
|
|
51
|
+
workerName: name,
|
|
52
|
+
verifyCandidate: async (candidate) => {
|
|
53
|
+
const selected = observeAddressing(context.artifact, candidate.url, name);
|
|
54
|
+
const release = assemble(context.artifact.build, selected);
|
|
55
|
+
await dependencies.waitForReadiness(release, context.signal, candidate.deploymentVersion, worker, name);
|
|
56
|
+
},
|
|
57
|
+
secrets,
|
|
58
|
+
...(parameters.route === undefined ? {} : { route: parameters.route }),
|
|
59
|
+
signal: context.signal,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
return failed(cause instanceof WranglerRolloutError ? cause.phase : 'provider', progress({
|
|
64
|
+
provider: partialProvider(cause, context.artifact),
|
|
65
|
+
...(cause instanceof WranglerRolloutError
|
|
66
|
+
? {
|
|
67
|
+
configuration: Object.freeze({
|
|
68
|
+
kind: 'partial',
|
|
69
|
+
evidence: { names: Object.keys(secrets).sort() },
|
|
70
|
+
}),
|
|
71
|
+
}
|
|
72
|
+
: {}),
|
|
73
|
+
}), cause);
|
|
74
|
+
}
|
|
75
|
+
const configuration = Object.freeze({
|
|
76
|
+
kind: 'applied',
|
|
77
|
+
evidence: Object.freeze({ names: Object.keys(secrets).sort() }),
|
|
78
|
+
});
|
|
79
|
+
const retained = Object.freeze({
|
|
80
|
+
kind: 'partial',
|
|
81
|
+
evidence: Object.freeze({
|
|
82
|
+
provider: 'cloudflare',
|
|
83
|
+
deployment: deployed.deploymentVersion,
|
|
84
|
+
artifact: context.artifact.digest,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
let selected;
|
|
88
|
+
try {
|
|
89
|
+
selected = observeAddressing(context.artifact, deployed.url, name);
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
return failed('addressing', progress({
|
|
93
|
+
provider: retained,
|
|
94
|
+
configuration,
|
|
95
|
+
}), cause);
|
|
96
|
+
}
|
|
97
|
+
let observed;
|
|
98
|
+
try {
|
|
99
|
+
observed = await dependencies.runWranglerDeploymentStatus({
|
|
100
|
+
projectDir: context.projectDir,
|
|
101
|
+
configPath,
|
|
102
|
+
workerName: name,
|
|
103
|
+
signal: context.signal,
|
|
104
|
+
});
|
|
105
|
+
requireCurrentDeployment(observed, deployed.deploymentVersion);
|
|
106
|
+
}
|
|
107
|
+
catch (cause) {
|
|
108
|
+
return failed('readiness', progress({
|
|
109
|
+
provider: retained,
|
|
110
|
+
configuration,
|
|
111
|
+
}), cause);
|
|
112
|
+
}
|
|
113
|
+
let release;
|
|
114
|
+
try {
|
|
115
|
+
release = assemble(context.artifact.build, selected);
|
|
116
|
+
}
|
|
117
|
+
catch (cause) {
|
|
118
|
+
return failed('release', progress({ provider: retained, configuration }), cause);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await dependencies.waitForReadiness(release, context.signal, deployed.deploymentVersion, worker);
|
|
122
|
+
}
|
|
123
|
+
catch (cause) {
|
|
124
|
+
return failed('readiness', progress({ provider: retained, configuration }), cause, release);
|
|
125
|
+
}
|
|
126
|
+
const provider = Object.freeze({
|
|
127
|
+
kind: 'applied',
|
|
128
|
+
evidence: Object.freeze({
|
|
129
|
+
provider: 'cloudflare',
|
|
130
|
+
deployment: deployed.deploymentVersion,
|
|
131
|
+
artifact: context.artifact.digest,
|
|
132
|
+
issuer: selected.issuer,
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
return Object.freeze({
|
|
136
|
+
status: 'ready',
|
|
137
|
+
phase: 'complete',
|
|
138
|
+
progress: Object.freeze({ ...progress({ configuration }), provider }),
|
|
139
|
+
release,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await rm(directory, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function partialProvider(cause, artifact) {
|
|
147
|
+
if (!(cause instanceof WranglerDeployError) || cause.applied?.deploymentVersion === undefined) {
|
|
148
|
+
return Object.freeze({ kind: 'unknown' });
|
|
149
|
+
}
|
|
150
|
+
let issuer;
|
|
151
|
+
if (cause.applied.url !== undefined) {
|
|
152
|
+
try {
|
|
153
|
+
issuer = addressing(cause.applied.url).issuer;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// The provider effect remains known when its printed URL is not Addressing.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return Object.freeze({
|
|
160
|
+
kind: 'partial',
|
|
161
|
+
evidence: Object.freeze({
|
|
162
|
+
provider: 'cloudflare',
|
|
163
|
+
deployment: cause.applied.deploymentVersion,
|
|
164
|
+
artifact: artifact.digest,
|
|
165
|
+
...(issuer === undefined ? {} : { issuer }),
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function requireCurrentDeployment(observed, expectedVersion) {
|
|
170
|
+
if (observed.versions.length !== 1 ||
|
|
171
|
+
observed.versions[0].percentage !== 100 ||
|
|
172
|
+
observed.versions[0].version !== expectedVersion) {
|
|
173
|
+
throw new TypeError('Cloudflare Worker is not one exact version at 100%.');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function observeAddressing(artifact, deployedUrl, expectedWorker) {
|
|
177
|
+
if (artifact.addressing.kind === 'configured') {
|
|
178
|
+
if (deployedUrl === undefined)
|
|
179
|
+
return artifact.addressing.addressing;
|
|
180
|
+
const observed = addressing(deployedUrl);
|
|
181
|
+
if (observed.issuer !== artifact.addressing.addressing.issuer) {
|
|
182
|
+
throw new TypeError('Cloudflare deployed URL differs from configured Addressing.');
|
|
183
|
+
}
|
|
184
|
+
return observed;
|
|
185
|
+
}
|
|
186
|
+
if (deployedUrl === undefined) {
|
|
187
|
+
throw new TypeError('Cloudflare did not report provider-assigned Addressing.');
|
|
188
|
+
}
|
|
189
|
+
const observed = addressing(deployedUrl);
|
|
190
|
+
if (artifact.addressing.claim !== expectedWorker) {
|
|
191
|
+
throw new TypeError('PreparedArtifact worker claim differs from the deployment target.');
|
|
192
|
+
}
|
|
193
|
+
const url = new URL(observed.issuer);
|
|
194
|
+
const labels = url.hostname.split('.');
|
|
195
|
+
if (url.protocol !== 'https:' ||
|
|
196
|
+
labels.length < 4 ||
|
|
197
|
+
labels[0] !== artifact.addressing.claim ||
|
|
198
|
+
labels.at(-2) !== 'workers' ||
|
|
199
|
+
labels.at(-1) !== 'dev') {
|
|
200
|
+
throw new TypeError('Cloudflare workers.dev URL does not satisfy its prepared worker claim.');
|
|
201
|
+
}
|
|
202
|
+
return observed;
|
|
203
|
+
}
|
|
204
|
+
function progress(input) {
|
|
205
|
+
return Object.freeze({
|
|
206
|
+
prerequisite: input.prerequisite ?? NONE,
|
|
207
|
+
provider: input.provider ?? NONE,
|
|
208
|
+
configuration: input.configuration ?? NONE,
|
|
209
|
+
installation: input.installation ?? NONE,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function failed(phase, progressValue, cause, release) {
|
|
213
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
status: 'failed',
|
|
216
|
+
phase,
|
|
217
|
+
progress: progressValue,
|
|
218
|
+
error,
|
|
219
|
+
...(release === undefined ? {} : { release }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { deployCloudflare, type CloudflareDeployDependencies } from './deploy.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { deployCloudflare } from './deploy.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const WORKERS_DEV_RE = /(https:\/\/[^\s]+\.workers\.dev)/iu;
|
|
2
|
+
const VERSION_ID_RE = /(?:Current\s+)?Version ID:\s*([a-z0-9_-]+)/iu;
|
|
3
|
+
export function parseDeployUrl(text, route) {
|
|
4
|
+
if (route !== undefined)
|
|
5
|
+
return `https://${route.replace(/\/.*$/u, '')}`;
|
|
6
|
+
return WORKERS_DEV_RE.exec(text)?.[1];
|
|
7
|
+
}
|
|
8
|
+
export function parseDeployVersionId(text) {
|
|
9
|
+
return VERSION_ID_RE.exec(text)?.[1];
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
2
|
+
type ReadinessFetch = NonNullable<NonNullable<Parameters<typeof verifyReadiness>[1]>['fetch']>;
|
|
3
|
+
/** Readiness is evidence for the provider version, not just a compatible Publication. */
|
|
4
|
+
export declare function versionReadinessFetch(issuer: string, version: string, fetcher?: typeof fetch, workerName?: string): ReadinessFetch;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Readiness is evidence for the provider version, not just a compatible Publication. */
|
|
2
|
+
export function versionReadinessFetch(issuer, version, fetcher = globalThis.fetch, workerName) {
|
|
3
|
+
return async (input, init) => {
|
|
4
|
+
if (new URL(input).origin !== issuer) {
|
|
5
|
+
throw new TypeError('Cloudflare readiness cannot leave the expected Worker origin.');
|
|
6
|
+
}
|
|
7
|
+
const headers = new Headers();
|
|
8
|
+
if (workerName !== undefined) {
|
|
9
|
+
headers.set('Cloudflare-Workers-Version-Overrides', `${workerName}="${version}"`);
|
|
10
|
+
}
|
|
11
|
+
const response = await fetcher(input, workerName === undefined ? init : { ...init, headers });
|
|
12
|
+
if (response.headers.get('X-Astrale-Worker-Version') !== version) {
|
|
13
|
+
await response.body?.cancel();
|
|
14
|
+
throw new Error('Cloudflare readiness response differs from the deployed Worker version.');
|
|
15
|
+
}
|
|
16
|
+
return response;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonWebKey } from '@astrale-os/sdk/auth';
|
|
2
|
+
export type SigningIdentityMaterial = JsonWebKey & {
|
|
3
|
+
readonly kid: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function createSigningIdentity(): Promise<SigningIdentityMaterial>;
|
|
6
|
+
/** Admit private Ed25519 material and its collision-resistant public identity. */
|
|
7
|
+
export declare function admitSigningIdentity(input: unknown, owner: string, legacyKeyId?: string): Promise<SigningIdentityMaterial>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { calculateJwkThumbprint, exportJWK, generateKeyPair, importJWK } from 'jose';
|
|
2
|
+
export async function createSigningIdentity() {
|
|
3
|
+
const { privateKey } = await generateKeyPair('EdDSA', { extractable: true });
|
|
4
|
+
const exported = await exportJWK(privateKey);
|
|
5
|
+
return admitSigningIdentity({
|
|
6
|
+
...exported,
|
|
7
|
+
alg: 'EdDSA',
|
|
8
|
+
kid: await calculateJwkThumbprint(exported),
|
|
9
|
+
}, 'generated identity');
|
|
10
|
+
}
|
|
11
|
+
/** Admit private Ed25519 material and its collision-resistant public identity. */
|
|
12
|
+
export async function admitSigningIdentity(input, owner, legacyKeyId) {
|
|
13
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input))
|
|
14
|
+
invalid(owner);
|
|
15
|
+
const value = input;
|
|
16
|
+
if (value.kty !== 'OKP' ||
|
|
17
|
+
value.crv !== 'Ed25519' ||
|
|
18
|
+
value.alg !== 'EdDSA' ||
|
|
19
|
+
typeof value.x !== 'string' ||
|
|
20
|
+
value.x.length === 0 ||
|
|
21
|
+
typeof value.d !== 'string' ||
|
|
22
|
+
value.d.length === 0) {
|
|
23
|
+
invalid(owner);
|
|
24
|
+
}
|
|
25
|
+
const thumbprint = await calculateJwkThumbprint(value);
|
|
26
|
+
const admitted = legacyKeyId !== undefined && (value.kid === undefined || value.kid === legacyKeyId)
|
|
27
|
+
? { ...value, kid: thumbprint }
|
|
28
|
+
: value;
|
|
29
|
+
if (admitted.kid !== thumbprint) {
|
|
30
|
+
throw new TypeError(`Cloudflare signing identity '${owner}' must use its JWK thumbprint as kid.`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
await importJWK(admitted, 'EdDSA');
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
throw invalid(owner, cause);
|
|
37
|
+
}
|
|
38
|
+
return admitted;
|
|
39
|
+
}
|
|
40
|
+
function invalid(owner, cause) {
|
|
41
|
+
throw new TypeError(`Cloudflare signing identity '${owner}' is not a private Ed25519 JWK.`, cause === undefined ? undefined : { cause });
|
|
42
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SigningIdentitySource } from './source.js';
|
|
2
|
+
/** Add the adapter-owned identity without allowing a second secret authority. */
|
|
3
|
+
export declare function signingIdentitySecrets(secrets: Readonly<Record<string, string>>, identity: SigningIdentitySource): Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Add the adapter-owned identity without allowing a second secret authority. */
|
|
2
|
+
export function signingIdentitySecrets(secrets, identity) {
|
|
3
|
+
if (Object.hasOwn(secrets, identity.binding)) {
|
|
4
|
+
throw new TypeError(`${identity.binding} is adapter-owned; configure 'signingIdentity' instead of declaring that secret.`);
|
|
5
|
+
}
|
|
6
|
+
return Object.freeze({ ...secrets, [identity.binding]: identity.secret });
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SIGNING_IDENTITY_BINDING } from './binding.js';
|
|
2
|
+
export interface SigningIdentitySource {
|
|
3
|
+
readonly binding: typeof SIGNING_IDENTITY_BINDING;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly secret: string;
|
|
6
|
+
readonly keyId: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SigningIdentityInput {
|
|
9
|
+
readonly projectDir: string;
|
|
10
|
+
readonly origin: string;
|
|
11
|
+
readonly source?: string;
|
|
12
|
+
readonly mode: 'local' | 'deploy';
|
|
13
|
+
}
|
|
14
|
+
/** Resolve one admitted Domain signing identity for local or remote provider custody. */
|
|
15
|
+
export declare function signingIdentity(input: SigningIdentityInput): Promise<SigningIdentitySource>;
|
|
16
|
+
/**
|
|
17
|
+
* Read an explicitly retained deployment identity. Deployment never generates
|
|
18
|
+
* one: a missing file is a pre-provider configuration failure, not an implicit
|
|
19
|
+
* key rotation.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readSigningIdentity(projectDir: string, declaration: string | undefined): Promise<SigningIdentitySource>;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve local development identity. The default is generated once inside
|
|
24
|
+
* `.astrale`; an explicit source is always read-only and must already exist.
|
|
25
|
+
*/
|
|
26
|
+
export declare function localSigningIdentity(projectDir: string, declaration: string | undefined, origin: string): Promise<SigningIdentitySource>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { link, mkdir, open, readFile, unlink } from 'node:fs/promises';
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { SIGNING_IDENTITY_BINDING } from './binding.js';
|
|
6
|
+
import { admitSigningIdentity, createSigningIdentity } from './material.js';
|
|
7
|
+
const DEFAULT_IDENTITY = join('.astrale', 'identity.json');
|
|
8
|
+
/** Resolve one admitted Domain signing identity for local or remote provider custody. */
|
|
9
|
+
export function signingIdentity(input) {
|
|
10
|
+
return input.mode === 'local'
|
|
11
|
+
? localSigningIdentity(input.projectDir, input.source, input.origin)
|
|
12
|
+
: readSigningIdentity(input.projectDir, input.source);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Read an explicitly retained deployment identity. Deployment never generates
|
|
16
|
+
* one: a missing file is a pre-provider configuration failure, not an implicit
|
|
17
|
+
* key rotation.
|
|
18
|
+
*/
|
|
19
|
+
export async function readSigningIdentity(projectDir, declaration) {
|
|
20
|
+
if (declaration === undefined || declaration.length === 0) {
|
|
21
|
+
throw new TypeError("Cloudflare deployment requires 'signingIdentity: <private-jwk-file>'. " +
|
|
22
|
+
'Run local development once to create .astrale/identity.json, then retain that file in managed secret storage.');
|
|
23
|
+
}
|
|
24
|
+
return readSource(pathOf(projectDir, declaration), declaration);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve local development identity. The default is generated once inside
|
|
28
|
+
* `.astrale`; an explicit source is always read-only and must already exist.
|
|
29
|
+
*/
|
|
30
|
+
export async function localSigningIdentity(projectDir, declaration, origin) {
|
|
31
|
+
if (declaration !== undefined)
|
|
32
|
+
return readSource(pathOf(projectDir, declaration), declaration);
|
|
33
|
+
const path = join(projectDir, DEFAULT_IDENTITY);
|
|
34
|
+
if (!existsSync(path)) {
|
|
35
|
+
const legacy = join(projectDir, '.astrale', 'identity.ts');
|
|
36
|
+
if (existsSync(legacy))
|
|
37
|
+
await migrateLegacyIdentity(legacy, path, origin);
|
|
38
|
+
else
|
|
39
|
+
await generateIdentity(path);
|
|
40
|
+
}
|
|
41
|
+
return readSource(path, DEFAULT_IDENTITY);
|
|
42
|
+
}
|
|
43
|
+
async function generateIdentity(path) {
|
|
44
|
+
const value = await createSigningIdentity();
|
|
45
|
+
if (await installIdentity(path, value)) {
|
|
46
|
+
process.stderr.write(`\x1b[33m!\x1b[0m new signing identity generated at .astrale/identity.json (kid ${value.kid}).\n` +
|
|
47
|
+
` Retain it in managed secret storage before deploying; remote deploy fails closed without an explicit identity file.\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function migrateLegacyIdentity(legacy, destination, origin) {
|
|
51
|
+
const source = await readFile(legacy, 'utf8');
|
|
52
|
+
const match = /export const PRIVATE_JWK = (\{[\s\S]*\}) as const\n?$/u.exec(source);
|
|
53
|
+
if (match?.[1] === undefined)
|
|
54
|
+
throw invalidIdentity(legacy);
|
|
55
|
+
let decoded;
|
|
56
|
+
try {
|
|
57
|
+
decoded = JSON.parse(match[1]);
|
|
58
|
+
}
|
|
59
|
+
catch (cause) {
|
|
60
|
+
throw invalidIdentity(legacy, cause);
|
|
61
|
+
}
|
|
62
|
+
const value = await admitSigningIdentity(decoded, legacy, `${origin}-key`);
|
|
63
|
+
if (await installIdentity(destination, value)) {
|
|
64
|
+
process.stderr.write(`\x1b[33m!\x1b[0m migrated .astrale/identity.ts to secret-backed .astrale/identity.json (kid ${value.kid}).\n`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Publish a complete owner-only identity once; concurrent environments reuse the winner. */
|
|
68
|
+
async function installIdentity(path, value) {
|
|
69
|
+
await mkdir(dirname(path), { recursive: true });
|
|
70
|
+
const staging = `${path}.${process.pid}.${randomUUID()}.next`;
|
|
71
|
+
const file = await open(staging, 'wx', 0o600);
|
|
72
|
+
try {
|
|
73
|
+
await file.writeFile(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
74
|
+
await file.sync();
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
await file.close();
|
|
78
|
+
await unlink(staging).catch(() => undefined);
|
|
79
|
+
throw cause;
|
|
80
|
+
}
|
|
81
|
+
await file.close();
|
|
82
|
+
try {
|
|
83
|
+
await link(staging, path);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch (cause) {
|
|
87
|
+
if (!(cause instanceof Error && 'code' in cause && cause.code === 'EEXIST'))
|
|
88
|
+
throw cause;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await unlink(staging).catch(() => undefined);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function readSource(path, declaration) {
|
|
96
|
+
let source;
|
|
97
|
+
try {
|
|
98
|
+
source = await readFile(path, 'utf8');
|
|
99
|
+
}
|
|
100
|
+
catch (cause) {
|
|
101
|
+
if (!(cause instanceof Error && 'code' in cause && cause.code === 'ENOENT'))
|
|
102
|
+
throw cause;
|
|
103
|
+
throw new TypeError(`Cloudflare signing identity '${declaration}' is missing at ${path}.`, {
|
|
104
|
+
cause,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
let decoded;
|
|
108
|
+
try {
|
|
109
|
+
decoded = JSON.parse(source);
|
|
110
|
+
}
|
|
111
|
+
catch (cause) {
|
|
112
|
+
throw invalidIdentity(declaration, cause);
|
|
113
|
+
}
|
|
114
|
+
const value = await admitSigningIdentity(decoded, declaration);
|
|
115
|
+
return Object.freeze({
|
|
116
|
+
binding: SIGNING_IDENTITY_BINDING,
|
|
117
|
+
path,
|
|
118
|
+
secret: JSON.stringify(value),
|
|
119
|
+
keyId: value.kid,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function pathOf(projectDir, declaration) {
|
|
123
|
+
return isAbsolute(declaration) ? declaration : resolve(projectDir, declaration);
|
|
124
|
+
}
|
|
125
|
+
function invalidIdentity(owner, cause) {
|
|
126
|
+
return new TypeError(`Cloudflare signing identity '${owner}' is not a private Ed25519 JWK.`, cause === undefined ? undefined : { cause });
|
|
127
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@astrale-os/adapter-cloudflare` — deploy an Astrale
|
|
3
|
-
* Cloudflare
|
|
2
|
+
* `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
|
|
3
|
+
* Cloudflare Workers on YOUR OWN Cloudflare account.
|
|
4
4
|
*
|
|
5
5
|
* import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* }
|
|
7
|
+
* environments: {
|
|
8
|
+
* development: { deployment: cloudflare({ secrets: '.env.dev' }) },
|
|
9
|
+
* production: { deployment: cloudflare({ route: 'crm.acme.dev' }) },
|
|
10
|
+
* }
|
|
11
11
|
*
|
|
12
|
-
* `
|
|
12
|
+
* `astrale-domain dev <environment>` remotely deploys each admitted update;
|
|
13
|
+
* an Environment with no `route` ships to
|
|
13
14
|
* `*.workers.dev`; an env with a `route` ships to that custom domain.
|
|
14
15
|
*
|
|
15
16
|
* Prefer a managed deploy (no Cloudflare account)? Use
|
|
16
17
|
* `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
|
|
17
|
-
* `/build` toolkit and ships it through the Astrale platform.
|
|
18
|
+
* `/build` toolkit and ships it through the Astrale platform. Generic Project
|
|
19
|
+
* tooling owns optional Kernel installation after provider readiness.
|
|
18
20
|
*/
|
|
19
21
|
export { cloudflare } from './cloudflare.js';
|
|
20
22
|
export type { CloudflareParams } from './params.js';
|
|
21
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@astrale-os/adapter-cloudflare` — deploy an Astrale
|
|
3
|
-
* Cloudflare
|
|
2
|
+
* `@astrale-os/adapter-cloudflare` — deploy an Astrale application through
|
|
3
|
+
* Cloudflare Workers on YOUR OWN Cloudflare account.
|
|
4
4
|
*
|
|
5
5
|
* import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* }
|
|
7
|
+
* environments: {
|
|
8
|
+
* development: { deployment: cloudflare({ secrets: '.env.dev' }) },
|
|
9
|
+
* production: { deployment: cloudflare({ route: 'crm.acme.dev' }) },
|
|
10
|
+
* }
|
|
11
11
|
*
|
|
12
|
-
* `
|
|
12
|
+
* `astrale-domain dev <environment>` remotely deploys each admitted update;
|
|
13
|
+
* an Environment with no `route` ships to
|
|
13
14
|
* `*.workers.dev`; an env with a `route` ships to that custom domain.
|
|
14
15
|
*
|
|
15
16
|
* Prefer a managed deploy (no Cloudflare account)? Use
|
|
16
17
|
* `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
|
|
17
|
-
* `/build` toolkit and ships it through the Astrale platform.
|
|
18
|
+
* `/build` toolkit and ships it through the Astrale platform. Generic Project
|
|
19
|
+
* tooling owns optional Kernel installation after provider readiness.
|
|
18
20
|
*/
|
|
19
21
|
export { cloudflare } from './cloudflare.js';
|
|
20
|
-
//# sourceMappingURL=index.js.map
|