@astrale-os/adapter-cloudflare 0.5.0-beta.7 → 0.5.0-beta.71

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 (174) hide show
  1. package/CHANGELOG.md +887 -0
  2. package/README.md +25 -0
  3. package/dist/adapter.d.ts +17 -0
  4. package/dist/adapter.js +177 -0
  5. package/dist/build/artifact.d.ts +7 -0
  6. package/dist/build/artifact.js +163 -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 +13 -1
  16. package/dist/build/codegen/wrangler.js +32 -17
  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 +93 -0
  31. package/dist/build/frontend/routes.d.ts +2 -0
  32. package/dist/build/frontend/routes.js +11 -0
  33. package/dist/build/frontend-v1.d.ts +16 -0
  34. package/dist/build/frontend-v1.js +133 -0
  35. package/dist/build/index.d.ts +10 -16
  36. package/dist/build/index.js +9 -16
  37. package/dist/build/materialize.d.ts +4 -0
  38. package/dist/build/materialize.js +10 -0
  39. package/dist/build/metafile.d.ts +5 -0
  40. package/dist/build/metafile.js +14 -0
  41. package/dist/build/parse-output.d.ts +0 -1
  42. package/dist/build/parse-output.js +0 -1
  43. package/dist/build/qualification.d.ts +5 -0
  44. package/dist/build/qualification.js +7 -0
  45. package/dist/build/roots.d.ts +2 -0
  46. package/dist/build/roots.js +34 -0
  47. package/dist/build/worker/artifact.d.ts +40 -0
  48. package/dist/build/worker/artifact.js +1 -0
  49. package/dist/build/worker/artifact.load.d.ts +3 -0
  50. package/dist/build/worker/artifact.load.js +308 -0
  51. package/dist/build/worker/artifact.manifest.d.ts +48 -0
  52. package/dist/build/worker/artifact.manifest.js +87 -0
  53. package/dist/build/worker/artifact.paths.d.ts +4 -0
  54. package/dist/build/worker/artifact.paths.js +25 -0
  55. package/dist/build/worker/codegen/entry.d.ts +2 -0
  56. package/dist/build/worker/codegen/entry.js +28 -0
  57. package/dist/build/worker/codegen/service.d.ts +16 -0
  58. package/dist/build/worker/codegen/service.js +52 -0
  59. package/dist/build/worker/execution.prepare.d.ts +18 -0
  60. package/dist/build/worker/execution.prepare.js +109 -0
  61. package/dist/build/worker/index.d.ts +2 -0
  62. package/dist/build/worker/index.js +1 -0
  63. package/dist/build/worker/modules.d.ts +11 -0
  64. package/dist/build/worker/modules.js +26 -0
  65. package/dist/build/worker/runtime.d.ts +10 -0
  66. package/dist/build/worker/runtime.js +35 -0
  67. package/dist/build/worker/specifier.d.ts +1 -0
  68. package/dist/build/worker/specifier.js +5 -0
  69. package/dist/build/wrangler/bundle.d.ts +10 -0
  70. package/dist/build/wrangler/bundle.js +29 -0
  71. package/dist/build/wrangler/deploy.d.ts +24 -0
  72. package/dist/build/wrangler/deploy.js +44 -0
  73. package/dist/build/wrangler/development.d.ts +17 -0
  74. package/dist/build/wrangler/development.js +136 -0
  75. package/dist/build/wrangler/process.d.ts +17 -0
  76. package/dist/build/wrangler/process.js +115 -0
  77. package/dist/build/wrangler/secrets.d.ts +6 -0
  78. package/dist/build/wrangler/secrets.js +58 -0
  79. package/dist/build/wrangler/status.d.ts +14 -0
  80. package/dist/build/wrangler/status.js +56 -0
  81. package/dist/build/wrangler/type-generation.d.ts +6 -0
  82. package/dist/build/wrangler/type-generation.js +19 -0
  83. package/dist/build/wrangler-cli.d.ts +6 -86
  84. package/dist/build/wrangler-cli.js +6 -275
  85. package/dist/cloudflare.d.ts +1 -34
  86. package/dist/cloudflare.js +1 -205
  87. package/dist/deploy/deploy.d.ts +15 -0
  88. package/dist/deploy/deploy.js +204 -0
  89. package/dist/deploy/index.d.ts +1 -0
  90. package/dist/deploy/index.js +1 -0
  91. package/dist/identity/binding.d.ts +2 -0
  92. package/dist/identity/binding.js +2 -0
  93. package/dist/identity/material.d.ts +7 -0
  94. package/dist/identity/material.js +42 -0
  95. package/dist/identity/secret.d.ts +3 -0
  96. package/dist/identity/secret.js +7 -0
  97. package/dist/identity/source.d.ts +26 -0
  98. package/dist/identity/source.js +98 -0
  99. package/dist/index.d.ts +3 -4
  100. package/dist/index.js +3 -4
  101. package/dist/params.d.ts +30 -5
  102. package/dist/params.js +0 -1
  103. package/dist/release/deployment.d.ts +44 -0
  104. package/dist/release/deployment.js +141 -0
  105. package/dist/release/index.d.ts +2 -0
  106. package/dist/release/index.js +2 -0
  107. package/dist/release/staged-worker-name.d.ts +3 -0
  108. package/dist/release/staged-worker-name.js +10 -0
  109. package/dist/worker/entry.d.ts +43 -0
  110. package/dist/worker/entry.js +181 -0
  111. package/dist/worker/fetch.d.ts +0 -1
  112. package/dist/worker/fetch.js +0 -1
  113. package/dist/worker/identity.d.ts +7 -0
  114. package/dist/worker/identity.js +23 -0
  115. package/dist/worker/index.d.ts +2 -61
  116. package/dist/worker/index.js +2 -216
  117. package/package.json +30 -19
  118. package/template/astrale.config.ts +19 -11
  119. package/dist/build/codegen/identity.d.ts +0 -10
  120. package/dist/build/codegen/identity.d.ts.map +0 -1
  121. package/dist/build/codegen/identity.js +0 -32
  122. package/dist/build/codegen/identity.js.map +0 -1
  123. package/dist/build/codegen/merge.d.ts.map +0 -1
  124. package/dist/build/codegen/merge.js.map +0 -1
  125. package/dist/build/codegen/worker.d.ts +0 -12
  126. package/dist/build/codegen/worker.d.ts.map +0 -1
  127. package/dist/build/codegen/worker.js +0 -40
  128. package/dist/build/codegen/worker.js.map +0 -1
  129. package/dist/build/codegen/wrangler.d.ts.map +0 -1
  130. package/dist/build/codegen/wrangler.js.map +0 -1
  131. package/dist/build/frontend.d.ts +0 -26
  132. package/dist/build/frontend.d.ts.map +0 -1
  133. package/dist/build/frontend.js +0 -263
  134. package/dist/build/frontend.js.map +0 -1
  135. package/dist/build/index.d.ts.map +0 -1
  136. package/dist/build/index.js.map +0 -1
  137. package/dist/build/parse-output.d.ts.map +0 -1
  138. package/dist/build/parse-output.js.map +0 -1
  139. package/dist/build/prepare.d.ts +0 -18
  140. package/dist/build/prepare.d.ts.map +0 -1
  141. package/dist/build/prepare.js +0 -117
  142. package/dist/build/prepare.js.map +0 -1
  143. package/dist/build/worker.d.ts +0 -15
  144. package/dist/build/worker.d.ts.map +0 -1
  145. package/dist/build/worker.js +0 -21
  146. package/dist/build/worker.js.map +0 -1
  147. package/dist/build/wrangler-cli.d.ts.map +0 -1
  148. package/dist/build/wrangler-cli.js.map +0 -1
  149. package/dist/cloudflare.d.ts.map +0 -1
  150. package/dist/cloudflare.js.map +0 -1
  151. package/dist/index.d.ts.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/params.d.ts.map +0 -1
  154. package/dist/params.js.map +0 -1
  155. package/dist/worker/fetch.d.ts.map +0 -1
  156. package/dist/worker/fetch.js.map +0 -1
  157. package/dist/worker/index.d.ts.map +0 -1
  158. package/dist/worker/index.js.map +0 -1
  159. package/src/build/codegen/identity.ts +0 -40
  160. package/src/build/codegen/merge.ts +0 -92
  161. package/src/build/codegen/worker.ts +0 -53
  162. package/src/build/codegen/wrangler.ts +0 -127
  163. package/src/build/frontend.ts +0 -324
  164. package/src/build/index.ts +0 -29
  165. package/src/build/parse-output.ts +0 -60
  166. package/src/build/prepare.ts +0 -161
  167. package/src/build/worker.ts +0 -37
  168. package/src/build/wrangler-cli.ts +0 -362
  169. package/src/cloudflare.ts +0 -251
  170. package/src/index.ts +0 -21
  171. package/src/params.ts +0 -92
  172. package/src/worker/fetch.ts +0 -46
  173. package/src/worker/index.ts +0 -329
  174. package/src/worker/tsconfig.json +0 -10
@@ -0,0 +1,109 @@
1
+ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative, resolve } from 'node:path';
3
+ import { SIGNING_IDENTITY_BINDING } from '../../identity/binding.js';
4
+ import { admitWorkerBundle } from '../bundle/admit.js';
5
+ import { generateExecutionEntry, generateExecutionService } from '../codegen/execution.js';
6
+ import { generateWranglerConfig } from '../codegen/wrangler.js';
7
+ import { workerInvocationTimeout, workerRequestLimit } from '../configuration.js';
8
+ import { qualifyWorkerBundle } from '../qualification.js';
9
+ import { runWranglerBundle } from '../wrangler-cli.js';
10
+ import { workerModuleSpecifier } from './specifier.js';
11
+ export const RELEASE_ENTRY = 'bundle/index.mjs';
12
+ export const RELEASE_SERVICE = 'bundle/service.mjs';
13
+ export async function prepareExecution(input) {
14
+ const runtimePath = resolve(input.context.projectDir, input.context.runtime.reference.path);
15
+ const runtimeSpecifier = moduleSpecifier(relative(input.directory, runtimePath));
16
+ const workerSpecifier = workerModuleSpecifier();
17
+ const maximumRequestBytes = workerRequestLimit(input.params.maximumRequestBytes);
18
+ const invocationTimeoutMs = workerInvocationTimeout(input.params.invocationTimeoutMs);
19
+ const serviceSource = generateExecutionService({
20
+ origin: input.context.build.schema.compiled.root.origin,
21
+ runtimeSpecifier,
22
+ workerSpecifier,
23
+ build: input.context.build,
24
+ ...(maximumRequestBytes === undefined ? {} : { maximumRequestBytes }),
25
+ ...(invocationTimeoutMs === undefined ? {} : { invocationTimeoutMs }),
26
+ ...(input.configuredIssuer === undefined && input.direct
27
+ ? { providerAssignedWorker: input.name }
28
+ : {}),
29
+ ...(input.router === undefined
30
+ ? {}
31
+ : {
32
+ router: {
33
+ binding: input.router.binding,
34
+ hostSuffix: input.router.hostSuffix,
35
+ minLabels: input.router.minLabels,
36
+ },
37
+ }),
38
+ });
39
+ const serviceSourcePath = join(input.directory, 'service.gen.ts');
40
+ await writeFile(serviceSourcePath, serviceSource);
41
+ const serviceConfigPath = join(input.directory, 'wrangler.service.prepare.jsonc');
42
+ await writeFile(serviceConfigPath, generateWranglerConfig({
43
+ workerName: input.name,
44
+ main: './service.gen.ts',
45
+ vars: runtimeVars(input.params, input.configuredIssuer),
46
+ secretBindings: [SIGNING_IDENTITY_BINDING],
47
+ ...(input.router === undefined
48
+ ? {}
49
+ : { router: { binding: input.router.binding, service: input.router.service } }),
50
+ ...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
51
+ selfBinding: false,
52
+ }));
53
+ const serviceOutput = join(input.directory, 'dist-service');
54
+ await rm(serviceOutput, { recursive: true, force: true });
55
+ const emittedService = await runWranglerBundle({
56
+ projectDir: input.context.projectDir,
57
+ configPath: serviceConfigPath,
58
+ outDir: serviceOutput,
59
+ });
60
+ await qualifyWorkerBundle({
61
+ metafilePath: emittedService.metafilePath,
62
+ inputRoot: dirname(serviceConfigPath),
63
+ });
64
+ await admitWorkerBundle(emittedService.bundlePath);
65
+ const service = new Uint8Array(await readFile(emittedService.bundlePath));
66
+ await mkdir(dirname(join(input.directory, RELEASE_SERVICE)), { recursive: true });
67
+ await writeFile(join(input.directory, RELEASE_SERVICE), service);
68
+ const sourcePath = join(input.directory, 'worker.gen.ts');
69
+ await writeFile(sourcePath, generateExecutionEntry('./service.mjs', workerSpecifier));
70
+ const configPath = join(input.directory, 'wrangler.prepare.jsonc');
71
+ await writeFile(configPath, generateWranglerConfig({
72
+ workerName: input.name,
73
+ ...(input.params.route === undefined ? {} : { route: input.params.route }),
74
+ vars: runtimeVars(input.params, input.configuredIssuer),
75
+ secretBindings: [SIGNING_IDENTITY_BINDING],
76
+ modules: { baseDir: '.', rules: [{ type: 'ESModule', globs: [RELEASE_SERVICE] }] },
77
+ ...(input.router === undefined
78
+ ? {}
79
+ : { router: { binding: input.router.binding, service: input.router.service } }),
80
+ ...(input.params.wrangler === undefined ? {} : { wrangler: input.params.wrangler }),
81
+ selfBinding: false,
82
+ }));
83
+ const output = join(input.directory, 'dist-prepare');
84
+ await rm(output, { recursive: true, force: true });
85
+ const emitted = await runWranglerBundle({
86
+ projectDir: input.context.projectDir,
87
+ configPath,
88
+ outDir: output,
89
+ });
90
+ await qualifyWorkerBundle({
91
+ metafilePath: emitted.metafilePath,
92
+ inputRoot: dirname(configPath),
93
+ });
94
+ await admitWorkerBundle(emitted.bundlePath);
95
+ return Object.freeze({
96
+ entry: new Uint8Array(await readFile(emitted.bundlePath)),
97
+ service,
98
+ });
99
+ }
100
+ export function runtimeVars(params, configuredIssuer) {
101
+ return {
102
+ ...params.vars,
103
+ ...(configuredIssuer === undefined ? {} : { WORKER_URL: configuredIssuer }),
104
+ };
105
+ }
106
+ function moduleSpecifier(input) {
107
+ const normalized = input.split('\\').join('/');
108
+ return normalized.startsWith('.') ? normalized : `./${normalized}`;
109
+ }
@@ -0,0 +1,2 @@
1
+ export { loadWorkerArtifact } from './artifact.load.js';
2
+ export type { WorkerArtifact } from './artifact.js';
@@ -0,0 +1 @@
1
+ export { loadWorkerArtifact } from './artifact.load.js';
@@ -0,0 +1,11 @@
1
+ import type { WorkerArtifact } from './artifact.js';
2
+ type WranglerModuleType = 'ESModule' | 'CompiledWasm' | 'Text' | 'Data';
3
+ /** Project one admitted Worker module inventory into Wrangler's relative module discovery. */
4
+ export declare function workerModules(worker: WorkerArtifact['release']): {
5
+ readonly baseDir: string;
6
+ readonly rules: readonly {
7
+ readonly type: WranglerModuleType;
8
+ readonly globs: readonly string[];
9
+ }[];
10
+ };
11
+ export {};
@@ -0,0 +1,26 @@
1
+ import { posix } from 'node:path';
2
+ const TYPES = Object.freeze({
3
+ esm: 'ESModule',
4
+ wasm: 'CompiledWasm',
5
+ text: 'Text',
6
+ data: 'Data',
7
+ });
8
+ /** Project one admitted Worker module inventory into Wrangler's relative module discovery. */
9
+ export function workerModules(worker) {
10
+ const directory = posix.dirname(worker.entrypoint);
11
+ const groups = new Map();
12
+ for (const module of worker.modules) {
13
+ if (module.path === worker.entrypoint)
14
+ continue;
15
+ const relative = posix.relative(directory, module.path);
16
+ if (relative === '' || relative === '..' || relative.startsWith('../')) {
17
+ throw new TypeError('Cloudflare Worker module escapes its entrypoint directory.');
18
+ }
19
+ const type = TYPES[module.kind];
20
+ const globs = groups.get(type) ?? [];
21
+ globs.push(relative);
22
+ groups.set(type, globs);
23
+ }
24
+ const rules = [...groups].map(([type, globs]) => Object.freeze({ type, globs: Object.freeze(globs) }));
25
+ return Object.freeze({ baseDir: `./${directory}`, rules: Object.freeze(rules) });
26
+ }
@@ -0,0 +1,10 @@
1
+ export interface WorkerRuntimeConfiguration {
2
+ readonly compatibilityDate: string;
3
+ readonly compatibilityFlags: readonly string[];
4
+ readonly vars: Readonly<Record<string, string>>;
5
+ readonly services: readonly {
6
+ readonly name: string;
7
+ readonly service: string;
8
+ }[];
9
+ }
10
+ export declare function admitWorkerRuntimeConfiguration(input: string): WorkerRuntimeConfiguration;
@@ -0,0 +1,35 @@
1
+ export function admitWorkerRuntimeConfiguration(input) {
2
+ const value = JSON.parse(input);
3
+ const compatibilityDate = value.compatibility_date;
4
+ const compatibilityFlags = value.compatibility_flags;
5
+ const vars = value.vars ?? {};
6
+ const services = value.services ?? [];
7
+ if (typeof compatibilityDate !== 'string' ||
8
+ !Array.isArray(compatibilityFlags) ||
9
+ compatibilityFlags.some((flag) => typeof flag !== 'string') ||
10
+ vars === null ||
11
+ typeof vars !== 'object' ||
12
+ Array.isArray(vars) ||
13
+ Object.values(vars).some((item) => typeof item !== 'string') ||
14
+ !Array.isArray(services)) {
15
+ invalidRuntime();
16
+ }
17
+ const admittedServices = services.map((input) => {
18
+ if (input === null || typeof input !== 'object' || Array.isArray(input))
19
+ invalidRuntime();
20
+ const binding = Reflect.get(input, 'binding');
21
+ const service = Reflect.get(input, 'service');
22
+ if (typeof binding !== 'string' || typeof service !== 'string')
23
+ invalidRuntime();
24
+ return Object.freeze({ name: binding, service });
25
+ });
26
+ return Object.freeze({
27
+ compatibilityDate,
28
+ compatibilityFlags: Object.freeze([...compatibilityFlags]),
29
+ vars: Object.freeze({ ...vars }),
30
+ services: Object.freeze(admittedServices),
31
+ });
32
+ }
33
+ function invalidRuntime() {
34
+ throw new TypeError('Generated Cloudflare runtime configuration is invalid.');
35
+ }
@@ -0,0 +1 @@
1
+ export declare function workerModuleSpecifier(): string;
@@ -0,0 +1,5 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ const workerModule = import.meta.resolve('@astrale-os/adapter-cloudflare/worker');
3
+ export function workerModuleSpecifier() {
4
+ return fileURLToPath(workerModule);
5
+ }
@@ -0,0 +1,10 @@
1
+ export declare function runWranglerBundle(args: {
2
+ projectDir: string;
3
+ configPath: string;
4
+ outDir: string;
5
+ onLog?: (line: string) => void;
6
+ signal?: AbortSignal;
7
+ }): Promise<{
8
+ readonly bundlePath: string;
9
+ readonly metafilePath: string;
10
+ }>;
@@ -0,0 +1,29 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { runCapture, wranglerCommand } from './process.js';
4
+ import { runWranglerTypes } from './type-generation.js';
5
+ export async function runWranglerBundle(args) {
6
+ await runWranglerTypes(args);
7
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
8
+ const { code, out } = await runCapture(cmd, [
9
+ ...prefix,
10
+ 'deploy',
11
+ '--dry-run',
12
+ '--outdir',
13
+ args.outDir,
14
+ '--metafile',
15
+ '--config',
16
+ args.configPath,
17
+ ], args.projectDir, args.onLog, args.signal);
18
+ if (code !== 0)
19
+ throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`);
20
+ const javascript = readdirSync(args.outDir).filter((file) => file.endsWith('.js'));
21
+ if (javascript.length === 0) {
22
+ throw new Error(`wrangler bundle produced no .js in ${args.outDir}`);
23
+ }
24
+ const preferred = javascript.find((file) => file.startsWith('worker')) ?? javascript[0];
25
+ return Object.freeze({
26
+ bundlePath: join(args.outDir, preferred),
27
+ metafilePath: join(args.outDir, 'bundle-meta.json'),
28
+ });
29
+ }
@@ -0,0 +1,24 @@
1
+ export interface DeployArgs {
2
+ configPath: string;
3
+ secrets?: Readonly<Record<string, string>>;
4
+ route?: string;
5
+ onLog?: (line: string) => void;
6
+ signal?: AbortSignal;
7
+ }
8
+ export declare class WranglerDeployError extends Error {
9
+ readonly applied?: {
10
+ readonly url?: string;
11
+ readonly deploymentVersion?: string;
12
+ };
13
+ constructor(message: string, applied?: {
14
+ readonly url?: string;
15
+ readonly deploymentVersion?: string;
16
+ });
17
+ }
18
+ export declare function runWranglerDeploy(args: DeployArgs & {
19
+ projectDir: string;
20
+ }): Promise<{
21
+ url?: string;
22
+ raw: string;
23
+ deploymentVersion: string;
24
+ }>;
@@ -0,0 +1,44 @@
1
+ import { parseDeployUrl, parseDeployVersionId } from '../parse-output.js';
2
+ import { runCapture, wranglerCommand } from './process.js';
3
+ import { secretRedactor, withSecretsFile } from './secrets.js';
4
+ import { runWranglerTypes } from './type-generation.js';
5
+ export class WranglerDeployError extends Error {
6
+ applied;
7
+ constructor(message, applied) {
8
+ super(message);
9
+ this.name = 'WranglerDeployError';
10
+ if (applied !== undefined)
11
+ this.applied = Object.freeze({ ...applied });
12
+ }
13
+ }
14
+ export async function runWranglerDeploy(args) {
15
+ await runWranglerTypes(args);
16
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
17
+ const secrets = args.secrets ?? {};
18
+ const execute = (secretsFile) => runCapture(cmd, [
19
+ ...prefix,
20
+ 'deploy',
21
+ '--config',
22
+ args.configPath,
23
+ ...(secretsFile === undefined ? [] : ['--secrets-file', secretsFile]),
24
+ ], args.projectDir, args.onLog, args.signal, secretRedactor(secrets));
25
+ const { code, out } = await withSecretsFile(secrets, execute);
26
+ const url = parseDeployUrl(out, args.route);
27
+ const deploymentVersion = parseDeployVersionId(out);
28
+ if (code !== 0) {
29
+ throw new WranglerDeployError(`wrangler deploy failed (code ${code}):\n${out.slice(-2000)}`, url === undefined && deploymentVersion === undefined
30
+ ? undefined
31
+ : {
32
+ ...(url === undefined ? {} : { url }),
33
+ ...(deploymentVersion === undefined ? {} : { deploymentVersion }),
34
+ });
35
+ }
36
+ if (deploymentVersion === undefined) {
37
+ throw new WranglerDeployError('wrangler deploy did not report its Version ID.', url === undefined ? undefined : { url });
38
+ }
39
+ return Object.freeze({
40
+ raw: out,
41
+ ...(url === undefined ? {} : { url }),
42
+ deploymentVersion,
43
+ });
44
+ }
@@ -0,0 +1,17 @@
1
+ export interface DevHandle {
2
+ url: string;
3
+ stop(): Promise<void>;
4
+ }
5
+ export declare function runWranglerDev(args: {
6
+ projectDir: string;
7
+ configPath: string;
8
+ port: number;
9
+ remote: boolean;
10
+ readonly ip?: string;
11
+ readonly autoPickPort?: boolean;
12
+ /** Pin ordinary local requests to one stable issuer after the final port is selected. */
13
+ readonly pinLocalOrigin?: boolean;
14
+ readonly secrets: Readonly<Record<string, string>>;
15
+ readonly onReload: () => void;
16
+ readonly onLog?: (line: string) => void;
17
+ }): Promise<DevHandle>;
@@ -0,0 +1,136 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createServer } from 'node:net';
3
+ import { parseDevReadyUrl } from '../parse-output.js';
4
+ import { stopChild, wranglerCommand, wranglerProcessEnv } from './process.js';
5
+ import { createSecretEnvironment, secretRedactor } from './secrets.js';
6
+ import { runWranglerTypes } from './type-generation.js';
7
+ export async function runWranglerDev(args) {
8
+ await runWranglerTypes(args);
9
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
10
+ const bindHost = args.ip ?? '127.0.0.1';
11
+ if (args.autoPickPort && !(await portFree(args.port, bindHost))) {
12
+ const picked = await ephemeralPort(bindHost);
13
+ args.onLog?.(`port ${args.port} is taken — using ${picked} instead (pass --port to pin one)`);
14
+ args = { ...args, port: picked };
15
+ }
16
+ const secretEnvironment = await createSecretEnvironment(args.secrets);
17
+ const redact = secretRedactor(args.secrets);
18
+ const fallbackUrl = `http://localhost:${args.port}`;
19
+ const wranglerArgs = [
20
+ ...prefix,
21
+ 'dev',
22
+ '--config',
23
+ args.configPath,
24
+ '--port',
25
+ String(args.port),
26
+ '--local-protocol',
27
+ 'http',
28
+ '--ip',
29
+ args.ip ?? '127.0.0.1',
30
+ ...(args.pinLocalOrigin ? ['--var', `WORKER_URL:${fallbackUrl}`] : []),
31
+ ...(secretEnvironment === undefined ? [] : ['--env-file', secretEnvironment.path]),
32
+ ...(args.remote ? ['--remote'] : []),
33
+ ];
34
+ let child;
35
+ try {
36
+ child = spawn(cmd, wranglerArgs, {
37
+ cwd: args.projectDir,
38
+ stdio: ['ignore', 'pipe', 'pipe'],
39
+ env: wranglerProcessEnv(args.projectDir),
40
+ });
41
+ }
42
+ catch (cause) {
43
+ await secretEnvironment?.remove();
44
+ throw cause;
45
+ }
46
+ let resolved = false;
47
+ let buildErrorTail = '';
48
+ let outputTail = '';
49
+ let url;
50
+ try {
51
+ url = await new Promise((resolve, reject) => {
52
+ const onData = (buf) => {
53
+ const text = redact(buf.toString());
54
+ outputTail = (outputTail + text).slice(-3000);
55
+ for (const line of text.split('\n')) {
56
+ if (line.trim())
57
+ args.onLog?.(line);
58
+ }
59
+ if (/✘ \[ERROR\]|Build failed|Could not resolve "/.test(text)) {
60
+ buildErrorTail = (buildErrorTail + text).slice(-2000);
61
+ }
62
+ if (!resolved) {
63
+ const ready = parseDevReadyUrl(text, args.port);
64
+ if (ready) {
65
+ resolved = true;
66
+ resolve(ready);
67
+ }
68
+ }
69
+ else if (/Reloading|Detected changes|Updated/i.test(text)) {
70
+ args.onReload();
71
+ }
72
+ };
73
+ child.stdout?.on('data', onData);
74
+ child.stderr?.on('data', onData);
75
+ child.on('exit', (code) => {
76
+ if (!resolved) {
77
+ resolved = true;
78
+ reject(new Error(`wrangler dev exited before becoming ready (code ${code ?? 'null'}).\n${outputTail}`));
79
+ }
80
+ });
81
+ child.on('error', (error) => {
82
+ if (!resolved) {
83
+ resolved = true;
84
+ reject(error);
85
+ }
86
+ });
87
+ setTimeout(() => {
88
+ if (!resolved && child.exitCode === null) {
89
+ resolved = true;
90
+ if (buildErrorTail) {
91
+ void stopChild(child);
92
+ reject(new Error(`worker build failed — the dev server would hang silently.\n${buildErrorTail}`));
93
+ }
94
+ else {
95
+ resolve(fallbackUrl);
96
+ }
97
+ }
98
+ }, 15_000).unref?.();
99
+ });
100
+ }
101
+ catch (cause) {
102
+ await stopChild(child);
103
+ await secretEnvironment?.remove();
104
+ throw cause;
105
+ }
106
+ return {
107
+ url: args.pinLocalOrigin ? fallbackUrl : url,
108
+ async stop() {
109
+ try {
110
+ await stopChild(child);
111
+ }
112
+ finally {
113
+ await secretEnvironment?.remove();
114
+ }
115
+ },
116
+ };
117
+ }
118
+ function portFree(port, host) {
119
+ return new Promise((resolve) => {
120
+ const server = createServer();
121
+ server.once('error', () => resolve(false));
122
+ server.once('listening', () => server.close(() => resolve(true)));
123
+ server.listen(port, host);
124
+ });
125
+ }
126
+ function ephemeralPort(host) {
127
+ return new Promise((resolve, reject) => {
128
+ const server = createServer();
129
+ server.once('error', reject);
130
+ server.listen(0, host, () => {
131
+ const address = server.address();
132
+ const port = typeof address === 'object' && address ? address.port : 0;
133
+ server.close(() => resolve(port));
134
+ });
135
+ });
136
+ }
@@ -0,0 +1,17 @@
1
+ import type { ChildProcess } from 'node:child_process';
2
+ export declare function wranglerCommand(projectDir: string, runtime?: {
3
+ readonly executable: string;
4
+ readonly bun: boolean;
5
+ }): {
6
+ cmd: string;
7
+ prefix: string[];
8
+ };
9
+ /** Keep Wrangler CLI diagnostic logs inside the generated project state. */
10
+ export declare function wranglerProcessEnv(projectDir: string): NodeJS.ProcessEnv;
11
+ export declare function runCapture(cmd: string, args: string[], cwd: string, onLog?: (line: string) => void, signal?: AbortSignal, redact?: (input: string) => string): Promise<{
12
+ code: number;
13
+ out: string;
14
+ stdout: string;
15
+ stderr: string;
16
+ }>;
17
+ export declare function stopChild(child: ChildProcess): Promise<void>;
@@ -0,0 +1,115 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ export function wranglerCommand(projectDir, runtime = {
6
+ executable: process.execPath,
7
+ bun: process.versions.bun !== undefined,
8
+ }) {
9
+ const local = join(projectDir, 'node_modules', '.bin', 'wrangler');
10
+ if (existsSync(local))
11
+ return { cmd: local, prefix: [] };
12
+ const require = createRequire(import.meta.url);
13
+ const packagePath = require.resolve('wrangler/package.json');
14
+ const manifest = JSON.parse(readFileSync(packagePath, 'utf8'));
15
+ const executable = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.wrangler;
16
+ if (!executable)
17
+ throw new TypeError('Installed Wrangler package has no wrangler executable.');
18
+ return {
19
+ cmd: runtime.bun ? 'node' : runtime.executable,
20
+ prefix: [resolve(dirname(packagePath), executable)],
21
+ };
22
+ }
23
+ /** Keep Wrangler CLI diagnostic logs inside the generated project state. */
24
+ export function wranglerProcessEnv(projectDir) {
25
+ return {
26
+ ...process.env,
27
+ WRANGLER_SEND_METRICS: 'false',
28
+ WRANGLER_LOG_PATH: process.env.WRANGLER_LOG_PATH || resolve(projectDir, '.astrale', 'logs', 'wrangler'),
29
+ };
30
+ }
31
+ export function runCapture(cmd, args, cwd, onLog, signal, redact = (input) => input) {
32
+ if (signal?.aborted)
33
+ return Promise.reject(new Error('Wrangler process cancelled.'));
34
+ return new Promise((resolve, reject) => {
35
+ const child = spawn(cmd, args, {
36
+ cwd,
37
+ stdio: ['ignore', 'pipe', 'pipe'],
38
+ env: wranglerProcessEnv(cwd),
39
+ });
40
+ let out = '';
41
+ let stdout = '';
42
+ let stderr = '';
43
+ let outputBytes = 0;
44
+ let terminating;
45
+ const timeout = setTimeout(() => terminate(new Error('Wrangler process timed out.')), 300_000);
46
+ const abort = () => terminate(new Error('Wrangler process cancelled.'));
47
+ signal?.addEventListener('abort', abort, { once: true });
48
+ let escalation;
49
+ const cleanup = () => {
50
+ clearTimeout(timeout);
51
+ if (escalation !== undefined)
52
+ clearTimeout(escalation);
53
+ signal?.removeEventListener('abort', abort);
54
+ };
55
+ function terminate(cause) {
56
+ if (terminating !== undefined)
57
+ return;
58
+ terminating = cause;
59
+ child.kill('SIGTERM');
60
+ escalation = setTimeout(() => {
61
+ if (child.exitCode === null && child.signalCode === null)
62
+ child.kill('SIGKILL');
63
+ }, 1_000);
64
+ }
65
+ const onData = (buffer, stream) => {
66
+ outputBytes += buffer.byteLength;
67
+ if (outputBytes > 1024 * 1024) {
68
+ terminate(new Error('Wrangler process exceeded its output limit.'));
69
+ return;
70
+ }
71
+ const chunk = buffer.toString();
72
+ out += chunk;
73
+ if (stream === 'stdout')
74
+ stdout += chunk;
75
+ else
76
+ stderr += chunk;
77
+ };
78
+ child.stdout?.on('data', (buffer) => onData(buffer, 'stdout'));
79
+ child.stderr?.on('data', (buffer) => onData(buffer, 'stderr'));
80
+ child.on('exit', (code) => {
81
+ cleanup();
82
+ if (terminating !== undefined)
83
+ reject(terminating);
84
+ else {
85
+ const safe = redact(out);
86
+ for (const line of safe.split('\n'))
87
+ if (line.trim())
88
+ onLog?.(line);
89
+ resolve({
90
+ code: code ?? 1,
91
+ out: safe,
92
+ stdout: redact(stdout),
93
+ stderr: redact(stderr),
94
+ });
95
+ }
96
+ });
97
+ child.on('error', (cause) => {
98
+ cleanup();
99
+ reject(terminating ?? cause);
100
+ });
101
+ });
102
+ }
103
+ export async function stopChild(child) {
104
+ if (child.exitCode !== null)
105
+ return;
106
+ await new Promise((resolve) => {
107
+ child.on('exit', () => resolve());
108
+ child.kill('SIGTERM');
109
+ setTimeout(() => {
110
+ if (child.exitCode === null)
111
+ child.kill('SIGKILL');
112
+ resolve();
113
+ }, 4000).unref?.();
114
+ });
115
+ }
@@ -0,0 +1,6 @@
1
+ export declare function withSecretsFile<Result>(secrets: Readonly<Record<string, string>>, operation: (path?: string) => Promise<Result>): Promise<Result>;
2
+ export declare function createSecretEnvironment(secrets: Readonly<Record<string, string>>): Promise<{
3
+ readonly path: string;
4
+ remove(): Promise<void>;
5
+ } | undefined>;
6
+ export declare function secretRedactor(secrets: Readonly<Record<string, string>>): (input: string) => string;