@codemarc/blt 1.7.0 → 1.8.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.
Files changed (52) hide show
  1. package/README.md +134 -4
  2. package/dist/blt +2 -0
  3. package/dist/blt.d.ts.map +1 -1
  4. package/dist/blt.js.map +1 -1
  5. package/dist/commands/data/helpers.d.ts +10 -0
  6. package/dist/commands/data/helpers.d.ts.map +1 -1
  7. package/dist/commands/data/helpers.js +34 -0
  8. package/dist/commands/data/helpers.js.map +1 -1
  9. package/dist/commands/data.d.ts.map +1 -1
  10. package/dist/commands/data.js +18 -3
  11. package/dist/commands/data.js.map +1 -1
  12. package/dist/commands/spin/dns.d.ts +7 -0
  13. package/dist/commands/spin/dns.d.ts.map +1 -0
  14. package/dist/commands/spin/dns.js +42 -0
  15. package/dist/commands/spin/dns.js.map +1 -0
  16. package/dist/commands/spin/down.d.ts +3 -0
  17. package/dist/commands/spin/down.d.ts.map +1 -0
  18. package/dist/commands/spin/down.js +17 -0
  19. package/dist/commands/spin/down.js.map +1 -0
  20. package/dist/commands/spin/helpers.d.ts +7 -0
  21. package/dist/commands/spin/helpers.d.ts.map +1 -0
  22. package/dist/commands/spin/helpers.js +38 -0
  23. package/dist/commands/spin/helpers.js.map +1 -0
  24. package/dist/commands/spin/list.d.ts +3 -0
  25. package/dist/commands/spin/list.d.ts.map +1 -0
  26. package/dist/commands/spin/list.js +20 -0
  27. package/dist/commands/spin/list.js.map +1 -0
  28. package/dist/commands/spin/setup.d.ts +3 -0
  29. package/dist/commands/spin/setup.d.ts.map +1 -0
  30. package/dist/commands/spin/setup.js +32 -0
  31. package/dist/commands/spin/setup.js.map +1 -0
  32. package/dist/commands/spin/ssh.d.ts +3 -0
  33. package/dist/commands/spin/ssh.d.ts.map +1 -0
  34. package/dist/commands/spin/ssh.js +14 -0
  35. package/dist/commands/spin/ssh.js.map +1 -0
  36. package/dist/commands/spin/status.d.ts +3 -0
  37. package/dist/commands/spin/status.d.ts.map +1 -0
  38. package/dist/commands/spin/status.js +21 -0
  39. package/dist/commands/spin/status.js.map +1 -0
  40. package/dist/commands/spin/up.d.ts +11 -0
  41. package/dist/commands/spin/up.d.ts.map +1 -0
  42. package/dist/commands/spin/up.js +34 -0
  43. package/dist/commands/spin/up.js.map +1 -0
  44. package/dist/commands/spin.d.ts +3 -0
  45. package/dist/commands/spin.d.ts.map +1 -0
  46. package/dist/commands/spin.js +166 -0
  47. package/dist/commands/spin.js.map +1 -0
  48. package/dist/lib/digitalocean.d.ts +60 -0
  49. package/dist/lib/digitalocean.d.ts.map +1 -0
  50. package/dist/lib/digitalocean.js +108 -0
  51. package/dist/lib/digitalocean.js.map +1 -0
  52. package/package.json +1 -1
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - **Workflow**: List, show, delete, reload, and deploy GitHub Actions workflows (requires `gh`)
20
20
  - **Show**: Schema info, row counts, env vars, DB version, repo list
21
21
  - **Env**: Decrypt trailz `.trailz/env/.env.<name>.bin` to `.env` via smash (`blt env get`; requires `SMASH_KEY`)
22
+ - **Spin**: Manage DigitalOcean droplets and DNS for BLT test environments
22
23
  - **Cleanup**: Remove generated SQL/instance files, clean infrequently used YAML tags
23
24
 
24
25
  ## Installation
@@ -51,6 +52,9 @@ blt template render legal/nda/mutual --data ./doc/nda/mutual-nda-blt-otter.yaml
51
52
  # Update version.json in current directory
52
53
  blt version update
53
54
 
55
+ # List DigitalOcean droplets
56
+ blt spin list
57
+
54
58
  # List Supabase storage buckets
55
59
  blt bucket names
56
60
 
@@ -77,6 +81,7 @@ blt deploy schema
77
81
  | `blt workflow`| list, show, delete, reload, deploy |
78
82
  | `blt bucket` | names, list, upload, download, url, clear |
79
83
  | `blt show` | schema, counts, env, db, repo |
84
+ | `blt spin` | up, down, list, status, ssh, dns, setup |
80
85
  | `blt env` | get (decrypt trailz env to `.env`) |
81
86
  | `blt cleanup` | generated, tags |
82
87
 
@@ -422,7 +427,9 @@ blt deploy sql ./migrations/001_init.sql
422
427
 
423
428
  Export **instance-shaped** rows from Postgres into portable **JSON**, **YAML**, or **TOON** ([Token-Oriented Object Notation](https://toonformat.dev)) payloads that mirror `data/schema/instances/<instance>/yaml/`: `table:` / `procedure:` / `function:` targets plus `{ row }[]`. TOON yields smaller prompts for LLM workflows; `blt data apply` accepts `.toon` snapshots the same as JSON/YAML.
424
429
 
425
- **Pulled assets (full pull):** `role_props`, `modules`, `settings` (one file per `kind`, e.g. `10-020-settings-02-location`), `payment_providers` (`10-020-settings-04-payment-providers`), `nodes`, `create_user` + `add_profile_role_by_email` (optional), `insert_tax`, menu graph (`insert_menu`, `insert_category`, `insert_item` split into `10-121-items-menu` + `10-125-items-NN-<tag>`, `insert_modifier_group` split into `10-135-groups-NN-<tag>`).
430
+ **Curated pulled assets:** `role_props`, `modules`, `settings` (one file per `kind`, e.g. `10-020-settings-02-location`), `payment_providers` (`10-020-settings-04-payment-providers`), `nodes`, `create_user` + `add_profile_role_by_email` (optional via flags), `insert_tax`, menu graph `insert_menu`, `insert_category`, **`public.items`** as **table-mode** rows split into `10-121-items-menu` + `10-125-items-NN-<tag>` (stable `id` values for FKs such as `order_items`), and `insert_modifier_group` split into `10-135-groups-NN-<tag>`. Older snapshots may still contain a legacy `insert_item` function asset instead of `public.items`.
431
+
432
+ **Extra `public.*` tables (default):** By default, pull also discovers other `public` base tables not covered above and writes one asset per table (stems like `10-900-public-<table>`). Use **`--skip-extra-tables`** to pull only the curated manifest.
426
433
 
427
434
  Use snapshots to seed a disposable dev DB from production-shaped data **without writes on pull**.
428
435
 
@@ -435,18 +442,20 @@ Use snapshots to seed a disposable dev DB from production-shaped data **without
435
442
 
436
443
  If `BLT_DATA_PULL_URL` is unset, pull falls back to `SUPABASE_CONNECTION_STRING` (still runs inside `BEGIN READ ONLY`).
437
444
 
438
- **Never** aim `blt data apply` at production. Replay runs `TRUNCATE … RESTART IDENTITY CASCADE` only on **function-mode** tables **listed in that snapshot’s manifest** (e.g. `groups`, `items`, `categories`, `menus`, and `taxes` when those assets are present), which also clears dependents such as `order_items` referencing `items`. Table / procedure assets use `INSERT` / `CALL` upserts and are not truncated globally.
445
+ **Never** aim `blt data apply` at production. Apply first runs **`TRUNCATE … RESTART IDENTITY CASCADE`** only for **snapshot targets present in that manifest** that replace whole tables: function-mode replay targets (`insert_menu` `menus`, `insert_category` `categories`, `insert_modifier_group` → `groups`, `insert_tax` `taxes`) and **table-mode `public.items`** when included. That clears dependent rows (e.g. `order_items` referencing `items`). Other table/procedure assets (`settings`, `nodes`, `payment_providers`, extra `public.*` dumps, etc.) are applied with **`INSERT` `ON CONFLICT`**, `CALL`, or `SELECT` function calls — they are **not** covered by that truncate block unless their target is in the truncate set above.
439
446
 
440
- ### Pull / apply flags and caveats
447
+ ### Pull / apply / remove flags and caveats
441
448
 
442
449
  | Flag | Purpose |
443
450
  |------|---------|
444
451
  | `--skip-users` | Omit `10-070-users` and `10-071-profile-roles-extra`. Use when pulling from prod to avoid staff PII in files. |
445
452
  | `--include-secrets` | Include `api_key`, `secret_key_encrypted`, `webhook_secret_encrypted` on `payment_providers` (default: secrets nulled). |
453
+ | `--skip-extra-tables` | Only pull the curated manifest; omit discovered extra `public.*` base tables. |
446
454
 
447
455
  - **`create_user` / PINs:** Pulled rows set `p_pin: null`; PINs are not recoverable from `auth.users`. To restore deterministic dev PINs after apply, re-run `blt build data <instance>` against the same DB from canonical instance YAML, or edit snapshot rows.
448
456
  - **`payment_providers`:** If the table is missing (older schema), pull writes an empty asset file.
449
457
  - **Item / group file names:** Splits follow content rules (`props.category` vs first `props.tags[]`), not byte-identical parity with hand-authored multi-file YAML (e.g. pizza menu + toppings in one file).
458
+ - **`blt data remove`:** Deletes **`data-snapshots/<instance>/`** (all stamped runs) under the snapshot base. Use `-o, --out <dir>` to match your pull base (default `data-snapshots`). `--dry-run` prints the path that would be removed.
450
459
 
451
460
  ### Commands
452
461
 
@@ -458,13 +467,19 @@ blt data pull laf --format yaml
458
467
  blt data pull laf --format toon
459
468
  # Prod-safe: omit users / profile_roles junction
460
469
  blt data pull prod --skip-users
470
+ # Curated manifest only (no extra public.* dumps)
471
+ blt data pull laf --skip-extra-tables
461
472
 
462
- # Replay onto dev (writes via insert_* SELECTs — dev only)
473
+ # Replay onto dev (truncates menu-graph targets from manifest, then UPSERT / CALL / SELECT — dev only)
463
474
  export BLT_DATA_LOAD_URL="postgresql://...@localhost:5432/postgres"
464
475
  blt data apply ./data-snapshots/laf/2026-05-12T12-30-45Z
465
476
 
466
477
  # Inspect generated SQL without executing
467
478
  blt data apply ./data-snapshots/laf/2026-05-12T12-30-45Z --dry-run
479
+
480
+ # Remove every stamped snapshot under ./data-snapshots/<instance>/
481
+ blt data remove laf
482
+ blt data remove laf --dry-run
468
483
  ```
469
484
 
470
485
  Snapshots default under `data-snapshots/` from the **current working directory**. The **`data`** git repo ignores `data-snapshots/` so accidental commits of customer data are avoided.
@@ -797,6 +812,121 @@ blt show repo --ssh
797
812
 
798
813
  ---
799
814
 
815
+ ## Spin Commands (DigitalOcean)
816
+
817
+ Manage DigitalOcean droplets and DNS records for BLT test environments. Uses the `blt spin` namespace.
818
+
819
+ **Authentication:** The token is resolved in this order:
820
+
821
+ 1. `DIGITALOCEAN_ACCESS_TOKEN` environment variable
822
+ 2. `DIGITALOCEAN_TOKEN` environment variable
823
+ 3. doctl config file (written by `doctl auth init`)
824
+
825
+ If you've already run `doctl auth init`, no extra env var setup is needed.
826
+
827
+ ### `blt spin up`
828
+
829
+ Create a new droplet. Waits for the droplet to become active and reports its IP.
830
+
831
+ ```bash
832
+ blt spin up --name test-laf --ssh-keys 12345678
833
+ blt spin up --name test-laf --size s-1vcpu-1gb --region nyc3 --image ubuntu-24-04-x64 --tag blt
834
+ ```
835
+
836
+ **Options:**
837
+
838
+ - `--name <name>` — Droplet name (required)
839
+ - `--size <size>` — Droplet size slug (default: `s-1vcpu-512mb-10gb`)
840
+ - `--region <region>` — Region slug (default: `nyc3`)
841
+ - `--image <image>` — OS image slug (default: `ubuntu-24-04-x64`)
842
+ - `--ssh-keys <keys>` — Comma-separated SSH key IDs or fingerprints
843
+ - `--tag <tag>` — Tag for the droplet (default: `blt`)
844
+
845
+ ### `blt spin down <name>`
846
+
847
+ Destroy a droplet. Prompts for confirmation unless `--force` is passed.
848
+
849
+ ```bash
850
+ blt spin down test-laf
851
+ blt spin down test-laf --force
852
+ ```
853
+
854
+ **Options:**
855
+
856
+ - `--force` — Skip confirmation prompt
857
+
858
+ ### `blt spin list`
859
+
860
+ List droplets filtered by tag.
861
+
862
+ ```bash
863
+ blt spin list
864
+ blt spin list --tag staging
865
+ ```
866
+
867
+ **Options:**
868
+
869
+ - `--tag <tag>` — Filter by tag (default: `blt`)
870
+
871
+ ### `blt spin status <name>`
872
+
873
+ Show detailed information about a droplet.
874
+
875
+ ```bash
876
+ blt spin status test-laf
877
+ blt spin status 12345678
878
+ ```
879
+
880
+ ### `blt spin ssh <name>`
881
+
882
+ SSH into a droplet. Resolves the droplet's public IP and opens an interactive SSH session.
883
+
884
+ ```bash
885
+ blt spin ssh test-laf
886
+ blt spin ssh test-laf --user root
887
+ ```
888
+
889
+ **Options:**
890
+
891
+ - `--user <user>` — SSH user (default: `blt`)
892
+
893
+ ### `blt spin dns <action>`
894
+
895
+ Manage DNS records for a domain.
896
+
897
+ ```bash
898
+ # Create an A record
899
+ blt spin dns create --domain bltcore.com --name test-laf --ip 1.2.3.4
900
+
901
+ # List all DNS records
902
+ blt spin dns list --domain bltcore.com
903
+
904
+ # Delete a record by ID
905
+ blt spin dns delete --domain bltcore.com --record-id 12345678
906
+ ```
907
+
908
+ **Options:**
909
+
910
+ - `--domain <domain>` — Domain name (default: `bltcore.com`)
911
+ - `--name <name>` — Record name / subdomain (for create)
912
+ - `--ip <ip>` — IP address for A record (for create)
913
+ - `--record-id <id>` — Record ID (for delete)
914
+
915
+ ### `blt spin setup <name>`
916
+
917
+ Bootstrap a fresh droplet by uploading and running `deploy/scripts/bootstrap-droplet.sh`. If the script doesn't exist, prints what would run.
918
+
919
+ ```bash
920
+ blt spin setup test-laf
921
+ blt spin setup test-laf --user root
922
+ ```
923
+
924
+ **Options:**
925
+
926
+ - `--user <user>` — SSH user for bootstrap (default: `root`)
927
+
928
+ ---
929
+
800
930
  ## Env Commands
801
931
 
802
932
  ### `blt env get <name>`
package/dist/blt CHANGED
@@ -14,6 +14,7 @@ import waiCommand from "./commands/wai";
14
14
  import templateCommand from "./commands/template";
15
15
  import envCommand from "./commands/env";
16
16
  import dataCommand from "./commands/data";
17
+ import spinCommand from "./commands/spin";
17
18
  import { join, dirname } from "node:path";
18
19
  import { readFileSync } from "node:fs";
19
20
  import { fileURLToPath } from "node:url";
@@ -41,6 +42,7 @@ waiCommand(program);
41
42
  templateCommand(program);
42
43
  envCommand(program);
43
44
  dataCommand(program);
45
+ spinCommand(program);
44
46
  // If no command is provided, show help
45
47
  const args = process.argv.slice(2);
46
48
  if (args.length === 0) {
package/dist/blt.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"blt.d.ts","sourceRoot":"","sources":["../src/blt.ts"],"names":[],"mappings":";AAyBA,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C"}
1
+ {"version":3,"file":"blt.d.ts","sourceRoot":"","sources":["../src/blt.ts"],"names":[],"mappings":";AA0BA,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C"}
package/dist/blt.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"blt.js","sourceRoot":"","sources":["../src/blt.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,aAAa,MAAM,mBAAmB,CAAC;AAC9C,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAC5C,OAAO,aAAa,MAAM,mBAAmB,CAAC;AAC9C,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAC5C,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,eAAe,MAAM,qBAAqB,CAAC;AAClD,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,eAAe,MAAM,qBAAqB,CAAC;AAClD,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC;AAE/D,MAAM,UAAU,iBAAiB;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,OAAO,WAAW,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;KACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC;KAC5B,WAAW,CAAC,cAAc,CAAC,CAAC;AAE5B,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,aAAa,CAAC,OAAO,CAAC,CAAC;AACvB,aAAa,CAAC,OAAO,CAAC,CAAC;AACvB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,WAAW,CAAC,OAAO,CAAC,CAAC;AAEvB,uCAAuC;AACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AACD,OAAO,CAAC,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"blt.js","sourceRoot":"","sources":["../src/blt.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,aAAa,MAAM,mBAAmB,CAAC;AAC9C,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAC5C,OAAO,aAAa,MAAM,mBAAmB,CAAC;AAC9C,OAAO,YAAY,MAAM,kBAAkB,CAAC;AAC5C,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAChD,OAAO,eAAe,MAAM,qBAAqB,CAAC;AAClD,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,eAAe,MAAM,qBAAqB,CAAC;AAClD,OAAO,UAAU,MAAM,gBAAgB,CAAC;AACxC,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAC1C,OAAO,WAAW,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAG,IAAI,EAAE,cAAc,CAAC,CAAC;AAE/D,MAAM,UAAU,iBAAiB;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,OAAO,WAAW,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;KACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC;KAC5B,WAAW,CAAC,cAAc,CAAC,CAAC;AAE5B,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,YAAY,CAAC,OAAO,CAAC,CAAC;AACtB,aAAa,CAAC,OAAO,CAAC,CAAC;AACvB,aAAa,CAAC,OAAO,CAAC,CAAC;AACvB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,cAAc,CAAC,OAAO,CAAC,CAAC;AACxB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,eAAe,CAAC,OAAO,CAAC,CAAC;AACzB,UAAU,CAAC,OAAO,CAAC,CAAC;AACpB,WAAW,CAAC,OAAO,CAAC,CAAC;AACrB,WAAW,CAAC,OAAO,CAAC,CAAC;AAEvB,uCAAuC;AACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AACD,OAAO,CAAC,GAAG,EAAE,CAAC"}
@@ -2,4 +2,14 @@
2
2
  export declare function redactedDbUrl(connectionString: string): string;
3
3
  /** ISO-ish UTC timestamp for snapshot folder names. */
4
4
  export declare function snapshotTimestamp(): string;
5
+ /**
6
+ * Resolve `"latest"` to the most recent snapshot directory.
7
+ *
8
+ * Scans `<baseDir>/<instance?>/<timestamp>/manifest.json` and returns the
9
+ * full path to the newest stamped snapshot folder. When no instance prefix is
10
+ * given the search covers every instance sub-directory.
11
+ *
12
+ * @returns absolute path to the snapshot directory, or `null` if none found.
13
+ */
14
+ export declare function resolveLatestSnapshot(baseDir: string): string | null;
5
15
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/commands/data/helpers.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/commands/data/helpers.ts"],"names":[],"mappings":"AAGA,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAS9D;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBpE"}
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  /** Redact credentials from a Postgres URL for manifest logging. */
2
4
  export function redactedDbUrl(connectionString) {
3
5
  try {
@@ -16,4 +18,36 @@ export function redactedDbUrl(connectionString) {
16
18
  export function snapshotTimestamp() {
17
19
  return new Date().toISOString().replace(/:/g, "-").replace(/\.\d{3}Z$/, "Z");
18
20
  }
21
+ /**
22
+ * Resolve `"latest"` to the most recent snapshot directory.
23
+ *
24
+ * Scans `<baseDir>/<instance?>/<timestamp>/manifest.json` and returns the
25
+ * full path to the newest stamped snapshot folder. When no instance prefix is
26
+ * given the search covers every instance sub-directory.
27
+ *
28
+ * @returns absolute path to the snapshot directory, or `null` if none found.
29
+ */
30
+ export function resolveLatestSnapshot(baseDir) {
31
+ if (!existsSync(baseDir))
32
+ return null;
33
+ let best = null;
34
+ const instanceDirs = readdirSync(baseDir, { withFileTypes: true })
35
+ .filter((d) => d.isDirectory());
36
+ for (const instDir of instanceDirs) {
37
+ const instPath = join(baseDir, instDir.name);
38
+ const stamps = readdirSync(instPath, { withFileTypes: true })
39
+ .filter((d) => d.isDirectory());
40
+ for (const stamp of stamps) {
41
+ const snapPath = join(instPath, stamp.name);
42
+ const manifestPath = join(snapPath, "manifest.json");
43
+ if (!existsSync(manifestPath))
44
+ continue;
45
+ const mtime = statSync(manifestPath).mtimeMs;
46
+ if (!best || mtime > best.mtime) {
47
+ best = { path: snapPath, mtime };
48
+ }
49
+ }
50
+ }
51
+ return best?.path ?? null;
52
+ }
19
53
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/commands/data/helpers.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,UAAU,aAAa,CAAC,gBAAwB;IACrD,IAAI,CAAC;QACJ,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,QAAQ;YAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;QACnC,IAAI,CAAC,CAAC,QAAQ;YAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;QACnC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,iCAAiC,CAAC;IAC1C,CAAC;AACF,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,iBAAiB;IAChC,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC"}
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/commands/data/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,mEAAmE;AACnE,MAAM,UAAU,aAAa,CAAC,gBAAwB;IACrD,IAAI,CAAC;QACJ,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,QAAQ;YAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;QACnC,IAAI,CAAC,CAAC,QAAQ;YAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;QACnC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,iCAAiC,CAAC;IAC1C,CAAC;AACF,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,iBAAiB;IAChC,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACpD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,IAAI,IAAI,GAA2C,IAAI,CAAC;IAExD,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SAChE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAEjC,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC3D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAEjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;YACrD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;gBAAE,SAAS;YACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC;YAC7C,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;gBACjC,IAAI,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC;AAC3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/commands/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,eAAe,CAAC;AAoB5E,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,OAAO,EAAE,OAAO,QAwHnD"}
1
+ {"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../src/commands/data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,eAAe,CAAC;AAsB5E,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,OAAO,EAAE,OAAO,QAqInD"}
@@ -1,17 +1,19 @@
1
1
  import { dataPull } from "./data/pull";
2
2
  import { dataApply } from "./data/apply";
3
3
  import { dataRemove } from "./data/remove";
4
+ import { resolveLatestSnapshot } from "./data/helpers";
4
5
  const dataHelp = `
5
6
  instance data snapshots (read-only pull, apply to dev)
6
7
 
7
8
  Usage:
8
9
  blt data pull <instance> [--out DIR] [--format json|yaml|toon] [--skip-users] [--include-secrets] [--skip-extra-tables]
9
- blt data apply <snapshotDir> [--dry-run]
10
+ blt data apply <snapshotDir|latest> [--dry-run] [--out DIR]
10
11
  blt data remove <instance> [--out DIR] [--dry-run]
11
12
 
12
13
  Pull reads from BLT_DATA_PULL_URL (recommended) or SUPABASE_CONNECTION_STRING in a READ ONLY transaction.
13
14
 
14
15
  Apply executes generated SQL against BLT_DATA_LOAD_URL or SUPABASE_CONNECTION_STRING — use only on disposable dev databases.
16
+ Use "latest" as the snapshot target to automatically apply the most recent snapshot.
15
17
 
16
18
  See tools/cli/README.md → "Data snapshots".
17
19
  `;
@@ -64,11 +66,24 @@ export default function dataCommand(program) {
64
66
  program
65
67
  .command("data apply", "Replay a snapshot bundle as SQL on the load database")
66
68
  .hide()
67
- .argument("<snapshotDir>", "Path to stamped snapshot folder (contains manifest.json)")
69
+ .argument("<snapshotDir>", 'Path to stamped snapshot folder (contains manifest.json), or "latest"')
68
70
  .option("--dry-run", "Print combined SQL instead of executing", { default: false })
71
+ .option("-o, --out <dir>", 'Snapshot base directory (used with "latest")', {
72
+ default: "data-snapshots",
73
+ })
69
74
  .action(async ({ args, options, logger, }) => {
70
75
  try {
71
- const dir = args.snapshotDir;
76
+ let dir = args.snapshotDir;
77
+ if (dir === "latest") {
78
+ const baseDir = options.out || "data-snapshots";
79
+ const resolved = resolveLatestSnapshot(baseDir);
80
+ if (!resolved) {
81
+ logger.error(`No snapshots found under ${baseDir}/`);
82
+ process.exit(1);
83
+ }
84
+ dir = resolved;
85
+ logger.info(`Resolved latest → ${dir}`);
86
+ }
72
87
  await dataApply(dir, { dryRun: !!options.dryRun }, logger);
73
88
  }
74
89
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/commands/data.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data.js","sourceRoot":"","sources":["../../src/commands/data.ts"],"names":[],"mappings":"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"}
@@ -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,CAqC7F"}
@@ -0,0 +1,32 @@
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
+ if (!existsSync(scriptPath)) {
14
+ logger.info("Bootstrap script not found — showing what would run:");
15
+ logger.info(` 1. scp ${scriptPath} ${user}@${ip}:/tmp/bootstrap-droplet.sh`);
16
+ logger.info(` 2. ssh ${user}@${ip} 'bash /tmp/bootstrap-droplet.sh'`);
17
+ logger.info(`\nCreate deploy/scripts/bootstrap-droplet.sh to enable automatic setup.`);
18
+ return;
19
+ }
20
+ logger.info(`Uploading bootstrap script to ${user}@${ip}…`);
21
+ const scp = spawnSync("scp", ["-o", "StrictHostKeyChecking=accept-new", scriptPath, `${user}@${ip}:/tmp/bootstrap-droplet.sh`], { stdio: "inherit" });
22
+ if (scp.status !== 0) {
23
+ throw new Error(`scp failed with exit code ${scp.status}`);
24
+ }
25
+ logger.info("Running bootstrap script…");
26
+ const ssh = spawnSync("ssh", ["-o", "StrictHostKeyChecking=accept-new", `${user}@${ip}`, "bash /tmp/bootstrap-droplet.sh"], { stdio: "inherit" });
27
+ if (ssh.status !== 0) {
28
+ throw new Error(`Bootstrap script failed with exit code ${ssh.status}`);
29
+ }
30
+ logger.info("Bootstrap complete.");
31
+ }
32
+ //# 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,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,sBAAsB,CAAC,CAAC;IACpF,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":"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"}
@@ -0,0 +1,60 @@
1
+ export interface Droplet {
2
+ id: number;
3
+ name: string;
4
+ status: string;
5
+ memory: number;
6
+ vcpus: number;
7
+ disk: number;
8
+ region: {
9
+ slug: string;
10
+ name: string;
11
+ };
12
+ image: {
13
+ slug: string;
14
+ name: string;
15
+ };
16
+ size_slug: string;
17
+ networks: {
18
+ v4: Array<{
19
+ ip_address: string;
20
+ type: string;
21
+ }>;
22
+ v6: Array<{
23
+ ip_address: string;
24
+ type: string;
25
+ }>;
26
+ };
27
+ tags: string[];
28
+ created_at: string;
29
+ }
30
+ export interface DnsRecord {
31
+ id: number;
32
+ type: string;
33
+ name: string;
34
+ data: string;
35
+ ttl: number;
36
+ }
37
+ export interface SshKey {
38
+ id: number;
39
+ name: string;
40
+ fingerprint: string;
41
+ public_key: string;
42
+ }
43
+ export interface CreateDropletOptions {
44
+ name: string;
45
+ size: string;
46
+ region: string;
47
+ image: string;
48
+ sshKeys?: string[];
49
+ tags?: string[];
50
+ userData?: string;
51
+ }
52
+ export declare function createDroplet(opts: CreateDropletOptions): Promise<Droplet>;
53
+ export declare function deleteDroplet(id: number): Promise<void>;
54
+ export declare function listDroplets(tag?: string): Promise<Droplet[]>;
55
+ export declare function getDroplet(idOrName: string): Promise<Droplet>;
56
+ export declare function listSshKeys(): Promise<SshKey[]>;
57
+ export declare function createDnsRecord(domain: string, type: string, name: string, data: string, ttl?: number): Promise<DnsRecord>;
58
+ export declare function listDnsRecords(domain: string): Promise<DnsRecord[]>;
59
+ export declare function deleteDnsRecord(domain: string, recordId: number): Promise<void>;
60
+ //# sourceMappingURL=digitalocean.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digitalocean.d.ts","sourceRoot":"","sources":["../../src/lib/digitalocean.ts"],"names":[],"mappings":"AAiEA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QACR,EAAE,EAAE,KAAK,CAAC;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAChD,EAAE,EAAE,KAAK,CAAC;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACjD,CAAC;IACF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,wBAAsB,aAAa,CACjC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAclB;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;AAED,wBAAsB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAMnE;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAcnE;AAMD,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAGrD;AAMD,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,SAAM,GACR,OAAO,CAAC,SAAS,CAAC,CASpB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAKzE;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAKf"}
@@ -0,0 +1,108 @@
1
+ // DigitalOcean API helper — pure API layer, no CLI wiring
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ const DO_API = "https://api.digitalocean.com/v2";
6
+ function readDoctlToken() {
7
+ const configPath = process.platform === "darwin"
8
+ ? join(homedir(), "Library", "Application Support", "doctl", "config.yaml")
9
+ : join(homedir(), ".config", "doctl", "config.yaml");
10
+ try {
11
+ const content = readFileSync(configPath, "utf-8");
12
+ const match = content.match(/^access-token:\s*(.+)$/m);
13
+ return match?.[1]?.trim() || undefined;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ function getToken() {
20
+ const token = process.env.DIGITALOCEAN_ACCESS_TOKEN ??
21
+ process.env.DIGITALOCEAN_TOKEN ??
22
+ readDoctlToken();
23
+ if (!token) {
24
+ throw new Error("DigitalOcean token not found. Either run 'doctl auth init' or set " +
25
+ "DIGITALOCEAN_ACCESS_TOKEN / DIGITALOCEAN_TOKEN.");
26
+ }
27
+ return token;
28
+ }
29
+ async function doFetch(path, options) {
30
+ const res = await fetch(`${DO_API}${path}`, {
31
+ ...options,
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ Authorization: `Bearer ${getToken()}`,
35
+ ...options?.headers,
36
+ },
37
+ });
38
+ if (res.status === 204)
39
+ return undefined;
40
+ const body = await res.text();
41
+ if (!res.ok) {
42
+ throw new Error(`DigitalOcean API ${res.status} ${res.statusText}: ${body}`);
43
+ }
44
+ return body ? JSON.parse(body) : undefined;
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Droplet operations
48
+ // ---------------------------------------------------------------------------
49
+ export async function createDroplet(opts) {
50
+ const { droplet } = await doFetch("/droplets", {
51
+ method: "POST",
52
+ body: JSON.stringify({
53
+ name: opts.name,
54
+ region: opts.region,
55
+ size: opts.size,
56
+ image: opts.image,
57
+ ssh_keys: opts.sshKeys ?? [],
58
+ tags: opts.tags ?? [],
59
+ ...(opts.userData ? { user_data: opts.userData } : {}),
60
+ }),
61
+ });
62
+ return droplet;
63
+ }
64
+ export async function deleteDroplet(id) {
65
+ await doFetch(`/droplets/${id}`, { method: "DELETE" });
66
+ }
67
+ export async function listDroplets(tag) {
68
+ const query = tag ? `?tag_name=${encodeURIComponent(tag)}` : "";
69
+ const { droplets } = await doFetch(`/droplets${query}`);
70
+ return droplets;
71
+ }
72
+ export async function getDroplet(idOrName) {
73
+ if (/^\d+$/.test(idOrName)) {
74
+ const { droplet } = await doFetch(`/droplets/${idOrName}`);
75
+ return droplet;
76
+ }
77
+ const all = await listDroplets();
78
+ const match = all.find((d) => d.name === idOrName);
79
+ if (!match) {
80
+ throw new Error(`Droplet not found: ${idOrName}`);
81
+ }
82
+ return match;
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // SSH key operations
86
+ // ---------------------------------------------------------------------------
87
+ export async function listSshKeys() {
88
+ const { ssh_keys } = await doFetch("/account/keys");
89
+ return ssh_keys;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // DNS operations
93
+ // ---------------------------------------------------------------------------
94
+ export async function createDnsRecord(domain, type, name, data, ttl = 300) {
95
+ const { domain_record } = await doFetch(`/domains/${encodeURIComponent(domain)}/records`, {
96
+ method: "POST",
97
+ body: JSON.stringify({ type, name, data, ttl }),
98
+ });
99
+ return domain_record;
100
+ }
101
+ export async function listDnsRecords(domain) {
102
+ const { domain_records } = await doFetch(`/domains/${encodeURIComponent(domain)}/records`);
103
+ return domain_records;
104
+ }
105
+ export async function deleteDnsRecord(domain, recordId) {
106
+ await doFetch(`/domains/${encodeURIComponent(domain)}/records/${recordId}`, { method: "DELETE" });
107
+ }
108
+ //# sourceMappingURL=digitalocean.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digitalocean.js","sourceRoot":"","sources":["../../src/lib/digitalocean.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,MAAM,GAAG,iCAAiC,CAAC;AAEjD,SAAS,cAAc;IACrB,MAAM,UAAU,GACd,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,OAAO,EAAE,aAAa,CAAC;QAC3E,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IAEzD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACvD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ;IACf,MAAM,KAAK,GACT,OAAO,CAAC,GAAG,CAAC,yBAAyB;QACrC,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC9B,cAAc,EAAE,CAAC;IAEnB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,oEAAoE;YAClE,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,IAAY,EACZ,OAAqB;IAErB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,EAAE;QAC1C,GAAG,OAAO;QACV,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,QAAQ,EAAE,EAAE;YACrC,GAAG,OAAO,EAAE,OAAO;SACpB;KACF,CAAC,CAAC;IAEH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,SAAc,CAAC;IAE9C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,SAAe,CAAC;AACpD,CAAC;AAiDD,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAA0B;IAE1B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAAuB,WAAW,EAAE;QACnE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE;YAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;YACrB,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC;KACH,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAU;IAC5C,MAAM,OAAO,CAAC,aAAa,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAY;IAC7C,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAChC,YAAY,KAAK,EAAE,CACpB,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,OAAO,CAC/B,aAAa,QAAQ,EAAE,CACxB,CAAC;QACF,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,YAAY,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAyB,eAAe,CAAC,CAAC;IAC5E,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,IAAY,EACZ,IAAY,EACZ,IAAY,EACZ,GAAG,GAAG,GAAG;IAET,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,OAAO,CACrC,YAAY,kBAAkB,CAAC,MAAM,CAAC,UAAU,EAChD;QACE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;KAChD,CACF,CAAC;IACF,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,OAAO,CACtC,YAAY,kBAAkB,CAAC,MAAM,CAAC,UAAU,CACjD,CAAC;IACF,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAAc,EACd,QAAgB;IAEhB,MAAM,OAAO,CACX,YAAY,kBAAkB,CAAC,MAAM,CAAC,YAAY,QAAQ,EAAE,EAC5D,EAAE,MAAM,EAAE,QAAQ,EAAE,CACrB,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemarc/blt",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "blt cli",
5
5
  "type": "module",
6
6
  "main": "dist/blt",