@astrale-os/adapter-cloudflare 0.4.10 → 0.5.0-beta.2
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/dist/build/codegen/identity.d.ts.map +1 -0
- package/dist/build/codegen/identity.js.map +1 -0
- package/dist/build/codegen/merge.d.ts.map +1 -0
- package/dist/build/codegen/merge.js.map +1 -0
- package/dist/build/codegen/worker.d.ts +12 -0
- package/dist/build/codegen/worker.d.ts.map +1 -0
- package/dist/build/codegen/worker.js +40 -0
- package/dist/build/codegen/worker.js.map +1 -0
- package/dist/{codegen → build/codegen}/wrangler.d.ts +6 -3
- package/dist/build/codegen/wrangler.d.ts.map +1 -0
- package/dist/{codegen → build/codegen}/wrangler.js +29 -10
- package/dist/build/codegen/wrangler.js.map +1 -0
- package/dist/build/frontend.d.ts +26 -0
- package/dist/build/frontend.d.ts.map +1 -0
- package/dist/build/frontend.js +263 -0
- package/dist/build/frontend.js.map +1 -0
- package/dist/{build.js → build/index.d.ts} +5 -4
- package/dist/build/index.d.ts.map +1 -0
- package/dist/{build.d.ts → build/index.js} +5 -5
- package/dist/build/index.js.map +1 -0
- package/dist/{parse-output.d.ts → build/parse-output.d.ts} +2 -0
- package/dist/build/parse-output.d.ts.map +1 -0
- package/dist/{parse-output.js → build/parse-output.js} +5 -0
- package/dist/build/parse-output.js.map +1 -0
- package/dist/build/prepare.d.ts +18 -0
- package/dist/build/prepare.d.ts.map +1 -0
- package/dist/build/prepare.js +117 -0
- package/dist/build/prepare.js.map +1 -0
- package/dist/build/worker.d.ts +15 -0
- package/dist/build/worker.d.ts.map +1 -0
- package/dist/build/worker.js +21 -0
- package/dist/build/worker.js.map +1 -0
- package/dist/{wrangler-cli.d.ts → build/wrangler-cli.d.ts} +8 -0
- package/dist/build/wrangler-cli.d.ts.map +1 -0
- package/dist/{wrangler-cli.js → build/wrangler-cli.js} +28 -4
- package/dist/build/wrangler-cli.js.map +1 -0
- package/dist/cloudflare.d.ts +21 -24
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +125 -191
- package/dist/cloudflare.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/params.d.ts +13 -16
- package/dist/params.d.ts.map +1 -1
- package/dist/worker/index.d.ts +48 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +186 -0
- package/dist/worker/index.js.map +1 -0
- package/package.json +13 -5
- package/src/build/codegen/worker.ts +53 -0
- package/src/{codegen → build/codegen}/wrangler.ts +34 -12
- package/src/build/frontend.ts +324 -0
- package/src/{build.ts → build/index.ts} +17 -4
- package/src/{parse-output.ts → build/parse-output.ts} +6 -0
- package/src/build/prepare.ts +161 -0
- package/src/build/worker.ts +37 -0
- package/src/{wrangler-cli.ts → build/wrangler-cli.ts} +44 -6
- package/src/cloudflare.ts +153 -223
- package/src/index.ts +4 -4
- package/src/params.ts +13 -17
- package/src/worker/index.ts +276 -0
- package/src/worker/tsconfig.json +10 -0
- package/template/astrale.config.ts +7 -7
- package/dist/assets-pack.d.ts +0 -8
- package/dist/assets-pack.d.ts.map +0 -1
- package/dist/assets-pack.js +0 -30
- package/dist/assets-pack.js.map +0 -1
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js.map +0 -1
- package/dist/client.d.ts +0 -17
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -59
- package/dist/client.js.map +0 -1
- package/dist/codegen/identity.d.ts.map +0 -1
- package/dist/codegen/identity.js.map +0 -1
- package/dist/codegen/merge.d.ts.map +0 -1
- package/dist/codegen/merge.js.map +0 -1
- package/dist/codegen/worker.d.ts +0 -51
- package/dist/codegen/worker.d.ts.map +0 -1
- package/dist/codegen/worker.js +0 -120
- package/dist/codegen/worker.js.map +0 -1
- package/dist/codegen/wrangler.d.ts.map +0 -1
- package/dist/codegen/wrangler.js.map +0 -1
- package/dist/parse-output.d.ts.map +0 -1
- package/dist/parse-output.js.map +0 -1
- package/dist/wrangler-cli.d.ts.map +0 -1
- package/dist/wrangler-cli.js.map +0 -1
- package/src/assets-pack.ts +0 -32
- package/src/client.ts +0 -89
- package/src/codegen/worker.ts +0 -136
- package/template/.env.example +0 -9
- package/template/.gitignore.template +0 -16
- package/template/CLAUDE.md +0 -13
- package/template/README.md +0 -47
- package/template/client/.gitignore.template +0 -4
- package/template/client/__tests__/app.test.tsx +0 -15
- package/template/client/components.json +0 -25
- package/template/client/index.html +0 -12
- package/template/client/package.json +0 -35
- package/template/client/src/app.tsx +0 -52
- package/template/client/src/components/ui/cn.ts +0 -6
- package/template/client/src/main.tsx +0 -9
- package/template/client/src/styles.css +0 -78
- package/template/client/tsconfig.json +0 -26
- package/template/client/vite.config.ts +0 -22
- package/template/client/vitest.config.ts +0 -13
- package/template/core/README.md +0 -7
- package/template/deps.ts +0 -9
- package/template/domain.ts +0 -11
- package/template/env.ts +0 -8
- package/template/icons.ts +0 -4
- package/template/integrations/README.md +0 -7
- package/template/oxlint.config.ts +0 -1
- package/template/package.json +0 -58
- package/template/pnpm-workspace.yaml +0 -31
- package/template/runtime/index.ts +0 -8
- package/template/schema/index.ts +0 -10
- package/template/simulation/README.md +0 -12
- package/template/tsconfig.json +0 -28
- package/template/vitest.config.ts +0 -7
- /package/dist/{codegen → build/codegen}/identity.d.ts +0 -0
- /package/dist/{codegen → build/codegen}/identity.js +0 -0
- /package/dist/{codegen → build/codegen}/merge.d.ts +0 -0
- /package/dist/{codegen → build/codegen}/merge.js +0 -0
- /package/src/{codegen → build/codegen}/identity.ts +0 -0
- /package/src/{codegen → build/codegen}/merge.ts +0 -0
package/src/cloudflare.ts
CHANGED
|
@@ -2,44 +2,87 @@
|
|
|
2
2
|
* `cloudflare(envs)` — the Cloudflare deployment adapter.
|
|
3
3
|
*
|
|
4
4
|
* Owns everything target-specific: it codegens the Worker entry + wrangler
|
|
5
|
-
* config (the dev never sees a `wrangler.jsonc`),
|
|
5
|
+
* config (the dev never sees a `wrangler.jsonc`), materializes the optional frontend
|
|
6
6
|
* SPA, and shells out to `wrangler`. `watch` → `wrangler dev` (local URL);
|
|
7
7
|
* `deploy` → `wrangler deploy` (workers.dev or a custom-domain URL); secrets →
|
|
8
8
|
* `wrangler secret bulk`. Both `watch` and `deploy` return the URL the CLI
|
|
9
9
|
* prints and `astrale domain install <url>` consumes.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type { DomainAdapter } from '@astrale-os/sdk'
|
|
13
13
|
|
|
14
14
|
import { defineAdapter } from '@astrale-os/sdk'
|
|
15
|
-
import { mkdir, rename, writeFile } from 'node:fs/promises'
|
|
16
|
-
import { join } from 'node:path'
|
|
17
15
|
|
|
18
|
-
import type { CloudflareParams } from './params'
|
|
16
|
+
import type { CloudflareParams } from './params.js'
|
|
19
17
|
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
import {
|
|
19
|
+
bulkPutSecrets,
|
|
20
|
+
buildCloudflareWorker,
|
|
21
|
+
buildFrontend,
|
|
22
|
+
logTo,
|
|
23
|
+
prepare,
|
|
24
|
+
runWranglerDeploy,
|
|
25
|
+
runWranglerDev,
|
|
26
|
+
runWranglerTypes,
|
|
27
|
+
watchFrontend,
|
|
28
|
+
} from './build/index.js'
|
|
27
29
|
|
|
28
30
|
export function cloudflare(
|
|
29
31
|
envs: Record<string, CloudflareParams>,
|
|
32
|
+
): DomainAdapter<CloudflareParams> {
|
|
33
|
+
return cloudflareWithDependencies(envs, {
|
|
34
|
+
bulkPutSecrets,
|
|
35
|
+
buildCloudflareWorker,
|
|
36
|
+
buildFrontend,
|
|
37
|
+
logTo,
|
|
38
|
+
prepare,
|
|
39
|
+
runWranglerDeploy,
|
|
40
|
+
runWranglerDev,
|
|
41
|
+
runWranglerTypes,
|
|
42
|
+
watchFrontend,
|
|
43
|
+
waitUntilLive,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface CloudflareAdapterDependencies {
|
|
48
|
+
readonly bulkPutSecrets: typeof bulkPutSecrets
|
|
49
|
+
readonly buildCloudflareWorker: typeof buildCloudflareWorker
|
|
50
|
+
readonly buildFrontend: typeof buildFrontend
|
|
51
|
+
readonly logTo: typeof logTo
|
|
52
|
+
readonly prepare: typeof prepare
|
|
53
|
+
readonly runWranglerDeploy: typeof runWranglerDeploy
|
|
54
|
+
readonly runWranglerDev: typeof runWranglerDev
|
|
55
|
+
readonly runWranglerTypes: typeof runWranglerTypes
|
|
56
|
+
readonly watchFrontend: typeof watchFrontend
|
|
57
|
+
readonly waitUntilLive: typeof waitUntilLive
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Exact orchestration seam used by adapter-level deployment qualification. */
|
|
61
|
+
export function cloudflareWithDependencies(
|
|
62
|
+
envs: Record<string, CloudflareParams>,
|
|
63
|
+
dependencies: CloudflareAdapterDependencies,
|
|
30
64
|
): DomainAdapter<CloudflareParams> {
|
|
31
65
|
return defineAdapter<CloudflareParams>({
|
|
32
66
|
name: 'cloudflare',
|
|
33
67
|
envs,
|
|
34
68
|
|
|
69
|
+
async build(params, ctx) {
|
|
70
|
+
await dependencies.buildCloudflareWorker({
|
|
71
|
+
params,
|
|
72
|
+
context: ctx,
|
|
73
|
+
onLog: dependencies.logTo(),
|
|
74
|
+
})
|
|
75
|
+
},
|
|
76
|
+
|
|
35
77
|
async watch(params, ctx) {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
const onLog = dependencies.logTo()
|
|
79
|
+
const frontend = await dependencies.buildFrontend({
|
|
80
|
+
definition: ctx.definition,
|
|
81
|
+
projectDir: ctx.projectDir,
|
|
82
|
+
onLog,
|
|
40
83
|
})
|
|
41
|
-
|
|
42
|
-
const handle = await runWranglerDev({
|
|
84
|
+
const { configPath, port } = await dependencies.prepare(params, ctx, 'dev', frontend)
|
|
85
|
+
const handle = await dependencies.runWranglerDev({
|
|
43
86
|
projectDir: ctx.projectDir,
|
|
44
87
|
configPath,
|
|
45
88
|
port,
|
|
@@ -47,58 +90,104 @@ export function cloudflare(
|
|
|
47
90
|
autoPickPort: params.port === undefined,
|
|
48
91
|
...(params.host ? { ip: '0.0.0.0' } : {}),
|
|
49
92
|
onReload: ctx.onReload,
|
|
50
|
-
onLog
|
|
93
|
+
onLog,
|
|
51
94
|
})
|
|
95
|
+
let frontendWatch: ReturnType<typeof watchFrontend>
|
|
96
|
+
try {
|
|
97
|
+
frontendWatch = dependencies.watchFrontend({
|
|
98
|
+
definition: ctx.definition,
|
|
99
|
+
projectDir: ctx.projectDir,
|
|
100
|
+
onLog,
|
|
101
|
+
// Routes and source recipes are immutable for this watch generation.
|
|
102
|
+
// Wrangler already observes the atomically swapped asset stage, so a
|
|
103
|
+
// source rebuild needs no config regeneration.
|
|
104
|
+
onRebuild: ctx.onReload,
|
|
105
|
+
})
|
|
106
|
+
} catch (error) {
|
|
107
|
+
try {
|
|
108
|
+
await handle.stop()
|
|
109
|
+
} catch (cleanupError) {
|
|
110
|
+
throw new AggregateError(
|
|
111
|
+
[error, cleanupError],
|
|
112
|
+
'cloudflare frontend watcher startup failed and Wrangler cleanup also failed',
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
throw error
|
|
116
|
+
}
|
|
52
117
|
// With --host the actionable URL is the PUBLIC one (the tunnel/proxy
|
|
53
118
|
// front), not the local bind — print and spec-stamp that.
|
|
54
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
url: params.host ?? handle.url,
|
|
121
|
+
async stop() {
|
|
122
|
+
const errors: unknown[] = []
|
|
123
|
+
try {
|
|
124
|
+
await frontendWatch?.stop()
|
|
125
|
+
} catch (error) {
|
|
126
|
+
errors.push(error)
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await handle.stop()
|
|
130
|
+
} catch (error) {
|
|
131
|
+
errors.push(error)
|
|
132
|
+
}
|
|
133
|
+
if (errors.length === 1) throw errors[0]
|
|
134
|
+
if (errors.length > 1) {
|
|
135
|
+
throw new AggregateError(errors, 'cloudflare development cleanup failed')
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
}
|
|
55
139
|
},
|
|
56
140
|
|
|
57
141
|
// Config hot-regen: rewrite the generated files only — the running
|
|
58
142
|
// `wrangler dev` watches its `--config` (and the worker entry) and reloads
|
|
59
143
|
// them itself, so a `astrale.config.ts` edit lands without a restart.
|
|
60
|
-
// Mirrors `watch`'s prep exactly (codegen +
|
|
144
|
+
// Mirrors `watch`'s prep exactly (codegen + frontend build), minus the spawn.
|
|
61
145
|
async regenerate(params, ctx) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
146
|
+
const frontend = await dependencies.buildFrontend({
|
|
147
|
+
definition: ctx.definition,
|
|
148
|
+
projectDir: ctx.projectDir,
|
|
149
|
+
onLog: dependencies.logTo(),
|
|
150
|
+
})
|
|
151
|
+
const { configPath } = await dependencies.prepare(params, ctx, 'dev', frontend)
|
|
152
|
+
await dependencies.runWranglerTypes({
|
|
153
|
+
projectDir: ctx.projectDir,
|
|
154
|
+
configPath,
|
|
155
|
+
onLog: dependencies.logTo(),
|
|
66
156
|
})
|
|
67
|
-
if (clientDir) await buildClient(clientDir, ctx.projectDir, logTo())
|
|
68
157
|
},
|
|
69
158
|
|
|
70
159
|
async deploy(params, ctx) {
|
|
71
|
-
const
|
|
160
|
+
const frontend = await dependencies.buildFrontend({
|
|
161
|
+
definition: ctx.definition,
|
|
162
|
+
projectDir: ctx.projectDir,
|
|
163
|
+
onLog: dependencies.logTo(),
|
|
164
|
+
})
|
|
72
165
|
const {
|
|
73
166
|
configPath,
|
|
74
167
|
fallbackConfigPath,
|
|
75
168
|
workerName: name,
|
|
76
|
-
} = await prepare(params, ctx, 'deploy',
|
|
77
|
-
|
|
78
|
-
bundleAssets: Boolean(clientDir),
|
|
79
|
-
})
|
|
80
|
-
if (clientDir) await buildClient(clientDir, ctx.projectDir, logTo())
|
|
81
|
-
const { url } = await runWranglerDeploy({
|
|
169
|
+
} = await dependencies.prepare(params, ctx, 'deploy', frontend)
|
|
170
|
+
const { url, deploymentVersion } = await dependencies.runWranglerDeploy({
|
|
82
171
|
projectDir: ctx.projectDir,
|
|
83
172
|
configPath,
|
|
84
173
|
workerName: name,
|
|
85
174
|
...(fallbackConfigPath ? { fallbackConfigPath } : {}),
|
|
86
175
|
...(params.route ? { route: params.route } : {}),
|
|
87
|
-
onLog: logTo(),
|
|
176
|
+
onLog: dependencies.logTo(),
|
|
88
177
|
})
|
|
89
178
|
if (Object.keys(ctx.secrets).length > 0) {
|
|
90
|
-
await bulkPutSecrets({
|
|
179
|
+
await dependencies.bulkPutSecrets({
|
|
91
180
|
projectDir: ctx.projectDir,
|
|
92
181
|
configPath,
|
|
93
182
|
secrets: ctx.secrets,
|
|
94
|
-
onLog: logTo(),
|
|
183
|
+
onLog: dependencies.logTo(),
|
|
95
184
|
})
|
|
96
185
|
}
|
|
97
186
|
// A first deploy on a fresh `*.workers.dev` host can take ~30-60s to
|
|
98
187
|
// propagate; an `astrale domain install <url>` issued right away would
|
|
99
188
|
// 404 (Cloudflare 1042). Block until the URL actually serves so the
|
|
100
189
|
// install line we print is immediately actionable.
|
|
101
|
-
await waitUntilLive(url, logTo())
|
|
190
|
+
await dependencies.waitUntilLive(url, dependencies.logTo(), deploymentVersion)
|
|
102
191
|
return { url }
|
|
103
192
|
},
|
|
104
193
|
|
|
@@ -108,160 +197,50 @@ export function cloudflare(
|
|
|
108
197
|
})
|
|
109
198
|
}
|
|
110
199
|
|
|
111
|
-
function resolveCloudflareClientDir(
|
|
112
|
-
params: CloudflareParams,
|
|
113
|
-
ctx: WatchCtx | DeployCtx,
|
|
114
|
-
): string | undefined {
|
|
115
|
-
return resolveClientDir({
|
|
116
|
-
adapterName: 'cloudflare',
|
|
117
|
-
env: ctx.env,
|
|
118
|
-
projectDir: ctx.projectDir,
|
|
119
|
-
domain: ctx.domain,
|
|
120
|
-
...(params.client ? { client: params.client } : {}),
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── codegen orchestration ──────────────────────────────────────────────────
|
|
125
|
-
|
|
126
|
-
export async function prepare(
|
|
127
|
-
params: CloudflareParams,
|
|
128
|
-
ctx: WatchCtx | DeployCtx,
|
|
129
|
-
mode: 'dev' | 'deploy',
|
|
130
|
-
clientAssets: { servesClient: boolean; bundleAssets: boolean } = {
|
|
131
|
-
servesClient: false,
|
|
132
|
-
bundleAssets: false,
|
|
133
|
-
},
|
|
134
|
-
): Promise<{ configPath: string; fallbackConfigPath?: string; port: number; workerName: string }> {
|
|
135
|
-
const astraleDir = join(ctx.projectDir, '.astrale')
|
|
136
|
-
await mkdir(astraleDir, { recursive: true })
|
|
137
|
-
await ensureIdentity(astraleDir, ctx.domain.origin)
|
|
138
|
-
|
|
139
|
-
const name = workerName(params, ctx.domain.origin)
|
|
140
|
-
// Instance-router service binding (on by default → admin-router): lets the
|
|
141
|
-
// worker reach other instances' hosts internally, so a cross-instance JWKS
|
|
142
|
-
// fetch doesn't 522. Resolved once, fed to BOTH codegens below.
|
|
143
|
-
const router = resolveRouter(params.router)
|
|
144
|
-
// Two distinct client signals: the WORKER's `/ui` hook and Wrangler's local
|
|
145
|
-
// assets binding. Direct Cloudflare deploys use both; managed deploys ship
|
|
146
|
-
// assets separately but still need the worker hook.
|
|
147
|
-
const servesClient = clientAssets.servesClient
|
|
148
|
-
const bundleAssets = clientAssets.bundleAssets
|
|
149
|
-
|
|
150
|
-
await writeFileAtomic(
|
|
151
|
-
join(astraleDir, 'worker.gen.ts'),
|
|
152
|
-
// Everything else — schema/methods/deps/views/functions/requires/postInstall —
|
|
153
|
-
// rides the `...domain` spread the generated entry imports from `../domain`;
|
|
154
|
-
// the only remaining build-time signal is whether to wire the /ui asset hook.
|
|
155
|
-
generateWorkerEntry({
|
|
156
|
-
origin: ctx.domain.origin,
|
|
157
|
-
hasClient: servesClient,
|
|
158
|
-
...(router
|
|
159
|
-
? {
|
|
160
|
-
router: {
|
|
161
|
-
binding: router.binding,
|
|
162
|
-
hostSuffix: router.hostSuffix,
|
|
163
|
-
minLabels: router.minLabels,
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
: {}),
|
|
167
|
-
}),
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
// Pin the worker's canonical serving URL (its `iss` identity) for routed
|
|
171
|
-
// deploys: a routed worker may ALSO be reachable via `*.workers.dev`, so
|
|
172
|
-
// resolving the URL from the per-request Host would let `iss` drift between
|
|
173
|
-
// hostnames. Dev + workers.dev-only deploys are single-host, so the worker
|
|
174
|
-
// falls back to the per-request Host (always the canonical URL there).
|
|
175
|
-
// An explicit `vars.WORKER_URL` (e.g. a tunnel/proxy front) wins.
|
|
176
|
-
// Dev behind a tunnel/proxy (`--host`) pins WORKER_URL the same way a routed
|
|
177
|
-
// deploy does — the per-request Host there is the proxy's INTERNAL hostname
|
|
178
|
-
// and would corrupt the worker's `iss`.
|
|
179
|
-
const workerUrl =
|
|
180
|
-
mode === 'deploy' && params.route
|
|
181
|
-
? `https://${params.route}`
|
|
182
|
-
: mode === 'dev' && params.host
|
|
183
|
-
? params.host
|
|
184
|
-
: undefined
|
|
185
|
-
|
|
186
|
-
// Dev injects secrets as local vars (the gitignored .astrale config never
|
|
187
|
-
// ships); deploy keeps secrets out of the config and pushes them encrypted.
|
|
188
|
-
const vars = {
|
|
189
|
-
...(workerUrl ? { WORKER_URL: workerUrl } : {}),
|
|
190
|
-
...(mode === 'dev' ? { ...params.vars, ...ctx.secrets } : { ...params.vars }),
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const baseConfig = {
|
|
194
|
-
workerName: name,
|
|
195
|
-
...(mode === 'deploy' && params.route ? { route: params.route } : {}),
|
|
196
|
-
hasClient: bundleAssets,
|
|
197
|
-
vars,
|
|
198
|
-
// Router service binding flows into BOTH the SELF and SELF-less fallback
|
|
199
|
-
// configs — the first-deploy two-pass must keep it. `admin-router` already
|
|
200
|
-
// exists, so unlike SELF it always resolves (no first-deploy chicken-egg).
|
|
201
|
-
...(router ? { router: { binding: router.binding, service: router.service } } : {}),
|
|
202
|
-
// Escape hatch: extra bindings (KV/R2/D1/queues/…) deep-merged on top. Same
|
|
203
|
-
// overlay in dev and deploy — it's infra, not env-specific plumbing — and it
|
|
204
|
-
// flows into both the SELF and the SELF-less fallback config below.
|
|
205
|
-
...(params.wrangler ? { wrangler: params.wrangler } : {}),
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Always self-bind: it enables autobinding (a handler calling its own domain).
|
|
209
|
-
// Locally the dev registry resolves it; on deploy the first deploy of a fresh
|
|
210
|
-
// worker can't (the script doesn't exist yet), so we also emit a SELF-less
|
|
211
|
-
// fallback config that `runWranglerDeploy` uses for a one-time two-pass deploy.
|
|
212
|
-
const configPath = join(astraleDir, 'wrangler.gen.jsonc')
|
|
213
|
-
await writeFileAtomic(configPath, generateWranglerConfig({ ...baseConfig, selfBinding: true }))
|
|
214
|
-
|
|
215
|
-
let fallbackConfigPath: string | undefined
|
|
216
|
-
if (mode === 'deploy') {
|
|
217
|
-
fallbackConfigPath = join(astraleDir, 'wrangler.no-self.gen.jsonc')
|
|
218
|
-
await writeFileAtomic(
|
|
219
|
-
fallbackConfigPath,
|
|
220
|
-
generateWranglerConfig({ ...baseConfig, selfBinding: false }),
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
configPath,
|
|
226
|
-
...(fallbackConfigPath ? { fallbackConfigPath } : {}),
|
|
227
|
-
port: params.port ?? DEFAULT_PORT,
|
|
228
|
-
workerName: name,
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Write-then-rename: hot-regen rewrites these files while a live `wrangler dev`
|
|
234
|
-
* watches them — a plain `writeFile` can be read truncated mid-write.
|
|
235
|
-
*/
|
|
236
|
-
async function writeFileAtomic(path: string, content: string): Promise<void> {
|
|
237
|
-
const tmp = `${path}.tmp`
|
|
238
|
-
await writeFile(tmp, content)
|
|
239
|
-
await rename(tmp, path)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
200
|
/**
|
|
243
|
-
* Poll the deployed
|
|
244
|
-
* 1042 "no script on this host" 404), or give up after `timeoutMs`. Resolves
|
|
245
|
-
* silently on the fast path; logs only when propagation actually makes us wait.
|
|
201
|
+
* Poll until the deployed Worker health endpoint returns exactly 200.
|
|
246
202
|
*/
|
|
247
203
|
async function waitUntilLive(
|
|
248
204
|
url: string,
|
|
249
205
|
onLog: (line: string) => void,
|
|
206
|
+
expectedDeploymentVersion?: string,
|
|
250
207
|
timeoutMs = 90_000,
|
|
251
208
|
): Promise<void> {
|
|
252
209
|
const probe = new URL('/health', url)
|
|
210
|
+
const metadata = new URL('/meta', url)
|
|
253
211
|
const deadline = Date.now() + timeoutMs
|
|
254
212
|
let waited = false
|
|
213
|
+
let lastStatus: number | undefined
|
|
214
|
+
let lastDeploymentVersion: string | undefined
|
|
255
215
|
for (;;) {
|
|
256
216
|
try {
|
|
257
217
|
const res = await fetch(probe, { redirect: 'manual' })
|
|
258
|
-
|
|
218
|
+
lastStatus = res.status
|
|
219
|
+
await res.body?.cancel()
|
|
220
|
+
if (res.status === 200) {
|
|
221
|
+
if (expectedDeploymentVersion === undefined) return
|
|
222
|
+
const meta = await fetch(metadata, { redirect: 'manual' })
|
|
223
|
+
if (meta.status === 200) {
|
|
224
|
+
const body = (await meta.json()) as { deploymentVersion?: unknown }
|
|
225
|
+
lastDeploymentVersion =
|
|
226
|
+
typeof body.deploymentVersion === 'string' ? body.deploymentVersion : undefined
|
|
227
|
+
if (lastDeploymentVersion === expectedDeploymentVersion) return
|
|
228
|
+
} else {
|
|
229
|
+
await meta.body?.cancel()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
259
232
|
} catch {
|
|
260
233
|
// Network hiccup — treat like "not live yet" and keep polling.
|
|
261
234
|
}
|
|
262
235
|
if (Date.now() >= deadline) {
|
|
263
|
-
|
|
264
|
-
|
|
236
|
+
throw new Error(
|
|
237
|
+
`deployed Worker health did not reach HTTP 200 within ${Math.round(timeoutMs / 1000)}s` +
|
|
238
|
+
(lastStatus === undefined ? '' : ` (last status ${lastStatus})`) +
|
|
239
|
+
(expectedDeploymentVersion === undefined
|
|
240
|
+
? ''
|
|
241
|
+
: `; deployment version ${JSON.stringify(lastDeploymentVersion)} ` +
|
|
242
|
+
`did not reach ${JSON.stringify(expectedDeploymentVersion)}`),
|
|
243
|
+
)
|
|
265
244
|
}
|
|
266
245
|
if (!waited) {
|
|
267
246
|
waited = true
|
|
@@ -270,52 +249,3 @@ async function waitUntilLive(
|
|
|
270
249
|
await new Promise((r) => setTimeout(r, 3000))
|
|
271
250
|
}
|
|
272
251
|
}
|
|
273
|
-
|
|
274
|
-
const WORKER_NAME_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
275
|
-
|
|
276
|
-
export function workerName(params: CloudflareParams, origin: string): string {
|
|
277
|
-
// An explicit name is the deployed worker's identity (and the SELF service
|
|
278
|
-
// target) — validate and reject an invalid one rather than silently rewriting
|
|
279
|
-
// it, so the dev fixes it instead of deploying under a surprising name.
|
|
280
|
-
if (params.workerName !== undefined) {
|
|
281
|
-
if (!WORKER_NAME_RE.test(params.workerName) || params.workerName.length > 63) {
|
|
282
|
-
throw new Error(
|
|
283
|
-
`cloudflare adapter: invalid \`workerName\` "${params.workerName}". A Cloudflare worker ` +
|
|
284
|
-
'name must be lowercase, start with a letter or digit, contain only letters, digits and ' +
|
|
285
|
-
'dashes, and be at most 63 chars.',
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
return params.workerName
|
|
289
|
-
}
|
|
290
|
-
// Derive from the origin: lowercase, collapse non-alphanumerics to a dash, then
|
|
291
|
-
// SLICE before trimming dashes so a cut landing mid-dash can't leave a trailing
|
|
292
|
-
// "-" (which wrangler rejects).
|
|
293
|
-
return origin
|
|
294
|
-
.toLowerCase()
|
|
295
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
296
|
-
.slice(0, 54)
|
|
297
|
-
.replace(/^-+|-+$/g, '')
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Resolve the instance-router config. ON by default (targets `admin-router`) so
|
|
302
|
-
* every adapter-deployed worker can reach other instances' hosts internally and
|
|
303
|
-
* verify cross-instance credentials without a 522; `false` disables it. The
|
|
304
|
-
* returned shape carries every value the worker- and wrangler-codegen need.
|
|
305
|
-
*/
|
|
306
|
-
export function resolveRouter(
|
|
307
|
-
ir: CloudflareParams['router'],
|
|
308
|
-
): { binding: string; service: string; hostSuffix: string; minLabels: number } | undefined {
|
|
309
|
-
if (ir === false) return undefined
|
|
310
|
-
const cfg = ir ?? {}
|
|
311
|
-
return {
|
|
312
|
-
binding: cfg.binding ?? 'ROUTER',
|
|
313
|
-
service: cfg.service ?? 'admin-router',
|
|
314
|
-
hostSuffix: cfg.hostSuffix ?? '.astrale.ai',
|
|
315
|
-
minLabels: cfg.minLabels ?? 4,
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function logTo(): (line: string) => void {
|
|
320
|
-
return (line: string) => process.stderr.write(`\x1b[2m ${line}\x1b[0m\n`)
|
|
321
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* import { cloudflare } from '@astrale-os/adapter-cloudflare'
|
|
6
6
|
*
|
|
7
7
|
* adapter: cloudflare({
|
|
8
|
-
* dev: {
|
|
9
|
-
* prod: {
|
|
8
|
+
* dev: { secrets: '.env.dev' },
|
|
9
|
+
* prod: { route: 'crm.acme.dev', secrets: '.env.prod' },
|
|
10
10
|
* })
|
|
11
11
|
*
|
|
12
12
|
* `dev` runs `wrangler dev` locally; an env with no `route` ships to
|
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
* `/build` toolkit and ships it through the Astrale platform.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export { cloudflare } from './cloudflare'
|
|
21
|
-
export type { CloudflareParams } from './params'
|
|
20
|
+
export { cloudflare } from './cloudflare.js'
|
|
21
|
+
export type { CloudflareParams } from './params.js'
|
package/src/params.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { ClientConfig } from './client'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* `CloudflareParams` — the provider-typed config for the Cloudflare adapter.
|
|
5
3
|
*
|
|
@@ -13,18 +11,16 @@ import type { ClientConfig } from './client'
|
|
|
13
11
|
* • remote (custom) — `deploy()` with `route`; URL = https://<route>
|
|
14
12
|
*/
|
|
15
13
|
export interface CloudflareParams {
|
|
16
|
-
/** Frontend assets to build and serve for this env's mounted `/ui` views. */
|
|
17
|
-
client?: ClientConfig
|
|
18
14
|
/**
|
|
19
15
|
* Custom route / hostname for a routed deploy, e.g. `'crm.acme.dev'`. Omit to
|
|
20
16
|
* deploy to `*.workers.dev`. Ignored by local `wrangler dev` (URL is
|
|
21
17
|
* localhost) but still used as the placeholder base domain there.
|
|
22
18
|
*/
|
|
23
|
-
route?: string
|
|
19
|
+
readonly route?: string
|
|
24
20
|
/** Gitignored secrets file (whole contents = secrets), e.g. `'.env.dev'`. */
|
|
25
|
-
secrets?: string
|
|
21
|
+
readonly secrets?: string
|
|
26
22
|
/** Local dev port for `wrangler dev`. Default 8787. */
|
|
27
|
-
port?: number
|
|
23
|
+
readonly port?: number
|
|
28
24
|
/**
|
|
29
25
|
* Public URL the dev server is reached through (tunnel / sandbox preview /
|
|
30
26
|
* reverse proxy), e.g. `'https://my-box.preview.dev'`. Sets BOTH effects an
|
|
@@ -33,13 +29,13 @@ export interface CloudflareParams {
|
|
|
33
29
|
* identity (`iss`) doesn't drift to the proxy's internal hostname.
|
|
34
30
|
* Usually injected by `pnpm dev --host <url>` rather than written here.
|
|
35
31
|
*/
|
|
36
|
-
host?: string
|
|
32
|
+
readonly host?: string
|
|
37
33
|
/** Override the Worker name. Default: derived from the domain origin. */
|
|
38
|
-
workerName?: string
|
|
34
|
+
readonly workerName?: string
|
|
39
35
|
/** Run `wrangler dev --remote` (edge isolate) instead of local workerd. */
|
|
40
|
-
remote?: boolean
|
|
36
|
+
readonly remote?: boolean
|
|
41
37
|
/** Extra plain (non-secret) vars to inject as Worker vars. */
|
|
42
|
-
vars?: Record<string, string>
|
|
38
|
+
readonly vars?: Record<string, string>
|
|
43
39
|
/**
|
|
44
40
|
* Escape hatch: raw wrangler config deep-merged over the generated base. Use
|
|
45
41
|
* it to declare extra bindings the adapter has no typed field for — KV, R2,
|
|
@@ -58,7 +54,7 @@ export interface CloudflareParams {
|
|
|
58
54
|
* the reserved binding names `SELF` and `ASSETS`. Durable Objects need extra
|
|
59
55
|
* worker-entry codegen and aren't supported through this field yet.
|
|
60
56
|
*/
|
|
61
|
-
wrangler?: Record<string, unknown>
|
|
57
|
+
readonly wrangler?: Record<string, unknown>
|
|
62
58
|
/**
|
|
63
59
|
* Route subrequests to platform INSTANCE hostnames through a service binding
|
|
64
60
|
* instead of the public edge. A Cloudflare Worker can't fetch a same-zone
|
|
@@ -81,16 +77,16 @@ export interface CloudflareParams {
|
|
|
81
77
|
* router: false // opt out
|
|
82
78
|
* router: { service: 'my-router' } // override target
|
|
83
79
|
*/
|
|
84
|
-
router?:
|
|
80
|
+
readonly router?:
|
|
85
81
|
| false
|
|
86
82
|
| {
|
|
87
83
|
/** Router Worker the binding targets. Default `'admin-router'`. */
|
|
88
|
-
service?: string
|
|
84
|
+
readonly service?: string
|
|
89
85
|
/** Service-binding name the worker reads. Default `'ROUTER'`. */
|
|
90
|
-
binding?: string
|
|
86
|
+
readonly binding?: string
|
|
91
87
|
/** Host suffix that marks an instance host. Default `'.astrale.ai'`. */
|
|
92
|
-
hostSuffix?: string
|
|
88
|
+
readonly hostSuffix?: string
|
|
93
89
|
/** Min dot-separated label count for an instance host. Default `4` (`<slug>.<region>.astrale.ai`). */
|
|
94
|
-
minLabels?: number
|
|
90
|
+
readonly minLabels?: number
|
|
95
91
|
}
|
|
96
92
|
}
|