@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.
- package/README.md +11 -0
- package/dist/adapter.d.ts +17 -0
- package/dist/adapter.js +140 -0
- package/dist/build/artifact.d.ts +8 -0
- package/dist/build/artifact.js +179 -0
- package/dist/build/closure.d.ts +2 -0
- package/dist/build/closure.js +22 -0
- package/dist/build/codegen/execution.d.ts +13 -0
- package/dist/build/codegen/execution.js +44 -0
- package/dist/build/codegen/gateway.d.ts +1 -0
- package/dist/build/codegen/gateway.js +7 -0
- package/dist/build/codegen/merge.d.ts +0 -1
- package/dist/build/codegen/merge.js +0 -1
- package/dist/build/codegen/wrangler.d.ts +10 -1
- package/dist/build/codegen/wrangler.js +22 -16
- package/dist/build/configuration.d.ts +9 -0
- package/dist/build/configuration.js +40 -0
- package/dist/build/frontend-v1.d.ts +16 -0
- package/dist/build/frontend-v1.js +133 -0
- package/dist/build/index.d.ts +8 -16
- package/dist/build/index.js +8 -16
- package/dist/build/metafile.d.ts +5 -0
- package/dist/build/metafile.js +14 -0
- package/dist/build/parse-output.d.ts +0 -1
- package/dist/build/parse-output.js +0 -1
- package/dist/build/qualification.d.ts +5 -0
- package/dist/build/qualification.js +7 -0
- package/dist/build/roots.d.ts +2 -0
- package/dist/build/roots.js +34 -0
- package/dist/build/wrangler-cli.d.ts +53 -34
- package/dist/build/wrangler-cli.js +284 -111
- package/dist/cloudflare.d.ts +1 -34
- package/dist/cloudflare.js +1 -205
- package/dist/deploy/deploy.d.ts +17 -0
- package/dist/deploy/deploy.js +207 -0
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/index.js +1 -0
- package/dist/identity/binding.d.ts +2 -0
- package/dist/identity/binding.js +2 -0
- package/dist/identity/material.d.ts +7 -0
- package/dist/identity/material.js +42 -0
- package/dist/identity/secret.d.ts +3 -0
- package/dist/identity/secret.js +7 -0
- package/dist/identity/source.d.ts +26 -0
- package/dist/identity/source.js +98 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +3 -4
- package/dist/params.d.ts +18 -5
- package/dist/params.js +0 -1
- package/dist/release/index.d.ts +2 -0
- package/dist/release/index.js +2 -0
- package/dist/release/namespace.d.ts +2 -0
- package/dist/release/namespace.js +6 -0
- package/dist/release/target.d.ts +2 -0
- package/dist/release/target.js +9 -0
- package/dist/worker/entry.d.ts +34 -0
- package/dist/worker/entry.js +157 -0
- package/dist/worker/fetch.d.ts +11 -0
- package/dist/worker/fetch.js +31 -0
- package/dist/worker/gateway.d.ts +15 -0
- package/dist/worker/gateway.js +26 -0
- package/dist/worker/identity.d.ts +7 -0
- package/dist/worker/identity.js +23 -0
- package/dist/worker/index.d.ts +3 -48
- package/dist/worker/index.js +3 -186
- package/package.json +21 -18
- package/template/astrale.config.ts +19 -11
- package/dist/build/codegen/identity.d.ts +0 -10
- package/dist/build/codegen/identity.d.ts.map +0 -1
- package/dist/build/codegen/identity.js +0 -32
- package/dist/build/codegen/identity.js.map +0 -1
- package/dist/build/codegen/merge.d.ts.map +0 -1
- package/dist/build/codegen/merge.js.map +0 -1
- package/dist/build/codegen/worker.d.ts +0 -12
- package/dist/build/codegen/worker.d.ts.map +0 -1
- package/dist/build/codegen/worker.js +0 -40
- package/dist/build/codegen/worker.js.map +0 -1
- package/dist/build/codegen/wrangler.d.ts.map +0 -1
- package/dist/build/codegen/wrangler.js.map +0 -1
- package/dist/build/frontend.d.ts +0 -26
- package/dist/build/frontend.d.ts.map +0 -1
- package/dist/build/frontend.js +0 -263
- package/dist/build/frontend.js.map +0 -1
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js.map +0 -1
- package/dist/build/parse-output.d.ts.map +0 -1
- package/dist/build/parse-output.js.map +0 -1
- package/dist/build/prepare.d.ts +0 -18
- package/dist/build/prepare.d.ts.map +0 -1
- package/dist/build/prepare.js +0 -117
- package/dist/build/prepare.js.map +0 -1
- package/dist/build/worker.d.ts +0 -15
- package/dist/build/worker.d.ts.map +0 -1
- package/dist/build/worker.js +0 -21
- package/dist/build/worker.js.map +0 -1
- package/dist/build/wrangler-cli.d.ts.map +0 -1
- package/dist/build/wrangler-cli.js.map +0 -1
- package/dist/cloudflare.d.ts.map +0 -1
- package/dist/cloudflare.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/params.d.ts.map +0 -1
- package/dist/params.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
- package/src/build/codegen/identity.ts +0 -40
- package/src/build/codegen/merge.ts +0 -92
- package/src/build/codegen/worker.ts +0 -53
- package/src/build/codegen/wrangler.ts +0 -127
- package/src/build/frontend.ts +0 -324
- package/src/build/index.ts +0 -29
- package/src/build/parse-output.ts +0 -60
- package/src/build/prepare.ts +0 -161
- package/src/build/worker.ts +0 -37
- package/src/build/wrangler-cli.ts +0 -362
- package/src/cloudflare.ts +0 -251
- package/src/index.ts +0 -21
- package/src/params.ts +0 -92
- package/src/worker/index.ts +0 -276
- package/src/worker/tsconfig.json +0 -10
package/src/build/frontend.ts
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import type { CompiledFrontendArtifact, DomainDefinition, FrontendFile } from '@astrale-os/sdk'
|
|
2
|
-
import type { ReactFrontendArtifact } from '@astrale-os/sdk/react'
|
|
3
|
-
|
|
4
|
-
import { compileFrontend } from '@astrale-os/sdk'
|
|
5
|
-
import { compileReactEntrypoint } from '@astrale-os/sdk/react'
|
|
6
|
-
import { spawn } from 'node:child_process'
|
|
7
|
-
import { watch } from 'node:fs'
|
|
8
|
-
import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises'
|
|
9
|
-
import { dirname, join, resolve, sep } from 'node:path'
|
|
10
|
-
|
|
11
|
-
export const FRONTEND_STAGE_DIR = '.astrale/frontend'
|
|
12
|
-
const FRONTEND_REBUILD_DELAY_MS = 75
|
|
13
|
-
let stageGeneration = 0
|
|
14
|
-
const buildQueues = new Map<string, Promise<void>>()
|
|
15
|
-
|
|
16
|
-
/** Materialize one Domain-owned frontend recipe into an immutable adapter artifact. */
|
|
17
|
-
export async function buildFrontend(input: {
|
|
18
|
-
readonly definition: DomainDefinition
|
|
19
|
-
readonly projectDir: string
|
|
20
|
-
readonly onLog?: (line: string) => void
|
|
21
|
-
}): Promise<CompiledFrontendArtifact | undefined> {
|
|
22
|
-
const key = resolve(input.projectDir)
|
|
23
|
-
const previous = buildQueues.get(key) ?? Promise.resolve()
|
|
24
|
-
let release = (): void => {}
|
|
25
|
-
const current = new Promise<void>((resolveBuild) => {
|
|
26
|
-
release = resolveBuild
|
|
27
|
-
})
|
|
28
|
-
const tail = previous.then(() => current)
|
|
29
|
-
buildQueues.set(key, tail)
|
|
30
|
-
await previous
|
|
31
|
-
try {
|
|
32
|
-
return await buildFrontendUnlocked(input)
|
|
33
|
-
} finally {
|
|
34
|
-
release()
|
|
35
|
-
if (buildQueues.get(key) === tail) buildQueues.delete(key)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function buildFrontendUnlocked(input: {
|
|
40
|
-
readonly definition: DomainDefinition
|
|
41
|
-
readonly projectDir: string
|
|
42
|
-
readonly onLog?: (line: string) => void
|
|
43
|
-
}): Promise<CompiledFrontendArtifact | undefined> {
|
|
44
|
-
const artifact = input.definition.frontend
|
|
45
|
-
if (artifact === undefined) return undefined
|
|
46
|
-
if (artifact.source.kind === 'external') {
|
|
47
|
-
return compileFrontend({
|
|
48
|
-
artifact,
|
|
49
|
-
files: [],
|
|
50
|
-
provenance: { adapter: 'cloudflare', source: 'external' },
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let files: readonly FrontendFile[]
|
|
55
|
-
if (artifact.source.kind === 'generated') {
|
|
56
|
-
files = artifact.source.files.map((file) => ({
|
|
57
|
-
path: file.path,
|
|
58
|
-
content:
|
|
59
|
-
typeof file.content === 'string' ? new TextEncoder().encode(file.content) : file.content,
|
|
60
|
-
...(file.mediaType === undefined ? {} : { mediaType: file.mediaType }),
|
|
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
|
-
}
|
|
91
|
-
const compiled = await compileFrontend({
|
|
92
|
-
artifact,
|
|
93
|
-
baseHref: 'https://frontend.invalid/',
|
|
94
|
-
files,
|
|
95
|
-
provenance: { adapter: 'cloudflare', source: artifact.source.kind },
|
|
96
|
-
})
|
|
97
|
-
await replaceStage(input.projectDir, compiled.files)
|
|
98
|
-
return compiled
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface FrontendWatchHandle {
|
|
102
|
-
stop(): Promise<void>
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface FrontendWatchSubscription {
|
|
106
|
-
close(): void
|
|
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: {
|
|
117
|
-
readonly definition: DomainDefinition
|
|
118
|
-
readonly projectDir: string
|
|
119
|
-
readonly onLog?: (line: string) => void
|
|
120
|
-
readonly onRebuild?: () => void
|
|
121
|
-
readonly delayMs?: number
|
|
122
|
-
readonly build?: typeof buildFrontend
|
|
123
|
-
readonly subscribe?: FrontendWatchSubscribe
|
|
124
|
-
}): FrontendWatchHandle | undefined {
|
|
125
|
-
const artifact = input.definition.frontend
|
|
126
|
-
if (
|
|
127
|
-
artifact === undefined ||
|
|
128
|
-
artifact.source.kind === 'external' ||
|
|
129
|
-
artifact.source.kind === 'generated'
|
|
130
|
-
)
|
|
131
|
-
return undefined
|
|
132
|
-
|
|
133
|
-
const source = artifact.source
|
|
134
|
-
const root =
|
|
135
|
-
source.kind === 'prebuilt'
|
|
136
|
-
? contained(input.projectDir, source.directory, 'prebuilt frontend watch root')
|
|
137
|
-
: resolve(input.projectDir)
|
|
138
|
-
const build = input.build ?? buildFrontend
|
|
139
|
-
const subscribe = input.subscribe ?? subscribeToFrontend
|
|
140
|
-
const delayMs = input.delayMs ?? FRONTEND_REBUILD_DELAY_MS
|
|
141
|
-
let stopped = false
|
|
142
|
-
let pending = false
|
|
143
|
-
let timer: ReturnType<typeof setTimeout> | undefined
|
|
144
|
-
let running: Promise<void> | undefined
|
|
145
|
-
|
|
146
|
-
const drain = async (): Promise<void> => {
|
|
147
|
-
if (stopped || running !== undefined || !pending) return
|
|
148
|
-
pending = false
|
|
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)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const schedule = (path?: string) => {
|
|
173
|
-
if (stopped || !rebuildsFor(source, path)) return
|
|
174
|
-
pending = true
|
|
175
|
-
if (timer !== undefined) clearTimeout(timer)
|
|
176
|
-
timer = setTimeout(() => {
|
|
177
|
-
timer = undefined
|
|
178
|
-
void drain()
|
|
179
|
-
}, delayMs)
|
|
180
|
-
}
|
|
181
|
-
const subscription = subscribe(root, schedule, (error) => {
|
|
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
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
async function readFiles(root: string, current = ''): Promise<readonly FrontendFile[]> {
|
|
207
|
-
const directory = join(root, current)
|
|
208
|
-
const entries = await readdir(directory, { withFileTypes: true })
|
|
209
|
-
const files: FrontendFile[] = []
|
|
210
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
211
|
-
const path = current === '' ? entry.name : `${current}/${entry.name}`
|
|
212
|
-
if (entry.isDirectory()) files.push(...(await readFiles(root, path)))
|
|
213
|
-
else if (entry.isFile())
|
|
214
|
-
files.push({ path, content: new Uint8Array(await readFile(join(root, path))) })
|
|
215
|
-
}
|
|
216
|
-
return files
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function replaceStage(projectDir: string, files: readonly FrontendFile[]): Promise<void> {
|
|
220
|
-
const stage = contained(projectDir, FRONTEND_STAGE_DIR, 'frontend stage')
|
|
221
|
-
const generation = `${process.pid}-${++stageGeneration}`
|
|
222
|
-
const next = `${stage}.next-${generation}`
|
|
223
|
-
const previous = `${stage}.previous-${generation}`
|
|
224
|
-
await rm(next, { recursive: true, force: true })
|
|
225
|
-
await mkdir(next, { recursive: true })
|
|
226
|
-
try {
|
|
227
|
-
for (const file of files) {
|
|
228
|
-
const path = contained(next, file.path, 'staged frontend file')
|
|
229
|
-
await mkdir(dirname(path), { recursive: true })
|
|
230
|
-
await writeFile(path, file.content)
|
|
231
|
-
}
|
|
232
|
-
await rm(previous, { recursive: true, force: true })
|
|
233
|
-
let retainedPrevious = false
|
|
234
|
-
try {
|
|
235
|
-
await rename(stage, previous)
|
|
236
|
-
retainedPrevious = true
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (!isMissing(error)) throw error
|
|
239
|
-
}
|
|
240
|
-
try {
|
|
241
|
-
await rename(next, stage)
|
|
242
|
-
} catch (error) {
|
|
243
|
-
if (retainedPrevious) await rename(previous, stage)
|
|
244
|
-
throw error
|
|
245
|
-
}
|
|
246
|
-
if (retainedPrevious) await rm(previous, { recursive: true, force: true })
|
|
247
|
-
} finally {
|
|
248
|
-
await rm(next, { recursive: true, force: true })
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
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
|
-
function isMissing(error: unknown): boolean {
|
|
289
|
-
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function contained(root: string, input: string, label: string): string {
|
|
293
|
-
const base = resolve(root)
|
|
294
|
-
const target = resolve(base, input)
|
|
295
|
-
if (target !== base && !target.startsWith(`${base}${sep}`)) {
|
|
296
|
-
throw new TypeError(`${label} must remain within the Domain project root.`)
|
|
297
|
-
}
|
|
298
|
-
return target
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function runBuild(
|
|
302
|
-
command: string,
|
|
303
|
-
args: readonly string[],
|
|
304
|
-
cwd: string,
|
|
305
|
-
onLog?: (line: string) => void,
|
|
306
|
-
): Promise<void> {
|
|
307
|
-
return new Promise((resolveBuild, reject) => {
|
|
308
|
-
const child = spawn(command, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
309
|
-
let output = ''
|
|
310
|
-
const collect = (buffer: Buffer) => {
|
|
311
|
-
const value = buffer.toString()
|
|
312
|
-
output += value
|
|
313
|
-
for (const line of value.split('\n')) if (line.trim() !== '') onLog?.(line)
|
|
314
|
-
}
|
|
315
|
-
child.stdout?.on('data', collect)
|
|
316
|
-
child.stderr?.on('data', collect)
|
|
317
|
-
child.on('error', reject)
|
|
318
|
-
child.on('exit', (code) =>
|
|
319
|
-
code === 0
|
|
320
|
-
? resolveBuild()
|
|
321
|
-
: reject(new Error(`frontend build failed (code ${code ?? 1}):\n${output.slice(-1500)}`)),
|
|
322
|
-
)
|
|
323
|
-
})
|
|
324
|
-
}
|
package/src/build/index.ts
DELETED
|
@@ -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
|
-
watchFrontend,
|
|
17
|
-
type FrontendWatchHandle,
|
|
18
|
-
type FrontendWatchSubscribe,
|
|
19
|
-
type FrontendWatchSubscription,
|
|
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
|
-
}
|
package/src/build/prepare.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BuildCtx,
|
|
3
|
-
CompiledFrontendArtifact,
|
|
4
|
-
DeployCtx,
|
|
5
|
-
FrontendRoute,
|
|
6
|
-
WatchCtx,
|
|
7
|
-
} from '@astrale-os/sdk'
|
|
8
|
-
|
|
9
|
-
import { mkdir, rename, writeFile } from 'node:fs/promises'
|
|
10
|
-
import { join } from 'node:path'
|
|
11
|
-
|
|
12
|
-
import type { CloudflareParams } from '../index.js'
|
|
13
|
-
|
|
14
|
-
import { ensureIdentity } from './codegen/identity.js'
|
|
15
|
-
import { generateWorkerEntry } from './codegen/worker.js'
|
|
16
|
-
import { generateWranglerConfig } from './codegen/wrangler.js'
|
|
17
|
-
|
|
18
|
-
const DEFAULT_PORT = 8787
|
|
19
|
-
const DEPLOYMENT_VERSION_BINDING = 'ASTRALE_DEPLOYMENT_VERSION'
|
|
20
|
-
const WORKER_NAME_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
21
|
-
|
|
22
|
-
/** Prepare immutable generated Worker sources and Wrangler configuration. */
|
|
23
|
-
export async function prepare(
|
|
24
|
-
params: CloudflareParams,
|
|
25
|
-
ctx: WatchCtx | DeployCtx | BuildCtx,
|
|
26
|
-
mode: 'build' | 'dev' | 'deploy',
|
|
27
|
-
frontend?: CompiledFrontendArtifact,
|
|
28
|
-
): Promise<{ configPath: string; fallbackConfigPath?: string; port: number; workerName: string }> {
|
|
29
|
-
if (Object.hasOwn(params.vars ?? {}, DEPLOYMENT_VERSION_BINDING)) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`cloudflare adapter: \`${DEPLOYMENT_VERSION_BINDING}\` is adapter-managed and cannot be set in \`vars\`.`,
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
const astraleDir = join(ctx.projectDir, '.astrale')
|
|
35
|
-
await mkdir(astraleDir, { recursive: true })
|
|
36
|
-
await ensureIdentity(astraleDir, ctx.definition.schema.origin)
|
|
37
|
-
|
|
38
|
-
const name = workerName(params, ctx.definition.schema.origin)
|
|
39
|
-
const router = resolveRouter(params.router)
|
|
40
|
-
const localFrontend =
|
|
41
|
-
ctx.definition.frontend !== undefined && ctx.definition.frontend.source.kind !== 'external'
|
|
42
|
-
if (localFrontend !== (frontend !== undefined && frontend.artifact.source.kind !== 'external')) {
|
|
43
|
-
throw new TypeError('cloudflare adapter: materialized frontend must match definition.frontend.')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
await writeFileAtomic(
|
|
47
|
-
join(astraleDir, 'worker.gen.ts'),
|
|
48
|
-
generateWorkerEntry({
|
|
49
|
-
origin: ctx.definition.schema.origin,
|
|
50
|
-
...(router
|
|
51
|
-
? {
|
|
52
|
-
router: {
|
|
53
|
-
binding: router.binding,
|
|
54
|
-
hostSuffix: router.hostSuffix,
|
|
55
|
-
minLabels: router.minLabels,
|
|
56
|
-
},
|
|
57
|
-
}
|
|
58
|
-
: {}),
|
|
59
|
-
}),
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
const workerUrl =
|
|
63
|
-
mode === 'deploy' && params.route
|
|
64
|
-
? `https://${params.route}`
|
|
65
|
-
: mode === 'dev' && params.host
|
|
66
|
-
? params.host
|
|
67
|
-
: undefined
|
|
68
|
-
const vars = {
|
|
69
|
-
...(workerUrl ? { WORKER_URL: workerUrl } : {}),
|
|
70
|
-
...(mode === 'dev'
|
|
71
|
-
? { ...params.vars, ...('secrets' in ctx ? ctx.secrets : {}) }
|
|
72
|
-
: { ...params.vars }),
|
|
73
|
-
}
|
|
74
|
-
const baseConfig = {
|
|
75
|
-
workerName: name,
|
|
76
|
-
...(mode === 'deploy' && params.route ? { route: params.route } : {}),
|
|
77
|
-
...(localFrontend
|
|
78
|
-
? {
|
|
79
|
-
frontend: {
|
|
80
|
-
directory: './frontend',
|
|
81
|
-
routes: localFrontendRoutes(ctx.definition),
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
: {}),
|
|
85
|
-
vars,
|
|
86
|
-
...(router ? { router: { binding: router.binding, service: router.service } } : {}),
|
|
87
|
-
...(params.wrangler ? { wrangler: params.wrangler } : {}),
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const configPath = join(astraleDir, 'wrangler.gen.jsonc')
|
|
91
|
-
await writeFileAtomic(configPath, generateWranglerConfig({ ...baseConfig, selfBinding: true }))
|
|
92
|
-
|
|
93
|
-
let fallbackConfigPath: string | undefined
|
|
94
|
-
if (mode === 'deploy') {
|
|
95
|
-
fallbackConfigPath = join(astraleDir, 'wrangler.no-self.gen.jsonc')
|
|
96
|
-
await writeFileAtomic(
|
|
97
|
-
fallbackConfigPath,
|
|
98
|
-
generateWranglerConfig({ ...baseConfig, selfBinding: false }),
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
configPath,
|
|
104
|
-
...(fallbackConfigPath ? { fallbackConfigPath } : {}),
|
|
105
|
-
port: params.port ?? DEFAULT_PORT,
|
|
106
|
-
workerName: name,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function localFrontendRoutes(
|
|
111
|
-
definition: BuildCtx['definition'] | DeployCtx['definition'] | WatchCtx['definition'],
|
|
112
|
-
): readonly string[] {
|
|
113
|
-
const frontend = definition.frontend
|
|
114
|
-
if (frontend === undefined || frontend.source.kind === 'external') return Object.freeze([])
|
|
115
|
-
return Object.freeze(
|
|
116
|
-
Object.values(frontend.routes as Readonly<Record<string, FrontendRoute>>)
|
|
117
|
-
.flatMap((route) => (route.path === undefined ? [] : [route.path]))
|
|
118
|
-
.sort(),
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function writeFileAtomic(path: string, content: string): Promise<void> {
|
|
123
|
-
const tmp = `${path}.tmp`
|
|
124
|
-
await writeFile(tmp, content)
|
|
125
|
-
await rename(tmp, path)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function workerName(params: CloudflareParams, origin: string): string {
|
|
129
|
-
if (params.workerName !== undefined) {
|
|
130
|
-
if (!WORKER_NAME_RE.test(params.workerName) || params.workerName.length > 63) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`cloudflare adapter: invalid \`workerName\` "${params.workerName}". A Cloudflare worker ` +
|
|
133
|
-
'name must be lowercase, start with a letter or digit, contain only letters, digits and ' +
|
|
134
|
-
'dashes, and be at most 63 chars.',
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
return params.workerName
|
|
138
|
-
}
|
|
139
|
-
return origin
|
|
140
|
-
.toLowerCase()
|
|
141
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
142
|
-
.slice(0, 54)
|
|
143
|
-
.replace(/^-+|-+$/g, '')
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function resolveRouter(
|
|
147
|
-
ir: CloudflareParams['router'],
|
|
148
|
-
): { binding: string; service: string; hostSuffix: string; minLabels: number } | undefined {
|
|
149
|
-
if (ir === false) return undefined
|
|
150
|
-
const cfg = ir ?? {}
|
|
151
|
-
return {
|
|
152
|
-
binding: cfg.binding ?? 'ROUTER',
|
|
153
|
-
service: cfg.service ?? 'admin-router',
|
|
154
|
-
hostSuffix: cfg.hostSuffix ?? '.astrale.ai',
|
|
155
|
-
minLabels: cfg.minLabels ?? 4,
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function logTo(): (line: string) => void {
|
|
160
|
-
return (line: string) => process.stderr.write(`\x1b[2m ${line}\x1b[0m\n`)
|
|
161
|
-
}
|
package/src/build/worker.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { BuildCtx, CompiledFrontendArtifact } from '@astrale-os/sdk'
|
|
2
|
-
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
|
|
5
|
-
import type { CloudflareParams } from '../params.js'
|
|
6
|
-
|
|
7
|
-
import { buildFrontend } from './frontend.js'
|
|
8
|
-
import { prepare } from './prepare.js'
|
|
9
|
-
import { runWranglerBundle } from './wrangler-cli.js'
|
|
10
|
-
|
|
11
|
-
export interface CloudflareWorkerBuild {
|
|
12
|
-
readonly frontend: CompiledFrontendArtifact | undefined
|
|
13
|
-
readonly configPath: string
|
|
14
|
-
readonly bundlePath: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** One shared frontend/config/Worker qualification lifecycle for Cloudflare-based adapters. */
|
|
18
|
-
export async function buildCloudflareWorker(input: {
|
|
19
|
-
readonly params: CloudflareParams
|
|
20
|
-
readonly context: BuildCtx
|
|
21
|
-
readonly outDir?: string
|
|
22
|
-
readonly onLog?: (line: string) => void
|
|
23
|
-
}): Promise<CloudflareWorkerBuild> {
|
|
24
|
-
const frontend = await buildFrontend({
|
|
25
|
-
definition: input.context.definition,
|
|
26
|
-
projectDir: input.context.projectDir,
|
|
27
|
-
onLog: input.onLog,
|
|
28
|
-
})
|
|
29
|
-
const { configPath } = await prepare(input.params, input.context, 'build', frontend)
|
|
30
|
-
const bundlePath = await runWranglerBundle({
|
|
31
|
-
projectDir: input.context.projectDir,
|
|
32
|
-
configPath,
|
|
33
|
-
outDir: input.outDir ?? join(input.context.projectDir, '.astrale', 'dist-build'),
|
|
34
|
-
onLog: input.onLog,
|
|
35
|
-
})
|
|
36
|
-
return Object.freeze({ frontend, configPath, bundlePath })
|
|
37
|
-
}
|