@helipod/deploy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aws-IJSDMGHO.js +8 -0
- package/dist/aws-IJSDMGHO.js.map +1 -0
- package/dist/chunk-5L44HV6S.js +180 -0
- package/dist/chunk-5L44HV6S.js.map +1 -0
- package/dist/chunk-GQDT3CFX.js +35 -0
- package/dist/chunk-GQDT3CFX.js.map +1 -0
- package/dist/chunk-HJGPCCBB.js +79 -0
- package/dist/chunk-HJGPCCBB.js.map +1 -0
- package/dist/chunk-ILHJYAS6.js +8 -0
- package/dist/chunk-ILHJYAS6.js.map +1 -0
- package/dist/chunk-MDIPGR5M.js +35 -0
- package/dist/chunk-MDIPGR5M.js.map +1 -0
- package/dist/chunk-NX3QN6QD.js +29 -0
- package/dist/chunk-NX3QN6QD.js.map +1 -0
- package/dist/chunk-X4TEO3P3.js +38 -0
- package/dist/chunk-X4TEO3P3.js.map +1 -0
- package/dist/cloudflare-ZKGFMLKX.js +8 -0
- package/dist/cloudflare-ZKGFMLKX.js.map +1 -0
- package/dist/docker-VADWCIE4.js +8 -0
- package/dist/docker-VADWCIE4.js.map +1 -0
- package/dist/fly-S66SPZSA.js +8 -0
- package/dist/fly-S66SPZSA.js.map +1 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +103 -0
- package/dist/index.js.map +1 -0
- package/dist/railway-3KHXAEF2.js +8 -0
- package/dist/railway-3KHXAEF2.js.map +1 -0
- package/dist/serve-OZUXWAEN.js +8 -0
- package/dist/serve-OZUXWAEN.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/targets/cloudflare.ts
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
// src/wrangler-reconcile.ts
|
|
10
|
+
function stripJsonc(text) {
|
|
11
|
+
let out = "";
|
|
12
|
+
let inStr = false;
|
|
13
|
+
let strQuote = "";
|
|
14
|
+
let inLine = false;
|
|
15
|
+
let inBlock = false;
|
|
16
|
+
for (let i = 0; i < text.length; i++) {
|
|
17
|
+
const c = text[i];
|
|
18
|
+
const n = text[i + 1];
|
|
19
|
+
if (inLine) {
|
|
20
|
+
if (c === "\n") {
|
|
21
|
+
inLine = false;
|
|
22
|
+
out += c;
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (inBlock) {
|
|
27
|
+
if (c === "*" && n === "/") {
|
|
28
|
+
inBlock = false;
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (inStr) {
|
|
34
|
+
out += c;
|
|
35
|
+
if (c === "\\") {
|
|
36
|
+
out += n ?? "";
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (c === strQuote) inStr = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (c === '"' || c === "'") {
|
|
44
|
+
inStr = true;
|
|
45
|
+
strQuote = c;
|
|
46
|
+
out += c;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (c === "/" && n === "/") {
|
|
50
|
+
inLine = true;
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (c === "/" && n === "*") {
|
|
55
|
+
inBlock = true;
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out += c;
|
|
60
|
+
}
|
|
61
|
+
return removeTrailingCommas(out);
|
|
62
|
+
}
|
|
63
|
+
function removeTrailingCommas(text) {
|
|
64
|
+
let out = "";
|
|
65
|
+
let inStr = false;
|
|
66
|
+
let q = "";
|
|
67
|
+
for (let i = 0; i < text.length; i++) {
|
|
68
|
+
const c = text[i];
|
|
69
|
+
if (inStr) {
|
|
70
|
+
out += c;
|
|
71
|
+
if (c === "\\") {
|
|
72
|
+
out += text[i + 1] ?? "";
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (c === q) inStr = false;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (c === '"' || c === "'") {
|
|
80
|
+
inStr = true;
|
|
81
|
+
q = c;
|
|
82
|
+
out += c;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (c === ",") {
|
|
86
|
+
let j = i + 1;
|
|
87
|
+
while (j < text.length && /\s/.test(text[j])) j++;
|
|
88
|
+
if (j < text.length && (text[j] === "}" || text[j] === "]")) continue;
|
|
89
|
+
}
|
|
90
|
+
out += c;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
var DO_BINDING = "HELIPOD_DO";
|
|
95
|
+
var DO_CLASS = "HelipodDO";
|
|
96
|
+
var R2_BINDING = "STORAGE_BUCKET";
|
|
97
|
+
var COMPAT_FLAG = "nodejs_compat";
|
|
98
|
+
function reconcileWrangler(input, opts) {
|
|
99
|
+
const config = structuredClone(input);
|
|
100
|
+
const added = [];
|
|
101
|
+
const dobj = config.durable_objects ??= {};
|
|
102
|
+
dobj.bindings ??= [];
|
|
103
|
+
if (!dobj.bindings.some((b) => b.name === DO_BINDING)) {
|
|
104
|
+
dobj.bindings.push({ name: DO_BINDING, class_name: DO_CLASS });
|
|
105
|
+
added.push(`durable_objects.${DO_BINDING}`);
|
|
106
|
+
}
|
|
107
|
+
const migrations = config.migrations ??= [];
|
|
108
|
+
const hasSqliteClass = migrations.some((m) => m.new_sqlite_classes?.includes(DO_CLASS));
|
|
109
|
+
if (!hasSqliteClass) {
|
|
110
|
+
const usedTags = new Set(migrations.map((m) => m.tag));
|
|
111
|
+
let n = migrations.length + 1;
|
|
112
|
+
while (usedTags.has(`v${n}`)) n++;
|
|
113
|
+
migrations.push({ tag: `v${n}`, new_sqlite_classes: [DO_CLASS] });
|
|
114
|
+
added.push(`migrations.${DO_CLASS}`);
|
|
115
|
+
}
|
|
116
|
+
const flags = config.compatibility_flags ??= [];
|
|
117
|
+
if (!flags.includes(COMPAT_FLAG)) {
|
|
118
|
+
flags.push(COMPAT_FLAG);
|
|
119
|
+
added.push(`compatibility_flags.${COMPAT_FLAG}`);
|
|
120
|
+
}
|
|
121
|
+
if (opts.needsR2) {
|
|
122
|
+
const buckets = config.r2_buckets ??= [];
|
|
123
|
+
if (!buckets.some((b) => b.binding === R2_BINDING)) {
|
|
124
|
+
buckets.push({ binding: R2_BINDING, bucket_name: opts.r2BucketName ?? "helipod-storage" });
|
|
125
|
+
added.push(`r2_buckets.${R2_BINDING}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { config, changed: added.length > 0, added };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/targets/cloudflare.ts
|
|
132
|
+
var WRANGLER = "wrangler.jsonc";
|
|
133
|
+
function extractDeployedUrl(stdout) {
|
|
134
|
+
return stdout.match(/https:\/\/[^\s]+/)?.[0];
|
|
135
|
+
}
|
|
136
|
+
var cloudflareTarget = {
|
|
137
|
+
name: "cloudflare",
|
|
138
|
+
async preflight(ctx) {
|
|
139
|
+
const v = await ctx.spawn.run("wrangler", ["--version"], { cwd: ctx.cwd, stdio: "capture" }).catch(() => {
|
|
140
|
+
throw new DeployError("wrangler not found \u2014 install it (npm i -D wrangler) and retry");
|
|
141
|
+
});
|
|
142
|
+
if (v.code !== 0) throw new DeployError("wrangler not found \u2014 install it (npm i -D wrangler) and retry");
|
|
143
|
+
if (!existsSync(join(ctx.cwd, WRANGLER))) {
|
|
144
|
+
throw new DeployError(`${WRANGLER} not found in ${ctx.cwd} \u2014 create one (see docs/enduser/deploy/cloudflare.md)`);
|
|
145
|
+
}
|
|
146
|
+
if (!ctx.interactive && !process.env.CLOUDFLARE_API_TOKEN) {
|
|
147
|
+
throw new DeployError("CLOUDFLARE_API_TOKEN is required for non-interactive (CI) deploy");
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async package(ctx) {
|
|
151
|
+
await ctx.codegen();
|
|
152
|
+
const path = join(ctx.cwd, WRANGLER);
|
|
153
|
+
const raw = readFileSync(path, "utf8");
|
|
154
|
+
const parsed = JSON.parse(stripJsonc(raw));
|
|
155
|
+
const r = reconcileWrangler(parsed, {
|
|
156
|
+
needsR2: Boolean(ctx.target.settings.r2),
|
|
157
|
+
r2BucketName: ctx.target.settings.r2BucketName == null ? void 0 : String(ctx.target.settings.r2BucketName)
|
|
158
|
+
});
|
|
159
|
+
if (r.changed) {
|
|
160
|
+
writeFileSync(path, JSON.stringify(r.config, null, 2) + "\n");
|
|
161
|
+
ctx.log(`reconciled ${WRANGLER}: added ${r.added.join(", ")}`);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
async push(ctx) {
|
|
165
|
+
const args = ["deploy"];
|
|
166
|
+
const wranglerEnv = ctx.target.settings.wranglerEnv == null ? void 0 : String(ctx.target.settings.wranglerEnv);
|
|
167
|
+
if (wranglerEnv) args.push("--env", wranglerEnv);
|
|
168
|
+
const r = await ctx.spawn.run("wrangler", args, { cwd: ctx.cwd, stdio: "capture" });
|
|
169
|
+
if (r.code !== 0) return { ok: false, error: `wrangler deploy failed: ${(r.stderr || r.stdout).trim()}` };
|
|
170
|
+
const url = extractDeployedUrl(r.stdout);
|
|
171
|
+
return { ok: true, url, detail: url ? `deployed to ${url}` : "deployed" };
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
stripJsonc,
|
|
177
|
+
reconcileWrangler,
|
|
178
|
+
cloudflareTarget
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=chunk-5L44HV6S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/targets/cloudflare.ts","../src/wrangler-reconcile.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { DeployTarget, DeployContext, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\nimport { stripJsonc, reconcileWrangler } from \"../wrangler-reconcile\";\n\nconst WRANGLER = \"wrangler.jsonc\";\n\n/** First https URL wrangler prints on deploy — the deployed Worker URL. */\nfunction extractDeployedUrl(stdout: string): string | undefined {\n return stdout.match(/https:\\/\\/[^\\s]+/)?.[0];\n}\n\nexport const cloudflareTarget: DeployTarget = {\n name: \"cloudflare\",\n async preflight(ctx) {\n const v = await ctx.spawn.run(\"wrangler\", [\"--version\"], { cwd: ctx.cwd, stdio: \"capture\" }).catch(() => {\n throw new DeployError(\"wrangler not found — install it (npm i -D wrangler) and retry\");\n });\n if (v.code !== 0) throw new DeployError(\"wrangler not found — install it (npm i -D wrangler) and retry\");\n if (!existsSync(join(ctx.cwd, WRANGLER))) {\n throw new DeployError(`${WRANGLER} not found in ${ctx.cwd} — create one (see docs/enduser/deploy/cloudflare.md)`);\n }\n if (!ctx.interactive && !process.env.CLOUDFLARE_API_TOKEN) {\n throw new DeployError(\"CLOUDFLARE_API_TOKEN is required for non-interactive (CI) deploy\");\n }\n },\n async package(ctx) {\n await ctx.codegen();\n const path = join(ctx.cwd, WRANGLER);\n const raw = readFileSync(path, \"utf8\");\n const parsed = JSON.parse(stripJsonc(raw)) as Record<string, unknown>;\n const r = reconcileWrangler(parsed, {\n needsR2: Boolean(ctx.target.settings.r2),\n r2BucketName: ctx.target.settings.r2BucketName == null ? undefined : String(ctx.target.settings.r2BucketName),\n });\n if (r.changed) {\n // NOTE: reconcile rewrites as plain JSON (comments not preserved) — only happens when a binding\n // is actually added; a project that already has the bindings keeps its commented wrangler.jsonc.\n writeFileSync(path, JSON.stringify(r.config, null, 2) + \"\\n\");\n ctx.log(`reconciled ${WRANGLER}: added ${r.added.join(\", \")}`);\n }\n },\n async push(ctx): Promise<DeployResult> {\n const args = [\"deploy\"];\n const wranglerEnv = ctx.target.settings.wranglerEnv == null ? undefined : String(ctx.target.settings.wranglerEnv);\n if (wranglerEnv) args.push(\"--env\", wranglerEnv);\n const r = await ctx.spawn.run(\"wrangler\", args, { cwd: ctx.cwd, stdio: \"capture\" });\n if (r.code !== 0) return { ok: false, error: `wrangler deploy failed: ${(r.stderr || r.stdout).trim()}` };\n const url = extractDeployedUrl(r.stdout);\n return { ok: true, url, detail: url ? `deployed to ${url}` : \"deployed\" };\n },\n};\n","/** String-aware JSONC → JSON: strips // and block comments and trailing commas, honoring string literals. */\nexport function stripJsonc(text: string): string {\n let out = \"\";\n let inStr = false;\n let strQuote = \"\";\n let inLine = false;\n let inBlock = false;\n for (let i = 0; i < text.length; i++) {\n const c = text[i];\n const n = text[i + 1];\n if (inLine) { if (c === \"\\n\") { inLine = false; out += c; } continue; }\n if (inBlock) { if (c === \"*\" && n === \"/\") { inBlock = false; i++; } continue; }\n if (inStr) {\n out += c;\n if (c === \"\\\\\") { out += n ?? \"\"; i++; continue; }\n if (c === strQuote) inStr = false;\n continue;\n }\n if (c === '\"' || c === \"'\") { inStr = true; strQuote = c; out += c; continue; }\n if (c === \"/\" && n === \"/\") { inLine = true; i++; continue; }\n if (c === \"/\" && n === \"*\") { inBlock = true; i++; continue; }\n out += c;\n }\n // Remove trailing commas before } or ], string-aware (comments are already gone at this point).\n return removeTrailingCommas(out);\n}\n\n/** Drop a comma immediately preceding (modulo whitespace) a } or ], without touching commas inside\n * string literals. Runs on already comment-stripped text. */\nfunction removeTrailingCommas(text: string): string {\n let out = \"\";\n let inStr = false;\n let q = \"\";\n for (let i = 0; i < text.length; i++) {\n const c = text[i];\n if (inStr) {\n out += c;\n if (c === \"\\\\\") { out += text[i + 1] ?? \"\"; i++; continue; }\n if (c === q) inStr = false;\n continue;\n }\n if (c === '\"' || c === \"'\") { inStr = true; q = c; out += c; continue; }\n if (c === \",\") {\n let j = i + 1;\n while (j < text.length && /\\s/.test(text[j]!)) j++;\n if (j < text.length && (text[j] === \"}\" || text[j] === \"]\")) continue; // trailing comma — drop it\n }\n out += c;\n }\n return out;\n}\n\nconst DO_BINDING = \"HELIPOD_DO\";\nconst DO_CLASS = \"HelipodDO\";\nconst R2_BINDING = \"STORAGE_BUCKET\";\nconst COMPAT_FLAG = \"nodejs_compat\";\n\nexport interface ReconcileOpts { needsR2?: boolean; r2BucketName?: string; }\nexport interface ReconcileResult { config: Record<string, unknown>; changed: boolean; added: string[]; }\n\n/** Additively ensure the Helipod DO bindings/migration/compat flag (+ optional R2) exist. Never\n * drops a user field. Returns a fresh config object; `changed` says whether anything was added. */\nexport function reconcileWrangler(input: Record<string, unknown>, opts: ReconcileOpts): ReconcileResult {\n const config: Record<string, unknown> = structuredClone(input);\n const added: string[] = [];\n\n // Durable Object binding\n const dobj = (config.durable_objects ??= {}) as { bindings?: Array<{ name: string; class_name: string }> };\n dobj.bindings ??= [];\n if (!dobj.bindings.some((b) => b.name === DO_BINDING)) {\n dobj.bindings.push({ name: DO_BINDING, class_name: DO_CLASS });\n added.push(`durable_objects.${DO_BINDING}`);\n }\n\n // SQLite class migration\n const migrations = (config.migrations ??= []) as Array<{ tag: string; new_sqlite_classes?: string[] }>;\n const hasSqliteClass = migrations.some((m) => m.new_sqlite_classes?.includes(DO_CLASS));\n if (!hasSqliteClass) {\n const usedTags = new Set(migrations.map((m) => m.tag));\n let n = migrations.length + 1;\n while (usedTags.has(`v${n}`)) n++;\n migrations.push({ tag: `v${n}`, new_sqlite_classes: [DO_CLASS] });\n added.push(`migrations.${DO_CLASS}`);\n }\n\n // nodejs_compat flag\n const flags = (config.compatibility_flags ??= []) as string[];\n if (!flags.includes(COMPAT_FLAG)) { flags.push(COMPAT_FLAG); added.push(`compatibility_flags.${COMPAT_FLAG}`); }\n\n // Optional R2 bucket (file storage)\n if (opts.needsR2) {\n const buckets = (config.r2_buckets ??= []) as Array<{ binding: string; bucket_name: string }>;\n if (!buckets.some((b) => b.binding === R2_BINDING)) {\n buckets.push({ binding: R2_BINDING, bucket_name: opts.r2BucketName ?? \"helipod-storage\" });\n added.push(`r2_buckets.${R2_BINDING}`);\n }\n }\n\n return { config, changed: added.length > 0, added };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/targets/railway.ts
|
|
6
|
+
var railwayTarget = {
|
|
7
|
+
name: "railway",
|
|
8
|
+
async preflight(ctx) {
|
|
9
|
+
const v = await ctx.spawn.run("railway", ["--version"], { cwd: ctx.cwd, stdio: "capture" }).catch(() => {
|
|
10
|
+
throw new DeployError("railway CLI not found \u2014 install railway CLI (npm i -g @railway/cli, or see https://docs.railway.com/guides/cli) and retry");
|
|
11
|
+
});
|
|
12
|
+
if (v.code !== 0) throw new DeployError("railway CLI not found \u2014 install railway CLI and retry");
|
|
13
|
+
if (!ctx.interactive && !process.env.RAILWAY_TOKEN) {
|
|
14
|
+
throw new DeployError("RAILWAY_TOKEN is required for non-interactive (CI) deploy");
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
async package(ctx) {
|
|
18
|
+
await ctx.codegen();
|
|
19
|
+
},
|
|
20
|
+
async push(ctx) {
|
|
21
|
+
const args = ["up"];
|
|
22
|
+
const service = ctx.target.settings.service == null ? void 0 : String(ctx.target.settings.service);
|
|
23
|
+
if (service) args.push("--service", service);
|
|
24
|
+
const environment = ctx.target.settings.environment == null ? void 0 : String(ctx.target.settings.environment);
|
|
25
|
+
if (environment) args.push("--environment", environment);
|
|
26
|
+
const r = await ctx.spawn.run("railway", args, { cwd: ctx.cwd, stdio: "capture" });
|
|
27
|
+
if (r.code !== 0) return { ok: false, error: `railway up failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };
|
|
28
|
+
return { ok: true, detail: (r.stdout || "deployed (railway up)").trim() };
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
railwayTarget
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-GQDT3CFX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/targets/railway.ts"],"sourcesContent":["import type { DeployTarget, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\n\nexport const railwayTarget: DeployTarget = {\n name: \"railway\",\n async preflight(ctx) {\n const v = await ctx.spawn.run(\"railway\", [\"--version\"], { cwd: ctx.cwd, stdio: \"capture\" }).catch(() => {\n throw new DeployError(\"railway CLI not found — install railway CLI (npm i -g @railway/cli, or see https://docs.railway.com/guides/cli) and retry\");\n });\n if (v.code !== 0) throw new DeployError(\"railway CLI not found — install railway CLI and retry\");\n // RAILWAY_TOKEN is Railway's documented CI/non-interactive project-token auth env var — required\n // in non-interactive mode since `railway up` otherwise falls back to an interactive login prompt.\n if (!ctx.interactive && !process.env.RAILWAY_TOKEN) {\n throw new DeployError(\"RAILWAY_TOKEN is required for non-interactive (CI) deploy\");\n }\n },\n async package(ctx) {\n // Railway builds the deployed image itself (from the repo's Dockerfile if present, else Nixpacks\n // auto-detection) when `railway up` uploads the project — there is nothing to bundle here beyond\n // refreshing codegen so the baked functions directory's `_generated` matches the functions\n // being deployed.\n await ctx.codegen();\n },\n async push(ctx): Promise<DeployResult> {\n const args = [\"up\"];\n const service = ctx.target.settings.service == null ? undefined : String(ctx.target.settings.service);\n if (service) args.push(\"--service\", service);\n const environment = ctx.target.settings.environment == null ? undefined : String(ctx.target.settings.environment);\n if (environment) args.push(\"--environment\", environment);\n const r = await ctx.spawn.run(\"railway\", args, { cwd: ctx.cwd, stdio: \"capture\" });\n if (r.code !== 0) return { ok: false, error: `railway up failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };\n return { ok: true, detail: (r.stdout || \"deployed (railway up)\").trim() };\n },\n};\n"],"mappings":";;;;;AAGO,IAAM,gBAA8B;AAAA,EACzC,MAAM;AAAA,EACN,MAAM,UAAU,KAAK;AACnB,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,WAAW,CAAC,WAAW,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC,EAAE,MAAM,MAAM;AACtG,YAAM,IAAI,YAAY,gIAA2H;AAAA,IACnJ,CAAC;AACD,QAAI,EAAE,SAAS,EAAG,OAAM,IAAI,YAAY,4DAAuD;AAG/F,QAAI,CAAC,IAAI,eAAe,CAAC,QAAQ,IAAI,eAAe;AAClD,YAAM,IAAI,YAAY,2DAA2D;AAAA,IACnF;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,KAAK;AAKjB,UAAM,IAAI,QAAQ;AAAA,EACpB;AAAA,EACA,MAAM,KAAK,KAA4B;AACrC,UAAM,OAAO,CAAC,IAAI;AAClB,UAAM,UAAU,IAAI,OAAO,SAAS,WAAW,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,OAAO;AACpG,QAAI,QAAS,MAAK,KAAK,aAAa,OAAO;AAC3C,UAAM,cAAc,IAAI,OAAO,SAAS,eAAe,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,WAAW;AAChH,QAAI,YAAa,MAAK,KAAK,iBAAiB,WAAW;AACvD,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,WAAW,MAAM,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC;AACjF,QAAI,EAAE,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,uBAAuB,EAAE,UAAU,EAAE,QAAQ,KAAK,KAAK,QAAQ,EAAE,IAAI,EAAE,GAAG;AACvH,WAAO,EAAE,IAAI,MAAM,SAAS,EAAE,UAAU,yBAAyB,KAAK,EAAE;AAAA,EAC1E;AACF;","names":[]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/module-hash.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
function sha256Hex(code) {
|
|
8
|
+
return createHash("sha256").update(code, "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
function partitionModules(local, remoteHashes) {
|
|
11
|
+
const changed = [];
|
|
12
|
+
const unchanged = [];
|
|
13
|
+
for (const f of local.files) {
|
|
14
|
+
const sha = sha256Hex(f.code);
|
|
15
|
+
if (remoteHashes[f.path] === sha) unchanged.push({ path: f.path, sha256: sha });
|
|
16
|
+
else changed.push({ path: f.path, code: f.code });
|
|
17
|
+
}
|
|
18
|
+
return { changed, unchanged };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/targets/serve.ts
|
|
22
|
+
function creds(ctx) {
|
|
23
|
+
const url = String(ctx.target.settings.url ?? process.env.HELIPOD_DEPLOY_URL ?? "").trim();
|
|
24
|
+
const adminKey = String(ctx.target.settings.adminKey ?? process.env.HELIPOD_ADMIN_KEY ?? "").trim();
|
|
25
|
+
return { url, adminKey };
|
|
26
|
+
}
|
|
27
|
+
var serveTarget = {
|
|
28
|
+
name: "serve",
|
|
29
|
+
async preflight(ctx) {
|
|
30
|
+
const { url, adminKey } = creds(ctx);
|
|
31
|
+
if (!url) throw new DeployError("serve target needs a url \u2014 pass --url or set deploy.targets.serve settings / HELIPOD_DEPLOY_URL");
|
|
32
|
+
if (!adminKey) throw new DeployError("HELIPOD_ADMIN_KEY is required to deploy to a serve target");
|
|
33
|
+
},
|
|
34
|
+
async package() {
|
|
35
|
+
},
|
|
36
|
+
async push(ctx) {
|
|
37
|
+
const { url, adminKey } = creds(ctx);
|
|
38
|
+
const base = url.replace(/\/$/, "");
|
|
39
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${adminKey}` };
|
|
40
|
+
const { files } = await ctx.packageApp();
|
|
41
|
+
const post = async (body) => {
|
|
42
|
+
let res;
|
|
43
|
+
try {
|
|
44
|
+
res = await fetch(`${base}/_admin/deploy`, { method: "POST", headers, body });
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return { ok: false, error: `could not reach ${base}: ${e instanceof Error ? e.message : String(e)}` };
|
|
47
|
+
}
|
|
48
|
+
if (res.status === 404) return { ok: false, error: "deploy not enabled on target (start serve with --allow-deploy)" };
|
|
49
|
+
const b = await res.json().catch(() => ({}));
|
|
50
|
+
return { ok: Boolean(res.ok && b.ok), rev: b.rev, functions: b.functions, kind: b.kind, error: b.error };
|
|
51
|
+
};
|
|
52
|
+
const done = (r2, extra) => ({ ok: true, url: base, detail: `rev ${r2.rev} (${r2.functions} functions${extra})` });
|
|
53
|
+
let remoteHashes = null;
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${base}/_admin/deploy/modules`, { headers });
|
|
56
|
+
if (res.ok) remoteHashes = await res.json().catch(() => null);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
if (remoteHashes) {
|
|
60
|
+
const { changed, unchanged } = partitionModules({ files }, remoteHashes);
|
|
61
|
+
const r2 = await post(JSON.stringify({ changed, unchanged }));
|
|
62
|
+
if (r2.ok) return done(r2, `, ${changed.length} changed`);
|
|
63
|
+
if (r2.kind === "stale-base") {
|
|
64
|
+
const full = await post(JSON.stringify({ files }));
|
|
65
|
+
return full.ok ? done(full, ", full retry") : { ok: false, error: full.error ?? "deploy failed" };
|
|
66
|
+
}
|
|
67
|
+
return { ok: false, error: r2.error ?? "deploy failed" };
|
|
68
|
+
}
|
|
69
|
+
const r = await post(JSON.stringify({ files }));
|
|
70
|
+
return r.ok ? done(r, "") : { ok: false, error: r.error ?? "deploy failed" };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
sha256Hex,
|
|
76
|
+
partitionModules,
|
|
77
|
+
serveTarget
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chunk-HJGPCCBB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/module-hash.ts","../src/targets/serve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { FileTree } from \"./types\";\n\n/** Lowercase-hex sha256 over the utf8 code string. Identical on client and server — never a re-transpile. */\nexport function sha256Hex(code: string): string {\n return createHash(\"sha256\").update(code, \"utf8\").digest(\"hex\");\n}\n\nexport interface DeltaPush {\n changed: Array<{ path: string; code: string }>;\n unchanged: Array<{ path: string; sha256: string }>;\n}\n\n/** Partition local files against the server's current per-path hashes. A file is `unchanged` iff the\n * server has the same path with an equal sha256; otherwise `changed` (new or modified). A path the\n * server has but the local tree lacks appears in neither list (deletion by omission). */\nexport function partitionModules(local: FileTree, remoteHashes: Record<string, string>): DeltaPush {\n const changed: Array<{ path: string; code: string }> = [];\n const unchanged: Array<{ path: string; sha256: string }> = [];\n for (const f of local.files) {\n const sha = sha256Hex(f.code);\n if (remoteHashes[f.path] === sha) unchanged.push({ path: f.path, sha256: sha });\n else changed.push({ path: f.path, code: f.code });\n }\n return { changed, unchanged };\n}\n","import type { DeployTarget, DeployContext, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\nimport { partitionModules } from \"../module-hash\";\n\n/** url/adminKey come from config settings, falling back to the slice-6b env vars (exact back-compat). */\nfunction creds(ctx: DeployContext): { url: string; adminKey: string } {\n const url = String(ctx.target.settings.url ?? process.env.HELIPOD_DEPLOY_URL ?? \"\").trim();\n const adminKey = String(ctx.target.settings.adminKey ?? process.env.HELIPOD_ADMIN_KEY ?? \"\").trim();\n return { url, adminKey };\n}\n\nexport const serveTarget: DeployTarget = {\n name: \"serve\",\n async preflight(ctx) {\n const { url, adminKey } = creds(ctx);\n if (!url) throw new DeployError(\"serve target needs a url — pass --url or set deploy.targets.serve settings / HELIPOD_DEPLOY_URL\");\n if (!adminKey) throw new DeployError(\"HELIPOD_ADMIN_KEY is required to deploy to a serve target\");\n },\n async package() { /* no artifact to pre-build; files come from ctx.packageApp() at push */ },\n async push(ctx): Promise<DeployResult> {\n const { url, adminKey } = creds(ctx);\n const base = url.replace(/\\/$/, \"\");\n const headers = { \"content-type\": \"application/json\", authorization: `Bearer ${adminKey}` };\n const { files } = await ctx.packageApp();\n\n // POST helper — returns a normalized shape the flow below branches on.\n const post = async (body: string): Promise<{ ok: boolean; rev?: string; functions?: number; kind?: string; error?: string }> => {\n let res: Response;\n try {\n res = await fetch(`${base}/_admin/deploy`, { method: \"POST\", headers, body });\n } catch (e) {\n return { ok: false, error: `could not reach ${base}: ${e instanceof Error ? e.message : String(e)}` };\n }\n if (res.status === 404) return { ok: false, error: \"deploy not enabled on target (start serve with --allow-deploy)\" };\n const b = (await res.json().catch(() => ({}))) as { ok?: boolean; rev?: string; functions?: number; kind?: string; error?: string };\n return { ok: Boolean(res.ok && b.ok), rev: b.rev, functions: b.functions, kind: b.kind, error: b.error };\n };\n const done = (r: { rev?: string; functions?: number }, extra: string): DeployResult => ({ ok: true, url: base, detail: `rev ${r.rev} (${r.functions} functions${extra})` });\n\n // 1. Probe the server's current module hashes (capability + delta base).\n let remoteHashes: Record<string, string> | null = null;\n try {\n const res = await fetch(`${base}/_admin/deploy/modules`, { headers });\n if (res.ok) remoteHashes = (await res.json().catch(() => null)) as Record<string, string> | null;\n // A non-ok (404 old server / disabled) leaves remoteHashes null → full push below.\n } catch {\n // network error surfaces on the POST below\n }\n\n // 2. Delta push when we have a base; otherwise a full push.\n if (remoteHashes) {\n const { changed, unchanged } = partitionModules({ files }, remoteHashes);\n const r = await post(JSON.stringify({ changed, unchanged }));\n if (r.ok) return done(r, `, ${changed.length} changed`);\n if (r.kind === \"stale-base\") {\n const full = await post(JSON.stringify({ files }));\n return full.ok ? done(full, \", full retry\") : { ok: false, error: full.error ?? \"deploy failed\" };\n }\n return { ok: false, error: r.error ?? \"deploy failed\" };\n }\n const r = await post(JSON.stringify({ files }));\n return r.ok ? done(r, \"\") : { ok: false, error: r.error ?? \"deploy failed\" };\n },\n};\n"],"mappings":"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","names":["r"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["export interface SpawnOptions { cwd?: string; env?: Record<string, string>; stdio?: \"inherit\" | \"capture\"; }\nexport interface SpawnResult { code: number; stdout: string; stderr: string; }\nexport interface Spawner { run(cmd: string, args: string[], opts?: SpawnOptions): Promise<SpawnResult>; }\n\nexport interface FileTree { files: Array<{ path: string; code: string }>; }\n\nexport interface ResolvedTarget {\n targetName: string; // the --target value, e.g. \"cloudflare\"\n provider: string; // \"serve\" | \"cloudflare\" | \"docker\" | \"railway\" | \"fly\" | \"aws\"\n env: string; // resolved environment name, e.g. \"production\"\n settings: Record<string, unknown>; // shared config merged with the env override\n}\n\nexport interface DeployContext {\n cwd: string; // project root (dir containing the functions dir)\n functionsDir: string; // path to the functions dir (default \"helipod/\")\n env: string; // = ResolvedTarget.env\n target: ResolvedTarget;\n interactive: boolean; // stdin.isTTY && !process.env.CI — false gates all prompts\n spawn: Spawner;\n log: (msg: string) => void;\n packageApp: () => Promise<FileTree>; // transpile the functions dir (provided by the CLI)\n codegen: () => Promise<void>; // refresh <functionsDir>/_generated (provided by the CLI)\n}\n\nexport interface DeployResult { ok: boolean; url?: string; detail?: string; error?: string; }\n\nexport interface DeployTarget {\n readonly name: string; // \"serve\" | \"cloudflare\" | \"docker\" | \"railway\" | \"fly\" | \"aws\"\n preflight(ctx: DeployContext): Promise<void>;\n package(ctx: DeployContext): Promise<void>;\n push(ctx: DeployContext): Promise<DeployResult>;\n}\n\nexport class DeployError extends Error {}\n"],"mappings":";AAkCO,IAAM,cAAN,cAA0B,MAAM;AAAC;","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/targets/fly.ts
|
|
6
|
+
var flyTarget = {
|
|
7
|
+
name: "fly",
|
|
8
|
+
async preflight(ctx) {
|
|
9
|
+
const v = await ctx.spawn.run("fly", ["version"], { cwd: ctx.cwd, stdio: "capture" }).catch(() => {
|
|
10
|
+
throw new DeployError("fly CLI not found \u2014 install flyctl (see https://fly.io/docs/flyctl/install/) and retry");
|
|
11
|
+
});
|
|
12
|
+
if (v.code !== 0) throw new DeployError("fly CLI not found \u2014 install flyctl and retry");
|
|
13
|
+
if (!ctx.interactive && !process.env.FLY_API_TOKEN) {
|
|
14
|
+
throw new DeployError("FLY_API_TOKEN is required for non-interactive (CI) deploy");
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
async package(ctx) {
|
|
18
|
+
await ctx.codegen();
|
|
19
|
+
},
|
|
20
|
+
async push(ctx) {
|
|
21
|
+
const args = ["deploy"];
|
|
22
|
+
const app = ctx.target.settings.app == null ? void 0 : String(ctx.target.settings.app);
|
|
23
|
+
if (app) args.push("--app", app);
|
|
24
|
+
const region = ctx.target.settings.region == null ? void 0 : String(ctx.target.settings.region);
|
|
25
|
+
if (region) args.push("--region", region);
|
|
26
|
+
const r = await ctx.spawn.run("fly", args, { cwd: ctx.cwd, stdio: "capture" });
|
|
27
|
+
if (r.code !== 0) return { ok: false, error: `fly deploy failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };
|
|
28
|
+
return { ok: true, detail: (r.stdout || "deployed (fly deploy)").trim() };
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
flyTarget
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-MDIPGR5M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/targets/fly.ts"],"sourcesContent":["import type { DeployTarget, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\n\nexport const flyTarget: DeployTarget = {\n name: \"fly\",\n async preflight(ctx) {\n const v = await ctx.spawn.run(\"fly\", [\"version\"], { cwd: ctx.cwd, stdio: \"capture\" }).catch(() => {\n throw new DeployError(\"fly CLI not found — install flyctl (see https://fly.io/docs/flyctl/install/) and retry\");\n });\n if (v.code !== 0) throw new DeployError(\"fly CLI not found — install flyctl and retry\");\n // FLY_API_TOKEN is Fly's documented CI/non-interactive token auth env var — required in\n // non-interactive mode since `fly deploy` otherwise falls back to an interactive login prompt.\n if (!ctx.interactive && !process.env.FLY_API_TOKEN) {\n throw new DeployError(\"FLY_API_TOKEN is required for non-interactive (CI) deploy\");\n }\n },\n async package(ctx) {\n // Fly builds the deployed image itself (from the repo's Dockerfile, per fly.toml's `build`\n // config, or a buildpack fallback) when `fly deploy` uploads the project — there is nothing to\n // bundle here beyond refreshing codegen so the baked functions directory's `_generated`\n // matches the functions being deployed.\n await ctx.codegen();\n },\n async push(ctx): Promise<DeployResult> {\n const args = [\"deploy\"];\n const app = ctx.target.settings.app == null ? undefined : String(ctx.target.settings.app);\n if (app) args.push(\"--app\", app);\n const region = ctx.target.settings.region == null ? undefined : String(ctx.target.settings.region);\n if (region) args.push(\"--region\", region);\n const r = await ctx.spawn.run(\"fly\", args, { cwd: ctx.cwd, stdio: \"capture\" });\n if (r.code !== 0) return { ok: false, error: `fly deploy failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };\n return { ok: true, detail: (r.stdout || \"deployed (fly deploy)\").trim() };\n },\n};\n"],"mappings":";;;;;AAGO,IAAM,YAA0B;AAAA,EACrC,MAAM;AAAA,EACN,MAAM,UAAU,KAAK;AACnB,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,CAAC,SAAS,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC,EAAE,MAAM,MAAM;AAChG,YAAM,IAAI,YAAY,6FAAwF;AAAA,IAChH,CAAC;AACD,QAAI,EAAE,SAAS,EAAG,OAAM,IAAI,YAAY,mDAA8C;AAGtF,QAAI,CAAC,IAAI,eAAe,CAAC,QAAQ,IAAI,eAAe;AAClD,YAAM,IAAI,YAAY,2DAA2D;AAAA,IACnF;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,KAAK;AAKjB,UAAM,IAAI,QAAQ;AAAA,EACpB;AAAA,EACA,MAAM,KAAK,KAA4B;AACrC,UAAM,OAAO,CAAC,QAAQ;AACtB,UAAM,MAAM,IAAI,OAAO,SAAS,OAAO,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,GAAG;AACxF,QAAI,IAAK,MAAK,KAAK,SAAS,GAAG;AAC/B,UAAM,SAAS,IAAI,OAAO,SAAS,UAAU,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,MAAM;AACjG,QAAI,OAAQ,MAAK,KAAK,YAAY,MAAM;AACxC,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC;AAC7E,QAAI,EAAE,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,uBAAuB,EAAE,UAAU,EAAE,QAAQ,KAAK,KAAK,QAAQ,EAAE,IAAI,EAAE,GAAG;AACvH,WAAO,EAAE,IAAI,MAAM,SAAS,EAAE,UAAU,yBAAyB,KAAK,EAAE;AAAA,EAC1E;AACF;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/targets/docker.ts
|
|
6
|
+
var dockerTarget = {
|
|
7
|
+
name: "docker",
|
|
8
|
+
async preflight(ctx) {
|
|
9
|
+
const v = await ctx.spawn.run("docker", ["version", "--format", "{{.Server.Version}}"], { stdio: "capture" }).catch(() => {
|
|
10
|
+
throw new DeployError("docker not found \u2014 install Docker to use the docker target");
|
|
11
|
+
});
|
|
12
|
+
if (v.code !== 0) {
|
|
13
|
+
throw new DeployError("Docker is installed but the daemon is not reachable \u2014 start Docker and retry");
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
async package(ctx) {
|
|
17
|
+
await ctx.codegen();
|
|
18
|
+
},
|
|
19
|
+
async push(ctx) {
|
|
20
|
+
const r = await ctx.spawn.run("docker", ["compose", "up", "-d", "--build"], { cwd: ctx.cwd, stdio: "inherit" });
|
|
21
|
+
if (r.code !== 0) return { ok: false, error: `docker compose up failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };
|
|
22
|
+
return { ok: true, detail: "container up (docker compose)" };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
dockerTarget
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=chunk-NX3QN6QD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/targets/docker.ts"],"sourcesContent":["import type { DeployTarget, DeployContext, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\n\nexport const dockerTarget: DeployTarget = {\n name: \"docker\",\n async preflight(ctx) {\n const v = await ctx.spawn.run(\"docker\", [\"version\", \"--format\", \"{{.Server.Version}}\"], { stdio: \"capture\" }).catch(() => {\n throw new DeployError(\"docker not found — install Docker to use the docker target\");\n });\n if (v.code !== 0) {\n throw new DeployError(\"Docker is installed but the daemon is not reachable — start Docker and retry\");\n }\n },\n async package(ctx) {\n // The image builds from the repo's Dockerfile/compose at push time (`--build`); refresh codegen\n // so the baked functions directory's `_generated` matches the functions being deployed.\n await ctx.codegen();\n },\n async push(ctx): Promise<DeployResult> {\n const r = await ctx.spawn.run(\"docker\", [\"compose\", \"up\", \"-d\", \"--build\"], { cwd: ctx.cwd, stdio: \"inherit\" });\n if (r.code !== 0) return { ok: false, error: `docker compose up failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };\n return { ok: true, detail: \"container up (docker compose)\" };\n },\n};\n"],"mappings":";;;;;AAGO,IAAM,eAA6B;AAAA,EACxC,MAAM;AAAA,EACN,MAAM,UAAU,KAAK;AACnB,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,YAAY,qBAAqB,GAAG,EAAE,OAAO,UAAU,CAAC,EAAE,MAAM,MAAM;AACxH,YAAM,IAAI,YAAY,iEAA4D;AAAA,IACpF,CAAC;AACD,QAAI,EAAE,SAAS,GAAG;AAChB,YAAM,IAAI,YAAY,mFAA8E;AAAA,IACtG;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,KAAK;AAGjB,UAAM,IAAI,QAAQ;AAAA,EACpB;AAAA,EACA,MAAM,KAAK,KAA4B;AACrC,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,UAAU,CAAC,WAAW,MAAM,MAAM,SAAS,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC;AAC9G,QAAI,EAAE,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,8BAA8B,EAAE,UAAU,EAAE,QAAQ,KAAK,KAAK,QAAQ,EAAE,IAAI,EAAE,GAAG;AAC9H,WAAO,EAAE,IAAI,MAAM,QAAQ,gCAAgC;AAAA,EAC7D;AACF;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeployError
|
|
3
|
+
} from "./chunk-ILHJYAS6.js";
|
|
4
|
+
|
|
5
|
+
// src/targets/aws.ts
|
|
6
|
+
var awsTarget = {
|
|
7
|
+
name: "aws",
|
|
8
|
+
async preflight(ctx) {
|
|
9
|
+
const v = await ctx.spawn.run("aws", ["--version"], { cwd: ctx.cwd, stdio: "capture" }).catch(() => {
|
|
10
|
+
throw new DeployError("aws CLI not found \u2014 install the AWS CLI (https://docs.aws.amazon.com/cli/) and retry");
|
|
11
|
+
});
|
|
12
|
+
if (v.code !== 0) throw new DeployError("aws CLI not found \u2014 install the AWS CLI and retry");
|
|
13
|
+
const serviceArn = ctx.target.settings.serviceArn == null ? void 0 : String(ctx.target.settings.serviceArn);
|
|
14
|
+
if (!serviceArn) {
|
|
15
|
+
throw new DeployError("aws target needs a serviceArn \u2014 set deploy.targets.<name> settings.serviceArn to the App Runner service ARN");
|
|
16
|
+
}
|
|
17
|
+
if (!ctx.interactive && !process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE) {
|
|
18
|
+
throw new DeployError("AWS_ACCESS_KEY_ID (or AWS_PROFILE) is required for non-interactive (CI) deploy");
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
async package(ctx) {
|
|
22
|
+
await ctx.codegen();
|
|
23
|
+
},
|
|
24
|
+
async push(ctx) {
|
|
25
|
+
const serviceArn = String(ctx.target.settings.serviceArn ?? "");
|
|
26
|
+
const args = ["apprunner", "start-deployment", "--service-arn", serviceArn];
|
|
27
|
+
const region = ctx.target.settings.region == null ? void 0 : String(ctx.target.settings.region);
|
|
28
|
+
if (region) args.push("--region", region);
|
|
29
|
+
const r = await ctx.spawn.run("aws", args, { cwd: ctx.cwd, stdio: "capture" });
|
|
30
|
+
if (r.code !== 0) return { ok: false, error: `aws apprunner start-deployment failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };
|
|
31
|
+
return { ok: true, detail: (r.stdout || "deployment started (aws apprunner start-deployment)").trim() };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
awsTarget
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-X4TEO3P3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/targets/aws.ts"],"sourcesContent":["import type { DeployTarget, DeployResult } from \"../types\";\nimport { DeployError } from \"../types\";\n\n/**\n * AWS provision target.\n *\n * v1 assumption: an AWS App Runner service already exists (provisioned out-of-band — console,\n * Terraform, CDK — the same posture as `railwayTarget` assuming a linked Railway service, or\n * `cloudflareTarget` assuming a `wrangler.jsonc`), configured to pull from an ECR repo that a\n * separate CI/build step pushes images to (out of scope here, same as `railwayTarget`'s note that\n * a project deploying to Railway is expected to already have a working Dockerfile). This adapter's\n * `push` triggers App Runner to roll out a fresh deployment of whatever image is currently\n * configured on the service, via `aws apprunner start-deployment --service-arn <arn>`.\n *\n * App Runner was chosen over raw ECS/Fargate because it is AWS's simplest fully-managed\n * \"point at a container image, get a URL\" service — the closest AWS analog to Railway/Fly's deploy\n * model, so the same single-CLI-command push shape applies with no separate task-definition/\n * service/target-group orchestration to reimplement here.\n */\nexport const awsTarget: DeployTarget = {\n name: \"aws\",\n async preflight(ctx) {\n const v = await ctx.spawn.run(\"aws\", [\"--version\"], { cwd: ctx.cwd, stdio: \"capture\" }).catch(() => {\n throw new DeployError(\"aws CLI not found — install the AWS CLI (https://docs.aws.amazon.com/cli/) and retry\");\n });\n if (v.code !== 0) throw new DeployError(\"aws CLI not found — install the AWS CLI and retry\");\n const serviceArn = ctx.target.settings.serviceArn == null ? undefined : String(ctx.target.settings.serviceArn);\n if (!serviceArn) {\n throw new DeployError(\"aws target needs a serviceArn — set deploy.targets.<name> settings.serviceArn to the App Runner service ARN\");\n }\n // AWS_ACCESS_KEY_ID (or an assumed profile via AWS_PROFILE) is the documented non-interactive\n // credential path — required in CI since the aws CLI otherwise falls back to interactive SSO\n // login / device-code prompts.\n if (!ctx.interactive && !process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_PROFILE) {\n throw new DeployError(\"AWS_ACCESS_KEY_ID (or AWS_PROFILE) is required for non-interactive (CI) deploy\");\n }\n },\n async package(ctx) {\n // App Runner deploys the image already configured on the service (an ECR repo pushed to by a\n // separate CI step — see the file's doc comment) — there is nothing to bundle here beyond\n // refreshing codegen so the baked functions directory's `_generated` matches the functions\n // being deployed.\n await ctx.codegen();\n },\n async push(ctx): Promise<DeployResult> {\n const serviceArn = String(ctx.target.settings.serviceArn ?? \"\");\n const args = [\"apprunner\", \"start-deployment\", \"--service-arn\", serviceArn];\n const region = ctx.target.settings.region == null ? undefined : String(ctx.target.settings.region);\n if (region) args.push(\"--region\", region);\n const r = await ctx.spawn.run(\"aws\", args, { cwd: ctx.cwd, stdio: \"capture\" });\n if (r.code !== 0) return { ok: false, error: `aws apprunner start-deployment failed: ${(r.stderr || r.stdout).trim() || `exit ${r.code}`}` };\n return { ok: true, detail: (r.stdout || \"deployment started (aws apprunner start-deployment)\").trim() };\n },\n};\n"],"mappings":";;;;;AAmBO,IAAM,YAA0B;AAAA,EACrC,MAAM;AAAA,EACN,MAAM,UAAU,KAAK;AACnB,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,CAAC,WAAW,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC,EAAE,MAAM,MAAM;AAClG,YAAM,IAAI,YAAY,2FAAsF;AAAA,IAC9G,CAAC;AACD,QAAI,EAAE,SAAS,EAAG,OAAM,IAAI,YAAY,wDAAmD;AAC3F,UAAM,aAAa,IAAI,OAAO,SAAS,cAAc,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,UAAU;AAC7G,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,YAAY,kHAA6G;AAAA,IACrI;AAIA,QAAI,CAAC,IAAI,eAAe,CAAC,QAAQ,IAAI,qBAAqB,CAAC,QAAQ,IAAI,aAAa;AAClF,YAAM,IAAI,YAAY,gFAAgF;AAAA,IACxG;AAAA,EACF;AAAA,EACA,MAAM,QAAQ,KAAK;AAKjB,UAAM,IAAI,QAAQ;AAAA,EACpB;AAAA,EACA,MAAM,KAAK,KAA4B;AACrC,UAAM,aAAa,OAAO,IAAI,OAAO,SAAS,cAAc,EAAE;AAC9D,UAAM,OAAO,CAAC,aAAa,oBAAoB,iBAAiB,UAAU;AAC1E,UAAM,SAAS,IAAI,OAAO,SAAS,UAAU,OAAO,SAAY,OAAO,IAAI,OAAO,SAAS,MAAM;AACjG,QAAI,OAAQ,MAAK,KAAK,YAAY,MAAM;AACxC,UAAM,IAAI,MAAM,IAAI,MAAM,IAAI,OAAO,MAAM,EAAE,KAAK,IAAI,KAAK,OAAO,UAAU,CAAC;AAC7E,QAAI,EAAE,SAAS,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,2CAA2C,EAAE,UAAU,EAAE,QAAQ,KAAK,KAAK,QAAQ,EAAE,IAAI,EAAE,GAAG;AAC3I,WAAO,EAAE,IAAI,MAAM,SAAS,EAAE,UAAU,uDAAuD,KAAK,EAAE;AAAA,EACxG;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { DeployConfig } from '@helipod/component';
|
|
2
|
+
|
|
3
|
+
interface SpawnOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
env?: Record<string, string>;
|
|
6
|
+
stdio?: "inherit" | "capture";
|
|
7
|
+
}
|
|
8
|
+
interface SpawnResult {
|
|
9
|
+
code: number;
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
}
|
|
13
|
+
interface Spawner {
|
|
14
|
+
run(cmd: string, args: string[], opts?: SpawnOptions): Promise<SpawnResult>;
|
|
15
|
+
}
|
|
16
|
+
interface FileTree {
|
|
17
|
+
files: Array<{
|
|
18
|
+
path: string;
|
|
19
|
+
code: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
interface ResolvedTarget {
|
|
23
|
+
targetName: string;
|
|
24
|
+
provider: string;
|
|
25
|
+
env: string;
|
|
26
|
+
settings: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
interface DeployContext {
|
|
29
|
+
cwd: string;
|
|
30
|
+
functionsDir: string;
|
|
31
|
+
env: string;
|
|
32
|
+
target: ResolvedTarget;
|
|
33
|
+
interactive: boolean;
|
|
34
|
+
spawn: Spawner;
|
|
35
|
+
log: (msg: string) => void;
|
|
36
|
+
packageApp: () => Promise<FileTree>;
|
|
37
|
+
codegen: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
interface DeployResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
url?: string;
|
|
42
|
+
detail?: string;
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
interface DeployTarget {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
preflight(ctx: DeployContext): Promise<void>;
|
|
48
|
+
package(ctx: DeployContext): Promise<void>;
|
|
49
|
+
push(ctx: DeployContext): Promise<DeployResult>;
|
|
50
|
+
}
|
|
51
|
+
declare class DeployError extends Error {
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare class NodeSpawner implements Spawner {
|
|
55
|
+
run(cmd: string, args: string[], opts?: SpawnOptions): Promise<SpawnResult>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ResolveInput {
|
|
59
|
+
deploy: DeployConfig | undefined;
|
|
60
|
+
target?: string;
|
|
61
|
+
env?: string;
|
|
62
|
+
inlineUrl?: string;
|
|
63
|
+
}
|
|
64
|
+
declare function resolveDeploy(input: ResolveInput): ResolvedTarget | {
|
|
65
|
+
error: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Lazy dynamic-import dispatch — a provider's adapter module loads only when it is used. */
|
|
69
|
+
declare function loadTarget(provider: string): Promise<DeployTarget>;
|
|
70
|
+
|
|
71
|
+
declare const serveTarget: DeployTarget;
|
|
72
|
+
|
|
73
|
+
declare const cloudflareTarget: DeployTarget;
|
|
74
|
+
|
|
75
|
+
declare const dockerTarget: DeployTarget;
|
|
76
|
+
|
|
77
|
+
declare const railwayTarget: DeployTarget;
|
|
78
|
+
|
|
79
|
+
declare const flyTarget: DeployTarget;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* AWS provision target.
|
|
83
|
+
*
|
|
84
|
+
* v1 assumption: an AWS App Runner service already exists (provisioned out-of-band — console,
|
|
85
|
+
* Terraform, CDK — the same posture as `railwayTarget` assuming a linked Railway service, or
|
|
86
|
+
* `cloudflareTarget` assuming a `wrangler.jsonc`), configured to pull from an ECR repo that a
|
|
87
|
+
* separate CI/build step pushes images to (out of scope here, same as `railwayTarget`'s note that
|
|
88
|
+
* a project deploying to Railway is expected to already have a working Dockerfile). This adapter's
|
|
89
|
+
* `push` triggers App Runner to roll out a fresh deployment of whatever image is currently
|
|
90
|
+
* configured on the service, via `aws apprunner start-deployment --service-arn <arn>`.
|
|
91
|
+
*
|
|
92
|
+
* App Runner was chosen over raw ECS/Fargate because it is AWS's simplest fully-managed
|
|
93
|
+
* "point at a container image, get a URL" service — the closest AWS analog to Railway/Fly's deploy
|
|
94
|
+
* model, so the same single-CLI-command push shape applies with no separate task-definition/
|
|
95
|
+
* service/target-group orchestration to reimplement here.
|
|
96
|
+
*/
|
|
97
|
+
declare const awsTarget: DeployTarget;
|
|
98
|
+
|
|
99
|
+
/** String-aware JSONC → JSON: strips // and block comments and trailing commas, honoring string literals. */
|
|
100
|
+
declare function stripJsonc(text: string): string;
|
|
101
|
+
interface ReconcileOpts {
|
|
102
|
+
needsR2?: boolean;
|
|
103
|
+
r2BucketName?: string;
|
|
104
|
+
}
|
|
105
|
+
interface ReconcileResult {
|
|
106
|
+
config: Record<string, unknown>;
|
|
107
|
+
changed: boolean;
|
|
108
|
+
added: string[];
|
|
109
|
+
}
|
|
110
|
+
/** Additively ensure the Helipod DO bindings/migration/compat flag (+ optional R2) exist. Never
|
|
111
|
+
* drops a user field. Returns a fresh config object; `changed` says whether anything was added. */
|
|
112
|
+
declare function reconcileWrangler(input: Record<string, unknown>, opts: ReconcileOpts): ReconcileResult;
|
|
113
|
+
|
|
114
|
+
/** Lowercase-hex sha256 over the utf8 code string. Identical on client and server — never a re-transpile. */
|
|
115
|
+
declare function sha256Hex(code: string): string;
|
|
116
|
+
interface DeltaPush {
|
|
117
|
+
changed: Array<{
|
|
118
|
+
path: string;
|
|
119
|
+
code: string;
|
|
120
|
+
}>;
|
|
121
|
+
unchanged: Array<{
|
|
122
|
+
path: string;
|
|
123
|
+
sha256: string;
|
|
124
|
+
}>;
|
|
125
|
+
}
|
|
126
|
+
/** Partition local files against the server's current per-path hashes. A file is `unchanged` iff the
|
|
127
|
+
* server has the same path with an equal sha256; otherwise `changed` (new or modified). A path the
|
|
128
|
+
* server has but the local tree lacks appears in neither list (deletion by omission). */
|
|
129
|
+
declare function partitionModules(local: FileTree, remoteHashes: Record<string, string>): DeltaPush;
|
|
130
|
+
|
|
131
|
+
export { type DeltaPush, type DeployContext, DeployError, type DeployResult, type DeployTarget, type FileTree, NodeSpawner, type ReconcileOpts, type ReconcileResult, type ResolveInput, type ResolvedTarget, type SpawnOptions, type SpawnResult, type Spawner, awsTarget, cloudflareTarget, dockerTarget, flyTarget, loadTarget, partitionModules, railwayTarget, reconcileWrangler, resolveDeploy, serveTarget, sha256Hex, stripJsonc };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
partitionModules,
|
|
3
|
+
serveTarget,
|
|
4
|
+
sha256Hex
|
|
5
|
+
} from "./chunk-HJGPCCBB.js";
|
|
6
|
+
import {
|
|
7
|
+
cloudflareTarget,
|
|
8
|
+
reconcileWrangler,
|
|
9
|
+
stripJsonc
|
|
10
|
+
} from "./chunk-5L44HV6S.js";
|
|
11
|
+
import {
|
|
12
|
+
dockerTarget
|
|
13
|
+
} from "./chunk-NX3QN6QD.js";
|
|
14
|
+
import {
|
|
15
|
+
railwayTarget
|
|
16
|
+
} from "./chunk-GQDT3CFX.js";
|
|
17
|
+
import {
|
|
18
|
+
flyTarget
|
|
19
|
+
} from "./chunk-MDIPGR5M.js";
|
|
20
|
+
import {
|
|
21
|
+
awsTarget
|
|
22
|
+
} from "./chunk-X4TEO3P3.js";
|
|
23
|
+
import {
|
|
24
|
+
DeployError
|
|
25
|
+
} from "./chunk-ILHJYAS6.js";
|
|
26
|
+
|
|
27
|
+
// src/spawner.ts
|
|
28
|
+
import { spawn } from "child_process";
|
|
29
|
+
var NodeSpawner = class {
|
|
30
|
+
run(cmd, args, opts = {}) {
|
|
31
|
+
const capture = opts.stdio === "capture";
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const child = spawn(cmd, args, {
|
|
34
|
+
cwd: opts.cwd,
|
|
35
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
36
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit"
|
|
37
|
+
});
|
|
38
|
+
let stdout = "";
|
|
39
|
+
let stderr = "";
|
|
40
|
+
child.stdout?.on("data", (d) => stdout += d.toString());
|
|
41
|
+
child.stderr?.on("data", (d) => stderr += d.toString());
|
|
42
|
+
child.on("error", (e) => reject(e));
|
|
43
|
+
child.on("close", (code) => resolve({ code: code ?? 0, stdout, stderr }));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/resolve.ts
|
|
49
|
+
function resolveDeploy(input) {
|
|
50
|
+
const env = input.env ?? "production";
|
|
51
|
+
const targetName = input.target ?? input.deploy?.defaultTarget ?? "serve";
|
|
52
|
+
const targets = input.deploy?.targets ?? {};
|
|
53
|
+
let cfg = targets[targetName];
|
|
54
|
+
if (!cfg) {
|
|
55
|
+
if (targetName === "serve") {
|
|
56
|
+
cfg = { provider: "serve" };
|
|
57
|
+
} else {
|
|
58
|
+
return { error: `unknown deploy target "${targetName}" \u2014 add it to helipod.config.ts deploy.targets` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const { provider, environments, ...shared } = cfg;
|
|
62
|
+
const envOverride = environments?.[env] ?? {};
|
|
63
|
+
const settings = { ...shared, ...envOverride };
|
|
64
|
+
if (input.inlineUrl) settings.url = input.inlineUrl;
|
|
65
|
+
return { targetName, provider: String(provider), env, settings };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/registry.ts
|
|
69
|
+
async function loadTarget(provider) {
|
|
70
|
+
switch (provider) {
|
|
71
|
+
case "serve":
|
|
72
|
+
return (await import("./serve-OZUXWAEN.js")).serveTarget;
|
|
73
|
+
case "cloudflare":
|
|
74
|
+
return (await import("./cloudflare-ZKGFMLKX.js")).cloudflareTarget;
|
|
75
|
+
case "docker":
|
|
76
|
+
return (await import("./docker-VADWCIE4.js")).dockerTarget;
|
|
77
|
+
case "railway":
|
|
78
|
+
return (await import("./railway-3KHXAEF2.js")).railwayTarget;
|
|
79
|
+
case "fly":
|
|
80
|
+
return (await import("./fly-S66SPZSA.js")).flyTarget;
|
|
81
|
+
case "aws":
|
|
82
|
+
return (await import("./aws-IJSDMGHO.js")).awsTarget;
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`no deploy adapter for provider "${provider}" (v1 supports: serve, cloudflare, docker, railway, fly, aws)`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
DeployError,
|
|
89
|
+
NodeSpawner,
|
|
90
|
+
awsTarget,
|
|
91
|
+
cloudflareTarget,
|
|
92
|
+
dockerTarget,
|
|
93
|
+
flyTarget,
|
|
94
|
+
loadTarget,
|
|
95
|
+
partitionModules,
|
|
96
|
+
railwayTarget,
|
|
97
|
+
reconcileWrangler,
|
|
98
|
+
resolveDeploy,
|
|
99
|
+
serveTarget,
|
|
100
|
+
sha256Hex,
|
|
101
|
+
stripJsonc
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spawner.ts","../src/resolve.ts","../src/registry.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport type { Spawner, SpawnOptions, SpawnResult } from \"./types\";\n\nexport class NodeSpawner implements Spawner {\n run(cmd: string, args: string[], opts: SpawnOptions = {}): Promise<SpawnResult> {\n const capture = opts.stdio === \"capture\";\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, {\n cwd: opts.cwd,\n env: opts.env ? { ...process.env, ...opts.env } : process.env,\n stdio: capture ? [\"ignore\", \"pipe\", \"pipe\"] : \"inherit\",\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (d) => (stdout += d.toString()));\n child.stderr?.on(\"data\", (d) => (stderr += d.toString()));\n child.on(\"error\", (e) => reject(e)); // e.g. ENOENT when the CLI is not installed\n child.on(\"close\", (code) => resolve({ code: code ?? 0, stdout, stderr }));\n });\n }\n}\n","import type { DeployConfig } from \"@helipod/component\";\nimport type { ResolvedTarget } from \"./types\";\n\nexport interface ResolveInput {\n deploy: DeployConfig | undefined;\n target?: string;\n env?: string;\n inlineUrl?: string;\n}\n\nexport function resolveDeploy(input: ResolveInput): ResolvedTarget | { error: string } {\n const env = input.env ?? \"production\";\n const targetName = input.target ?? input.deploy?.defaultTarget ?? \"serve\";\n const targets = input.deploy?.targets ?? {};\n let cfg = targets[targetName];\n\n if (!cfg) {\n if (targetName === \"serve\") {\n cfg = { provider: \"serve\" }; // synthesized default serve target (back-compat)\n } else {\n return { error: `unknown deploy target \"${targetName}\" — add it to helipod.config.ts deploy.targets` };\n }\n }\n\n const { provider, environments, ...shared } = cfg;\n const envOverride = environments?.[env] ?? {};\n const settings: Record<string, unknown> = { ...shared, ...envOverride };\n if (input.inlineUrl) settings.url = input.inlineUrl;\n\n return { targetName, provider: String(provider), env, settings };\n}\n","import type { DeployTarget } from \"./types\";\n\n/** Lazy dynamic-import dispatch — a provider's adapter module loads only when it is used. */\nexport async function loadTarget(provider: string): Promise<DeployTarget> {\n switch (provider) {\n case \"serve\": return (await import(\"./targets/serve\")).serveTarget;\n case \"cloudflare\": return (await import(\"./targets/cloudflare\")).cloudflareTarget;\n case \"docker\": return (await import(\"./targets/docker\")).dockerTarget;\n case \"railway\": return (await import(\"./targets/railway\")).railwayTarget;\n case \"fly\": return (await import(\"./targets/fly\")).flyTarget;\n case \"aws\": return (await import(\"./targets/aws\")).awsTarget;\n default: throw new Error(`no deploy adapter for provider \"${provider}\" (v1 supports: serve, cloudflare, docker, railway, fly, aws)`);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AAGf,IAAM,cAAN,MAAqC;AAAA,EAC1C,IAAI,KAAa,MAAgB,OAAqB,CAAC,GAAyB;AAC9E,UAAM,UAAU,KAAK,UAAU;AAC/B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,QAAQ,MAAM,KAAK,MAAM;AAAA,QAC7B,KAAK,KAAK;AAAA,QACV,KAAK,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,IAAI,IAAI,QAAQ;AAAA,QAC1D,OAAO,UAAU,CAAC,UAAU,QAAQ,MAAM,IAAI;AAAA,MAChD,CAAC;AACD,UAAI,SAAS;AACb,UAAI,SAAS;AACb,YAAM,QAAQ,GAAG,QAAQ,CAAC,MAAO,UAAU,EAAE,SAAS,CAAE;AACxD,YAAM,QAAQ,GAAG,QAAQ,CAAC,MAAO,UAAU,EAAE,SAAS,CAAE;AACxD,YAAM,GAAG,SAAS,CAAC,MAAM,OAAO,CAAC,CAAC;AAClC,YAAM,GAAG,SAAS,CAAC,SAAS,QAAQ,EAAE,MAAM,QAAQ,GAAG,QAAQ,OAAO,CAAC,CAAC;AAAA,IAC1E,CAAC;AAAA,EACH;AACF;;;ACVO,SAAS,cAAc,OAAyD;AACrF,QAAM,MAAM,MAAM,OAAO;AACzB,QAAM,aAAa,MAAM,UAAU,MAAM,QAAQ,iBAAiB;AAClE,QAAM,UAAU,MAAM,QAAQ,WAAW,CAAC;AAC1C,MAAI,MAAM,QAAQ,UAAU;AAE5B,MAAI,CAAC,KAAK;AACR,QAAI,eAAe,SAAS;AAC1B,YAAM,EAAE,UAAU,QAAQ;AAAA,IAC5B,OAAO;AACL,aAAO,EAAE,OAAO,0BAA0B,UAAU,sDAAiD;AAAA,IACvG;AAAA,EACF;AAEA,QAAM,EAAE,UAAU,cAAc,GAAG,OAAO,IAAI;AAC9C,QAAM,cAAc,eAAe,GAAG,KAAK,CAAC;AAC5C,QAAM,WAAoC,EAAE,GAAG,QAAQ,GAAG,YAAY;AACtE,MAAI,MAAM,UAAW,UAAS,MAAM,MAAM;AAE1C,SAAO,EAAE,YAAY,UAAU,OAAO,QAAQ,GAAG,KAAK,SAAS;AACjE;;;AC3BA,eAAsB,WAAW,UAAyC;AACxE,UAAQ,UAAU;AAAA,IAChB,KAAK;AAAS,cAAQ,MAAM,OAAO,qBAAiB,GAAG;AAAA,IACvD,KAAK;AAAc,cAAQ,MAAM,OAAO,0BAAsB,GAAG;AAAA,IACjE,KAAK;AAAU,cAAQ,MAAM,OAAO,sBAAkB,GAAG;AAAA,IACzD,KAAK;AAAW,cAAQ,MAAM,OAAO,uBAAmB,GAAG;AAAA,IAC3D,KAAK;AAAO,cAAQ,MAAM,OAAO,mBAAe,GAAG;AAAA,IACnD,KAAK;AAAO,cAAQ,MAAM,OAAO,mBAAe,GAAG;AAAA,IACnD;AAAS,YAAM,IAAI,MAAM,mCAAmC,QAAQ,+DAA+D;AAAA,EACrI;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helipod/deploy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"test": "vitest run --exclude 'test/*-e2e.test.ts'",
|
|
16
|
+
"test:e2e": "vitest run test/*-e2e.test.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"clean": "rm -rf dist .turbo"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@helipod/component": "0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.10.5",
|
|
25
|
+
"tsup": "^8.3.5",
|
|
26
|
+
"typescript": "^5.7.2",
|
|
27
|
+
"vitest": "^2.1.8"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/helipod-sh/helipod.git",
|
|
39
|
+
"directory": "packages/deploy"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/helipod-sh/helipod"
|
|
42
|
+
}
|