@astrale-os/adapter-cloudflare 0.5.0-beta.13 → 0.5.0-beta.131

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/CHANGELOG.md +1515 -0
  2. package/README.md +46 -2
  3. package/dist/adapter.d.ts +14 -0
  4. package/dist/adapter.js +82 -0
  5. package/dist/build/artifact.d.ts +7 -0
  6. package/dist/build/artifact.js +165 -0
  7. package/dist/build/bundle/admit.d.ts +1 -0
  8. package/dist/build/bundle/admit.js +9 -0
  9. package/dist/build/closure.d.ts +2 -0
  10. package/dist/build/closure.js +22 -0
  11. package/dist/build/codegen/execution.d.ts +3 -0
  12. package/dist/build/codegen/execution.js +2 -0
  13. package/dist/build/codegen/merge.d.ts +0 -1
  14. package/dist/build/codegen/merge.js +0 -1
  15. package/dist/build/codegen/wrangler.d.ts +12 -3
  16. package/dist/build/codegen/wrangler.js +26 -18
  17. package/dist/build/configuration/addressing.d.ts +2 -0
  18. package/dist/build/configuration/addressing.js +11 -0
  19. package/dist/build/configuration/invocation-timeout.d.ts +2 -0
  20. package/dist/build/configuration/invocation-timeout.js +9 -0
  21. package/dist/build/configuration/request-limit.d.ts +2 -0
  22. package/dist/build/configuration/request-limit.js +9 -0
  23. package/dist/build/configuration/router.d.ts +7 -0
  24. package/dist/build/configuration/router.js +11 -0
  25. package/dist/build/configuration/worker-name.d.ts +2 -0
  26. package/dist/build/configuration/worker-name.js +18 -0
  27. package/dist/build/configuration.d.ts +5 -0
  28. package/dist/build/configuration.js +5 -0
  29. package/dist/build/direct.d.ts +4 -0
  30. package/dist/build/direct.js +90 -0
  31. package/dist/build/frontend/files.d.ts +9 -0
  32. package/dist/build/frontend/files.js +33 -0
  33. package/dist/build/frontend/readiness.d.ts +10 -0
  34. package/dist/build/frontend/readiness.js +21 -0
  35. package/dist/build/frontend/revision.d.ts +5 -0
  36. package/dist/build/frontend/revision.js +14 -0
  37. package/dist/build/frontend/vite.d.ts +8 -0
  38. package/dist/build/frontend/vite.js +32 -0
  39. package/dist/build/frontend/vite.process.d.ts +7 -0
  40. package/dist/build/frontend/vite.process.js +65 -0
  41. package/dist/build/frontend/vite.runner.d.ts +1 -0
  42. package/dist/build/frontend/vite.runner.js +120 -0
  43. package/dist/build/index.d.ts +9 -16
  44. package/dist/build/index.js +8 -16
  45. package/dist/build/materialize.d.ts +4 -0
  46. package/dist/build/materialize.js +10 -0
  47. package/dist/build/metafile.d.ts +5 -0
  48. package/dist/build/metafile.js +14 -0
  49. package/dist/build/qualification.d.ts +5 -0
  50. package/dist/build/qualification.js +7 -0
  51. package/dist/build/roots.d.ts +2 -0
  52. package/dist/build/roots.js +34 -0
  53. package/dist/build/worker/artifact.d.ts +40 -0
  54. package/dist/build/worker/artifact.js +1 -0
  55. package/dist/build/worker/artifact.load.d.ts +3 -0
  56. package/dist/build/worker/artifact.load.js +308 -0
  57. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  58. package/dist/build/worker/artifact.manifest.js +87 -0
  59. package/dist/build/worker/artifact.paths.d.ts +4 -0
  60. package/dist/build/worker/artifact.paths.js +25 -0
  61. package/dist/build/worker/codegen/entry.d.ts +2 -0
  62. package/dist/build/worker/codegen/entry.js +28 -0
  63. package/dist/build/worker/codegen/service.d.ts +19 -0
  64. package/dist/build/worker/codegen/service.js +53 -0
  65. package/dist/build/worker/execution.prepare.d.ts +19 -0
  66. package/dist/build/worker/execution.prepare.js +115 -0
  67. package/dist/build/worker/index.d.ts +2 -0
  68. package/dist/build/worker/index.js +1 -0
  69. package/dist/build/worker/modules.d.ts +11 -0
  70. package/dist/build/worker/modules.js +26 -0
  71. package/dist/build/worker/runtime.d.ts +10 -0
  72. package/dist/build/worker/runtime.js +35 -0
  73. package/dist/build/worker/specifier.d.ts +1 -0
  74. package/dist/build/worker/specifier.js +5 -0
  75. package/dist/cloudflare.d.ts +1 -45
  76. package/dist/cloudflare.js +1 -202
  77. package/dist/deploy/deploy.d.ts +17 -0
  78. package/dist/deploy/deploy.js +221 -0
  79. package/dist/deploy/index.d.ts +1 -0
  80. package/dist/deploy/index.js +1 -0
  81. package/dist/deploy/output.d.ts +2 -0
  82. package/dist/deploy/output.js +10 -0
  83. package/dist/deploy/readiness.d.ts +5 -0
  84. package/dist/deploy/readiness.js +18 -0
  85. package/dist/identity/binding.d.ts +2 -0
  86. package/dist/identity/binding.js +2 -0
  87. package/dist/identity/index.d.ts +3 -0
  88. package/dist/identity/index.js +3 -0
  89. package/dist/identity/material.d.ts +7 -0
  90. package/dist/identity/material.js +42 -0
  91. package/dist/identity/secret.d.ts +3 -0
  92. package/dist/identity/secret.js +7 -0
  93. package/dist/identity/source.d.ts +26 -0
  94. package/dist/identity/source.js +127 -0
  95. package/dist/index.d.ts +10 -9
  96. package/dist/index.js +10 -9
  97. package/dist/params.d.ts +40 -36
  98. package/dist/params.js +0 -1
  99. package/dist/release/deployment.d.ts +44 -0
  100. package/dist/release/deployment.js +141 -0
  101. package/dist/release/index.d.ts +2 -0
  102. package/dist/release/index.js +2 -0
  103. package/dist/release/staged-worker-name.d.ts +3 -0
  104. package/dist/release/staged-worker-name.js +10 -0
  105. package/dist/worker/entry.d.ts +48 -0
  106. package/dist/worker/entry.js +157 -0
  107. package/dist/worker/fetch.d.ts +0 -1
  108. package/dist/worker/fetch.js +0 -1
  109. package/dist/worker/frontend.d.ts +4 -0
  110. package/dist/worker/frontend.js +46 -0
  111. package/dist/worker/identity.d.ts +7 -0
  112. package/dist/worker/identity.js +23 -0
  113. package/dist/worker/index.d.ts +2 -61
  114. package/dist/worker/index.js +2 -218
  115. package/dist/wrangler/bundle.d.ts +10 -0
  116. package/dist/wrangler/bundle.js +28 -0
  117. package/dist/wrangler/deploy.d.ts +24 -0
  118. package/dist/wrangler/deploy.js +44 -0
  119. package/dist/wrangler/index.d.ts +5 -0
  120. package/dist/wrangler/index.js +5 -0
  121. package/dist/wrangler/process.d.ts +15 -0
  122. package/dist/wrangler/process.js +102 -0
  123. package/dist/wrangler/rollout.d.ts +27 -0
  124. package/dist/wrangler/rollout.js +75 -0
  125. package/dist/wrangler/secrets.d.ts +2 -0
  126. package/dist/wrangler/secrets.js +24 -0
  127. package/dist/wrangler/status.d.ts +18 -0
  128. package/dist/wrangler/status.js +75 -0
  129. package/dist/wrangler/triggers.d.ts +5 -0
  130. package/dist/wrangler/triggers.js +12 -0
  131. package/dist/wrangler/type-generation.d.ts +6 -0
  132. package/dist/wrangler/type-generation.js +19 -0
  133. package/dist/wrangler/versions.d.ts +13 -0
  134. package/dist/wrangler/versions.js +38 -0
  135. package/package.json +34 -19
  136. package/template/astrale.config.ts +30 -31
  137. package/dist/build/codegen/identity.d.ts +0 -10
  138. package/dist/build/codegen/identity.d.ts.map +0 -1
  139. package/dist/build/codegen/identity.js +0 -32
  140. package/dist/build/codegen/identity.js.map +0 -1
  141. package/dist/build/codegen/merge.d.ts.map +0 -1
  142. package/dist/build/codegen/merge.js.map +0 -1
  143. package/dist/build/codegen/worker.d.ts +0 -12
  144. package/dist/build/codegen/worker.d.ts.map +0 -1
  145. package/dist/build/codegen/worker.js +0 -40
  146. package/dist/build/codegen/worker.js.map +0 -1
  147. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  148. package/dist/build/codegen/wrangler.js.map +0 -1
  149. package/dist/build/frontend.d.ts +0 -27
  150. package/dist/build/frontend.d.ts.map +0 -1
  151. package/dist/build/frontend.js +0 -252
  152. package/dist/build/frontend.js.map +0 -1
  153. package/dist/build/index.d.ts.map +0 -1
  154. package/dist/build/index.js.map +0 -1
  155. package/dist/build/parse-output.d.ts +0 -27
  156. package/dist/build/parse-output.d.ts.map +0 -1
  157. package/dist/build/parse-output.js +0 -58
  158. package/dist/build/parse-output.js.map +0 -1
  159. package/dist/build/prepare.d.ts +0 -18
  160. package/dist/build/prepare.d.ts.map +0 -1
  161. package/dist/build/prepare.js +0 -138
  162. package/dist/build/prepare.js.map +0 -1
  163. package/dist/build/worker.d.ts +0 -15
  164. package/dist/build/worker.d.ts.map +0 -1
  165. package/dist/build/worker.js +0 -21
  166. package/dist/build/worker.js.map +0 -1
  167. package/dist/build/wrangler-cli.d.ts +0 -88
  168. package/dist/build/wrangler-cli.d.ts.map +0 -1
  169. package/dist/build/wrangler-cli.js +0 -283
  170. package/dist/build/wrangler-cli.js.map +0 -1
  171. package/dist/cloudflare.d.ts.map +0 -1
  172. package/dist/cloudflare.js.map +0 -1
  173. package/dist/index.d.ts.map +0 -1
  174. package/dist/index.js.map +0 -1
  175. package/dist/params.d.ts.map +0 -1
  176. package/dist/params.js.map +0 -1
  177. package/dist/worker/fetch.d.ts.map +0 -1
  178. package/dist/worker/fetch.js.map +0 -1
  179. package/dist/worker/index.d.ts.map +0 -1
  180. package/dist/worker/index.js.map +0 -1
  181. package/src/build/codegen/identity.ts +0 -40
  182. package/src/build/codegen/merge.ts +0 -92
  183. package/src/build/codegen/worker.ts +0 -53
  184. package/src/build/codegen/wrangler.ts +0 -128
  185. package/src/build/frontend.ts +0 -298
  186. package/src/build/index.ts +0 -29
  187. package/src/build/parse-output.ts +0 -60
  188. package/src/build/prepare.ts +0 -189
  189. package/src/build/worker.ts +0 -37
  190. package/src/build/wrangler-cli.ts +0 -372
  191. package/src/cloudflare.ts +0 -275
  192. package/src/index.ts +0 -21
  193. package/src/params.ts +0 -92
  194. package/src/worker/fetch.ts +0 -46
  195. package/src/worker/index.ts +0 -332
  196. package/src/worker/tsconfig.json +0 -10
@@ -1,53 +0,0 @@
1
- /** Codegen for the Cloudflare module entry owned by the adapter. */
2
-
3
- export interface WorkerCodegenOptions {
4
- readonly origin: string
5
- /** Optional same-zone routing service selected by the adapter environment. */
6
- readonly router?: {
7
- readonly binding: string
8
- readonly hostSuffix: string
9
- readonly minLabels: number
10
- }
11
- }
12
-
13
- export function generateWorkerEntry(options: WorkerCodegenOptions): string {
14
- const router = options.router
15
- const routerPredicate =
16
- router === undefined
17
- ? ''
18
- : `
19
- function isInstanceHost(host: string): boolean {
20
- return host.endsWith('${router.hostSuffix}') && host.split('.').length >= ${router.minLabels}
21
- }
22
- `
23
- const routeSubrequest =
24
- router === undefined
25
- ? ''
26
- : `
27
- routeSubrequest: (url, env) => {
28
- const labels = url.hostname.split('.')
29
- if (env.DISPATCHER && labels[1] === 'svc') return env.DISPATCHER.get(labels[0]!)
30
- if (env.${router.binding} && isInstanceHost(url.hostname)) return env.${router.binding}
31
- return null
32
- },`
33
-
34
- return `// AUTO-GENERATED by @astrale-os/adapter-cloudflare — do not edit.
35
- // Regenerated by every Astrale Domain build, dev, and deploy operation.
36
- // Domain origin: ${options.origin}
37
- import type { CloudflareAdapterBindings } from '@astrale-os/adapter-cloudflare/worker'
38
- import type { ApplicationEnvironment } from '../implementation.js'
39
-
40
- import { cloudflareWorkerEntry } from '@astrale-os/adapter-cloudflare/worker'
41
- import { domain } from '../implementation.js'
42
- import { PRIVATE_JWK } from './identity.js'
43
-
44
- type WorkerEnv = CloudflareEnv & ApplicationEnvironment & CloudflareAdapterBindings
45
- ${routerPredicate}
46
- export default cloudflareWorkerEntry<WorkerEnv>()({
47
- definition: domain,
48
- privateKey: PRIVATE_JWK,
49
- resolveUrl: (env, requestOrigin) => env.WORKER_URL ?? requestOrigin,
50
- selfBinding: (env) => env.SELF,${routeSubrequest}
51
- })
52
- `
53
- }
@@ -1,128 +0,0 @@
1
- /**
2
- * Codegen for the Worker config (`.astrale/wrangler.gen.jsonc`). This is the
3
- * `wrangler.jsonc` that used to be hand-copied into every domain — now an
4
- * internal adapter detail the dev never sees.
5
- *
6
- * `main` is `./worker.gen.ts` (same dir). `assets.directory` points at the
7
- * adapter's materialized frontend stage. A `SELF` service binding enables autobinding
8
- * (a handler calling back into its own domain). Custom-domain deploys attach a
9
- * `custom_domain` route; otherwise the Worker ships to `*.workers.dev`.
10
- *
11
- * A dev's `params.wrangler` escape hatch is deep-merged on top of this base
12
- * (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
13
- */
14
-
15
- import { deepMergeConfig } from './merge.js'
16
-
17
- export interface WranglerCodegenOptions {
18
- workerName: string
19
- /** Full custom hostname for a routed deploy (e.g. 'crm.acme.dev'). */
20
- route?: string
21
- /** Exact materialized local frontend; absence means no ASSETS binding. */
22
- frontend?: {
23
- readonly directory: string
24
- readonly routes: readonly string[]
25
- readonly dev: boolean
26
- }
27
- /** Plain (non-secret) vars to bake in. */
28
- vars?: Record<string, string>
29
- /** Add the SELF service binding (autobinding). */
30
- selfBinding: boolean
31
- /**
32
- * Add a service binding to a router Worker for instance-host subrequest
33
- * routing (see `CloudflareParams.router`). Pairs with the worker
34
- * entry's `routeSubrequest`.
35
- */
36
- router?: { binding: string; service: string }
37
- /**
38
- * Raw wrangler config to deep-merge over the generated base — the escape
39
- * hatch for extra bindings (KV, R2, D1, queues, service bindings, extra
40
- * `compatibility_flags`, cron `triggers`, …). Arrays concat; new keys are
41
- * added as-is. The adapter-owned keys `name`, `main`, `assets`, `routes` are
42
- * rejected (throws) — use `workerName` / `route` instead.
43
- */
44
- wrangler?: Record<string, unknown>
45
- }
46
-
47
- // Wrangler 4.121.0's pinned workerd accepts dates through 2026-08-11.
48
- const COMPATIBILITY_DATE = '2026-08-11'
49
-
50
- export function generateWranglerConfig(opts: WranglerCodegenOptions): string {
51
- const config: Record<string, unknown> = {
52
- name: opts.workerName,
53
- main: './worker.gen.ts',
54
- compatibility_date: COMPATIBILITY_DATE,
55
- compatibility_flags: ['nodejs_compat'],
56
- workers_dev: true,
57
- version_metadata: { binding: 'ASTRALE_VERSION_METADATA' },
58
- observability: { enabled: true },
59
- }
60
-
61
- if (opts.route) {
62
- config.routes = [{ pattern: opts.route, custom_domain: true }]
63
- }
64
-
65
- if (opts.frontend !== undefined) {
66
- config.assets = {
67
- directory: opts.frontend.directory,
68
- binding: 'ASSETS',
69
- run_worker_first: opts.frontend.dev
70
- ? true
71
- : [
72
- '/invoke',
73
- '/invoke/*',
74
- '/install',
75
- '/install/*',
76
- '/health',
77
- '/health/*',
78
- '/.well-known',
79
- '/.well-known/*',
80
- ...opts.frontend.routes,
81
- ],
82
- }
83
- }
84
-
85
- const services: Array<{ binding: string; service: string }> = []
86
- if (opts.selfBinding) services.push({ binding: 'SELF', service: opts.workerName })
87
- if (opts.router) {
88
- services.push({ binding: opts.router.binding, service: opts.router.service })
89
- }
90
- if (services.length > 0) config.services = services
91
-
92
- if (opts.vars && Object.keys(opts.vars).length > 0) {
93
- config.vars = opts.vars
94
- }
95
-
96
- const merged = opts.wrangler ? mergeUserConfig(config, opts.wrangler) : config
97
- return JSON.stringify(merged, null, 2) + '\n'
98
- }
99
-
100
- /** Keys the adapter owns and won't let `params.wrangler` clobber. */
101
- const ADAPTER_OWNED_KEYS = [
102
- 'name',
103
- 'main',
104
- 'assets',
105
- 'routes',
106
- 'compatibility_date',
107
- 'version_metadata',
108
- ]
109
-
110
- /**
111
- * Deep-merge the dev's raw wrangler overlay over the generated config, after
112
- * refusing any attempt to override an adapter-owned key. Throwing (rather than
113
- * silently dropping) keeps a stray `main` from breaking the Worker unnoticed.
114
- */
115
- function mergeUserConfig(
116
- base: Record<string, unknown>,
117
- user: Record<string, unknown>,
118
- ): Record<string, unknown> {
119
- const collisions = ADAPTER_OWNED_KEYS.filter((k) => k in user)
120
- if (collisions.length > 0) {
121
- throw new Error(
122
- `wrangler: cannot override adapter-managed key(s): ${collisions.join(', ')}. ` +
123
- 'Use `workerName` for the worker name and `route` for routing; ' +
124
- '`main` and `assets` are generated by the adapter.',
125
- )
126
- }
127
- return deepMergeConfig(base, user)
128
- }
@@ -1,298 +0,0 @@
1
- import type { CompiledFrontendArtifact, DomainDefinition, FrontendFile } from '@astrale-os/sdk'
2
- import type { ChildProcess } from 'node:child_process'
3
-
4
- import { compileFrontend } from '@astrale-os/sdk'
5
- import { spawn } from 'node:child_process'
6
- import { existsSync } from 'node:fs'
7
- import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
8
- import { createServer } from 'node:net'
9
- import { dirname, join, resolve, sep } from 'node:path'
10
-
11
- export const FRONTEND_STAGE_DIR = '.astrale/frontend'
12
- const FRONTEND_BUILD_DIR = '.astrale/frontend-build'
13
- let stageGeneration = 0
14
- const buildQueues = new Map<string, Promise<void>>()
15
-
16
- export interface ViteBuildInput {
17
- readonly projectDir: string
18
- readonly output: string
19
- readonly config?: string
20
- readonly onLog?: (line: string) => void
21
- }
22
-
23
- export type ViteBuild = (input: ViteBuildInput) => Promise<void>
24
-
25
- /** Materialize one Domain-owned frontend recipe into an immutable adapter artifact. */
26
- export async function buildFrontend(input: {
27
- readonly definition: DomainDefinition
28
- readonly projectDir: string
29
- readonly onLog?: (line: string) => void
30
- readonly buildVite?: ViteBuild
31
- }): Promise<CompiledFrontendArtifact | undefined> {
32
- const key = resolve(input.projectDir)
33
- const previous = buildQueues.get(key) ?? Promise.resolve()
34
- let release = (): void => {}
35
- const current = new Promise<void>((resolveBuild) => {
36
- release = resolveBuild
37
- })
38
- const tail = previous.then(() => current)
39
- buildQueues.set(key, tail)
40
- await previous
41
- try {
42
- return await buildFrontendUnlocked(input)
43
- } finally {
44
- release()
45
- if (buildQueues.get(key) === tail) buildQueues.delete(key)
46
- }
47
- }
48
-
49
- async function buildFrontendUnlocked(input: {
50
- readonly definition: DomainDefinition
51
- readonly projectDir: string
52
- readonly onLog?: (line: string) => void
53
- readonly buildVite?: ViteBuild
54
- }): Promise<CompiledFrontendArtifact | undefined> {
55
- const artifact = input.definition.frontend
56
- if (artifact === undefined) return undefined
57
- if (artifact.source.kind === 'external') {
58
- return compileFrontend({
59
- artifact,
60
- files: [],
61
- provenance: { adapter: 'cloudflare', source: 'external' },
62
- })
63
- }
64
- const output = contained(input.projectDir, FRONTEND_BUILD_DIR, 'Vite frontend staging output')
65
- await rm(output, { recursive: true, force: true })
66
- await (input.buildVite ?? runViteBuild)({
67
- projectDir: input.projectDir,
68
- output,
69
- ...(artifact.source.config === undefined ? {} : { config: artifact.source.config }),
70
- ...(input.onLog === undefined ? {} : { onLog: input.onLog }),
71
- })
72
- const files = await readFiles(output)
73
- const compiled = await compileFrontend({
74
- artifact,
75
- baseHref: 'https://frontend.invalid/',
76
- files,
77
- provenance: { adapter: 'cloudflare', source: artifact.source.kind },
78
- })
79
- await replaceStage(input.projectDir, compiled.files)
80
- return compiled
81
- }
82
-
83
- export interface FrontendDevHandle {
84
- readonly url: string
85
- stop(): Promise<void>
86
- }
87
-
88
- /** Start the Domain project's own Vite server; the Worker remains the browser-facing origin. */
89
- export async function startFrontendDev(input: {
90
- readonly definition: DomainDefinition
91
- readonly projectDir: string
92
- readonly onLog?: (line: string) => void
93
- }): Promise<FrontendDevHandle | undefined> {
94
- const artifact = input.definition.frontend
95
- if (artifact === undefined || artifact.source.kind !== 'vite') return undefined
96
- const command = viteCommand(input.projectDir)
97
- const port = await ephemeralPort('127.0.0.1')
98
- const args = [
99
- ...command.prefix,
100
- '--host',
101
- '127.0.0.1',
102
- '--port',
103
- String(port),
104
- '--strictPort',
105
- ...(artifact.source.config === undefined ? [] : ['--config', artifact.source.config]),
106
- ]
107
- const child = spawn(command.command, args, {
108
- cwd: input.projectDir,
109
- stdio: ['ignore', 'pipe', 'pipe'],
110
- })
111
- const output: string[] = []
112
- const collect = (buffer: Buffer) => {
113
- for (const line of buffer.toString().split('\n')) {
114
- if (line.trim() === '') continue
115
- output.push(line)
116
- if (output.length > 40) output.shift()
117
- input.onLog?.(line)
118
- }
119
- }
120
- child.stdout?.on('data', collect)
121
- child.stderr?.on('data', collect)
122
- const url = `http://127.0.0.1:${port}`
123
- try {
124
- await waitUntilReady(url, child, output)
125
- } catch (error) {
126
- await stopChild(child)
127
- throw error
128
- }
129
- return Object.freeze({ url, stop: () => stopChild(child) })
130
- }
131
-
132
- async function readFiles(root: string, current = ''): Promise<readonly FrontendFile[]> {
133
- const directory = join(root, current)
134
- const entries = await readdir(directory, { withFileTypes: true })
135
- const files: FrontendFile[] = []
136
- for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
137
- const path = current === '' ? entry.name : `${current}/${entry.name}`
138
- if (entry.isDirectory()) files.push(...(await readFiles(root, path)))
139
- else if (entry.isFile())
140
- files.push({ path, content: new Uint8Array(await readFile(join(root, path))) })
141
- }
142
- return files
143
- }
144
-
145
- async function replaceStage(projectDir: string, files: readonly FrontendFile[]): Promise<void> {
146
- const stage = contained(projectDir, FRONTEND_STAGE_DIR, 'frontend stage')
147
- const generation = `${process.pid}-${++stageGeneration}`
148
- const next = `${stage}.next-${generation}`
149
- const previous = `${stage}.previous-${generation}`
150
- await rm(next, { recursive: true, force: true })
151
- await mkdir(next, { recursive: true })
152
- try {
153
- for (const file of files) {
154
- const path = contained(next, file.path, 'staged frontend file')
155
- await mkdir(dirname(path), { recursive: true })
156
- await writeFile(path, file.content)
157
- }
158
- await rm(previous, { recursive: true, force: true })
159
- let retainedPrevious = false
160
- try {
161
- await rename(stage, previous)
162
- retainedPrevious = true
163
- } catch (error) {
164
- if (!isMissing(error)) throw error
165
- }
166
- try {
167
- await rename(next, stage)
168
- } catch (error) {
169
- if (retainedPrevious) await rename(previous, stage)
170
- throw error
171
- }
172
- if (retainedPrevious) await rm(previous, { recursive: true, force: true })
173
- } finally {
174
- await rm(next, { recursive: true, force: true })
175
- }
176
- }
177
-
178
- function isMissing(error: unknown): boolean {
179
- return error instanceof Error && 'code' in error && error.code === 'ENOENT'
180
- }
181
-
182
- function contained(root: string, input: string, label: string): string {
183
- const base = resolve(root)
184
- const target = resolve(base, input)
185
- if (target !== base && !target.startsWith(`${base}${sep}`)) {
186
- throw new TypeError(`${label} must remain within the Domain project root.`)
187
- }
188
- return target
189
- }
190
-
191
- function viteCommand(projectDir: string): { command: string; prefix: readonly string[] } {
192
- const command = join(projectDir, 'node_modules', '.bin', 'vite')
193
- if (!existsSync(command)) {
194
- throw new TypeError(
195
- 'Vite frontend requires the Domain project to install vite as a development dependency.',
196
- )
197
- }
198
- return { command, prefix: [] }
199
- }
200
-
201
- async function runViteBuild(input: ViteBuildInput): Promise<void> {
202
- const command = viteCommand(input.projectDir)
203
- await runProcess(
204
- command.command,
205
- [
206
- ...command.prefix,
207
- 'build',
208
- '--outDir',
209
- input.output,
210
- '--emptyOutDir',
211
- ...(input.config === undefined ? [] : ['--config', input.config]),
212
- ],
213
- input.projectDir,
214
- input.onLog,
215
- )
216
- }
217
-
218
- function runProcess(
219
- command: string,
220
- args: readonly string[],
221
- cwd: string,
222
- onLog?: (line: string) => void,
223
- ): Promise<void> {
224
- return new Promise((resolveBuild, reject) => {
225
- const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
226
- let output = ''
227
- const collect = (buffer: Buffer) => {
228
- const value = buffer.toString()
229
- output += value
230
- for (const line of value.split('\n')) if (line.trim() !== '') onLog?.(line)
231
- }
232
- child.stdout?.on('data', collect)
233
- child.stderr?.on('data', collect)
234
- child.on('error', reject)
235
- child.on('exit', (code) =>
236
- code === 0
237
- ? resolveBuild()
238
- : reject(
239
- new Error(`Vite frontend build failed (code ${code ?? 1}):\n${output.slice(-1500)}`),
240
- ),
241
- )
242
- })
243
- }
244
-
245
- function ephemeralPort(host: string): Promise<number> {
246
- return new Promise((resolvePort, reject) => {
247
- const server = createServer()
248
- server.once('error', reject)
249
- server.listen(0, host, () => {
250
- const address = server.address()
251
- const port = typeof address === 'object' && address !== null ? address.port : 0
252
- server.close((error) => (error === undefined ? resolvePort(port) : reject(error)))
253
- })
254
- })
255
- }
256
-
257
- async function waitUntilReady(
258
- url: string,
259
- child: ChildProcess,
260
- output: readonly string[],
261
- ): Promise<void> {
262
- const deadline = Date.now() + 15_000
263
- while (Date.now() < deadline) {
264
- if (child.exitCode !== null) {
265
- throw new Error(
266
- `Vite frontend dev server exited before becoming ready (code ${child.exitCode}).\n${output.join('\n')}`,
267
- )
268
- }
269
- try {
270
- const response = await fetch(url, { signal: AbortSignal.timeout(500) })
271
- await response.body?.cancel()
272
- return
273
- } catch {
274
- await new Promise((resolveDelay) => setTimeout(resolveDelay, 50))
275
- }
276
- }
277
- throw new Error(`Vite frontend dev server did not become ready at ${url}.\n${output.join('\n')}`)
278
- }
279
-
280
- function stopChild(child: ChildProcess): Promise<void> {
281
- if (child.exitCode !== null || child.killed) return Promise.resolve()
282
- return new Promise((resolveStop) => {
283
- let settled = false
284
- const finish = () => {
285
- if (settled) return
286
- settled = true
287
- clearTimeout(timer)
288
- resolveStop()
289
- }
290
- const timer = setTimeout(() => {
291
- if (child.exitCode === null) child.kill('SIGKILL')
292
- finish()
293
- }, 2_000)
294
- timer.unref?.()
295
- child.once('exit', finish)
296
- child.kill('SIGTERM')
297
- })
298
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * `@astrale-os/adapter-cloudflare/build` — the shared Cloudflare-Workers build
3
- * toolkit, exposed for sibling deploy adapters (e.g.
4
- * `@astrale-os/adapter-astrale`).
5
- *
6
- * This is the "compile a `defineDomain` into a Cloudflare Workers bundle + run
7
- * local dev" surface that every Cloudflare-Workers deploy strategy shares,
8
- * regardless of WHERE the bundle ultimately ships. It is NOT part of the public
9
- * adapter API (`@astrale-os/adapter-cloudflare`) — it's the seam two adapters
10
- * build on so the codegen/bundling/dev logic lives in exactly one place.
11
- */
12
-
13
- export {
14
- buildFrontend,
15
- FRONTEND_STAGE_DIR,
16
- startFrontendDev,
17
- type FrontendDevHandle,
18
- type ViteBuild,
19
- type ViteBuildInput,
20
- } from './frontend.js'
21
- export { logTo, prepare } from './prepare.js'
22
- export { buildCloudflareWorker, type CloudflareWorkerBuild } from './worker.js'
23
- export {
24
- bulkPutSecrets,
25
- runWranglerBundle,
26
- runWranglerDeploy,
27
- runWranglerDev,
28
- runWranglerTypes,
29
- } from './wrangler-cli.js'
@@ -1,60 +0,0 @@
1
- /**
2
- * Pure parsers for `wrangler` stdout. Kept separate from the process-spawning in
3
- * `wrangler-cli.ts` so the brittle bit — scraping wrangler's human output — is
4
- * unit-testable against captured fixtures without shelling out.
5
- */
6
-
7
- const READY_RE = /(?:Ready on|Listening on|⎔.*?at)\s+(https?:\/\/[^\s]+)/i
8
- const WORKERS_DEV_RE = /(https:\/\/[^\s]+\.workers\.dev)/i
9
- const VERSION_ID_RE = /(?:Current\s+)?Version ID:\s*([a-z0-9_-]+)/i
10
-
11
- /**
12
- * Local dev URL from `wrangler dev` output, normalized to an origin on the given
13
- * port. Returns `undefined` when the output hasn't reported readiness yet.
14
- *
15
- * The fallback (when no explicit "Ready on" line) only accepts a localhost URL
16
- * on the EXPECTED port — wrangler may print unrelated localhost URLs (e.g. an
17
- * inspector/devtools endpoint on a different port) before readiness, and taking
18
- * one of those would point `astrale domain install <url>` at a dead endpoint.
19
- */
20
- export function parseDevReadyUrl(text: string, port: number): string | undefined {
21
- const ready = READY_RE.exec(text)
22
- if (ready) return normalizeLocal(ready[1]!, port)
23
- // `port` is a number, so it's safe to interpolate into the pattern. The
24
- // negative lookahead stops `:8787` from matching inside `:87870`.
25
- const localhostOnPort = new RegExp(
26
- `(https?://(?:localhost|127\\.0\\.0\\.1):${port})(?![0-9])`,
27
- 'i',
28
- )
29
- const m = localhostOnPort.exec(text)
30
- return m ? normalizeLocal(m[1]!, port) : undefined
31
- }
32
-
33
- /**
34
- * Authoritative public URL from `wrangler deploy` output. A custom-domain
35
- * `route` wins (the deploy attaches it); otherwise the printed `*.workers.dev`
36
- * URL. Returns `undefined` when neither is available (caller surfaces the raw
37
- * output).
38
- */
39
- export function parseDeployUrl(text: string, route?: string): string | undefined {
40
- if (route) return `https://${route.replace(/\/.*$/, '')}`
41
- const m = WORKERS_DEV_RE.exec(text)
42
- return m ? m[1] : undefined
43
- }
44
-
45
- /** Cloudflare version published by the final Wrangler deploy pass. */
46
- export function parseDeployVersionId(text: string): string | undefined {
47
- return VERSION_ID_RE.exec(text)?.[1]
48
- }
49
-
50
- /** Normalize a local dev URL to an origin: `0.0.0.0` → `localhost`, fill the port. */
51
- export function normalizeLocal(url: string, port: number): string {
52
- try {
53
- const u = new URL(url)
54
- if (u.hostname === '0.0.0.0') u.hostname = 'localhost'
55
- if (!u.port) u.port = String(port)
56
- return u.origin
57
- } catch {
58
- return `http://localhost:${port}`
59
- }
60
- }