@cardelli/ambit 0.1.4 → 0.2.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/esm/cli/commands/create/index.d.ts +2 -0
- package/esm/cli/commands/create/index.d.ts.map +1 -0
- package/esm/cli/commands/create/index.js +292 -0
- package/esm/cli/commands/create/machine.d.ts +33 -0
- package/esm/cli/commands/create/machine.d.ts.map +1 -0
- package/esm/cli/commands/create/machine.js +162 -0
- package/esm/cli/commands/deploy/index.d.ts +2 -0
- package/esm/cli/commands/deploy/index.d.ts.map +1 -0
- package/esm/cli/commands/deploy/index.js +290 -0
- package/esm/cli/commands/deploy/machine.d.ts +52 -0
- package/esm/cli/commands/deploy/machine.d.ts.map +1 -0
- package/esm/cli/commands/deploy/machine.js +116 -0
- package/esm/cli/commands/deploy/modes.d.ts +18 -0
- package/esm/cli/commands/deploy/modes.d.ts.map +1 -0
- package/esm/cli/commands/deploy/modes.js +152 -0
- package/esm/cli/commands/destroy/app.d.ts +2 -0
- package/esm/cli/commands/destroy/app.d.ts.map +1 -0
- package/esm/cli/commands/destroy/app.js +173 -0
- package/esm/cli/commands/destroy/index.d.ts +2 -0
- package/esm/cli/commands/destroy/index.d.ts.map +1 -0
- package/esm/cli/commands/destroy/index.js +63 -0
- package/esm/cli/commands/destroy/network.d.ts +2 -0
- package/esm/cli/commands/destroy/network.d.ts.map +1 -0
- package/esm/cli/commands/destroy/network.js +210 -0
- package/esm/cli/commands/doctor.d.ts.map +1 -0
- package/esm/cli/commands/doctor.js +295 -0
- package/esm/{src/cli → cli}/commands/list.d.ts.map +1 -1
- package/esm/{src/cli → cli}/commands/list.js +39 -54
- package/esm/cli/commands/status.d.ts.map +1 -0
- package/esm/cli/commands/status.js +331 -0
- package/esm/cli/mod.d.ts.map +1 -0
- package/esm/{src/cli → cli}/mod.js +4 -4
- package/esm/deno.d.ts +4 -18
- package/esm/deno.js +5 -19
- package/esm/deps/jsr.io/@std/path/1.1.4/constants.d.ts +1 -1
- package/esm/deps/jsr.io/@zod/zod/4.3.6/src/v4/core/json-schema-generator.d.ts +1 -1
- package/esm/lib/args.d.ts +11 -0
- package/esm/lib/args.d.ts.map +1 -0
- package/esm/lib/args.js +28 -0
- package/esm/lib/cli.d.ts +0 -2
- package/esm/lib/cli.d.ts.map +1 -1
- package/esm/lib/cli.js +41 -27
- package/esm/lib/command.d.ts +21 -49
- package/esm/lib/command.d.ts.map +1 -1
- package/esm/lib/command.js +55 -95
- package/esm/lib/machine.d.ts +11 -0
- package/esm/lib/machine.d.ts.map +1 -0
- package/esm/lib/machine.js +15 -0
- package/esm/lib/output.d.ts +3 -2
- package/esm/lib/output.d.ts.map +1 -1
- package/esm/lib/output.js +25 -11
- package/esm/lib/result.d.ts +18 -7
- package/esm/lib/result.d.ts.map +1 -1
- package/esm/lib/result.js +46 -1
- package/esm/main.d.ts +6 -6
- package/esm/main.d.ts.map +1 -1
- package/esm/main.js +7 -9
- package/esm/providers/fly.d.ts +81 -0
- package/esm/providers/fly.d.ts.map +1 -0
- package/esm/providers/fly.js +372 -0
- package/esm/providers/tailscale.d.ts +31 -0
- package/esm/providers/tailscale.d.ts.map +1 -0
- package/esm/providers/tailscale.js +150 -0
- package/esm/{src/schemas → schemas}/fly.d.ts +1 -11
- package/esm/schemas/fly.d.ts.map +1 -0
- package/esm/{src/schemas → schemas}/fly.js +14 -56
- package/esm/{src/schemas → schemas}/tailscale.d.ts +1 -2
- package/esm/schemas/tailscale.d.ts.map +1 -0
- package/esm/{src/schemas → schemas}/tailscale.js +2 -3
- package/esm/src/{docker/router → router}/Dockerfile +0 -11
- package/esm/src/router/start.sh +101 -0
- package/esm/util/constants.d.ts +13 -0
- package/esm/util/constants.d.ts.map +1 -0
- package/esm/util/constants.js +34 -0
- package/esm/{src → util}/credentials.d.ts +0 -1
- package/esm/util/credentials.d.ts.map +1 -0
- package/esm/{src → util}/credentials.js +3 -5
- package/esm/{src → util}/discovery.d.ts +20 -4
- package/esm/util/discovery.d.ts.map +1 -0
- package/esm/{src → util}/discovery.js +38 -15
- package/esm/util/fly-transforms.d.ts +27 -0
- package/esm/util/fly-transforms.d.ts.map +1 -0
- package/esm/util/fly-transforms.js +87 -0
- package/esm/{src → util}/guard.d.ts +1 -2
- package/esm/util/guard.d.ts.map +1 -0
- package/esm/{src → util}/guard.js +27 -27
- package/esm/util/naming.d.ts +5 -0
- package/esm/util/naming.d.ts.map +1 -0
- package/esm/util/naming.js +12 -0
- package/esm/{src → util}/resolve.d.ts +2 -3
- package/esm/util/resolve.d.ts.map +1 -0
- package/esm/{src → util}/resolve.js +1 -2
- package/esm/util/session.d.ts +16 -0
- package/esm/util/session.d.ts.map +1 -0
- package/esm/util/session.js +19 -0
- package/esm/util/tailscale-local.d.ts +13 -0
- package/esm/util/tailscale-local.d.ts.map +1 -0
- package/esm/util/tailscale-local.js +63 -0
- package/esm/{src → util}/template.d.ts +2 -4
- package/esm/util/template.d.ts.map +1 -0
- package/esm/{src → util}/template.js +14 -17
- package/package.json +1 -43
- package/esm/lib/paths.d.ts +0 -3
- package/esm/lib/paths.d.ts.map +0 -1
- package/esm/lib/paths.js +0 -5
- package/esm/src/cli/commands/create.d.ts +0 -2
- package/esm/src/cli/commands/create.d.ts.map +0 -1
- package/esm/src/cli/commands/create.js +0 -294
- package/esm/src/cli/commands/deploy.d.ts +0 -2
- package/esm/src/cli/commands/deploy.d.ts.map +0 -1
- package/esm/src/cli/commands/deploy.js +0 -426
- package/esm/src/cli/commands/destroy.d.ts +0 -2
- package/esm/src/cli/commands/destroy.d.ts.map +0 -1
- package/esm/src/cli/commands/destroy.js +0 -340
- package/esm/src/cli/commands/doctor.d.ts.map +0 -1
- package/esm/src/cli/commands/doctor.js +0 -141
- package/esm/src/cli/commands/status.d.ts.map +0 -1
- package/esm/src/cli/commands/status.js +0 -152
- package/esm/src/cli/mod.d.ts.map +0 -1
- package/esm/src/credentials.d.ts.map +0 -1
- package/esm/src/discovery.d.ts.map +0 -1
- package/esm/src/docker/router/start.sh +0 -146
- package/esm/src/guard.d.ts.map +0 -1
- package/esm/src/providers/fly.d.ts +0 -70
- package/esm/src/providers/fly.d.ts.map +0 -1
- package/esm/src/providers/fly.js +0 -411
- package/esm/src/providers/tailscale.d.ts +0 -31
- package/esm/src/providers/tailscale.d.ts.map +0 -1
- package/esm/src/providers/tailscale.js +0 -195
- package/esm/src/resolve.d.ts.map +0 -1
- package/esm/src/schemas/config.d.ts +0 -5
- package/esm/src/schemas/config.d.ts.map +0 -1
- package/esm/src/schemas/config.js +0 -22
- package/esm/src/schemas/fly.d.ts.map +0 -1
- package/esm/src/schemas/tailscale.d.ts.map +0 -1
- package/esm/src/template.d.ts.map +0 -1
- /package/esm/{src/cli → cli}/commands/doctor.d.ts +0 -0
- /package/esm/{src/cli → cli}/commands/list.d.ts +0 -0
- /package/esm/{src/cli → cli}/commands/status.d.ts +0 -0
- /package/esm/{src/cli → cli}/mod.d.ts +0 -0
- /package/esm/src/{docker/router → router}/fly.toml +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Fly Transforms — Pure Data Transformations
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Machine State Mapping
|
|
6
|
+
// =============================================================================
|
|
7
|
+
/**
|
|
8
|
+
* Map Fly machine state to internal state.
|
|
9
|
+
* Fly states: created, starting, started, stopping, stopped, destroying, destroyed
|
|
10
|
+
*/
|
|
11
|
+
export const mapFlyMachineState = (flyState) => {
|
|
12
|
+
switch (flyState.toLowerCase()) {
|
|
13
|
+
case "started":
|
|
14
|
+
return "running";
|
|
15
|
+
case "stopped":
|
|
16
|
+
case "suspended":
|
|
17
|
+
return "frozen";
|
|
18
|
+
case "created":
|
|
19
|
+
case "starting":
|
|
20
|
+
return "creating";
|
|
21
|
+
case "destroying":
|
|
22
|
+
case "destroyed":
|
|
23
|
+
case "failed":
|
|
24
|
+
return "failed";
|
|
25
|
+
default:
|
|
26
|
+
return "creating";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Machine Size Mapping
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Map Fly guest config to machine size enum.
|
|
34
|
+
*/
|
|
35
|
+
export const mapFlyMachineSize = (guest) => {
|
|
36
|
+
if (!guest)
|
|
37
|
+
return "shared-cpu-1x";
|
|
38
|
+
const cpus = guest.cpus;
|
|
39
|
+
if (cpus >= 4)
|
|
40
|
+
return "shared-cpu-4x";
|
|
41
|
+
if (cpus >= 2)
|
|
42
|
+
return "shared-cpu-2x";
|
|
43
|
+
return "shared-cpu-1x";
|
|
44
|
+
};
|
|
45
|
+
/** Map raw Fly machines to internal MachineResult format. */
|
|
46
|
+
export const mapMachines = (raw) => {
|
|
47
|
+
return raw.map((m) => ({
|
|
48
|
+
id: m.id,
|
|
49
|
+
state: mapFlyMachineState(m.state),
|
|
50
|
+
size: mapFlyMachineSize(m.config?.guest),
|
|
51
|
+
region: m.region,
|
|
52
|
+
privateIp: m.private_ip,
|
|
53
|
+
}));
|
|
54
|
+
};
|
|
55
|
+
export const getSizeConfig = (size) => {
|
|
56
|
+
switch (size) {
|
|
57
|
+
case "shared-cpu-1x":
|
|
58
|
+
return { cpus: 1, memoryMb: 1024 };
|
|
59
|
+
case "shared-cpu-2x":
|
|
60
|
+
return { cpus: 2, memoryMb: 2048 };
|
|
61
|
+
case "shared-cpu-4x":
|
|
62
|
+
return { cpus: 4, memoryMb: 4096 };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Error Detail Extraction
|
|
67
|
+
// =============================================================================
|
|
68
|
+
/**
|
|
69
|
+
* Pull the last non-empty, non-decoration line from fly stderr.
|
|
70
|
+
* Fly often prints progress lines then the actual error at the end.
|
|
71
|
+
*/
|
|
72
|
+
export const extractErrorDetail = (stderr) => {
|
|
73
|
+
const lines = stderr
|
|
74
|
+
.split("\n")
|
|
75
|
+
.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").trim())
|
|
76
|
+
.filter((l) => l.length > 0 && !l.startsWith("-->") && l !== "Error");
|
|
77
|
+
return lines[lines.length - 1] ?? "unknown error";
|
|
78
|
+
};
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Subnet Extraction
|
|
81
|
+
// =============================================================================
|
|
82
|
+
export const extractSubnet = (privateIp) => {
|
|
83
|
+
// privateIp format: fdaa:X:XXXX::Y
|
|
84
|
+
// Extract first 3 hextets and append ::/48
|
|
85
|
+
const parts = privateIp.split(":");
|
|
86
|
+
return `${parts[0]}:${parts[1]}:${parts[2]}::/48`;
|
|
87
|
+
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import type { FlyProvider } from "./providers/fly.js";
|
|
1
|
+
import type { FlyProvider } from "../providers/fly.js";
|
|
3
2
|
/**
|
|
4
3
|
* Returns true if the given name is a public TLD (case-insensitive).
|
|
5
4
|
* Used to prevent creating networks that would shadow real DNS.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/util/guard.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAsLvD;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,OACN,CAAC;AAMtC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAOjD;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA8ChE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC,CA4F5B"}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// - auditDeploy: post-deploy check that releases public IPs and reports
|
|
10
10
|
//
|
|
11
11
|
// =============================================================================
|
|
12
|
-
import "../_dnt.polyfills.js";
|
|
13
12
|
import { parse as parseToml } from "../deps/jsr.io/@std/toml/1.0.11/mod.js";
|
|
13
|
+
import { ROUTER_APP_PREFIX } from "./constants.js";
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// Public TLD Blocklist (source: https://data.iana.org/TLD/tlds-alpha-by-domain.txt)
|
|
16
16
|
// =============================================================================
|
|
@@ -201,7 +201,7 @@ export const isPublicTld = (name) => PUBLIC_TLDS.has(name.toLowerCase());
|
|
|
201
201
|
* Workload apps must not start with the ambit- prefix.
|
|
202
202
|
*/
|
|
203
203
|
export function assertNotRouter(app) {
|
|
204
|
-
if (app.startsWith(
|
|
204
|
+
if (app.startsWith(ROUTER_APP_PREFIX)) {
|
|
205
205
|
throw new Error("Cannot deploy ambit infrastructure apps (ambit-* prefix). " +
|
|
206
206
|
"Use 'ambit create' to manage routers.");
|
|
207
207
|
}
|
|
@@ -235,23 +235,21 @@ export function scanFlyToml(tomlContent) {
|
|
|
235
235
|
result.errors.push("http_service.force_https is enabled. " +
|
|
236
236
|
"This implies public HTTPS which is incompatible with Flycast-only deployment.");
|
|
237
237
|
}
|
|
238
|
-
// Check [[services]] for TLS on 443
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
for (const svc of services) {
|
|
243
|
-
const ports = svc.ports;
|
|
238
|
+
// Check [[services]] for TLS handler on port 443
|
|
239
|
+
if (Array.isArray(parsed.services)) {
|
|
240
|
+
const hasTlsHandler = parsed.services.some((svc) => {
|
|
241
|
+
const ports = svc?.ports;
|
|
244
242
|
if (!Array.isArray(ports))
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
return false;
|
|
244
|
+
return ports.some((p) => {
|
|
245
|
+
const port = p;
|
|
246
|
+
const handlers = port?.handlers;
|
|
247
|
+
return Array.isArray(handlers) && handlers.includes("tls") &&
|
|
248
|
+
port?.port === 443;
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
if (hasTlsHandler) {
|
|
252
|
+
result.errors.push("TLS Handler on Port 443 Is Incompatible with Flycast-Only Deployment.");
|
|
255
253
|
}
|
|
256
254
|
}
|
|
257
255
|
return result;
|
|
@@ -271,7 +269,7 @@ export async function auditDeploy(fly, app, targetNetwork) {
|
|
|
271
269
|
warnings: [],
|
|
272
270
|
};
|
|
273
271
|
// Phase 1: Check and clean IPs — only keep Flycast on the target network
|
|
274
|
-
const ips = await fly.
|
|
272
|
+
const ips = await fly.ips.list(app);
|
|
275
273
|
for (const ip of ips) {
|
|
276
274
|
const ipNetwork = ip.Network?.Name || "default";
|
|
277
275
|
if (ip.Type === "private_v6" && ipNetwork === targetNetwork) {
|
|
@@ -283,23 +281,23 @@ export async function auditDeploy(fly, app, targetNetwork) {
|
|
|
283
281
|
}
|
|
284
282
|
else if (ip.Type === "private_v6") {
|
|
285
283
|
// Flycast on wrong network (e.g. default from --flycast flag) — release
|
|
286
|
-
await fly.
|
|
284
|
+
await fly.ips.release(app, ip.Address);
|
|
287
285
|
result.warnings.push(`Released Flycast IP ${ip.Address} on wrong network '${ipNetwork}' (expected '${targetNetwork}')`);
|
|
288
286
|
}
|
|
289
287
|
else {
|
|
290
288
|
// Public IP — release immediately
|
|
291
|
-
await fly.
|
|
289
|
+
await fly.ips.release(app, ip.Address);
|
|
292
290
|
result.public_ips_released++;
|
|
293
291
|
}
|
|
294
292
|
}
|
|
295
293
|
// Phase 2: Remove auto-generated .fly.dev certs to keep the app fully private
|
|
296
|
-
const certs = await fly.
|
|
294
|
+
const certs = await fly.certs.list(app);
|
|
297
295
|
for (const hostname of certs) {
|
|
298
|
-
await fly.
|
|
296
|
+
await fly.certs.remove(app, hostname);
|
|
299
297
|
result.certs_removed++;
|
|
300
298
|
}
|
|
301
299
|
// Phase 3: Inspect merged config for dangerous patterns
|
|
302
|
-
const config = await fly.getConfig(app);
|
|
300
|
+
const config = await fly.apps.getConfig(app);
|
|
303
301
|
if (config) {
|
|
304
302
|
const services = config.services;
|
|
305
303
|
if (Array.isArray(services) && services.length > 0) {
|
|
@@ -320,10 +318,12 @@ export async function auditDeploy(fly, app, targetNetwork) {
|
|
|
320
318
|
// Phase 4: Verify Flycast allocation on target network
|
|
321
319
|
const hasTargetFlycast = result.flycast_allocations.some((a) => a.network === targetNetwork);
|
|
322
320
|
if (!hasTargetFlycast) {
|
|
323
|
-
// Allocate on the target network
|
|
324
|
-
await fly.
|
|
321
|
+
// Allocate on the target network, then re-list to get the actual address
|
|
322
|
+
await fly.ips.allocateFlycast(app, targetNetwork);
|
|
323
|
+
const refreshed = await fly.ips.list(app);
|
|
324
|
+
const newIp = refreshed.find((ip) => ip.Type === "private_v6" && ip.Network?.Name === targetNetwork);
|
|
325
325
|
result.flycast_allocations.push({
|
|
326
|
-
address: "
|
|
326
|
+
address: newIp?.Address ?? "allocated",
|
|
327
327
|
network: targetNetwork,
|
|
328
328
|
});
|
|
329
329
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const getRouterAppName: (network: string, randomSuffix: string) => string;
|
|
2
|
+
export declare const getRouterSuffix: (routerAppName: string, network: string) => string;
|
|
3
|
+
export declare const getWorkloadAppName: (name: string, routerId: string) => string;
|
|
4
|
+
export declare const getRouterTag: (network: string) => string;
|
|
5
|
+
//# sourceMappingURL=naming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/util/naming.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAEF,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,eAAe,MAAM,EACrB,SAAS,MAAM,KACd,MAGF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,MAAM,MAAM,EACZ,UAAU,MAAM,KACf,MAEF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,MAAgC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ROUTER_APP_PREFIX } from "./constants.js";
|
|
2
|
+
export const getRouterAppName = (network, randomSuffix) => {
|
|
3
|
+
return `${ROUTER_APP_PREFIX}${network}-${randomSuffix}`;
|
|
4
|
+
};
|
|
5
|
+
export const getRouterSuffix = (routerAppName, network) => {
|
|
6
|
+
const prefix = `${ROUTER_APP_PREFIX}${network}-`;
|
|
7
|
+
return routerAppName.slice(prefix.length);
|
|
8
|
+
};
|
|
9
|
+
export const getWorkloadAppName = (name, routerId) => {
|
|
10
|
+
return `${name}-${routerId}`;
|
|
11
|
+
};
|
|
12
|
+
export const getRouterTag = (network) => `tag:ambit-${network}`;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import "../_dnt.polyfills.js";
|
|
2
1
|
import type { Output } from "../lib/output.js";
|
|
3
|
-
import type { FlyProvider } from "
|
|
2
|
+
import type { FlyProvider } from "../providers/fly.js";
|
|
4
3
|
/**
|
|
5
4
|
* Resolve Fly.io organization: --org flag → single org auto-select → prompt.
|
|
6
5
|
*/
|
|
7
6
|
export declare const resolveOrg: (fly: FlyProvider, args: {
|
|
8
7
|
org?: string;
|
|
9
8
|
json?: boolean;
|
|
10
|
-
}, out: Output<
|
|
9
|
+
}, out: Output<any>) => Promise<string>;
|
|
11
10
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/util/resolve.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMvD;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,KAAK,WAAW,EAChB,MAAM;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EACtC,KAAK,MAAM,CAAC,GAAG,CAAC,KACf,OAAO,CAAC,MAAM,CA6BhB,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
2
|
// Resolve - Org Resolution Helper
|
|
3
3
|
// =============================================================================
|
|
4
|
-
import "../_dnt.polyfills.js";
|
|
5
4
|
import { prompt } from "../lib/cli.js";
|
|
6
5
|
// =============================================================================
|
|
7
6
|
// Resolve Org
|
|
@@ -15,7 +14,7 @@ export const resolveOrg = async (fly, args, out) => {
|
|
|
15
14
|
if (args.json) {
|
|
16
15
|
return out.die("--org Is Required in JSON Mode");
|
|
17
16
|
}
|
|
18
|
-
const orgs = await fly.
|
|
17
|
+
const orgs = await fly.orgs.list();
|
|
19
18
|
const orgSlugs = Object.keys(orgs);
|
|
20
19
|
if (orgSlugs.length === 0) {
|
|
21
20
|
return out.die("No Fly.io Organizations Found");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type FlyProvider } from "../providers/fly.js";
|
|
2
|
+
import { type TailscaleProvider } from "../providers/tailscale.js";
|
|
3
|
+
import type { Output } from "../lib/output.js";
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap the three shared prerequisites every command needs:
|
|
6
|
+
* validates fly CLI + Tailscale key, authenticates with Fly, and resolves org.
|
|
7
|
+
*/
|
|
8
|
+
export declare const initSession: <T extends Record<string, unknown>>(out: Output<T>, opts: {
|
|
9
|
+
json: boolean;
|
|
10
|
+
org?: string;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
fly: FlyProvider;
|
|
13
|
+
tailscale: TailscaleProvider;
|
|
14
|
+
org: string;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/util/session.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,KAAK,MAAM,CAAC,CAAC,CAAC,EACd,MAAM;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KACpC,OAAO,CAAC;IAAE,GAAG,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAOzE,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Session — Shared Prerequisites Initialization
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { createFlyProvider } from "../providers/fly.js";
|
|
5
|
+
import { createTailscaleProvider, } from "../providers/tailscale.js";
|
|
6
|
+
import { checkDependencies } from "./credentials.js";
|
|
7
|
+
import { resolveOrg } from "./resolve.js";
|
|
8
|
+
/**
|
|
9
|
+
* Bootstrap the three shared prerequisites every command needs:
|
|
10
|
+
* validates fly CLI + Tailscale key, authenticates with Fly, and resolves org.
|
|
11
|
+
*/
|
|
12
|
+
export const initSession = async (out, opts) => {
|
|
13
|
+
const { tailscaleKey } = await checkDependencies(out);
|
|
14
|
+
const fly = createFlyProvider();
|
|
15
|
+
await fly.auth.login({ interactive: !opts.json });
|
|
16
|
+
const tailscale = createTailscaleProvider(tailscaleKey);
|
|
17
|
+
const org = await resolveOrg(fly, opts, out);
|
|
18
|
+
return { fly, tailscale, org };
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TailscaleDevice } from "../schemas/tailscale.js";
|
|
2
|
+
import type { TailscaleProvider } from "../providers/tailscale.js";
|
|
3
|
+
export declare const isTailscaleInstalled: () => Promise<boolean>;
|
|
4
|
+
export declare const isAcceptRoutesEnabled: () => Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Enable accept-routes on the local client.
|
|
7
|
+
* Returns true if successful, false if it failed (likely permissions).
|
|
8
|
+
*/
|
|
9
|
+
export declare const enableAcceptRoutes: () => Promise<boolean>;
|
|
10
|
+
export declare const waitForDevice: (provider: TailscaleProvider, hostname: string, timeoutMs?: number) => Promise<TailscaleDevice>;
|
|
11
|
+
export declare const isTagOwnerConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
|
|
12
|
+
export declare const isAutoApproverConfigured: (policy: Record<string, unknown> | null, tag: string) => boolean;
|
|
13
|
+
//# sourceMappingURL=tailscale-local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscale-local.d.ts","sourceRoot":"","sources":["../../src/util/tailscale-local.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAMnE,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,OAAO,CAE5D,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,OAAO,CAM7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,OAAO,CAG1D,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,EAC3B,UAAU,MAAM,EAChB,YAAW,MAAe,KACzB,OAAO,CAAC,eAAe,CAazB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACtC,KAAK,MAAM,KACV,OAgBF,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Tailscale Local — CLI Operations + Pure Policy Checks
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { commandExists, die } from "../lib/cli.js";
|
|
5
|
+
import { runCommand } from "../lib/command.js";
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Local CLI Operations
|
|
8
|
+
// =============================================================================
|
|
9
|
+
export const isTailscaleInstalled = async () => {
|
|
10
|
+
return await commandExists("tailscale");
|
|
11
|
+
};
|
|
12
|
+
export const isAcceptRoutesEnabled = async () => {
|
|
13
|
+
const result = await runCommand(["tailscale", "debug", "prefs"]);
|
|
14
|
+
return result.json().match({
|
|
15
|
+
ok: (prefs) => prefs.RouteAll === true,
|
|
16
|
+
err: () => false,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Enable accept-routes on the local client.
|
|
21
|
+
* Returns true if successful, false if it failed (likely permissions).
|
|
22
|
+
*/
|
|
23
|
+
export const enableAcceptRoutes = async () => {
|
|
24
|
+
const result = await runCommand(["tailscale", "set", "--accept-routes"]);
|
|
25
|
+
return result.ok;
|
|
26
|
+
};
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Wait for Device
|
|
29
|
+
// =============================================================================
|
|
30
|
+
export const waitForDevice = async (provider, hostname, timeoutMs = 120000) => {
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
const pollInterval = 5000;
|
|
33
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
34
|
+
const device = await provider.devices.getByHostname(hostname);
|
|
35
|
+
if (device) {
|
|
36
|
+
return device;
|
|
37
|
+
}
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
39
|
+
}
|
|
40
|
+
return die(`Timeout Waiting for Device '${hostname}'`);
|
|
41
|
+
};
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// Pure Policy Checks
|
|
44
|
+
// =============================================================================
|
|
45
|
+
export const isTagOwnerConfigured = (policy, tag) => {
|
|
46
|
+
if (!policy)
|
|
47
|
+
return false;
|
|
48
|
+
const tagOwners = policy.tagOwners;
|
|
49
|
+
if (!tagOwners)
|
|
50
|
+
return false;
|
|
51
|
+
return tag in tagOwners;
|
|
52
|
+
};
|
|
53
|
+
export const isAutoApproverConfigured = (policy, tag) => {
|
|
54
|
+
if (!policy)
|
|
55
|
+
return false;
|
|
56
|
+
const autoApprovers = policy.autoApprovers;
|
|
57
|
+
if (!autoApprovers)
|
|
58
|
+
return false;
|
|
59
|
+
const routes = autoApprovers.routes;
|
|
60
|
+
if (!routes)
|
|
61
|
+
return false;
|
|
62
|
+
return Object.values(routes).some((approvers) => Array.isArray(approvers) && approvers.includes(tag));
|
|
63
|
+
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
/** Kinds of errors that can occur when fetching a template from GitHub. */
|
|
3
|
-
export type TemplateErrorKind = "NotFound" | "RateLimited" | "HttpError" | "ExtractionFailed" | "PathNotFound" | "PathNotDirectory" | "EmptyArchive" | "NetworkError";
|
|
1
|
+
import { Result } from "../lib/result.js";
|
|
4
2
|
/** Parsed GitHub template reference. */
|
|
5
3
|
export interface TemplateRef {
|
|
6
4
|
owner: string;
|
|
@@ -12,7 +10,7 @@ export interface TemplateRef {
|
|
|
12
10
|
export type TemplateFetchResult = Result<{
|
|
13
11
|
tempDir: string;
|
|
14
12
|
templateDir: string;
|
|
15
|
-
}
|
|
13
|
+
}>;
|
|
16
14
|
/**
|
|
17
15
|
* Parse a template reference string into its components.
|
|
18
16
|
* Returns null if the format is invalid.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/util/template.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CACtC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CACzC,CAAC;AAyBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,WAAW,GAAG,IAyB5D,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,KACf,OAAO,CAAC,mBAAmB,CAqG7B,CAAC"}
|
|
@@ -17,15 +17,12 @@
|
|
|
17
17
|
import * as dntShim from "../_dnt.shims.js";
|
|
18
18
|
import { join } from "../deps/jsr.io/@std/path/1.1.4/mod.js";
|
|
19
19
|
import { runCommand } from "../lib/command.js";
|
|
20
|
+
import { Result } from "../lib/result.js";
|
|
20
21
|
// =============================================================================
|
|
21
22
|
// Internal Helpers
|
|
22
23
|
// =============================================================================
|
|
23
24
|
/** Shorthand for returning a typed fetch error. */
|
|
24
|
-
const fail = (
|
|
25
|
-
ok: false,
|
|
26
|
-
kind,
|
|
27
|
-
message,
|
|
28
|
-
});
|
|
25
|
+
const fail = (message) => Result.err(message);
|
|
29
26
|
/** Format a template reference for display. */
|
|
30
27
|
const formatRef = (ref) => {
|
|
31
28
|
const base = ref.path === "."
|
|
@@ -96,9 +93,9 @@ export const fetchTemplate = async (ref) => {
|
|
|
96
93
|
}
|
|
97
94
|
catch { /* ignore */ }
|
|
98
95
|
};
|
|
99
|
-
const cleanFail = (
|
|
96
|
+
const cleanFail = (message) => {
|
|
100
97
|
cleanup();
|
|
101
|
-
return fail(
|
|
98
|
+
return fail(message);
|
|
102
99
|
};
|
|
103
100
|
try {
|
|
104
101
|
const url = ref.ref
|
|
@@ -113,13 +110,13 @@ export const fetchTemplate = async (ref) => {
|
|
|
113
110
|
if (!response.ok) {
|
|
114
111
|
const repo = formatRepo(ref);
|
|
115
112
|
if (response.status === 404) {
|
|
116
|
-
return cleanFail(
|
|
113
|
+
return cleanFail(`Template Repository Not Found: ${repo}. ` +
|
|
117
114
|
"Check that the repository exists and is public.");
|
|
118
115
|
}
|
|
119
116
|
if (response.status === 403) {
|
|
120
|
-
return cleanFail("
|
|
117
|
+
return cleanFail("GitHub API Rate Limit Exceeded. Try again later.");
|
|
121
118
|
}
|
|
122
|
-
return cleanFail(
|
|
119
|
+
return cleanFail(`GitHub API Returned HTTP ${response.status} for ${repo}`);
|
|
123
120
|
}
|
|
124
121
|
// Write tarball to disk
|
|
125
122
|
const tarballPath = join(tempDir, "template.tar.gz");
|
|
@@ -135,31 +132,31 @@ export const fetchTemplate = async (ref) => {
|
|
|
135
132
|
"-C",
|
|
136
133
|
extractDir,
|
|
137
134
|
]);
|
|
138
|
-
if (!extractResult.
|
|
139
|
-
return cleanFail("
|
|
135
|
+
if (!extractResult.ok) {
|
|
136
|
+
return cleanFail("Failed to Extract Template Archive");
|
|
140
137
|
}
|
|
141
138
|
// GitHub tarballs have a single top-level dir (owner-repo-commitish/)
|
|
142
139
|
const entries = [...dntShim.Deno.readDirSync(extractDir)];
|
|
143
140
|
const topLevel = entries.find((e) => e.isDirectory);
|
|
144
141
|
if (!topLevel) {
|
|
145
|
-
return cleanFail("
|
|
142
|
+
return cleanFail("Template Archive Has No Top-Level Directory");
|
|
146
143
|
}
|
|
147
144
|
// Locate the template subdirectory
|
|
148
145
|
const templateDir = join(extractDir, topLevel.name, ref.path);
|
|
149
146
|
try {
|
|
150
147
|
const stat = dntShim.Deno.statSync(templateDir);
|
|
151
148
|
if (!stat.isDirectory) {
|
|
152
|
-
return cleanFail(
|
|
149
|
+
return cleanFail(`Template Path '${ref.path}' Is Not a Directory in ${formatRepo(ref)}`);
|
|
153
150
|
}
|
|
154
151
|
}
|
|
155
152
|
catch {
|
|
156
|
-
return cleanFail(
|
|
153
|
+
return cleanFail(`Template Path '${ref.path}' Not Found in ${formatRepo(ref)}`);
|
|
157
154
|
}
|
|
158
|
-
return {
|
|
155
|
+
return Result.ok({ tempDir, templateDir });
|
|
159
156
|
}
|
|
160
157
|
catch (e) {
|
|
161
158
|
if (e instanceof TypeError) {
|
|
162
|
-
return cleanFail("
|
|
159
|
+
return cleanFail("Network Error: Could Not Reach GitHub");
|
|
163
160
|
}
|
|
164
161
|
cleanup();
|
|
165
162
|
throw e;
|
package/package.json
CHANGED
|
@@ -1,50 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardelli/ambit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Deploy apps to the cloud that only you and your AI agents can reach",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"module": "./esm/src/providers/fly.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
"./providers/fly": {
|
|
9
|
-
"import": "./esm/src/providers/fly.js"
|
|
10
|
-
},
|
|
11
|
-
"./providers/tailscale": {
|
|
12
|
-
"import": "./esm/src/providers/tailscale.js"
|
|
13
|
-
},
|
|
14
|
-
"./schemas/config": {
|
|
15
|
-
"import": "./esm/src/schemas/config.js"
|
|
16
|
-
},
|
|
17
|
-
"./schemas/fly": {
|
|
18
|
-
"import": "./esm/src/schemas/fly.js"
|
|
19
|
-
},
|
|
20
|
-
"./schemas/tailscale": {
|
|
21
|
-
"import": "./esm/src/schemas/tailscale.js"
|
|
22
|
-
},
|
|
23
|
-
"./lib/cli": {
|
|
24
|
-
"import": "./esm/lib/cli.js"
|
|
25
|
-
},
|
|
26
|
-
"./lib/command": {
|
|
27
|
-
"import": "./esm/lib/command.js"
|
|
28
|
-
},
|
|
29
|
-
"./lib/output": {
|
|
30
|
-
"import": "./esm/lib/output.js"
|
|
31
|
-
},
|
|
32
|
-
"./lib/paths": {
|
|
33
|
-
"import": "./esm/lib/paths.js"
|
|
34
|
-
},
|
|
35
|
-
"./src/credentials": {
|
|
36
|
-
"import": "./esm/src/credentials.js"
|
|
37
|
-
},
|
|
38
|
-
"./src/discovery": {
|
|
39
|
-
"import": "./esm/src/discovery.js"
|
|
40
|
-
},
|
|
41
|
-
"./src/guard": {
|
|
42
|
-
"import": "./esm/src/guard.js"
|
|
43
|
-
},
|
|
44
|
-
"./src/resolve": {
|
|
45
|
-
"import": "./esm/src/resolve.js"
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
6
|
"scripts": {},
|
|
49
7
|
"bin": {
|
|
50
8
|
"ambit": "./esm/main.js"
|
package/esm/lib/paths.d.ts
DELETED
package/esm/lib/paths.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAG9B,eAAO,MAAM,kBAAkB,QAAO,MACqB,CAAC"}
|
package/esm/lib/paths.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// Path Helpers - Locate Package Resources
|
|
3
|
-
// =============================================================================
|
|
4
|
-
import "../_dnt.polyfills.js";
|
|
5
|
-
export const getRouterDockerDir = () => new URL("../src/docker/router", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/commands/create.ts"],"names":[],"mappings":""}
|