@astrale-os/adapter-cloudflare 0.1.1 → 0.1.3

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 (52) hide show
  1. package/dist/astrale.d.ts +27 -0
  2. package/dist/astrale.d.ts.map +1 -0
  3. package/dist/astrale.js +163 -0
  4. package/dist/astrale.js.map +1 -0
  5. package/dist/client.d.ts +7 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +43 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/cloudflare.d.ts +22 -0
  10. package/dist/cloudflare.d.ts.map +1 -0
  11. package/dist/cloudflare.js +186 -0
  12. package/dist/cloudflare.js.map +1 -0
  13. package/dist/codegen/identity.d.ts +10 -0
  14. package/dist/codegen/identity.d.ts.map +1 -0
  15. package/dist/codegen/identity.js +32 -0
  16. package/dist/codegen/identity.js.map +1 -0
  17. package/dist/codegen/merge.d.ts +20 -0
  18. package/dist/codegen/merge.d.ts.map +1 -0
  19. package/dist/codegen/merge.js +91 -0
  20. package/dist/codegen/merge.js.map +1 -0
  21. package/dist/codegen/worker.d.ts +31 -0
  22. package/dist/codegen/worker.d.ts.map +1 -0
  23. package/dist/codegen/worker.js +93 -0
  24. package/dist/codegen/worker.js.map +1 -0
  25. package/dist/codegen/wrangler.d.ts +34 -0
  26. package/dist/codegen/wrangler.d.ts.map +1 -0
  27. package/dist/codegen/wrangler.js +61 -0
  28. package/dist/codegen/wrangler.js.map +1 -0
  29. package/dist/index.d.ts +18 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +17 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/params.d.ts +69 -0
  34. package/dist/params.d.ts.map +1 -0
  35. package/dist/params.js +2 -0
  36. package/dist/params.js.map +1 -0
  37. package/dist/parse-output.d.ts +25 -0
  38. package/dist/parse-output.d.ts.map +1 -0
  39. package/dist/parse-output.js +53 -0
  40. package/dist/parse-output.js.map +1 -0
  41. package/dist/wrangler-cli.d.ts +74 -0
  42. package/dist/wrangler-cli.d.ts.map +1 -0
  43. package/dist/wrangler-cli.js +201 -0
  44. package/dist/wrangler-cli.js.map +1 -0
  45. package/package.json +2 -2
  46. package/src/astrale.ts +194 -0
  47. package/src/cloudflare.ts +2 -2
  48. package/src/index.ts +2 -1
  49. package/src/params.ts +20 -0
  50. package/src/wrangler-cli.ts +26 -1
  51. package/template/astrale.config.ts +9 -0
  52. package/template/package.json +10 -1
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Thin wrapper around the `wrangler` binary — the only place the adapter shells
3
+ * out. `watch` runs `wrangler dev` (local workerd) and resolves once the worker
4
+ * reports its local URL; `deploy` runs `wrangler deploy` and parses the
5
+ * authoritative public URL from the output; secrets go through `wrangler secret
6
+ * bulk`. The wrangler binary is resolved from the project's `node_modules/.bin`,
7
+ * falling back to `npx wrangler`.
8
+ */
9
+ import { spawn } from 'node:child_process';
10
+ import { existsSync, readdirSync } from 'node:fs';
11
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
12
+ import { tmpdir } from 'node:os';
13
+ import { join } from 'node:path';
14
+ import { parseDeployUrl, parseDevReadyUrl } from './parse-output';
15
+ function wranglerCommand(projectDir) {
16
+ const local = join(projectDir, 'node_modules', '.bin', 'wrangler');
17
+ if (existsSync(local))
18
+ return { cmd: local, prefix: [] };
19
+ return { cmd: 'npx', prefix: ['--yes', 'wrangler'] };
20
+ }
21
+ export async function runWranglerDev(args) {
22
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
23
+ const wranglerArgs = [
24
+ ...prefix,
25
+ 'dev',
26
+ '--config',
27
+ args.configPath,
28
+ '--port',
29
+ String(args.port),
30
+ '--local-protocol',
31
+ 'http',
32
+ '--ip',
33
+ '127.0.0.1',
34
+ ...(args.remote ? ['--remote'] : []),
35
+ ];
36
+ const child = spawn(cmd, wranglerArgs, {
37
+ cwd: args.projectDir,
38
+ stdio: ['ignore', 'pipe', 'pipe'],
39
+ env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
40
+ });
41
+ const fallbackUrl = `http://localhost:${args.port}`;
42
+ let resolved = false;
43
+ const url = await new Promise((resolve, reject) => {
44
+ const onData = (buf) => {
45
+ const text = buf.toString();
46
+ for (const line of text.split('\n')) {
47
+ if (line.trim())
48
+ args.onLog?.(line);
49
+ }
50
+ if (!resolved) {
51
+ const ready = parseDevReadyUrl(text, args.port);
52
+ if (ready) {
53
+ resolved = true;
54
+ resolve(ready);
55
+ }
56
+ }
57
+ else if (/Reloading|Detected changes|Updated/i.test(text)) {
58
+ args.onReload();
59
+ }
60
+ };
61
+ child.stdout?.on('data', onData);
62
+ child.stderr?.on('data', onData);
63
+ child.on('exit', (code) => {
64
+ if (!resolved) {
65
+ resolved = true;
66
+ reject(new Error(`wrangler dev exited before becoming ready (code ${code ?? 'null'}).`));
67
+ }
68
+ });
69
+ child.on('error', (err) => {
70
+ if (!resolved) {
71
+ resolved = true;
72
+ reject(err);
73
+ }
74
+ });
75
+ // Safety net: if we never matched a URL but the process is alive, assume the
76
+ // conventional localhost URL after a grace period.
77
+ setTimeout(() => {
78
+ if (!resolved && child.exitCode === null) {
79
+ resolved = true;
80
+ resolve(fallbackUrl);
81
+ }
82
+ }, 15_000).unref?.();
83
+ });
84
+ return { url, stop: () => stopChild(child) };
85
+ }
86
+ export async function runWranglerDeploy(args) {
87
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
88
+ const deploy = (configPath) => runCapture(cmd, [...prefix, 'deploy', '--config', configPath], args.projectDir, args.onLog);
89
+ return deployWithSelfFallback(deploy, args);
90
+ }
91
+ /**
92
+ * Deploy with a one-time SELF-binding first-deploy recovery. A fresh worker that
93
+ * declares a `services: SELF` binding can't deploy (its own script doesn't exist
94
+ * yet); on THAT specific failure we deploy the SELF-less fallback to create the
95
+ * script, then redeploy WITH SELF so autobinding works. Steady state (every
96
+ * deploy after the first) is a single pass. The `deploy` runner is injected so
97
+ * the orchestration is unit-testable without shelling out.
98
+ */
99
+ export async function deployWithSelfFallback(deploy, args) {
100
+ let { code, out } = await deploy(args.configPath);
101
+ if (code !== 0 && args.fallbackConfigPath && isSelfBindingMissing(out, args.workerName)) {
102
+ args.onLog?.('SELF service binding not resolvable yet (first deploy) — creating the worker, then re-binding SELF');
103
+ const first = await deploy(args.fallbackConfigPath);
104
+ if (first.code !== 0) {
105
+ throw new Error(`wrangler deploy (no-self pass) failed (code ${first.code}):\n${first.out.slice(-2000)}`);
106
+ }
107
+ ;
108
+ ({ code, out } = await deploy(args.configPath));
109
+ }
110
+ if (code !== 0) {
111
+ throw new Error(`wrangler deploy failed (code ${code}):\n${out.slice(-2000)}`);
112
+ }
113
+ const url = parseDeployUrl(out, args.route);
114
+ if (!url) {
115
+ throw new Error(`could not determine deployed URL from wrangler output:\n${out.slice(-2000)}`);
116
+ }
117
+ return { url, raw: out };
118
+ }
119
+ /**
120
+ * True when wrangler failed specifically because the worker's OWN `SELF` service
121
+ * binding can't resolve yet (first deploy of a fresh script). Requires the
122
+ * worker name so an unrelated missing-service error doesn't trigger the two-pass.
123
+ */
124
+ export function isSelfBindingMissing(out, workerName) {
125
+ const bindingError = /could not resolve binding/i.test(out) ||
126
+ /service\b[^\n]*\bnot found/i.test(out) ||
127
+ /script\b[^\n]*\bnot found/i.test(out);
128
+ if (!bindingError || !/\bSELF\b/.test(out))
129
+ return false;
130
+ return workerName ? out.includes(workerName) : true;
131
+ }
132
+ export async function bulkPutSecrets(args) {
133
+ const names = Object.keys(args.secrets);
134
+ if (names.length === 0)
135
+ return;
136
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
137
+ const dir = await mkdtemp(join(tmpdir(), 'astrale-secrets-'));
138
+ const file = join(dir, 'secrets.json');
139
+ try {
140
+ await writeFile(file, JSON.stringify(args.secrets));
141
+ const { code, out } = await runCapture(cmd, [...prefix, 'secret', 'bulk', file, '--config', args.configPath], args.projectDir, args.onLog);
142
+ if (code !== 0) {
143
+ throw new Error(`wrangler secret bulk failed (code ${code}):\n${out.slice(-1500)}`);
144
+ }
145
+ }
146
+ finally {
147
+ await rm(dir, { recursive: true, force: true });
148
+ }
149
+ }
150
+ // ── internals ──────────────────────────────────────────────────────────────
151
+ function runCapture(cmd, args, cwd, onLog) {
152
+ return new Promise((resolve, reject) => {
153
+ const child = spawn(cmd, args, {
154
+ cwd,
155
+ stdio: ['ignore', 'pipe', 'pipe'],
156
+ env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
157
+ });
158
+ let out = '';
159
+ const onData = (buf) => {
160
+ const text = buf.toString();
161
+ out += text;
162
+ for (const line of text.split('\n'))
163
+ if (line.trim())
164
+ onLog?.(line);
165
+ };
166
+ child.stdout?.on('data', onData);
167
+ child.stderr?.on('data', onData);
168
+ child.on('exit', (code) => resolve({ code: code ?? 1, out }));
169
+ child.on('error', reject);
170
+ });
171
+ }
172
+ async function stopChild(child) {
173
+ if (child.exitCode !== null)
174
+ return;
175
+ await new Promise((resolve) => {
176
+ child.on('exit', () => resolve());
177
+ child.kill('SIGTERM');
178
+ setTimeout(() => {
179
+ if (child.exitCode === null)
180
+ child.kill('SIGKILL');
181
+ resolve();
182
+ }, 4000).unref?.();
183
+ });
184
+ }
185
+ /**
186
+ * Build the single-module worker bundle WITHOUT deploying — `wrangler deploy
187
+ * --dry-run --outdir` (no Cloudflare auth required). Returns the path of the
188
+ * bundled .js module (the managed adapter publishes its bytes).
189
+ */
190
+ export async function runWranglerBundle(args) {
191
+ const { cmd, prefix } = wranglerCommand(args.projectDir);
192
+ const { code, out } = await runCapture(cmd, [...prefix, 'deploy', '--dry-run', '--outdir', args.outDir, '--config', args.configPath], args.projectDir, args.onLog);
193
+ if (code !== 0)
194
+ throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`);
195
+ const js = readdirSync(args.outDir).filter((f) => f.endsWith('.js'));
196
+ if (js.length === 0)
197
+ throw new Error(`wrangler bundle produced no .js in ${args.outDir}`);
198
+ const preferred = js.find((f) => f.startsWith('worker')) ?? js[0];
199
+ return join(args.outDir, preferred);
200
+ }
201
+ //# sourceMappingURL=wrangler-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrangler-cli.js","sourceRoot":"","sources":["../src/wrangler-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEjE,SAAS,eAAe,CAAC,UAAkB;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;IAClE,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;IACxD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAA;AACtD,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAOpC;IACC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxD,MAAM,YAAY,GAAG;QACnB,GAAG,MAAM;QACT,KAAK;QACL,UAAU;QACV,IAAI,CAAC,UAAU;QACf,QAAQ;QACR,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACjB,kBAAkB;QAClB,MAAM;QACN,MAAM;QACN,WAAW;QACX,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACrC,CAAA;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE;QACrC,GAAG,EAAE,IAAI,CAAC,UAAU;QACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,EAAE;KACxD,CAAC,CAAA;IAEF,MAAM,WAAW,GAAG,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAA;IACnD,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxD,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,EAAE;YAC7B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACpC,IAAI,IAAI,CAAC,IAAI,EAAE;oBAAE,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAA;YACrC,CAAC;YACD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC/C,IAAI,KAAK,EAAE,CAAC;oBACV,QAAQ,GAAG,IAAI,CAAA;oBACf,OAAO,CAAC,KAAK,CAAC,CAAA;gBAChB,CAAC;YACH,CAAC;iBAAM,IAAI,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5D,IAAI,CAAC,QAAQ,EAAE,CAAA;YACjB,CAAC;QACH,CAAC,CAAA;QACD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAA;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,IAAI,IAAI,MAAM,IAAI,CAAC,CAAC,CAAA;YAC1F,CAAC;QACH,CAAC,CAAC,CAAA;QACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAA;gBACf,MAAM,CAAC,GAAG,CAAC,CAAA;YACb,CAAC;QACH,CAAC,CAAC,CAAA;QACF,6EAA6E;QAC7E,mDAAmD;QACnD,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACzC,QAAQ,GAAG,IAAI,CAAA;gBACf,OAAO,CAAC,WAAW,CAAC,CAAA;YACtB,CAAC;QACH,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;AAC9C,CAAC;AAYD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAyC;IAEzC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxD,MAAM,MAAM,GAAG,CAAC,UAAkB,EAAE,EAAE,CACpC,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7F,OAAO,sBAAsB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAsE,EACtE,IAAgB;IAEhB,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAEjD,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,kBAAkB,IAAI,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACxF,IAAI,CAAC,KAAK,EAAE,CACV,oGAAoG,CACrG,CAAA;QACD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QACnD,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,+CAA+C,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CACzF,CAAA;QACH,CAAC;QACD,CAAC;QAAA,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAA;IAClD,CAAC;IAED,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChF,CAAC;IACD,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,2DAA2D,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChG,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,UAAmB;IACnE,MAAM,YAAY,GAChB,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC;QACtC,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,CAAC,YAAY,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACxD,OAAO,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAKpC;IACC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAC9B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACtC,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QACnD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,UAAU,CACpC,GAAG,EACH,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,EAChE,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,KAAK,CACX,CAAA;QACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACrF,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACjD,CAAC;AACH,CAAC;AAED,8EAA8E;AAE9E,SAAS,UAAU,CACjB,GAAW,EACX,IAAc,EACd,GAAW,EACX,KAA8B;IAE9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG;YACH,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YACjC,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,EAAE;SACxD,CAAC,CAAA;QACF,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,EAAE;YAC7B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;YAC3B,GAAG,IAAI,IAAI,CAAA;YACX,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,IAAI,IAAI,CAAC,IAAI,EAAE;oBAAE,KAAK,EAAE,CAAC,IAAI,CAAC,CAAA;QACrE,CAAC,CAAA;QACD,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;QAC7D,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAAmB;IAC1C,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAM;IACnC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;QACjC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACrB,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAClD,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAA;IACpB,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAKvC;IACC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,UAAU,CACpC,GAAG,EACH,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,EACxF,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,KAAK,CACX,CAAA;IACD,IAAI,IAAI,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC5F,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IACpE,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;IACzF,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAE,CAAA;IAClE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AACrC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/adapter-cloudflare",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Deploy an Astrale domain as a standalone Cloudflare Worker",
5
5
  "keywords": [
6
6
  "adapter",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "jose": "^6.1.3",
31
- "@astrale-os/devkit": "^0.1.1"
31
+ "@astrale-os/devkit": "^0.1.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@astrale-os/ox": ">=0.1.0 <1.0.0",
package/src/astrale.ts ADDED
@@ -0,0 +1,194 @@
1
+ /**
2
+ * `astrale(envs)` — the Astrale-managed deployment adapter.
3
+ *
4
+ * Ships the domain THROUGH the platform instead of to the author's own cloud
5
+ * account: `deploy` builds the same single-module workerd bundle the Cloudflare
6
+ * adapter ships (shared codegen + `wrangler deploy --dry-run`), then publishes
7
+ * it via the admin domain's catalog — `DomainPublished.register` →
8
+ * `::release { bundleBase64 }` (the worker stages the bytes in the platform
9
+ * registry) → `::install { instanceId, source: { kind: 'package' } }`, which
10
+ * deploys it as a host-local Service next to the target instance and installs
11
+ * the domain on it. No Cloudflare account, no wrangler auth — the only
12
+ * credential is the author's Astrale identity, supplied by the `astrale` CLI
13
+ * session this adapter shells out to (the same trust source as
14
+ * `astrale instance install`).
15
+ *
16
+ * `watch` is identical to the Cloudflare adapter's local dev (wrangler dev on
17
+ * localhost) — managed deploys change WHERE the worker runs, not how you
18
+ * iterate locally.
19
+ *
20
+ * Current limits (v1): client/ SPA assets are not shipped (managed packages
21
+ * serve no `/ui` yet) and no runtime secrets reach the service — the bundle
22
+ * must be self-contained (its signing identity is baked in by the codegen).
23
+ */
24
+
25
+ import type { DomainAdapter } from '@astrale-os/devkit'
26
+
27
+ import { defineAdapter } from '@astrale-os/devkit'
28
+ import { spawn } from 'node:child_process'
29
+ import { existsSync } from 'node:fs'
30
+ import { readFile } from 'node:fs/promises'
31
+ import { join } from 'node:path'
32
+
33
+ import type { AstraleParams } from './params'
34
+
35
+ import { logTo, prepare } from './cloudflare'
36
+ import { runWranglerBundle, runWranglerDev } from './wrangler-cli'
37
+
38
+ const DEFAULT_PORT = 8787
39
+ const DEFAULT_ADMIN_URL = 'https://admin.eu.astrale.ai/api'
40
+ const DEFAULT_REGION = 'eu'
41
+ const ADMIN_ORIGIN = 'admin.astrale.ai'
42
+
43
+ export function astrale(envs: Record<string, AstraleParams>): DomainAdapter<AstraleParams> {
44
+ return defineAdapter<AstraleParams>({
45
+ name: 'astrale',
46
+ envs,
47
+
48
+ // Local dev is unchanged: wrangler dev on localhost. Managed deploys change
49
+ // WHERE the worker ships, not the inner loop.
50
+ async watch(params, ctx) {
51
+ const { configPath } = await prepare(
52
+ { ...(params.vars ? { vars: params.vars } : {}), port: params.port ?? DEFAULT_PORT },
53
+ ctx,
54
+ 'dev',
55
+ )
56
+ const handle = await runWranglerDev({
57
+ projectDir: ctx.projectDir,
58
+ configPath,
59
+ port: params.port ?? DEFAULT_PORT,
60
+ remote: false,
61
+ onReload: ctx.onReload,
62
+ onLog: logTo(),
63
+ })
64
+ return { url: handle.url, stop: handle.stop }
65
+ },
66
+
67
+ async deploy(params, ctx) {
68
+ const log = logTo()
69
+ if (ctx.clientDir) {
70
+ log(
71
+ `client/ detected — managed deploys do not ship SPA assets yet; ` +
72
+ `/ui will not serve on the managed service (functions/methods are unaffected).`,
73
+ )
74
+ }
75
+
76
+ // 1. Codegen + bundle: the exact worker module a Cloudflare deploy would
77
+ // ship, produced locally with `wrangler deploy --dry-run` (no auth).
78
+ // `clientDir` is stripped: managed packages don't ship SPA assets yet,
79
+ // and leaving it in would put a dangling `assets.directory` (the
80
+ // unbuilt dist-client) into the generated config.
81
+ const { clientDir: _omitted, ...bundleCtx } = ctx
82
+ const { configPath } = await prepare({}, bundleCtx, 'dev')
83
+ const outDir = join(ctx.projectDir, '.astrale', 'dist-managed')
84
+ const bundlePath = await runWranglerBundle({
85
+ projectDir: ctx.projectDir,
86
+ configPath,
87
+ outDir,
88
+ onLog: log,
89
+ })
90
+ const bundle = await readFile(bundlePath)
91
+
92
+ // 2. Publish through the platform, authenticated by the author's astrale
93
+ // CLI session. The release payload rides STDIN — a bundle is megabytes
94
+ // of base64, far past argv limits.
95
+ const name = params.name ?? packageNameFor(ctx.domain.origin)
96
+ const adminUrl = params.adminUrl ?? DEFAULT_ADMIN_URL
97
+ const version = `0.0.0-managed.${Date.now()}`
98
+
99
+ log(`registering "${name}" (${ctx.domain.origin}) in the platform catalog…`)
100
+ await astraleCall(ctx.projectDir, params, adminUrl, {
101
+ path: `/${ADMIN_ORIGIN}/class.DomainPublished/register`,
102
+ data: { origin: ctx.domain.origin, name },
103
+ })
104
+
105
+ log(`publishing ${name}@${version} (${bundle.length} bytes)…`)
106
+ await astraleCall(ctx.projectDir, params, adminUrl, {
107
+ path: `/admin/domains/${name}::release`,
108
+ data: { version, bundleBase64: bundle.toString('base64'), makeCurrent: true },
109
+ viaStdin: true,
110
+ })
111
+
112
+ log(`installing on instance "${params.instance}"…`)
113
+ const installed = (await astraleCall(ctx.projectDir, params, adminUrl, {
114
+ path: `/admin/domains/${name}::install`,
115
+ data: { instanceId: params.instance, source: { kind: 'package' } },
116
+ })) as { serviceSlug?: string; state?: string; error?: string | null }
117
+
118
+ if (installed.error || installed.state !== 'installed') {
119
+ throw new Error(
120
+ `managed install failed: state=${installed.state ?? '?'} error=${installed.error ?? '?'}`,
121
+ )
122
+ }
123
+ if (!installed.serviceSlug) {
124
+ throw new Error('managed install returned no serviceSlug — cannot derive the service URL')
125
+ }
126
+ const region = params.region ?? DEFAULT_REGION
127
+ return { url: `https://${installed.serviceSlug}.svc.${region}.astrale.ai` }
128
+ },
129
+
130
+ secretsFile(params) {
131
+ return params.secrets
132
+ },
133
+ })
134
+ }
135
+
136
+ /** Catalog package name from the origin: first DNS label (e.g. `my-notes.example.dev` → `my-notes`). */
137
+ function packageNameFor(origin: string): string {
138
+ const label = origin.split('.')[0] ?? origin
139
+ return label.toLowerCase().replace(/[^a-z0-9-]+/g, '-')
140
+ }
141
+
142
+ /**
143
+ * Invoke the user's `astrale` CLI for one platform call and parse its JSON
144
+ * output. The CLI owns identity (IdP session, audience scoping, delegation
145
+ * minting) — reimplementing that here would fork the trust path.
146
+ */
147
+ async function astraleCall(
148
+ projectDir: string,
149
+ params: AstraleParams,
150
+ adminUrl: string,
151
+ call: { path: string; data: unknown; viaStdin?: boolean },
152
+ ): Promise<unknown> {
153
+ const args = ['call', call.path, '--url', adminUrl, '--json']
154
+ if (params.identity) args.push('--as', params.identity)
155
+ const payload = JSON.stringify(call.data)
156
+ if (!call.viaStdin) args.push('--data', payload)
157
+
158
+ const { code, out } = await new Promise<{ code: number; out: string }>((resolve, reject) => {
159
+ const child = spawn(astraleBin(projectDir), args, {
160
+ cwd: projectDir,
161
+ stdio: [call.viaStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
162
+ })
163
+ let out = ''
164
+ child.stdout?.on('data', (b: Buffer) => (out += b.toString()))
165
+ child.stderr?.on('data', (b: Buffer) => (out += b.toString()))
166
+ child.on('exit', (c) => resolve({ code: c ?? 1, out }))
167
+ child.on('error', reject)
168
+ if (call.viaStdin) {
169
+ child.stdin?.write(payload)
170
+ child.stdin?.end()
171
+ }
172
+ })
173
+ if (code !== 0) {
174
+ throw new Error(
175
+ `astrale call ${call.path} failed (exit ${code}): ${out.trim().slice(0, 500)}\n` +
176
+ `Is the astrale CLI installed and signed in? (astrale auth login)`,
177
+ )
178
+ }
179
+ try {
180
+ return JSON.parse(out)
181
+ } catch {
182
+ throw new Error(`astrale call ${call.path}: could not parse CLI output: ${out.slice(0, 300)}`)
183
+ }
184
+ }
185
+
186
+ /** The user's astrale CLI: ~/.astrale/bin/astrale when present, else PATH. */
187
+ function astraleBin(_projectDir: string): string {
188
+ const home = process.env.HOME
189
+ if (home) {
190
+ const installed = join(home, '.astrale', 'bin', 'astrale')
191
+ if (existsSync(installed)) return installed
192
+ }
193
+ return 'astrale'
194
+ }
package/src/cloudflare.ts CHANGED
@@ -85,7 +85,7 @@ export function cloudflare(
85
85
 
86
86
  // ── codegen orchestration ──────────────────────────────────────────────────
87
87
 
88
- async function prepare(
88
+ export async function prepare(
89
89
  params: CloudflareParams,
90
90
  ctx: WatchCtx | DeployCtx,
91
91
  mode: 'dev' | 'deploy',
@@ -223,6 +223,6 @@ export function workerName(params: CloudflareParams, origin: string): string {
223
223
  .replace(/^-+|-+$/g, '')
224
224
  }
225
225
 
226
- function logTo(): (line: string) => void {
226
+ export function logTo(): (line: string) => void {
227
227
  return (line: string) => process.stderr.write(`\x1b[2m ${line}\x1b[0m\n`)
228
228
  }
package/src/index.ts CHANGED
@@ -13,5 +13,6 @@
13
13
  * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
14
  */
15
15
 
16
+ export { astrale } from './astrale'
16
17
  export { cloudflare } from './cloudflare'
17
- export type { CloudflareParams } from './params'
18
+ export type { AstraleParams, CloudflareParams } from './params'
package/src/params.ts CHANGED
@@ -47,3 +47,23 @@ export interface CloudflareParams {
47
47
  */
48
48
  wrangler?: Record<string, unknown>
49
49
  }
50
+
51
+ /** Params for the Astrale-managed adapter (`astrale(envs)`). */
52
+ export interface AstraleParams {
53
+ /** Target instance slug the domain installs onto (e.g. `'bryan-e2e5'`). */
54
+ instance: string
55
+ /** Catalog package name. Default: the origin's first DNS label. */
56
+ name?: string
57
+ /** Admin kernel URL the publish calls target. Default the platform admin. */
58
+ adminUrl?: string
59
+ /** Routing region of the `.svc` service URL. Default `'eu'`. */
60
+ region?: string
61
+ /** CLI identity (`--as`) for the publish calls. Default: the CLI's active identity. */
62
+ identity?: string
63
+ /** Gitignored secrets file — injected ONLY into local `watch` (managed services receive no secrets yet). */
64
+ secrets?: string
65
+ /** Local dev port for `wrangler dev`. Default 8787. */
66
+ port?: number
67
+ /** Extra plain vars for local `watch`. */
68
+ vars?: Record<string, string>
69
+ }
@@ -10,7 +10,7 @@
10
10
  import type { ChildProcess } from 'node:child_process'
11
11
 
12
12
  import { spawn } from 'node:child_process'
13
- import { existsSync } from 'node:fs'
13
+ import { existsSync, readdirSync } from 'node:fs'
14
14
  import { mkdtemp, rm, writeFile } from 'node:fs/promises'
15
15
  import { tmpdir } from 'node:os'
16
16
  import { join } from 'node:path'
@@ -238,3 +238,28 @@ async function stopChild(child: ChildProcess): Promise<void> {
238
238
  }, 4000).unref?.()
239
239
  })
240
240
  }
241
+
242
+ /**
243
+ * Build the single-module worker bundle WITHOUT deploying — `wrangler deploy
244
+ * --dry-run --outdir` (no Cloudflare auth required). Returns the path of the
245
+ * bundled .js module (the managed adapter publishes its bytes).
246
+ */
247
+ export async function runWranglerBundle(args: {
248
+ projectDir: string
249
+ configPath: string
250
+ outDir: string
251
+ onLog?: (line: string) => void
252
+ }): Promise<string> {
253
+ const { cmd, prefix } = wranglerCommand(args.projectDir)
254
+ const { code, out } = await runCapture(
255
+ cmd,
256
+ [...prefix, 'deploy', '--dry-run', '--outdir', args.outDir, '--config', args.configPath],
257
+ args.projectDir,
258
+ args.onLog,
259
+ )
260
+ if (code !== 0) throw new Error(`wrangler bundle failed (exit ${code}): ${out.slice(-400)}`)
261
+ const js = readdirSync(args.outDir).filter((f) => f.endsWith('.js'))
262
+ if (js.length === 0) throw new Error(`wrangler bundle produced no .js in ${args.outDir}`)
263
+ const preferred = js.find((f) => f.startsWith('worker')) ?? js[0]!
264
+ return join(args.outDir, preferred)
265
+ }
@@ -11,6 +11,15 @@
11
11
  */
12
12
  import { cloudflare } from '@astrale-os/adapter-cloudflare'
13
13
  import { defineDomain } from '@astrale-os/devkit'
14
+ // No Cloudflare account? Swap the adapter for the Astrale-managed one — it
15
+ // publishes the same bundle THROUGH the platform and installs it on your
16
+ // instance as a managed service (auth = your `astrale auth login` session):
17
+ //
18
+ // import { astrale } from '@astrale-os/adapter-cloudflare'
19
+ // adapter: astrale({
20
+ // dev: { secrets: '.env.dev' }, // local wrangler dev, unchanged
21
+ // prod: { instance: '<your-instance-slug>' } // pnpm prod → managed deploy
22
+ // }),
14
23
 
15
24
  import { schema } from './schema'
16
25
 
@@ -2,6 +2,9 @@
2
2
  "name": "astrale-domain",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "workspaces": [
6
+ "client"
7
+ ],
5
8
  "type": "module",
6
9
  "scripts": {
7
10
  "dev": "astrale-domain dev",
@@ -16,6 +19,7 @@
16
19
  "@astrale-os/kernel-core": ">=0.3.0 <1.0.0",
17
20
  "@astrale-os/kernel-dsl": ">=0.1.0 <1.0.0",
18
21
  "@astrale-os/sdk": ">=0.1.0 <1.0.0",
22
+ "@hono/node-server": "^1.19.0",
19
23
  "zod": "^4.3.6"
20
24
  },
21
25
  "devDependencies": {
@@ -26,5 +30,10 @@
26
30
  },
27
31
  "engines": {
28
32
  "node": ">=22"
29
- }
33
+ },
34
+ "trustedDependencies": [
35
+ "esbuild",
36
+ "sharp",
37
+ "workerd"
38
+ ]
30
39
  }