@astrale-os/adapter-cloudflare 0.5.0-beta.71 → 0.5.0-beta.72
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 +14 -0
- package/README.md +4 -0
- package/dist/adapter.d.ts +2 -4
- package/dist/adapter.js +5 -106
- package/dist/build/artifact.js +7 -5
- package/dist/build/{frontend-v1.d.ts → frontend/files.d.ts} +0 -9
- package/dist/build/frontend/files.js +60 -0
- package/dist/build/index.d.ts +2 -2
- package/dist/build/index.js +2 -2
- package/dist/build/worker/execution.prepare.js +1 -1
- package/dist/deploy/deploy.d.ts +2 -1
- package/dist/deploy/deploy.js +1 -1
- 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/{build/frontend-v1.js → development/frontend.js} +31 -55
- 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 +18 -0
- package/dist/development/resolved-fetch.js +78 -0
- package/dist/development/worker-output.d.ts +2 -0
- package/dist/development/worker-output.js +25 -0
- package/dist/{build/wrangler/development.d.ts → development/worker.d.ts} +5 -5
- package/dist/{build/wrangler/development.js → development/worker.js} +24 -13
- package/dist/identity/source.js +37 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/params.d.ts +3 -3
- package/dist/{build/wrangler → wrangler}/bundle.js +0 -2
- package/dist/{build/wrangler → wrangler}/deploy.js +1 -1
- package/dist/wrangler/index.d.ts +5 -0
- package/dist/wrangler/index.js +5 -0
- package/package.json +2 -2
- package/template/astrale.config.ts +6 -6
- package/dist/build/parse-output.d.ts +0 -26
- package/dist/build/parse-output.js +0 -57
- package/dist/build/wrangler-cli.d.ts +0 -6
- package/dist/build/wrangler-cli.js +0 -6
- /package/dist/{build/wrangler → wrangler}/bundle.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/deploy.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/process.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/process.js +0 -0
- /package/dist/{build/wrangler → wrangler}/secrets.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/secrets.js +0 -0
- /package/dist/{build/wrangler → wrangler}/status.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/status.js +0 -0
- /package/dist/{build/wrangler → wrangler}/type-generation.d.ts +0 -0
- /package/dist/{build/wrangler → wrangler}/type-generation.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0-beta.72](https://github.com/astrale-os/sdk/compare/adapter-cloudflare-v0.5.0-beta.71...adapter-cloudflare-v0.5.0-beta.72) (2026-08-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add coordinated Domain development sessions ([#300](https://github.com/astrale-os/sdk/issues/300)) ([102104f](https://github.com/astrale-os/sdk/commit/102104f1ed55333f5892db23c036578aa0ffd2e1))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @astrale-os/sdk bumped to 0.5.0-beta.69
|
|
16
|
+
|
|
3
17
|
## [0.5.0-beta.71](https://github.com/astrale-os/sdk/compare/adapter-cloudflare-v0.5.0-beta.70...adapter-cloudflare-v0.5.0-beta.71) (2026-08-28)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,10 @@ npm install @astrale-os/sdk@beta @astrale-os/adapter-cloudflare@beta
|
|
|
10
10
|
|
|
11
11
|
Bare installs return to stable releases when the package set is promoted and npm `latest` advances.
|
|
12
12
|
|
|
13
|
+
`pnpm dev` starts the Worker and optional Vite frontend with hot reload on collision-free allocated
|
|
14
|
+
ports. It does not own Kernel installation or public ingress; the managed Astrale adapter composes
|
|
15
|
+
those lifecycle responsibilities around this provider-local session.
|
|
16
|
+
|
|
13
17
|
## Provider-local staging
|
|
14
18
|
|
|
15
19
|
Managed Cloudflare providers may stage immutable implementation candidates without exposing their
|
package/dist/adapter.d.ts
CHANGED
|
@@ -2,14 +2,12 @@ import type { Release } from '@astrale-os/sdk/deployment/release';
|
|
|
2
2
|
import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
3
3
|
import type { CloudflareParams } from './params.js';
|
|
4
4
|
import { artifact } from './build/artifact.js';
|
|
5
|
-
import { startFrontend } from './build/frontend-v1.js';
|
|
6
|
-
import { runWranglerDev } from './build/wrangler-cli.js';
|
|
7
5
|
import { type CloudflareDeployDependencies } from './deploy/index.js';
|
|
6
|
+
import { developCloudflare } from './development/develop.js';
|
|
8
7
|
import { signingIdentity } from './identity/source.js';
|
|
9
8
|
export interface CloudflareDependencies extends CloudflareDeployDependencies {
|
|
10
9
|
readonly artifact: typeof artifact;
|
|
11
|
-
readonly
|
|
12
|
-
readonly startFrontend: typeof startFrontend;
|
|
10
|
+
readonly develop: typeof developCloudflare;
|
|
13
11
|
readonly signingIdentity: typeof signingIdentity;
|
|
14
12
|
}
|
|
15
13
|
export declare function cloudflare(environments: Readonly<Record<string, CloudflareParams>>): import("@astrale-os/sdk/deployment/adapter").Adapter<CloudflareParams>;
|
package/dist/adapter.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { defineAdapter } from '@astrale-os/sdk/deployment/adapter';
|
|
2
2
|
import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
3
|
-
import { writeFile } from 'node:fs/promises';
|
|
4
3
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
4
|
import { artifact, materialize } from './build/artifact.js';
|
|
7
|
-
import { generateWranglerConfig } from './build/codegen/wrangler.js';
|
|
8
|
-
import { resolveRouter, workerName } from './build/configuration.js';
|
|
9
|
-
import { startFrontend } from './build/frontend-v1.js';
|
|
10
|
-
import { loadWorkerArtifact } from './build/worker/index.js';
|
|
11
|
-
import { workerModules } from './build/worker/modules.js';
|
|
12
|
-
import { runWranglerDeploymentStatus, runWranglerDeploy, runWranglerDev, } from './build/wrangler-cli.js';
|
|
13
5
|
import { deployCloudflare } from './deploy/index.js';
|
|
14
|
-
import {
|
|
6
|
+
import { developCloudflare } from './development/develop.js';
|
|
15
7
|
import { signingIdentity } from './identity/source.js';
|
|
8
|
+
import { runWranglerDeploy } from './wrangler/deploy.js';
|
|
9
|
+
import { runWranglerDeploymentStatus } from './wrangler/status.js';
|
|
16
10
|
const packageVersion = createRequire(import.meta.url)('../package.json')
|
|
17
11
|
.version;
|
|
18
12
|
export function cloudflare(environments) {
|
|
@@ -21,8 +15,7 @@ export function cloudflare(environments) {
|
|
|
21
15
|
materialize,
|
|
22
16
|
runWranglerDeploy,
|
|
23
17
|
runWranglerDeploymentStatus,
|
|
24
|
-
|
|
25
|
-
startFrontend,
|
|
18
|
+
develop: developCloudflare,
|
|
26
19
|
signingIdentity,
|
|
27
20
|
waitForReadiness: (release, signal) => waitForReadiness(verifyReadiness, release, signal),
|
|
28
21
|
});
|
|
@@ -33,105 +26,11 @@ export function cloudflareWithDependencies(environments, dependencies) {
|
|
|
33
26
|
version: packageVersion,
|
|
34
27
|
environments,
|
|
35
28
|
prepare: dependencies.artifact,
|
|
36
|
-
|
|
37
|
-
const workerArtifact = loadWorkerArtifact(context.artifact);
|
|
38
|
-
const identity = await dependencies.signingIdentity({
|
|
39
|
-
projectDir: context.projectDir,
|
|
40
|
-
source: parameters.signingIdentity,
|
|
41
|
-
origin: context.artifact.build.schema.compiled.root.origin,
|
|
42
|
-
mode: 'local',
|
|
43
|
-
});
|
|
44
|
-
const secrets = signingIdentitySecrets(context.secrets, identity);
|
|
45
|
-
const directory = join(context.projectDir, '.astrale', 'runtime');
|
|
46
|
-
await dependencies.materialize(directory, context.artifact.files);
|
|
47
|
-
const frontend = await dependencies.startFrontend({
|
|
48
|
-
frontend: context.artifact.build.frontend,
|
|
49
|
-
projectDir: context.projectDir,
|
|
50
|
-
});
|
|
51
|
-
const name = workerName(parameters, context.artifact.build.schema.compiled.root.origin);
|
|
52
|
-
// A local workerd cannot reach the default deployed router Worker through a service
|
|
53
|
-
// binding. Keep it for remote isolates and explicit multi-Worker development; ordinary
|
|
54
|
-
// local execution falls back to the admitted fetch path when ROUTER is absent.
|
|
55
|
-
const router = parameters.remote === true || parameters.router !== undefined
|
|
56
|
-
? resolveRouter(parameters.router)
|
|
57
|
-
: undefined;
|
|
58
|
-
const configPath = join(directory, 'wrangler.watch.jsonc');
|
|
59
|
-
await writeFile(configPath, generateWranglerConfig({
|
|
60
|
-
workerName: name,
|
|
61
|
-
main: `./${workerArtifact.release.entrypoint}`,
|
|
62
|
-
modules: workerModules(workerArtifact.release),
|
|
63
|
-
vars: {
|
|
64
|
-
...parameters.vars,
|
|
65
|
-
...(parameters.host === undefined ? {} : { WORKER_URL: parameters.host }),
|
|
66
|
-
...(frontend === undefined ? {} : { FRONTEND_DEV_URL: frontend.url }),
|
|
67
|
-
},
|
|
68
|
-
secretBindings: Object.keys(secrets).sort(),
|
|
69
|
-
...(context.artifact.build.frontend?.source.kind === 'vite'
|
|
70
|
-
? {
|
|
71
|
-
frontend: {
|
|
72
|
-
directory: './frontend',
|
|
73
|
-
routes: frontendRoutes(context.artifact),
|
|
74
|
-
dev: true,
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
: {}),
|
|
78
|
-
...(router === undefined
|
|
79
|
-
? {}
|
|
80
|
-
: { router: { binding: router.binding, service: router.service } }),
|
|
81
|
-
...(parameters.wrangler === undefined ? {} : { wrangler: parameters.wrangler }),
|
|
82
|
-
selfBinding: true,
|
|
83
|
-
}));
|
|
84
|
-
let worker;
|
|
85
|
-
try {
|
|
86
|
-
worker = await dependencies.runWranglerDev({
|
|
87
|
-
projectDir: context.projectDir,
|
|
88
|
-
configPath,
|
|
89
|
-
port: parameters.port ?? 8787,
|
|
90
|
-
remote: parameters.remote ?? false,
|
|
91
|
-
secrets,
|
|
92
|
-
autoPickPort: parameters.port === undefined,
|
|
93
|
-
pinLocalOrigin: parameters.host === undefined && parameters.remote !== true,
|
|
94
|
-
...(parameters.host === undefined ? {} : { ip: '0.0.0.0' }),
|
|
95
|
-
onReload: context.onReload,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
catch (cause) {
|
|
99
|
-
await frontend?.stop().catch(() => undefined);
|
|
100
|
-
throw cause;
|
|
101
|
-
}
|
|
102
|
-
return Object.freeze({
|
|
103
|
-
url: parameters.host ?? worker.url,
|
|
104
|
-
localUrl: worker.url,
|
|
105
|
-
async stop() {
|
|
106
|
-
const results = await Promise.allSettled([
|
|
107
|
-
worker.stop(),
|
|
108
|
-
frontend?.stop() ?? Promise.resolve(),
|
|
109
|
-
]);
|
|
110
|
-
const failures = results.filter((result) => result.status === 'rejected');
|
|
111
|
-
if (failures.length === 1)
|
|
112
|
-
throw failures[0].reason;
|
|
113
|
-
if (failures.length > 1) {
|
|
114
|
-
throw new AggregateError(failures.map((failure) => failure.reason), 'Cloudflare development cleanup failed.');
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
},
|
|
29
|
+
develop: (parameters, context) => dependencies.develop(parameters, context),
|
|
119
30
|
deploy: (parameters, context) => deployCloudflare(parameters, context, dependencies),
|
|
120
31
|
secretsFile: (parameters) => parameters.secrets,
|
|
121
32
|
});
|
|
122
33
|
}
|
|
123
|
-
function frontendRoutes(artifact) {
|
|
124
|
-
const frontend = artifact.build.frontend;
|
|
125
|
-
if (frontend === undefined || frontend.source.kind !== 'vite')
|
|
126
|
-
return [];
|
|
127
|
-
return Object.freeze(frontend.routes
|
|
128
|
-
.flatMap((route) => frontend.source.kind === 'vite' && frontend.source.spa && route.path !== '/'
|
|
129
|
-
? [route.path, `${route.path}/*`]
|
|
130
|
-
: route.path === '/'
|
|
131
|
-
? ['/*']
|
|
132
|
-
: [route.path])
|
|
133
|
-
.sort());
|
|
134
|
-
}
|
|
135
34
|
export async function waitForReadiness(verify, release, signal) {
|
|
136
35
|
const deadline = Date.now() + 90_000;
|
|
137
36
|
let last;
|
package/dist/build/artifact.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SIGNING_IDENTITY_BINDING } from '../identity/binding.js';
|
|
|
5
5
|
import { workerDeployment } from '../release/index.js';
|
|
6
6
|
import { generateWranglerConfig } from './codegen/wrangler.js';
|
|
7
7
|
import { deploymentAddressing, resolveRouter, workerName } from './configuration.js';
|
|
8
|
-
import { frontendFiles } from './frontend
|
|
8
|
+
import { frontendFiles } from './frontend/files.js';
|
|
9
9
|
import { frontendRoutes } from './frontend/routes.js';
|
|
10
10
|
import { encodeWorkerArtifactManifest, inferMediaType, workerContentDigest, WORKER_MANIFEST_PATH, } from './worker/artifact.manifest.js';
|
|
11
11
|
import { prepareExecution, RELEASE_ENTRY, RELEASE_SERVICE, runtimeVars, } from './worker/execution.prepare.js';
|
|
@@ -38,10 +38,12 @@ async function prepare(params, context, direct) {
|
|
|
38
38
|
configuredIssuer,
|
|
39
39
|
router,
|
|
40
40
|
});
|
|
41
|
-
const frontend =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const frontend = context.mode === 'development'
|
|
42
|
+
? []
|
|
43
|
+
: await frontendFiles({
|
|
44
|
+
frontend: context.build.frontend,
|
|
45
|
+
projectDir: context.projectDir,
|
|
46
|
+
});
|
|
45
47
|
const releaseConfig = generateWranglerConfig({
|
|
46
48
|
workerName: name,
|
|
47
49
|
main: `./${RELEASE_ENTRY}`,
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import type { Build } from '@astrale-os/sdk/deployment/build';
|
|
2
2
|
import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
|
|
3
|
-
export interface FrontendDevHandle {
|
|
4
|
-
readonly url: string;
|
|
5
|
-
stop(): Promise<void>;
|
|
6
|
-
}
|
|
7
3
|
export declare function frontendFiles(input: {
|
|
8
4
|
readonly frontend: Build['frontend'];
|
|
9
5
|
readonly projectDir: string;
|
|
10
6
|
readonly onLog?: (line: string) => void;
|
|
11
7
|
}): Promise<readonly frontendBuild.FrontendFile[]>;
|
|
12
|
-
export declare function startFrontend(input: {
|
|
13
|
-
readonly frontend: Build['frontend'];
|
|
14
|
-
readonly projectDir: string;
|
|
15
|
-
readonly onLog?: (line: string) => void;
|
|
16
|
-
}): Promise<FrontendDevHandle | undefined>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { frontend as frontendBuild } from '@astrale-os/sdk/deployment/build';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { readFile, readdir, rm } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
export async function frontendFiles(input) {
|
|
7
|
+
const frontend = input.frontend;
|
|
8
|
+
if (frontend === undefined)
|
|
9
|
+
return [];
|
|
10
|
+
if (frontend.source.kind === 'external')
|
|
11
|
+
return frontendBuild.files(frontend, []);
|
|
12
|
+
const output = join(input.projectDir, '.astrale', 'frontend-build');
|
|
13
|
+
await rm(output, { recursive: true, force: true });
|
|
14
|
+
await run(viteCommand(input.projectDir), [
|
|
15
|
+
'build',
|
|
16
|
+
'--outDir',
|
|
17
|
+
output,
|
|
18
|
+
'--emptyOutDir',
|
|
19
|
+
...(frontend.source.config === undefined ? [] : ['--config', frontend.source.config]),
|
|
20
|
+
], input.projectDir, input.onLog);
|
|
21
|
+
return frontendBuild.files(frontend, await readFiles(output));
|
|
22
|
+
}
|
|
23
|
+
async function readFiles(root, current = '') {
|
|
24
|
+
const entries = await readdir(join(root, current), { withFileTypes: true });
|
|
25
|
+
const files = [];
|
|
26
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
27
|
+
const path = current === '' ? entry.name : `${current}/${entry.name}`;
|
|
28
|
+
if (entry.isDirectory())
|
|
29
|
+
files.push(...(await readFiles(root, path)));
|
|
30
|
+
else if (entry.isFile()) {
|
|
31
|
+
files.push({ path, content: new Uint8Array(await readFile(join(root, path))) });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return files;
|
|
35
|
+
}
|
|
36
|
+
function viteCommand(projectDir) {
|
|
37
|
+
const command = join(projectDir, 'node_modules', '.bin', 'vite');
|
|
38
|
+
if (!existsSync(command))
|
|
39
|
+
throw new TypeError('Vite frontend requires a project-local vite binary.');
|
|
40
|
+
return command;
|
|
41
|
+
}
|
|
42
|
+
function run(command, args, cwd, onLog) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
45
|
+
let output = '';
|
|
46
|
+
const collect = (buffer) => {
|
|
47
|
+
const value = buffer.toString();
|
|
48
|
+
output += value;
|
|
49
|
+
for (const line of value.split('\n'))
|
|
50
|
+
if (line.trim() !== '')
|
|
51
|
+
onLog?.(line);
|
|
52
|
+
};
|
|
53
|
+
child.stdout?.on('data', collect);
|
|
54
|
+
child.stderr?.on('data', collect);
|
|
55
|
+
child.on('error', reject);
|
|
56
|
+
child.on('exit', (code) => code === 0
|
|
57
|
+
? resolve()
|
|
58
|
+
: reject(new Error(`Vite failed (code ${code ?? 1}):\n${output.slice(-1500)}`)));
|
|
59
|
+
});
|
|
60
|
+
}
|
package/dist/build/index.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
|
2
2
|
export { loadWorkerArtifact } from './worker/index.js';
|
|
3
3
|
export type { WorkerArtifact } from './worker/index.js';
|
|
4
4
|
export { resolveRouter, workerName } from './configuration.js';
|
|
5
|
-
export { frontendFiles
|
|
5
|
+
export { frontendFiles } from './frontend/files.js';
|
|
6
6
|
export { qualifyWorkerBundle } from './qualification.js';
|
|
7
7
|
export { assertSingletonRuntimeRoots } from './roots.js';
|
|
8
8
|
export { signingIdentity } from '../identity/source.js';
|
|
9
9
|
export { signingIdentitySecrets } from '../identity/secret.js';
|
|
10
|
-
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus,
|
|
10
|
+
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
|
package/dist/build/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { artifact, materialize, prepareWorkerArtifact } from './artifact.js';
|
|
2
2
|
export { loadWorkerArtifact } from './worker/index.js';
|
|
3
3
|
export { resolveRouter, workerName } from './configuration.js';
|
|
4
|
-
export { frontendFiles
|
|
4
|
+
export { frontendFiles } from './frontend/files.js';
|
|
5
5
|
export { qualifyWorkerBundle } from './qualification.js';
|
|
6
6
|
export { assertSingletonRuntimeRoots } from './roots.js';
|
|
7
7
|
export { signingIdentity } from '../identity/source.js';
|
|
8
8
|
export { signingIdentitySecrets } from '../identity/secret.js';
|
|
9
|
-
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus,
|
|
9
|
+
export { runWranglerBundle, runWranglerDeploy, runWranglerDeploymentStatus, runWranglerTypes, } from '../wrangler/index.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { SIGNING_IDENTITY_BINDING } from '../../identity/binding.js';
|
|
4
|
+
import { runWranglerBundle } from '../../wrangler/bundle.js';
|
|
4
5
|
import { admitWorkerBundle } from '../bundle/admit.js';
|
|
5
6
|
import { generateExecutionEntry, generateExecutionService } from '../codegen/execution.js';
|
|
6
7
|
import { generateWranglerConfig } from '../codegen/wrangler.js';
|
|
7
8
|
import { workerInvocationTimeout, workerRequestLimit } from '../configuration.js';
|
|
8
9
|
import { qualifyWorkerBundle } from '../qualification.js';
|
|
9
|
-
import { runWranglerBundle } from '../wrangler-cli.js';
|
|
10
10
|
import { workerModuleSpecifier } from './specifier.js';
|
|
11
11
|
export const RELEASE_ENTRY = 'bundle/index.mjs';
|
|
12
12
|
export const RELEASE_SERVICE = 'bundle/service.mjs';
|
package/dist/deploy/deploy.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { DeployContext, DeployResult } from '@astrale-os/sdk/deployment/adapter';
|
|
2
2
|
import type { Release } from '@astrale-os/sdk/deployment/release';
|
|
3
3
|
import type { materialize } from '../build/artifact.js';
|
|
4
|
-
import type { runWranglerDeploy, runWranglerDeploymentStatus } from '../build/wrangler-cli.js';
|
|
5
4
|
import type { signingIdentity } from '../identity/source.js';
|
|
6
5
|
import type { CloudflareParams } from '../params.js';
|
|
6
|
+
import type { runWranglerDeploy } from '../wrangler/deploy.js';
|
|
7
|
+
import type { runWranglerDeploymentStatus } from '../wrangler/status.js';
|
|
7
8
|
export interface CloudflareDeployDependencies {
|
|
8
9
|
readonly materialize: typeof materialize;
|
|
9
10
|
readonly runWranglerDeploy: typeof runWranglerDeploy;
|
package/dist/deploy/deploy.js
CHANGED
|
@@ -4,8 +4,8 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { workerName } from '../build/configuration.js';
|
|
5
5
|
import { admitDirectPlacement } from '../build/direct.js';
|
|
6
6
|
import { loadWorkerArtifact } from '../build/worker/index.js';
|
|
7
|
-
import { WranglerDeployError } from '../build/wrangler-cli.js';
|
|
8
7
|
import { signingIdentitySecrets } from '../identity/secret.js';
|
|
8
|
+
import { WranglerDeployError } from '../wrangler/deploy.js';
|
|
9
9
|
const NONE = Object.freeze({ kind: 'none' });
|
|
10
10
|
/** Atomically advance one stable Worker address to the prepared implementation. */
|
|
11
11
|
export async function deployCloudflare(parameters, context, dependencies) {
|
|
@@ -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,15 @@
|
|
|
1
|
+
import type { DevelopmentContext, DevelopmentSession } from '@astrale-os/sdk/deployment/adapter';
|
|
2
|
+
import type { CloudflareParams } from '../params.js';
|
|
3
|
+
import { signingIdentity } from '../identity/source.js';
|
|
4
|
+
import { startFrontend } from './frontend.js';
|
|
5
|
+
import { allocatePort } from './port.js';
|
|
6
|
+
import { verifyDevelopmentRelease } from './readiness.js';
|
|
7
|
+
import { startWorker } from './worker.js';
|
|
8
|
+
export interface CloudflareDevelopmentDependencies {
|
|
9
|
+
readonly signingIdentity: typeof signingIdentity;
|
|
10
|
+
readonly startFrontend: typeof startFrontend;
|
|
11
|
+
readonly startWorker: typeof startWorker;
|
|
12
|
+
readonly allocatePort: typeof allocatePort;
|
|
13
|
+
readonly verify: typeof verifyDevelopmentRelease;
|
|
14
|
+
}
|
|
15
|
+
export declare function developCloudflare(parameters: CloudflareParams, context: DevelopmentContext, dependencies?: CloudflareDevelopmentDependencies): Promise<DevelopmentSession>;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { addressing, assemble } from '@astrale-os/sdk/deployment/release';
|
|
2
|
+
import { mkdir, rename, rm, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { generateWranglerConfig } from '../build/codegen/wrangler.js';
|
|
5
|
+
import { resolveRouter, workerName } from '../build/configuration.js';
|
|
6
|
+
import { loadWorkerArtifact } from '../build/worker/index.js';
|
|
7
|
+
import { workerModules } from '../build/worker/modules.js';
|
|
8
|
+
import { signingIdentitySecrets } from '../identity/secret.js';
|
|
9
|
+
import { signingIdentity } from '../identity/source.js';
|
|
10
|
+
import { startFrontend } from './frontend.js';
|
|
11
|
+
import { admitHost } from './host.js';
|
|
12
|
+
import { allocatePort } from './port.js';
|
|
13
|
+
import { verifyDevelopmentRelease } from './readiness.js';
|
|
14
|
+
import { startWorker } from './worker.js';
|
|
15
|
+
export async function developCloudflare(parameters, context, dependencies = {
|
|
16
|
+
signingIdentity,
|
|
17
|
+
startFrontend,
|
|
18
|
+
startWorker,
|
|
19
|
+
allocatePort,
|
|
20
|
+
verify: verifyDevelopmentRelease,
|
|
21
|
+
}) {
|
|
22
|
+
const hostSource = context.overrides.publicUrl ?? parameters.host;
|
|
23
|
+
const host = hostSource === undefined ? undefined : admitHost(hostSource);
|
|
24
|
+
const configuredPort = context.overrides.localPort ?? parameters.port;
|
|
25
|
+
if (host !== undefined && configuredPort === undefined) {
|
|
26
|
+
throw new TypeError('Cloudflare development with a public host requires a strict local port.');
|
|
27
|
+
}
|
|
28
|
+
const port = configuredPort ?? (await dependencies.allocatePort());
|
|
29
|
+
const directory = join(context.projectDir, '.astrale', 'development', context.environment, 'cloudflare');
|
|
30
|
+
await mkdir(directory, { recursive: true });
|
|
31
|
+
const configPath = join(directory, 'wrangler.development.jsonc');
|
|
32
|
+
const identity = await dependencies.signingIdentity({
|
|
33
|
+
projectDir: context.projectDir,
|
|
34
|
+
source: parameters.signingIdentity,
|
|
35
|
+
origin: context.artifact.build.schema.compiled.root.origin,
|
|
36
|
+
mode: 'local',
|
|
37
|
+
});
|
|
38
|
+
let current = Object.freeze({ artifact: context.artifact, secrets: context.secrets });
|
|
39
|
+
let artifactPaths = new Set();
|
|
40
|
+
let resources;
|
|
41
|
+
let stopping = false;
|
|
42
|
+
let resolveFinished;
|
|
43
|
+
let rejectFinished;
|
|
44
|
+
const finished = new Promise((resolve, reject) => {
|
|
45
|
+
resolveFinished = resolve;
|
|
46
|
+
rejectFinished = reject;
|
|
47
|
+
});
|
|
48
|
+
const realize = async (input) => {
|
|
49
|
+
artifactPaths = await applyArtifact(directory, artifactPaths, input.artifact);
|
|
50
|
+
if (input.artifact.build.frontend?.source.kind === 'vite') {
|
|
51
|
+
await mkdir(join(directory, 'frontend'), { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
const frontend = await dependencies.startFrontend({
|
|
54
|
+
frontend: input.artifact.build.frontend,
|
|
55
|
+
projectDir: context.projectDir,
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(configPath, configuration(parameters, input.artifact, frontend?.url, host, Object.keys(signingIdentitySecrets(input.secrets, identity)).sort()));
|
|
59
|
+
const worker = await dependencies.startWorker({
|
|
60
|
+
projectDir: context.projectDir,
|
|
61
|
+
configPath,
|
|
62
|
+
port,
|
|
63
|
+
remote: parameters.remote ?? false,
|
|
64
|
+
secrets: signingIdentitySecrets(input.secrets, identity),
|
|
65
|
+
autoPickPort: false,
|
|
66
|
+
pinLocalOrigin: host === undefined && parameters.remote !== true,
|
|
67
|
+
...(host === undefined ? {} : { ip: '0.0.0.0' }),
|
|
68
|
+
});
|
|
69
|
+
const value = Object.freeze({ ...(frontend === undefined ? {} : { frontend }), worker });
|
|
70
|
+
supervise(value);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
catch (cause) {
|
|
74
|
+
await frontend?.stop().catch(() => undefined);
|
|
75
|
+
throw cause;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const supervise = (selected) => {
|
|
79
|
+
for (const child of [selected.worker.finished, selected.frontend?.finished]) {
|
|
80
|
+
child?.catch((cause) => {
|
|
81
|
+
if (!stopping && resources === selected)
|
|
82
|
+
rejectFinished(cause);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const placementFor = (selected) => Object.freeze({ releaseUrl: host ?? selected.worker.url, localUrl: selected.worker.url });
|
|
87
|
+
const verify = async (input, selected) => {
|
|
88
|
+
const placement = placementFor(selected);
|
|
89
|
+
const release = assemble(input.artifact.build, addressing(placement.releaseUrl));
|
|
90
|
+
await dependencies.verify({ release, localUrl: placement.localUrl, signal: context.signal });
|
|
91
|
+
await context.onPlacement(placement);
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
resources = await realize(current);
|
|
95
|
+
await verify(current, resources);
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
stopping = true;
|
|
99
|
+
if (resources !== undefined)
|
|
100
|
+
await stopResources(resources).catch(() => undefined);
|
|
101
|
+
await rm(directory, { recursive: true, force: true }).catch(() => undefined);
|
|
102
|
+
throw cause;
|
|
103
|
+
}
|
|
104
|
+
let updateTail = Promise.resolve();
|
|
105
|
+
const session = Object.freeze({
|
|
106
|
+
current: () => placementFor(resources),
|
|
107
|
+
update(next) {
|
|
108
|
+
if (stopping)
|
|
109
|
+
return Promise.reject(new Error('Cloudflare development session is stopping.'));
|
|
110
|
+
const operation = updateTail.then(async () => {
|
|
111
|
+
if (stopping)
|
|
112
|
+
throw new Error('Cloudflare development session is stopping.');
|
|
113
|
+
sameDomain(current.artifact, next.artifact);
|
|
114
|
+
const previous = current;
|
|
115
|
+
const previousResources = resources;
|
|
116
|
+
const restart = frontendSignature(previous.artifact) !== frontendSignature(next.artifact) ||
|
|
117
|
+
stable(previous.secrets) !== stable(next.secrets);
|
|
118
|
+
if (!restart) {
|
|
119
|
+
try {
|
|
120
|
+
artifactPaths = await applyArtifact(directory, artifactPaths, next.artifact);
|
|
121
|
+
await writeFile(configPath, configuration(parameters, next.artifact, resources.frontend?.url, host, Object.keys(signingIdentitySecrets(next.secrets, identity)).sort()));
|
|
122
|
+
await verify(next, resources);
|
|
123
|
+
current = Object.freeze({ artifact: next.artifact, secrets: next.secrets });
|
|
124
|
+
}
|
|
125
|
+
catch (cause) {
|
|
126
|
+
artifactPaths = await applyArtifact(directory, artifactPaths, previous.artifact);
|
|
127
|
+
await writeFile(configPath, configuration(parameters, previous.artifact, resources.frontend?.url, host, Object.keys(signingIdentitySecrets(previous.secrets, identity)).sort()));
|
|
128
|
+
throw cause;
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await stopResources(previousResources);
|
|
134
|
+
}
|
|
135
|
+
catch (cause) {
|
|
136
|
+
rejectFinished(cause);
|
|
137
|
+
throw cause;
|
|
138
|
+
}
|
|
139
|
+
let replacement;
|
|
140
|
+
try {
|
|
141
|
+
replacement = await realize(next);
|
|
142
|
+
await verify(next, replacement);
|
|
143
|
+
resources = replacement;
|
|
144
|
+
current = Object.freeze({ artifact: next.artifact, secrets: next.secrets });
|
|
145
|
+
}
|
|
146
|
+
catch (cause) {
|
|
147
|
+
if (replacement !== undefined)
|
|
148
|
+
await stopResources(replacement).catch(() => undefined);
|
|
149
|
+
try {
|
|
150
|
+
resources = await realize(previous);
|
|
151
|
+
await verify(previous, resources);
|
|
152
|
+
}
|
|
153
|
+
catch (rollback) {
|
|
154
|
+
rejectFinished(new AggregateError([cause, rollback], 'Cloudflare development update and rollback failed.'));
|
|
155
|
+
}
|
|
156
|
+
throw cause;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
updateTail = operation.catch(() => undefined);
|
|
160
|
+
return operation;
|
|
161
|
+
},
|
|
162
|
+
finished,
|
|
163
|
+
async stop() {
|
|
164
|
+
if (stopping)
|
|
165
|
+
return finished.catch(() => undefined);
|
|
166
|
+
stopping = true;
|
|
167
|
+
await updateTail;
|
|
168
|
+
try {
|
|
169
|
+
try {
|
|
170
|
+
await stopResources(resources);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
await rm(directory, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
resolveFinished();
|
|
176
|
+
}
|
|
177
|
+
catch (cause) {
|
|
178
|
+
rejectFinished(cause);
|
|
179
|
+
throw cause;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
return session;
|
|
184
|
+
}
|
|
185
|
+
function configuration(parameters, artifact, frontendUrl, host, secretBindings) {
|
|
186
|
+
const worker = loadWorkerArtifact(artifact);
|
|
187
|
+
const router = parameters.remote === true || parameters.router !== undefined
|
|
188
|
+
? resolveRouter(parameters.router)
|
|
189
|
+
: undefined;
|
|
190
|
+
return generateWranglerConfig({
|
|
191
|
+
workerName: workerName(parameters, artifact.build.schema.compiled.root.origin),
|
|
192
|
+
main: `./${worker.release.entrypoint}`,
|
|
193
|
+
modules: workerModules(worker.release),
|
|
194
|
+
vars: {
|
|
195
|
+
...parameters.vars,
|
|
196
|
+
...(host === undefined ? {} : { WORKER_URL: host }),
|
|
197
|
+
...(frontendUrl === undefined ? {} : { FRONTEND_DEV_URL: frontendUrl }),
|
|
198
|
+
},
|
|
199
|
+
secretBindings,
|
|
200
|
+
...(artifact.build.frontend?.source.kind === 'vite'
|
|
201
|
+
? { frontend: { directory: './frontend', routes: frontendRoutes(artifact), dev: true } }
|
|
202
|
+
: {}),
|
|
203
|
+
...(router === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: { router: { binding: router.binding, service: router.service } }),
|
|
206
|
+
...(parameters.wrangler === undefined ? {} : { wrangler: parameters.wrangler }),
|
|
207
|
+
selfBinding: true,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async function applyArtifact(root, previous, artifact) {
|
|
211
|
+
const next = new Set(artifact.files.map((file) => file.path));
|
|
212
|
+
for (const file of artifact.files) {
|
|
213
|
+
const path = join(root, file.path);
|
|
214
|
+
const staging = `${path}.next`;
|
|
215
|
+
await mkdir(dirname(path), { recursive: true });
|
|
216
|
+
await writeFile(staging, file.bytes());
|
|
217
|
+
await rename(staging, path);
|
|
218
|
+
}
|
|
219
|
+
for (const path of previous) {
|
|
220
|
+
if (!next.has(path))
|
|
221
|
+
await unlink(join(root, path)).catch(() => undefined);
|
|
222
|
+
}
|
|
223
|
+
return next;
|
|
224
|
+
}
|
|
225
|
+
async function stopResources(resources) {
|
|
226
|
+
const results = await Promise.allSettled([
|
|
227
|
+
resources.worker.stop(),
|
|
228
|
+
resources.frontend?.stop() ?? Promise.resolve(),
|
|
229
|
+
]);
|
|
230
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
231
|
+
if (failures.length === 1)
|
|
232
|
+
throw failures[0].reason;
|
|
233
|
+
if (failures.length > 1) {
|
|
234
|
+
throw new AggregateError(failures.map((failure) => failure.reason), 'Cloudflare development cleanup failed.');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function frontendRoutes(artifact) {
|
|
238
|
+
const frontend = artifact.build.frontend;
|
|
239
|
+
if (frontend === undefined || frontend.source.kind !== 'vite')
|
|
240
|
+
return [];
|
|
241
|
+
return Object.freeze(frontend.routes
|
|
242
|
+
.flatMap((route) => frontend.source.kind === 'vite' && frontend.source.spa && route.path !== '/'
|
|
243
|
+
? [route.path, `${route.path}/*`]
|
|
244
|
+
: route.path === '/'
|
|
245
|
+
? ['/*']
|
|
246
|
+
: [route.path])
|
|
247
|
+
.sort());
|
|
248
|
+
}
|
|
249
|
+
function frontendSignature(artifact) {
|
|
250
|
+
return stable(artifact.build.frontend);
|
|
251
|
+
}
|
|
252
|
+
function sameDomain(previous, next) {
|
|
253
|
+
if (previous.build.schema.compiled.root.origin !== next.build.schema.compiled.root.origin) {
|
|
254
|
+
throw new TypeError('Cloudflare development update changed the Domain origin.');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function stable(input) {
|
|
258
|
+
if (input === null || typeof input !== 'object')
|
|
259
|
+
return JSON.stringify(input);
|
|
260
|
+
if (Array.isArray(input))
|
|
261
|
+
return `[${input.map(stable).join(',')}]`;
|
|
262
|
+
const value = input;
|
|
263
|
+
return `{${Object.keys(value)
|
|
264
|
+
.sort()
|
|
265
|
+
.map((key) => `${JSON.stringify(key)}:${stable(value[key])}`)
|
|
266
|
+
.join(',')}}`;
|
|
267
|
+
}
|