@astrale-os/adapter-cloudflare 0.5.0-beta.11 → 0.5.0-beta.13
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/wrangler.d.ts +1 -0
- package/dist/build/codegen/wrangler.d.ts.map +1 -1
- package/dist/build/codegen/wrangler.js +13 -13
- package/dist/build/codegen/wrangler.js.map +1 -1
- package/dist/build/frontend.d.ts +13 -12
- package/dist/build/frontend.d.ts.map +1 -1
- package/dist/build/frontend.js +120 -131
- package/dist/build/frontend.js.map +1 -1
- package/dist/build/index.d.ts +1 -1
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +1 -1
- package/dist/build/index.js.map +1 -1
- package/dist/build/prepare.d.ts +1 -1
- package/dist/build/prepare.d.ts.map +1 -1
- package/dist/build/prepare.js +26 -5
- package/dist/build/prepare.js.map +1 -1
- package/dist/cloudflare.d.ts +15 -4
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +67 -70
- package/dist/cloudflare.js.map +1 -1
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +21 -15
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -2
- package/src/build/codegen/wrangler.ts +14 -13
- package/src/build/frontend.ts +147 -173
- package/src/build/index.ts +4 -4
- package/src/build/prepare.ts +34 -6
- package/src/cloudflare.ts +94 -70
- package/src/worker/index.ts +24 -17
package/src/cloudflare.ts
CHANGED
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { DomainAdapter } from '@astrale-os/sdk'
|
|
13
|
+
import type { DeployFetch } from '@astrale-os/sdk/deployment'
|
|
13
14
|
|
|
14
15
|
import { defineAdapter } from '@astrale-os/sdk'
|
|
16
|
+
import { deployCheck } from '@astrale-os/sdk/deployment'
|
|
17
|
+
import { resolve } from 'node:path'
|
|
15
18
|
|
|
16
19
|
import type { CloudflareParams } from './params.js'
|
|
17
20
|
|
|
@@ -24,7 +27,7 @@ import {
|
|
|
24
27
|
runWranglerDeploy,
|
|
25
28
|
runWranglerDev,
|
|
26
29
|
runWranglerTypes,
|
|
27
|
-
|
|
30
|
+
startFrontendDev,
|
|
28
31
|
} from './build/index.js'
|
|
29
32
|
|
|
30
33
|
export function cloudflare(
|
|
@@ -39,7 +42,7 @@ export function cloudflare(
|
|
|
39
42
|
runWranglerDeploy,
|
|
40
43
|
runWranglerDev,
|
|
41
44
|
runWranglerTypes,
|
|
42
|
-
|
|
45
|
+
startFrontendDev,
|
|
43
46
|
waitUntilLive,
|
|
44
47
|
})
|
|
45
48
|
}
|
|
@@ -53,7 +56,7 @@ interface CloudflareAdapterDependencies {
|
|
|
53
56
|
readonly runWranglerDeploy: typeof runWranglerDeploy
|
|
54
57
|
readonly runWranglerDev: typeof runWranglerDev
|
|
55
58
|
readonly runWranglerTypes: typeof runWranglerTypes
|
|
56
|
-
readonly
|
|
59
|
+
readonly startFrontendDev: typeof startFrontendDev
|
|
57
60
|
readonly waitUntilLive: typeof waitUntilLive
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -62,6 +65,7 @@ export function cloudflareWithDependencies(
|
|
|
62
65
|
envs: Record<string, CloudflareParams>,
|
|
63
66
|
dependencies: CloudflareAdapterDependencies,
|
|
64
67
|
): DomainAdapter<CloudflareParams> {
|
|
68
|
+
const frontendDevUrls = new Map<string, string>()
|
|
65
69
|
return defineAdapter<CloudflareParams>({
|
|
66
70
|
name: 'cloudflare',
|
|
67
71
|
envs,
|
|
@@ -76,52 +80,56 @@ export function cloudflareWithDependencies(
|
|
|
76
80
|
|
|
77
81
|
async watch(params, ctx) {
|
|
78
82
|
const onLog = dependencies.logTo()
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
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>
|
|
83
|
+
const projectKey = resolve(ctx.projectDir)
|
|
84
|
+
let frontendDev: Awaited<ReturnType<typeof startFrontendDev>>
|
|
85
|
+
let handle: Awaited<ReturnType<typeof runWranglerDev>> | undefined
|
|
96
86
|
try {
|
|
97
|
-
|
|
87
|
+
frontendDev = await dependencies.startFrontendDev({
|
|
98
88
|
definition: ctx.definition,
|
|
99
89
|
projectDir: ctx.projectDir,
|
|
100
90
|
onLog,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
})
|
|
92
|
+
if (frontendDev !== undefined) frontendDevUrls.set(projectKey, frontendDev.url)
|
|
93
|
+
const { configPath, port } = await dependencies.prepare(
|
|
94
|
+
params,
|
|
95
|
+
ctx,
|
|
96
|
+
'dev',
|
|
97
|
+
undefined,
|
|
98
|
+
frontendDev?.url,
|
|
99
|
+
)
|
|
100
|
+
handle = await dependencies.runWranglerDev({
|
|
101
|
+
projectDir: ctx.projectDir,
|
|
102
|
+
configPath,
|
|
103
|
+
port,
|
|
104
|
+
remote: Boolean(params.remote),
|
|
105
|
+
autoPickPort: params.port === undefined,
|
|
106
|
+
...(params.host ? { ip: '0.0.0.0' } : {}),
|
|
107
|
+
onReload: ctx.onReload,
|
|
108
|
+
onLog,
|
|
105
109
|
})
|
|
106
110
|
} catch (error) {
|
|
111
|
+
frontendDevUrls.delete(projectKey)
|
|
107
112
|
try {
|
|
108
|
-
await
|
|
113
|
+
await frontendDev?.stop()
|
|
109
114
|
} catch (cleanupError) {
|
|
110
115
|
throw new AggregateError(
|
|
111
116
|
[error, cleanupError],
|
|
112
|
-
'cloudflare
|
|
117
|
+
'cloudflare development startup failed and Vite cleanup also failed',
|
|
113
118
|
)
|
|
114
119
|
}
|
|
115
120
|
throw error
|
|
116
121
|
}
|
|
122
|
+
if (handle === undefined) throw new Error('cloudflare local Worker failed to start')
|
|
117
123
|
// With --host the actionable URL is the PUBLIC one (the tunnel/proxy
|
|
118
124
|
// front), not the local bind — print and spec-stamp that.
|
|
119
125
|
return {
|
|
120
126
|
url: params.host ?? handle.url,
|
|
127
|
+
localUrl: handle.url,
|
|
121
128
|
async stop() {
|
|
122
129
|
const errors: unknown[] = []
|
|
130
|
+
frontendDevUrls.delete(projectKey)
|
|
123
131
|
try {
|
|
124
|
-
await
|
|
132
|
+
await frontendDev?.stop()
|
|
125
133
|
} catch (error) {
|
|
126
134
|
errors.push(error)
|
|
127
135
|
}
|
|
@@ -138,17 +146,16 @@ export function cloudflareWithDependencies(
|
|
|
138
146
|
}
|
|
139
147
|
},
|
|
140
148
|
|
|
141
|
-
// Config hot-regen
|
|
142
|
-
//
|
|
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.
|
|
149
|
+
// Config hot-regen rewrites generated files only. Wrangler reloads its config and entry;
|
|
150
|
+
// the author-owned Vite process keeps its own source/config watch lifecycle.
|
|
145
151
|
async regenerate(params, ctx) {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
const { configPath } = await dependencies.prepare(
|
|
153
|
+
params,
|
|
154
|
+
ctx,
|
|
155
|
+
'dev',
|
|
156
|
+
undefined,
|
|
157
|
+
frontendDevUrls.get(resolve(ctx.projectDir)),
|
|
158
|
+
)
|
|
152
159
|
await dependencies.runWranglerTypes({
|
|
153
160
|
projectDir: ctx.projectDir,
|
|
154
161
|
configPath,
|
|
@@ -187,7 +194,13 @@ export function cloudflareWithDependencies(
|
|
|
187
194
|
// propagate; an `astrale domain install <url>` issued right away would
|
|
188
195
|
// 404 (Cloudflare 1042). Block until the URL actually serves so the
|
|
189
196
|
// install line we print is immediately actionable.
|
|
190
|
-
await dependencies.waitUntilLive(url, dependencies.logTo(),
|
|
197
|
+
await dependencies.waitUntilLive(url, dependencies.logTo(), {
|
|
198
|
+
expectedOrigin: ctx.definition.schema.origin,
|
|
199
|
+
expectedRevision: ctx.definition.domain.$.revision,
|
|
200
|
+
...(deploymentVersion === undefined
|
|
201
|
+
? {}
|
|
202
|
+
: { expectedDeploymentVersion: deploymentVersion }),
|
|
203
|
+
})
|
|
191
204
|
return { url }
|
|
192
205
|
},
|
|
193
206
|
|
|
@@ -198,54 +211,65 @@ export function cloudflareWithDependencies(
|
|
|
198
211
|
}
|
|
199
212
|
|
|
200
213
|
/**
|
|
201
|
-
* Poll until the deployed Worker
|
|
214
|
+
* Poll until the deployed Worker exposes one complete canonical deployment chain.
|
|
202
215
|
*/
|
|
203
|
-
async function waitUntilLive(
|
|
216
|
+
export async function waitUntilLive(
|
|
204
217
|
url: string,
|
|
205
218
|
onLog: (line: string) => void,
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
expectations: {
|
|
220
|
+
readonly expectedOrigin: Parameters<typeof deployCheck>[0]['expectedOrigin']
|
|
221
|
+
readonly expectedRevision: Parameters<typeof deployCheck>[0]['expectedRevision']
|
|
222
|
+
readonly expectedDeploymentVersion?: string
|
|
223
|
+
readonly fetch?: DeployFetch
|
|
224
|
+
readonly timeoutMs?: number
|
|
225
|
+
readonly fetchTimeoutMs?: number
|
|
226
|
+
readonly retryMs?: number
|
|
227
|
+
readonly sleep?: (ms: number) => Promise<void>
|
|
228
|
+
},
|
|
208
229
|
): Promise<void> {
|
|
209
|
-
const
|
|
210
|
-
const
|
|
230
|
+
const timeoutMs = expectations.timeoutMs ?? 90_000
|
|
231
|
+
const fetchTimeoutMs = expectations.fetchTimeoutMs ?? 5_000
|
|
232
|
+
const retryMs = expectations.retryMs ?? 3_000
|
|
233
|
+
const sleep =
|
|
234
|
+
expectations.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)))
|
|
211
235
|
const deadline = Date.now() + timeoutMs
|
|
212
236
|
let waited = false
|
|
213
|
-
let
|
|
214
|
-
let lastDeploymentVersion: string | undefined
|
|
237
|
+
let lastReason = 'not checked'
|
|
215
238
|
for (;;) {
|
|
216
239
|
try {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
240
|
+
const deployed = await deployCheck({
|
|
241
|
+
url,
|
|
242
|
+
expectedOrigin: expectations.expectedOrigin,
|
|
243
|
+
expectedRevision: expectations.expectedRevision,
|
|
244
|
+
signal: AbortSignal.timeout(fetchTimeoutMs),
|
|
245
|
+
...(expectations.fetch === undefined ? {} : { fetch: expectations.fetch }),
|
|
246
|
+
log: () => undefined,
|
|
247
|
+
})
|
|
248
|
+
const actualDeploymentVersion = new URL(deployed.schema.bundle.href).searchParams.get(
|
|
249
|
+
'deployment',
|
|
250
|
+
)
|
|
251
|
+
if (
|
|
252
|
+
expectations.expectedDeploymentVersion === undefined ||
|
|
253
|
+
actualDeploymentVersion === expectations.expectedDeploymentVersion
|
|
254
|
+
) {
|
|
255
|
+
return
|
|
231
256
|
}
|
|
232
|
-
|
|
233
|
-
|
|
257
|
+
lastReason =
|
|
258
|
+
`bundle deployment=${JSON.stringify(actualDeploymentVersion)} ` +
|
|
259
|
+
`expected=${JSON.stringify(expectations.expectedDeploymentVersion)}`
|
|
260
|
+
} catch (error) {
|
|
261
|
+
lastReason = error instanceof Error ? error.message : String(error)
|
|
234
262
|
}
|
|
235
263
|
if (Date.now() >= deadline) {
|
|
236
264
|
throw new Error(
|
|
237
|
-
`deployed Worker
|
|
238
|
-
(
|
|
239
|
-
(expectedDeploymentVersion === undefined
|
|
240
|
-
? ''
|
|
241
|
-
: `; deployment version ${JSON.stringify(lastDeploymentVersion)} ` +
|
|
242
|
-
`did not reach ${JSON.stringify(expectedDeploymentVersion)}`),
|
|
265
|
+
`deployed Worker did not expose a valid Publication chain within ` +
|
|
266
|
+
`${Math.round(timeoutMs / 1000)}s; last check: ${lastReason}`,
|
|
243
267
|
)
|
|
244
268
|
}
|
|
245
269
|
if (!waited) {
|
|
246
270
|
waited = true
|
|
247
271
|
onLog('waiting for the URL to go live (first deploys can take ~30-60s)…')
|
|
248
272
|
}
|
|
249
|
-
await
|
|
273
|
+
await sleep(Math.min(retryMs, Math.max(0, deadline - Date.now())))
|
|
250
274
|
}
|
|
251
275
|
}
|
package/src/worker/index.ts
CHANGED
|
@@ -158,14 +158,6 @@ export function cloudflareWorkerEntry<
|
|
|
158
158
|
const deploymentQuery = `deployment=${encodeURIComponent(version)}`
|
|
159
159
|
const issuerId = issuer.accept(url)
|
|
160
160
|
const host = new HonoApplication()
|
|
161
|
-
host.get('/meta', (context) =>
|
|
162
|
-
context.json({
|
|
163
|
-
origin: definition.schema.origin,
|
|
164
|
-
issuer: issuerId,
|
|
165
|
-
schemaRevision: definition.domain.$.revision,
|
|
166
|
-
deploymentVersion: version,
|
|
167
|
-
}),
|
|
168
|
-
)
|
|
169
161
|
const serving: Application<Hono> = createRemoteApplication({
|
|
170
162
|
definition,
|
|
171
163
|
environment,
|
|
@@ -267,20 +259,35 @@ async function frontendAsset<S extends schema.DomainSchema>(
|
|
|
267
259
|
): Promise<Response | undefined> {
|
|
268
260
|
const frontend = definition.frontend
|
|
269
261
|
if (frontend === undefined || frontend.source.kind === 'external') return undefined
|
|
262
|
+
if (isReservedServicePath(url.pathname)) return undefined
|
|
263
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') return undefined
|
|
264
|
+
const dev = environment.FRONTEND_DEV_URL?.replace(/\/+$/u, '')
|
|
265
|
+
if (dev !== undefined && dev.length > 0) {
|
|
266
|
+
return fetch(new Request(new URL(`${url.pathname}${url.search}`, `${dev}/`), request))
|
|
267
|
+
}
|
|
270
268
|
const routes = frontend.routes as Readonly<Record<string, FrontendRoute>>
|
|
271
|
-
const
|
|
269
|
+
const spa = frontend.source.kind === 'vite' && frontend.source.spa
|
|
270
|
+
const route = Object.values(routes)
|
|
271
|
+
.filter(
|
|
272
|
+
(candidate) =>
|
|
273
|
+
candidate.path === url.pathname ||
|
|
274
|
+
(spa && (candidate.path === '/' || url.pathname.startsWith(`${candidate.path}/`))),
|
|
275
|
+
)
|
|
276
|
+
.sort((left, right) => right.path.length - left.path.length)[0]
|
|
272
277
|
const staticAsset = url.pathname.startsWith('/assets/')
|
|
273
278
|
if (route === undefined && !staticAsset) return undefined
|
|
274
|
-
if (request.method !== 'GET' && request.method !== 'HEAD') return undefined
|
|
275
279
|
const binding = environment.ASSETS
|
|
276
280
|
if (binding === undefined) return undefined
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
const asset = await binding.fetch(request)
|
|
282
|
+
if (asset.status !== 404 || route === undefined) return asset
|
|
283
|
+
await asset.body?.cancel()
|
|
284
|
+
return binding.fetch(new Request(new URL(`/${route.document}${url.search}`, url.origin), request))
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isReservedServicePath(path: string): boolean {
|
|
288
|
+
return ['/invoke', '/install', '/health', '/meta', '/.well-known'].some(
|
|
289
|
+
(reserved) => path === reserved || path.startsWith(`${reserved}/`),
|
|
290
|
+
)
|
|
284
291
|
}
|
|
285
292
|
|
|
286
293
|
function requestOrigin(url: URL, request: Request): string {
|