@codemarc/blt 1.7.0 → 1.8.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.
Files changed (87) hide show
  1. package/README.md +162 -4
  2. package/dist/blt +4 -0
  3. package/dist/blt.d.ts.map +1 -1
  4. package/dist/blt.js.map +1 -1
  5. package/dist/commands/app/data.d.ts +29 -0
  6. package/dist/commands/app/data.d.ts.map +1 -0
  7. package/dist/commands/app/data.js +70 -0
  8. package/dist/commands/app/data.js.map +1 -0
  9. package/dist/commands/app/env-check.d.ts +36 -0
  10. package/dist/commands/app/env-check.d.ts.map +1 -0
  11. package/dist/commands/app/env-check.js +110 -0
  12. package/dist/commands/app/env-check.js.map +1 -0
  13. package/dist/commands/app/paths.d.ts +6 -0
  14. package/dist/commands/app/paths.d.ts.map +1 -0
  15. package/dist/commands/app/paths.js +41 -0
  16. package/dist/commands/app/paths.js.map +1 -0
  17. package/dist/commands/app/pos.d.ts +8 -0
  18. package/dist/commands/app/pos.d.ts.map +1 -0
  19. package/dist/commands/app/pos.js +74 -0
  20. package/dist/commands/app/pos.js.map +1 -0
  21. package/dist/commands/app/shell.d.ts +9 -0
  22. package/dist/commands/app/shell.d.ts.map +1 -0
  23. package/dist/commands/app/shell.js +33 -0
  24. package/dist/commands/app/shell.js.map +1 -0
  25. package/dist/commands/app.d.ts +3 -0
  26. package/dist/commands/app.d.ts.map +1 -0
  27. package/dist/commands/app.js +139 -0
  28. package/dist/commands/app.js.map +1 -0
  29. package/dist/commands/data/apply.d.ts.map +1 -1
  30. package/dist/commands/data/apply.js +20 -3
  31. package/dist/commands/data/apply.js.map +1 -1
  32. package/dist/commands/data/helpers.d.ts +12 -2
  33. package/dist/commands/data/helpers.d.ts.map +1 -1
  34. package/dist/commands/data/helpers.js +44 -3
  35. package/dist/commands/data/helpers.js.map +1 -1
  36. package/dist/commands/data/pull.js +2 -2
  37. package/dist/commands/data/pull.js.map +1 -1
  38. package/dist/commands/data.d.ts.map +1 -1
  39. package/dist/commands/data.js +18 -3
  40. package/dist/commands/data.js.map +1 -1
  41. package/dist/commands/env/get.d.ts.map +1 -1
  42. package/dist/commands/env/get.js +4 -2
  43. package/dist/commands/env/get.js.map +1 -1
  44. package/dist/commands/spin/dns.d.ts +7 -0
  45. package/dist/commands/spin/dns.d.ts.map +1 -0
  46. package/dist/commands/spin/dns.js +42 -0
  47. package/dist/commands/spin/dns.js.map +1 -0
  48. package/dist/commands/spin/down.d.ts +3 -0
  49. package/dist/commands/spin/down.d.ts.map +1 -0
  50. package/dist/commands/spin/down.js +17 -0
  51. package/dist/commands/spin/down.js.map +1 -0
  52. package/dist/commands/spin/helpers.d.ts +7 -0
  53. package/dist/commands/spin/helpers.d.ts.map +1 -0
  54. package/dist/commands/spin/helpers.js +38 -0
  55. package/dist/commands/spin/helpers.js.map +1 -0
  56. package/dist/commands/spin/list.d.ts +3 -0
  57. package/dist/commands/spin/list.d.ts.map +1 -0
  58. package/dist/commands/spin/list.js +20 -0
  59. package/dist/commands/spin/list.js.map +1 -0
  60. package/dist/commands/spin/setup.d.ts +3 -0
  61. package/dist/commands/spin/setup.d.ts.map +1 -0
  62. package/dist/commands/spin/setup.js +33 -0
  63. package/dist/commands/spin/setup.js.map +1 -0
  64. package/dist/commands/spin/ssh.d.ts +3 -0
  65. package/dist/commands/spin/ssh.d.ts.map +1 -0
  66. package/dist/commands/spin/ssh.js +14 -0
  67. package/dist/commands/spin/ssh.js.map +1 -0
  68. package/dist/commands/spin/status.d.ts +3 -0
  69. package/dist/commands/spin/status.d.ts.map +1 -0
  70. package/dist/commands/spin/status.js +21 -0
  71. package/dist/commands/spin/status.js.map +1 -0
  72. package/dist/commands/spin/up.d.ts +11 -0
  73. package/dist/commands/spin/up.d.ts.map +1 -0
  74. package/dist/commands/spin/up.js +34 -0
  75. package/dist/commands/spin/up.js.map +1 -0
  76. package/dist/commands/spin.d.ts +3 -0
  77. package/dist/commands/spin.d.ts.map +1 -0
  78. package/dist/commands/spin.js +166 -0
  79. package/dist/commands/spin.js.map +1 -0
  80. package/dist/lib/digitalocean.d.ts +60 -0
  81. package/dist/lib/digitalocean.d.ts.map +1 -0
  82. package/dist/lib/digitalocean.js +108 -0
  83. package/dist/lib/digitalocean.js.map +1 -0
  84. package/dist/lib/repositories.d.ts.map +1 -1
  85. package/dist/lib/repositories.js +6 -0
  86. package/dist/lib/repositories.js.map +1 -1
  87. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { formatEnvFileHeader, BLT_ENV_NAME_KEY } from "../app/env-check";
3
4
  /** Single path segment for `.env.<name>` under `.trailz/env/`. */
4
5
  const NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
5
6
  /**
@@ -49,7 +50,8 @@ export async function envGet(name, logger) {
49
50
  process.exit(proc.exitCode ?? 1);
50
51
  }
51
52
  const outPath = join(cwd, ".env");
52
- await Bun.write(outPath, proc.stdout);
53
- logger.info(`Wrote ${outPath}`);
53
+ const body = proc.stdout.toString().replace(/^\uFEFF/, "").trimStart();
54
+ await Bun.write(outPath, `${formatEnvFileHeader(name)}${body}\n`);
55
+ logger.info(`Wrote ${outPath} (${BLT_ENV_NAME_KEY}=${name})`);
54
56
  }
55
57
  //# sourceMappingURL=get.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/commands/env/get.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,kEAAkE;AAClE,MAAM,OAAO,GAAG,8BAA8B,CAAC;AAE/C;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAC5E,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAY,EAAE,MAAc;IACxD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,CACX,sEAAsE,CACtE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACxC,qEAAqE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAElC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CACX,oDAAoD,OAAO,IAAI;YAC9D,6FAA6F,CAC9F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC;QAC1B,GAAG,EAAE,CAAC,GAAG,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC;QAClC,GAAG;QACH,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,SAAS;KACjB,CAAC,CAAC;IAEH,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/commands/env/get.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzE,kEAAkE;AAClE,MAAM,OAAO,GAAG,8BAA8B,CAAC;AAE/C;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAC5E,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAY,EAAE,MAAc;IACxD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,CACX,sEAAsE,CACtE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACxC,qEAAqE;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAElC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CACX,oDAAoD,OAAO,IAAI;YAC9D,6FAA6F,CAC9F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC;QAC1B,GAAG,EAAE,CAAC,GAAG,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC;QAClC,GAAG;QACH,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,SAAS;KACjB,CAAC,CAAC;IAEH,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;IACvE,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IAClE,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,KAAK,gBAAgB,IAAI,IAAI,GAAG,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinDns(action: string, domain: string, opts: {
3
+ name?: string;
4
+ ip?: string;
5
+ recordId?: string;
6
+ }, logger: Logger): Promise<void>;
7
+ //# sourceMappingURL=dns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/dns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,wBAAsB,OAAO,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EACvD,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAyCf"}
@@ -0,0 +1,42 @@
1
+ import { createDnsRecord, listDnsRecords, deleteDnsRecord } from "../../lib/digitalocean";
2
+ import { formatTable } from "./helpers";
3
+ export async function spinDns(action, domain, opts, logger) {
4
+ switch (action) {
5
+ case "create": {
6
+ if (!opts.name || !opts.ip) {
7
+ throw new Error("spin dns create requires --name and --ip");
8
+ }
9
+ const record = await createDnsRecord(domain, "A", opts.name, opts.ip);
10
+ logger.info(`DNS record created — id: ${record.id}, ${record.name}.${domain} → ${record.data}`);
11
+ break;
12
+ }
13
+ case "list": {
14
+ const records = await listDnsRecords(domain);
15
+ if (records.length === 0) {
16
+ logger.info(`No DNS records found for ${domain}.`);
17
+ return;
18
+ }
19
+ const header = ["ID", "TYPE", "NAME", "DATA", "TTL"];
20
+ const rows = records.map((r) => [
21
+ String(r.id),
22
+ r.type,
23
+ r.name,
24
+ r.data,
25
+ String(r.ttl),
26
+ ]);
27
+ console.log(formatTable([header, ...rows]));
28
+ break;
29
+ }
30
+ case "delete": {
31
+ if (!opts.recordId) {
32
+ throw new Error("spin dns delete requires --record-id");
33
+ }
34
+ await deleteDnsRecord(domain, Number(opts.recordId));
35
+ logger.info(`DNS record ${opts.recordId} deleted from ${domain}.`);
36
+ break;
37
+ }
38
+ default:
39
+ throw new Error(`Unknown dns action "${action}". Use: create, list, delete`);
40
+ }
41
+ }
42
+ //# sourceMappingURL=dns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns.js","sourceRoot":"","sources":["../../../src/commands/spin/dns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,OAAO,CAC5B,MAAc,EACd,MAAc,EACd,IAAuD,EACvD,MAAc;IAEd,QAAQ,MAAM,EAAE,CAAC;QAChB,KAAK,QAAQ,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,4BAA4B,MAAM,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,IAAI,MAAM,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAChG,MAAM;QACP,CAAC;QAED,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,4BAA4B,MAAM,GAAG,CAAC,CAAC;gBACnD,OAAO;YACR,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YACrD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBACZ,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI;gBACN,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;aACb,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM;QACP,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ,iBAAiB,MAAM,GAAG,CAAC,CAAC;YACnE,MAAM;QACP,CAAC;QAED;YACC,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,8BAA8B,CAAC,CAAC;IAC/E,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinDown(nameOrId: string, force: boolean, logger: Logger): Promise<void>;
3
+ //# sourceMappingURL=down.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"down.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/down.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9F"}
@@ -0,0 +1,17 @@
1
+ import { deleteDroplet, getDroplet } from "../../lib/digitalocean";
2
+ import { confirm, getPublicIp } from "./helpers";
3
+ export async function spinDown(nameOrId, force, logger) {
4
+ const droplet = await getDroplet(nameOrId);
5
+ const ip = getPublicIp(droplet) ?? "n/a";
6
+ if (!force) {
7
+ const ok = await confirm(`Destroy droplet "${droplet.name}" (id: ${droplet.id}, ip: ${ip})?`);
8
+ if (!ok) {
9
+ logger.info("Aborted.");
10
+ return;
11
+ }
12
+ }
13
+ logger.info(`Destroying droplet "${droplet.name}" (${droplet.id})…`);
14
+ await deleteDroplet(droplet.id);
15
+ logger.info("Droplet destroyed.");
16
+ }
17
+ //# sourceMappingURL=down.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"down.js","sourceRoot":"","sources":["../../../src/commands/spin/down.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,KAAc,EAAE,MAAc;IAC9E,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;IAEzC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,EAAE,GAAG,MAAM,OAAO,CACvB,oBAAoB,OAAO,CAAC,IAAI,UAAU,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,CACnE,CAAC;QACF,IAAI,CAAC,EAAE,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;IACF,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,uBAAuB,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;IACrE,MAAM,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Logger } from "@caporal/core";
2
+ import { type Droplet } from "../../lib/digitalocean";
3
+ export declare function getPublicIp(droplet: Droplet): string | undefined;
4
+ export declare function waitForActive(idOrName: string, logger: Logger, timeoutMs?: number, intervalMs?: number): Promise<Droplet>;
5
+ export declare function confirm(prompt: string): Promise<boolean>;
6
+ export declare function formatTable(rows: string[][]): string;
7
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAElE,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEhE;AAED,wBAAsB,aAAa,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,SAAU,EACnB,UAAU,SAAQ,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ9D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAWpD"}
@@ -0,0 +1,38 @@
1
+ import { createInterface } from "node:readline";
2
+ import { getDroplet } from "../../lib/digitalocean";
3
+ export function getPublicIp(droplet) {
4
+ return droplet.networks.v4.find((n) => n.type === "public")?.ip_address;
5
+ }
6
+ export async function waitForActive(idOrName, logger, timeoutMs = 120_000, intervalMs = 5_000) {
7
+ const start = Date.now();
8
+ while (Date.now() - start < timeoutMs) {
9
+ const d = await getDroplet(idOrName);
10
+ const ip = getPublicIp(d);
11
+ if (d.status === "active" && ip)
12
+ return d;
13
+ logger.info(` status: ${d.status} — waiting…`);
14
+ await new Promise((r) => setTimeout(r, intervalMs));
15
+ }
16
+ throw new Error(`Timed out waiting for droplet ${idOrName} to become active`);
17
+ }
18
+ export async function confirm(prompt) {
19
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
20
+ return new Promise((resolve) => {
21
+ rl.question(`${prompt} [y/N] `, (answer) => {
22
+ rl.close();
23
+ resolve(answer.trim().toLowerCase() === "y");
24
+ });
25
+ });
26
+ }
27
+ export function formatTable(rows) {
28
+ if (rows.length === 0)
29
+ return "";
30
+ const widths = rows[0].map((_, col) => Math.max(...rows.map((row) => (row[col] ?? "").length)));
31
+ return rows
32
+ .map((row) => row.map((cell, i) => {
33
+ const w = widths[i];
34
+ return cell.length >= w ? cell : cell + " ".repeat(w - cell.length);
35
+ }).join(" "))
36
+ .join("\n");
37
+ }
38
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/commands/spin/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAgB,MAAM,wBAAwB,CAAC;AAElE,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC3C,OAAO,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,UAAU,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,QAAgB,EAChB,MAAc,EACd,SAAS,GAAG,OAAO,EACnB,UAAU,GAAG,KAAK;IAElB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,SAAS,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE;YAAE,OAAO,CAAC,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,aAAa,CAAC,CAAC;QAChD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,QAAQ,mBAAmB,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,MAAc;IAC3C,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,EAAE,CAAC,QAAQ,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE;YAC1C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CACrC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CACvD,CAAC;IACF,OAAO,IAAI;SACT,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACb,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinList(tag: string, logger: Logger): Promise<void>;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBzE"}
@@ -0,0 +1,20 @@
1
+ import { listDroplets } from "../../lib/digitalocean";
2
+ import { formatTable, getPublicIp } from "./helpers";
3
+ export async function spinList(tag, logger) {
4
+ const droplets = await listDroplets(tag);
5
+ if (droplets.length === 0) {
6
+ logger.info(`No droplets found with tag "${tag}".`);
7
+ return;
8
+ }
9
+ const header = ["NAME", "ID", "IP", "STATUS", "REGION", "SIZE"];
10
+ const rows = droplets.map((d) => [
11
+ d.name,
12
+ String(d.id),
13
+ getPublicIp(d) ?? "pending",
14
+ d.status,
15
+ d.region.slug,
16
+ d.size_slug,
17
+ ]);
18
+ console.log(formatTable([header, ...rows]));
19
+ }
20
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/commands/spin/list.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAW,EAAE,MAAc;IACzD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;IAEzC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,+BAA+B,GAAG,IAAI,CAAC,CAAC;QACpD,OAAO;IACR,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAChC,CAAC,CAAC,IAAI;QACN,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACZ,WAAW,CAAC,CAAC,CAAC,IAAI,SAAS;QAC3B,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,SAAS;KACX,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinSetup(nameOrId: string, user: string, logger: Logger): Promise<void>;
3
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/setup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAO5C,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC7F"}
@@ -0,0 +1,33 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getDroplet } from "../../lib/digitalocean";
5
+ import { getPublicIp } from "./helpers";
6
+ export async function spinSetup(nameOrId, user, logger) {
7
+ const droplet = await getDroplet(nameOrId);
8
+ const ip = getPublicIp(droplet);
9
+ if (!ip) {
10
+ throw new Error(`Droplet "${droplet.name}" has no public IP yet (status: ${droplet.status})`);
11
+ }
12
+ //const scriptPath = join(process.cwd(), "deploy", "scripts", "bootstrap-droplet.sh");
13
+ const scriptPath = join(process.cwd(), "bootstrap-droplet.sh");
14
+ if (!existsSync(scriptPath)) {
15
+ logger.info("Bootstrap script not found — showing what would run:");
16
+ logger.info(` 1. scp ${scriptPath} ${user}@${ip}:/tmp/bootstrap-droplet.sh`);
17
+ logger.info(` 2. ssh ${user}@${ip} 'bash /tmp/bootstrap-droplet.sh'`);
18
+ logger.info(`\nCreate deploy/scripts/bootstrap-droplet.sh to enable automatic setup.`);
19
+ return;
20
+ }
21
+ logger.info(`Uploading bootstrap script to ${user}@${ip}…`);
22
+ const scp = spawnSync("scp", ["-o", "StrictHostKeyChecking=accept-new", scriptPath, `${user}@${ip}:/tmp/bootstrap-droplet.sh`], { stdio: "inherit" });
23
+ if (scp.status !== 0) {
24
+ throw new Error(`scp failed with exit code ${scp.status}`);
25
+ }
26
+ logger.info("Running bootstrap script…");
27
+ const ssh = spawnSync("ssh", ["-o", "StrictHostKeyChecking=accept-new", `${user}@${ip}`, "bash /tmp/bootstrap-droplet.sh"], { stdio: "inherit" });
28
+ if (ssh.status !== 0) {
29
+ throw new Error(`Bootstrap script failed with exit code ${ssh.status}`);
30
+ }
31
+ logger.info("Bootstrap complete.");
32
+ }
33
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/commands/spin/setup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAY,EAAE,MAAc;IAC7E,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,IAAI,mCAAmC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/F,CAAC;IAED,sFAAsF;IACtF,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,sBAAsB,CAAC,CAAC;IAC/D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC,YAAY,UAAU,IAAI,IAAI,IAAI,EAAE,4BAA4B,CAAC,CAAC;QAC9E,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,mCAAmC,CAAC,CAAC;QACvE,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;QACvF,OAAO;IACR,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,iCAAiC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,SAAS,CACpB,KAAK,EACL,CAAC,IAAI,EAAE,kCAAkC,EAAE,UAAU,EAAE,GAAG,IAAI,IAAI,EAAE,4BAA4B,CAAC,EACjG,EAAE,KAAK,EAAE,SAAS,EAAE,CACpB,CAAC;IACF,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,SAAS,CACpB,KAAK,EACL,CAAC,IAAI,EAAE,kCAAkC,EAAE,GAAG,IAAI,IAAI,EAAE,EAAE,EAAE,gCAAgC,CAAC,EAC7F,EAAE,KAAK,EAAE,SAAS,EAAE,CACpB,CAAC;IACF,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0CAA0C,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinSsh(nameOrId: string, user: string, logger: Logger): Promise<void>;
3
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/ssh.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAK5C,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAc3F"}
@@ -0,0 +1,14 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { getDroplet } from "../../lib/digitalocean";
3
+ import { getPublicIp } from "./helpers";
4
+ export async function spinSsh(nameOrId, user, logger) {
5
+ const droplet = await getDroplet(nameOrId);
6
+ const ip = getPublicIp(droplet);
7
+ if (!ip) {
8
+ throw new Error(`Droplet "${droplet.name}" has no public IP yet (status: ${droplet.status})`);
9
+ }
10
+ logger.info(`Connecting to ${user}@${ip} (${droplet.name})…`);
11
+ const result = spawnSync("ssh", ["-o", "StrictHostKeyChecking=accept-new", `${user}@${ip}`], { stdio: "inherit" });
12
+ process.exit(result.status ?? 0);
13
+ }
14
+ //# sourceMappingURL=ssh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.js","sourceRoot":"","sources":["../../../src/commands/spin/ssh.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB,EAAE,IAAY,EAAE,MAAc;IAC3E,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,IAAI,mCAAmC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,iBAAiB,IAAI,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,SAAS,CACvB,KAAK,EACL,CAAC,IAAI,EAAE,kCAAkC,EAAE,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,EAC3D,EAAE,KAAK,EAAE,SAAS,EAAE,CACpB,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export declare function spinStatus(nameOrId: string, _logger: Logger): Promise<void>;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBjF"}
@@ -0,0 +1,21 @@
1
+ import { getDroplet } from "../../lib/digitalocean";
2
+ import { formatTable, getPublicIp } from "./helpers";
3
+ export async function spinStatus(nameOrId, _logger) {
4
+ const d = await getDroplet(nameOrId);
5
+ const ip = getPublicIp(d) ?? "pending";
6
+ console.log(formatTable([
7
+ ["Name", d.name],
8
+ ["ID", String(d.id)],
9
+ ["Status", d.status],
10
+ ["IP", ip],
11
+ ["Region", `${d.region.slug} (${d.region.name})`],
12
+ ["Size", d.size_slug],
13
+ ["Image", `${d.image.slug} (${d.image.name})`],
14
+ ["vCPUs", String(d.vcpus)],
15
+ ["Memory", `${d.memory} MB`],
16
+ ["Disk", `${d.disk} GB`],
17
+ ["Tags", d.tags.join(", ") || "none"],
18
+ ["Created", d.created_at],
19
+ ]));
20
+ }
21
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/spin/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,OAAe;IACjE,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IAEvC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;QACvB,CAAC,MAAM,EAAK,CAAC,CAAC,IAAI,CAAC;QACnB,CAAC,IAAI,EAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC,QAAQ,EAAG,CAAC,CAAC,MAAM,CAAC;QACrB,CAAC,IAAI,EAAO,EAAE,CAAC;QACf,CAAC,QAAQ,EAAG,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;QAClD,CAAC,MAAM,EAAK,CAAC,CAAC,SAAS,CAAC;QACxB,CAAC,OAAO,EAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;QAChD,CAAC,OAAO,EAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,QAAQ,EAAG,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;QAC7B,CAAC,MAAM,EAAK,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC;QAC3B,CAAC,MAAM,EAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;QACxC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;KACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Logger } from "@caporal/core";
2
+ export type SpinUpOpts = {
3
+ name: string;
4
+ size: string;
5
+ region: string;
6
+ image: string;
7
+ sshKeys: string[];
8
+ tag: string;
9
+ };
10
+ export declare function spinUp(opts: SpinUpOpts, logger: Logger): Promise<void>;
11
+ //# sourceMappingURL=up.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"up.d.ts","sourceRoot":"","sources":["../../../src/commands/spin/up.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,MAAM,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC5E"}
@@ -0,0 +1,34 @@
1
+ import { createDroplet, listSshKeys } from "../../lib/digitalocean";
2
+ import { getPublicIp, waitForActive } from "./helpers";
3
+ export async function spinUp(opts, logger) {
4
+ if (opts.sshKeys.length === 0) {
5
+ const keys = await listSshKeys();
6
+ const bltKeys = keys.filter((k) => k.name.toLowerCase().includes("blt"));
7
+ if (bltKeys.length > 0) {
8
+ opts.sshKeys = bltKeys.map((k) => String(k.id));
9
+ logger.info(`Auto-selected SSH keys: ${bltKeys.map((k) => k.name).join(", ")}`);
10
+ }
11
+ else if (keys.length > 0) {
12
+ opts.sshKeys = keys.map((k) => String(k.id));
13
+ logger.info(`No 'blt' SSH keys found — using all account keys: ${keys.map((k) => k.name).join(", ")}`);
14
+ }
15
+ else {
16
+ logger.warn("No SSH keys found in DO account. Droplet will use password auth (check email for root password).");
17
+ }
18
+ }
19
+ logger.info(`Creating droplet "${opts.name}" (${opts.size}) in ${opts.region}…`);
20
+ const droplet = await createDroplet({
21
+ name: opts.name,
22
+ size: opts.size,
23
+ region: opts.region,
24
+ image: opts.image,
25
+ sshKeys: opts.sshKeys,
26
+ tags: [opts.tag],
27
+ });
28
+ logger.info(`Droplet created — id: ${droplet.id}, status: ${droplet.status}`);
29
+ logger.info("Waiting for droplet to become active…");
30
+ const ready = await waitForActive(String(droplet.id), logger);
31
+ const ip = getPublicIp(ready) ?? "unknown";
32
+ logger.info(`Droplet ready — id: ${ready.id}, ip: ${ip}`);
33
+ }
34
+ //# sourceMappingURL=up.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"up.js","sourceRoot":"","sources":["../../../src/commands/spin/up.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAWvD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAgB,EAAE,MAAc;IAC5D,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,WAAW,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC3C,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CACV,2BAA2B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClE,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,IAAI,CACV,qDAAqD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACzF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,IAAI,CACV,kGAAkG,CAClG,CAAC;QACH,CAAC;IACF,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,qBAAqB,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;QACnC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;KAChB,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CAAC,yBAAyB,OAAO,CAAC,EAAE,aAAa,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAE9E,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC;IAC3C,MAAM,CAAC,IAAI,CAAC,uBAAuB,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Program } from "@caporal/core";
2
+ export default function spinCommand(program: Program): void;
3
+ //# sourceMappingURL=spin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spin.d.ts","sourceRoot":"","sources":["../../src/commands/spin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,eAAe,CAAC;AAwB5E,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,OAAO,EAAE,OAAO,QAuNnD"}
@@ -0,0 +1,166 @@
1
+ import { spinUp } from "./spin/up";
2
+ import { spinDown } from "./spin/down";
3
+ import { spinList } from "./spin/list";
4
+ import { spinStatus } from "./spin/status";
5
+ import { spinSsh } from "./spin/ssh";
6
+ import { spinDns } from "./spin/dns";
7
+ import { spinSetup } from "./spin/setup";
8
+ const spinHelp = `
9
+ Manage DigitalOcean droplets for BLT test environments
10
+
11
+ Usage:
12
+ blt spin up --name <name> [--size slug] [--region slug] [--image slug] [--ssh-keys ids] [--tag tag]
13
+ blt spin down <name> [--force]
14
+ blt spin list [--tag tag]
15
+ blt spin status <name>
16
+ blt spin ssh <name> [--user user]
17
+ blt spin dns <action> --domain <domain> [--name sub] [--ip addr] [--record-id id]
18
+ blt spin setup <name>
19
+
20
+ Requires DIGITALOCEAN_ACCESS_TOKEN env var.
21
+ `;
22
+ export default function spinCommand(program) {
23
+ program
24
+ .command("spin", "Manage DigitalOcean droplets for BLT environments")
25
+ .help(spinHelp)
26
+ .action(() => {
27
+ console.log(spinHelp);
28
+ });
29
+ // -- spin up ---------------------------------------------------------------
30
+ program
31
+ .command("spin up", "Create a new droplet")
32
+ .hide()
33
+ .option("--name <name>", "Droplet name", { required: true })
34
+ .option("--size <size>", "Droplet size slug", {
35
+ default: "s-1vcpu-512mb-10gb",
36
+ })
37
+ .option("--region <region>", "Region slug", { default: "nyc3" })
38
+ .option("--image <image>", "OS image slug", {
39
+ default: "ubuntu-24-04-x64",
40
+ })
41
+ .option("--ssh-keys <keys>", "Comma-separated SSH key IDs or fingerprints")
42
+ .option("--tag <tag>", "Tag for the droplet", { default: "blt" })
43
+ .action(async ({ options, logger, }) => {
44
+ try {
45
+ const sshKeysRaw = options.sshKeys;
46
+ const sshKeys = sshKeysRaw
47
+ ? String(sshKeysRaw).split(",").map((k) => k.trim()).filter(Boolean)
48
+ : [];
49
+ await spinUp({
50
+ name: String(options.name),
51
+ size: String(options.size),
52
+ region: String(options.region),
53
+ image: String(options.image),
54
+ tag: String(options.tag),
55
+ sshKeys,
56
+ }, logger);
57
+ }
58
+ catch (error) {
59
+ const message = error instanceof Error ? error.message : String(error);
60
+ logger.error(`spin up failed: ${message}`);
61
+ process.exit(1);
62
+ }
63
+ });
64
+ // -- spin down -------------------------------------------------------------
65
+ program
66
+ .command("spin down", "Destroy a droplet")
67
+ .hide()
68
+ .argument("<name>", "Droplet name or ID")
69
+ .option("--force", "Skip confirmation", { default: false })
70
+ .action(async ({ args, options, logger, }) => {
71
+ try {
72
+ await spinDown(args.name, !!options.force, logger);
73
+ }
74
+ catch (error) {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ logger.error(`spin down failed: ${message}`);
77
+ process.exit(1);
78
+ }
79
+ });
80
+ // -- spin list -------------------------------------------------------------
81
+ program
82
+ .command("spin list", "List BLT droplets")
83
+ .hide()
84
+ .option("--tag <tag>", "Filter by tag", { default: "blt" })
85
+ .action(async ({ options, logger, }) => {
86
+ try {
87
+ await spinList(options.tag, logger);
88
+ }
89
+ catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ logger.error(`spin list failed: ${message}`);
92
+ process.exit(1);
93
+ }
94
+ });
95
+ // -- spin status -----------------------------------------------------------
96
+ program
97
+ .command("spin status", "Check droplet status")
98
+ .hide()
99
+ .argument("<name>", "Droplet name or ID")
100
+ .action(async ({ args, logger, }) => {
101
+ try {
102
+ await spinStatus(args.name, logger);
103
+ }
104
+ catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ logger.error(`spin status failed: ${message}`);
107
+ process.exit(1);
108
+ }
109
+ });
110
+ // -- spin ssh --------------------------------------------------------------
111
+ program
112
+ .command("spin ssh", "SSH into a droplet")
113
+ .hide()
114
+ .argument("<name>", "Droplet name or ID")
115
+ .option("--user <user>", "SSH user", { default: "blt" })
116
+ .action(async ({ args, options, logger, }) => {
117
+ try {
118
+ await spinSsh(args.name, options.user, logger);
119
+ }
120
+ catch (error) {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ logger.error(`spin ssh failed: ${message}`);
123
+ process.exit(1);
124
+ }
125
+ });
126
+ // -- spin dns --------------------------------------------------------------
127
+ program
128
+ .command("spin dns", "Manage DNS records for a domain")
129
+ .hide()
130
+ .argument("<action>", "Action: create, list, delete")
131
+ .option("--domain <domain>", "Domain name", { default: "bltcore.com" })
132
+ .option("--name <name>", "Record name (subdomain)")
133
+ .option("--ip <ip>", "IP address for A record")
134
+ .option("--record-id <id>", "Record ID (for delete)")
135
+ .action(async ({ args, options, logger, }) => {
136
+ try {
137
+ await spinDns(args.action, options.domain, {
138
+ name: options.name,
139
+ ip: options.ip,
140
+ recordId: options.recordId,
141
+ }, logger);
142
+ }
143
+ catch (error) {
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ logger.error(`spin dns failed: ${message}`);
146
+ process.exit(1);
147
+ }
148
+ });
149
+ // -- spin setup ------------------------------------------------------------
150
+ program
151
+ .command("spin setup", "Bootstrap a fresh droplet (Docker, users, dirs)")
152
+ .hide()
153
+ .argument("<name>", "Droplet name or ID")
154
+ .option("--user <user>", "SSH user for bootstrap", { default: "root" })
155
+ .action(async ({ args, options, logger, }) => {
156
+ try {
157
+ await spinSetup(args.name, options.user, logger);
158
+ }
159
+ catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ logger.error(`spin setup failed: ${message}`);
162
+ process.exit(1);
163
+ }
164
+ });
165
+ }
166
+ //# sourceMappingURL=spin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spin.js","sourceRoot":"","sources":["../../src/commands/spin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;CAahB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,OAAgB;IACnD,OAAO;SACL,OAAO,CAAC,MAAM,EAAE,mDAAmD,CAAC;SACpE,IAAI,CAAC,QAAQ,CAAC;SACd,MAAM,CAAC,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEJ,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,SAAS,EAAE,sBAAsB,CAAC;SAC1C,IAAI,EAAE;SACN,MAAM,CAAC,eAAe,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SAC3D,MAAM,CAAC,eAAe,EAAE,mBAAmB,EAAE;QAC7C,OAAO,EAAE,oBAAoB;KAC7B,CAAC;SACD,MAAM,CAAC,mBAAmB,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SAC/D,MAAM,CAAC,iBAAiB,EAAE,eAAe,EAAE;QAC3C,OAAO,EAAE,kBAAkB;KAC3B,CAAC;SACD,MAAM,CAAC,mBAAmB,EAAE,6CAA6C,CAAC;SAC1E,MAAM,CAAC,aAAa,EAAE,qBAAqB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;SAChE,MAAM,CACN,KAAK,EAAE,EACN,OAAO,EACP,MAAM,GAIN,EAAE,EAAE;QACJ,IAAI,CAAC;YACL,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;YACnC,MAAM,OAAO,GAAG,UAAU;gBACzB,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;gBACpE,CAAC,CAAC,EAAE,CAAC;YACN,MAAM,MAAM,CACX;gBACC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC1B,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;gBAC9B,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;gBAC5B,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;gBACxB,OAAO;aACP,EACA,MAAM,CACN,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,mBAAmB,OAAO,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC;SACzC,IAAI,EAAE;SACN,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SACxC,MAAM,CAAC,SAAS,EAAE,mBAAmB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;SAC1D,MAAM,CACN,KAAK,EAAE,EACN,IAAI,EACJ,OAAO,EACP,MAAM,GAKN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAc,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC;SACzC,IAAI,EAAE;SACN,MAAM,CAAC,aAAa,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;SAC1D,MAAM,CACN,KAAK,EAAE,EACN,OAAO,EACP,MAAM,GAIN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAa,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,qBAAqB,OAAO,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,aAAa,EAAE,sBAAsB,CAAC;SAC9C,IAAI,EAAE;SACN,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SACxC,MAAM,CACN,KAAK,EAAE,EACN,IAAI,EACJ,MAAM,GAIN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,UAAU,CAAC,IAAI,CAAC,IAAc,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,UAAU,EAAE,oBAAoB,CAAC;SACzC,IAAI,EAAE;SACN,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SACxC,MAAM,CAAC,eAAe,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;SACvD,MAAM,CACN,KAAK,EAAE,EACN,IAAI,EACJ,OAAO,EACP,MAAM,GAKN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,OAAO,CAAC,IAAI,CAAC,IAAc,EAAE,OAAO,CAAC,IAAc,EAAE,MAAM,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,UAAU,EAAE,iCAAiC,CAAC;SACtD,IAAI,EAAE;SACN,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC;SACpD,MAAM,CAAC,mBAAmB,EAAE,aAAa,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;SACtE,MAAM,CAAC,eAAe,EAAE,yBAAyB,CAAC;SAClD,MAAM,CAAC,WAAW,EAAE,yBAAyB,CAAC;SAC9C,MAAM,CAAC,kBAAkB,EAAE,wBAAwB,CAAC;SACpD,MAAM,CACN,KAAK,EAAE,EACN,IAAI,EACJ,OAAO,EACP,MAAM,GAKN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,OAAO,CACZ,IAAI,CAAC,MAAgB,EACrB,OAAO,CAAC,MAAgB,EACxB;gBACC,IAAI,EAAE,OAAO,CAAC,IAA0B;gBACxC,EAAE,EAAE,OAAO,CAAC,EAAwB;gBACpC,QAAQ,EAAE,OAAO,CAAC,QAA8B;aAChD,EACD,MAAM,CACN,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;IAEH,6EAA6E;IAC7E,OAAO;SACL,OAAO,CAAC,YAAY,EAAE,iDAAiD,CAAC;SACxE,IAAI,EAAE;SACN,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;SACxC,MAAM,CAAC,eAAe,EAAE,wBAAwB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;SACtE,MAAM,CACN,KAAK,EAAE,EACN,IAAI,EACJ,OAAO,EACP,MAAM,GAKN,EAAE,EAAE;QACJ,IAAI,CAAC;YACJ,MAAM,SAAS,CAAC,IAAI,CAAC,IAAc,EAAE,OAAO,CAAC,IAAc,EAAE,MAAM,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CACD,CAAC;AACJ,CAAC"}