@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
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// Destroy Command - Destroy Networks or Apps
|
|
3
|
-
// =============================================================================
|
|
4
|
-
import * as dntShim from "../../../_dnt.shims.js";
|
|
5
|
-
import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
|
|
6
|
-
import { bold, confirm } from "../../../lib/cli.js";
|
|
7
|
-
import { createOutput } from "../../../lib/output.js";
|
|
8
|
-
import { registerCommand } from "../mod.js";
|
|
9
|
-
import { createFlyProvider } from "../../providers/fly.js";
|
|
10
|
-
import { createTailscaleProvider } from "../../providers/tailscale.js";
|
|
11
|
-
import { checkDependencies } from "../../credentials.js";
|
|
12
|
-
import { findRouterApp, findWorkloadApp, listWorkloadAppsOnNetwork, } from "../../discovery.js";
|
|
13
|
-
import { resolveOrg } from "../../resolve.js";
|
|
14
|
-
import { assertNotRouter } from "../../guard.js";
|
|
15
|
-
// =============================================================================
|
|
16
|
-
// Top-Level Help
|
|
17
|
-
// =============================================================================
|
|
18
|
-
const showDestroyHelp = () => {
|
|
19
|
-
console.log(`
|
|
20
|
-
${bold("ambit destroy")} - Destroy Networks or Apps
|
|
21
|
-
|
|
22
|
-
${bold("USAGE")}
|
|
23
|
-
ambit destroy network <name> [options]
|
|
24
|
-
ambit destroy app <app>.<network> [options]
|
|
25
|
-
|
|
26
|
-
${bold("SUBCOMMANDS")}
|
|
27
|
-
network Tear down a router, clean up DNS and tailnet device
|
|
28
|
-
app Destroy a workload app on a network
|
|
29
|
-
|
|
30
|
-
${bold("OPTIONS")}
|
|
31
|
-
--help Show help for a subcommand
|
|
32
|
-
|
|
33
|
-
${bold("EXAMPLES")}
|
|
34
|
-
ambit destroy network browsers
|
|
35
|
-
ambit destroy app my-app.browsers
|
|
36
|
-
ambit destroy app my-app --network browsers
|
|
37
|
-
|
|
38
|
-
Run 'ambit destroy network --help' or 'ambit destroy app --help' for details.
|
|
39
|
-
`);
|
|
40
|
-
};
|
|
41
|
-
// =============================================================================
|
|
42
|
-
// Destroy Network
|
|
43
|
-
// =============================================================================
|
|
44
|
-
const destroyNetwork = async (argv) => {
|
|
45
|
-
const args = parseArgs(argv, {
|
|
46
|
-
string: ["network", "org"],
|
|
47
|
-
boolean: ["help", "yes", "json"],
|
|
48
|
-
alias: { y: "yes" },
|
|
49
|
-
});
|
|
50
|
-
if (args.help) {
|
|
51
|
-
console.log(`
|
|
52
|
-
${bold("ambit destroy network")} - Tear Down Router
|
|
53
|
-
|
|
54
|
-
${bold("USAGE")}
|
|
55
|
-
ambit destroy network <name> [--org <org>] [--yes] [--json]
|
|
56
|
-
|
|
57
|
-
${bold("OPTIONS")}
|
|
58
|
-
--org <org> Fly.io organization slug
|
|
59
|
-
-y, --yes Skip confirmation prompts
|
|
60
|
-
--json Output as JSON
|
|
61
|
-
|
|
62
|
-
${bold("EXAMPLES")}
|
|
63
|
-
ambit destroy network browsers
|
|
64
|
-
ambit destroy network browsers --org my-org --yes
|
|
65
|
-
`);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const out = createOutput(args.json);
|
|
69
|
-
// Accept network as positional or --network flag (backward compat)
|
|
70
|
-
const network = (typeof args._[0] === "string" ? args._[0] : undefined) || args.network;
|
|
71
|
-
if (!network) {
|
|
72
|
-
return out.die("Network name required. Usage: ambit destroy network <name>");
|
|
73
|
-
}
|
|
74
|
-
// ===========================================================================
|
|
75
|
-
// Prerequisites
|
|
76
|
-
// ===========================================================================
|
|
77
|
-
const { tailscaleKey } = await checkDependencies(out);
|
|
78
|
-
const fly = createFlyProvider();
|
|
79
|
-
await fly.ensureAuth({ interactive: !args.json });
|
|
80
|
-
const tailscale = createTailscaleProvider("-", tailscaleKey);
|
|
81
|
-
const org = await resolveOrg(fly, args, out);
|
|
82
|
-
// ===========================================================================
|
|
83
|
-
// Discover Router
|
|
84
|
-
// ===========================================================================
|
|
85
|
-
const spinner = out.spinner("Discovering Router");
|
|
86
|
-
const app = await findRouterApp(fly, org, network);
|
|
87
|
-
if (!app) {
|
|
88
|
-
spinner.fail("Router Not Found");
|
|
89
|
-
return out.die(`No Router Found for Network '${network}'`);
|
|
90
|
-
}
|
|
91
|
-
spinner.success(`Found Router: ${app.appName}`);
|
|
92
|
-
let tsDevice = null;
|
|
93
|
-
try {
|
|
94
|
-
tsDevice = await tailscale.getDeviceByHostname(app.appName);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
/* device may not exist */
|
|
98
|
-
}
|
|
99
|
-
const tag = tsDevice?.tags?.[0] ?? null;
|
|
100
|
-
// ===========================================================================
|
|
101
|
-
// Check for Workload Apps on This Network
|
|
102
|
-
// ===========================================================================
|
|
103
|
-
const workloadApps = await listWorkloadAppsOnNetwork(fly, org, network);
|
|
104
|
-
// ===========================================================================
|
|
105
|
-
// Confirm
|
|
106
|
-
// ===========================================================================
|
|
107
|
-
out.blank()
|
|
108
|
-
.header("ambit Destroy Network")
|
|
109
|
-
.blank()
|
|
110
|
-
.text(` Network: ${network}`)
|
|
111
|
-
.text(` Router App: ${app.appName}`)
|
|
112
|
-
.text(` Tag: ${tag ?? "unknown"}`)
|
|
113
|
-
.blank();
|
|
114
|
-
if (workloadApps.length > 0) {
|
|
115
|
-
out.warn(`${workloadApps.length} workload app(s) still on network '${network}':`);
|
|
116
|
-
for (const wa of workloadApps) {
|
|
117
|
-
out.text(` - ${wa.appName}`);
|
|
118
|
-
}
|
|
119
|
-
out.blank();
|
|
120
|
-
out.dim("These apps will lose connectivity when the router is destroyed.");
|
|
121
|
-
out.dim(`Consider destroying them first with: ambit destroy app <name>.${network}`);
|
|
122
|
-
out.blank();
|
|
123
|
-
}
|
|
124
|
-
if (!args.yes && !args.json) {
|
|
125
|
-
const confirmed = await confirm("Destroy this router?");
|
|
126
|
-
if (!confirmed) {
|
|
127
|
-
out.text("Cancelled.");
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
out.blank();
|
|
131
|
-
}
|
|
132
|
-
// ===========================================================================
|
|
133
|
-
// Tear Down
|
|
134
|
-
// ===========================================================================
|
|
135
|
-
const dnsSpinner = out.spinner("Clearing Split DNS");
|
|
136
|
-
try {
|
|
137
|
-
await tailscale.clearSplitDns(network);
|
|
138
|
-
dnsSpinner.success("Split DNS Cleared");
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
dnsSpinner.fail("Split DNS Already Cleared");
|
|
142
|
-
}
|
|
143
|
-
const deviceSpinner = out.spinner("Removing Tailscale Device");
|
|
144
|
-
try {
|
|
145
|
-
const device = await tailscale.getDeviceByHostname(app.appName);
|
|
146
|
-
if (device) {
|
|
147
|
-
await tailscale.deleteDevice(device.id);
|
|
148
|
-
deviceSpinner.success("Tailscale Device Removed");
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
deviceSpinner.success("Tailscale Device Not Found (Already Removed)");
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
deviceSpinner.fail("Could Not Remove Tailscale Device");
|
|
156
|
-
}
|
|
157
|
-
const appSpinner = out.spinner("Destroying Fly App");
|
|
158
|
-
try {
|
|
159
|
-
await fly.deleteApp(app.appName);
|
|
160
|
-
appSpinner.success("Fly App Destroyed");
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
appSpinner.fail("Could Not Destroy Fly App");
|
|
164
|
-
}
|
|
165
|
-
// ===========================================================================
|
|
166
|
-
// Done
|
|
167
|
-
// ===========================================================================
|
|
168
|
-
out.done({
|
|
169
|
-
destroyed: true,
|
|
170
|
-
appName: app.appName,
|
|
171
|
-
workloadAppsWarned: workloadApps.length,
|
|
172
|
-
});
|
|
173
|
-
out.ok("Router Destroyed");
|
|
174
|
-
if (tag) {
|
|
175
|
-
out.blank()
|
|
176
|
-
.dim("If you added ACL policy entries for this router, remember to remove:")
|
|
177
|
-
.dim(` tagOwners: ${tag}`)
|
|
178
|
-
.dim(` autoApprovers: routes for ${tag}`)
|
|
179
|
-
.dim(` acls: rules referencing ${tag}`)
|
|
180
|
-
.blank();
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
out.blank()
|
|
184
|
-
.dim("If you added ACL policy entries for this router, remember to remove")
|
|
185
|
-
.dim("the associated tag from tagOwners, autoApprovers, and acls.")
|
|
186
|
-
.blank();
|
|
187
|
-
}
|
|
188
|
-
out.print();
|
|
189
|
-
};
|
|
190
|
-
// =============================================================================
|
|
191
|
-
// Destroy App
|
|
192
|
-
// =============================================================================
|
|
193
|
-
const destroyApp = async (argv) => {
|
|
194
|
-
const args = parseArgs(argv, {
|
|
195
|
-
string: ["network", "org"],
|
|
196
|
-
boolean: ["help", "yes", "json"],
|
|
197
|
-
alias: { y: "yes" },
|
|
198
|
-
});
|
|
199
|
-
if (args.help) {
|
|
200
|
-
console.log(`
|
|
201
|
-
${bold("ambit destroy app")} - Destroy a Workload App
|
|
202
|
-
|
|
203
|
-
${bold("USAGE")}
|
|
204
|
-
ambit destroy app <app>.<network> [--org <org>] [--yes] [--json]
|
|
205
|
-
ambit destroy app <app> --network <name> [--org <org>] [--yes] [--json]
|
|
206
|
-
|
|
207
|
-
${bold("OPTIONS")}
|
|
208
|
-
--network <name> Target network (if not using dot syntax)
|
|
209
|
-
--org <org> Fly.io organization slug
|
|
210
|
-
-y, --yes Skip confirmation prompts
|
|
211
|
-
--json Output as JSON
|
|
212
|
-
|
|
213
|
-
${bold("EXAMPLES")}
|
|
214
|
-
ambit destroy app my-app.browsers
|
|
215
|
-
ambit destroy app my-app --network browsers --yes
|
|
216
|
-
`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const out = createOutput(args.json);
|
|
220
|
-
// ===========================================================================
|
|
221
|
-
// Parse App & Network
|
|
222
|
-
// ===========================================================================
|
|
223
|
-
const appArg = args._[0];
|
|
224
|
-
if (!appArg || typeof appArg !== "string") {
|
|
225
|
-
return out.die("Missing app name. Usage: ambit destroy app <app>.<network>");
|
|
226
|
-
}
|
|
227
|
-
let app;
|
|
228
|
-
let network;
|
|
229
|
-
if (appArg.includes(".")) {
|
|
230
|
-
const parts = appArg.split(".");
|
|
231
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
232
|
-
return out.die(`'${appArg}' should have exactly one dot, like my-app.my-network`);
|
|
233
|
-
}
|
|
234
|
-
if (args.network) {
|
|
235
|
-
return out.die(`Network is already part of the name ('${appArg}'), --network is not needed`);
|
|
236
|
-
}
|
|
237
|
-
app = parts[0];
|
|
238
|
-
network = parts[1];
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
app = appArg;
|
|
242
|
-
if (!args.network) {
|
|
243
|
-
return out.die(`Missing network. Use: ambit destroy app ${app}.<network>`);
|
|
244
|
-
}
|
|
245
|
-
network = args.network;
|
|
246
|
-
}
|
|
247
|
-
try {
|
|
248
|
-
assertNotRouter(app);
|
|
249
|
-
}
|
|
250
|
-
catch (e) {
|
|
251
|
-
return out.die(e instanceof Error ? e.message : String(e));
|
|
252
|
-
}
|
|
253
|
-
// ===========================================================================
|
|
254
|
-
// Prerequisites
|
|
255
|
-
// ===========================================================================
|
|
256
|
-
const { tailscaleKey: _tailscaleKey } = await checkDependencies(out);
|
|
257
|
-
const fly = createFlyProvider();
|
|
258
|
-
await fly.ensureAuth({ interactive: !args.json });
|
|
259
|
-
const org = await resolveOrg(fly, args, out);
|
|
260
|
-
// ===========================================================================
|
|
261
|
-
// Discover App
|
|
262
|
-
// ===========================================================================
|
|
263
|
-
const spinner = out.spinner("Discovering App");
|
|
264
|
-
const workloadApp = await findWorkloadApp(fly, org, app, network);
|
|
265
|
-
if (!workloadApp) {
|
|
266
|
-
spinner.fail("App Not Found");
|
|
267
|
-
// Check if app exists on a different network
|
|
268
|
-
const anyApp = await findWorkloadApp(fly, org, app);
|
|
269
|
-
if (anyApp) {
|
|
270
|
-
return out.die(`App '${app}' exists on network '${anyApp.network}', not '${network}'`);
|
|
271
|
-
}
|
|
272
|
-
return out.die(`No app '${app}' found on network '${network}'`);
|
|
273
|
-
}
|
|
274
|
-
spinner.success(`Found App: ${workloadApp.appName} (network: ${workloadApp.network})`);
|
|
275
|
-
// ===========================================================================
|
|
276
|
-
// Confirm
|
|
277
|
-
// ===========================================================================
|
|
278
|
-
out.blank()
|
|
279
|
-
.header("ambit Destroy App")
|
|
280
|
-
.blank()
|
|
281
|
-
.text(` App: ${workloadApp.appName}`)
|
|
282
|
-
.text(` Network: ${workloadApp.network}`)
|
|
283
|
-
.blank();
|
|
284
|
-
if (!args.yes && !args.json) {
|
|
285
|
-
const confirmed = await confirm(`Destroy app '${app}' on network '${network}'?`);
|
|
286
|
-
if (!confirmed) {
|
|
287
|
-
out.text("Cancelled.");
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
out.blank();
|
|
291
|
-
}
|
|
292
|
-
// ===========================================================================
|
|
293
|
-
// Destroy
|
|
294
|
-
// ===========================================================================
|
|
295
|
-
const appSpinner = out.spinner("Destroying Fly App");
|
|
296
|
-
try {
|
|
297
|
-
await fly.deleteApp(app);
|
|
298
|
-
appSpinner.success("Fly App Destroyed");
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
appSpinner.fail("Could Not Destroy Fly App");
|
|
302
|
-
}
|
|
303
|
-
// ===========================================================================
|
|
304
|
-
// Done
|
|
305
|
-
// ===========================================================================
|
|
306
|
-
out.done({ destroyed: true, appName: app, network });
|
|
307
|
-
out.ok("App Destroyed");
|
|
308
|
-
out.blank();
|
|
309
|
-
out.print();
|
|
310
|
-
};
|
|
311
|
-
// =============================================================================
|
|
312
|
-
// Dispatcher
|
|
313
|
-
// =============================================================================
|
|
314
|
-
const destroy = async (argv) => {
|
|
315
|
-
const subcommand = typeof argv[0] === "string" ? argv[0] : undefined;
|
|
316
|
-
if (subcommand === "network") {
|
|
317
|
-
return destroyNetwork(argv.slice(1));
|
|
318
|
-
}
|
|
319
|
-
if (subcommand === "app") {
|
|
320
|
-
return destroyApp(argv.slice(1));
|
|
321
|
-
}
|
|
322
|
-
// Handle --help at the top level
|
|
323
|
-
const args = parseArgs(argv, { boolean: ["help"] });
|
|
324
|
-
if (args.help) {
|
|
325
|
-
showDestroyHelp();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// No valid subcommand
|
|
329
|
-
showDestroyHelp();
|
|
330
|
-
dntShim.Deno.exit(1);
|
|
331
|
-
};
|
|
332
|
-
// =============================================================================
|
|
333
|
-
// Register Command
|
|
334
|
-
// =============================================================================
|
|
335
|
-
registerCommand({
|
|
336
|
-
name: "destroy",
|
|
337
|
-
description: "Destroy a network (router) or a workload app",
|
|
338
|
-
usage: "ambit destroy network|app <name> [options]",
|
|
339
|
-
run: destroy,
|
|
340
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/commands/doctor.ts"],"names":[],"mappings":""}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// Doctor Command - Verify Environment and Infrastructure Health
|
|
3
|
-
// =============================================================================
|
|
4
|
-
import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
|
|
5
|
-
import { bold } from "../../../lib/cli.js";
|
|
6
|
-
import { createOutput } from "../../../lib/output.js";
|
|
7
|
-
import { runCommand } from "../../../lib/command.js";
|
|
8
|
-
import { registerCommand } from "../mod.js";
|
|
9
|
-
import { createFlyProvider } from "../../providers/fly.js";
|
|
10
|
-
import { createTailscaleProvider, isAcceptRoutesEnabled, isTailscaleInstalled, } from "../../providers/tailscale.js";
|
|
11
|
-
import { checkDependencies } from "../../credentials.js";
|
|
12
|
-
import { findRouterApp, getRouterMachineInfo, getRouterTailscaleInfo, listRouterApps, } from "../../discovery.js";
|
|
13
|
-
import { resolveOrg } from "../../resolve.js";
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Doctor Command
|
|
16
|
-
// =============================================================================
|
|
17
|
-
const doctor = async (argv) => {
|
|
18
|
-
const args = parseArgs(argv, {
|
|
19
|
-
string: ["network", "org"],
|
|
20
|
-
boolean: ["help", "json"],
|
|
21
|
-
});
|
|
22
|
-
if (args.help) {
|
|
23
|
-
console.log(`
|
|
24
|
-
${bold("ambit doctor")} - Verify Environment and Infrastructure Health
|
|
25
|
-
|
|
26
|
-
${bold("USAGE")}
|
|
27
|
-
ambit doctor [--network <name>] [--org <org>] [--json]
|
|
28
|
-
|
|
29
|
-
${bold("OPTIONS")}
|
|
30
|
-
--network <name> Check a specific router (otherwise checks all)
|
|
31
|
-
--org <org> Fly.io organization slug
|
|
32
|
-
--json Output as JSON
|
|
33
|
-
|
|
34
|
-
${bold("CHECKS")}
|
|
35
|
-
- Tailscale CLI installed and connected
|
|
36
|
-
- Accept-routes enabled
|
|
37
|
-
- Router(s) created and running
|
|
38
|
-
- Router(s) visible in tailnet
|
|
39
|
-
`);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const out = createOutput(args.json);
|
|
43
|
-
out.blank().header("ambit Doctor").blank();
|
|
44
|
-
const results = [];
|
|
45
|
-
const report = (name, ok, hint) => {
|
|
46
|
-
results.push({ name, ok, hint });
|
|
47
|
-
if (ok) {
|
|
48
|
-
out.ok(name);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
out.err(name);
|
|
52
|
-
if (hint)
|
|
53
|
-
out.dim(` ${hint}`);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
// =========================================================================
|
|
57
|
-
// Prerequisites
|
|
58
|
-
// =========================================================================
|
|
59
|
-
const { tailscaleKey } = await checkDependencies(out);
|
|
60
|
-
const fly = createFlyProvider();
|
|
61
|
-
await fly.ensureAuth({ interactive: !args.json });
|
|
62
|
-
const tailscale = createTailscaleProvider("-", tailscaleKey);
|
|
63
|
-
const org = await resolveOrg(fly, args, out);
|
|
64
|
-
// =========================================================================
|
|
65
|
-
// Local Checks
|
|
66
|
-
// =========================================================================
|
|
67
|
-
report("Tailscale Installed", await isTailscaleInstalled(), "Install from https://tailscale.com/download");
|
|
68
|
-
const tsStatus = await runCommand(["tailscale", "status", "--json"]);
|
|
69
|
-
let tsConnected = false;
|
|
70
|
-
if (tsStatus.success) {
|
|
71
|
-
try {
|
|
72
|
-
const parsed = JSON.parse(tsStatus.stdout);
|
|
73
|
-
tsConnected = parsed.BackendState === "Running";
|
|
74
|
-
}
|
|
75
|
-
catch { /* ignore */ }
|
|
76
|
-
}
|
|
77
|
-
report("Tailscale Connected", tsConnected, "Run: tailscale up");
|
|
78
|
-
report("Accept Routes Enabled", await isAcceptRoutesEnabled(), "Run: sudo tailscale set --accept-routes");
|
|
79
|
-
// =========================================================================
|
|
80
|
-
// Router Checks
|
|
81
|
-
// =========================================================================
|
|
82
|
-
if (args.network) {
|
|
83
|
-
const app = await findRouterApp(fly, org, args.network);
|
|
84
|
-
report(`Router Exists (${args.network})`, app !== null, `Create with: ambit create ${args.network}`);
|
|
85
|
-
const machine = app ? await getRouterMachineInfo(fly, app.appName) : null;
|
|
86
|
-
report(`Router Running (${args.network})`, machine?.state === "started", machine ? `Machine state: ${machine.state}` : "No machine found");
|
|
87
|
-
const ts = app
|
|
88
|
-
? await getRouterTailscaleInfo(tailscale, app.appName)
|
|
89
|
-
: null;
|
|
90
|
-
report(`Router in Tailnet (${args.network})`, ts !== null, "Router may still be starting, or check router logs");
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
const routerApps = await listRouterApps(fly, org);
|
|
94
|
-
if (routerApps.length === 0) {
|
|
95
|
-
report("Routers Discovered", false, "Run: ambit create <network>");
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
let runningCount = 0;
|
|
99
|
-
let inTailnetCount = 0;
|
|
100
|
-
for (const app of routerApps) {
|
|
101
|
-
const machine = await getRouterMachineInfo(fly, app.appName);
|
|
102
|
-
if (machine?.state === "started")
|
|
103
|
-
runningCount++;
|
|
104
|
-
const ts = await getRouterTailscaleInfo(tailscale, app.appName);
|
|
105
|
-
if (ts)
|
|
106
|
-
inTailnetCount++;
|
|
107
|
-
}
|
|
108
|
-
report("Routers Discovered", runningCount > 0, `${routerApps.length} Router(s): ${runningCount} Running, ${inTailnetCount} In Tailnet`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// =========================================================================
|
|
112
|
-
// Summary
|
|
113
|
-
// =========================================================================
|
|
114
|
-
const issues = results.filter((r) => !r.ok).length;
|
|
115
|
-
if (issues === 0) {
|
|
116
|
-
out.done({ checks: results });
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
out.fail(`${issues} Issue${issues > 1 ? "s" : ""} Found`, {
|
|
120
|
-
checks: results,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
out.blank();
|
|
124
|
-
if (issues === 0) {
|
|
125
|
-
out.text("All Checks Passed.");
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
out.text(`${issues} Issue${issues > 1 ? "s" : ""} Found.`);
|
|
129
|
-
}
|
|
130
|
-
out.blank();
|
|
131
|
-
out.print();
|
|
132
|
-
};
|
|
133
|
-
// =============================================================================
|
|
134
|
-
// Register Command
|
|
135
|
-
// =============================================================================
|
|
136
|
-
registerCommand({
|
|
137
|
-
name: "doctor",
|
|
138
|
-
description: "Check that Tailscale and the router are working correctly",
|
|
139
|
-
usage: "ambit doctor [--network <name>] [--org <org>] [--json]",
|
|
140
|
-
run: doctor,
|
|
141
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/src/cli/commands/status.ts"],"names":[],"mappings":""}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// =============================================================================
|
|
2
|
-
// Status Command - Show Router Status
|
|
3
|
-
// =============================================================================
|
|
4
|
-
import { parseArgs } from "../../../deps/jsr.io/@std/cli/1.0.28/mod.js";
|
|
5
|
-
import { Table } from "../../../deps/jsr.io/@cliffy/table/1.0.0/mod.js";
|
|
6
|
-
import { bold } from "../../../lib/cli.js";
|
|
7
|
-
import { createOutput } from "../../../lib/output.js";
|
|
8
|
-
import { registerCommand } from "../mod.js";
|
|
9
|
-
import { createFlyProvider } from "../../providers/fly.js";
|
|
10
|
-
import { createTailscaleProvider, } from "../../providers/tailscale.js";
|
|
11
|
-
import { checkDependencies } from "../../credentials.js";
|
|
12
|
-
import { findRouterApp, getRouterMachineInfo, getRouterTailscaleInfo, listRouterApps, } from "../../discovery.js";
|
|
13
|
-
import { resolveOrg } from "../../resolve.js";
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Status Command
|
|
16
|
-
// =============================================================================
|
|
17
|
-
const status = async (argv) => {
|
|
18
|
-
const args = parseArgs(argv, {
|
|
19
|
-
string: ["network", "org"],
|
|
20
|
-
boolean: ["help", "json"],
|
|
21
|
-
});
|
|
22
|
-
if (args.help) {
|
|
23
|
-
console.log(`
|
|
24
|
-
${bold("ambit status")} - Show Router Status
|
|
25
|
-
|
|
26
|
-
${bold("USAGE")}
|
|
27
|
-
ambit status [--network <name>] [--org <org>] [--json]
|
|
28
|
-
|
|
29
|
-
${bold("OPTIONS")}
|
|
30
|
-
--network <name> Show detailed status for a specific network
|
|
31
|
-
--org <org> Fly.io organization slug
|
|
32
|
-
--json Output as JSON
|
|
33
|
-
|
|
34
|
-
${bold("EXAMPLES")}
|
|
35
|
-
ambit status Show summary of all routers
|
|
36
|
-
ambit status --network browsers Show detailed status for one router
|
|
37
|
-
`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
// =========================================================================
|
|
41
|
-
// Prerequisites
|
|
42
|
-
// =========================================================================
|
|
43
|
-
const { tailscaleKey } = await checkDependencies(createOutput(args.json));
|
|
44
|
-
const fly = createFlyProvider();
|
|
45
|
-
await fly.ensureAuth({ interactive: !args.json });
|
|
46
|
-
const tailscale = createTailscaleProvider("-", tailscaleKey);
|
|
47
|
-
// =========================================================================
|
|
48
|
-
// Status
|
|
49
|
-
// =========================================================================
|
|
50
|
-
if (args.network) {
|
|
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 ? `socks5://[${machine.privateIp}]:1080` : "unknown"}`);
|
|
82
|
-
if (machine?.subnet) {
|
|
83
|
-
out.text(` Subnet: ${machine.subnet}`);
|
|
84
|
-
}
|
|
85
|
-
out.blank();
|
|
86
|
-
if (ts) {
|
|
87
|
-
out.text(` Tailscale IP: ${ts.ip}`)
|
|
88
|
-
.text(` Online: ${ts.online ? "yes" : "no"}`);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
out.text(" Tailscale: Not Found in Tailnet");
|
|
92
|
-
}
|
|
93
|
-
out.blank();
|
|
94
|
-
out.done({
|
|
95
|
-
network: app.network,
|
|
96
|
-
router: app,
|
|
97
|
-
machine,
|
|
98
|
-
tag,
|
|
99
|
-
tailscale: ts,
|
|
100
|
-
});
|
|
101
|
-
out.print();
|
|
102
|
-
};
|
|
103
|
-
// =============================================================================
|
|
104
|
-
// Summary Table of All Routers
|
|
105
|
-
// =============================================================================
|
|
106
|
-
const showAllStatus = async (fly, tailscale, args) => {
|
|
107
|
-
const out = createOutput(args.json);
|
|
108
|
-
const org = await resolveOrg(fly, args, out);
|
|
109
|
-
const spinner = out.spinner("Discovering Routers");
|
|
110
|
-
const routerApps = await listRouterApps(fly, org);
|
|
111
|
-
spinner.success(`Found ${routerApps.length} Router${routerApps.length !== 1 ? "s" : ""}`);
|
|
112
|
-
if (routerApps.length === 0) {
|
|
113
|
-
out.blank()
|
|
114
|
-
.text("No Routers Found.")
|
|
115
|
-
.dim(" Create one with: ambit create <network>")
|
|
116
|
-
.blank();
|
|
117
|
-
out.done({ routers: [] });
|
|
118
|
-
out.print();
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const routers = [];
|
|
122
|
-
for (const app of routerApps) {
|
|
123
|
-
const machine = await getRouterMachineInfo(fly, app.appName);
|
|
124
|
-
const ts = await getRouterTailscaleInfo(tailscale, app.appName);
|
|
125
|
-
routers.push({ ...app, machine, tailscale: ts });
|
|
126
|
-
}
|
|
127
|
-
out.blank().header("Router Status").blank();
|
|
128
|
-
const rows = routers.map((r) => {
|
|
129
|
-
const tsStatus = r.tailscale
|
|
130
|
-
? r.tailscale.online ? "online" : "offline"
|
|
131
|
-
: "not found";
|
|
132
|
-
return [r.network, r.appName, r.machine?.state ?? "unknown", tsStatus];
|
|
133
|
-
});
|
|
134
|
-
const table = new Table()
|
|
135
|
-
.header(["Network", "App", "State", "Tailscale"])
|
|
136
|
-
.body(rows)
|
|
137
|
-
.indent(2)
|
|
138
|
-
.padding(2);
|
|
139
|
-
out.text(table.toString());
|
|
140
|
-
out.blank();
|
|
141
|
-
out.done({ routers });
|
|
142
|
-
out.print();
|
|
143
|
-
};
|
|
144
|
-
// =============================================================================
|
|
145
|
-
// Register Command
|
|
146
|
-
// =============================================================================
|
|
147
|
-
registerCommand({
|
|
148
|
-
name: "status",
|
|
149
|
-
description: "Show router status, network, and tailnet info",
|
|
150
|
-
usage: "ambit status [--network <name>] [--org <org>] [--json]",
|
|
151
|
-
run: status,
|
|
152
|
-
});
|
package/esm/src/cli/mod.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../src/src/cli/mod.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAQD,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,KAAG,IAElD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,GAAG,SAEnD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,OAAO,EAExC,CAAC;AAQF,eAAO,MAAM,QAAQ,QAAO,IA6B3B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,IAE9B,CAAC;AAMF,eAAO,MAAM,MAAM,GAAU,MAAM,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAuCzD,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/src/discovery.ts"],"names":[],"mappings":"AAcA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAOlE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAaD,wDAAwD;AACxD,eAAO,MAAM,cAAc,GACzB,KAAK,WAAW,EAChB,KAAK,MAAM,KACV,OAAO,CAAC,SAAS,EAAE,CAcrB,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,aAAa,GACxB,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,SAAS,GAAG,IAAI,CAG1B,CAAC;AAMF,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,eAAO,MAAM,yBAAyB,GACpC,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,KACd,OAAO,CAAC,WAAW,EAAE,CAcvB,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,eAAe,GAC1B,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAU,MAAM,KACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAmB5B,CAAC;AAMF,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,GAC/B,KAAK,WAAW,EAChB,SAAS,MAAM,KACd,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAclC,CAAC;AAMF,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GACjC,WAAW,iBAAiB,EAC5B,SAAS,MAAM,KACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAcpC,CAAC"}
|