jarvis-launcher 1.1.0 → 1.1.2

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 (61) hide show
  1. package/README.md +28 -19
  2. package/dist/commands/backup.d.ts +4 -0
  3. package/dist/commands/backup.js +4 -0
  4. package/dist/commands/backup.js.map +1 -1
  5. package/dist/commands/init.js +15 -0
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/registry-login.d.ts +7 -6
  8. package/dist/commands/registry-login.js +15 -17
  9. package/dist/commands/registry-login.js.map +1 -1
  10. package/dist/commands/start.d.ts +18 -0
  11. package/dist/commands/start.js +119 -9
  12. package/dist/commands/start.js.map +1 -1
  13. package/dist/homepage/logo.d.ts +1 -0
  14. package/dist/homepage/logo.js +25 -13
  15. package/dist/homepage/logo.js.map +1 -1
  16. package/dist/homepage/menu.js +4 -0
  17. package/dist/homepage/menu.js.map +1 -1
  18. package/dist/homepage/prompts.d.ts +69 -24
  19. package/dist/homepage/prompts.js +229 -40
  20. package/dist/homepage/prompts.js.map +1 -1
  21. package/dist/homepage/seed-progress.d.ts +44 -0
  22. package/dist/homepage/seed-progress.js +57 -0
  23. package/dist/homepage/seed-progress.js.map +1 -0
  24. package/dist/homepage/status-view.js +2 -1
  25. package/dist/homepage/status-view.js.map +1 -1
  26. package/dist/lib/azure-blob.d.ts +151 -0
  27. package/dist/lib/azure-blob.js +362 -0
  28. package/dist/lib/azure-blob.js.map +1 -0
  29. package/dist/lib/backup.d.ts +20 -5
  30. package/dist/lib/backup.js +33 -2
  31. package/dist/lib/backup.js.map +1 -1
  32. package/dist/lib/credentials.d.ts +73 -0
  33. package/dist/lib/credentials.js +198 -0
  34. package/dist/lib/credentials.js.map +1 -0
  35. package/dist/lib/docker.d.ts +34 -0
  36. package/dist/lib/docker.js +65 -0
  37. package/dist/lib/docker.js.map +1 -1
  38. package/dist/lib/entra.d.ts +69 -0
  39. package/dist/lib/entra.js +86 -0
  40. package/dist/lib/entra.js.map +1 -0
  41. package/dist/lib/health.js +2 -1
  42. package/dist/lib/health.js.map +1 -1
  43. package/dist/lib/progress.d.ts +59 -3
  44. package/dist/lib/progress.js +89 -6
  45. package/dist/lib/progress.js.map +1 -1
  46. package/dist/lib/registry.d.ts +29 -15
  47. package/dist/lib/registry.js +47 -34
  48. package/dist/lib/registry.js.map +1 -1
  49. package/dist/lib/restore.d.ts +29 -4
  50. package/dist/lib/restore.js +52 -4
  51. package/dist/lib/restore.js.map +1 -1
  52. package/dist/lib/seed-plan.d.ts +14 -0
  53. package/dist/lib/seed-plan.js +1 -0
  54. package/dist/lib/seed-plan.js.map +1 -1
  55. package/dist/lib/state.d.ts +50 -8
  56. package/dist/lib/state.js +61 -10
  57. package/dist/lib/state.js.map +1 -1
  58. package/dist/lib/terminal-style.d.ts +5 -0
  59. package/dist/lib/terminal-style.js +8 -0
  60. package/dist/lib/terminal-style.js.map +1 -0
  61. package/package.json +5 -2
package/README.md CHANGED
@@ -12,14 +12,12 @@ You need exactly three things on the host:
12
12
 
13
13
  1. **Docker** with **Compose v2** (`docker compose`, the plugin — not the old `docker-compose`). Docker Desktop on macOS/Windows or Docker Engine on Linux both work.
14
14
  2. **Node.js ≥ 20 (LTS)** — required to install and run the CLI.
15
- 3. An **ACR repository-scoped token** for the integrator image registry `integratori-ggeybpb3azacdpfw.azurecr.io`. The token comes as a pair:
16
- - the **token name** is the Docker login **username**;
17
- - the **token password** is the **password**.
15
+ 3. A **Microsoft Entra application client secret** for the integrator's Azure tenant. One Entra application authenticates **both** the image registry `integratori-ggeybpb3azacdpfw.azurecr.io` (it must hold the **AcrPull** role) and the seed catalog in Azure Blob Storage (it must hold **Storage Blob Data Reader** on the `seedintegratori`/`seeds` container). You supply only the **client secret** — the application (client) id and tenant id are baked in.
18
16
 
19
17
  That is the whole floor. In particular you do **not** need:
20
18
 
21
19
  - **No** host-side MongoDB tools (`mongosh`, `mongorestore`, …) — every Mongo operation runs inside a container.
22
- - **No** Azure CLI — authentication is a plain `docker login` against the registry (see [`registry-login`](#registry-login)).
20
+ - **No** Azure CLI — the launcher logs Docker in as the Entra application (`docker login … --password-stdin`) and talks to Blob Storage over plain REST (see [`registry-login`](#registry-login)).
23
21
 
24
22
  > **Apple Silicon (arm64):** the optional **SQL Server** service (`enable sql` / `start --sql`) runs only as an **x64** image — Microsoft ships no arm64 build of SQL Server — so on an Apple Silicon Mac it runs under emulation. Enable **"Use Rosetta for x86/amd64 emulation"** in Docker Desktop → *Settings → General* for it to start reliably; without it the QEMU fallback is slow and SQL Server can crash. The always-on core stack (Jarvis / Mongo / ES) runs natively on arm64 and is unaffected.
25
23
 
@@ -60,21 +58,20 @@ The all-in-one image lives in a private ACR, so Docker must be authenticated bef
60
58
  jarvis-launcher registry-login
61
59
  ```
62
60
 
63
- You are prompted (masked) for the **token name** (username) and **token password**. The credential is stored by **Docker's own credential store** (`~/.docker/config.json` / your OS keychain) — the launcher persists no secret itself — so you are asked **only once per machine**. Subsequent commands that pull the image reuse the stored credential silently; if the credential is ever missing or rejected, `start`/`update` re-prompt automatically and retry once.
61
+ You are prompted (masked) for the **Entra application client secret**. The launcher logs Docker in as the Entra application — `docker login <registry> -u <client-id> --password-stdin` — and Docker's own credential store (`~/.docker/config.json` / your OS keychain) persists the resulting login. The **prompted client secret** is also persisted by the launcher, in your **OS keychain** (macOS Keychain / Windows Credential Manager / Linux Secret Service via `@napi-rs/keyring`), falling back to a `chmod 0600` `~/.jarvis-launcher/credentials.json` when the keychain is unavailable — so you are asked **only once per machine**, and the **same** secret is reused for the seed catalog. Subsequent commands reuse it silently; if it is ever **rejected**, the launcher clears the stored secret and re-prompts, then retries once (a Docker-daemon/network failure is reported as such and does **not** clear the stored secret).
64
62
 
65
- You can supply the credential non-interactively (e.g. in CI):
63
+ You can supply the client secret non-interactively (e.g. in CI):
66
64
 
67
65
  ```bash
68
- # via flags
69
- jarvis-launcher registry-login --user <token-name> --password <token-password>
66
+ # via flag
67
+ jarvis-launcher registry-login --client-secret <client-secret>
70
68
 
71
- # or via environment variables
72
- export JARVIS_REGISTRY_USER=<token-name>
73
- export JARVIS_REGISTRY_PASSWORD=<token-password>
69
+ # or via environment variable
70
+ export JARVIS_CLIENT_SECRET=<client-secret>
74
71
  jarvis-launcher registry-login
75
72
  ```
76
73
 
77
- The credential-source precedence is **flags → environment variables → masked prompt**. `--registry <host>` overrides the default registry host if needed.
74
+ The client-secret precedence is **flag → `JARVIS_CLIENT_SECRET` env → stored (keychain / file) → masked prompt**. The flag and the env var are deliberately **ephemeral, per-invocation overrides**: they are **never written to the keychain** (only a prompted secret is persisted), and when one of them is rejected the launcher leaves any stored secret untouched — fix or unset the override instead. `--registry <host>` overrides the default registry host if needed. The application (client) id and tenant id default to the integrator's and can be overridden with `JARVIS_CLIENT_ID` / `JARVIS_TENANT_ID`.
78
75
 
79
76
  > **`registry-login` is Docker registry authentication.** It is **not** the same as the Jarvis dev-[`login`](#login) command, which opens a browser impersonation session into a running Jarvis. Keep the two distinct.
80
77
 
@@ -119,12 +116,14 @@ Notes:
119
116
 
120
117
  ### Pre-seeding the initial state (`seed/state.json`)
121
118
 
122
- An integrator packaging a scenario can ship a **`seed/state.json`** file to pre-declare the environment's **initial** state, so the first `init`/`start` picks up the right project name, optional profiles, and image tag without anyone having to remember the flags. It is read **only when the scenario has no `.orchestrator/state.json` yet** (a brand-new environment); once the scenario has been started, its own `.orchestrator/state.json` drives everything and the seed is ignored.
119
+ An integrator packaging a scenario can ship a **`seed/state.json`** file to give the seed an identity (a name and description) and pre-declare the environment's **initial** state, so the first `init`/`start` picks up the right project name, optional profiles, and image tag without anyone having to remember the flags. It is read **only when the scenario has no `.orchestrator/state.json` yet** (a brand-new environment); once the scenario has been started, its own `.orchestrator/state.json` drives everything and the seed is ignored.
123
120
 
124
- Only three properties are honored — anything else in the file (notably a `ports` block) is ignored, so you can copy a working environment's `.orchestrator/state.json` verbatim and only the portable bits are applied (fresh host ports are always re-allocated for the new machine):
121
+ Only the properties below are honored — anything else in the file (notably a `ports` block) is ignored, so you can copy a working environment's `.orchestrator/state.json` verbatim and only the portable bits are applied (fresh host ports are always re-allocated for the new machine):
125
122
 
126
123
  ```json
127
124
  {
125
+ "name": "CMMS Demo",
126
+ "description": "Maintenance-management demo with SQL and sample data",
128
127
  "projectName": "cmms",
129
128
  "enabledProfiles": ["sql"],
130
129
  "tag": "1.2.3"
@@ -133,13 +132,23 @@ Only three properties are honored — anything else in the file (notably a `port
133
132
 
134
133
  | Property | Meaning | Default when omitted |
135
134
  |---|---|---|
135
+ | `name` | short human-readable label for the seed (identity metadata; not applied to the environment) | none |
136
+ | `description` | free-text summary of what the seed installs (identity metadata; not applied to the environment) | none |
136
137
  | `projectName` | the `docker compose -p` project name | derived from the folder name |
137
138
  | `enabledProfiles` | optional profiles to turn on (`sql`, `mail`) | none |
138
139
  | `tag` | base image tag to pin the scenario to | `latest` |
139
140
 
141
+ `name` and `description` describe the seed itself — they are read back via `readSeedState` (the groundwork for letting the launcher list and choose a seed to import) but do not change what `start` brings up.
142
+
140
143
  Explicit CLI flags always win over the seed: `--tag` overrides `tag`, and `--sql`/`--mail` are **added to** the seed's `enabledProfiles`.
141
144
 
142
- In the interactive homepage, choosing **Start** on a brand-new scenario that ships a `seed/state.json` skips the initial questions (folder / tag / SQL / mail / force) and starts straight away — the seed already pre-declares that initial state, so there is nothing to ask.
145
+ In the interactive homepage, choosing **Start** on a brand-new (uninitialized) folder first asks for the **scenario folder** (default: the current directory — one Enter keeps it, like Stop/Restore/Backup), then opens a submenu for that folder. A **local `seed/` takes precedence** over the remote catalog — when the folder already ships one (any payload counts, including a config-only seed), the remote catalog is never contacted (no client-secret prompt / network):
146
+
147
+ - **Start from local seed** *(shown only when a local `seed/` is present)* — install the shipped seed by starting the folder. A seed **declared** by `seed/state.json` dictates its own environment and starts as-is (the status header above already previews what it will install); an **undeclared** seed (data/config only) still asks for the image tag and the optional SQL/mail toggles — with SQL defaulting to **on** when the seed ships a SQL backup.
148
+ - **Start from an existing scenario** *(shown only when there is no local seed)* — pick from the **remote seed catalog**: the `.zip` blobs in the `seedintegratori`/`seeds` container, each shown by its blob-metadata `name`/`description`. Picking one downloads and extracts it into `<folder>/seed` (staged first — a failed download never leaves a half-installed or wiped seed), then starts it. An empty or unreachable catalog (offline / rejected secret) shows a short notice and returns.
149
+ - **Start from an empty environment** — bring up a blank stack. If the folder ships a `seed/` (even a config-only one), this first asks to confirm and then **permanently deletes** the `seed/` folder (so the stack comes up empty instead of re-applying the seed), then asks for the image tag and the optional SQL/mail toggles.
150
+
151
+ Choosing **Start** on an already-initialized folder (a stopped environment) skips the folder prompt and the submenu and resumes it directly.
143
152
 
144
153
  ### Pre-seeding the base configs (`seed/config/`)
145
154
 
@@ -179,7 +188,7 @@ After a restore, Elasticsearch repopulates in the background; the launcher print
179
188
  | Command | Synopsis | What it does |
180
189
  |---|---|---|
181
190
  | [`init`](#init) | `init [folder]` | Provision the three base config files into an empty/config-less folder. |
182
- | [`registry-login`](#registry-login) | `registry-login [--registry <host>] [--user <name>] [--password <pwd>]` | Authenticate Docker against the image registry (once per machine). |
191
+ | [`registry-login`](#registry-login) | `registry-login [--registry <host>] [--client-secret <secret>]` | Authenticate Docker against the image registry as the Entra application (once per machine). |
183
192
  | [`start`](#start) | `start [folder] [--sql] [--mail] [--force]` | Bring up the full stack (auto-init + auto-restore) and report the URLs. |
184
193
  | [`stop`](#stop) | `stop [folder]` | Stop the stack (remove containers) while keeping all on-disk data. |
185
194
  | [`restore`](#restore) | `restore [folder] [--force]` | Restore the prepared artifact into the running stack (gated by `--force`). |
@@ -200,10 +209,10 @@ You rarely need `init` on its own — `start` performs the same provisioning aut
200
209
  ### `registry-login`
201
210
 
202
211
  ```bash
203
- jarvis-launcher registry-login [--registry <host>] [--user <token-name>] [--password <token-password>]
212
+ jarvis-launcher registry-login [--registry <host>] [--client-secret <secret>]
204
213
  ```
205
214
 
206
- Forces a fresh `docker login` against the integrator registry so you can pre-seed or refresh the stored credential. See [Authenticate once](#authenticate-once-registry-login) above for the credential sources and the once-per-machine behavior.
215
+ Forces a fresh `docker login` against the integrator registry (as the Entra application) so you can pre-seed or refresh the stored credential. See [Authenticate once](#authenticate-once-registry-login) above for the client-secret sources and the once-per-machine behavior.
207
216
 
208
217
  ### `start`
209
218
 
@@ -458,7 +467,7 @@ Docker/Mongo/ES-driven behavior (real `compose up/down`, copy-from-image, live r
458
467
 
459
468
  ## Troubleshooting
460
469
 
461
- - **`unauthorized` / `401` when pulling** — your Docker credential for the registry is missing or expired. Run `jarvis-launcher registry-login` again (or supply `--user`/`--password`).
470
+ - **`unauthorized` / `401` when pulling** — the Entra client secret is missing, expired, or the application lacks the **AcrPull** role. Run `jarvis-launcher registry-login` again (or supply `--client-secret` / `JARVIS_CLIENT_SECRET`); a rejected secret is cleared automatically so you are re-prompted.
462
471
  - **"Refusing to restore: data already exists"** — the database is already populated. Re-run `restore` with `--force` to drop and reload, or leave it as-is.
463
472
  - **Port already in use** — the launcher re-probes persisted ports on every `start` and re-allocates a fresh block if one is taken, reporting the new proxy URL. There is a tiny window between picking a port and `compose up`; if a true conflict is hit, you get a clear bind-conflict error rather than a hang.
464
473
  - **"Nothing to stop / no started stack"** — that scenario was never started (no `.orchestrator/state.json`). Run `start` first.
@@ -34,6 +34,8 @@ export interface RunBackupResult {
34
34
  dsdataPath: string | null;
35
35
  /** Absolute path to the automation artifact when it was refreshed. */
36
36
  automationPath: string | null;
37
+ /** Absolute path to the encryption-keys artifact when it was refreshed. */
38
+ keysPath: string | null;
37
39
  /** Absolute path to the base-config artifact when it was refreshed. */
38
40
  configPath: string | null;
39
41
  /** Absolute path to the SQL `.bak` when it was refreshed (null when SQL is off or the seed ships none). */
@@ -42,6 +44,8 @@ export interface RunBackupResult {
42
44
  dsFiles: number;
43
45
  /** Top-level entries snapshotted into `seed/automation` (0 when absent). */
44
46
  automationEntries: number;
47
+ /** Top-level entries snapshotted into `seed/keys` (0 when absent). */
48
+ keyEntries: number;
45
49
  /** Base config files snapshotted into `seed/config` (0 when skipped). */
46
50
  configFiles: number;
47
51
  }
@@ -112,6 +112,8 @@ export async function runBackup(options = {}) {
112
112
  parts.push(`seed/dsdata (${result.dsFiles} document-store files)`);
113
113
  if (result.automationEntries > 0)
114
114
  parts.push('seed/automation');
115
+ if (result.keyEntries > 0)
116
+ parts.push('seed/keys (encryption keys)');
115
117
  if (result.configFiles > 0)
116
118
  parts.push('seed/config (base configuration)');
117
119
  if (sqlBackupPath)
@@ -121,10 +123,12 @@ export async function runBackup(options = {}) {
121
123
  dumpPath: join(base, 'seed', 'dump'),
122
124
  dsdataPath: result.dsFiles > 0 ? join(base, 'seed', 'dsdata') : null,
123
125
  automationPath: result.automationEntries > 0 ? join(base, 'seed', 'automation') : null,
126
+ keysPath: result.keyEntries > 0 ? join(base, 'seed', 'keys') : null,
124
127
  configPath: result.configFiles > 0 ? join(base, 'seed', 'config') : null,
125
128
  sqlBackupPath,
126
129
  dsFiles: result.dsFiles,
127
130
  automationEntries: result.automationEntries,
131
+ keyEntries: result.keyEntries,
128
132
  configFiles: result.configFiles,
129
133
  };
130
134
  // The backup brought `mongo` (and `sql`, when enabled) up on its own to run the
@@ -1 +1 @@
1
- {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/commands/backup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/commands/backup.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { prepareScenario } from '../lib/scenario.js';
2
2
  import { provisionConfigs, provisionConfigsFromSeed, missingConfigs } from '../lib/config.js';
3
+ import { dockerPull } from '../lib/docker.js';
3
4
  import { resolveImageTag, DEFAULT_BASE_TAG } from '../lib/registry.js';
4
5
  import { readState, readSeedState, writeState, defaultProjectName, } from '../lib/state.js';
5
6
  import { progress } from '../lib/progress.js';
@@ -82,6 +83,20 @@ export async function runInit(folder, tag) {
82
83
  // take a while on a cold cache.
83
84
  progress(`Provisioning base configuration into "${base}"…`);
84
85
  const { baseTag, image } = await resolveImageTag({ baseTag: tag ?? seed?.tag });
86
+ // This branch exists to materialize the image's current reference configs, so a
87
+ // stale locally cached copy of the resolved tag (the moving `latest` above all)
88
+ // must be refreshed first — the copy-from-image below never pulls when a local
89
+ // image exists. A fast no-op when already current; a failure is downgraded to a
90
+ // warning because `resolveImageTag` just proved the tag reachable, and the copy
91
+ // below falls back to the cached image (or surfaces the real error itself).
92
+ progress(`Pulling ${image} so the configuration comes from the newest build…`);
93
+ try {
94
+ await dockerPull(image);
95
+ }
96
+ catch (error) {
97
+ progress(`⚠️ Could not pull ${image} — continuing with the locally cached image.\n` +
98
+ `${error.message}`);
99
+ }
85
100
  const provisioned = await provisionConfigs({ base, image });
86
101
  // Persist the initial state: a non-`latest` pinned tag (so a later tag-less `start`
87
102
  // reuses it without re-asking) plus any seed-declared project name / profiles.
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
1
  import { Command } from 'commander';
2
2
  /**
3
- * `registry-login` — explicit `docker login` to the integrator-distribution ACR.
3
+ * `registry-login` — explicit `docker login` to the integrator-distribution ACR
4
+ * as the shared Microsoft Entra application.
4
5
  *
5
6
  * A thin wrapper over {@link ensureRegistryAuth}: it always forces a fresh login
6
7
  * (`force: true`, skipping the no-op probe) so the integrator can pre-seed or
7
- * refresh the credential at will (FR-ORC-047). Credentials follow the lib's own
8
- * precedence — `--registry`/`--user`/`--password` flags, then the
9
- * `JARVIS_REGISTRY_USER`/`JARVIS_REGISTRY_PASSWORD` env vars, then a masked
10
- * prompt — and are persisted by Docker's own credential store, so this is a
11
- * once-per-machine step (SC-ORC-008). No secret is written by the orchestrator.
8
+ * refresh the credential at will (FR-ORC-047). The client secret follows the lib's
9
+ * own precedence — the `--client-secret` flag, then the `JARVIS_CLIENT_SECRET` env
10
+ * var, then the stored copy (OS keychain / fallback file), then a masked prompt —
11
+ * and the resulting `docker login` is persisted by Docker's own credential store,
12
+ * so this is a once-per-machine step (SC-ORC-008).
12
13
  *
13
14
  * This is **Docker registry auth**, distinct from the Jarvis dev-`login` command
14
15
  * (browser impersonation).
@@ -1,15 +1,16 @@
1
1
  import { ensureRegistryAuth, DEFAULT_REGISTRY, } from '../lib/registry.js';
2
2
  import { progress } from '../lib/progress.js';
3
3
  /**
4
- * `registry-login` — explicit `docker login` to the integrator-distribution ACR.
4
+ * `registry-login` — explicit `docker login` to the integrator-distribution ACR
5
+ * as the shared Microsoft Entra application.
5
6
  *
6
7
  * A thin wrapper over {@link ensureRegistryAuth}: it always forces a fresh login
7
8
  * (`force: true`, skipping the no-op probe) so the integrator can pre-seed or
8
- * refresh the credential at will (FR-ORC-047). Credentials follow the lib's own
9
- * precedence — `--registry`/`--user`/`--password` flags, then the
10
- * `JARVIS_REGISTRY_USER`/`JARVIS_REGISTRY_PASSWORD` env vars, then a masked
11
- * prompt — and are persisted by Docker's own credential store, so this is a
12
- * once-per-machine step (SC-ORC-008). No secret is written by the orchestrator.
9
+ * refresh the credential at will (FR-ORC-047). The client secret follows the lib's
10
+ * own precedence — the `--client-secret` flag, then the `JARVIS_CLIENT_SECRET` env
11
+ * var, then the stored copy (OS keychain / fallback file), then a masked prompt —
12
+ * and the resulting `docker login` is persisted by Docker's own credential store,
13
+ * so this is a once-per-machine step (SC-ORC-008).
13
14
  *
14
15
  * This is **Docker registry auth**, distinct from the Jarvis dev-`login` command
15
16
  * (browser impersonation).
@@ -21,27 +22,24 @@ import { progress } from '../lib/progress.js';
21
22
  export function registerRegistryLoginCommand(program) {
22
23
  program
23
24
  .command('registry-login')
24
- .description('Authenticate Docker against the Jarvis image registry (once per machine). ' +
25
- 'Prompts for an ACR repository-scoped token (token name + password) unless ' +
26
- 'supplied via flags or the JARVIS_REGISTRY_USER/JARVIS_REGISTRY_PASSWORD env vars.')
25
+ .description('Authenticate Docker against the Jarvis image registry (once per machine) as the ' +
26
+ 'Microsoft Entra application. Uses the client secret from --client-secret, the ' +
27
+ 'JARVIS_CLIENT_SECRET env var, the stored secret, or a masked prompt.')
27
28
  .option('--registry <registry>', 'registry host to log in to (defaults to the integrator ACR)')
28
- .option('--user <user>', 'ACR repository-scoped token name (username)')
29
- .option('--password <password>', 'long-lived token password')
29
+ .option('--client-secret <secret>', 'the Entra application client secret')
30
30
  .action(async (opts) => {
31
31
  // Force a fresh login; pass only the flags the integrator actually supplied
32
- // so absent ones fall through to the lib's env → masked-prompt precedence.
32
+ // so absent ones fall through to the lib's env → stored → prompt precedence.
33
33
  const authOptions = { force: true };
34
34
  if (opts.registry !== undefined)
35
35
  authOptions.registry = opts.registry;
36
- if (opts.user !== undefined)
37
- authOptions.user = opts.user;
38
- if (opts.password !== undefined)
39
- authOptions.password = opts.password;
36
+ if (opts.clientSecret !== undefined)
37
+ authOptions.clientSecret = opts.clientSecret;
40
38
  const registry = opts.registry ?? DEFAULT_REGISTRY;
41
39
  progress(`Logging Docker in to ${registry}…`);
42
40
  await ensureRegistryAuth(authOptions);
43
41
  // The lib throws on failure, so reaching here means the login succeeded —
44
- // confirm it explicitly (creds may have come from flags/env with no prompt).
42
+ // confirm it explicitly (the secret may have come from a flag/env with no prompt).
45
43
  progress(`Logged in to ${registry}. Docker will reuse this credential.`);
46
44
  });
47
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry-login.js","sourceRoot":"","sources":["../../src/commands/registry-login.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GAEjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB;IAC3D,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CACV,4EAA4E;QAC1E,4EAA4E;QAC5E,mFAAmF,CACtF;SACA,MAAM,CAAC,uBAAuB,EAAE,6DAA6D,CAAC;SAC9F,MAAM,CAAC,eAAe,EAAE,6CAA6C,CAAC;SACtE,MAAM,CAAC,uBAAuB,EAAE,2BAA2B,CAAC;SAC5D,MAAM,CAAC,KAAK,EAAE,IAA6D,EAAE,EAAE;QAC9E,4EAA4E;QAC5E,2EAA2E;QAC3E,MAAM,WAAW,GAA8B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QACtE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,WAAW,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1D,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAEtE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,gBAAgB,CAAC;QACnD,QAAQ,CAAC,wBAAwB,QAAQ,GAAG,CAAC,CAAC;QAC9C,MAAM,kBAAkB,CAAC,WAAW,CAAC,CAAC;QACtC,0EAA0E;QAC1E,6EAA6E;QAC7E,QAAQ,CAAC,gBAAgB,QAAQ,sCAAsC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"registry-login.js","sourceRoot":"","sources":["../../src/commands/registry-login.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GAEjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAgB;IAC3D,OAAO;SACJ,OAAO,CAAC,gBAAgB,CAAC;SACzB,WAAW,CACV,kFAAkF;QAChF,gFAAgF;QAChF,sEAAsE,CACzE;SACA,MAAM,CAAC,uBAAuB,EAAE,6DAA6D,CAAC;SAC9F,MAAM,CAAC,0BAA0B,EAAE,qCAAqC,CAAC;SACzE,MAAM,CAAC,KAAK,EAAE,IAAkD,EAAE,EAAE;QACnE,4EAA4E;QAC5E,6EAA6E;QAC7E,MAAM,WAAW,GAA8B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC/D,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;YAAE,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QACtE,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,WAAW,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QAElF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,gBAAgB,CAAC;QACnD,QAAQ,CAAC,wBAAwB,QAAQ,GAAG,CAAC,CAAC;QAC9C,MAAM,kBAAkB,CAAC,WAAW,CAAC,CAAC;QACtC,0EAA0E;QAC1E,mFAAmF;QACnF,QAAQ,CAAC,gBAAgB,QAAQ,sCAAsC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1,5 +1,14 @@
1
1
  import type { Command } from 'commander';
2
2
  import { type SeedSqlOptions } from '../lib/sql.js';
3
+ /** Resolution for a new environment whose compose project name already exists in docker. */
4
+ export type ProjectCollisionResolution = {
5
+ action: 'replace';
6
+ } | {
7
+ action: 'rename';
8
+ newName: string;
9
+ } | {
10
+ action: 'abort';
11
+ };
3
12
  /** Options for {@link runStart}. */
4
13
  export interface StartOptions {
5
14
  /** Scenario folder (positional arg); defaults to the current working dir. */
@@ -23,6 +32,15 @@ export interface StartOptions {
23
32
  * to `lib/sql`'s {@link seedSql}.
24
33
  */
25
34
  seedSqlImpl?: (options: SeedSqlOptions) => Promise<void>;
35
+ /**
36
+ * Injectable collision prompt, invoked on a brand-new environment (no persisted
37
+ * state) whose resolved project name already has containers/volumes in docker.
38
+ * Defaults to an `@inquirer` select+input (mirrors `resolveImageTag`'s injected
39
+ * `promptForTag`); pinned by the unit tests. The default throws when stdin is
40
+ * not a TTY, so a non-interactive run fails with an actionable message instead
41
+ * of an opaque prompt error.
42
+ */
43
+ onProjectNameCollision?: (project: string) => Promise<ProjectCollisionResolution>;
26
44
  }
27
45
  /**
28
46
  * Bring up a fully-working, logged-in environment in one command. See module docs
@@ -1,10 +1,11 @@
1
1
  import { writeFileSync } from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { input, select } from '@inquirer/prompts';
3
4
  import { prepareScenario } from '../lib/scenario.js';
4
5
  import { provisionConfigs, provisionConfigsFromSeed, missingConfigs } from '../lib/config.js';
5
6
  import { resolveImageTag, DEFAULT_BASE_TAG } from '../lib/registry.js';
6
7
  import { readMongoCreds, generateEnv, templatePath, DEFAULT_SQL_SA_PASSWORD } from '../lib/compose.js';
7
- import { composeUp, listComposeServices } from '../lib/docker.js';
8
+ import { composeUp, composeProjectExists, destroyComposeProject, dockerPull, listComposeServices, } from '../lib/docker.js';
8
9
  import { writeExternalServicesCompose, externalComposePath } from '../lib/external-services.js';
9
10
  import { mergeProxyConfig } from '../lib/proxy-merge.js';
10
11
  import { findBackupFile, seedSql } from '../lib/sql.js';
@@ -13,9 +14,10 @@ import { restore, isArtifactPresent } from '../lib/restore.js';
13
14
  import { waitForProxy, pollReindexProgress, REINDEX_BANNER } from '../lib/health.js';
14
15
  import { waitPortFree, allocatePortBlock, allocateAttachedPorts } from '../lib/ports.js';
15
16
  import { OPTIONAL_PROFILE_PORTS, optionalComposeServices, isOptionalProfile } from '../lib/profiles.js';
16
- import { progress, createProgressBar } from '../lib/progress.js';
17
+ import { progress, createProgressBar, createSpinner } from '../lib/progress.js';
18
+ import { gray } from '../lib/terminal-style.js';
17
19
  import { buildLoginUrl, ADMIN_EMAIL, USER_EMAIL } from './login.js';
18
- import { readState, readSeedState, writeState, defaultProjectName, envFilePath, } from '../lib/state.js';
20
+ import { readState, readSeedState, writeState, defaultProjectName, sanitizeProjectName, envFilePath, } from '../lib/state.js';
19
21
  /**
20
22
  * start.ts — `jarvis-launcher start [folder] [--sql] [--mail] [--force]` (T-2-22).
21
23
  *
@@ -181,6 +183,76 @@ async function persistedPortsStillFree(ports) {
181
183
  const results = await Promise.all(persistedPorts.map((port) => waitPortFree(port)));
182
184
  return results.every((free) => free);
183
185
  }
186
+ /**
187
+ * Default {@link StartOptions.onProjectNameCollision}: ask the integrator how to
188
+ * proceed when docker already holds an environment under the resolved project
189
+ * name. Non-interactive runs (no TTY) fail fast with an actionable message —
190
+ * nothing has been started or modified at this point.
191
+ */
192
+ async function promptProjectNameCollision(project) {
193
+ if (!process.stdin.isTTY) {
194
+ throw new Error(`There is already an environment named "${project}" (leftover containers or data ` +
195
+ `volumes from a previous environment). Re-run interactively to choose what to do, ` +
196
+ `use a different folder name, or remove the existing environment first. ` +
197
+ `Nothing was started.`);
198
+ }
199
+ const action = await select({
200
+ message: `There is already an environment named "${project}". How do you want to proceed?`,
201
+ choices: [
202
+ {
203
+ name: `Permanently delete the existing "${project}" environment and create a fresh one`,
204
+ value: 'replace',
205
+ },
206
+ { name: 'Create this environment under a different name', value: 'rename' },
207
+ { name: 'Abort — leave everything untouched', value: 'abort' },
208
+ ],
209
+ });
210
+ if (action !== 'rename')
211
+ return { action };
212
+ const newName = await input({ message: 'New project name', default: `${project}-2` });
213
+ return { action: 'rename', newName };
214
+ }
215
+ /**
216
+ * Resolve the compose project name for a BRAND-NEW environment. Compose keys
217
+ * everything off `-p <project>`, so a new folder whose name matches an existing
218
+ * stack would silently adopt that stack's containers, network and data volumes.
219
+ * Probe docker for any footprint of the candidate name and, on a collision, ask
220
+ * the integrator (via `prompt`) to replace the leftover environment, pick a
221
+ * different name (re-probed — the new name may itself collide), or abort.
222
+ *
223
+ * A probe failure (docker engine unreachable) returns the name as-is: a
224
+ * read-only check must never abort `start` — the later `composeUp` surfaces the
225
+ * real engine error (mirrors {@link isProjectRunning}).
226
+ */
227
+ async function resolveNewProjectName(initial, prompt) {
228
+ let project = initial;
229
+ for (;;) {
230
+ let exists;
231
+ try {
232
+ exists = await composeProjectExists(project);
233
+ }
234
+ catch {
235
+ return project;
236
+ }
237
+ if (!exists)
238
+ return project;
239
+ const resolution = await prompt(project);
240
+ if (resolution.action === 'abort') {
241
+ throw new Error(`Aborted: docker already has an environment named "${project}". Nothing was started.`);
242
+ }
243
+ if (resolution.action === 'replace') {
244
+ progress(`Deleting the existing "${project}" docker environment (containers + data volumes)…`);
245
+ await destroyComposeProject(project);
246
+ return project;
247
+ }
248
+ const requested = resolution.newName.trim() || `${project}-2`;
249
+ const sanitized = sanitizeProjectName(requested);
250
+ if (sanitized !== requested) {
251
+ progress(`Project name sanitized to "${sanitized}".`);
252
+ }
253
+ project = sanitized;
254
+ }
255
+ }
184
256
  /**
185
257
  * Bring up a fully-working, logged-in environment in one command. See module docs
186
258
  * for the load-bearing ordering (FR-ORC-011).
@@ -212,6 +284,17 @@ export async function runStart(options) {
212
284
  // Only consulted when there is NO persisted state — an already-started scenario is
213
285
  // driven entirely by its own `.orchestrator/state.json`. Explicit flags win over it.
214
286
  const seedState = persistedState === null ? await readSeedState(base) : null;
287
+ // 1a. Resolve the compose project name — persisted, else seed-pinned, else derived
288
+ // from the folder basename. On a BRAND-NEW environment, probe docker for a
289
+ // leftover same-named stack FIRST (before the registry or configs are touched,
290
+ // so an abort leaves no trace): compose would otherwise silently adopt its
291
+ // containers and data volumes. A folder that only ever ran `init` carries a
292
+ // state file and skips this gate — same `persistedState === null` signal as the
293
+ // stale-volume / brand-new-folder SQL paths below.
294
+ let project = persistedState?.projectName ?? seedState?.projectName ?? defaultProjectName(base);
295
+ if (persistedState === null) {
296
+ project = await resolveNewProjectName(project, options.onProjectNameCollision ?? promptProjectNameCollision);
297
+ }
215
298
  // 1b. Resolve the image tag ONCE up front. The tag is the explicit `--tag`, else
216
299
  // the tag remembered in state (so a non-`latest` tag is picked just once), else
217
300
  // the seed's tag on a brand-new environment, else the arch-correct `latest`.
@@ -223,6 +306,26 @@ export async function runStart(options) {
223
306
  const { baseTag, image } = await resolveImageTag({
224
307
  baseTag: options.tag ?? persistedState?.tag ?? seedState?.tag,
225
308
  });
309
+ // 1c. New environment: a locally cached image for the resolved tag may be stale —
310
+ // compose's default `missing` pull policy would silently reuse it, the moving
311
+ // `latest` in particular. Refresh it explicitly so a brand-new environment
312
+ // always starts on the newest published build; this also freshens the image
313
+ // the auto-init below copies configs from. `docker pull` is a fast no-op when
314
+ // the local image is already current. A failure is downgraded to a warning:
315
+ // `resolveImageTag` just proved the tag reachable, so a failure here is
316
+ // transient, and compose falls back to the cached image (or surfaces the real
317
+ // error itself when none exists). Routine restarts of an EXISTING environment
318
+ // never pull — `update` stays the deliberate refresh path.
319
+ if (persistedState === null) {
320
+ progress(`Pulling ${image} so the new environment starts on the newest build…`);
321
+ try {
322
+ await dockerPull(image);
323
+ }
324
+ catch (error) {
325
+ progress(`⚠️ Could not pull ${image} — continuing with the locally cached image.\n` +
326
+ `${error.message}`);
327
+ }
328
+ }
226
329
  // 2. Config absent → auto-init by calling the LIBs directly (FR-ORC-038),
227
330
  // seed-first: a seed-shipped `seed/config/` wins over the image copies, the
228
331
  // image fills whatever the seed did not provide. This runs BEFORE
@@ -243,9 +346,8 @@ export async function runStart(options) {
243
346
  // 3. Read Mongo creds ONCE (R-ORC2) — used by the empty-check + restore below.
244
347
  const mongoCreds = readMongoCreds(base);
245
348
  // Enabled profiles: union of the persisted (or, on a brand-new environment, the
246
- // seed-declared) set and the requested --sql/--mail. The project name likewise
247
- // falls back to the seed's before the folder-derived default.
248
- const project = persistedState?.projectName ?? seedState?.projectName ?? defaultProjectName(base);
349
+ // seed-declared) set and the requested --sql/--mail. The project name was
350
+ // resolved (and collision-checked) in step 1a above.
249
351
  const network = networkFor(project);
250
352
  const profiles = mergeProfiles(persistedState?.enabledProfiles ?? seedState?.enabledProfiles, options);
251
353
  // 4. Ports: re-probe persisted block; re-allocate + rewrite state if any taken
@@ -444,13 +546,21 @@ export async function runStart(options) {
444
546
  progress('Starting Jarvis…');
445
547
  await composeUp({ project, template: templates, envFile, services: ['jarvis'], wait: true, image });
446
548
  // 9. Gate the report on the readiness poll (R-ORC8 — clear timeout, never a hang).
447
- progress('Waiting for Jarvis to be ready…');
448
- await waitForProxy(ports.proxy);
549
+ // The poll is silent for up to 5 minutes, so it runs behind a spinner with a
550
+ // live elapsed counter; `done()` in the finally settles the line whether the
551
+ // proxy came up or the timeout error is about to surface.
552
+ const readiness = createSpinner('Waiting for Jarvis to be ready…', { showElapsed: true });
553
+ try {
554
+ await waitForProxy(ports.proxy);
555
+ }
556
+ finally {
557
+ readiness.done();
558
+ }
449
559
  reportAccess({ ports, profiles, externalServices });
450
560
  // After a restore the env is never presented as fully ready: print the banner +
451
561
  // best-effort reindex poll (FR-ORC-036, SC-ORC-003). The poll never throws.
452
562
  if (didRestore) {
453
- console.log(REINDEX_BANNER);
563
+ console.log(gray(REINDEX_BANNER));
454
564
  await pollReindexProgress(ports.es);
455
565
  }
456
566
  }