@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/build/frontend.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import type { CompiledFrontendArtifact, DomainDefinition, FrontendFile } from '@astrale-os/sdk'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ChildProcess } from 'node:child_process'
|
|
3
3
|
|
|
4
4
|
import { compileFrontend } from '@astrale-os/sdk'
|
|
5
|
-
import { compileReactEntrypoint } from '@astrale-os/sdk/react'
|
|
6
5
|
import { spawn } from 'node:child_process'
|
|
7
|
-
import {
|
|
6
|
+
import { existsSync } from 'node:fs'
|
|
8
7
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
8
|
+
import { createServer } from 'node:net'
|
|
9
9
|
import { dirname, join, resolve, sep } from 'node:path'
|
|
10
10
|
|
|
11
11
|
export const FRONTEND_STAGE_DIR = '.astrale/frontend'
|
|
12
|
-
const
|
|
12
|
+
const FRONTEND_BUILD_DIR = '.astrale/frontend-build'
|
|
13
13
|
let stageGeneration = 0
|
|
14
14
|
const buildQueues = new Map<string, Promise<void>>()
|
|
15
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
|
+
|
|
16
25
|
/** Materialize one Domain-owned frontend recipe into an immutable adapter artifact. */
|
|
17
26
|
export async function buildFrontend(input: {
|
|
18
27
|
readonly definition: DomainDefinition
|
|
19
28
|
readonly projectDir: string
|
|
20
29
|
readonly onLog?: (line: string) => void
|
|
30
|
+
readonly buildVite?: ViteBuild
|
|
21
31
|
}): Promise<CompiledFrontendArtifact | undefined> {
|
|
22
32
|
const key = resolve(input.projectDir)
|
|
23
33
|
const previous = buildQueues.get(key) ?? Promise.resolve()
|
|
@@ -40,6 +50,7 @@ async function buildFrontendUnlocked(input: {
|
|
|
40
50
|
readonly definition: DomainDefinition
|
|
41
51
|
readonly projectDir: string
|
|
42
52
|
readonly onLog?: (line: string) => void
|
|
53
|
+
readonly buildVite?: ViteBuild
|
|
43
54
|
}): Promise<CompiledFrontendArtifact | undefined> {
|
|
44
55
|
const artifact = input.definition.frontend
|
|
45
56
|
if (artifact === undefined) return undefined
|
|
@@ -50,44 +61,15 @@ async function buildFrontendUnlocked(input: {
|
|
|
50
61
|
provenance: { adapter: 'cloudflare', source: 'external' },
|
|
51
62
|
})
|
|
52
63
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} else if (artifact.source.kind === 'prebuilt') {
|
|
63
|
-
files = await readFiles(
|
|
64
|
-
contained(input.projectDir, artifact.source.directory, 'prebuilt frontend'),
|
|
65
|
-
)
|
|
66
|
-
} else {
|
|
67
|
-
if (isReactFrontend(artifact)) {
|
|
68
|
-
const root = contained(input.projectDir, artifact.source.root, 'React frontend root')
|
|
69
|
-
for (const file of compileReactEntrypoint(artifact)) {
|
|
70
|
-
const path = contained(root, file.path, 'generated React entrypoint')
|
|
71
|
-
await mkdir(dirname(path), { recursive: true })
|
|
72
|
-
await writeFile(path, file.content)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const output = contained(input.projectDir, artifact.source.outDir, 'Vite frontend output')
|
|
76
|
-
await rm(output, { recursive: true, force: true })
|
|
77
|
-
await runBuild(
|
|
78
|
-
artifact.source.command[0],
|
|
79
|
-
[
|
|
80
|
-
...artifact.source.command.slice(1),
|
|
81
|
-
artifact.source.root,
|
|
82
|
-
'--outDir',
|
|
83
|
-
output,
|
|
84
|
-
'--emptyOutDir',
|
|
85
|
-
],
|
|
86
|
-
input.projectDir,
|
|
87
|
-
input.onLog,
|
|
88
|
-
)
|
|
89
|
-
files = await readFiles(output)
|
|
90
|
-
}
|
|
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)
|
|
91
73
|
const compiled = await compileFrontend({
|
|
92
74
|
artifact,
|
|
93
75
|
baseHref: 'https://frontend.invalid/',
|
|
@@ -98,109 +80,53 @@ async function buildFrontendUnlocked(input: {
|
|
|
98
80
|
return compiled
|
|
99
81
|
}
|
|
100
82
|
|
|
101
|
-
export interface
|
|
83
|
+
export interface FrontendDevHandle {
|
|
84
|
+
readonly url: string
|
|
102
85
|
stop(): Promise<void>
|
|
103
86
|
}
|
|
104
87
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export type FrontendWatchSubscribe = (
|
|
110
|
-
root: string,
|
|
111
|
-
onChange: (path?: string) => void,
|
|
112
|
-
onError: (error: Error) => void,
|
|
113
|
-
) => FrontendWatchSubscription
|
|
114
|
-
|
|
115
|
-
/** Keep Wrangler's materialized frontend stage current without owning config regeneration. */
|
|
116
|
-
export function watchFrontend(input: {
|
|
88
|
+
/** Start the Domain project's own Vite server; the Worker remains the browser-facing origin. */
|
|
89
|
+
export async function startFrontendDev(input: {
|
|
117
90
|
readonly definition: DomainDefinition
|
|
118
91
|
readonly projectDir: string
|
|
119
92
|
readonly onLog?: (line: string) => void
|
|
120
|
-
|
|
121
|
-
readonly delayMs?: number
|
|
122
|
-
readonly build?: typeof buildFrontend
|
|
123
|
-
readonly subscribe?: FrontendWatchSubscribe
|
|
124
|
-
}): FrontendWatchHandle | undefined {
|
|
93
|
+
}): Promise<FrontendDevHandle | undefined> {
|
|
125
94
|
const artifact = input.definition.frontend
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
running = (async () => {
|
|
150
|
-
try {
|
|
151
|
-
await build({
|
|
152
|
-
definition: input.definition,
|
|
153
|
-
projectDir: input.projectDir,
|
|
154
|
-
onLog: input.onLog,
|
|
155
|
-
})
|
|
156
|
-
if (!stopped) input.onRebuild?.()
|
|
157
|
-
} catch (error) {
|
|
158
|
-
input.onLog?.(
|
|
159
|
-
`frontend rebuild failed; keeping the last good stage: ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
})()
|
|
163
|
-
await running
|
|
164
|
-
running = undefined
|
|
165
|
-
if (!stopped && pending) {
|
|
166
|
-
timer = setTimeout(() => {
|
|
167
|
-
timer = undefined
|
|
168
|
-
void drain()
|
|
169
|
-
}, delayMs)
|
|
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)
|
|
170
118
|
}
|
|
171
119
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
180
128
|
}
|
|
181
|
-
|
|
182
|
-
input.onLog?.(`frontend watcher failed: ${error.message}`)
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return Object.freeze({
|
|
186
|
-
async stop() {
|
|
187
|
-
if (stopped) return
|
|
188
|
-
stopped = true
|
|
189
|
-
pending = false
|
|
190
|
-
if (timer !== undefined) clearTimeout(timer)
|
|
191
|
-
timer = undefined
|
|
192
|
-
subscription.close()
|
|
193
|
-
await running
|
|
194
|
-
},
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function isReactFrontend(
|
|
199
|
-
artifact: DomainDefinition['frontend'],
|
|
200
|
-
): artifact is ReactFrontendArtifact {
|
|
201
|
-
return (
|
|
202
|
-
artifact !== undefined && (artifact as { readonly framework?: unknown }).framework === 'react'
|
|
203
|
-
)
|
|
129
|
+
return Object.freeze({ url, stop: () => stopChild(child) })
|
|
204
130
|
}
|
|
205
131
|
|
|
206
132
|
async function readFiles(root: string, current = ''): Promise<readonly FrontendFile[]> {
|
|
@@ -249,42 +175,6 @@ async function replaceStage(projectDir: string, files: readonly FrontendFile[]):
|
|
|
249
175
|
}
|
|
250
176
|
}
|
|
251
177
|
|
|
252
|
-
function subscribeToFrontend(
|
|
253
|
-
root: string,
|
|
254
|
-
onChange: (path?: string) => void,
|
|
255
|
-
onError: (error: Error) => void,
|
|
256
|
-
): FrontendWatchSubscription {
|
|
257
|
-
const watcher = watch(root, { recursive: true }, (_event, path) => {
|
|
258
|
-
onChange(path === null ? undefined : String(path))
|
|
259
|
-
})
|
|
260
|
-
watcher.on('error', onError)
|
|
261
|
-
return watcher
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function rebuildsFor(
|
|
265
|
-
source: Exclude<
|
|
266
|
-
NonNullable<DomainDefinition['frontend']>['source'],
|
|
267
|
-
{ readonly kind: 'external' | 'generated' }
|
|
268
|
-
>,
|
|
269
|
-
path: string | undefined,
|
|
270
|
-
): boolean {
|
|
271
|
-
if (path === undefined || source.kind === 'prebuilt') return true
|
|
272
|
-
const normalized = path.replaceAll('\\', '/')
|
|
273
|
-
if (
|
|
274
|
-
normalized === '.astrale' ||
|
|
275
|
-
normalized.startsWith('.astrale/') ||
|
|
276
|
-
normalized === 'node_modules' ||
|
|
277
|
-
normalized.startsWith('node_modules/') ||
|
|
278
|
-
normalized === '.git' ||
|
|
279
|
-
normalized.startsWith('.git/')
|
|
280
|
-
)
|
|
281
|
-
return false
|
|
282
|
-
const outDir = source.outDir.replace(/^\.\//u, '')
|
|
283
|
-
if (normalized === outDir || normalized.startsWith(`${outDir}/`)) return false
|
|
284
|
-
const root = source.root.replace(/^\.\//u, '')
|
|
285
|
-
return normalized !== `${root}/astrale.generated.tsx` && normalized !== `${root}/index.html`
|
|
286
|
-
}
|
|
287
|
-
|
|
288
178
|
function isMissing(error: unknown): boolean {
|
|
289
179
|
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
|
290
180
|
}
|
|
@@ -298,7 +188,34 @@ function contained(root: string, input: string, label: string): string {
|
|
|
298
188
|
return target
|
|
299
189
|
}
|
|
300
190
|
|
|
301
|
-
function
|
|
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(
|
|
302
219
|
command: string,
|
|
303
220
|
args: readonly string[],
|
|
304
221
|
cwd: string,
|
|
@@ -318,7 +235,64 @@ function runBuild(
|
|
|
318
235
|
child.on('exit', (code) =>
|
|
319
236
|
code === 0
|
|
320
237
|
? resolveBuild()
|
|
321
|
-
: reject(
|
|
238
|
+
: reject(
|
|
239
|
+
new Error(`Vite frontend build failed (code ${code ?? 1}):\n${output.slice(-1500)}`),
|
|
240
|
+
),
|
|
322
241
|
)
|
|
323
242
|
})
|
|
324
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
|
+
}
|
package/src/build/index.ts
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
export {
|
|
14
14
|
buildFrontend,
|
|
15
15
|
FRONTEND_STAGE_DIR,
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
16
|
+
startFrontendDev,
|
|
17
|
+
type FrontendDevHandle,
|
|
18
|
+
type ViteBuild,
|
|
19
|
+
type ViteBuildInput,
|
|
20
20
|
} from './frontend.js'
|
|
21
21
|
export { logTo, prepare } from './prepare.js'
|
|
22
22
|
export { buildCloudflareWorker, type CloudflareWorkerBuild } from './worker.js'
|
package/src/build/prepare.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { generateWranglerConfig } from './codegen/wrangler.js'
|
|
|
17
17
|
|
|
18
18
|
const DEFAULT_PORT = 8787
|
|
19
19
|
const DEPLOYMENT_VERSION_BINDING = 'ASTRALE_DEPLOYMENT_VERSION'
|
|
20
|
+
const FRONTEND_DEV_BINDING = 'FRONTEND_DEV_URL'
|
|
20
21
|
const WORKER_NAME_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
21
22
|
|
|
22
23
|
/** Prepare immutable generated Worker sources and Wrangler configuration. */
|
|
@@ -25,12 +26,23 @@ export async function prepare(
|
|
|
25
26
|
ctx: WatchCtx | DeployCtx | BuildCtx,
|
|
26
27
|
mode: 'build' | 'dev' | 'deploy',
|
|
27
28
|
frontend?: CompiledFrontendArtifact,
|
|
29
|
+
frontendDevUrl?: string,
|
|
28
30
|
): Promise<{ configPath: string; fallbackConfigPath?: string; port: number; workerName: string }> {
|
|
29
31
|
if (Object.hasOwn(params.vars ?? {}, DEPLOYMENT_VERSION_BINDING)) {
|
|
30
32
|
throw new Error(
|
|
31
33
|
`cloudflare adapter: \`${DEPLOYMENT_VERSION_BINDING}\` is adapter-managed and cannot be set in \`vars\`.`,
|
|
32
34
|
)
|
|
33
35
|
}
|
|
36
|
+
if (Object.hasOwn(params.vars ?? {}, FRONTEND_DEV_BINDING)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`cloudflare adapter: \`${FRONTEND_DEV_BINDING}\` is adapter-managed and cannot be set in \`vars\`.`,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
if (frontendDevUrl !== undefined && (mode !== 'dev' || !localFrontendDefinition(ctx))) {
|
|
42
|
+
throw new TypeError(
|
|
43
|
+
'cloudflare adapter: frontendDevUrl requires a local Vite frontend in dev mode.',
|
|
44
|
+
)
|
|
45
|
+
}
|
|
34
46
|
const astraleDir = join(ctx.projectDir, '.astrale')
|
|
35
47
|
await mkdir(astraleDir, { recursive: true })
|
|
36
48
|
await ensureIdentity(astraleDir, ctx.definition.schema.origin)
|
|
@@ -39,9 +51,14 @@ export async function prepare(
|
|
|
39
51
|
const router = resolveRouter(params.router)
|
|
40
52
|
const localFrontend =
|
|
41
53
|
ctx.definition.frontend !== undefined && ctx.definition.frontend.source.kind !== 'external'
|
|
42
|
-
|
|
54
|
+
const developmentFrontend = mode === 'dev' && frontendDevUrl !== undefined
|
|
55
|
+
if (
|
|
56
|
+
!developmentFrontend &&
|
|
57
|
+
localFrontend !== (frontend !== undefined && frontend.artifact.source.kind !== 'external')
|
|
58
|
+
) {
|
|
43
59
|
throw new TypeError('cloudflare adapter: materialized frontend must match definition.frontend.')
|
|
44
60
|
}
|
|
61
|
+
if (developmentFrontend) await mkdir(join(astraleDir, 'frontend'), { recursive: true })
|
|
45
62
|
|
|
46
63
|
await writeFileAtomic(
|
|
47
64
|
join(astraleDir, 'worker.gen.ts'),
|
|
@@ -67,6 +84,7 @@ export async function prepare(
|
|
|
67
84
|
: undefined
|
|
68
85
|
const vars = {
|
|
69
86
|
...(workerUrl ? { WORKER_URL: workerUrl } : {}),
|
|
87
|
+
...(frontendDevUrl === undefined ? {} : { FRONTEND_DEV_URL: frontendDevUrl }),
|
|
70
88
|
...(mode === 'dev'
|
|
71
89
|
? { ...params.vars, ...('secrets' in ctx ? ctx.secrets : {}) }
|
|
72
90
|
: { ...params.vars }),
|
|
@@ -79,6 +97,7 @@ export async function prepare(
|
|
|
79
97
|
frontend: {
|
|
80
98
|
directory: './frontend',
|
|
81
99
|
routes: localFrontendRoutes(ctx.definition),
|
|
100
|
+
dev: frontendDevUrl !== undefined,
|
|
82
101
|
},
|
|
83
102
|
}
|
|
84
103
|
: {}),
|
|
@@ -112,11 +131,20 @@ function localFrontendRoutes(
|
|
|
112
131
|
): readonly string[] {
|
|
113
132
|
const frontend = definition.frontend
|
|
114
133
|
if (frontend === undefined || frontend.source.kind === 'external') return Object.freeze([])
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
const routes = Object.values(frontend.routes as Readonly<Record<string, FrontendRoute>>)
|
|
135
|
+
.flatMap((route) =>
|
|
136
|
+
frontend.source.kind === 'vite' && frontend.source.spa && route.path !== '/'
|
|
137
|
+
? [route.path, `${route.path}/*`]
|
|
138
|
+
: route.path === '/'
|
|
139
|
+
? ['/*']
|
|
140
|
+
: [route.path],
|
|
141
|
+
)
|
|
142
|
+
.sort()
|
|
143
|
+
return Object.freeze(routes)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function localFrontendDefinition(ctx: WatchCtx | DeployCtx | BuildCtx): boolean {
|
|
147
|
+
return ctx.definition.frontend?.source.kind === 'vite'
|
|
120
148
|
}
|
|
121
149
|
|
|
122
150
|
async function writeFileAtomic(path: string, content: string): Promise<void> {
|