@astrale-os/adapter-cloudflare 0.5.0-beta.2 → 0.5.0-beta.21

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.
Files changed (120) hide show
  1. package/README.md +11 -0
  2. package/dist/adapter.d.ts +17 -0
  3. package/dist/adapter.js +140 -0
  4. package/dist/build/artifact.d.ts +8 -0
  5. package/dist/build/artifact.js +179 -0
  6. package/dist/build/closure.d.ts +2 -0
  7. package/dist/build/closure.js +22 -0
  8. package/dist/build/codegen/execution.d.ts +13 -0
  9. package/dist/build/codegen/execution.js +44 -0
  10. package/dist/build/codegen/gateway.d.ts +1 -0
  11. package/dist/build/codegen/gateway.js +7 -0
  12. package/dist/build/codegen/merge.d.ts +0 -1
  13. package/dist/build/codegen/merge.js +0 -1
  14. package/dist/build/codegen/wrangler.d.ts +10 -1
  15. package/dist/build/codegen/wrangler.js +22 -16
  16. package/dist/build/configuration.d.ts +9 -0
  17. package/dist/build/configuration.js +40 -0
  18. package/dist/build/frontend-v1.d.ts +16 -0
  19. package/dist/build/frontend-v1.js +133 -0
  20. package/dist/build/index.d.ts +8 -16
  21. package/dist/build/index.js +8 -16
  22. package/dist/build/metafile.d.ts +5 -0
  23. package/dist/build/metafile.js +14 -0
  24. package/dist/build/parse-output.d.ts +0 -1
  25. package/dist/build/parse-output.js +0 -1
  26. package/dist/build/qualification.d.ts +5 -0
  27. package/dist/build/qualification.js +7 -0
  28. package/dist/build/roots.d.ts +2 -0
  29. package/dist/build/roots.js +34 -0
  30. package/dist/build/wrangler-cli.d.ts +53 -34
  31. package/dist/build/wrangler-cli.js +284 -111
  32. package/dist/cloudflare.d.ts +1 -34
  33. package/dist/cloudflare.js +1 -205
  34. package/dist/deploy/deploy.d.ts +17 -0
  35. package/dist/deploy/deploy.js +207 -0
  36. package/dist/deploy/index.d.ts +1 -0
  37. package/dist/deploy/index.js +1 -0
  38. package/dist/identity/binding.d.ts +2 -0
  39. package/dist/identity/binding.js +2 -0
  40. package/dist/identity/material.d.ts +7 -0
  41. package/dist/identity/material.js +42 -0
  42. package/dist/identity/secret.d.ts +3 -0
  43. package/dist/identity/secret.js +7 -0
  44. package/dist/identity/source.d.ts +26 -0
  45. package/dist/identity/source.js +98 -0
  46. package/dist/index.d.ts +3 -4
  47. package/dist/index.js +3 -4
  48. package/dist/params.d.ts +18 -5
  49. package/dist/params.js +0 -1
  50. package/dist/release/index.d.ts +2 -0
  51. package/dist/release/index.js +2 -0
  52. package/dist/release/namespace.d.ts +2 -0
  53. package/dist/release/namespace.js +6 -0
  54. package/dist/release/target.d.ts +2 -0
  55. package/dist/release/target.js +9 -0
  56. package/dist/worker/entry.d.ts +34 -0
  57. package/dist/worker/entry.js +157 -0
  58. package/dist/worker/fetch.d.ts +11 -0
  59. package/dist/worker/fetch.js +31 -0
  60. package/dist/worker/gateway.d.ts +15 -0
  61. package/dist/worker/gateway.js +26 -0
  62. package/dist/worker/identity.d.ts +7 -0
  63. package/dist/worker/identity.js +23 -0
  64. package/dist/worker/index.d.ts +3 -48
  65. package/dist/worker/index.js +3 -186
  66. package/package.json +21 -18
  67. package/template/astrale.config.ts +19 -11
  68. package/dist/build/codegen/identity.d.ts +0 -10
  69. package/dist/build/codegen/identity.d.ts.map +0 -1
  70. package/dist/build/codegen/identity.js +0 -32
  71. package/dist/build/codegen/identity.js.map +0 -1
  72. package/dist/build/codegen/merge.d.ts.map +0 -1
  73. package/dist/build/codegen/merge.js.map +0 -1
  74. package/dist/build/codegen/worker.d.ts +0 -12
  75. package/dist/build/codegen/worker.d.ts.map +0 -1
  76. package/dist/build/codegen/worker.js +0 -40
  77. package/dist/build/codegen/worker.js.map +0 -1
  78. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  79. package/dist/build/codegen/wrangler.js.map +0 -1
  80. package/dist/build/frontend.d.ts +0 -26
  81. package/dist/build/frontend.d.ts.map +0 -1
  82. package/dist/build/frontend.js +0 -263
  83. package/dist/build/frontend.js.map +0 -1
  84. package/dist/build/index.d.ts.map +0 -1
  85. package/dist/build/index.js.map +0 -1
  86. package/dist/build/parse-output.d.ts.map +0 -1
  87. package/dist/build/parse-output.js.map +0 -1
  88. package/dist/build/prepare.d.ts +0 -18
  89. package/dist/build/prepare.d.ts.map +0 -1
  90. package/dist/build/prepare.js +0 -117
  91. package/dist/build/prepare.js.map +0 -1
  92. package/dist/build/worker.d.ts +0 -15
  93. package/dist/build/worker.d.ts.map +0 -1
  94. package/dist/build/worker.js +0 -21
  95. package/dist/build/worker.js.map +0 -1
  96. package/dist/build/wrangler-cli.d.ts.map +0 -1
  97. package/dist/build/wrangler-cli.js.map +0 -1
  98. package/dist/cloudflare.d.ts.map +0 -1
  99. package/dist/cloudflare.js.map +0 -1
  100. package/dist/index.d.ts.map +0 -1
  101. package/dist/index.js.map +0 -1
  102. package/dist/params.d.ts.map +0 -1
  103. package/dist/params.js.map +0 -1
  104. package/dist/worker/index.d.ts.map +0 -1
  105. package/dist/worker/index.js.map +0 -1
  106. package/src/build/codegen/identity.ts +0 -40
  107. package/src/build/codegen/merge.ts +0 -92
  108. package/src/build/codegen/worker.ts +0 -53
  109. package/src/build/codegen/wrangler.ts +0 -127
  110. package/src/build/frontend.ts +0 -324
  111. package/src/build/index.ts +0 -29
  112. package/src/build/parse-output.ts +0 -60
  113. package/src/build/prepare.ts +0 -161
  114. package/src/build/worker.ts +0 -37
  115. package/src/build/wrangler-cli.ts +0 -362
  116. package/src/cloudflare.ts +0 -251
  117. package/src/index.ts +0 -21
  118. package/src/params.ts +0 -92
  119. package/src/worker/index.ts +0 -276
  120. package/src/worker/tsconfig.json +0 -10
@@ -1,362 +0,0 @@
1
- /**
2
- * Thin wrapper around the `wrangler` binary — the only place the adapter shells
3
- * out. `watch` runs `wrangler dev` (local workerd) and resolves once the worker
4
- * reports its local URL; `deploy` runs `wrangler deploy` and parses the
5
- * authoritative public URL from the output; secrets go through `wrangler secret
6
- * bulk`. The wrangler binary is resolved from the project's `node_modules/.bin`,
7
- * falling back to `npx wrangler`.
8
- */
9
-
10
- import type { ChildProcess } from 'node:child_process'
11
-
12
- import { spawn } from 'node:child_process'
13
- import { existsSync, readdirSync } from 'node:fs'
14
- import { mkdtemp, rm, writeFile } from 'node:fs/promises'
15
- import { createServer } from 'node:net'
16
- import { tmpdir } from 'node:os'
17
- import { dirname, join } from 'node:path'
18
-
19
- import { parseDeployUrl, parseDeployVersionId, parseDevReadyUrl } from './parse-output.js'
20
-
21
- function wranglerCommand(projectDir: string): { cmd: string; prefix: string[] } {
22
- const local = join(projectDir, 'node_modules', '.bin', 'wrangler')
23
- if (existsSync(local)) return { cmd: local, prefix: [] }
24
- return { cmd: 'npx', prefix: ['--yes', 'wrangler'] }
25
- }
26
-
27
- export interface DevHandle {
28
- url: string
29
- stop(): Promise<void>
30
- }
31
-
32
- /**
33
- * True when `port` is free on `host`. A taken port previously made `wrangler
34
- * dev` either die ("Address already in use") or — worse — a FIRST probe hit a
35
- * DIFFERENT project's dev server on the same port (observed in agent runs:
36
- * every reader of the docs converges on 8787/8899).
37
- */
38
- function portFree(port: number, host: string): Promise<boolean> {
39
- return new Promise((resolve) => {
40
- const srv = createServer()
41
- srv.once('error', () => resolve(false))
42
- srv.once('listening', () => srv.close(() => resolve(true)))
43
- srv.listen(port, host)
44
- })
45
- }
46
-
47
- /** An OS-assigned free port. */
48
- function ephemeralPort(host: string): Promise<number> {
49
- return new Promise((resolve, reject) => {
50
- const srv = createServer()
51
- srv.once('error', reject)
52
- srv.listen(0, host, () => {
53
- const addr = srv.address()
54
- const port = typeof addr === 'object' && addr ? addr.port : 0
55
- srv.close(() => resolve(port))
56
- })
57
- })
58
- }
59
-
60
- export async function runWranglerDev(args: {
61
- projectDir: string
62
- configPath: string
63
- port: number
64
- remote: boolean
65
- /** Bind address — '0.0.0.0' for tunneled/proxied dev (`--host`). Default loopback. */
66
- ip?: string
67
- /** When the port was NOT explicitly chosen, dodge a taken one automatically. */
68
- autoPickPort?: boolean
69
- onReload: () => void
70
- onLog?: (line: string) => void
71
- }): Promise<DevHandle> {
72
- await runWranglerTypes(args)
73
- const { cmd, prefix } = wranglerCommand(args.projectDir)
74
- const bindHost = args.ip ?? '127.0.0.1'
75
- if (args.autoPickPort && !(await portFree(args.port, bindHost))) {
76
- const picked = await ephemeralPort(bindHost)
77
- args.onLog?.(`port ${args.port} is taken — using ${picked} instead (pass --port to pin one)`)
78
- args = { ...args, port: picked }
79
- }
80
- const wranglerArgs = [
81
- ...prefix,
82
- 'dev',
83
- '--config',
84
- args.configPath,
85
- '--port',
86
- String(args.port),
87
- '--local-protocol',
88
- 'http',
89
- '--ip',
90
- args.ip ?? '127.0.0.1',
91
- ...(args.remote ? ['--remote'] : []),
92
- ]
93
-
94
- const child = spawn(cmd, wranglerArgs, {
95
- cwd: args.projectDir,
96
- stdio: ['ignore', 'pipe', 'pipe'],
97
- env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
98
- })
99
-
100
- const fallbackUrl = `http://localhost:${args.port}`
101
- // (args.port may have been re-picked above — everything below reads args.port)
102
- let resolved = false
103
- // Wrangler's watch mode STAYS ALIVE on a build failure (listener up, worker
104
- // never answers) — the classic silent hang. Track build errors so the
105
- // safety net can fail LOUD instead of resolving a dead URL.
106
- let buildErrorTail = ''
107
- let outputTail = ''
108
-
109
- const url = await new Promise<string>((resolve, reject) => {
110
- const onData = (buf: Buffer) => {
111
- const text = buf.toString()
112
- outputTail = (outputTail + text).slice(-3000)
113
- for (const line of text.split('\n')) {
114
- if (line.trim()) args.onLog?.(line)
115
- }
116
- if (/✘ \[ERROR\]|Build failed|Could not resolve "/.test(text)) {
117
- buildErrorTail = (buildErrorTail + text).slice(-2000)
118
- }
119
- if (!resolved) {
120
- const ready = parseDevReadyUrl(text, args.port)
121
- if (ready) {
122
- resolved = true
123
- resolve(ready)
124
- }
125
- } else if (/Reloading|Detected changes|Updated/i.test(text)) {
126
- args.onReload()
127
- }
128
- }
129
- child.stdout?.on('data', onData)
130
- child.stderr?.on('data', onData)
131
- child.on('exit', (code) => {
132
- if (!resolved) {
133
- resolved = true
134
- reject(
135
- new Error(
136
- `wrangler dev exited before becoming ready (code ${code ?? 'null'}).\n${outputTail}`,
137
- ),
138
- )
139
- }
140
- })
141
- child.on('error', (err) => {
142
- if (!resolved) {
143
- resolved = true
144
- reject(err)
145
- }
146
- })
147
- // Safety net: if we never matched a URL but the process is alive, assume the
148
- // conventional localhost URL after a grace period — UNLESS the build
149
- // failed, in which case a listener may be up but the worker will never
150
- // answer: fail loud with the build output instead of handing back a dead
151
- // URL.
152
- setTimeout(() => {
153
- if (!resolved && child.exitCode === null) {
154
- resolved = true
155
- if (buildErrorTail) {
156
- void stopChild(child)
157
- reject(
158
- new Error(
159
- `worker build failed — the dev server would hang silently.\n${buildErrorTail}`,
160
- ),
161
- )
162
- } else {
163
- resolve(fallbackUrl)
164
- }
165
- }
166
- }, 15_000).unref?.()
167
- })
168
-
169
- return { url, stop: () => stopChild(child) }
170
- }
171
-
172
- export interface DeployArgs {
173
- configPath: string
174
- /** SELF-less config for the first-deploy two-pass (see `deployWithSelfFallback`). */
175
- fallbackConfigPath?: string
176
- /** The worker's own name, to scope the self-binding-missing detection. */
177
- workerName?: string
178
- route?: string
179
- onLog?: (line: string) => void
180
- }
181
-
182
- export async function runWranglerDeploy(
183
- args: DeployArgs & { projectDir: string },
184
- ): Promise<{ url: string; raw: string; deploymentVersion?: string }> {
185
- await runWranglerTypes(args)
186
- const { cmd, prefix } = wranglerCommand(args.projectDir)
187
- const deploy = (configPath: string) =>
188
- runCapture(cmd, [...prefix, 'deploy', '--config', configPath], args.projectDir, args.onLog)
189
- return deployWithSelfFallback(deploy, args)
190
- }
191
-
192
- /**
193
- * Deploy with a one-time SELF-binding first-deploy recovery. A fresh worker that
194
- * declares a `services: SELF` binding can't deploy (its own script doesn't exist
195
- * yet); on THAT specific failure we deploy the SELF-less fallback to create the
196
- * script, then redeploy WITH SELF so autobinding works. Steady state (every
197
- * deploy after the first) is a single pass. The `deploy` runner is injected so
198
- * the orchestration is unit-testable without shelling out.
199
- */
200
- export async function deployWithSelfFallback(
201
- deploy: (configPath: string) => Promise<{ code: number; out: string }>,
202
- args: DeployArgs,
203
- ): Promise<{ url: string; raw: string; deploymentVersion?: string }> {
204
- let { code, out } = await deploy(args.configPath)
205
-
206
- if (code !== 0 && args.fallbackConfigPath && isSelfBindingMissing(out, args.workerName)) {
207
- args.onLog?.(
208
- 'SELF service binding not resolvable yet (first deploy) — creating the worker, then re-binding SELF',
209
- )
210
- const first = await deploy(args.fallbackConfigPath)
211
- if (first.code !== 0) {
212
- throw new Error(
213
- `wrangler deploy (no-self pass) failed (code ${first.code}):\n${first.out.slice(-2000)}`,
214
- )
215
- }
216
- ;({ code, out } = await deploy(args.configPath))
217
- }
218
-
219
- if (code !== 0) {
220
- throw new Error(`wrangler deploy failed (code ${code}):\n${out.slice(-2000)}`)
221
- }
222
- const url = parseDeployUrl(out, args.route)
223
- if (!url) {
224
- throw new Error(`could not determine deployed URL from wrangler output:\n${out.slice(-2000)}`)
225
- }
226
- const deploymentVersion = parseDeployVersionId(out)
227
- return { url, raw: out, ...(deploymentVersion === undefined ? {} : { deploymentVersion }) }
228
- }
229
-
230
- /**
231
- * True when wrangler failed specifically because the worker's OWN `SELF` service
232
- * binding can't resolve yet (first deploy of a fresh script). Requires the
233
- * worker name so an unrelated missing-service error doesn't trigger the two-pass.
234
- */
235
- export function isSelfBindingMissing(out: string, workerName?: string): boolean {
236
- const bindingError =
237
- /could not resolve binding/i.test(out) ||
238
- /service\b[^\n]*\bnot found/i.test(out) ||
239
- /script\b[^\n]*\bnot found/i.test(out)
240
- if (!bindingError || !/\bSELF\b/.test(out)) return false
241
- return workerName ? out.includes(workerName) : true
242
- }
243
-
244
- export async function bulkPutSecrets(args: {
245
- projectDir: string
246
- configPath: string
247
- secrets: Record<string, string>
248
- onLog?: (line: string) => void
249
- }): Promise<void> {
250
- const names = Object.keys(args.secrets)
251
- if (names.length === 0) return
252
- const { cmd, prefix } = wranglerCommand(args.projectDir)
253
- const dir = await mkdtemp(join(tmpdir(), 'astrale-secrets-'))
254
- const file = join(dir, 'secrets.json')
255
- try {
256
- await writeFile(file, JSON.stringify(args.secrets))
257
- const { code, out } = await runCapture(
258
- cmd,
259
- [...prefix, 'secret', 'bulk', file, '--config', args.configPath],
260
- args.projectDir,
261
- args.onLog,
262
- )
263
- if (code !== 0) {
264
- throw new Error(`wrangler secret bulk failed (code ${code}):\n${out.slice(-1500)}`)
265
- }
266
- } finally {
267
- await rm(dir, { recursive: true, force: true })
268
- }
269
- }
270
-
271
- // ── internals ──────────────────────────────────────────────────────────────
272
-
273
- function runCapture(
274
- cmd: string,
275
- args: string[],
276
- cwd: string,
277
- onLog?: (line: string) => void,
278
- ): Promise<{ code: number; out: string }> {
279
- return new Promise((resolve, reject) => {
280
- const child = spawn(cmd, args, {
281
- cwd,
282
- stdio: ['ignore', 'pipe', 'pipe'],
283
- env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
284
- })
285
- let out = ''
286
- const onData = (buf: Buffer) => {
287
- const text = buf.toString()
288
- out += text
289
- for (const line of text.split('\n')) if (line.trim()) onLog?.(line)
290
- }
291
- child.stdout?.on('data', onData)
292
- child.stderr?.on('data', onData)
293
- child.on('exit', (code) => resolve({ code: code ?? 1, out }))
294
- child.on('error', reject)
295
- })
296
- }
297
-
298
- async function stopChild(child: ChildProcess): Promise<void> {
299
- if (child.exitCode !== null) return
300
- await new Promise<void>((resolve) => {
301
- child.on('exit', () => resolve())
302
- child.kill('SIGTERM')
303
- setTimeout(() => {
304
- if (child.exitCode === null) child.kill('SIGKILL')
305
- resolve()
306
- }, 4000).unref?.()
307
- })
308
- }
309
-
310
- /**
311
- * Build the single-module worker bundle WITHOUT deploying — `wrangler deploy
312
- * --dry-run --outdir` (no Cloudflare auth required). Returns the path of the
313
- * bundled .js module (the managed adapter publishes its bytes).
314
- */
315
- export async function runWranglerBundle(args: {
316
- projectDir: string
317
- configPath: string
318
- outDir: string
319
- onLog?: (line: string) => void
320
- }): Promise<string> {
321
- await runWranglerTypes(args)
322
- const { cmd, prefix } = wranglerCommand(args.projectDir)
323
- const { code, out } = await runCapture(
324
- cmd,
325
- [...prefix, 'deploy', '--dry-run', '--outdir', args.outDir, '--config', args.configPath],
326
- args.projectDir,
327
- args.onLog,
328
- )
329
- if (code !== 0) throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`)
330
- const js = readdirSync(args.outDir).filter((f) => f.endsWith('.js'))
331
- if (js.length === 0) throw new Error(`wrangler bundle produced no .js in ${args.outDir}`)
332
- const preferred = js.find((f) => f.startsWith('worker')) ?? js[0]!
333
- return join(args.outDir, preferred)
334
- }
335
-
336
- /** Generate the exact binding and runtime types selected by the generated config. */
337
- export async function runWranglerTypes(args: {
338
- readonly projectDir: string
339
- readonly configPath: string
340
- readonly onLog?: (line: string) => void
341
- }): Promise<string> {
342
- const { cmd, prefix } = wranglerCommand(args.projectDir)
343
- const outputPath = join(dirname(args.configPath), 'worker-configuration.d.ts')
344
- const { code, out } = await runCapture(
345
- cmd,
346
- [
347
- ...prefix,
348
- 'types',
349
- outputPath,
350
- '--env-interface',
351
- 'CloudflareEnv',
352
- '--config',
353
- args.configPath,
354
- ],
355
- args.projectDir,
356
- args.onLog,
357
- )
358
- if (code !== 0) {
359
- throw new Error(`wrangler types failed (code ${code}):\n${out.slice(-2000)}`)
360
- }
361
- return outputPath
362
- }
package/src/cloudflare.ts DELETED
@@ -1,251 +0,0 @@
1
- /**
2
- * `cloudflare(envs)` — the Cloudflare deployment adapter.
3
- *
4
- * Owns everything target-specific: it codegens the Worker entry + wrangler
5
- * config (the dev never sees a `wrangler.jsonc`), materializes the optional frontend
6
- * SPA, and shells out to `wrangler`. `watch` → `wrangler dev` (local URL);
7
- * `deploy` → `wrangler deploy` (workers.dev or a custom-domain URL); secrets →
8
- * `wrangler secret bulk`. Both `watch` and `deploy` return the URL the CLI
9
- * prints and `astrale domain install <url>` consumes.
10
- */
11
-
12
- import type { DomainAdapter } from '@astrale-os/sdk'
13
-
14
- import { defineAdapter } from '@astrale-os/sdk'
15
-
16
- import type { CloudflareParams } from './params.js'
17
-
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'
29
-
30
- export function cloudflare(
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,
64
- ): DomainAdapter<CloudflareParams> {
65
- return defineAdapter<CloudflareParams>({
66
- name: 'cloudflare',
67
- envs,
68
-
69
- async build(params, ctx) {
70
- await dependencies.buildCloudflareWorker({
71
- params,
72
- context: ctx,
73
- onLog: dependencies.logTo(),
74
- })
75
- },
76
-
77
- async watch(params, ctx) {
78
- const onLog = dependencies.logTo()
79
- const frontend = await dependencies.buildFrontend({
80
- definition: ctx.definition,
81
- projectDir: ctx.projectDir,
82
- onLog,
83
- })
84
- const { configPath, port } = await dependencies.prepare(params, ctx, 'dev', frontend)
85
- const handle = await dependencies.runWranglerDev({
86
- projectDir: ctx.projectDir,
87
- configPath,
88
- port,
89
- remote: Boolean(params.remote),
90
- autoPickPort: params.port === undefined,
91
- ...(params.host ? { ip: '0.0.0.0' } : {}),
92
- onReload: ctx.onReload,
93
- onLog,
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
- }
117
- // With --host the actionable URL is the PUBLIC one (the tunnel/proxy
118
- // front), not the local bind — print and spec-stamp that.
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
- }
139
- },
140
-
141
- // Config hot-regen: rewrite the generated files only — the running
142
- // `wrangler dev` watches its `--config` (and the worker entry) and reloads
143
- // them itself, so a `astrale.config.ts` edit lands without a restart.
144
- // Mirrors `watch`'s prep exactly (codegen + frontend build), minus the spawn.
145
- async regenerate(params, ctx) {
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(),
156
- })
157
- },
158
-
159
- async deploy(params, ctx) {
160
- const frontend = await dependencies.buildFrontend({
161
- definition: ctx.definition,
162
- projectDir: ctx.projectDir,
163
- onLog: dependencies.logTo(),
164
- })
165
- const {
166
- configPath,
167
- fallbackConfigPath,
168
- workerName: name,
169
- } = await dependencies.prepare(params, ctx, 'deploy', frontend)
170
- const { url, deploymentVersion } = await dependencies.runWranglerDeploy({
171
- projectDir: ctx.projectDir,
172
- configPath,
173
- workerName: name,
174
- ...(fallbackConfigPath ? { fallbackConfigPath } : {}),
175
- ...(params.route ? { route: params.route } : {}),
176
- onLog: dependencies.logTo(),
177
- })
178
- if (Object.keys(ctx.secrets).length > 0) {
179
- await dependencies.bulkPutSecrets({
180
- projectDir: ctx.projectDir,
181
- configPath,
182
- secrets: ctx.secrets,
183
- onLog: dependencies.logTo(),
184
- })
185
- }
186
- // A first deploy on a fresh `*.workers.dev` host can take ~30-60s to
187
- // propagate; an `astrale domain install <url>` issued right away would
188
- // 404 (Cloudflare 1042). Block until the URL actually serves so the
189
- // install line we print is immediately actionable.
190
- await dependencies.waitUntilLive(url, dependencies.logTo(), deploymentVersion)
191
- return { url }
192
- },
193
-
194
- secretsFile(params) {
195
- return params.secrets
196
- },
197
- })
198
- }
199
-
200
- /**
201
- * Poll until the deployed Worker health endpoint returns exactly 200.
202
- */
203
- async function waitUntilLive(
204
- url: string,
205
- onLog: (line: string) => void,
206
- expectedDeploymentVersion?: string,
207
- timeoutMs = 90_000,
208
- ): Promise<void> {
209
- const probe = new URL('/health', url)
210
- const metadata = new URL('/meta', url)
211
- const deadline = Date.now() + timeoutMs
212
- let waited = false
213
- let lastStatus: number | undefined
214
- let lastDeploymentVersion: string | undefined
215
- for (;;) {
216
- try {
217
- const res = await fetch(probe, { redirect: 'manual' })
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
- }
232
- } catch {
233
- // Network hiccup — treat like "not live yet" and keep polling.
234
- }
235
- if (Date.now() >= deadline) {
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
- )
244
- }
245
- if (!waited) {
246
- waited = true
247
- onLog('waiting for the URL to go live (first deploys can take ~30-60s)…')
248
- }
249
- await new Promise((r) => setTimeout(r, 3000))
250
- }
251
- }
package/src/index.ts DELETED
@@ -1,21 +0,0 @@
1
- /**
2
- * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker on YOUR OWN Cloudflare account.
4
- *
5
- * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
- *
7
- * adapter: cloudflare({
8
- * dev: { secrets: '.env.dev' },
9
- * prod: { route: 'crm.acme.dev', secrets: '.env.prod' },
10
- * })
11
- *
12
- * `dev` runs `wrangler dev` locally; an env with no `route` ships to
13
- * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
- *
15
- * Prefer a managed deploy (no Cloudflare account)? Use
16
- * `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
17
- * `/build` toolkit and ships it through the Astrale platform.
18
- */
19
-
20
- export { cloudflare } from './cloudflare.js'
21
- export type { CloudflareParams } from './params.js'