@astrale-os/adapter-cloudflare 0.5.0-beta.2 → 0.5.0-beta.20
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/README.md +11 -0
- package/dist/adapter.d.ts +17 -0
- package/dist/adapter.js +140 -0
- package/dist/build/artifact.d.ts +8 -0
- package/dist/build/artifact.js +179 -0
- package/dist/build/closure.d.ts +2 -0
- package/dist/build/closure.js +22 -0
- package/dist/build/codegen/execution.d.ts +13 -0
- package/dist/build/codegen/execution.js +44 -0
- package/dist/build/codegen/gateway.d.ts +1 -0
- package/dist/build/codegen/gateway.js +7 -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 +10 -1
- package/dist/build/codegen/wrangler.js +22 -16
- package/dist/build/configuration.d.ts +9 -0
- package/dist/build/configuration.js +40 -0
- package/dist/build/frontend-v1.d.ts +16 -0
- package/dist/build/frontend-v1.js +133 -0
- package/dist/build/index.d.ts +8 -16
- package/dist/build/index.js +8 -16
- package/dist/build/metafile.d.ts +5 -0
- package/dist/build/metafile.js +14 -0
- package/dist/build/parse-output.d.ts +0 -1
- package/dist/build/parse-output.js +0 -1
- 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/wrangler-cli.d.ts +53 -34
- package/dist/build/wrangler-cli.js +284 -111
- package/dist/cloudflare.d.ts +1 -34
- package/dist/cloudflare.js +1 -205
- package/dist/deploy/deploy.d.ts +17 -0
- package/dist/deploy/deploy.js +207 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -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 +98 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -4
- package/dist/params.d.ts +18 -5
- package/dist/params.js +0 -1
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/namespace.d.ts +2 -0
- package/dist/release/namespace.js +6 -0
- package/dist/release/target.d.ts +2 -0
- package/dist/release/target.js +9 -0
- package/dist/worker/entry.d.ts +34 -0
- package/dist/worker/entry.js +157 -0
- package/dist/worker/fetch.d.ts +11 -0
- package/dist/worker/fetch.js +31 -0
- package/dist/worker/gateway.d.ts +15 -0
- package/dist/worker/gateway.js +26 -0
- package/dist/worker/identity.d.ts +7 -0
- package/dist/worker/identity.js +23 -0
- package/dist/worker/index.d.ts +3 -48
- package/dist/worker/index.js +3 -186
- package/package.json +16 -18
- package/template/astrale.config.ts +19 -11
- 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.map +0 -1
- 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.map +0 -1
- 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/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 -362
- package/src/cloudflare.ts +0 -251
- package/src/index.ts +0 -21
- package/src/params.ts +0 -92
- package/src/worker/index.ts +0 -276
- package/src/worker/tsconfig.json +0 -10
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# `@astrale-os/adapter-cloudflare`
|
|
2
|
+
|
|
3
|
+
Cloudflare Worker deployment adapter for Astrale Domains.
|
|
4
|
+
|
|
5
|
+
During the SDK beta program, install the beta package set explicitly:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @astrale-os/sdk@beta @astrale-os/adapter-cloudflare@beta
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Bare installs return to stable releases when the package set is promoted and npm `latest` advances.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Release } from '@astrale-os/sdk/deployment/release';
|
|
2
|
+
import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
3
|
+
import type { CloudflareParams } from './params.js';
|
|
4
|
+
import { artifact } from './build/artifact.js';
|
|
5
|
+
import { startFrontend } from './build/frontend-v1.js';
|
|
6
|
+
import { runWranglerDev } from './build/wrangler-cli.js';
|
|
7
|
+
import { type CloudflareDeployDependencies } from './deploy/index.js';
|
|
8
|
+
import { signingIdentity } from './identity/source.js';
|
|
9
|
+
export interface CloudflareDependencies extends CloudflareDeployDependencies {
|
|
10
|
+
readonly artifact: typeof artifact;
|
|
11
|
+
readonly runWranglerDev: typeof runWranglerDev;
|
|
12
|
+
readonly startFrontend: typeof startFrontend;
|
|
13
|
+
readonly signingIdentity: typeof signingIdentity;
|
|
14
|
+
}
|
|
15
|
+
export declare function cloudflare(environments: Readonly<Record<string, CloudflareParams>>): import("@astrale-os/sdk/deployment/adapter").Adapter<CloudflareParams>;
|
|
16
|
+
export declare function cloudflareWithDependencies(environments: Readonly<Record<string, CloudflareParams>>, dependencies: CloudflareDependencies): import("@astrale-os/sdk/deployment/adapter").Adapter<CloudflareParams>;
|
|
17
|
+
export declare function waitForReadiness(verify: typeof verifyReadiness, release: Release): Promise<void>;
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineAdapter } from '@astrale-os/sdk/deployment/adapter';
|
|
2
|
+
import { verifyReadiness } from '@astrale-os/sdk/deployment/verify';
|
|
3
|
+
import { writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
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 { ensureWranglerDispatchNamespace, runWranglerDeploymentStatus, runWranglerDeploy, runWranglerDev, runWranglerReleaseDeploy, } from './build/wrangler-cli.js';
|
|
11
|
+
import { deployCloudflare } from './deploy/index.js';
|
|
12
|
+
import { signingIdentitySecrets } from './identity/secret.js';
|
|
13
|
+
import { signingIdentity } from './identity/source.js';
|
|
14
|
+
const packageVersion = createRequire(import.meta.url)('../package.json')
|
|
15
|
+
.version;
|
|
16
|
+
export function cloudflare(environments) {
|
|
17
|
+
return cloudflareWithDependencies(environments, {
|
|
18
|
+
artifact,
|
|
19
|
+
materialize,
|
|
20
|
+
ensureWranglerDispatchNamespace,
|
|
21
|
+
runWranglerDeploy,
|
|
22
|
+
runWranglerReleaseDeploy,
|
|
23
|
+
runWranglerDeploymentStatus,
|
|
24
|
+
runWranglerDev,
|
|
25
|
+
startFrontend,
|
|
26
|
+
signingIdentity,
|
|
27
|
+
waitForReadiness: (release) => waitForReadiness(verifyReadiness, release),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function cloudflareWithDependencies(environments, dependencies) {
|
|
31
|
+
return defineAdapter({
|
|
32
|
+
name: 'cloudflare',
|
|
33
|
+
version: packageVersion,
|
|
34
|
+
environments,
|
|
35
|
+
prepare: dependencies.artifact,
|
|
36
|
+
async watch(parameters, context) {
|
|
37
|
+
const identity = await dependencies.signingIdentity({
|
|
38
|
+
projectDir: context.projectDir,
|
|
39
|
+
source: parameters.signingIdentity,
|
|
40
|
+
origin: context.artifact.build.schema.compiled.root.origin,
|
|
41
|
+
mode: 'local',
|
|
42
|
+
});
|
|
43
|
+
const secrets = signingIdentitySecrets(context.secrets, identity);
|
|
44
|
+
const directory = join(context.projectDir, '.astrale', 'runtime');
|
|
45
|
+
await dependencies.materialize(directory, context.artifact.files);
|
|
46
|
+
const frontend = await dependencies.startFrontend({
|
|
47
|
+
frontend: context.artifact.build.frontend,
|
|
48
|
+
projectDir: context.projectDir,
|
|
49
|
+
});
|
|
50
|
+
const name = workerName(parameters, context.artifact.build.schema.compiled.root.origin);
|
|
51
|
+
const router = resolveRouter(parameters.router);
|
|
52
|
+
const configPath = join(directory, 'wrangler.watch.jsonc');
|
|
53
|
+
await writeFile(configPath, generateWranglerConfig({
|
|
54
|
+
workerName: name,
|
|
55
|
+
vars: {
|
|
56
|
+
...parameters.vars,
|
|
57
|
+
...(parameters.host === undefined ? {} : { WORKER_URL: parameters.host }),
|
|
58
|
+
...(frontend === undefined ? {} : { FRONTEND_DEV_URL: frontend.url }),
|
|
59
|
+
},
|
|
60
|
+
secretBindings: Object.keys(secrets).sort(),
|
|
61
|
+
...(context.artifact.build.frontend?.source.kind === 'vite'
|
|
62
|
+
? {
|
|
63
|
+
frontend: {
|
|
64
|
+
directory: './frontend',
|
|
65
|
+
routes: frontendRoutes(context.artifact),
|
|
66
|
+
dev: true,
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
: {}),
|
|
70
|
+
...(router === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { router: { binding: router.binding, service: router.service } }),
|
|
73
|
+
...(parameters.wrangler === undefined ? {} : { wrangler: parameters.wrangler }),
|
|
74
|
+
selfBinding: true,
|
|
75
|
+
}));
|
|
76
|
+
let worker;
|
|
77
|
+
try {
|
|
78
|
+
worker = await dependencies.runWranglerDev({
|
|
79
|
+
projectDir: context.projectDir,
|
|
80
|
+
configPath,
|
|
81
|
+
port: parameters.port ?? 8787,
|
|
82
|
+
remote: parameters.remote ?? false,
|
|
83
|
+
secrets,
|
|
84
|
+
autoPickPort: parameters.port === undefined,
|
|
85
|
+
...(parameters.host === undefined ? {} : { ip: '0.0.0.0' }),
|
|
86
|
+
onReload: context.onReload,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
await frontend?.stop().catch(() => undefined);
|
|
91
|
+
throw cause;
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
url: parameters.host ?? worker.url,
|
|
95
|
+
localUrl: worker.url,
|
|
96
|
+
async stop() {
|
|
97
|
+
const results = await Promise.allSettled([
|
|
98
|
+
worker.stop(),
|
|
99
|
+
frontend?.stop() ?? Promise.resolve(),
|
|
100
|
+
]);
|
|
101
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
102
|
+
if (failures.length === 1)
|
|
103
|
+
throw failures[0].reason;
|
|
104
|
+
if (failures.length > 1) {
|
|
105
|
+
throw new AggregateError(failures.map((failure) => failure.reason), 'Cloudflare development cleanup failed.');
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
deploy: (parameters, context) => deployCloudflare(parameters, context, dependencies),
|
|
111
|
+
secretsFile: (parameters) => parameters.secrets,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function frontendRoutes(artifact) {
|
|
115
|
+
const frontend = artifact.build.frontend;
|
|
116
|
+
if (frontend === undefined || frontend.source.kind !== 'vite')
|
|
117
|
+
return [];
|
|
118
|
+
return Object.freeze(frontend.routes
|
|
119
|
+
.flatMap((route) => frontend.source.kind === 'vite' && frontend.source.spa && route.path !== '/'
|
|
120
|
+
? [route.path, `${route.path}/*`]
|
|
121
|
+
: route.path === '/'
|
|
122
|
+
? ['/*']
|
|
123
|
+
: [route.path])
|
|
124
|
+
.sort());
|
|
125
|
+
}
|
|
126
|
+
export async function waitForReadiness(verify, release) {
|
|
127
|
+
const deadline = Date.now() + 90_000;
|
|
128
|
+
let last;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
try {
|
|
131
|
+
await verify(release, { signal: AbortSignal.timeout(5_000) });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
catch (cause) {
|
|
135
|
+
last = cause;
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw last instanceof Error ? last : new Error('Cloudflare Release did not become ready.');
|
|
140
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArtifactSource, PrepareContext } from '@astrale-os/sdk/deployment/adapter';
|
|
2
|
+
import type { CloudflareParams } from '../params.js';
|
|
3
|
+
/** Prepare one revision Service and its stateless gateway without provider mutation. */
|
|
4
|
+
export declare function artifact(params: CloudflareParams, context: PrepareContext): Promise<ArtifactSource>;
|
|
5
|
+
export declare function materialize(root: string, files: readonly {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
bytes(): Uint8Array;
|
|
8
|
+
}[]): Promise<void>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { material } from '@astrale-os/sdk/deployment/build';
|
|
2
|
+
import { addressing } from '@astrale-os/sdk/deployment/release';
|
|
3
|
+
import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { SIGNING_IDENTITY_BINDING } from '../identity/binding.js';
|
|
7
|
+
import { releaseNamespace, releaseWorkerName } from '../release/index.js';
|
|
8
|
+
import { generateExecutionEntry } from './codegen/execution.js';
|
|
9
|
+
import { generateGatewayEntry } from './codegen/gateway.js';
|
|
10
|
+
import { generateWranglerConfig } from './codegen/wrangler.js';
|
|
11
|
+
import { deploymentAddressing, resolveRouter, workerName } from './configuration.js';
|
|
12
|
+
import { frontendFiles } from './frontend-v1.js';
|
|
13
|
+
import { qualifyWorkerBundle } from './qualification.js';
|
|
14
|
+
import { runWranglerBundle } from './wrangler-cli.js';
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
/** Prepare one revision Service and its stateless gateway without provider mutation. */
|
|
17
|
+
export async function artifact(params, context) {
|
|
18
|
+
const stateDirectory = join(context.projectDir, '.astrale');
|
|
19
|
+
const directory = join(stateDirectory, 'runtime');
|
|
20
|
+
await mkdir(directory, { recursive: true });
|
|
21
|
+
const runtimePath = resolve(context.projectDir, context.runtime.reference.path);
|
|
22
|
+
const runtimeSpecifier = moduleSpecifier(relative(directory, runtimePath));
|
|
23
|
+
const router = resolveRouter(params.router);
|
|
24
|
+
const name = workerName(params, context.build.schema.compiled.root.origin);
|
|
25
|
+
const revision = context.build.schema.compiled.root.revision;
|
|
26
|
+
const namespace = releaseNamespace(name);
|
|
27
|
+
const target = releaseWorkerName(revision);
|
|
28
|
+
const configuredIssuer = deploymentAddressing(params);
|
|
29
|
+
const source = generateExecutionEntry({
|
|
30
|
+
origin: context.build.schema.compiled.root.origin,
|
|
31
|
+
runtimeSpecifier,
|
|
32
|
+
material: material(context.build),
|
|
33
|
+
...(configuredIssuer === undefined ? { providerAssignedWorker: name } : {}),
|
|
34
|
+
...(router === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: {
|
|
37
|
+
router: {
|
|
38
|
+
binding: router.binding,
|
|
39
|
+
hostSuffix: router.hostSuffix,
|
|
40
|
+
minLabels: router.minLabels,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
const sourcePath = join(directory, 'worker.gen.ts');
|
|
45
|
+
await writeFile(sourcePath, source);
|
|
46
|
+
const configPath = join(directory, 'wrangler.prepare.jsonc');
|
|
47
|
+
await writeFile(configPath, generateWranglerConfig({
|
|
48
|
+
workerName: name,
|
|
49
|
+
...(params.route === undefined ? {} : { route: params.route }),
|
|
50
|
+
vars: {
|
|
51
|
+
...params.vars,
|
|
52
|
+
...(configuredIssuer === undefined ? {} : { WORKER_URL: configuredIssuer }),
|
|
53
|
+
},
|
|
54
|
+
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
55
|
+
...(router === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { router: { binding: router.binding, service: router.service } }),
|
|
58
|
+
...(params.wrangler === undefined ? {} : { wrangler: params.wrangler }),
|
|
59
|
+
selfBinding: false,
|
|
60
|
+
}));
|
|
61
|
+
const output = join(directory, 'dist-prepare');
|
|
62
|
+
await rm(output, { recursive: true, force: true });
|
|
63
|
+
const emitted = await runWranglerBundle({
|
|
64
|
+
projectDir: context.projectDir,
|
|
65
|
+
configPath,
|
|
66
|
+
outDir: output,
|
|
67
|
+
});
|
|
68
|
+
const frontend = await frontendFiles({
|
|
69
|
+
frontend: context.build.frontend,
|
|
70
|
+
projectDir: context.projectDir,
|
|
71
|
+
});
|
|
72
|
+
await qualifyWorkerBundle({
|
|
73
|
+
metafilePath: emitted.metafilePath,
|
|
74
|
+
inputRoot: dirname(configPath),
|
|
75
|
+
});
|
|
76
|
+
await admitBundle(emitted.bundlePath);
|
|
77
|
+
const bundle = await readFile(emitted.bundlePath);
|
|
78
|
+
const gatewaySource = join(directory, 'gateway.gen.ts');
|
|
79
|
+
await writeFile(gatewaySource, generateGatewayEntry(revision));
|
|
80
|
+
const gatewayPrepareConfig = join(directory, 'wrangler.gateway.prepare.jsonc');
|
|
81
|
+
await writeFile(gatewayPrepareConfig, generateWranglerConfig({
|
|
82
|
+
workerName: name,
|
|
83
|
+
main: './gateway.gen.ts',
|
|
84
|
+
...(params.route === undefined ? {} : { route: params.route }),
|
|
85
|
+
releases: { binding: 'RELEASES', namespace },
|
|
86
|
+
selfBinding: false,
|
|
87
|
+
}));
|
|
88
|
+
const gatewayOutput = join(directory, 'dist-gateway');
|
|
89
|
+
await rm(gatewayOutput, { recursive: true, force: true });
|
|
90
|
+
const gateway = await runWranglerBundle({
|
|
91
|
+
projectDir: context.projectDir,
|
|
92
|
+
configPath: gatewayPrepareConfig,
|
|
93
|
+
outDir: gatewayOutput,
|
|
94
|
+
});
|
|
95
|
+
await qualifyWorkerBundle({
|
|
96
|
+
metafilePath: gateway.metafilePath,
|
|
97
|
+
inputRoot: dirname(gatewayPrepareConfig),
|
|
98
|
+
});
|
|
99
|
+
await admitBundle(gateway.bundlePath);
|
|
100
|
+
const gatewayBundle = await readFile(gateway.bundlePath);
|
|
101
|
+
const releaseConfig = generateWranglerConfig({
|
|
102
|
+
workerName: target,
|
|
103
|
+
main: './bundle/index.mjs',
|
|
104
|
+
vars: {
|
|
105
|
+
...params.vars,
|
|
106
|
+
...(configuredIssuer === undefined ? {} : { WORKER_URL: configuredIssuer }),
|
|
107
|
+
},
|
|
108
|
+
secretBindings: [SIGNING_IDENTITY_BINDING],
|
|
109
|
+
...(context.build.frontend?.source.kind === 'vite'
|
|
110
|
+
? {
|
|
111
|
+
frontend: {
|
|
112
|
+
directory: './frontend',
|
|
113
|
+
routes: frontendRoutes(context.build.frontend),
|
|
114
|
+
dev: false,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
: {}),
|
|
118
|
+
...(router === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { router: { binding: router.binding, service: router.service } }),
|
|
121
|
+
...(params.wrangler === undefined ? {} : { wrangler: params.wrangler }),
|
|
122
|
+
selfBinding: false,
|
|
123
|
+
});
|
|
124
|
+
const gatewayConfig = generateWranglerConfig({
|
|
125
|
+
workerName: name,
|
|
126
|
+
main: './gateway/index.mjs',
|
|
127
|
+
...(params.route === undefined ? {} : { route: params.route }),
|
|
128
|
+
releases: { binding: 'RELEASES', namespace },
|
|
129
|
+
selfBinding: false,
|
|
130
|
+
});
|
|
131
|
+
const files = [
|
|
132
|
+
{ path: 'wrangler.release.jsonc', content: encoder.encode(releaseConfig) },
|
|
133
|
+
{ path: 'wrangler.gateway.jsonc', content: encoder.encode(gatewayConfig) },
|
|
134
|
+
{ path: 'bundle/index.mjs', content: new Uint8Array(bundle) },
|
|
135
|
+
{ path: 'gateway/index.mjs', content: new Uint8Array(gatewayBundle) },
|
|
136
|
+
...frontend.map((file) => ({
|
|
137
|
+
path: `frontend/${file.path}`,
|
|
138
|
+
content: file.bytes(),
|
|
139
|
+
})),
|
|
140
|
+
];
|
|
141
|
+
return Object.freeze({
|
|
142
|
+
entrypoint: 'bundle/index.mjs',
|
|
143
|
+
files: Object.freeze(files),
|
|
144
|
+
addressing: configuredIssuer === undefined
|
|
145
|
+
? Object.freeze({ kind: 'provider-assigned', claim: name })
|
|
146
|
+
: Object.freeze({ kind: 'configured', addressing: addressing(configuredIssuer) }),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function admitBundle(path) {
|
|
150
|
+
const loaded = (await import(`${pathToFileURL(path).href}?admit=${Date.now()}`));
|
|
151
|
+
if (loaded.default === null ||
|
|
152
|
+
typeof loaded.default !== 'object' ||
|
|
153
|
+
typeof Reflect.get(loaded.default, 'fetch') !== 'function') {
|
|
154
|
+
throw new TypeError('Generated Worker bundle does not export a Fetch handler.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function frontendRoutes(frontend) {
|
|
158
|
+
if (frontend.source.kind !== 'vite')
|
|
159
|
+
return [];
|
|
160
|
+
return Object.freeze(frontend.routes
|
|
161
|
+
.flatMap((route) => frontend.source.kind === 'vite' && frontend.source.spa && route.path !== '/'
|
|
162
|
+
? [route.path, `${route.path}/*`]
|
|
163
|
+
: route.path === '/'
|
|
164
|
+
? ['/*']
|
|
165
|
+
: [route.path])
|
|
166
|
+
.sort());
|
|
167
|
+
}
|
|
168
|
+
export async function materialize(root, files) {
|
|
169
|
+
await rm(root, { recursive: true, force: true });
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
const path = join(root, file.path);
|
|
172
|
+
await mkdir(dirname(path), { recursive: true });
|
|
173
|
+
await writeFile(path, file.bytes());
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function moduleSpecifier(input) {
|
|
177
|
+
const normalized = input.split('\\').join('/');
|
|
178
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
179
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readWranglerMetafile } from './metafile.js';
|
|
2
|
+
const FORBIDDEN_RUNTIME_INPUTS = [
|
|
3
|
+
'/v1/builder/',
|
|
4
|
+
'/v1/compiled/compiler/',
|
|
5
|
+
'/node_modules/ajv/',
|
|
6
|
+
'/node_modules/re2js/',
|
|
7
|
+
'/kernel-core/domain/',
|
|
8
|
+
'/core/domain/',
|
|
9
|
+
'/kernel-core/schema/index.',
|
|
10
|
+
'/core/schema/index.',
|
|
11
|
+
];
|
|
12
|
+
/** Prove emitted Worker reachability excludes authoring, compilation, and broad Core Schema owners. */
|
|
13
|
+
export async function assertRuntimeClosure(metafilePath) {
|
|
14
|
+
const metafile = await readWranglerMetafile(metafilePath);
|
|
15
|
+
const forbidden = Object.keys(metafile.inputs).filter((input) => {
|
|
16
|
+
const normalized = `/${input.replaceAll('\\', '/')}`.replace(/(\/node_modules\/@astrale-os\/kernel-[^/]+)\/dist\//gu, '$1/');
|
|
17
|
+
return FORBIDDEN_RUNTIME_INPUTS.some((fragment) => normalized.includes(fragment));
|
|
18
|
+
});
|
|
19
|
+
if (forbidden.length > 0) {
|
|
20
|
+
throw new Error(`Cloudflare Worker reaches build-side modules: ${forbidden.sort().join(', ')}.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BuildMaterial } from '@astrale-os/sdk/deployment/build';
|
|
2
|
+
export interface ExecutionCodegenOptions {
|
|
3
|
+
readonly origin: string;
|
|
4
|
+
readonly runtimeSpecifier: string;
|
|
5
|
+
readonly material: BuildMaterial;
|
|
6
|
+
readonly providerAssignedWorker?: string;
|
|
7
|
+
readonly router?: {
|
|
8
|
+
readonly binding: string;
|
|
9
|
+
readonly hostSuffix: string;
|
|
10
|
+
readonly minLabels: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function generateExecutionEntry(options: ExecutionCodegenOptions): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function generateExecutionEntry(options) {
|
|
2
|
+
const router = options.router;
|
|
3
|
+
const routerPredicate = router === undefined
|
|
4
|
+
? ''
|
|
5
|
+
: `
|
|
6
|
+
function isInstanceHost(host: string): boolean {
|
|
7
|
+
return host.endsWith(${JSON.stringify(router.hostSuffix)}) && host.split('.').length >= ${router.minLabels}
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
const routeSubrequest = router === undefined
|
|
11
|
+
? ''
|
|
12
|
+
: `
|
|
13
|
+
routeSubrequest: (url, env) => {
|
|
14
|
+
const labels = url.hostname.split('.')
|
|
15
|
+
if (env.DISPATCHER && labels[1] === 'svc') return env.DISPATCHER.get(labels[0]!)
|
|
16
|
+
if (env.${router.binding} && isInstanceHost(url.hostname)) return env.${router.binding}
|
|
17
|
+
return null
|
|
18
|
+
},`;
|
|
19
|
+
return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
|
|
20
|
+
// Domain origin: ${options.origin}
|
|
21
|
+
import type { BuildMaterial } from '@astrale-os/sdk/deployment/build'
|
|
22
|
+
import type { CloudflareAdapterBindings } from '@astrale-os/adapter-cloudflare/worker'
|
|
23
|
+
|
|
24
|
+
import { cloudflareWorkerEntry } from '@astrale-os/adapter-cloudflare/worker'
|
|
25
|
+
import runtime from ${JSON.stringify(options.runtimeSpecifier)}
|
|
26
|
+
import { decodeSigningIdentity, runtimeBindings } from '@astrale-os/adapter-cloudflare/worker'
|
|
27
|
+
|
|
28
|
+
type WorkerEnv = CloudflareEnv & CloudflareAdapterBindings & {
|
|
29
|
+
readonly ASTRALE_SIGNING_IDENTITY: string
|
|
30
|
+
}
|
|
31
|
+
type RuntimeEnv = Omit<WorkerEnv, 'ASTRALE_SIGNING_IDENTITY'>
|
|
32
|
+
const MATERIAL = ${JSON.stringify(options.material)} as const satisfies BuildMaterial
|
|
33
|
+
${routerPredicate}
|
|
34
|
+
export default cloudflareWorkerEntry<WorkerEnv, RuntimeEnv>()({
|
|
35
|
+
runtime,
|
|
36
|
+
material: MATERIAL,
|
|
37
|
+
privateKey: (env) => decodeSigningIdentity(env.ASTRALE_SIGNING_IDENTITY),
|
|
38
|
+
resolveEnvironment: runtimeBindings,
|
|
39
|
+
resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? env.IDENTITY_ISS ?? requestOrigin,
|
|
40
|
+
${options.providerAssignedWorker === undefined ? '' : `providerAssignedWorker: ${JSON.stringify(options.providerAssignedWorker)},`}
|
|
41
|
+
selfBinding: (env) => env.SELF,${routeSubrequest}
|
|
42
|
+
})
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateGatewayEntry(revision: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function generateGatewayEntry(revision) {
|
|
2
|
+
return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
|
|
3
|
+
import { cloudflareGatewayEntry } from '@astrale-os/adapter-cloudflare/worker'
|
|
4
|
+
|
|
5
|
+
export default cloudflareGatewayEntry({ current: ${JSON.stringify(revision)} })
|
|
6
|
+
`;
|
|
7
|
+
}
|
|
@@ -13,17 +13,27 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export interface WranglerCodegenOptions {
|
|
15
15
|
workerName: string;
|
|
16
|
+
/** Provider-ready entrypoint relative to the generated config. */
|
|
17
|
+
main?: string;
|
|
16
18
|
/** Full custom hostname for a routed deploy (e.g. 'crm.acme.dev'). */
|
|
17
19
|
route?: string;
|
|
18
20
|
/** Exact materialized local frontend; absence means no ASSETS binding. */
|
|
19
21
|
frontend?: {
|
|
20
22
|
readonly directory: string;
|
|
21
23
|
readonly routes: readonly string[];
|
|
24
|
+
readonly dev: boolean;
|
|
22
25
|
};
|
|
23
26
|
/** Plain (non-secret) vars to bake in. */
|
|
24
27
|
vars?: Record<string, string>;
|
|
28
|
+
/** Secret binding names required by generated Worker code. */
|
|
29
|
+
secretBindings?: readonly string[];
|
|
25
30
|
/** Add the SELF service binding (autobinding). */
|
|
26
31
|
selfBinding: boolean;
|
|
32
|
+
/** Bind the stable gateway to its private revision namespace. */
|
|
33
|
+
releases?: {
|
|
34
|
+
readonly binding: string;
|
|
35
|
+
readonly namespace: string;
|
|
36
|
+
};
|
|
27
37
|
/**
|
|
28
38
|
* Add a service binding to a router Worker for instance-host subrequest
|
|
29
39
|
* routing (see `CloudflareParams.router`). Pairs with the worker
|
|
@@ -43,4 +53,3 @@ export interface WranglerCodegenOptions {
|
|
|
43
53
|
wrangler?: Record<string, unknown>;
|
|
44
54
|
}
|
|
45
55
|
export declare function generateWranglerConfig(opts: WranglerCodegenOptions): string;
|
|
46
|
-
//# sourceMappingURL=wrangler.d.ts.map
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
|
|
13
13
|
*/
|
|
14
14
|
import { deepMergeConfig } from './merge.js';
|
|
15
|
-
//
|
|
15
|
+
// Keep the generated date on the runtime baseline qualified with Wrangler 4.123.0.
|
|
16
16
|
const COMPATIBILITY_DATE = '2026-08-11';
|
|
17
17
|
export function generateWranglerConfig(opts) {
|
|
18
18
|
const config = {
|
|
19
19
|
name: opts.workerName,
|
|
20
|
-
main: './worker.gen.ts',
|
|
20
|
+
main: opts.main ?? './worker.gen.ts',
|
|
21
21
|
compatibility_date: COMPATIBILITY_DATE,
|
|
22
22
|
compatibility_flags: ['nodejs_compat'],
|
|
23
23
|
workers_dev: true,
|
|
@@ -31,19 +31,19 @@ export function generateWranglerConfig(opts) {
|
|
|
31
31
|
config.assets = {
|
|
32
32
|
directory: opts.frontend.directory,
|
|
33
33
|
binding: 'ASSETS',
|
|
34
|
-
run_worker_first:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
run_worker_first: opts.frontend.dev
|
|
35
|
+
? true
|
|
36
|
+
: [
|
|
37
|
+
'/releases',
|
|
38
|
+
'/releases/*',
|
|
39
|
+
'/install',
|
|
40
|
+
'/install/*',
|
|
41
|
+
'/health',
|
|
42
|
+
'/health/*',
|
|
43
|
+
'/.well-known',
|
|
44
|
+
'/.well-known/*',
|
|
45
|
+
...opts.frontend.routes,
|
|
46
|
+
],
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
const services = [];
|
|
@@ -57,6 +57,12 @@ export function generateWranglerConfig(opts) {
|
|
|
57
57
|
if (opts.vars && Object.keys(opts.vars).length > 0) {
|
|
58
58
|
config.vars = opts.vars;
|
|
59
59
|
}
|
|
60
|
+
if (opts.secretBindings && opts.secretBindings.length > 0) {
|
|
61
|
+
config.secrets = { required: [...opts.secretBindings] };
|
|
62
|
+
}
|
|
63
|
+
if (opts.releases !== undefined) {
|
|
64
|
+
config.dispatch_namespaces = [opts.releases];
|
|
65
|
+
}
|
|
60
66
|
const merged = opts.wrangler ? mergeUserConfig(config, opts.wrangler) : config;
|
|
61
67
|
return JSON.stringify(merged, null, 2) + '\n';
|
|
62
68
|
}
|
|
@@ -68,6 +74,7 @@ const ADAPTER_OWNED_KEYS = [
|
|
|
68
74
|
'routes',
|
|
69
75
|
'compatibility_date',
|
|
70
76
|
'version_metadata',
|
|
77
|
+
'secrets',
|
|
71
78
|
];
|
|
72
79
|
/**
|
|
73
80
|
* Deep-merge the dev's raw wrangler overlay over the generated config, after
|
|
@@ -83,4 +90,3 @@ function mergeUserConfig(base, user) {
|
|
|
83
90
|
}
|
|
84
91
|
return deepMergeConfig(base, user);
|
|
85
92
|
}
|
|
86
|
-
//# sourceMappingURL=wrangler.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CloudflareParams } from '../params.js';
|
|
2
|
+
export declare function deploymentAddressing(parameters: CloudflareParams): string | undefined;
|
|
3
|
+
export declare function workerName(parameters: CloudflareParams, origin: string): string;
|
|
4
|
+
export declare function resolveRouter(input: CloudflareParams['router']): {
|
|
5
|
+
readonly binding: string;
|
|
6
|
+
readonly service: string;
|
|
7
|
+
readonly hostSuffix: string;
|
|
8
|
+
readonly minLabels: number;
|
|
9
|
+
} | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { addressing } from '@astrale-os/sdk/deployment/release';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
4
|
+
export function deploymentAddressing(parameters) {
|
|
5
|
+
const routed = parameters.route === undefined ? undefined : addressing(`https://${parameters.route}`).issuer;
|
|
6
|
+
if (parameters.addressing === undefined)
|
|
7
|
+
return routed;
|
|
8
|
+
const configured = addressing(parameters.addressing).issuer;
|
|
9
|
+
if (routed !== undefined && configured !== routed) {
|
|
10
|
+
throw new TypeError('Cloudflare route and addressing identify different origins.');
|
|
11
|
+
}
|
|
12
|
+
return configured;
|
|
13
|
+
}
|
|
14
|
+
export function workerName(parameters, origin) {
|
|
15
|
+
if (parameters.workerName !== undefined) {
|
|
16
|
+
if (!WORKER_NAME.test(parameters.workerName) || parameters.workerName.length > 63) {
|
|
17
|
+
throw new TypeError('Cloudflare workerName is invalid.');
|
|
18
|
+
}
|
|
19
|
+
return parameters.workerName;
|
|
20
|
+
}
|
|
21
|
+
const readable = origin
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
24
|
+
.slice(0, 12)
|
|
25
|
+
.replace(/^-+|-+$/gu, '') || 'domain';
|
|
26
|
+
const hexadecimal = createHash('sha256').update(origin).digest('hex');
|
|
27
|
+
const identity = BigInt(`0x${hexadecimal}`).toString(36).padStart(50, '0');
|
|
28
|
+
return `${readable}-${identity}`;
|
|
29
|
+
}
|
|
30
|
+
export function resolveRouter(input) {
|
|
31
|
+
if (input === false)
|
|
32
|
+
return undefined;
|
|
33
|
+
const value = input ?? {};
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
binding: value.binding ?? 'ROUTER',
|
|
36
|
+
service: value.service ?? 'admin-router',
|
|
37
|
+
hostSuffix: value.hostSuffix ?? '.astrale.ai',
|
|
38
|
+
minLabels: value.minLabels ?? 4,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Build } from '@astrale-os/sdk/deployment/build';
|
|
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
|
+
export declare function frontendFiles(input: {
|
|
8
|
+
readonly frontend: Build['frontend'];
|
|
9
|
+
readonly projectDir: string;
|
|
10
|
+
readonly onLog?: (line: string) => void;
|
|
11
|
+
}): 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>;
|