@rrlab/cli 1.3.4 → 1.3.6-git-c9903ab.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## Prerequisites
10
10
 
11
- - Node.js >= 20.0.0
11
+ - Node.js >= 22.18.0
12
12
 
13
13
  ## Installation
14
14
 
@@ -40,6 +40,7 @@ See [`CLI.md`](./CLI.md) for the full reference (auto-generated per release).
40
40
  | `oxc` | [oxlint](https://oxc.rs/docs/guide/usage/linter.html) + [oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) | `rr plugins add oxc` |
41
41
  | `ts` | [tsc](https://www.typescriptlang.org) | `rr plugins add ts` |
42
42
  | `tsdown` | [tsdown](https://tsdown.dev) | `rr plugins add tsdown` |
43
+ | `vitest` | [vitest](https://vitest.dev) | `rr plugins add vitest` |
43
44
 
44
45
  Install one with:
45
46
 
@@ -79,19 +80,23 @@ _.path = ["{{config_root}}/node_modules/.bin"]
79
80
  PATH_add node_modules/.bin
80
81
  ```
81
82
 
82
- Then add a guarded eval to your shell rc — the guard makes it a no-op when you open a shell outside any project:
83
+ Then add a guarded eval to your shell rc:
83
84
 
84
85
  ```sh
85
86
  # zsh — ~/.zshrc
86
- command -v rr >/dev/null 2>&1 && eval "$(rr completion zsh)"
87
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion zsh 2>/dev/null)"
87
88
 
88
89
  # bash — ~/.bashrc
89
- command -v rr >/dev/null 2>&1 && eval "$(rr completion bash)"
90
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion bash 2>/dev/null)"
90
91
 
91
92
  # fish — ~/.config/fish/config.fish
92
- command -v rr >/dev/null 2>&1; and rr completion fish | source
93
+ command -v rr >/dev/null 2>&1; and rr completion fish 2>/dev/null | source
93
94
  ```
94
95
 
96
+ The `command -v` guard skips the line when no `rr` is on `PATH`. A mise shim still passes it: recent versions of mise keep `~/.local/share/mise/shims` on `PATH` after `mise activate`, even outside any project (the `activate_shims` setting, on by default). Once mise has installed a tool that provides `rr`, such as `npm:@rrlab/cli` in some project's `mise.toml`, an `rr` shim exists in every directory, and where no version is active it fails with `mise ERROR No version is set for shim: rr` on stderr. The `2>/dev/null` discards that error, so the line stays silent outside a project.
97
+
98
+ The line hides every error, so if completion doesn't load, run `rr completion zsh` by hand to see why.
99
+
95
100
  ### Option B — without a per-project PATH manager
96
101
 
97
102
  Cache the completion script once, then source the cached file from your shell rc. Run the generation step from inside a project that has `@rrlab/cli` installed:
@@ -1,7 +1,7 @@
1
1
  // @generated by @usage-spec/commander from Commander.js metadata
2
2
  name rr
3
3
  bin rr
4
- version "1.3.4"
4
+ version "1.3.6-git-c9903ab.0"
5
5
  usage "[options] [command]"
6
6
  flag --about help="show credits & inspiration"
7
7
  flag --usage help="print KDL spec for this CLI (https://kdl.dev)"
@@ -52,7 +52,7 @@ cmd doctor help="run all plugin doctors" {
52
52
  long_help "Runs the `doctor()` of every configured plugin capability. Each plugin reports ok / not working. The exit code is non-zero if any reports not working."
53
53
  }
54
54
  cmd completion help="print shell completion script" {
55
- long_help "Prints a shell completion script for rr. Add to your shell rc file:\n\n bash: eval \"$(rr completion bash)\"\n zsh: eval \"$(rr completion zsh)\"\n fish: rr completion fish | source"
55
+ long_help "Prints a shell completion script for rr. Add your shell's line to its rc file:\n\n # bash — ~/.bashrc\n command -v rr >/dev/null 2>&1 && eval \"$(rr completion bash 2>/dev/null)\"\n\n # zsh — ~/.zshrc\n command -v rr >/dev/null 2>&1 && eval \"$(rr completion zsh 2>/dev/null)\"\n\n # fish — ~/.config/fish/config.fish\n command -v rr >/dev/null 2>&1; and rr completion fish 2>/dev/null | source"
56
56
  arg <shell> help="target shell" {
57
57
  choices bash zsh fish
58
58
  }
@@ -1,6 +1,6 @@
1
1
  import { ToolService } from "../tool-service.mjs";
2
2
  import { probeBins } from "../bin-probe.mjs";
3
- import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-CipsEAq5.mjs";
3
+ import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-Bszob22e.mjs";
4
4
  //#region src/lib/plugin/__tests__/bin-probe.test.ts
5
5
  const FROM = import.meta.url;
6
6
  var ResolvableService = class extends ToolService {
@@ -1,5 +1,5 @@
1
1
  import { decideScaffold } from "../decide-scaffold.mjs";
2
- import { i as it, n as vi, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-CipsEAq5.mjs";
2
+ import { i as it, n as vi, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-Bszob22e.mjs";
3
3
  //#region src/lib/plugin/__tests__/decide-scaffold.test.ts
4
4
  function makeCtx(overrides = {}) {
5
5
  return {
@@ -1,6 +1,6 @@
1
1
  import { ToolService } from "../tool-service.mjs";
2
2
  import { definePlugin } from "../define-plugin.mjs";
3
- import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-CipsEAq5.mjs";
3
+ import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-Bszob22e.mjs";
4
4
  //#region src/lib/plugin/__tests__/define-plugin.test.ts
5
5
  function ctx() {
6
6
  return {
@@ -1,5 +1,5 @@
1
1
  import { pickPreset } from "../pick-preset.mjs";
2
- import { i as it, n as vi, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-CipsEAq5.mjs";
2
+ import { i as it, n as vi, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-Bszob22e.mjs";
3
3
  //#region src/lib/plugin/__tests__/pick-preset.test.ts
4
4
  function makeCtx(overrides = {}) {
5
5
  return {
@@ -1,5 +1,5 @@
1
1
  import { t as PluginRegistry } from "../../registry-B-GhzVIO.mjs";
2
- import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-CipsEAq5.mjs";
2
+ import { i as it, r as describe, t as globalExpect } from "../../test.DNmyFkvJ-Bszob22e.mjs";
3
3
  //#region src/lib/plugin/__tests__/registry.test.ts
4
4
  function plugin(name) {
5
5
  return {
package/dist/run.mjs CHANGED
@@ -12,7 +12,7 @@ import { rimraf } from "rimraf";
12
12
  import * as clack from "@clack/prompts";
13
13
  import { addDependency, detectPackageManager, removeDependency } from "nypm";
14
14
  import fs$1 from "node:fs/promises";
15
- import { builders, generateCode, loadFile, parseModule, writeFile } from "magicast";
15
+ import { builders, generateCode, loadFile, parseModule } from "magicast";
16
16
  import * as cjson from "comment-json";
17
17
  import { generateToStdout } from "@usage-spec/commander";
18
18
  //#region src/errors/known-error.ts
@@ -622,11 +622,16 @@ const SHELLS = [
622
622
  ];
623
623
  const usageColor = colorize("#24C55E");
624
624
  function createCompletionCommand() {
625
- return createCommand("completion").summary(`print shell completion script`).description(`Prints a shell completion script for rr. Add to your shell rc file:
625
+ return createCommand("completion").summary(`print shell completion script`).description(`Prints a shell completion script for rr. Add your shell's line to its rc file:
626
626
 
627
- bash: eval "$(rr completion bash)"
628
- zsh: eval "$(rr completion zsh)"
629
- fish: rr completion fish | source`).addArgument(new Argument("<shell>", `target shell`).choices(SHELLS)).addHelpText("afterAll", `\nUnder the hood, this command uses ${usageColor("usage")} (https://usage.jdx.dev).
627
+ # bash — ~/.bashrc
628
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion bash 2>/dev/null)"
629
+
630
+ # zsh — ~/.zshrc
631
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion zsh 2>/dev/null)"
632
+
633
+ # fish — ~/.config/fish/config.fish
634
+ command -v rr >/dev/null 2>&1; and rr completion fish 2>/dev/null | source`).configureHelp({ helpWidth: Number.POSITIVE_INFINITY }).addArgument(new Argument("<shell>", `target shell`).choices(SHELLS)).addHelpText("afterAll", `\nUnder the hood, this command uses ${usageColor("usage")} (https://usage.jdx.dev).
630
635
  Make sure to have it installed and available in your PATH.`);
631
636
  }
632
637
  //#endregion
@@ -948,12 +953,7 @@ var ConfigAstService = class {
948
953
  };
949
954
  }
950
955
  async save(loaded) {
951
- if (loaded.isNew) {
952
- const { code } = generateCode(loaded.mod);
953
- await fs$1.writeFile(loaded.filepath, code, "utf8");
954
- return;
955
- }
956
- await writeFile(loaded.mod.$ast, loaded.filepath);
956
+ await fs$1.writeFile(loaded.filepath, tidy(generateCode(loaded.mod).code), "utf8");
957
957
  }
958
958
  /**
959
959
  * Returns true when the config's `plugins` array contains a call to the
@@ -974,11 +974,14 @@ var ConfigAstService = class {
974
974
  */
975
975
  addPlugin(mod, entry) {
976
976
  if (this.hasPlugin(mod, entry.exportName)) return { changed: false };
977
- if (!mod.imports[entry.exportName]) mod.imports.$add({
978
- from: entry.pkgName,
979
- imported: "default",
980
- local: entry.exportName
981
- });
977
+ if (!mod.imports[entry.exportName]) {
978
+ mod.imports.$add({
979
+ from: entry.pkgName,
980
+ imported: "default",
981
+ local: entry.exportName
982
+ });
983
+ this.#placeImport(mod, entry.pkgName);
984
+ }
982
985
  this.#ensureDefineConfig(mod);
983
986
  this.#ensurePluginsArray(mod).push(builders.functionCall(entry.exportName));
984
987
  return { changed: true };
@@ -1023,12 +1026,32 @@ var ConfigAstService = class {
1023
1026
  if (!opts || typeof opts !== "object") return void 0;
1024
1027
  return opts.plugins;
1025
1028
  }
1029
+ /**
1030
+ * Moves the import of `source`, which `imports.$add` puts first, to its
1031
+ * sorted place among the other imports, leaving those in their order: a
1032
+ * sorted file stays sorted, so `rr jsc` passes on what rr wrote.
1033
+ */
1034
+ #placeImport(mod, source) {
1035
+ const program = mod.$ast;
1036
+ if (program.type !== "Program") return;
1037
+ const { body } = program;
1038
+ const from = body.findIndex((node) => node.type === "ImportDeclaration" && node.source.value === source);
1039
+ if (from === -1) return;
1040
+ const [added] = body.splice(from, 1);
1041
+ if (!added) return;
1042
+ const before = body.findIndex((node) => node.type === "ImportDeclaration" && compareImportSources(node.source.value, source) > 0);
1043
+ const lastImport = body.reduce((last, node, i) => node.type === "ImportDeclaration" ? i : last, -1);
1044
+ body.splice(before === -1 ? lastImport + 1 : before, 0, added);
1045
+ }
1026
1046
  #ensureDefineConfig(mod) {
1027
- if (!mod.imports.defineConfig) mod.imports.$add({
1028
- from: "@rrlab/cli/config",
1029
- imported: "defineConfig",
1030
- local: "defineConfig"
1031
- });
1047
+ if (!mod.imports.defineConfig) {
1048
+ mod.imports.$add({
1049
+ from: "@rrlab/cli/config",
1050
+ imported: "defineConfig",
1051
+ local: "defineConfig"
1052
+ });
1053
+ this.#placeImport(mod, "@rrlab/cli/config");
1054
+ }
1032
1055
  if (!mod.exports.default) mod.exports.default = builders.functionCall("defineConfig", { plugins: [] });
1033
1056
  }
1034
1057
  #ensurePluginsArray(mod) {
@@ -1049,6 +1072,30 @@ var ConfigAstService = class {
1049
1072
  if (typeof callee === "string") return callee;
1050
1073
  }
1051
1074
  };
1075
+ /**
1076
+ * Undoes two recast habits that formatters reject: it keeps the blank line that
1077
+ * followed an import when rr inserts another after it, and it drops the final
1078
+ * newline.
1079
+ */
1080
+ function tidy(code) {
1081
+ const joined = code.replace(/^(import\b[^;]*;)\n(?:[ \t]*\n)+(?=import\b)/gm, "$1\n");
1082
+ return joined.endsWith("\n") ? joined : `${joined}\n`;
1083
+ }
1084
+ /**
1085
+ * Biome's organizeImports order: protocol imports (`node:fs`), then packages,
1086
+ * then aliases (`#src/…`, `@/…`, `~/…`), then paths. Within a group, plain
1087
+ * code-unit order, which matches Biome for these names (`ts-plugin` before
1088
+ * `tsdown-plugin`; `localeCompare` flips them).
1089
+ */
1090
+ function compareImportSources(a, b) {
1091
+ const group = (source) => {
1092
+ if (/^[a-z]+:/.test(source)) return 0;
1093
+ if (/^(#|@\/|~)/.test(source)) return 2;
1094
+ if (source.startsWith(".") || source.startsWith("/")) return 3;
1095
+ return 1;
1096
+ };
1097
+ return group(a) - group(b) || (a < b ? -1 : a > b ? 1 : 0);
1098
+ }
1052
1099
  const MINIMAL_TEMPLATE = `import { defineConfig } from "@rrlab/cli/config";
1053
1100
 
1054
1101
  export default defineConfig({
@@ -1248,6 +1295,14 @@ async function pathExists(p) {
1248
1295
  //#endregion
1249
1296
  //#region src/services/prompts.ts
1250
1297
  /**
1298
+ * Whether a prompt can run: clack reads keys from stdin and draws on stdout,
1299
+ * so both have to be terminals. An agent, a script or CI usually has neither,
1300
+ * and a prompt there waits forever or exits on a closed stdin.
1301
+ */
1302
+ function canPrompt() {
1303
+ return !!process.stdin.isTTY && !!process.stdout.isTTY;
1304
+ }
1305
+ /**
1251
1306
  * Adapter that exposes the subset of `@clack/prompts` matching the
1252
1307
  * `ClackPrompts` contract from `@rrlab/cli/plugin`. The contract intentionally
1253
1308
  * stays narrow (`select`, `confirm`, `isCancel`) so plugin install hooks can
@@ -1445,7 +1500,7 @@ async function addPluginAction({ ctx, args, options }) {
1445
1500
  flags: {
1446
1501
  force: !!options.force,
1447
1502
  yes: !!options.yes,
1448
- nonInteractive: !!options.yes
1503
+ nonInteractive: !!options.yes || !canPrompt()
1449
1504
  },
1450
1505
  release: new ReleaseService(tag)
1451
1506
  };
@@ -1504,6 +1559,13 @@ async function listPluginsAction({ ctx }) {
1504
1559
  for (const name of plugins) logger.info(` - ${name}`);
1505
1560
  }
1506
1561
  //#endregion
1562
+ //#region src/errors/confirmation-required.ts
1563
+ var ConfirmationRequiredError = class extends KnownError {
1564
+ constructor(command) {
1565
+ super(`'${command}' asks for confirmation, and there's no terminal to ask in. Re-run it with --yes.`);
1566
+ }
1567
+ };
1568
+ //#endregion
1507
1569
  //#region src/actions/plugins/remove.ts
1508
1570
  async function removePluginAction({ ctx, args, options }) {
1509
1571
  const { name } = args;
@@ -1523,7 +1585,7 @@ async function removePluginAction({ ctx, args, options }) {
1523
1585
  prompts: createClackPrompts(),
1524
1586
  flags: {
1525
1587
  yes: !!options.yes,
1526
- nonInteractive: !!options.yes
1588
+ nonInteractive: !!options.yes || !canPrompt()
1527
1589
  }
1528
1590
  });
1529
1591
  } catch (err) {
@@ -1552,6 +1614,7 @@ async function removePluginAction({ ctx, args, options }) {
1552
1614
  return;
1553
1615
  }
1554
1616
  if (!options.yes) {
1617
+ if (!canPrompt()) throw new ConfirmationRequiredError(`rr plugins remove ${name}`);
1555
1618
  const choice = await clack.confirm({
1556
1619
  message: "Proceed?",
1557
1620
  initialValue: false
@@ -81,7 +81,7 @@ function C({ force: e } = {}) {
81
81
  }
82
82
  var y = C();
83
83
  //#endregion
84
- //#region ../../node_modules/.pnpm/@vitest+pretty-format@4.1.6/node_modules/@vitest/pretty-format/dist/index.js
84
+ //#region ../../node_modules/.pnpm/@vitest+pretty-format@4.1.12/node_modules/@vitest/pretty-format/dist/index.js
85
85
  function _mergeNamespaces(n, m) {
86
86
  m.forEach(function(e) {
87
87
  e && typeof e !== "string" && !Array.isArray(e) && Object.keys(e).forEach(function(k) {
@@ -964,7 +964,7 @@ const plugins = {
964
964
  Error: ErrorPlugin
965
965
  };
966
966
  //#endregion
967
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/display.js
967
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/display.js
968
968
  const ansiColors$1 = {
969
969
  bold: ["1", "22"],
970
970
  dim: ["2", "22"],
@@ -1518,7 +1518,7 @@ function objDisplay$1(obj, options = {}) {
1518
1518
  return str;
1519
1519
  }
1520
1520
  //#endregion
1521
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/helpers.js
1521
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/helpers.js
1522
1522
  /**
1523
1523
  * Get original stacktrace without source map support the most performant way.
1524
1524
  * - Create only 1 stack frame.
@@ -3004,7 +3004,7 @@ function getCommonAndChangedSubstrings(diffs, op, hasCommonDiff) {
3004
3004
  return diffs.reduce((reduced, diff) => reduced + (diff[0] === 0 ? diff[1] : diff[0] === op ? hasCommonDiff ? y.inverse(diff[1]) : diff[1] : ""), "");
3005
3005
  }
3006
3006
  //#endregion
3007
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/serialize.js
3007
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/serialize.js
3008
3008
  const IS_RECORD_SYMBOL$1 = "@@__IMMUTABLE_RECORD__@@";
3009
3009
  const IS_COLLECTION_SYMBOL = "@@__IMMUTABLE_ITERABLE__@@";
3010
3010
  function isImmutable(v) {
@@ -3078,7 +3078,7 @@ function normalizeErrorMessage(message) {
3078
3078
  return message.replace(/\(0\s?,\s?__vite_ssr_import_\d+__.(\w+)\)/g, "$1").replace(/__(vite_ssr_import|vi_import)_\d+__\./g, "").replace(/getByTestId('__vitest_\d+__')/g, "page");
3079
3079
  }
3080
3080
  //#endregion
3081
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/error.js
3081
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/error.js
3082
3082
  function processError(_err, diffOptions, seen = /* @__PURE__ */ new WeakSet()) {
3083
3083
  if (!_err || typeof _err !== "object") return { message: String(_err) };
3084
3084
  const err = _err;
@@ -3108,7 +3108,7 @@ function prettifyValue(value, options) {
3108
3108
  return value;
3109
3109
  }
3110
3110
  //#endregion
3111
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/timers.js
3111
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/timers.js
3112
3112
  const SAFE_TIMERS_SYMBOL = Symbol("vitest:SAFE_TIMERS");
3113
3113
  function getSafeTimers() {
3114
3114
  const { setTimeout: safeSetTimeout, setInterval: safeSetInterval, clearInterval: safeClearInterval, clearTimeout: safeClearTimeout, setImmediate: safeSetImmediate, clearImmediate: safeClearImmediate, queueMicrotask: safeQueueMicrotask } = globalThis[SAFE_TIMERS_SYMBOL] || globalThis;
@@ -3142,7 +3142,7 @@ function delay(timeout, scheduler = setTimeout) {
3142
3142
  return new Promise((resolve) => scheduler(resolve, timeout));
3143
3143
  }
3144
3144
  //#endregion
3145
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js
3145
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js
3146
3146
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
3147
3147
  function normalizeWindowsPath(input = "") {
3148
3148
  if (!input) return input;
@@ -3221,7 +3221,7 @@ const isAbsolute = function(p) {
3221
3221
  return _IS_ABSOLUTE_RE.test(p);
3222
3222
  };
3223
3223
  //#endregion
3224
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/source-map.js
3224
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/source-map.js
3225
3225
  var comma = ",".charCodeAt(0);
3226
3226
  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3227
3227
  var intToChar = new Uint8Array(64);
@@ -3589,7 +3589,7 @@ function getOriginalPosition(map, needle) {
3589
3589
  return result;
3590
3590
  }
3591
3591
  //#endregion
3592
- //#region ../../node_modules/.pnpm/@vitest+runner@4.1.6/node_modules/@vitest/runner/dist/chunk-artifact.js
3592
+ //#region ../../node_modules/.pnpm/@vitest+runner@4.1.12/node_modules/@vitest/runner/dist/chunk-artifact.js
3593
3593
  var PendingError = class extends Error {
3594
3594
  code = "VITEST_PENDING";
3595
3595
  taskId;
@@ -5092,7 +5092,7 @@ function manageArtifactAttachment(attachment) {
5092
5092
  if (attachment.body != null) attachment.bodyEncoding ??= "base64";
5093
5093
  }
5094
5094
  //#endregion
5095
- //#region ../../node_modules/.pnpm/vitest@4.1.6_@types+node@25.9.1_vite@8.0.13_@types+node@25.9.1_yaml@2.8.4_/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js
5095
+ //#region ../../node_modules/.pnpm/vitest@4.1.12_@types+node@25.9.1_vite@8.3.4_@types+node@25.9.1_yaml@2.9.1_/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js
5096
5096
  const NAME_WORKER_STATE = "__vitest_worker__";
5097
5097
  function getWorkerState() {
5098
5098
  const workerState = globalThis[NAME_WORKER_STATE];
@@ -5132,7 +5132,7 @@ async function waitForImportsToResolve() {
5132
5132
  await waitForImportsToResolve();
5133
5133
  }
5134
5134
  //#endregion
5135
- //#region ../../node_modules/.pnpm/@vitest+spy@4.1.6/node_modules/@vitest/spy/dist/index.js
5135
+ //#region ../../node_modules/.pnpm/@vitest+spy@4.1.12/node_modules/@vitest/spy/dist/index.js
5136
5136
  function isMockFunction(fn) {
5137
5137
  return typeof fn === "function" && "_isMockFunction" in fn && fn._isMockFunction === true;
5138
5138
  }
@@ -5515,7 +5515,7 @@ function throwConstructorError(shorthand) {
5515
5515
  throw new TypeError(`Cannot use \`${shorthand}\` when called with \`new\`. Use \`mockImplementation\` with a \`class\` keyword instead. See https://vitest.dev/api/mock#class-support for more information.`);
5516
5516
  }
5517
5517
  //#endregion
5518
- //#region ../../node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js
5518
+ //#region ../../node_modules/.pnpm/chai@6.3.0/node_modules/chai/index.js
5519
5519
  var __defProp = Object.defineProperty;
5520
5520
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
5521
5521
  enumerable: true,
@@ -5596,6 +5596,7 @@ function compatibleConstructor(thrown, errorLike) {
5596
5596
  __name(compatibleConstructor, "compatibleConstructor");
5597
5597
  function compatibleMessage(thrown, errMatcher) {
5598
5598
  const comparisonString = typeof thrown === "string" ? thrown : thrown.message;
5599
+ if (comparisonString === void 0) return false;
5599
5600
  if (isRegExp(errMatcher)) return errMatcher.test(comparisonString);
5600
5601
  else if (typeof errMatcher === "string") return comparisonString.indexOf(errMatcher) !== -1;
5601
5602
  return false;
@@ -6252,8 +6253,10 @@ function inspect2(obj, showHidden, depth, colors) {
6252
6253
  __name(inspect2, "inspect");
6253
6254
  function objDisplay(obj) {
6254
6255
  let str = inspect2(obj), type3 = Object.prototype.toString.call(obj);
6255
- if (config.truncateThreshold && str.length >= config.truncateThreshold) if (type3 === "[object Function]") return !obj.name || obj.name === "" ? "[Function]" : "[Function: " + obj.name + "]";
6256
- else if (type3 === "[object Array]") return "[ Array(" + obj.length + ") ]";
6256
+ if (config.truncateThreshold && str.length >= config.truncateThreshold) if (type3 === "[object Function]") {
6257
+ const objAsFunction = obj;
6258
+ return !objAsFunction.name || objAsFunction.name === "" ? "[Function]" : "[Function: " + objAsFunction.name + "]";
6259
+ } else if (type3 === "[object Array]") return "[ Array(" + obj.length + ") ]";
6257
6260
  else if (type3 === "[object Object]") {
6258
6261
  let keys = Object.keys(obj);
6259
6262
  return "{ Object (" + (keys.length > 2 ? keys.splice(0, 2).join(", ") + ", ..." : keys.join(", ")) + ") }";
@@ -6266,25 +6269,24 @@ function getMessage2(obj, args) {
6266
6269
  let val = flag(obj, "object");
6267
6270
  let expected = args[3];
6268
6271
  let actual = getActual(obj, args);
6269
- let msg = negate ? args[2] : args[1];
6272
+ let rawMsg = negate ? args[2] : args[1];
6270
6273
  let flagMsg = flag(obj, "message");
6271
- if (typeof msg === "function") msg = msg();
6272
- msg = msg || "";
6273
- msg = msg.replace(/#\{this\}/g, function() {
6274
- return objDisplay(val);
6275
- }).replace(/#\{act\}/g, function() {
6276
- return objDisplay(actual);
6277
- }).replace(/#\{exp\}/g, function() {
6274
+ if (typeof rawMsg === "function") rawMsg = rawMsg();
6275
+ const msg = (rawMsg || "").replace(/#\{(this|act|exp)\}/g, function(_, tag) {
6276
+ if (tag === "this") return objDisplay(val);
6277
+ if (tag === "act") return objDisplay(actual);
6278
6278
  return objDisplay(expected);
6279
6279
  });
6280
6280
  return flagMsg ? flagMsg + ": " + msg : msg;
6281
6281
  }
6282
6282
  __name(getMessage2, "getMessage");
6283
- function transferFlags(assertion, object, includeAll) {
6284
- let flags = assertion.__flags || (assertion.__flags = /* @__PURE__ */ Object.create(null));
6285
- if (!object.__flags) object.__flags = /* @__PURE__ */ Object.create(null);
6283
+ function transferFlags(source, object, includeAll) {
6284
+ const sourceWithFlags = source;
6285
+ const objWithFlags = object;
6286
+ let flags = sourceWithFlags.__flags || (sourceWithFlags.__flags = /* @__PURE__ */ Object.create(null));
6287
+ const objFlags = objWithFlags.__flags || (objWithFlags.__flags = /* @__PURE__ */ Object.create(null));
6286
6288
  includeAll = arguments.length === 3 ? includeAll : true;
6287
- for (let flag3 in flags) if (includeAll || flag3 !== "object" && flag3 !== "ssfi" && flag3 !== "lockSsfi" && flag3 != "message") object.__flags[flag3] = flags[flag3];
6289
+ for (let flag3 in flags) if (includeAll || flag3 !== "object" && flag3 !== "ssfi" && flag3 !== "lockSsfi" && flag3 != "message") objFlags[flag3] = flags[flag3];
6288
6290
  }
6289
6291
  __name(transferFlags, "transferFlags");
6290
6292
  function type2(obj) {
@@ -6611,9 +6613,9 @@ var _Assertion = class _Assertion {
6611
6613
  * - `eql`: This flag contains the deepEqual function to be used by the assertion.
6612
6614
  *
6613
6615
  * @param {unknown} obj target of the assertion
6614
- * @param {string} [msg] (optional) custom error message
6615
- * @param {Function} [ssfi] (optional) starting point for removing stack frames
6616
- * @param {boolean} [lockSsfi] (optional) whether or not the ssfi flag is locked
6616
+ * @param {string=} msg (optional) custom error message
6617
+ * @param {Function=} ssfi (optional) starting point for removing stack frames
6618
+ * @param {boolean=} lockSsfi (optional) whether or not the ssfi flag is locked
6617
6619
  */
6618
6620
  constructor(obj, msg, ssfi, lockSsfi) {
6619
6621
  /** @type {{}} */
@@ -6647,44 +6649,44 @@ var _Assertion = class _Assertion {
6647
6649
  }
6648
6650
  /**
6649
6651
  * @param {string} name
6650
- * @param {Function} fn
6652
+ * @param {(...args: never) => unknown} fn
6651
6653
  */
6652
6654
  static addProperty(name, fn) {
6653
6655
  addProperty(this.prototype, name, fn);
6654
6656
  }
6655
6657
  /**
6656
6658
  * @param {string} name
6657
- * @param {Function} fn
6659
+ * @param {(...args: never) => unknown} fn
6658
6660
  */
6659
6661
  static addMethod(name, fn) {
6660
6662
  addMethod(this.prototype, name, fn);
6661
6663
  }
6662
6664
  /**
6663
6665
  * @param {string} name
6664
- * @param {Function} fn
6665
- * @param {Function} chainingBehavior
6666
+ * @param {(...args: never) => unknown} fn
6667
+ * @param {(...args: never) => unknown} chainingBehavior
6666
6668
  */
6667
6669
  static addChainableMethod(name, fn, chainingBehavior) {
6668
6670
  addChainableMethod(this.prototype, name, fn, chainingBehavior);
6669
6671
  }
6670
6672
  /**
6671
6673
  * @param {string} name
6672
- * @param {Function} fn
6674
+ * @param {(...args: never) => unknown} fn
6673
6675
  */
6674
6676
  static overwriteProperty(name, fn) {
6675
6677
  overwriteProperty(this.prototype, name, fn);
6676
6678
  }
6677
6679
  /**
6678
6680
  * @param {string} name
6679
- * @param {Function} fn
6681
+ * @param {(...args: never) => unknown} fn
6680
6682
  */
6681
6683
  static overwriteMethod(name, fn) {
6682
6684
  overwriteMethod(this.prototype, name, fn);
6683
6685
  }
6684
6686
  /**
6685
6687
  * @param {string} name
6686
- * @param {Function} fn
6687
- * @param {Function} chainingBehavior
6688
+ * @param {(...args: never) => unknown} fn
6689
+ * @param {(...args: never) => unknown} chainingBehavior
6688
6690
  */
6689
6691
  static overwriteChainableMethod(name, fn, chainingBehavior) {
6690
6692
  overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
@@ -6741,6 +6743,11 @@ __name(_Assertion, "Assertion");
6741
6743
  var Assertion = _Assertion;
6742
6744
  var events = new EventTarget();
6743
6745
  var _PluginEvent = class _PluginEvent extends Event {
6746
+ /**
6747
+ * @param {string} type
6748
+ * @param {string} name
6749
+ * @param {Function} fn
6750
+ */
6744
6751
  constructor(type3, name, fn) {
6745
6752
  super(type3);
6746
6753
  this.name = String(name);
@@ -6771,7 +6778,7 @@ function addProperty(ctx, name, getter) {
6771
6778
  __name(addProperty, "addProperty");
6772
6779
  var fnLengthDesc = Object.getOwnPropertyDescriptor(function() {}, "length");
6773
6780
  function addLengthGuard(fn, assertionName, isChainable) {
6774
- if (!fnLengthDesc.configurable) return fn;
6781
+ if (!fnLengthDesc?.configurable) return fn;
6775
6782
  Object.defineProperty(fn, "length", { get: /* @__PURE__ */ __name(function() {
6776
6783
  if (isChainable) throw Error("Invalid Chai property: " + assertionName + ".length. Due to a compatibility issue, \"length\" cannot directly follow \"" + assertionName + "\". Use \"" + assertionName + ".lengthOf\" instead.");
6777
6784
  throw Error("Invalid Chai property: " + assertionName + ".length. See docs for proper usage of \"" + assertionName + "\".");
@@ -6803,7 +6810,7 @@ function proxify(obj, nonChainableMethodName) {
6803
6810
  if (!isProxyEnabled()) return obj;
6804
6811
  return new Proxy(obj, { get: /* @__PURE__ */ __name(function proxyGetter(target, property) {
6805
6812
  if (typeof property === "string" && config.proxyExcludedKeys.indexOf(property) === -1 && !Reflect.has(target, property)) {
6806
- if (nonChainableMethodName) throw Error("Invalid Chai property: " + nonChainableMethodName + "." + property + ". See docs for proper usage of \"" + nonChainableMethodName + "\".");
6813
+ if (nonChainableMethodName) throw Error("Invalid Chai property: " + String(nonChainableMethodName) + "." + property + ". See docs for proper usage of \"" + String(nonChainableMethodName) + "\".");
6807
6814
  let suggestion = null;
6808
6815
  let suggestionDistance = 4;
6809
6816
  getProperties(target).forEach(function(prop) {
@@ -6859,7 +6866,8 @@ function addMethod(ctx, name, method) {
6859
6866
  }
6860
6867
  __name(addMethod, "addMethod");
6861
6868
  function overwriteProperty(ctx, name, getter) {
6862
- let _get = Object.getOwnPropertyDescriptor(ctx, name), _super = /* @__PURE__ */ __name(function() {}, "_super");
6869
+ let _get = Object.getOwnPropertyDescriptor(ctx, name);
6870
+ let _super = /* @__PURE__ */ __name(function() {}, "_super");
6863
6871
  if (_get && "function" === typeof _get.get) _super = _get.get;
6864
6872
  Object.defineProperty(ctx, name, {
6865
6873
  get: /* @__PURE__ */ __name(function overwritingPropertyGetter() {
@@ -6878,11 +6886,12 @@ function overwriteProperty(ctx, name, getter) {
6878
6886
  }
6879
6887
  __name(overwriteProperty, "overwriteProperty");
6880
6888
  function overwriteMethod(ctx, name, method) {
6881
- let _method = ctx[name], _super = /* @__PURE__ */ __name(function() {
6882
- throw new Error(name + " is not a function");
6889
+ let _method = ctx[name];
6890
+ let _super = /* @__PURE__ */ __name(function() {
6891
+ throw new Error(String(name) + " is not a function");
6883
6892
  }, "_super");
6884
6893
  if (_method && "function" === typeof _method) _super = _method;
6885
- let overwritingMethodWrapper = /* @__PURE__ */ __name(function() {
6894
+ function overwritingMethodWrapper() {
6886
6895
  if (!flag(this, "lockSsfi")) flag(this, "ssfi", overwritingMethodWrapper);
6887
6896
  let origLockSsfi = flag(this, "lockSsfi");
6888
6897
  flag(this, "lockSsfi", true);
@@ -6892,8 +6901,9 @@ function overwriteMethod(ctx, name, method) {
6892
6901
  let newAssertion = new Assertion();
6893
6902
  transferFlags(this, newAssertion);
6894
6903
  return newAssertion;
6895
- }, "overwritingMethodWrapper");
6896
- addLengthGuard(overwritingMethodWrapper, name, false);
6904
+ }
6905
+ __name(overwritingMethodWrapper, "overwritingMethodWrapper");
6906
+ addLengthGuard(overwritingMethodWrapper, String(name), false);
6897
6907
  ctx[name] = proxify(overwritingMethodWrapper, name);
6898
6908
  }
6899
6909
  __name(overwriteMethod, "overwriteMethod");
@@ -6907,6 +6917,12 @@ var excludeNames = Object.getOwnPropertyNames(testFn).filter(function(name) {
6907
6917
  var call = Function.prototype.call;
6908
6918
  var apply = Function.prototype.apply;
6909
6919
  var _PluginAddChainableMethodEvent = class _PluginAddChainableMethodEvent extends PluginEvent {
6920
+ /**
6921
+ * @param {string} type
6922
+ * @param {string} name
6923
+ * @param {Function} fn
6924
+ * @param {Function} chainingBehavior
6925
+ */
6910
6926
  constructor(type3, name, fn, chainingBehavior) {
6911
6927
  super(type3, name, fn);
6912
6928
  this.chainingBehavior = chainingBehavior;
@@ -6915,24 +6931,26 @@ var _PluginAddChainableMethodEvent = class _PluginAddChainableMethodEvent extend
6915
6931
  __name(_PluginAddChainableMethodEvent, "PluginAddChainableMethodEvent");
6916
6932
  var PluginAddChainableMethodEvent = _PluginAddChainableMethodEvent;
6917
6933
  function addChainableMethod(ctx, name, method, chainingBehavior) {
6934
+ const ctxChainable = ctx;
6918
6935
  if (typeof chainingBehavior !== "function") chainingBehavior = /* @__PURE__ */ __name(function() {}, "chainingBehavior");
6919
6936
  let chainableBehavior = {
6920
6937
  method,
6921
6938
  chainingBehavior
6922
6939
  };
6923
- if (!ctx.__methods) ctx.__methods = {};
6924
- ctx.__methods[name] = chainableBehavior;
6940
+ if (!ctxChainable.__methods) ctxChainable.__methods = {};
6941
+ ctxChainable.__methods[name] = chainableBehavior;
6925
6942
  Object.defineProperty(ctx, name, {
6926
6943
  get: /* @__PURE__ */ __name(function chainableMethodGetter() {
6927
6944
  chainableBehavior.chainingBehavior.call(this);
6928
- let chainableMethodWrapper = /* @__PURE__ */ __name(function() {
6945
+ function chainableMethodWrapper(...args) {
6929
6946
  if (!flag(this, "lockSsfi")) flag(this, "ssfi", chainableMethodWrapper);
6930
- let result = chainableBehavior.method.apply(this, arguments);
6947
+ let result = chainableBehavior.method.apply(this, args);
6931
6948
  if (result !== void 0) return result;
6932
6949
  let newAssertion = new Assertion();
6933
6950
  transferFlags(this, newAssertion);
6934
6951
  return newAssertion;
6935
- }, "chainableMethodWrapper");
6952
+ }
6953
+ __name(chainableMethodWrapper, "chainableMethodWrapper");
6936
6954
  addLengthGuard(chainableMethodWrapper, name, true);
6937
6955
  if (canSetPrototype) {
6938
6956
  let prototype = Object.create(this);
@@ -6941,7 +6959,8 @@ function addChainableMethod(ctx, name, method, chainingBehavior) {
6941
6959
  Object.setPrototypeOf(chainableMethodWrapper, prototype);
6942
6960
  } else Object.getOwnPropertyNames(ctx).forEach(function(asserterName) {
6943
6961
  if (excludeNames.indexOf(asserterName) !== -1) return;
6944
- Object.defineProperty(chainableMethodWrapper, asserterName, Object.getOwnPropertyDescriptor(ctx, asserterName));
6962
+ let pd = Object.getOwnPropertyDescriptor(ctx, asserterName);
6963
+ if (pd) Object.defineProperty(chainableMethodWrapper, asserterName, pd);
6945
6964
  });
6946
6965
  transferFlags(this, chainableMethodWrapper);
6947
6966
  return proxify(chainableMethodWrapper);
@@ -6978,7 +6997,7 @@ __name(compareByInspect, "compareByInspect");
6978
6997
  function getOwnEnumerablePropertySymbols(obj) {
6979
6998
  if (typeof Object.getOwnPropertySymbols !== "function") return [];
6980
6999
  return Object.getOwnPropertySymbols(obj).filter(function(sym) {
6981
- return Object.getOwnPropertyDescriptor(obj, sym).enumerable;
7000
+ return Object.getOwnPropertyDescriptor(obj, sym)?.enumerable;
6982
7001
  });
6983
7002
  }
6984
7003
  __name(getOwnEnumerablePropertySymbols, "getOwnEnumerablePropertySymbols");
@@ -7001,13 +7020,14 @@ function getOperator(obj, args) {
7001
7020
  let negate = flag(obj, "negate");
7002
7021
  let expected = args[3];
7003
7022
  let msg = negate ? args[2] : args[1];
7004
- if (operator) return operator;
7023
+ if (operator) return String(operator);
7005
7024
  if (typeof msg === "function") msg = msg();
7006
7025
  msg = msg || "";
7007
7026
  if (!msg) return;
7008
- if (/\shave\s/.test(msg)) return;
7027
+ const msgString = String(msg);
7028
+ if (/\shave\s/.test(msgString)) return;
7009
7029
  let isObject = isObjectType(expected);
7010
- if (/\snot\s/.test(msg)) return isObject ? "notDeepStrictEqual" : "notStrictEqual";
7030
+ if (/\snot\s/.test(msgString)) return isObject ? "notDeepStrictEqual" : "notStrictEqual";
7011
7031
  return isObject ? "deepStrictEqual" : "strictEqual";
7012
7032
  }
7013
7033
  __name(getOperator, "getOperator");
@@ -7511,18 +7531,19 @@ function assertKeys(keys) {
7511
7531
  if (!keys.length) throw new AssertionError(flagMsg + "keys required", void 0, ssfi);
7512
7532
  let len = keys.length, any = flag2(this, "any"), all = flag2(this, "all"), expected = keys, isEql = isDeep ? flag2(this, "eql") : (val1, val2) => val1 === val2;
7513
7533
  if (!any && !all) all = true;
7514
- if (any) ok = expected.some(function(expectedKey) {
7534
+ const uniqueExpected = [...new Set(expected)];
7535
+ if (any) ok = uniqueExpected.some(function(expectedKey) {
7515
7536
  return actual.some(function(actualKey) {
7516
7537
  return isEql(expectedKey, actualKey);
7517
7538
  });
7518
7539
  });
7519
7540
  if (all) {
7520
- ok = expected.every(function(expectedKey) {
7541
+ ok = uniqueExpected.every(function(expectedKey) {
7521
7542
  return actual.some(function(actualKey) {
7522
7543
  return isEql(expectedKey, actualKey);
7523
7544
  });
7524
7545
  });
7525
- if (!flag2(this, "contains")) ok = ok && keys.length == actual.length;
7546
+ if (!flag2(this, "contains")) ok = ok && uniqueExpected.length == actual.length;
7526
7547
  }
7527
7548
  if (len > 1) {
7528
7549
  keys = keys.map(function(key) {
@@ -7674,28 +7695,32 @@ Assertion.addMethod("members", function(subset, msg) {
7674
7695
  Assertion.addProperty("iterable", function(msg) {
7675
7696
  if (msg) flag2(this, "message", msg);
7676
7697
  let obj = flag2(this, "object");
7677
- this.assert(obj != void 0 && obj[Symbol.iterator], "expected #{this} to be an iterable", "expected #{this} to not be an iterable", obj);
7698
+ this.assert(obj != void 0 && typeof obj[Symbol.iterator] === "function", "expected #{this} to be an iterable", "expected #{this} to not be an iterable", obj);
7678
7699
  });
7679
7700
  function oneOf(list, msg) {
7680
7701
  if (msg) flag2(this, "message", msg);
7681
7702
  let expected = flag2(this, "object"), flagMsg = flag2(this, "message"), ssfi = flag2(this, "ssfi"), contains = flag2(this, "contains"), isDeep = flag2(this, "deep"), eql = flag2(this, "eql");
7682
7703
  new Assertion(list, flagMsg, ssfi, true).to.be.an("array");
7683
7704
  if (contains) this.assert(list.some(function(possibility) {
7684
- return expected.indexOf(possibility) > -1;
7685
- }), "expected #{this} to contain one of #{exp}", "expected #{this} to not contain one of #{exp}", list, expected);
7705
+ return expected.indexOf(possibility) > -1 || isDeep && Array.isArray(expected) && expected.some(function(item) {
7706
+ return eql(item, possibility);
7707
+ });
7708
+ }), "expected #{this} to " + (isDeep ? "deeply " : "") + "contain one of #{exp}", "expected #{this} to not " + (isDeep ? "deeply " : "") + "contain one of #{exp}", list, expected);
7686
7709
  else if (isDeep) this.assert(list.some(function(possibility) {
7687
7710
  return eql(expected, possibility);
7688
- }), "expected #{this} to deeply equal one of #{exp}", "expected #{this} to deeply equal one of #{exp}", list, expected);
7711
+ }), "expected #{this} to deeply equal one of #{exp}", "expected #{this} to not deeply equal one of #{exp}", list, expected);
7689
7712
  else this.assert(list.indexOf(expected) > -1, "expected #{this} to be one of #{exp}", "expected #{this} to not be one of #{exp}", list, expected);
7690
7713
  }
7691
7714
  __name(oneOf, "oneOf");
7692
7715
  Assertion.addMethod("oneOf", oneOf);
7693
7716
  function assertChanges(subject, prop, msg) {
7694
7717
  if (msg) flag2(this, "message", msg);
7695
- let fn = flag2(this, "object"), flagMsg = flag2(this, "message"), ssfi = flag2(this, "ssfi");
7718
+ const fn = flag2(this, "object");
7719
+ const flagMsg = flag2(this, "message");
7720
+ const ssfi = flag2(this, "ssfi");
7696
7721
  new Assertion(fn, flagMsg, ssfi, true).is.a("function");
7697
7722
  let initial;
7698
- if (!prop) {
7723
+ if (prop === null || prop === void 0) {
7699
7724
  new Assertion(subject, flagMsg, ssfi, true).is.a("function");
7700
7725
  initial = subject();
7701
7726
  } else {
@@ -7703,7 +7728,9 @@ function assertChanges(subject, prop, msg) {
7703
7728
  initial = subject[prop];
7704
7729
  }
7705
7730
  fn();
7706
- let final = prop === void 0 || prop === null ? subject() : subject[prop];
7731
+ let final = prop === void 0 || prop === null ? subject() :
7732
+ /** @type {Record<PropertyKey, unknown>} */
7733
+ subject[prop];
7707
7734
  let msgObj = prop === void 0 || prop === null ? initial : "." + prop;
7708
7735
  flag2(this, "deltaMsgObj", msgObj);
7709
7736
  flag2(this, "initialDeltaValue", initial);
@@ -7717,10 +7744,12 @@ Assertion.addMethod("change", assertChanges);
7717
7744
  Assertion.addMethod("changes", assertChanges);
7718
7745
  function assertIncreases(subject, prop, msg) {
7719
7746
  if (msg) flag2(this, "message", msg);
7720
- let fn = flag2(this, "object"), flagMsg = flag2(this, "message"), ssfi = flag2(this, "ssfi");
7747
+ const fn = flag2(this, "object");
7748
+ const flagMsg = flag2(this, "message");
7749
+ const ssfi = flag2(this, "ssfi");
7721
7750
  new Assertion(fn, flagMsg, ssfi, true).is.a("function");
7722
7751
  let initial;
7723
- if (!prop) {
7752
+ if (prop === null || prop === void 0) {
7724
7753
  new Assertion(subject, flagMsg, ssfi, true).is.a("function");
7725
7754
  initial = subject();
7726
7755
  } else {
@@ -7729,7 +7758,9 @@ function assertIncreases(subject, prop, msg) {
7729
7758
  }
7730
7759
  new Assertion(initial, flagMsg, ssfi, true).is.a("number");
7731
7760
  fn();
7732
- let final = prop === void 0 || prop === null ? subject() : subject[prop];
7761
+ let final = prop === void 0 || prop === null ? subject() :
7762
+ /** @type {Record<PropertyKey, number>} */
7763
+ subject[prop];
7733
7764
  let msgObj = prop === void 0 || prop === null ? initial : "." + prop;
7734
7765
  flag2(this, "deltaMsgObj", msgObj);
7735
7766
  flag2(this, "initialDeltaValue", initial);
@@ -7743,10 +7774,12 @@ Assertion.addMethod("increase", assertIncreases);
7743
7774
  Assertion.addMethod("increases", assertIncreases);
7744
7775
  function assertDecreases(subject, prop, msg) {
7745
7776
  if (msg) flag2(this, "message", msg);
7746
- let fn = flag2(this, "object"), flagMsg = flag2(this, "message"), ssfi = flag2(this, "ssfi");
7777
+ const fn = flag2(this, "object");
7778
+ const flagMsg = flag2(this, "message");
7779
+ const ssfi = flag2(this, "ssfi");
7747
7780
  new Assertion(fn, flagMsg, ssfi, true).is.a("function");
7748
7781
  let initial;
7749
- if (!prop) {
7782
+ if (prop === null || prop === void 0) {
7750
7783
  new Assertion(subject, flagMsg, ssfi, true).is.a("function");
7751
7784
  initial = subject();
7752
7785
  } else {
@@ -7755,7 +7788,9 @@ function assertDecreases(subject, prop, msg) {
7755
7788
  }
7756
7789
  new Assertion(initial, flagMsg, ssfi, true).is.a("number");
7757
7790
  fn();
7758
- let final = prop === void 0 || prop === null ? subject() : subject[prop];
7791
+ let final = prop === void 0 || prop === null ? subject() :
7792
+ /** @type {Record<PropertyKey, number>} */
7793
+ subject[prop];
7759
7794
  let msgObj = prop === void 0 || prop === null ? initial : "." + prop;
7760
7795
  flag2(this, "deltaMsgObj", msgObj);
7761
7796
  flag2(this, "initialDeltaValue", initial);
@@ -7834,12 +7869,12 @@ function expect(val, message) {
7834
7869
  }
7835
7870
  __name(expect, "expect");
7836
7871
  expect.fail = function(actual, expected, message, operator) {
7872
+ let msg = message;
7837
7873
  if (arguments.length < 2) {
7838
- message = actual;
7874
+ msg = actual;
7839
7875
  actual = void 0;
7840
7876
  }
7841
- message = message || "expect.fail()";
7842
- throw new AssertionError(message, {
7877
+ throw new AssertionError(msg || "expect.fail()", {
7843
7878
  actual,
7844
7879
  expected,
7845
7880
  operator
@@ -8291,7 +8326,7 @@ assert$1.oneOf = function(inList, list, msg) {
8291
8326
  new Assertion(inList, msg, assert$1.oneOf, true).to.be.oneOf(list);
8292
8327
  };
8293
8328
  assert$1.isIterable = function(obj, msg) {
8294
- if (obj == void 0 || !obj[Symbol.iterator]) {
8329
+ if (obj == void 0 || typeof obj[Symbol.iterator] !== "function") {
8295
8330
  msg = msg ? `${msg} expected ${inspect2(obj)} to be an iterable` : `expected ${inspect2(obj)} to be an iterable`;
8296
8331
  throw new AssertionError(msg, void 0, assert$1.isIterable);
8297
8332
  }
@@ -8485,7 +8520,7 @@ function use(fn) {
8485
8520
  }
8486
8521
  __name(use, "use");
8487
8522
  //#endregion
8488
- //#region ../../node_modules/.pnpm/@vitest+expect@4.1.6/node_modules/@vitest/expect/dist/index.js
8523
+ //#region ../../node_modules/.pnpm/@vitest+expect@4.1.12/node_modules/@vitest/expect/dist/index.js
8489
8524
  const ChaiStyleAssertions = (chai, utils) => {
8490
8525
  function defProperty(name, delegateTo) {
8491
8526
  utils.addProperty(chai.Assertion.prototype, name, function() {
@@ -9947,7 +9982,7 @@ const JestExtend = (chai, utils) => {
9947
9982
  });
9948
9983
  };
9949
9984
  //#endregion
9950
- //#region ../../node_modules/.pnpm/@vitest+utils@4.1.6/node_modules/@vitest/utils/dist/offset.js
9985
+ //#region ../../node_modules/.pnpm/@vitest+utils@4.1.12/node_modules/@vitest/utils/dist/offset.js
9951
9986
  const lineSplitRE = /\r?\n/;
9952
9987
  function positionToOffset(source, lineNumber, columnNumber) {
9953
9988
  const lines = source.split(lineSplitRE);
@@ -9971,10 +10006,10 @@ function offsetToLineNumber(source, offset) {
9971
10006
  return line + 1;
9972
10007
  }
9973
10008
  //#endregion
9974
- //#region ../../node_modules/.pnpm/vitest@4.1.6_@types+node@25.9.1_vite@8.0.13_@types+node@25.9.1_yaml@2.8.4_/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js
10009
+ //#region ../../node_modules/.pnpm/vitest@4.1.12_@types+node@25.9.1_vite@8.3.4_@types+node@25.9.1_yaml@2.9.1_/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js
9975
10010
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
9976
10011
  //#endregion
9977
- //#region ../../node_modules/.pnpm/vitest@4.1.6_@types+node@25.9.1_vite@8.0.13_@types+node@25.9.1_yaml@2.8.4_/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js
10012
+ //#region ../../node_modules/.pnpm/vitest@4.1.12_@types+node@25.9.1_vite@8.3.4_@types+node@25.9.1_yaml@2.9.1_/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js
9978
10013
  const RealDate = Date;
9979
10014
  let now = null;
9980
10015
  var MockDate = class MockDate extends RealDate {
@@ -10023,7 +10058,7 @@ function resetDate() {
10023
10058
  }
10024
10059
  const { get } = Reflect;
10025
10060
  //#endregion
10026
- //#region ../../node_modules/.pnpm/@vitest+snapshot@4.1.6/node_modules/@vitest/snapshot/dist/index.js
10061
+ //#region ../../node_modules/.pnpm/@vitest+snapshot@4.1.12/node_modules/@vitest/snapshot/dist/index.js
10027
10062
  function getDefaultExportFromCjs(x) {
10028
10063
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
10029
10064
  }
@@ -10937,7 +10972,7 @@ function raceWith(promise, other) {
10937
10972
  }))]);
10938
10973
  }
10939
10974
  //#endregion
10940
- //#region ../../node_modules/.pnpm/vitest@4.1.6_@types+node@25.9.1_vite@8.0.13_@types+node@25.9.1_yaml@2.8.4_/node_modules/vitest/dist/chunks/test.DNmyFkvJ.js
10975
+ //#region ../../node_modules/.pnpm/vitest@4.1.12_@types+node@25.9.1_vite@8.3.4_@types+node@25.9.1_yaml@2.9.1_/node_modules/vitest/dist/chunks/test.DNmyFkvJ.js
10941
10976
  var fakeTimersSrc = {};
10942
10977
  var global$1;
10943
10978
  var hasRequiredGlobal;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rrlab/cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.6-git-c9903ab.0",
4
4
  "description": "The CLI toolbox to fullstack common scripts in Variable Land",
5
5
  "homepage": "https://github.com/withvariablelab/dx/tree/main/run-run/cli#readme",
6
6
  "bugs": {
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "engines": {
46
- "node": ">=20.0.0"
46
+ "node": ">=22.18.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@clack/prompts": "0.11.0",
@@ -10,7 +10,7 @@ import { ConfigAstService } from "#src/services/config-ast.ts";
10
10
  import type { ContextValue } from "#src/services/context.ts";
11
11
  import { applyFileOp } from "#src/services/file-ops.ts";
12
12
  import { logger } from "#src/services/logger.ts";
13
- import { createClackPrompts } from "#src/services/prompts.ts";
13
+ import { canPrompt, createClackPrompts } from "#src/services/prompts.ts";
14
14
  import { ReleaseService } from "#src/services/release.ts";
15
15
  import { describeWorkspaceChoice, resolveWorkspaceChoice, toNypmWorkspace } from "#src/services/workspace-target.ts";
16
16
  import { type AddOptions, hasInPackageJson, type PluginModule, reportFileOp } from "./shared.ts";
@@ -104,7 +104,7 @@ export async function addPluginAction({ ctx, args, options }: AddPluginActionCon
104
104
  flags: {
105
105
  force: !!options.force,
106
106
  yes: !!options.yes,
107
- nonInteractive: !!options.yes,
107
+ nonInteractive: !!options.yes || !canPrompt(),
108
108
  },
109
109
  release: new ReleaseService(tag),
110
110
  };
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import * as clack from "@clack/prompts";
3
3
  import { detectPackageManager, removeDependency } from "nypm";
4
+ import { ConfirmationRequiredError } from "#src/errors/confirmation-required.ts";
4
5
  import { PLUGINS_DIRECTORY, type PluginName } from "#src/lib/plugin/directory.ts";
5
6
  import type { UninstallResult } from "#src/lib/plugin/types.ts";
6
7
  import { withSpinner } from "#src/render/steps.ts";
@@ -8,7 +9,7 @@ import { ConfigAstService } from "#src/services/config-ast.ts";
8
9
  import type { ContextValue } from "#src/services/context.ts";
9
10
  import { applyFileOp, describeFileOp } from "#src/services/file-ops.ts";
10
11
  import { logger } from "#src/services/logger.ts";
11
- import { createClackPrompts } from "#src/services/prompts.ts";
12
+ import { canPrompt, createClackPrompts } from "#src/services/prompts.ts";
12
13
  import { describeWorkspaceChoice, resolveWorkspaceChoice, toNypmWorkspace } from "#src/services/workspace-target.ts";
13
14
  import { hasInPackageJson, type PluginModule, type RemoveOptions, reportFileOp } from "./shared.ts";
14
15
 
@@ -41,7 +42,7 @@ export async function removePluginAction({ ctx, args, options }: RemovePluginAct
41
42
  logger,
42
43
  appPkg: ctx.appPkg,
43
44
  prompts: createClackPrompts(),
44
- flags: { yes: !!options.yes, nonInteractive: !!options.yes },
45
+ flags: { yes: !!options.yes, nonInteractive: !!options.yes || !canPrompt() },
45
46
  });
46
47
  }
47
48
  } catch (err) {
@@ -83,6 +84,8 @@ export async function removePluginAction({ ctx, args, options }: RemovePluginAct
83
84
  }
84
85
 
85
86
  if (!options.yes) {
87
+ // Removing deletes files and dependencies: without a terminal to confirm in, insist on --yes.
88
+ if (!canPrompt()) throw new ConfirmationRequiredError(`rr plugins remove ${name}`);
86
89
  const choice = await clack.confirm({ message: "Proceed?", initialValue: false });
87
90
  if (clack.isCancel(choice) || choice !== true) {
88
91
  clack.outro("Aborted.");
@@ -0,0 +1,7 @@
1
+ import { KnownError } from "./known-error.ts";
2
+
3
+ export class ConfirmationRequiredError extends KnownError {
4
+ constructor(command: string) {
5
+ super(`'${command}' asks for confirmation, and there's no terminal to ask in. Re-run it with --yes.`);
6
+ }
7
+ }
@@ -13,12 +13,21 @@ export function createCompletionCommand() {
13
13
  return createCommand("completion")
14
14
  .summary(`print shell completion script`)
15
15
  .description(
16
- `Prints a shell completion script for rr. Add to your shell rc file:
16
+ `Prints a shell completion script for rr. Add your shell's line to its rc file:
17
17
 
18
- bash: eval "$(rr completion bash)"
19
- zsh: eval "$(rr completion zsh)"
20
- fish: rr completion fish | source`,
18
+ # bash — ~/.bashrc
19
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion bash 2>/dev/null)"
20
+
21
+ # zsh — ~/.zshrc
22
+ command -v rr >/dev/null 2>&1 && eval "$(rr completion zsh 2>/dev/null)"
23
+
24
+ # fish — ~/.config/fish/config.fish
25
+ command -v rr >/dev/null 2>&1; and rr completion fish 2>/dev/null | source`,
21
26
  )
27
+ .configureHelp({
28
+ // Commander hard-wraps help at the terminal width; a wrapped rc line breaks when pasted.
29
+ helpWidth: Number.POSITIVE_INFINITY,
30
+ })
22
31
  .addArgument(new Argument("<shell>", `target shell`).choices(SHELLS))
23
32
  .addHelpText(
24
33
  "afterAll",
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { builders, generateCode, loadFile, type ProxifiedModule, parseModule, writeFile } from "magicast";
3
+ import { builders, generateCode, loadFile, type ProxifiedModule, parseModule } from "magicast";
4
4
 
5
5
  const CONFIG_FILENAMES = ["run-run.config.ts", "run-run.config.mts"] as const;
6
6
 
@@ -50,14 +50,7 @@ export class ConfigAstService {
50
50
  }
51
51
 
52
52
  async save(loaded: LoadedConfig): Promise<void> {
53
- if (loaded.isNew) {
54
- // For a fresh file, write the generated code instead of using
55
- // `writeFile` — writeFile expects an existing file as a starting point.
56
- const { code } = generateCode(loaded.mod);
57
- await fs.writeFile(loaded.filepath, code, "utf8");
58
- return;
59
- }
60
- await writeFile(loaded.mod.$ast, loaded.filepath);
53
+ await fs.writeFile(loaded.filepath, tidy(generateCode(loaded.mod).code), "utf8");
61
54
  }
62
55
 
63
56
  /**
@@ -84,6 +77,7 @@ export class ConfigAstService {
84
77
  // Ensure the import binding exists.
85
78
  if (!mod.imports[entry.exportName]) {
86
79
  mod.imports.$add({ from: entry.pkgName, imported: "default", local: entry.exportName });
80
+ this.#placeImport(mod, entry.pkgName);
87
81
  }
88
82
 
89
83
  // Ensure `defineConfig` import + default export shape.
@@ -151,9 +145,30 @@ export class ConfigAstService {
151
145
  return opts.plugins;
152
146
  }
153
147
 
148
+ /**
149
+ * Moves the import of `source`, which `imports.$add` puts first, to its
150
+ * sorted place among the other imports, leaving those in their order: a
151
+ * sorted file stays sorted, so `rr jsc` passes on what rr wrote.
152
+ */
153
+ #placeImport(mod: ProxifiedModule, source: string): void {
154
+ const program = mod.$ast;
155
+ if (program.type !== "Program") return;
156
+ const { body } = program;
157
+ const from = body.findIndex((node) => node.type === "ImportDeclaration" && node.source.value === source);
158
+ if (from === -1) return;
159
+ const [added] = body.splice(from, 1);
160
+ if (!added) return;
161
+ const before = body.findIndex(
162
+ (node) => node.type === "ImportDeclaration" && compareImportSources(node.source.value, source) > 0,
163
+ );
164
+ const lastImport = body.reduce((last, node, i) => (node.type === "ImportDeclaration" ? i : last), -1);
165
+ body.splice(before === -1 ? lastImport + 1 : before, 0, added);
166
+ }
167
+
154
168
  #ensureDefineConfig(mod: ProxifiedModule): void {
155
169
  if (!mod.imports.defineConfig) {
156
170
  mod.imports.$add({ from: "@rrlab/cli/config", imported: "defineConfig", local: "defineConfig" });
171
+ this.#placeImport(mod, "@rrlab/cli/config");
157
172
  }
158
173
  // biome-ignore lint/suspicious/noExplicitAny: magicast proxies are opaque
159
174
  const def = (mod.exports as any).default;
@@ -194,6 +209,32 @@ export class ConfigAstService {
194
209
  }
195
210
  }
196
211
 
212
+ /**
213
+ * Undoes two recast habits that formatters reject: it keeps the blank line that
214
+ * followed an import when rr inserts another after it, and it drops the final
215
+ * newline.
216
+ */
217
+ function tidy(code: string): string {
218
+ const joined = code.replace(/^(import\b[^;]*;)\n(?:[ \t]*\n)+(?=import\b)/gm, "$1\n");
219
+ return joined.endsWith("\n") ? joined : `${joined}\n`;
220
+ }
221
+
222
+ /**
223
+ * Biome's organizeImports order: protocol imports (`node:fs`), then packages,
224
+ * then aliases (`#src/…`, `@/…`, `~/…`), then paths. Within a group, plain
225
+ * code-unit order, which matches Biome for these names (`ts-plugin` before
226
+ * `tsdown-plugin`; `localeCompare` flips them).
227
+ */
228
+ function compareImportSources(a: string, b: string): number {
229
+ const group = (source: string) => {
230
+ if (/^[a-z]+:/.test(source)) return 0;
231
+ if (/^(#|@\/|~)/.test(source)) return 2;
232
+ if (source.startsWith(".") || source.startsWith("/")) return 3;
233
+ return 1;
234
+ };
235
+ return group(a) - group(b) || (a < b ? -1 : a > b ? 1 : 0);
236
+ }
237
+
197
238
  const MINIMAL_TEMPLATE = `import { defineConfig } from "@rrlab/cli/config";
198
239
 
199
240
  export default defineConfig({
@@ -1,6 +1,15 @@
1
1
  import * as clack from "@clack/prompts";
2
2
  import type { ClackPrompts } from "#src/lib/plugin/types.ts";
3
3
 
4
+ /**
5
+ * Whether a prompt can run: clack reads keys from stdin and draws on stdout,
6
+ * so both have to be terminals. An agent, a script or CI usually has neither,
7
+ * and a prompt there waits forever or exits on a closed stdin.
8
+ */
9
+ export function canPrompt(): boolean {
10
+ return !!process.stdin.isTTY && !!process.stdout.isTTY;
11
+ }
12
+
4
13
  /**
5
14
  * Adapter that exposes the subset of `@clack/prompts` matching the
6
15
  * `ClackPrompts` contract from `@rrlab/cli/plugin`. The contract intentionally