@cardelli/ambit 0.1.0 → 0.1.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAKA,OAAO,sBAAsB,CAAC;AAiB9B,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AACvE,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,MAAiC,CAAC;AACrE,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,MAAiC,CAAC;AACrE,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AACzE,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC3E,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AACvE,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AAMvE,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,KAAG,IAE3C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAMF,eAAO,MAAM,GAAG,GAAI,SAAS,MAAM,KAAG,KAGrC,CAAC;AAQF,qBAAa,OAAO;IAClB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAM;IAErB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAe5B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI7B,IAAI,IAAI,IAAI;IASZ,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAI5B;AAMD,eAAO,MAAM,MAAM,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAW5D,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAG9D,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAIhE,CAAC;AAMF,eAAO,MAAM,UAAU,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAO9D,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,OAO7C,CAAC;AAMF,eAAO,MAAM,YAAY,QAAO,MAG/B,CAAC;AAEF,eAAO,MAAM,aAAa,QAAO,MAEhC,CAAC;AAEF,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,IAAI,CAOpD,CAAC;AAMF,eAAO,MAAM,QAAQ,GAAI,SAAQ,MAAU,KAAG,MAO7C,CAAC;AAMF,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAYpE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AAKA,OAAO,sBAAsB,CAAC;AAiB9B,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AACvE,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,MAAiC,CAAC;AACrE,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,MAAiC,CAAC;AACrE,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AACzE,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC3E,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AACvE,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,MAAkC,CAAC;AAMvE,eAAO,MAAM,QAAQ,GAAI,SAAS,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,KAAG,IAE3C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,IAE5C,CAAC;AAMF,eAAO,MAAM,GAAG,GAAI,SAAS,MAAM,KAAG,KAGrC,CAAC;AAQF,qBAAa,OAAO;IAClB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAM;IAErB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAe5B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI7B,IAAI,IAAI,IAAI;IASZ,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAI5B;AAMD,eAAO,MAAM,MAAM,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAW5D,CAAC;AAEF,eAAO,MAAM,OAAO,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAG9D,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAIhE,CAAC;AAMF,eAAO,MAAM,UAAU,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAO9D,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,OAO7C,CAAC;AAMF,eAAO,MAAM,YAAY,QAAO,MAG/B,CAAC;AAEF,eAAO,MAAM,aAAa,QAAO,MAEhC,CAAC;AAEF,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,IAAI,CAOpD,CAAC;AAMF,eAAO,MAAM,QAAQ,GAAI,SAAQ,MAAU,KAAG,MAO7C,CAAC;AAMF,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CASpE,CAAC"}
package/esm/lib/cli.js CHANGED
@@ -161,16 +161,12 @@ export const randomId = (length = 6) => {
161
161
  // Command Exists Check
162
162
  // =============================================================================
163
163
  export const commandExists = async (command) => {
164
- try {
165
- const cmd = new dntShim.Deno.Command("which", {
166
- args: [command],
167
- stdout: "null",
168
- stderr: "null",
164
+ const { spawn } = await import("node:child_process");
165
+ return new Promise((resolve) => {
166
+ const child = spawn("which", [command], {
167
+ stdio: "ignore",
169
168
  });
170
- const { success } = await cmd.output();
171
- return success;
172
- }
173
- catch {
174
- return false;
175
- }
169
+ child.on("error", () => resolve(false));
170
+ child.on("close", (code) => resolve(code === 0));
171
+ });
176
172
  };
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAW9B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;CACtC,KACA,OAAO,CAAC,aAAa,CA8BvB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,CAAC,EACpC,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAmBxD,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,EACb,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC,aAAa,CAavB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM9C,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAkB5C,CAAC"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/lib/command.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAU9B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;CACtC,KACA,OAAO,CAAC,aAAa,CAwCvB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,CAAC,EACpC,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAmBxD,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,EACb,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC,aAAa,CAavB,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,QAAQ,GACnB,OAAO,MAAM,EACb,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM9C,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EAAE,EACd,UAAU;IACR,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,KACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAkB5C,CAAC"}
@@ -2,7 +2,7 @@
2
2
  // Shell Command Helpers
3
3
  // =============================================================================
4
4
  import "../_dnt.polyfills.js";
5
- import * as dntShim from "../_dnt.shims.js";
5
+ import { spawn } from "node:child_process";
6
6
  import { Spinner } from "./cli.js";
7
7
  // =============================================================================
8
8
  // Run Command
@@ -10,34 +10,41 @@ import { Spinner } from "./cli.js";
10
10
  /**
11
11
  * Run a command and capture output.
12
12
  */
13
- export const runCommand = async (args, options) => {
13
+ export const runCommand = (args, options) => {
14
14
  const [cmd, ...cmdArgs] = args;
15
- try {
16
- const command = new dntShim.Deno.Command(cmd, {
17
- args: cmdArgs,
15
+ return new Promise((resolve) => {
16
+ const child = spawn(cmd, cmdArgs, {
18
17
  cwd: options?.cwd,
19
- env: options?.env,
20
- stdin: options?.stdin ?? "null",
21
- stdout: "piped",
22
- stderr: "piped",
18
+ env: options?.env ? { ...process.env, ...options.env } : undefined,
19
+ stdio: [
20
+ options?.stdin === "inherit" ? "inherit" : "ignore",
21
+ "pipe",
22
+ "pipe",
23
+ ],
23
24
  });
24
- const { code, stdout, stderr } = await command.output();
25
- const decoder = new TextDecoder();
26
- return {
27
- success: code === 0,
28
- code,
29
- stdout: decoder.decode(stdout),
30
- stderr: decoder.decode(stderr),
31
- };
32
- }
33
- catch (error) {
34
- return {
35
- success: false,
36
- code: -1,
37
- stdout: "",
38
- stderr: error instanceof Error ? error.message : String(error),
39
- };
40
- }
25
+ const stdout = [];
26
+ const stderr = [];
27
+ child.stdout.setEncoding("utf8");
28
+ child.stderr.setEncoding("utf8");
29
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
30
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
31
+ child.on("error", (error) => {
32
+ resolve({
33
+ success: false,
34
+ code: -1,
35
+ stdout: "",
36
+ stderr: error.message,
37
+ });
38
+ });
39
+ child.on("close", (code) => {
40
+ resolve({
41
+ success: code === 0,
42
+ code: code ?? 1,
43
+ stdout: stdout.join(""),
44
+ stderr: stderr.join(""),
45
+ });
46
+ });
47
+ });
41
48
  };
42
49
  // =============================================================================
43
50
  // Run Command with JSON Output
@@ -101,21 +108,19 @@ export const runQuiet = async (label, args, options) => {
101
108
  /**
102
109
  * Run a command interactively (inherits stdio).
103
110
  */
104
- export const runInteractive = async (args, options) => {
111
+ export const runInteractive = (args, options) => {
105
112
  const [cmd, ...cmdArgs] = args;
106
- try {
107
- const command = new dntShim.Deno.Command(cmd, {
108
- args: cmdArgs,
113
+ return new Promise((resolve) => {
114
+ const child = spawn(cmd, cmdArgs, {
109
115
  cwd: options?.cwd,
110
- env: options?.env,
111
- stdin: "inherit",
112
- stdout: "inherit",
113
- stderr: "inherit",
116
+ env: options?.env ? { ...process.env, ...options.env } : undefined,
117
+ stdio: "inherit",
114
118
  });
115
- const { code } = await command.output();
116
- return { success: code === 0, code };
117
- }
118
- catch {
119
- return { success: false, code: -1 };
120
- }
119
+ child.on("error", () => {
120
+ resolve({ success: false, code: -1 });
121
+ });
122
+ child.on("close", (code) => {
123
+ resolve({ success: (code ?? 1) === 0, code: code ?? 1 });
124
+ });
125
+ });
121
126
  };
@@ -6,7 +6,8 @@ import { bold, randomId, readSecret } from "../../../lib/cli.js";
6
6
  import { createOutput } from "../../../lib/output.js";
7
7
  import { registerCommand } from "../mod.js";
8
8
  import { extractSubnet, getRouterTag } from "../../schemas/config.js";
9
- import { createFlyProvider, getRouterAppName } from "../../providers/fly.js";
9
+ import { isPublicTld } from "../../guard.js";
10
+ import { createFlyProvider, FlyDeployError, getRouterAppName } from "../../providers/fly.js";
10
11
  import { createTailscaleProvider, enableAcceptRoutes, isAcceptRoutesEnabled, isTailscaleInstalled, waitForDevice, } from "../../providers/tailscale.js";
11
12
  import { getCredentialStore } from "../../credentials.js";
12
13
  import { resolveOrg } from "../../resolve.js";
@@ -53,6 +54,9 @@ ${bold("EXAMPLES")}
53
54
  if (!network) {
54
55
  return out.die("Network Name Required. Usage: ambit create <network>");
55
56
  }
57
+ if (isPublicTld(network)) {
58
+ return out.die(`"${network}" Is a Public TLD and Cannot Be Used as a Network Name`);
59
+ }
56
60
  const tag = args.tag || getRouterTag(network);
57
61
  const selfApprove = args["self-approve"] ?? false;
58
62
  out.blank()
@@ -164,7 +168,16 @@ ${bold("EXAMPLES")}
164
168
  out.ok("Set Router Secrets");
165
169
  const dockerDir = new URL("../../docker/router", globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url).pathname;
166
170
  out.blank().dim("Deploying Router...");
167
- await fly.routerDeploy(routerAppName, dockerDir, { region });
171
+ try {
172
+ await fly.routerDeploy(routerAppName, dockerDir, { region });
173
+ }
174
+ catch (e) {
175
+ if (e instanceof FlyDeployError) {
176
+ out.dim(` ${e.detail}`);
177
+ return out.die(e.message);
178
+ }
179
+ throw e;
180
+ }
168
181
  out.ok("Router Deployed");
169
182
  out.blank();
170
183
  const joinSpinner = out.spinner("Waiting for Router to Join Tailnet");
@@ -243,7 +256,6 @@ ${bold("EXAMPLES")}
243
256
  .dim("Control their access:")
244
257
  .dim(" https://login.tailscale.com/admin/acls/visual/general-access-rules")
245
258
  .blank();
246
- // Print recommended ACL policy
247
259
  if (subnet && selfApprove) {
248
260
  out.header("Recommended Tailscale ACL Policy:")
249
261
  .blank()
@@ -6,7 +6,7 @@ import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.27/mod.js";
6
6
  import { bold, confirm, fileExists } from "../../../lib/cli.js";
7
7
  import { createOutput } from "../../../lib/output.js";
8
8
  import { registerCommand } from "../mod.js";
9
- import { createFlyProvider } from "../../providers/fly.js";
9
+ import { createFlyProvider, FlyDeployError } from "../../providers/fly.js";
10
10
  import { findRouterApp } from "../../discovery.js";
11
11
  import { resolveOrg } from "../../resolve.js";
12
12
  import { assertNotRouter, auditDeploy, scanFlyToml } from "../../guard.js";
@@ -172,11 +172,20 @@ ${bold("EXAMPLES")}
172
172
  // ==========================================================================
173
173
  out.header("Step 5: Deploy").blank();
174
174
  out.dim("Deploying with --no-public-ips --flycast ...");
175
- await fly.deploySafe(app, {
176
- image: args.image,
177
- config: configPath,
178
- region: args.region,
179
- });
175
+ try {
176
+ await fly.deploySafe(app, {
177
+ image: args.image,
178
+ config: configPath,
179
+ region: args.region,
180
+ });
181
+ }
182
+ catch (e) {
183
+ if (e instanceof FlyDeployError) {
184
+ out.dim(` ${e.detail}`);
185
+ return out.die(e.message);
186
+ }
187
+ throw e;
188
+ }
180
189
  out.ok("Deploy Succeeded");
181
190
  out.blank();
182
191
  // ==========================================================================
@@ -6,7 +6,8 @@ import { bold, confirm } from "../../../lib/cli.js";
6
6
  import { createOutput } from "../../../lib/output.js";
7
7
  import { registerCommand } from "../mod.js";
8
8
  import { createFlyProvider } from "../../providers/fly.js";
9
- import { requireTailscaleProvider } from "../../credentials.js";
9
+ import { createTailscaleProvider } from "../../providers/tailscale.js";
10
+ import { checkDependencies } from "../../credentials.js";
10
11
  import { findRouterApp } from "../../discovery.js";
11
12
  import { resolveOrg } from "../../resolve.js";
12
13
  // =============================================================================
@@ -37,12 +38,17 @@ ${bold("OPTIONS")}
37
38
  if (!args.network) {
38
39
  return out.die("--network Is Required");
39
40
  }
41
+ // =========================================================================
42
+ // Prerequisites
43
+ // =========================================================================
44
+ const { tailscaleKey } = await checkDependencies(out);
40
45
  const fly = createFlyProvider();
41
- await fly.ensureInstalled();
42
46
  await fly.ensureAuth({ interactive: !args.json });
43
- const tailscale = await requireTailscaleProvider(out);
47
+ const tailscale = createTailscaleProvider("-", tailscaleKey);
44
48
  const org = await resolveOrg(fly, args, out);
45
- // 1. Find the router app
49
+ // =========================================================================
50
+ // Discover Router
51
+ // =========================================================================
46
52
  const spinner = out.spinner("Discovering Router");
47
53
  const app = await findRouterApp(fly, org, args.network);
48
54
  if (!app) {
@@ -50,13 +56,15 @@ ${bold("OPTIONS")}
50
56
  return out.die(`No Router Found for Network '${args.network}'`);
51
57
  }
52
58
  spinner.success(`Found Router: ${app.appName}`);
53
- // Get tailscale device to read the actual tag
54
59
  let tsDevice = null;
55
60
  try {
56
61
  tsDevice = await tailscale.getDeviceByHostname(app.appName);
57
62
  }
58
63
  catch { /* device may not exist */ }
59
64
  const tag = tsDevice?.tags?.[0] ?? null;
65
+ // =========================================================================
66
+ // Confirm
67
+ // =========================================================================
60
68
  out.blank()
61
69
  .header("ambit Destroy")
62
70
  .blank()
@@ -72,7 +80,9 @@ ${bold("OPTIONS")}
72
80
  }
73
81
  out.blank();
74
82
  }
75
- // 2. Clean up tailscale
83
+ // =========================================================================
84
+ // Tear Down
85
+ // =========================================================================
76
86
  const dnsSpinner = out.spinner("Clearing Split DNS");
77
87
  try {
78
88
  await tailscale.clearSplitDns(app.network);
@@ -95,7 +105,6 @@ ${bold("OPTIONS")}
95
105
  catch {
96
106
  deviceSpinner.fail("Could Not Remove Tailscale Device");
97
107
  }
98
- // 3. Destroy Fly app
99
108
  const appSpinner = out.spinner("Destroying Fly App");
100
109
  try {
101
110
  await fly.deleteApp(app.appName);
@@ -104,6 +113,9 @@ ${bold("OPTIONS")}
104
113
  catch {
105
114
  appSpinner.fail("Could Not Destroy Fly App");
106
115
  }
116
+ // =========================================================================
117
+ // Done
118
+ // =========================================================================
107
119
  out.done({ destroyed: true, appName: app.appName });
108
120
  out.ok("Router Destroyed");
109
121
  if (tag) {
@@ -7,8 +7,8 @@ import { createOutput } from "../../../lib/output.js";
7
7
  import { runCommand } from "../../../lib/command.js";
8
8
  import { registerCommand } from "../mod.js";
9
9
  import { createFlyProvider } from "../../providers/fly.js";
10
- import { isAcceptRoutesEnabled, isTailscaleInstalled, } from "../../providers/tailscale.js";
11
- import { requireTailscaleProvider } from "../../credentials.js";
10
+ import { createTailscaleProvider, isAcceptRoutesEnabled, isTailscaleInstalled, } from "../../providers/tailscale.js";
11
+ import { checkDependencies } from "../../credentials.js";
12
12
  import { findRouterApp, getRouterMachineInfo, getRouterTailscaleInfo, listRouterApps, } from "../../discovery.js";
13
13
  import { resolveOrg } from "../../resolve.js";
14
14
  // =============================================================================
@@ -54,15 +54,15 @@ ${bold("CHECKS")}
54
54
  }
55
55
  };
56
56
  // =========================================================================
57
- // Prerequisites (fail fast)
57
+ // Prerequisites
58
58
  // =========================================================================
59
+ const { tailscaleKey } = await checkDependencies(out);
59
60
  const fly = createFlyProvider();
60
- await fly.ensureInstalled();
61
61
  await fly.ensureAuth({ interactive: !args.json });
62
- const tailscale = await requireTailscaleProvider(out);
62
+ const tailscale = createTailscaleProvider("-", tailscaleKey);
63
63
  const org = await resolveOrg(fly, args, out);
64
64
  // =========================================================================
65
- // Local checks
65
+ // Local Checks
66
66
  // =========================================================================
67
67
  report("Tailscale Installed", await isTailscaleInstalled(), "Install from https://tailscale.com/download");
68
68
  const tsStatus = await runCommand(["tailscale", "status", "--json"]);
@@ -77,7 +77,7 @@ ${bold("CHECKS")}
77
77
  report("Tailscale Connected", tsConnected, "Run: tailscale up");
78
78
  report("Accept Routes Enabled", await isAcceptRoutesEnabled(), "Run: sudo tailscale set --accept-routes");
79
79
  // =========================================================================
80
- // Router checks
80
+ // Router Checks
81
81
  // =========================================================================
82
82
  if (args.network) {
83
83
  const app = await findRouterApp(fly, org, args.network);
@@ -7,7 +7,8 @@ import { bold } from "../../../lib/cli.js";
7
7
  import { createOutput } from "../../../lib/output.js";
8
8
  import { registerCommand } from "../mod.js";
9
9
  import { createFlyProvider } from "../../providers/fly.js";
10
- import { requireTailscaleProvider } from "../../credentials.js";
10
+ import { createTailscaleProvider } from "../../providers/tailscale.js";
11
+ import { checkDependencies } from "../../credentials.js";
11
12
  import { getRouterMachineInfo, getRouterTailscaleInfo, listRouterApps, } from "../../discovery.js";
12
13
  import { resolveOrg } from "../../resolve.js";
13
14
  // =============================================================================
@@ -32,12 +33,17 @@ ${bold("OPTIONS")}
32
33
  return;
33
34
  }
34
35
  const out = createOutput(args.json);
36
+ // =========================================================================
37
+ // Prerequisites
38
+ // =========================================================================
39
+ const { tailscaleKey } = await checkDependencies(out);
35
40
  const fly = createFlyProvider();
36
- await fly.ensureInstalled();
37
41
  await fly.ensureAuth({ interactive: !args.json });
38
- const tailscale = await requireTailscaleProvider(out);
42
+ const tailscale = createTailscaleProvider("-", tailscaleKey);
39
43
  const org = await resolveOrg(fly, args, out);
40
- // 1. Find all router apps
44
+ // =========================================================================
45
+ // Discover Routers
46
+ // =========================================================================
41
47
  const spinner = out.spinner("Discovering Routers");
42
48
  const routerApps = await listRouterApps(fly, org);
43
49
  spinner.success(`Found ${routerApps.length} Router${routerApps.length !== 1 ? "s" : ""}`);
@@ -50,14 +56,15 @@ ${bold("OPTIONS")}
50
56
  out.print();
51
57
  return;
52
58
  }
53
- // 2. Get machine + tailscale state for each
54
59
  const routers = [];
55
60
  for (const app of routerApps) {
56
61
  const machine = await getRouterMachineInfo(fly, app.appName);
57
62
  const ts = await getRouterTailscaleInfo(tailscale, app.appName);
58
63
  routers.push({ ...app, machine, tailscale: ts });
59
64
  }
60
- // 3. Render
65
+ // =========================================================================
66
+ // Render
67
+ // =========================================================================
61
68
  out.blank().header("Routers").blank();
62
69
  const rows = routers.map((r) => {
63
70
  const tsStatus = r.tailscale
@@ -7,7 +7,8 @@ import { bold } from "../../../lib/cli.js";
7
7
  import { createOutput } from "../../../lib/output.js";
8
8
  import { registerCommand } from "../mod.js";
9
9
  import { createFlyProvider } from "../../providers/fly.js";
10
- import { requireTailscaleProvider } from "../../credentials.js";
10
+ import { createTailscaleProvider, } from "../../providers/tailscale.js";
11
+ import { checkDependencies } from "../../credentials.js";
11
12
  import { findRouterApp, getRouterMachineInfo, getRouterTailscaleInfo, listRouterApps, } from "../../discovery.js";
12
13
  import { resolveOrg } from "../../resolve.js";
13
14
  // =============================================================================
@@ -36,106 +37,111 @@ ${bold("EXAMPLES")}
36
37
  `);
37
38
  return;
38
39
  }
40
+ // =========================================================================
41
+ // Prerequisites
42
+ // =========================================================================
43
+ const { tailscaleKey } = await checkDependencies(createOutput(args.json));
39
44
  const fly = createFlyProvider();
40
- await fly.ensureInstalled();
41
45
  await fly.ensureAuth({ interactive: !args.json });
46
+ const tailscale = createTailscaleProvider("-", tailscaleKey);
47
+ // =========================================================================
48
+ // Status
49
+ // =========================================================================
42
50
  if (args.network) {
43
- // ========================================================================
44
- // Single Router Detailed View
45
- // ========================================================================
46
- const out = createOutput(args.json);
47
- const tailscale = await requireTailscaleProvider(out);
48
- const org = await resolveOrg(fly, args, out);
49
- // 1. Find the router
50
- const app = await findRouterApp(fly, org, args.network);
51
- if (!app) {
52
- return out.die(`No Router Found for Network '${args.network}'`);
53
- }
54
- // 2. Get machine state
55
- const machine = await getRouterMachineInfo(fly, app.appName);
56
- // 3. Get tailscale state
57
- const ts = await getRouterTailscaleInfo(tailscale, app.appName);
58
- const tag = ts?.tags?.[0] ?? null;
59
- out.blank()
60
- .header("ambit Status")
61
- .blank()
62
- .text(` Network: ${bold(app.network)}`)
63
- .text(` TLD: *.${app.network}`)
64
- .text(` Tag: ${tag ?? "unknown"}`)
65
- .blank()
66
- .text(` Router App: ${app.appName}`)
67
- .text(` Region: ${machine?.region ?? "unknown"}`)
68
- .text(` Machine State: ${machine?.state ?? "unknown"}`)
69
- .text(` Private IP: ${machine?.privateIp ?? "unknown"}`)
70
- .text(` SOCKS Proxy: ${machine?.privateIp
71
- ? `socks5://[${machine.privateIp}]:1080`
72
- : "unknown"}`);
73
- if (machine?.subnet) {
74
- out.text(` Subnet: ${machine.subnet}`);
75
- }
76
- out.blank();
77
- if (ts) {
78
- out.text(` Tailscale IP: ${ts.ip}`)
79
- .text(` Online: ${ts.online ? "yes" : "no"}`);
80
- }
81
- else {
82
- out.text(" Tailscale: Not Found in Tailnet");
83
- }
84
- out.blank();
85
- out.done({
86
- network: app.network,
87
- router: app,
88
- machine,
89
- tag,
90
- tailscale: ts,
91
- });
92
- out.print();
51
+ await showNetworkStatus(fly, tailscale, args);
52
+ }
53
+ else {
54
+ await showAllStatus(fly, tailscale, args);
55
+ }
56
+ };
57
+ // =============================================================================
58
+ // Single Router Detailed View
59
+ // =============================================================================
60
+ const showNetworkStatus = async (fly, tailscale, args) => {
61
+ const out = createOutput(args.json);
62
+ const org = await resolveOrg(fly, args, out);
63
+ const app = await findRouterApp(fly, org, args.network);
64
+ if (!app) {
65
+ return out.die(`No Router Found for Network '${args.network}'`);
66
+ }
67
+ const machine = await getRouterMachineInfo(fly, app.appName);
68
+ const ts = await getRouterTailscaleInfo(tailscale, app.appName);
69
+ const tag = ts?.tags?.[0] ?? null;
70
+ out.blank()
71
+ .header("ambit Status")
72
+ .blank()
73
+ .text(` Network: ${bold(app.network)}`)
74
+ .text(` TLD: *.${app.network}`)
75
+ .text(` Tag: ${tag ?? "unknown"}`)
76
+ .blank()
77
+ .text(` Router App: ${app.appName}`)
78
+ .text(` Region: ${machine?.region ?? "unknown"}`)
79
+ .text(` Machine State: ${machine?.state ?? "unknown"}`)
80
+ .text(` Private IP: ${machine?.privateIp ?? "unknown"}`)
81
+ .text(` SOCKS Proxy: ${machine?.privateIp
82
+ ? `socks5://[${machine.privateIp}]:1080`
83
+ : "unknown"}`);
84
+ if (machine?.subnet) {
85
+ out.text(` Subnet: ${machine.subnet}`);
86
+ }
87
+ out.blank();
88
+ if (ts) {
89
+ out.text(` Tailscale IP: ${ts.ip}`)
90
+ .text(` Online: ${ts.online ? "yes" : "no"}`);
93
91
  }
94
92
  else {
95
- // ========================================================================
96
- // Summary Table of All Routers
97
- // ========================================================================
98
- const out = createOutput(args.json);
99
- const tailscale = await requireTailscaleProvider(out);
100
- const org = await resolveOrg(fly, args, out);
101
- // 1. Find all router apps
102
- const spinner = out.spinner("Discovering Routers");
103
- const routerApps = await listRouterApps(fly, org);
104
- spinner.success(`Found ${routerApps.length} Router${routerApps.length !== 1 ? "s" : ""}`);
105
- if (routerApps.length === 0) {
106
- out.blank()
107
- .text("No Routers Found.")
108
- .dim(" Create one with: ambit create <network>")
109
- .blank();
110
- out.done({ routers: [] });
111
- out.print();
112
- return;
113
- }
114
- // 2. Get machine + tailscale state for each
115
- const routers = [];
116
- for (const app of routerApps) {
117
- const machine = await getRouterMachineInfo(fly, app.appName);
118
- const ts = await getRouterTailscaleInfo(tailscale, app.appName);
119
- routers.push({ ...app, machine, tailscale: ts });
120
- }
121
- // 3. Render
122
- out.blank().header("Router Status").blank();
123
- const rows = routers.map((r) => {
124
- const tsStatus = r.tailscale
125
- ? r.tailscale.online ? "online" : "offline"
126
- : "not found";
127
- return [r.network, r.appName, r.machine?.state ?? "unknown", tsStatus];
128
- });
129
- const table = new Table()
130
- .header(["Network", "App", "State", "Tailscale"])
131
- .body(rows)
132
- .indent(2)
133
- .padding(2);
134
- out.text(table.toString());
135
- out.blank();
136
- out.done({ routers });
93
+ out.text(" Tailscale: Not Found in Tailnet");
94
+ }
95
+ out.blank();
96
+ out.done({
97
+ network: app.network,
98
+ router: app,
99
+ machine,
100
+ tag,
101
+ tailscale: ts,
102
+ });
103
+ out.print();
104
+ };
105
+ // =============================================================================
106
+ // Summary Table of All Routers
107
+ // =============================================================================
108
+ const showAllStatus = async (fly, tailscale, args) => {
109
+ const out = createOutput(args.json);
110
+ const org = await resolveOrg(fly, args, out);
111
+ const spinner = out.spinner("Discovering Routers");
112
+ const routerApps = await listRouterApps(fly, org);
113
+ spinner.success(`Found ${routerApps.length} Router${routerApps.length !== 1 ? "s" : ""}`);
114
+ if (routerApps.length === 0) {
115
+ out.blank()
116
+ .text("No Routers Found.")
117
+ .dim(" Create one with: ambit create <network>")
118
+ .blank();
119
+ out.done({ routers: [] });
137
120
  out.print();
121
+ return;
138
122
  }
123
+ const routers = [];
124
+ for (const app of routerApps) {
125
+ const machine = await getRouterMachineInfo(fly, app.appName);
126
+ const ts = await getRouterTailscaleInfo(tailscale, app.appName);
127
+ routers.push({ ...app, machine, tailscale: ts });
128
+ }
129
+ out.blank().header("Router Status").blank();
130
+ const rows = routers.map((r) => {
131
+ const tsStatus = r.tailscale
132
+ ? r.tailscale.online ? "online" : "offline"
133
+ : "not found";
134
+ return [r.network, r.appName, r.machine?.state ?? "unknown", tsStatus];
135
+ });
136
+ const table = new Table()
137
+ .header(["Network", "App", "State", "Tailscale"])
138
+ .body(rows)
139
+ .indent(2)
140
+ .padding(2);
141
+ out.text(table.toString());
142
+ out.blank();
143
+ out.done({ routers });
144
+ out.print();
139
145
  };
140
146
  // =============================================================================
141
147
  // Register Command
@@ -1,13 +1,22 @@
1
1
  import "../_dnt.polyfills.js";
2
- import { type TailscaleProvider } from "./providers/tailscale.js";
3
2
  export interface CredentialStore {
4
3
  getTailscaleApiKey(): Promise<string | null>;
5
4
  setTailscaleApiKey(key: string): Promise<void>;
6
5
  }
7
6
  export declare const createConfigCredentialStore: () => CredentialStore;
8
7
  export declare const getCredentialStore: () => CredentialStore;
9
- /** Get a TailscaleProvider or die via out.die(). Respects JSON mode. */
10
- export declare const requireTailscaleProvider: (out: {
8
+ /**
9
+ * Verify that flyctl CLI and Tailscale API key are both available.
10
+ * Reports ALL missing dependencies before dying, so the user can
11
+ * fix everything in one pass instead of hitting errors one at a time.
12
+ *
13
+ * Returns the validated Tailscale API key for explicit injection into
14
+ * the provider created by the caller.
15
+ */
16
+ export declare const checkDependencies: (out: {
17
+ err(msg: string): unknown;
11
18
  die(msg: string): never;
12
- }) => Promise<TailscaleProvider>;
19
+ }) => Promise<{
20
+ tailscaleKey: string;
21
+ }>;
13
22
  //# sourceMappingURL=credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/src/credentials.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAQ9B,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAclC,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAgBrC,CAAC;AAMF,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GACnC,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC/B,OAAO,CAAC,iBAAiB,CAS3B,CAAC"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/src/credentials.ts"],"names":[],"mappings":"AAGA,OAAO,sBAAsB,CAAC;AAqB9B,MAAM,WAAW,eAAe;IAC9B,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAQD,eAAO,MAAM,2BAA2B,QAAO,eA0B9C,CAAC;AAMF,eAAO,MAAM,kBAAkB,QAAO,eAgBrC,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC5B,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,KAC1D,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAyBlC,CAAC"}
@@ -4,9 +4,8 @@
4
4
  import "../_dnt.polyfills.js";
5
5
  import * as dntShim from "../_dnt.shims.js";
6
6
  import { z } from "../deps/jsr.io/@zod/zod/4.3.6/src/index.js";
7
- import { ensureConfigDir, fileExists } from "../lib/cli.js";
7
+ import { commandExists, ensureConfigDir, fileExists } from "../lib/cli.js";
8
8
  import { getConfigDir } from "./schemas/config.js";
9
- import { createTailscaleProvider, } from "./providers/tailscale.js";
10
9
  // =============================================================================
11
10
  // Schema
12
11
  // =============================================================================
@@ -59,14 +58,32 @@ export const getCredentialStore = () => {
59
58
  };
60
59
  };
61
60
  // =============================================================================
62
- // Require Tailscale Provider (fail-fast)
61
+ // Check Dependencies (batch validation)
63
62
  // =============================================================================
64
- /** Get a TailscaleProvider or die via out.die(). Respects JSON mode. */
65
- export const requireTailscaleProvider = async (out) => {
66
- const store = getCredentialStore();
67
- const key = await store.getTailscaleApiKey();
63
+ /**
64
+ * Verify that flyctl CLI and Tailscale API key are both available.
65
+ * Reports ALL missing dependencies before dying, so the user can
66
+ * fix everything in one pass instead of hitting errors one at a time.
67
+ *
68
+ * Returns the validated Tailscale API key for explicit injection into
69
+ * the provider created by the caller.
70
+ */
71
+ export const checkDependencies = async (out) => {
72
+ const errors = [];
73
+ if (!(await commandExists("fly"))) {
74
+ errors.push("Flyctl Not Found. Install from https://fly.io/docs/flyctl/install/");
75
+ }
76
+ const key = await getCredentialStore().getTailscaleApiKey();
68
77
  if (!key) {
69
- return out.die("Tailscale API Key Required. Run 'ambit create' or set TAILSCALE_API_KEY");
78
+ errors.push("Tailscale API Key Required. Run 'ambit create' or set TAILSCALE_API_KEY");
79
+ }
80
+ if (errors.length === 1) {
81
+ return out.die(errors[0]);
82
+ }
83
+ if (errors.length > 1) {
84
+ for (const e of errors)
85
+ out.err(e);
86
+ return out.die("Missing Prerequisites");
70
87
  }
71
- return createTailscaleProvider("-", key);
88
+ return { tailscaleKey: key };
72
89
  };
@@ -1,4 +1,10 @@
1
+ import "../_dnt.polyfills.js";
1
2
  import type { FlyProvider } from "./providers/fly.js";
3
+ /**
4
+ * Returns true if the given name is a public TLD (case-insensitive).
5
+ * Used to prevent creating networks that would shadow real DNS.
6
+ */
7
+ export declare const isPublicTld: (name: string) => boolean;
2
8
  export interface PreflightResult {
3
9
  scanned: boolean;
4
10
  errors: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/src/guard.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAMtD,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,CAwDhE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAwF5B"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/src/guard.ts"],"names":[],"mappings":"AAWA,OAAO,sBAAsB,CAAC;AAI9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAqLtD;;;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,CAwDhE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAwF5B"}
package/esm/src/guard.js CHANGED
@@ -9,8 +9,191 @@
9
9
  // - auditDeploy: post-deploy check that releases public IPs and reports
10
10
  //
11
11
  // =============================================================================
12
+ import "../_dnt.polyfills.js";
12
13
  import { parse as parseToml } from "../deps/jsr.io/@std/toml/1.0.11/mod.js";
13
14
  // =============================================================================
15
+ // Public TLD Blocklist (source: https://data.iana.org/TLD/tlds-alpha-by-domain.txt)
16
+ // =============================================================================
17
+ // deno-fmt-ignore
18
+ const PUBLIC_TLDS = new Set([
19
+ "aaa", "aarp", "abb", "abbott", "abbvie", "abc", "able", "abogado", "abudhabi", "ac",
20
+ "academy", "accenture", "accountant", "accountants", "aco", "actor", "ad", "ads",
21
+ "adult", "ae", "aeg", "aero", "aetna", "af", "afl", "africa", "ag", "agakhan", "agency",
22
+ "ai", "aig", "airbus", "airforce", "airtel", "akdn", "al", "alibaba", "alipay",
23
+ "allfinanz", "allstate", "ally", "alsace", "alstom", "am", "amazon", "americanexpress",
24
+ "americanfamily", "amex", "amfam", "amica", "amsterdam", "analytics", "android",
25
+ "anquan", "anz", "ao", "aol", "apartments", "app", "apple", "aq", "aquarelle", "ar",
26
+ "arab", "aramco", "archi", "army", "arpa", "art", "arte", "as", "asda", "asia",
27
+ "associates", "at", "athleta", "attorney", "au", "auction", "audi", "audible", "audio",
28
+ "auspost", "author", "auto", "autos", "aw", "aws", "ax", "axa", "az", "azure", "ba",
29
+ "baby", "baidu", "banamex", "band", "bank", "bar", "barcelona", "barclaycard",
30
+ "barclays", "barefoot", "bargains", "baseball", "basketball", "bauhaus", "bayern",
31
+ "bb", "bbc", "bbt", "bbva", "bcg", "bcn", "bd", "be", "beats", "beauty", "beer",
32
+ "berlin", "best", "bestbuy", "bet", "bf", "bg", "bh", "bharti", "bi", "bible", "bid",
33
+ "bike", "bing", "bingo", "bio", "biz", "bj", "black", "blackfriday", "blockbuster",
34
+ "blog", "bloomberg", "blue", "bm", "bms", "bmw", "bn", "bnpparibas", "bo", "boats",
35
+ "boehringer", "bofa", "bom", "bond", "boo", "book", "booking", "bosch", "bostik",
36
+ "boston", "bot", "boutique", "box", "br", "bradesco", "bridgestone", "broadway",
37
+ "broker", "brother", "brussels", "bs", "bt", "build", "builders", "business", "buy",
38
+ "buzz", "bv", "bw", "by", "bz", "bzh", "ca", "cab", "cafe", "cal", "call",
39
+ "calvinklein", "cam", "camera", "camp", "canon", "capetown", "capital", "capitalone",
40
+ "car", "caravan", "cards", "care", "career", "careers", "cars", "casa", "case", "cash",
41
+ "casino", "cat", "catering", "catholic", "cba", "cbn", "cbre", "cc", "cd", "center",
42
+ "ceo", "cern", "cf", "cfa", "cfd", "cg", "ch", "chanel", "channel", "charity", "chase",
43
+ "chat", "cheap", "chintai", "christmas", "chrome", "church", "ci", "cipriani",
44
+ "circle", "cisco", "citadel", "citi", "citic", "city", "ck", "cl", "claims",
45
+ "cleaning", "click", "clinic", "clinique", "clothing", "cloud", "club", "clubmed",
46
+ "cm", "cn", "co", "coach", "codes", "coffee", "college", "cologne", "com", "commbank",
47
+ "community", "company", "compare", "computer", "comsec", "condos", "construction",
48
+ "consulting", "contact", "contractors", "cooking", "cool", "coop", "corsica",
49
+ "country", "coupon", "coupons", "courses", "cpa", "cr", "credit", "creditcard",
50
+ "creditunion", "cricket", "crown", "crs", "cruise", "cruises", "cu", "cuisinella",
51
+ "cv", "cw", "cx", "cy", "cymru", "cyou", "cz", "dad", "dance", "data", "date",
52
+ "dating", "datsun", "day", "dclk", "dds", "de", "deal", "dealer", "deals", "degree",
53
+ "delivery", "dell", "deloitte", "delta", "democrat", "dental", "dentist", "desi",
54
+ "design", "dev", "dhl", "diamonds", "diet", "digital", "direct", "directory",
55
+ "discount", "discover", "dish", "diy", "dj", "dk", "dm", "dnp", "do", "docs", "doctor",
56
+ "dog", "domains", "dot", "download", "drive", "dtv", "dubai", "dupont", "durban",
57
+ "dvag", "dvr", "dz", "earth", "eat", "ec", "eco", "edeka", "edu", "education", "ee",
58
+ "eg", "email", "emerck", "energy", "engineer", "engineering", "enterprises", "epson",
59
+ "equipment", "er", "ericsson", "erni", "es", "esq", "estate", "et", "eu", "eurovision",
60
+ "eus", "events", "exchange", "expert", "exposed", "express", "extraspace", "fage",
61
+ "fail", "fairwinds", "faith", "family", "fan", "fans", "farm", "farmers", "fashion",
62
+ "fast", "fedex", "feedback", "ferrari", "ferrero", "fi", "fidelity", "fido", "film",
63
+ "final", "finance", "financial", "fire", "firestone", "firmdale", "fish", "fishing",
64
+ "fit", "fitness", "fj", "fk", "flickr", "flights", "flir", "florist", "flowers", "fly",
65
+ "fm", "fo", "foo", "food", "football", "ford", "forex", "forsale", "forum",
66
+ "foundation", "fox", "fr", "free", "fresenius", "frl", "frogans", "frontier", "ftr",
67
+ "fujitsu", "fun", "fund", "furniture", "futbol", "fyi", "ga", "gal", "gallery",
68
+ "gallo", "gallup", "game", "games", "gap", "garden", "gay", "gb", "gbiz", "gd", "gdn",
69
+ "ge", "gea", "gent", "genting", "george", "gf", "gg", "ggee", "gh", "gi", "gift",
70
+ "gifts", "gives", "giving", "gl", "glass", "gle", "global", "globo", "gm", "gmail",
71
+ "gmbh", "gmo", "gmx", "gn", "godaddy", "gold", "goldpoint", "golf", "goodyear",
72
+ "goog", "google", "gop", "got", "gov", "gp", "gq", "gr", "grainger", "graphics",
73
+ "gratis", "green", "gripe", "grocery", "group", "gs", "gt", "gu", "gucci", "guge",
74
+ "guide", "guitars", "guru", "gw", "gy", "hair", "hamburg", "hangout", "haus", "hbo",
75
+ "hdfc", "hdfcbank", "health", "healthcare", "help", "helsinki", "here", "hermes",
76
+ "hiphop", "hisamitsu", "hitachi", "hiv", "hk", "hkt", "hm", "hn", "hockey",
77
+ "holdings", "holiday", "homedepot", "homegoods", "homes", "homesense", "honda",
78
+ "horse", "hospital", "host", "hosting", "hot", "hotels", "hotmail", "house", "how",
79
+ "hr", "hsbc", "ht", "hu", "hughes", "hyatt", "hyundai", "ibm", "icbc", "ice", "icu",
80
+ "id", "ie", "ieee", "ifm", "ikano", "il", "im", "imamat", "imdb", "immo", "immobilien",
81
+ "in", "inc", "industries", "infiniti", "info", "ing", "ink", "institute", "insurance",
82
+ "insure", "int", "international", "intuit", "investments", "io", "ipiranga", "iq",
83
+ "ir", "irish", "is", "ismaili", "ist", "istanbul", "it", "itau", "itv", "jaguar",
84
+ "java", "jcb", "je", "jeep", "jetzt", "jewelry", "jio", "jll", "jm", "jmp", "jnj", "jo",
85
+ "jobs", "joburg", "jot", "joy", "jp", "jpmorgan", "jprs", "juegos", "juniper",
86
+ "kaufen", "kddi", "ke", "kerryhotels", "kerryproperties", "kfh", "kg", "kh", "ki",
87
+ "kia", "kids", "kim", "kindle", "kitchen", "kiwi", "km", "kn", "koeln", "komatsu",
88
+ "kosher", "kp", "kpmg", "kpn", "kr", "krd", "kred", "kuokgroup", "kw", "ky", "kyoto",
89
+ "kz", "la", "lacaixa", "lamborghini", "lamer", "land", "landrover", "lanxess",
90
+ "lasalle", "lat", "latino", "latrobe", "law", "lawyer", "lb", "lc", "lds", "lease",
91
+ "leclerc", "lefrak", "legal", "lego", "lexus", "lgbt", "li", "lidl", "life",
92
+ "lifeinsurance", "lifestyle", "lighting", "like", "lilly", "limited", "limo",
93
+ "lincoln", "link", "live", "living", "lk", "llc", "llp", "loan", "loans", "locker",
94
+ "locus", "lol", "london", "lotte", "lotto", "love", "lpl", "lplfinancial", "lr", "ls",
95
+ "lt", "ltd", "ltda", "lu", "lundbeck", "luxe", "luxury", "lv", "ly", "ma", "madrid",
96
+ "maif", "maison", "makeup", "man", "management", "mango", "map", "market",
97
+ "marketing", "markets", "marriott", "marshalls", "mattel", "mba", "mc", "mckinsey",
98
+ "md", "me", "med", "media", "meet", "melbourne", "meme", "memorial", "men", "menu",
99
+ "merckmsd", "mg", "mh", "miami", "microsoft", "mil", "mini", "mint", "mit",
100
+ "mitsubishi", "mk", "ml", "mlb", "mls", "mm", "mma", "mn", "mo", "mobi", "mobile",
101
+ "moda", "moe", "moi", "mom", "monash", "money", "monster", "mormon", "mortgage",
102
+ "moscow", "moto", "motorcycles", "mov", "movie", "mp", "mq", "mr", "ms", "msd", "mt",
103
+ "mtn", "mtr", "mu", "museum", "music", "mv", "mw", "mx", "my", "mz", "na", "nab",
104
+ "nagoya", "name", "navy", "nba", "nc", "ne", "nec", "net", "netbank", "netflix",
105
+ "network", "neustar", "new", "news", "next", "nextdirect", "nexus", "nf", "nfl", "ng",
106
+ "ngo", "nhk", "ni", "nico", "nike", "nikon", "ninja", "nissan", "nissay", "nl", "no",
107
+ "nokia", "norton", "now", "nowruz", "nowtv", "np", "nr", "nra", "nrw", "ntt", "nu",
108
+ "nyc", "nz", "obi", "observer", "office", "okinawa", "olayan", "olayangroup", "ollo",
109
+ "om", "omega", "one", "ong", "onl", "online", "ooo", "open", "oracle", "orange", "org",
110
+ "organic", "origins", "osaka", "otsuka", "ott", "ovh", "pa", "page", "panasonic",
111
+ "paris", "pars", "partners", "parts", "party", "pay", "pccw", "pe", "pet", "pf",
112
+ "pfizer", "pg", "ph", "pharmacy", "phd", "philips", "phone", "photo", "photography",
113
+ "photos", "physio", "pics", "pictet", "pictures", "pid", "pin", "ping", "pink",
114
+ "pioneer", "pizza", "pk", "pl", "place", "play", "playstation", "plumbing", "plus",
115
+ "pm", "pn", "pnc", "pohl", "poker", "politie", "porn", "post", "pr", "praxi", "press",
116
+ "prime", "pro", "prod", "productions", "prof", "progressive", "promo", "properties",
117
+ "property", "protection", "pru", "prudential", "ps", "pt", "pub", "pw", "pwc", "py",
118
+ "qa", "qpon", "quebec", "quest", "racing", "radio", "re", "read", "realestate",
119
+ "realtor", "realty", "recipes", "red", "redumbrella", "rehab", "reise", "reisen",
120
+ "reit", "reliance", "ren", "rent", "rentals", "repair", "report", "republican",
121
+ "rest", "restaurant", "review", "reviews", "rexroth", "rich", "richardli", "ricoh",
122
+ "ril", "rio", "rip", "ro", "rocks", "rodeo", "rogers", "room", "rs", "rsvp", "ru",
123
+ "rugby", "ruhr", "run", "rw", "rwe", "ryukyu", "sa", "saarland", "safe", "safety",
124
+ "sakura", "sale", "salon", "samsclub", "samsung", "sandvik", "sandvikcoromant",
125
+ "sanofi", "sap", "sarl", "sas", "save", "saxo", "sb", "sbi", "sbs", "sc", "scb",
126
+ "schaeffler", "schmidt", "scholarships", "school", "schule", "schwarz", "science",
127
+ "scot", "sd", "se", "search", "seat", "secure", "security", "seek", "select", "sener",
128
+ "services", "seven", "sew", "sex", "sexy", "sfr", "sg", "sh", "shangrila", "sharp",
129
+ "shell", "shia", "shiksha", "shoes", "shop", "shopping", "shouji", "show", "si",
130
+ "silk", "sina", "singles", "site", "sj", "sk", "ski", "skin", "sky", "skype", "sl",
131
+ "sling", "sm", "smart", "smile", "sn", "sncf", "so", "soccer", "social", "softbank",
132
+ "software", "sohu", "solar", "solutions", "song", "sony", "soy", "spa", "space",
133
+ "sport", "spot", "sr", "srl", "ss", "st", "stada", "staples", "star", "statebank",
134
+ "statefarm", "stc", "stcgroup", "stockholm", "storage", "store", "stream", "studio",
135
+ "study", "style", "su", "sucks", "supplies", "supply", "support", "surf", "surgery",
136
+ "suzuki", "sv", "swatch", "swiss", "sx", "sy", "sydney", "systems", "sz", "tab",
137
+ "taipei", "talk", "taobao", "target", "tatamotors", "tatar", "tattoo", "tax", "taxi",
138
+ "tc", "tci", "td", "tdk", "team", "tech", "technology", "tel", "temasek", "tennis",
139
+ "teva", "tf", "tg", "th", "thd", "theater", "theatre", "tiaa", "tickets", "tienda",
140
+ "tips", "tires", "tirol", "tj", "tjmaxx", "tjx", "tk", "tkmaxx", "tl", "tm", "tmall",
141
+ "tn", "to", "today", "tokyo", "tools", "top", "toray", "toshiba", "total", "tours",
142
+ "town", "toyota", "toys", "tr", "trade", "trading", "training", "travel", "travelers",
143
+ "travelersinsurance", "trust", "trv", "tt", "tube", "tui", "tunes", "tushu", "tv",
144
+ "tvs", "tw", "tz", "ua", "ubank", "ubs", "ug", "uk", "unicom", "university", "uno",
145
+ "uol", "ups", "us", "uy", "uz", "va", "vacations", "vana", "vanguard", "vc", "ve",
146
+ "vegas", "ventures", "verisign", "versicherung", "vet", "vg", "vi", "viajes", "video",
147
+ "vig", "viking", "villas", "vin", "vip", "virgin", "visa", "vision", "viva", "vivo",
148
+ "vlaanderen", "vn", "vodka", "volvo", "vote", "voting", "voto", "voyage", "vu",
149
+ "wales", "walmart", "walter", "wang", "wanggou", "watch", "watches", "weather",
150
+ "weatherchannel", "webcam", "weber", "website", "wed", "wedding", "weibo", "weir",
151
+ "wf", "whoswho", "wien", "wiki", "williamhill", "win", "windows", "wine", "winners",
152
+ "wme", "woodside", "work", "works", "world", "wow", "ws", "wtc", "wtf", "xbox",
153
+ "xerox", "xihuan", "xin", "xn--11b4c3d", "xn--1ck2e1b", "xn--1qqw23a",
154
+ "xn--2scrj9c", "xn--30rr7y", "xn--3bst00m", "xn--3ds443g", "xn--3e0b707e",
155
+ "xn--3hcrj9c", "xn--3pxu8k", "xn--42c2d9a", "xn--45br5cyl", "xn--45brj9c",
156
+ "xn--45q11c", "xn--4dbrk0ce", "xn--4gbrim", "xn--54b7fta0cc", "xn--55qw42g",
157
+ "xn--55qx5d", "xn--5su34j936bgsg", "xn--5tzm5g", "xn--6frz82g",
158
+ "xn--6qq986b3xl", "xn--80adxhks", "xn--80ao21a", "xn--80aqecdr1a",
159
+ "xn--80asehdb", "xn--80aswg", "xn--8y0a063a", "xn--90a3ac", "xn--90ae",
160
+ "xn--90ais", "xn--9dbq2a", "xn--9et52u", "xn--9krt00a", "xn--b4w605ferd",
161
+ "xn--bck1b9a5dre4c", "xn--c1avg", "xn--c2br7g", "xn--cck2b3b",
162
+ "xn--cckwcxetd", "xn--cg4bki", "xn--clchc0ea0b2g2a9gcd", "xn--czr694b",
163
+ "xn--czrs0t", "xn--czru2d", "xn--d1acj3b", "xn--d1alf", "xn--e1a4c",
164
+ "xn--eckvdtc9d", "xn--efvy88h", "xn--fct429k", "xn--fhbei", "xn--fiq228c5hs",
165
+ "xn--fiq64b", "xn--fiqs8s", "xn--fiqz9s", "xn--fjq720a", "xn--flw351e",
166
+ "xn--fpcrj9c3d", "xn--fzc2c9e2c", "xn--fzys8d69uvgm", "xn--g2xx48c",
167
+ "xn--gckr3f0f", "xn--gecrj9c", "xn--gk3at1e", "xn--h2breg3eve", "xn--h2brj9c",
168
+ "xn--h2brj9c8c", "xn--hxt814e", "xn--i1b6b1a6a2e", "xn--imr513n",
169
+ "xn--io0a7i", "xn--j1aef", "xn--j1amh", "xn--j6w193g", "xn--jlq480n2rg",
170
+ "xn--jvr189m", "xn--kcrx77d1x4a", "xn--kprw13d", "xn--kpry57d", "xn--kput3i",
171
+ "xn--l1acc", "xn--lgbbat1ad8j", "xn--mgb9awbf", "xn--mgba3a3ejt",
172
+ "xn--mgba3a4f16a", "xn--mgba7c0bbn0a", "xn--mgbaam7a8h", "xn--mgbab2bd",
173
+ "xn--mgbah1a3hjkrd", "xn--mgbai9azgqp6j", "xn--mgbayh7gpa", "xn--mgbbh1a",
174
+ "xn--mgbbh1a71e", "xn--mgbc0a9azcg", "xn--mgbca7dzdo", "xn--mgbcpq6gpa1a",
175
+ "xn--mgberp4a5d4ar", "xn--mgbgu82a", "xn--mgbi4ecexp", "xn--mgbpl2fh",
176
+ "xn--mgbt3dhd", "xn--mgbtx2b", "xn--mgbx4cd0ab", "xn--mix891f",
177
+ "xn--mk1bu44c", "xn--mxtq1m", "xn--ngbc5azd", "xn--ngbe9e0a", "xn--ngbrx",
178
+ "xn--node", "xn--nqv7f", "xn--nqv7fs00ema", "xn--nyqy26a", "xn--o3cw4h",
179
+ "xn--ogbpf8fl", "xn--otu796d", "xn--p1acf", "xn--p1ai", "xn--pgbs0dh",
180
+ "xn--pssy2u", "xn--q7ce6a", "xn--q9jyb4c", "xn--qcka1pmc", "xn--qxa6a",
181
+ "xn--qxam", "xn--rhqv96g", "xn--rovu88b", "xn--rvc1e0am3e", "xn--s9brj9c",
182
+ "xn--ses554g", "xn--t60b56a", "xn--tckwe", "xn--tiq49xqyj", "xn--unup4y",
183
+ "xn--vermgensberater-ctb", "xn--vermgensberatung-pwb", "xn--vhquv",
184
+ "xn--vuq861b", "xn--w4r85el8fhu5dnra", "xn--w4rs40l", "xn--wgbh1c",
185
+ "xn--wgbl6a", "xn--xhq521b", "xn--xkc2al3hye2a", "xn--xkc2dl3a5ee0h",
186
+ "xn--y9a3aq", "xn--yfro4i67o", "xn--ygbi2ammx", "xn--zfr164b", "xxx", "xyz",
187
+ "yachts", "yahoo", "yamaxun", "yandex", "ye", "yodobashi", "yoga", "yokohama", "you",
188
+ "youtube", "yt", "yun", "za", "zappos", "zara", "zero", "zip", "zm", "zone", "zuerich",
189
+ "zw",
190
+ ]);
191
+ /**
192
+ * Returns true if the given name is a public TLD (case-insensitive).
193
+ * Used to prevent creating networks that would shadow real DNS.
194
+ */
195
+ export const isPublicTld = (name) => PUBLIC_TLDS.has(name.toLowerCase());
196
+ // =============================================================================
14
197
  // assertNotRouter
15
198
  // =============================================================================
16
199
  /**
@@ -1,5 +1,15 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import { type FlyApp, type FlyAppInfo, type FlyIp, type FlyMachine } from "../schemas/fly.js";
3
+ /**
4
+ * Thrown when a `fly deploy` command fails. Carries the raw stderr so callers
5
+ * can surface it through `out` (respecting JSON mode) instead of printing
6
+ * directly.
7
+ */
8
+ export declare class FlyDeployError extends Error {
9
+ /** Last meaningful line from flyctl stderr. */
10
+ readonly detail: string;
11
+ constructor(app: string, stderr: string);
12
+ }
3
13
  export type MachineSize = "shared-cpu-1x" | "shared-cpu-2x" | "shared-cpu-4x";
4
14
  export interface MachineConfig {
5
15
  size: MachineSize;
@@ -1 +1 @@
1
- {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../../src/src/providers/fly.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fly.d.ts","sourceRoot":"","sources":["../../../src/src/providers/fly.ts"],"names":[],"mappings":"AAGA,OAAO,yBAAyB,CAAC;AAajC,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EAIf,KAAK,KAAK,EAEV,KAAK,UAAU,EAMhB,MAAM,mBAAmB,CAAC;AAa3B;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMxC;AAmBD,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,aAAa,GACxB,MAAM,WAAW,KAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CASlC,CAAC;AAMF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,MAAM,WAAW,WAAW;IAC1B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,UAAU,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5C,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACjD,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1E,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,UAAU,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,YAAY,CACV,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;CACzD;AAMD,eAAO,MAAM,iBAAiB,QAAO,WA6bpC,CAAC;AAMF,eAAO,MAAM,gBAAgB,GAC3B,SAAS,MAAM,EACf,cAAc,MAAM,KACnB,MAEF,CAAC"}
@@ -12,6 +12,35 @@ import { fileExists } from "../../lib/cli.js";
12
12
  // Constants
13
13
  // =============================================================================
14
14
  const ROUTER_APP_PREFIX = "ambit-";
15
+ // =============================================================================
16
+ // Deploy Error
17
+ // =============================================================================
18
+ /**
19
+ * Thrown when a `fly deploy` command fails. Carries the raw stderr so callers
20
+ * can surface it through `out` (respecting JSON mode) instead of printing
21
+ * directly.
22
+ */
23
+ export class FlyDeployError extends Error {
24
+ /** Last meaningful line from flyctl stderr. */
25
+ detail;
26
+ constructor(app, stderr) {
27
+ const detail = extractErrorDetail(stderr);
28
+ super(`Deploy Failed for '${app}'`);
29
+ this.name = "FlyDeployError";
30
+ this.detail = detail;
31
+ }
32
+ }
33
+ /**
34
+ * Pull the last non-empty, non-decoration line from fly stderr.
35
+ * Fly often prints progress lines then the actual error at the end.
36
+ */
37
+ const extractErrorDetail = (stderr) => {
38
+ const lines = stderr
39
+ .split("\n")
40
+ .map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").trim())
41
+ .filter((l) => l.length > 0 && !l.startsWith("-->") && l !== "Error");
42
+ return lines[lines.length - 1] ?? "unknown error";
43
+ };
15
44
  export const getSizeConfig = (size) => {
16
45
  switch (size) {
17
46
  case "shared-cpu-1x":
@@ -226,8 +255,7 @@ export const createFlyProvider = () => {
226
255
  }
227
256
  const result = await runCommand(args);
228
257
  if (!result.success) {
229
- console.error(result.stderr);
230
- return die(`Deploy Failed for '${app}'`);
258
+ throw new FlyDeployError(app, result.stderr);
231
259
  }
232
260
  },
233
261
  async listIps(app) {
@@ -306,8 +334,7 @@ export const createFlyProvider = () => {
306
334
  }
307
335
  const result = await runCommand(args);
308
336
  if (!result.success) {
309
- console.error(result.stderr);
310
- return die(`Deploy Failed for '${app}'`);
337
+ throw new FlyDeployError(app, result.stderr);
311
338
  }
312
339
  },
313
340
  async listCerts(app) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardelli/ambit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Tailscale subnet router manager for Fly.io custom networks",
5
5
  "license": "MIT",
6
6
  "module": "./esm/src/providers/fly.js",
@@ -38,6 +38,9 @@
38
38
  "./src/discovery": {
39
39
  "import": "./esm/src/discovery.js"
40
40
  },
41
+ "./src/guard": {
42
+ "import": "./esm/src/guard.js"
43
+ },
41
44
  "./src/resolve": {
42
45
  "import": "./esm/src/resolve.js"
43
46
  }