@tikoci/quickchr 0.4.8 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  > **Experimental** — quickchr is under active development. Most testing has been on
4
4
  > macOS (Apple Silicon + Intel), with CI on Linux (x86_64 + aarch64). Windows support
5
- > is early-stage: unit tests pass on `windows-latest` CI, but integration tests
6
- > (running a real CHR via QEMU for Windows) are not yet automated. Expect rough edges
7
- > on Windows — bug reports welcome.
5
+ > is early-stage: unit tests pass on `windows-latest` CI, and a weekly sweep boots a real
6
+ > CHR on Windows (TCG, anchor smoke subset only). Expect rough edges on Windows — bug
7
+ > reports welcome.
8
8
 
9
9
  CLI and library to download, launch, and manage MikroTik CHR virtual machines via QEMU.
10
10
 
@@ -57,6 +57,23 @@ Library:
57
57
  bun add @tikoci/quickchr
58
58
  ```
59
59
 
60
+ ### Prerelease QA (0.5.1)
61
+
62
+ 0.5.1 is published to npm `next` first; the unqualified install above uses `latest`,
63
+ and 0.5.1 is promoted there once QA passes. After 0.5.1 is published, pin it for
64
+ reproducible CLI or library QA:
65
+
66
+ ```bash
67
+ bun install -g @tikoci/quickchr@0.5.1
68
+ bun add @tikoci/quickchr@0.5.1
69
+ quickchr --version
70
+ ```
71
+
72
+ Use `@next` instead of `@0.5.1` to follow later candidates. See the
73
+ [0.5.1 changelog](./CHANGELOG.md#051--2026-10-22) for the changes since 0.5.0,
74
+ including storage admission, cache accounting, and disk preservation fixes.
75
+ The [0.5.0 notes](./CHANGELOG.md#050--2026-10-22) cover migration from 0.4.x.
76
+
60
77
  ### CLI Usage
61
78
 
62
79
  The easiest way to start is the interactive wizard — it walks through
@@ -158,6 +175,11 @@ quickchr remove my-chr
158
175
  quickchr doctor
159
176
  ```
160
177
 
178
+ `quickchr stop <name>` terminates QEMU (a power cut). For an explicit guest
179
+ shutdown request, use `quickchr shutdown <name> [--timeout <seconds>]` (default
180
+ 10 seconds, or `QUICKCHR_SHUTDOWN_TIMEOUT`). It waits for QEMU to exit and clearly
181
+ reports a forced power cut if the guest does not stop within the grace budget.
182
+
161
183
  ### Create / Start Options
162
184
 
163
185
  | Flag | Description | Default |
@@ -172,8 +194,9 @@ quickchr doctor
172
194
  | `--boot-size <size>` | Resize boot disk (e.g., 512M, 2G). Requires `qemu-img`. | |
173
195
  | `--add-disk <size>` | Attach an extra blank qcow2 disk. Repeatable. Requires `qemu-img`. | |
174
196
  | `--forward <spec>` | Add or pin a QEMU SLiRP hostfwd mapping. Repeatable. | |
175
- | `--bg` / `--background` | Run in background (default) | true |
197
+ | `--no-wait` | Return once QEMU is running; readiness is up to the caller. Not with provisioning flags or `--fg` | |
176
198
  | `--fg` / `--foreground` | Run in foreground — serial console on stdio | |
199
+ | `--bg` / `--background` | **Deprecated, does nothing** — background is already the default. Warns; use `--no-wait` to return early | |
177
200
  | `--add-package <pkg>` | Extra package to install (repeatable) | |
178
201
  | `--install-all-packages` | Install all packages from `all_packages.zip` | |
179
202
  | `--add-user <user:pass>` | Create a user after boot | |
@@ -189,6 +212,7 @@ quickchr doctor
189
212
  | `--device-mode <m>` | Configure device-mode: rose\|advanced\|basic\|home\|auto\|skip | |
190
213
  | `--device-mode-enable <f>` | Set one or more device-mode flags to yes | |
191
214
  | `--device-mode-disable <f>` | Set one or more device-mode flags to no | |
215
+ | `--no-device-mode` | Skip device-mode provisioning entirely | |
192
216
  | `--port-base <port>` | Starting port number | Auto (9100+) |
193
217
  | `--timeout-extra <s>` | Add extra seconds to the auto-computed boot timeout | |
194
218
  | `--dry-run` | Print what would run without executing | |
@@ -204,7 +228,7 @@ quickchr separates **boot-only** machine management from **post-boot provisionin
204
228
 
205
229
  | Feature | Minimum RouterOS | Notes |
206
230
  |---------|------------------|-------|
207
- | Boot / start / stop | Any RouterOS 7.x | Boot-only path; no post-boot RouterOS mutations |
231
+ | Boot / start / stop | Any RouterOS 7.x; RouterOS 6 on x86 (best-effort) | Boot-only path; no post-boot RouterOS mutations. 6.x has no REST: readiness is a native-API login, and provisioning is refused — see MANUAL "RouterOS 6 (boot-only)" |
208
232
  | Disk resize / extra disks / disk inspection | Any RouterOS 7.x | Requires host `qemu-img`; QEMU-local only |
209
233
  | Network attachment / port mappings | Any RouterOS 7.x | QEMU networking setup; not provisioning |
210
234
  | Managed login / custom user / disable-admin | 7.20.8+ | Post-boot provisioning |
@@ -232,7 +256,7 @@ In **foreground** mode, your terminal becomes the CHR serial console. Use these
232
256
  | `Ctrl-A C` | Toggle QEMU monitor (`quit` to force-stop) |
233
257
  | `Ctrl-A H` | List all key shortcuts |
234
258
 
235
- > **Note:** Background QEMU processes are true OS-level orphans — `quickchr` does not use shell job control (`&`). After the command returns you can close the terminal and QEMU keeps running. Use `quickchr stop <name>` to shut it down cleanly.
259
+ > **Note:** Background QEMU processes are true OS-level orphans — `quickchr` does not use shell job control (`&`). After the command returns you can close the terminal and QEMU keeps running. `quickchr stop <name>` ends it with a power cut; `quickchr shutdown <name>` asks the guest to shut down first, and still falls back to a power cut if it does not exit in time.
236
260
 
237
261
  ### Disk Support
238
262
 
@@ -359,7 +383,8 @@ const result = await chr.exec("/system/resource/print");
359
383
  console.log(result);
360
384
 
361
385
  // Stop (or remove)
362
- await chr.stop();
386
+ await chr.stop(); // power cut for normal teardown
387
+ // Alternatively, replace stop() above with: await chr.shutdown({ timeoutMs: 10_000 });
363
388
  await chr.remove();
364
389
 
365
390
  console.log(chr.state.bootDiskFormat); // "qcow2"
@@ -430,7 +455,7 @@ afterAll(async () => {
430
455
  |----------|-------------|
431
456
  | `MIKROTIK_WEB_ACCOUNT` | MikroTik.com account email (for license renewal) |
432
457
  | `MIKROTIK_WEB_PASSWORD` | MikroTik.com password (for license renewal) |
433
- | `QUICKCHR_DEFAULT_CHANNEL`, `QUICKCHR_DEFAULT_ARCH`, `QUICKCHR_CACHE_MAX_SIZE`, `QUICKCHR_TIMEOUT_EXTRA`, `QUICKCHR_SECURE_LOGIN` | Env-tier overrides for the 5 `quickchr settings` keys — see [MANUAL.md §3 `settings`](./MANUAL.md#3-cli-command-reference) |
458
+ | `QUICKCHR_DEFAULT_CHANNEL`, `QUICKCHR_DEFAULT_ARCH`, `QUICKCHR_ACCEL`, `QUICKCHR_CACHE_MAX_SIZE`, `QUICKCHR_STORAGE_RESERVE`, `QUICKCHR_TIMEOUT_EXTRA`, `QUICKCHR_SECURE_LOGIN` | Env-tier overrides for the 7 `quickchr settings` keys — see [MANUAL.md §3 `settings`](./MANUAL.md#3-cli-command-reference) |
434
459
  | `QUICKCHR_INTEGRATION` | Set to `1` to run integration tests |
435
460
  | `QUICKCHR_TEST_TARGET` | Integration tests only: RouterOS channel (`stable`/`long-term`/`testing`/`development`) or pinned version (e.g. `7.22.1`) to boot. Empty = `stable`. The Integration workflow resolves each `routeros-target` once, then sets this to that exact pin across platforms |
436
461
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikoci/quickchr",
3
- "version": "0.4.8",
3
+ "version": "0.5.1",
4
4
  "description": "CLI and library to download, launch, and manage MikroTik CHR virtual machines via QEMU",
5
5
  "module": "src/index.ts",
6
6
  "main": "src/index.ts",
@@ -25,7 +25,7 @@
25
25
  "lint": "biome check . && tsc --noEmit",
26
26
  "lint:biome": "biome check .",
27
27
  "lint:typecheck": "tsc --noEmit",
28
- "lint:markdown": "bunx markdownlint-cli2 '**/*.md'",
28
+ "lint:markdown": "markdownlint-cli2 '**/*.md'",
29
29
  "lint:cspell": "cspell lint . --no-progress",
30
30
  "lint:examples": "bun run scripts/validate-examples.ts",
31
31
  "lint:specifiers": "bun run scripts/validate-network-specifiers.ts",
package/src/cli/flags.ts CHANGED
@@ -24,13 +24,27 @@ const CREATE_FLAGS = [
24
24
 
25
25
  /** `start` also boots, so it owns the run-time flags on top of the create set. */
26
26
  const START_ONLY_FLAGS = [
27
- "all", "dry-run", "background", "bg", "fg", "foreground",
28
- "install-deps", "timeout-extra",
27
+ "all", "dry-run", "wait", "background", "bg", "fg", "foreground",
28
+ "timeout-extra",
29
29
  "license-level", "license-account", "license-password",
30
30
  ] as const;
31
31
 
32
+ /** `set` applies a change to a machine that has already booted, so it takes the
33
+ * post-boot subset of the provisioning flags plus `--license`'s credential flags.
34
+ *
35
+ * It is listed here for the **valueless** check, not for strictness: `set` still
36
+ * tolerates an unknown flag like every other non-creating command. The check that
37
+ * matters is arity — `quickchr set lab --device-mode` with no value parses as a
38
+ * boolean and would otherwise reach "Nothing to set", which blames the user for the
39
+ * wrong thing. */
40
+ const SET_ONLY_FLAGS = [
41
+ "name", "license", "level", "account", "password",
42
+ "device-mode", "device-mode-enable", "device-mode-disable",
43
+ ] as const;
44
+
32
45
  export const ADD_FLAGS: readonly string[] = CREATE_FLAGS;
33
46
  export const START_FLAGS: readonly string[] = [...CREATE_FLAGS, ...START_ONLY_FLAGS];
47
+ export const SET_FLAGS: readonly string[] = SET_ONLY_FLAGS;
34
48
 
35
49
  /** Every flag that takes a value, across every command — `parseFlags`'s arity table.
36
50
  *
@@ -82,10 +96,15 @@ export const VALUE_FLAGS: ReadonlySet<string> = new Set([
82
96
  * that `--add-network` already expresses in general (#164). Removing them silently would
83
97
  * repeat the defect they were removed for: a flag that is accepted and does nothing. A
84
98
  * removed name stays known for as long as anyone might still type it, and says what
85
- * replaced it. */
99
+ * replaced it.
100
+ *
101
+ * `--install-deps` is the same defect from the other side: `start` accepted it from
102
+ * the beginning and nothing ever read it (#193). It never installed anything, so the
103
+ * replacement is the command that names what to install. */
86
104
  export const REMOVED_FLAGS: ReadonlyMap<string, string> = new Map([
87
105
  ["vmnet-shared", "--add-network shared"],
88
106
  ["vmnet-bridge", "--add-network bridged:<iface>"],
107
+ ["install-deps", "quickchr doctor (it never installed anything; doctor lists missing host dependencies and how to install them)"],
89
108
  ]);
90
109
 
91
110
  /** Value-taking flags with a documented `--no-` spelling, where `false` is a real