toiljs 0.0.123 → 0.0.125

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.
@@ -650,11 +650,11 @@ var require_valid = __commonJS({
650
650
  "node_modules/semver/functions/valid.js"(exports, module) {
651
651
  "use strict";
652
652
  var parse = require_parse();
653
- var valid = (version2, options) => {
653
+ var valid2 = (version2, options) => {
654
654
  const v = parse(version2, options);
655
655
  return v ? v.version : null;
656
656
  };
657
- module.exports = valid;
657
+ module.exports = valid2;
658
658
  }
659
659
  });
660
660
 
@@ -871,8 +871,8 @@ var require_eq = __commonJS({
871
871
  "node_modules/semver/functions/eq.js"(exports, module) {
872
872
  "use strict";
873
873
  var compare = require_compare();
874
- var eq = (a2, b, loose) => compare(a2, b, loose) === 0;
875
- module.exports = eq;
874
+ var eq2 = (a2, b, loose) => compare(a2, b, loose) === 0;
875
+ module.exports = eq2;
876
876
  }
877
877
  });
878
878
 
@@ -910,7 +910,7 @@ var require_lte = __commonJS({
910
910
  var require_cmp = __commonJS({
911
911
  "node_modules/semver/functions/cmp.js"(exports, module) {
912
912
  "use strict";
913
- var eq = require_eq();
913
+ var eq2 = require_eq();
914
914
  var neq = require_neq();
915
915
  var gt = require_gt();
916
916
  var gte = require_gte();
@@ -937,7 +937,7 @@ var require_cmp = __commonJS({
937
937
  case "":
938
938
  case "=":
939
939
  case "==":
940
- return eq(a2, b, loose);
940
+ return eq2(a2, b, loose);
941
941
  case "!=":
942
942
  return neq(a2, b, loose);
943
943
  case ">":
@@ -1584,7 +1584,7 @@ var require_satisfies = __commonJS({
1584
1584
  "node_modules/semver/functions/satisfies.js"(exports, module) {
1585
1585
  "use strict";
1586
1586
  var Range = require_range();
1587
- var satisfies2 = (version2, range, options) => {
1587
+ var satisfies4 = (version2, range, options) => {
1588
1588
  try {
1589
1589
  range = new Range(range, options);
1590
1590
  } catch (er) {
@@ -1592,7 +1592,7 @@ var require_satisfies = __commonJS({
1592
1592
  }
1593
1593
  return range.test(version2);
1594
1594
  };
1595
- module.exports = satisfies2;
1595
+ module.exports = satisfies4;
1596
1596
  }
1597
1597
  });
1598
1598
 
@@ -1728,14 +1728,14 @@ var require_valid2 = __commonJS({
1728
1728
  "node_modules/semver/ranges/valid.js"(exports, module) {
1729
1729
  "use strict";
1730
1730
  var Range = require_range();
1731
- var validRange2 = (range, options) => {
1731
+ var validRange4 = (range, options) => {
1732
1732
  try {
1733
1733
  return new Range(range, options).range || "*";
1734
1734
  } catch (er) {
1735
1735
  return null;
1736
1736
  }
1737
1737
  };
1738
- module.exports = validRange2;
1738
+ module.exports = validRange4;
1739
1739
  }
1740
1740
  });
1741
1741
 
@@ -1747,7 +1747,7 @@ var require_outside = __commonJS({
1747
1747
  var Comparator = require_comparator();
1748
1748
  var { ANY } = Comparator;
1749
1749
  var Range = require_range();
1750
- var satisfies2 = require_satisfies();
1750
+ var satisfies4 = require_satisfies();
1751
1751
  var gt = require_gt();
1752
1752
  var lt = require_lt();
1753
1753
  var lte = require_lte();
@@ -1774,7 +1774,7 @@ var require_outside = __commonJS({
1774
1774
  default:
1775
1775
  throw new TypeError('Must provide a hilo val of "<" or ">"');
1776
1776
  }
1777
- if (satisfies2(version2, range, options)) {
1777
+ if (satisfies4(version2, range, options)) {
1778
1778
  return false;
1779
1779
  }
1780
1780
  for (let i2 = 0; i2 < range.set.length; ++i2) {
@@ -1833,12 +1833,12 @@ var require_intersects = __commonJS({
1833
1833
  "node_modules/semver/ranges/intersects.js"(exports, module) {
1834
1834
  "use strict";
1835
1835
  var Range = require_range();
1836
- var intersects = (r1, r2, options) => {
1836
+ var intersects2 = (r1, r2, options) => {
1837
1837
  r1 = new Range(r1, options);
1838
1838
  r2 = new Range(r2, options);
1839
1839
  return r1.intersects(r2, options);
1840
1840
  };
1841
- module.exports = intersects;
1841
+ module.exports = intersects2;
1842
1842
  }
1843
1843
  });
1844
1844
 
@@ -1846,7 +1846,7 @@ var require_intersects = __commonJS({
1846
1846
  var require_simplify = __commonJS({
1847
1847
  "node_modules/semver/ranges/simplify.js"(exports, module) {
1848
1848
  "use strict";
1849
- var satisfies2 = require_satisfies();
1849
+ var satisfies4 = require_satisfies();
1850
1850
  var compare = require_compare();
1851
1851
  module.exports = (versions, range, options) => {
1852
1852
  const set = [];
@@ -1854,7 +1854,7 @@ var require_simplify = __commonJS({
1854
1854
  let prev = null;
1855
1855
  const v = versions.sort((a2, b) => compare(a2, b, options));
1856
1856
  for (const version2 of v) {
1857
- const included = satisfies2(version2, range, options);
1857
+ const included = satisfies4(version2, range, options);
1858
1858
  if (included) {
1859
1859
  prev = version2;
1860
1860
  if (!first) {
@@ -1899,9 +1899,9 @@ var require_subset = __commonJS({
1899
1899
  var Range = require_range();
1900
1900
  var Comparator = require_comparator();
1901
1901
  var { ANY } = Comparator;
1902
- var satisfies2 = require_satisfies();
1902
+ var satisfies4 = require_satisfies();
1903
1903
  var compare = require_compare();
1904
- var subset2 = (sub, dom, options = {}) => {
1904
+ var subset3 = (sub, dom, options = {}) => {
1905
1905
  if (sub === dom) {
1906
1906
  return true;
1907
1907
  }
@@ -1967,15 +1967,15 @@ var require_subset = __commonJS({
1967
1967
  return null;
1968
1968
  }
1969
1969
  }
1970
- for (const eq of eqSet) {
1971
- if (gt && !satisfies2(eq, String(gt), options)) {
1970
+ for (const eq2 of eqSet) {
1971
+ if (gt && !satisfies4(eq2, String(gt), options)) {
1972
1972
  return null;
1973
1973
  }
1974
- if (lt && !satisfies2(eq, String(lt), options)) {
1974
+ if (lt && !satisfies4(eq2, String(lt), options)) {
1975
1975
  return null;
1976
1976
  }
1977
1977
  for (const c2 of dom) {
1978
- if (!satisfies2(eq, String(c2), options)) {
1978
+ if (!satisfies4(eq2, String(c2), options)) {
1979
1979
  return false;
1980
1980
  }
1981
1981
  }
@@ -2050,7 +2050,7 @@ var require_subset = __commonJS({
2050
2050
  const comp = compare(a2.semver, b.semver, options);
2051
2051
  return comp < 0 ? a2 : comp > 0 ? b : b.operator === "<" && a2.operator === "<=" ? b : a2;
2052
2052
  };
2053
- module.exports = subset2;
2053
+ module.exports = subset3;
2054
2054
  }
2055
2055
  });
2056
2056
 
@@ -2063,7 +2063,7 @@ var require_semver2 = __commonJS({
2063
2063
  var SemVer = require_semver();
2064
2064
  var identifiers = require_identifiers();
2065
2065
  var parse = require_parse();
2066
- var valid = require_valid();
2066
+ var valid2 = require_valid();
2067
2067
  var clean2 = require_clean();
2068
2068
  var inc = require_inc();
2069
2069
  var diff = require_diff();
@@ -2079,7 +2079,7 @@ var require_semver2 = __commonJS({
2079
2079
  var rsort = require_rsort();
2080
2080
  var gt = require_gt();
2081
2081
  var lt = require_lt();
2082
- var eq = require_eq();
2082
+ var eq2 = require_eq();
2083
2083
  var neq = require_neq();
2084
2084
  var gte = require_gte();
2085
2085
  var lte = require_lte();
@@ -2088,21 +2088,21 @@ var require_semver2 = __commonJS({
2088
2088
  var truncate = require_truncate();
2089
2089
  var Comparator = require_comparator();
2090
2090
  var Range = require_range();
2091
- var satisfies2 = require_satisfies();
2091
+ var satisfies4 = require_satisfies();
2092
2092
  var toComparators = require_to_comparators();
2093
2093
  var maxSatisfying = require_max_satisfying();
2094
2094
  var minSatisfying = require_min_satisfying();
2095
2095
  var minVersion2 = require_min_version();
2096
- var validRange2 = require_valid2();
2096
+ var validRange4 = require_valid2();
2097
2097
  var outside = require_outside();
2098
2098
  var gtr = require_gtr();
2099
2099
  var ltr = require_ltr();
2100
- var intersects = require_intersects();
2100
+ var intersects2 = require_intersects();
2101
2101
  var simplifyRange = require_simplify();
2102
- var subset2 = require_subset();
2102
+ var subset3 = require_subset();
2103
2103
  module.exports = {
2104
2104
  parse,
2105
- valid,
2105
+ valid: valid2,
2106
2106
  clean: clean2,
2107
2107
  inc,
2108
2108
  diff,
@@ -2118,7 +2118,7 @@ var require_semver2 = __commonJS({
2118
2118
  rsort,
2119
2119
  gt,
2120
2120
  lt,
2121
- eq,
2121
+ eq: eq2,
2122
2122
  neq,
2123
2123
  gte,
2124
2124
  lte,
@@ -2127,18 +2127,18 @@ var require_semver2 = __commonJS({
2127
2127
  truncate,
2128
2128
  Comparator,
2129
2129
  Range,
2130
- satisfies: satisfies2,
2130
+ satisfies: satisfies4,
2131
2131
  toComparators,
2132
2132
  maxSatisfying,
2133
2133
  minSatisfying,
2134
2134
  minVersion: minVersion2,
2135
- validRange: validRange2,
2135
+ validRange: validRange4,
2136
2136
  outside,
2137
2137
  gtr,
2138
2138
  ltr,
2139
- intersects,
2139
+ intersects: intersects2,
2140
2140
  simplifyRange,
2141
- subset: subset2,
2141
+ subset: subset3,
2142
2142
  SemVer,
2143
2143
  re: internalRe.re,
2144
2144
  src: internalRe.src,
@@ -4149,7 +4149,7 @@ function scaffold(name, template, features, aiTools, images) {
4149
4149
  oxlint: "^1.87.0",
4150
4150
  "oxlint-tsgolint": "^7.0.2003",
4151
4151
  prettier: "^3.9.9",
4152
- toilscript: ">=0.1.63",
4152
+ toilscript: ">=0.1.64",
4153
4153
  typescript: "^7.0.2"
4154
4154
  };
4155
4155
  for (const dep of requiredPackages(features).sort()) {
@@ -4525,9 +4525,9 @@ async function runCreate(opts2) {
4525
4525
  name = answer.trim() || "my-toil-app";
4526
4526
  }
4527
4527
  }
4528
- const valid = isValidName(name);
4529
- if (valid !== true) {
4530
- cancel(valid);
4528
+ const valid2 = isValidName(name);
4529
+ if (valid2 !== true) {
4530
+ cancel(valid2);
4531
4531
  process.exit(1);
4532
4532
  }
4533
4533
  const targetDir = resolveProjectDir(opts2.cwd, name);
@@ -4838,9 +4838,9 @@ function runDb(action, fileArg, opts2) {
4838
4838
  }
4839
4839
 
4840
4840
  // src/cli/doctor.ts
4841
- import fs5 from "node:fs";
4842
- import { createRequire } from "node:module";
4843
- import path6 from "node:path";
4841
+ import fs6 from "node:fs";
4842
+ import { createRequire as createRequire2 } from "node:module";
4843
+ import path7 from "node:path";
4844
4844
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
4845
4845
  import {
4846
4846
  loadConfig as loadConfig2,
@@ -5164,7 +5164,7 @@ function checkWasmBuilt(exists) {
5164
5164
  fix: "Run your server build (toilscript) before toiljs start."
5165
5165
  };
5166
5166
  }
5167
- var RPC_TOILSCRIPT_MIN = "0.1.63";
5167
+ var RPC_TOILSCRIPT_MIN = "0.1.64";
5168
5168
  function checkRpcWiring(f) {
5169
5169
  const missing = [];
5170
5170
  if (!f.toilscriptOk) missing.push(`toilscript >=${RPC_TOILSCRIPT_MIN}`);
@@ -5250,10 +5250,240 @@ function hasFailures(summary) {
5250
5250
  return summary.fail > 0;
5251
5251
  }
5252
5252
 
5253
+ // src/cli/test-setup.ts
5254
+ import fs5 from "node:fs/promises";
5255
+ import path6 from "node:path";
5256
+ import { createRequire } from "node:module";
5257
+ var import_semver3 = __toESM(require_semver2(), 1);
5258
+
5259
+ // src/cli/testing-versions.ts
5260
+ var import_semver2 = __toESM(require_semver2(), 1);
5261
+ function isSupportedTestingRange(name, range) {
5262
+ const supported = TEST_DEPENDENCIES[name];
5263
+ return supported === void 0 || (0, import_semver2.validRange)(range) !== null && (0, import_semver2.subset)(range, supported);
5264
+ }
5265
+ function isTestingSource(range) {
5266
+ return /^(file:|workspace:|link:|git[+:]|https?:|github:|npm:)/.test(range);
5267
+ }
5268
+
5269
+ // src/cli/test-setup.ts
5270
+ async function findTestConfiguration(root) {
5271
+ const files = await fs5.readdir(root);
5272
+ const custom = files.find((file) => /^vitest\.config\.[cm]?[jt]s$/.test(file)) ?? files.find((file) => /^vite\.config\.[cm]?[jt]s$/.test(file));
5273
+ const source = custom === void 0 ? "" : await fs5.readFile(path6.join(root, custom), "utf8");
5274
+ const native = custom === void 0 || source.includes("from 'toiljs/vitest'") || source.includes('from "toiljs/vitest"');
5275
+ return { file: custom ?? null, native };
5276
+ }
5277
+ async function writeMissing(root, file, content) {
5278
+ const target = path6.join(root, file);
5279
+ await fs5.mkdir(path6.dirname(target), { recursive: true });
5280
+ try {
5281
+ await fs5.writeFile(target, content, { flag: "wx" });
5282
+ return true;
5283
+ } catch (error) {
5284
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
5285
+ return false;
5286
+ }
5287
+ }
5288
+ async function ensureTesting(root, repairVersions = false) {
5289
+ const pkgPath = path6.join(root, "package.json");
5290
+ const raw = JSON.parse(await fs5.readFile(pkgPath, "utf8"));
5291
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
5292
+ throw new Error("toiljs test: package.json must contain an object.");
5293
+ const pkg = raw;
5294
+ for (const field of [pkg.scripts, pkg.dependencies, pkg.devDependencies]) {
5295
+ if (field !== void 0 && (field === null || typeof field !== "object" || Array.isArray(field) || Object.values(field).some((value) => typeof value !== "string")))
5296
+ throw new Error("toiljs test: package scripts and dependencies must be string maps.");
5297
+ }
5298
+ const config = await findTestConfiguration(root);
5299
+ const { native } = config;
5300
+ const changes = [];
5301
+ const skipped = [];
5302
+ let changed = false;
5303
+ let install = false;
5304
+ const require2 = createRequire(pkgPath);
5305
+ for (const [name, range] of Object.entries(TEST_DEPENDENCIES)) {
5306
+ for (const dependencies of [pkg.dependencies, pkg.devDependencies]) {
5307
+ const declared = dependencies?.[name];
5308
+ if (repairVersions && dependencies && declared && !isSupportedTestingRange(name, declared) && !isTestingSource(declared)) {
5309
+ dependencies[name] = range;
5310
+ changes.push(`package.json (${name} ${declared} -> ${range})`);
5311
+ changed = true;
5312
+ install = true;
5313
+ }
5314
+ }
5315
+ if (pkg.dependencies?.[name] === void 0 && pkg.devDependencies?.[name] === void 0) {
5316
+ pkg.devDependencies ??= {};
5317
+ pkg.devDependencies[name] = range;
5318
+ changes.push(`package.json (add ${name})`);
5319
+ changed = true;
5320
+ install = true;
5321
+ }
5322
+ try {
5323
+ require2.resolve(name);
5324
+ } catch {
5325
+ install = true;
5326
+ }
5327
+ }
5328
+ if (repairVersions) {
5329
+ const runnerRange = pkg.dependencies?.vitest ?? pkg.devDependencies?.vitest;
5330
+ const coverageDependencies = pkg.dependencies?.["@vitest/coverage-v8"] !== void 0 ? pkg.dependencies : pkg.devDependencies;
5331
+ if (runnerRange && isSupportedTestingRange("vitest", runnerRange) && coverageDependencies && (0, import_semver3.validRange)(coverageDependencies["@vitest/coverage-v8"] ?? "") !== null) {
5332
+ const installedRunner = await testVersion(require2, "vitest");
5333
+ const installedCoverage = await testVersion(
5334
+ require2,
5335
+ "@vitest/coverage-v8"
5336
+ );
5337
+ const target = installedRunner && installedCoverage && installedRunner !== installedCoverage && (0, import_semver3.satisfies)(installedRunner, runnerRange) ? installedRunner : (0, import_semver3.intersects)(coverageDependencies["@vitest/coverage-v8"], runnerRange) ? coverageDependencies["@vitest/coverage-v8"] : runnerRange;
5338
+ if (coverageDependencies["@vitest/coverage-v8"] !== target) {
5339
+ coverageDependencies["@vitest/coverage-v8"] = target;
5340
+ changes.push("package.json (align V8 coverage with Vitest)");
5341
+ changed = true;
5342
+ install = true;
5343
+ }
5344
+ if (installedRunner && installedCoverage && installedRunner !== installedCoverage)
5345
+ install = true;
5346
+ }
5347
+ }
5348
+ pkg.scripts ??= {};
5349
+ for (const [name, script] of Object.entries(TEST_SCRIPTS)) {
5350
+ if (pkg.scripts[name]?.trim()) continue;
5351
+ pkg.scripts[name] = script;
5352
+ changes.push(`package.json (add script ${name})`);
5353
+ changed = true;
5354
+ }
5355
+ if (changed) await fs5.writeFile(pkgPath, JSON.stringify(raw, null, 4) + "\n");
5356
+ if (config.file === null) {
5357
+ for (const [file, content] of Object.entries(testingFiles()))
5358
+ if (await writeMissing(root, file, content)) changes.push(file);
5359
+ }
5360
+ if (native) await includeTestTypes(root, config.file ?? "vitest.config.ts", changes, skipped);
5361
+ await ignoreTestReports(root, changes);
5362
+ return { install, native, changed: changes, skipped };
5363
+ }
5364
+ async function includeTestTypes(root, config, changes, skipped) {
5365
+ const file = path6.join(root, "tsconfig.json");
5366
+ let source;
5367
+ try {
5368
+ source = await fs5.readFile(file, "utf8");
5369
+ } catch (error) {
5370
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
5371
+ throw error;
5372
+ }
5373
+ try {
5374
+ const raw = JSON.parse(source);
5375
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
5376
+ throw new Error("Expected an object");
5377
+ const parsed = raw;
5378
+ if (!isStringArray(parsed.include)) {
5379
+ skipped.push(
5380
+ "tsconfig.json: include tests and the Vitest config in your existing file selection."
5381
+ );
5382
+ return;
5383
+ }
5384
+ const include = parsed.include;
5385
+ const missing = ["tests", config].filter(
5386
+ (entry) => !include.includes(entry)
5387
+ );
5388
+ if (!missing.length) return;
5389
+ include.push(...missing);
5390
+ await fs5.writeFile(file, JSON.stringify(parsed, null, 4) + "\n");
5391
+ changes.push("tsconfig.json (include tests and Vitest config)");
5392
+ } catch {
5393
+ skipped.push(
5394
+ "tsconfig.json: include tests and the Vitest config manually; custom JSON was preserved."
5395
+ );
5396
+ }
5397
+ }
5398
+ async function ignoreTestReports(root, changes) {
5399
+ const file = path6.join(root, ".gitignore");
5400
+ let source = "";
5401
+ try {
5402
+ source = await fs5.readFile(file, "utf8");
5403
+ } catch (error) {
5404
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
5405
+ }
5406
+ const lines = source.split(/\r?\n/).map((line) => line.trim().replace(/\/$/, ""));
5407
+ const missing = ["coverage", ".vitest"].filter(
5408
+ (entry) => !lines.includes(entry) && !lines.includes("/" + entry)
5409
+ );
5410
+ if (!missing.length) return;
5411
+ await fs5.writeFile(
5412
+ file,
5413
+ source + (source && !source.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n"
5414
+ );
5415
+ changes.push(".gitignore (test reports)");
5416
+ }
5417
+ async function testVersion(require2, name) {
5418
+ try {
5419
+ const raw = JSON.parse(
5420
+ await fs5.readFile(require2.resolve(`${name}/package.json`), "utf8")
5421
+ );
5422
+ return raw !== null && typeof raw === "object" && "version" in raw && typeof raw.version === "string" ? raw.version : null;
5423
+ } catch {
5424
+ return null;
5425
+ }
5426
+ }
5427
+ function isStringArray(value) {
5428
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
5429
+ }
5430
+
5431
+ // src/cli/testing-checks.ts
5432
+ var import_semver4 = __toESM(require_semver2(), 1);
5433
+ function checkTesting(facts) {
5434
+ const configured = Object.keys(TEST_DEPENDENCIES).some(
5435
+ (name) => facts.dependencies[name] !== void 0
5436
+ ) || facts.configuration.file?.startsWith("vitest.config.") === true;
5437
+ const repair = "Run `toiljs doctor --fix`, then your package manager install (or `toiljs test` to install missing tooling).";
5438
+ const checks = Object.entries(TEST_DEPENDENCIES).map(([name, range]) => {
5439
+ const installed = facts.installed[name] ?? null;
5440
+ const declared = facts.dependencies[name];
5441
+ const ok = installed !== null && declared !== void 0 && (0, import_semver4.satisfies)(installed, range) && (isSupportedTestingRange(name, declared) || isTestingSource(declared));
5442
+ return {
5443
+ id: `testing:${name}`,
5444
+ label: name,
5445
+ status: ok ? "pass" : configured ? "fail" : "warn",
5446
+ detail: `${installed ?? "not installed"}; declared ${declared ?? "missing"} (requires ${range})`,
5447
+ fix: ok ? void 0 : repair
5448
+ };
5449
+ });
5450
+ const runner = facts.installed.vitest ?? null;
5451
+ const coverage = facts.installed["@vitest/coverage-v8"] ?? null;
5452
+ if (runner !== null && coverage !== null) {
5453
+ const match = (0, import_semver4.valid)(runner) !== null && (0, import_semver4.valid)(coverage) !== null && (0, import_semver4.eq)(runner, coverage);
5454
+ checks.push({
5455
+ id: "testing:coverage-match",
5456
+ label: "Vitest / V8 coverage versions",
5457
+ status: match ? "pass" : "fail",
5458
+ detail: `vitest ${runner}, @vitest/coverage-v8 ${coverage}`,
5459
+ fix: match ? void 0 : "Install matching versions of vitest and @vitest/coverage-v8; run `toiljs update --yes` to refresh both."
5460
+ });
5461
+ }
5462
+ const configPresent = facts.configuration.file !== null;
5463
+ checks.push({
5464
+ id: "testing:config",
5465
+ label: "Vitest configuration",
5466
+ status: configPresent ? "pass" : "warn",
5467
+ detail: configPresent ? `${facts.configuration.file} (${facts.configuration.native ? "toiljs preset" : "custom, preserved"})` : "missing",
5468
+ fix: configPresent ? void 0 : repair
5469
+ });
5470
+ const missing = Object.keys(TEST_SCRIPTS).filter(
5471
+ (name) => !facts.scripts[name]?.trim()
5472
+ );
5473
+ checks.push({
5474
+ id: "testing:scripts",
5475
+ label: "Unit, browser, watch, and coverage scripts",
5476
+ status: missing.length ? "warn" : "pass",
5477
+ detail: missing.length ? `missing ${missing.join(", ")}` : "configured",
5478
+ fix: missing.length ? repair : void 0
5479
+ });
5480
+ return checks;
5481
+ }
5482
+
5253
5483
  // src/cli/doctor.ts
5254
5484
  function readJsonObject(file) {
5255
5485
  try {
5256
- const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
5486
+ const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
5257
5487
  return typeof parsed === "object" && parsed !== null ? parsed : null;
5258
5488
  } catch {
5259
5489
  return null;
@@ -5267,16 +5497,16 @@ function stringRecord(value) {
5267
5497
  }
5268
5498
  function readFile(file) {
5269
5499
  try {
5270
- return fs5.readFileSync(file, "utf8");
5500
+ return fs6.readFileSync(file, "utf8");
5271
5501
  } catch {
5272
5502
  return null;
5273
5503
  }
5274
5504
  }
5275
5505
  function writeFile(file, content) {
5276
- fs5.writeFileSync(file, content);
5506
+ fs6.writeFileSync(file, content);
5277
5507
  }
5278
5508
  function isPackageInstalled(root, name) {
5279
- const require2 = createRequire(path6.join(root, "package.json"));
5509
+ const require2 = createRequire2(path7.join(root, "package.json"));
5280
5510
  for (const id of [`${name}/package.json`, name]) {
5281
5511
  try {
5282
5512
  require2.resolve(id);
@@ -5285,22 +5515,22 @@ function isPackageInstalled(root, name) {
5285
5515
  }
5286
5516
  }
5287
5517
  for (let dir = root; ; ) {
5288
- if (fs5.existsSync(path6.join(dir, "node_modules", name, "package.json"))) return true;
5289
- const parent = path6.dirname(dir);
5518
+ if (fs6.existsSync(path7.join(dir, "node_modules", name, "package.json"))) return true;
5519
+ const parent = path7.dirname(dir);
5290
5520
  if (parent === dir) return false;
5291
5521
  dir = parent;
5292
5522
  }
5293
5523
  }
5294
5524
  function installedVersion(root, name) {
5295
- const require2 = createRequire(path6.join(root, "package.json"));
5525
+ const require2 = createRequire2(path7.join(root, "package.json"));
5296
5526
  let pkgPath = null;
5297
5527
  try {
5298
5528
  pkgPath = require2.resolve(`${name}/package.json`);
5299
5529
  } catch {
5300
5530
  for (let dir = root; pkgPath === null; ) {
5301
- const candidate = path6.join(dir, "node_modules", name, "package.json");
5302
- if (fs5.existsSync(candidate)) pkgPath = candidate;
5303
- const parent = path6.dirname(dir);
5531
+ const candidate = path7.join(dir, "node_modules", name, "package.json");
5532
+ if (fs6.existsSync(candidate)) pkgPath = candidate;
5533
+ const parent = path7.dirname(dir);
5304
5534
  if (parent === dir) break;
5305
5535
  dir = parent;
5306
5536
  }
@@ -5312,7 +5542,7 @@ function installedVersion(root, name) {
5312
5542
  function applyTypeScriptFix(root) {
5313
5543
  const changed = [];
5314
5544
  const skipped = [];
5315
- const pkgPath = path6.join(root, "package.json");
5545
+ const pkgPath = path7.join(root, "package.json");
5316
5546
  const pkg = readJsonObject(pkgPath);
5317
5547
  if (pkg === null) return { changed, skipped };
5318
5548
  let declared = false;
@@ -5350,14 +5580,14 @@ function looksLikeSemverRange(range) {
5350
5580
  return /^\s*[v^~>=<]*\s*\d+\.\d+/.test(range);
5351
5581
  }
5352
5582
  function gatherRpcFacts(root) {
5353
- const pkg = readJsonObject(path6.join(root, "package.json"));
5583
+ const pkg = readJsonObject(path7.join(root, "package.json"));
5354
5584
  const scripts = pkg ? stringRecord(pkg.scripts) : {};
5355
5585
  const deps = {
5356
5586
  ...pkg ? stringRecord(pkg.dependencies) : {},
5357
5587
  ...pkg ? stringRecord(pkg.devDependencies) : {}
5358
5588
  };
5359
- const tsconfig = readJsonObject(path6.join(root, "tsconfig.json"));
5360
- const gitignore = readFile(path6.join(root, ".gitignore"));
5589
+ const tsconfig = readJsonObject(path7.join(root, "tsconfig.json"));
5590
+ const gitignore = readFile(path7.join(root, ".gitignore"));
5361
5591
  const buildServerWired = [scripts["build:server"], scripts["build"]].some(
5362
5592
  (s) => typeof s === "string" && (s.includes("--rpcModule") || /\btoiljs\s+build\b/.test(s))
5363
5593
  );
@@ -5376,7 +5606,7 @@ function gatherRpcFacts(root) {
5376
5606
  function serverSources(root, toilconfig) {
5377
5607
  const dirs = /* @__PURE__ */ new Set();
5378
5608
  const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
5379
- for (const e of entries) dirs.add(path6.dirname(path6.resolve(root, e)));
5609
+ for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
5380
5610
  const out2 = [];
5381
5611
  const cap = 200;
5382
5612
  const maxDepth = 16;
@@ -5384,13 +5614,13 @@ function serverSources(root, toilconfig) {
5384
5614
  if (out2.length >= cap || depth > maxDepth) return;
5385
5615
  let listing;
5386
5616
  try {
5387
- listing = fs5.readdirSync(current, { withFileTypes: true });
5617
+ listing = fs6.readdirSync(current, { withFileTypes: true });
5388
5618
  } catch {
5389
5619
  return;
5390
5620
  }
5391
5621
  for (const entry of listing) {
5392
5622
  if (out2.length >= cap) break;
5393
- const full = path6.join(current, entry.name);
5623
+ const full = path7.join(current, entry.name);
5394
5624
  if (entry.isDirectory()) {
5395
5625
  if (entry.name !== "node_modules") visit(full, depth + 1);
5396
5626
  } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
@@ -5414,7 +5644,7 @@ function gatherRestFacts(root, toilconfig) {
5414
5644
  return { hasControllers, dispatched };
5415
5645
  }
5416
5646
  function secretDefined(root, key) {
5417
- const raw = readFile(path6.join(root, ".env.secrets"));
5647
+ const raw = readFile(path7.join(root, ".env.secrets"));
5418
5648
  if (raw === null) return false;
5419
5649
  return new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=\\s*\\S`, "m").test(raw);
5420
5650
  }
@@ -5431,21 +5661,21 @@ function gatherAuthFacts(root, toilconfig) {
5431
5661
  function serverTsconfigPath(root, toilconfig) {
5432
5662
  const dirs = /* @__PURE__ */ new Set();
5433
5663
  const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
5434
- for (const e of entries) dirs.add(path6.dirname(path6.resolve(root, e)));
5435
- if (dirs.size === 0) dirs.add(path6.join(root, "server"));
5664
+ for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
5665
+ if (dirs.size === 0) dirs.add(path7.join(root, "server"));
5436
5666
  for (const dir of dirs) {
5437
- const p = path6.join(dir, "tsconfig.json");
5438
- if (fs5.existsSync(p)) return p;
5667
+ const p = path7.join(dir, "tsconfig.json");
5668
+ if (fs6.existsSync(p)) return p;
5439
5669
  }
5440
5670
  return null;
5441
5671
  }
5442
5672
  function serverMigrationsExist(root, toilconfig) {
5443
5673
  const dirs = /* @__PURE__ */ new Set();
5444
5674
  const entries = Array.isArray(toilconfig?.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
5445
- for (const e of entries) dirs.add(path6.dirname(path6.resolve(root, e)));
5446
- if (dirs.size === 0) dirs.add(path6.join(root, "server"));
5675
+ for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
5676
+ if (dirs.size === 0) dirs.add(path7.join(root, "server"));
5447
5677
  for (const dir of dirs) {
5448
- if (fs5.existsSync(path6.join(dir, "migrations"))) return true;
5678
+ if (fs6.existsSync(path7.join(dir, "migrations"))) return true;
5449
5679
  }
5450
5680
  return false;
5451
5681
  }
@@ -5460,7 +5690,7 @@ function tsconfigHasToilPlugin(tsconfig) {
5460
5690
  function applyRpcFix(root) {
5461
5691
  const changed = [];
5462
5692
  const skipped = [];
5463
- const pkgPath = path6.join(root, "package.json");
5693
+ const pkgPath = path7.join(root, "package.json");
5464
5694
  const pkgRaw = readFile(pkgPath);
5465
5695
  const pkg = pkgRaw !== null ? readJsonObject(pkgPath) : null;
5466
5696
  if (pkg !== null) {
@@ -5499,7 +5729,7 @@ function applyRpcFix(root) {
5499
5729
  } else if (pkgRaw !== null) {
5500
5730
  skipped.push("package.json (unparseable)");
5501
5731
  }
5502
- const tsPath = path6.join(root, "tsconfig.json");
5732
+ const tsPath = path7.join(root, "tsconfig.json");
5503
5733
  const tsRaw = readFile(tsPath);
5504
5734
  const tsconfig = tsRaw !== null ? readJsonObject(tsPath) : null;
5505
5735
  if (tsconfig !== null) {
@@ -5538,7 +5768,7 @@ function applyRpcFix(root) {
5538
5768
  } else if (tsRaw !== null) {
5539
5769
  skipped.push('tsconfig.json (JSON with comments, add "shared" + paths by hand)');
5540
5770
  }
5541
- const giPath = path6.join(root, ".gitignore");
5771
+ const giPath = path7.join(root, ".gitignore");
5542
5772
  const giRaw = readFile(giPath);
5543
5773
  if (giRaw === null) {
5544
5774
  writeFile(giPath, `${RPC_GITIGNORE_LINE}
@@ -5550,21 +5780,21 @@ function applyRpcFix(root) {
5550
5780
  `);
5551
5781
  changed.push(".gitignore");
5552
5782
  }
5553
- const serverToilconfig = readJsonObject(path6.join(root, "toilconfig.json"));
5783
+ const serverToilconfig = readJsonObject(path7.join(root, "toilconfig.json"));
5554
5784
  if (serverToilconfig !== null) {
5555
5785
  const entries = Array.isArray(serverToilconfig.entries) ? serverToilconfig.entries.filter(
5556
5786
  (e) => typeof e === "string"
5557
5787
  ) : [];
5558
5788
  const dirs = /* @__PURE__ */ new Set();
5559
- for (const e of entries) dirs.add(path6.dirname(path6.resolve(root, e)));
5560
- if (dirs.size === 0) dirs.add(path6.join(root, "server"));
5789
+ for (const e of entries) dirs.add(path7.dirname(path7.resolve(root, e)));
5790
+ if (dirs.size === 0) dirs.add(path7.join(root, "server"));
5561
5791
  for (const dir of dirs) {
5562
- const envPath = path6.join(dir, "toil-server-env.d.ts");
5792
+ const envPath = path7.join(dir, "toil-server-env.d.ts");
5563
5793
  if (readFile(envPath) !== TOIL_SERVER_ENV_DTS2) {
5564
5794
  try {
5565
- fs5.mkdirSync(dir, { recursive: true });
5795
+ fs6.mkdirSync(dir, { recursive: true });
5566
5796
  writeFile(envPath, TOIL_SERVER_ENV_DTS2);
5567
- changed.push(path6.relative(root, envPath));
5797
+ changed.push(path7.relative(root, envPath));
5568
5798
  } catch {
5569
5799
  }
5570
5800
  }
@@ -5594,7 +5824,7 @@ function prettierPluginPresent(root, pkg) {
5594
5824
  return true;
5595
5825
  }
5596
5826
  for (const name of PRETTIER_CONFIG_FILES) {
5597
- const raw = readFile(path6.join(root, name));
5827
+ const raw = readFile(path7.join(root, name));
5598
5828
  if (raw !== null && PRETTIER_MENTION.test(raw)) return true;
5599
5829
  }
5600
5830
  return false;
@@ -5603,7 +5833,7 @@ function applyPrettierFix(root, pkg) {
5603
5833
  const changed = [];
5604
5834
  const skipped = [];
5605
5835
  if (prettierPluginPresent(root, pkg)) return { changed, skipped };
5606
- const pkgPath = path6.join(root, "package.json");
5836
+ const pkgPath = path7.join(root, "package.json");
5607
5837
  const pkgConfig = pkg ? asRecord(pkg.prettier) : null;
5608
5838
  if (pkgConfig !== null) {
5609
5839
  const full = readJsonObject(pkgPath);
@@ -5619,7 +5849,7 @@ function applyPrettierFix(root, pkg) {
5619
5849
  }
5620
5850
  }
5621
5851
  for (const name of [".prettierrc", ".prettierrc.json"]) {
5622
- const filePath = path6.join(root, name);
5852
+ const filePath = path7.join(root, name);
5623
5853
  const raw = readFile(filePath);
5624
5854
  if (raw === null) continue;
5625
5855
  const obj = readJsonObject(filePath);
@@ -5635,13 +5865,13 @@ function applyPrettierFix(root, pkg) {
5635
5865
  changed.push(name);
5636
5866
  return { changed, skipped };
5637
5867
  }
5638
- const jsConfig = PRETTIER_CONFIG_FILES.find((name) => readFile(path6.join(root, name)) !== null);
5868
+ const jsConfig = PRETTIER_CONFIG_FILES.find((name) => readFile(path7.join(root, name)) !== null);
5639
5869
  if (jsConfig) {
5640
5870
  skipped.push(`${jsConfig} (add "${PRETTIER_PLUGIN}" to its plugins by hand)`);
5641
5871
  return { changed, skipped };
5642
5872
  }
5643
5873
  writeFile(
5644
- path6.join(root, ".prettierrc.json"),
5874
+ path7.join(root, ".prettierrc.json"),
5645
5875
  JSON.stringify({ plugins: [PRETTIER_PLUGIN] }, null, 4) + "\n"
5646
5876
  );
5647
5877
  changed.push(".prettierrc.json");
@@ -5655,7 +5885,7 @@ function applyServerEditorFix(root, toilconfig) {
5655
5885
  const parsed = readJsonObject(tsPath);
5656
5886
  if (parsed === null)
5657
5887
  skipped.push(
5658
- `${path6.relative(root, tsPath)} (remove legacy ts-plugin from JSONC manually)`
5888
+ `${path7.relative(root, tsPath)} (remove legacy ts-plugin from JSONC manually)`
5659
5889
  );
5660
5890
  else if (tsconfigHasToilPlugin(parsed)) {
5661
5891
  const co = asRecord(parsed.compilerOptions);
@@ -5667,12 +5897,12 @@ function applyServerEditorFix(root, toilconfig) {
5667
5897
  if (plugins.length === 0) delete co.plugins;
5668
5898
  else co.plugins = plugins;
5669
5899
  writeFile(tsPath, JSON.stringify(parsed, null, 4) + "\n");
5670
- changed.push(path6.relative(root, tsPath));
5900
+ changed.push(path7.relative(root, tsPath));
5671
5901
  }
5672
5902
  }
5673
5903
  }
5674
- const vsPath = path6.join(root, ".vscode/settings.json");
5675
- const vs = fs5.existsSync(vsPath) ? readJsonObject(vsPath) : {};
5904
+ const vsPath = path7.join(root, ".vscode/settings.json");
5905
+ const vs = fs6.existsSync(vsPath) ? readJsonObject(vsPath) : {};
5676
5906
  if (vs === null)
5677
5907
  skipped.push(".vscode/settings.json (enable js/ts.experimental.useTsgo manually)");
5678
5908
  else {
@@ -5681,7 +5911,7 @@ function applyServerEditorFix(root, toilconfig) {
5681
5911
  delete vs["typescript.enablePromptUseWorkspaceTsdk"];
5682
5912
  vs["js/ts.experimental.useTsgo"] = true;
5683
5913
  if (JSON.stringify(vs) !== before) {
5684
- fs5.mkdirSync(path6.dirname(vsPath), { recursive: true });
5914
+ fs6.mkdirSync(path7.dirname(vsPath), { recursive: true });
5685
5915
  writeFile(vsPath, JSON.stringify(vs, null, 4) + "\n");
5686
5916
  changed.push(".vscode/settings.json");
5687
5917
  }
@@ -5697,8 +5927,8 @@ async function checkNativeTypeScriptConfig(root) {
5697
5927
  status: "fail",
5698
5928
  detail: "Install TypeScript 7 before validating compiler configuration."
5699
5929
  };
5700
- const configPath = path6.join(root, "tsconfig.json");
5701
- if (!fs5.existsSync(configPath))
5930
+ const configPath = path7.join(root, "tsconfig.json");
5931
+ if (!fs6.existsSync(configPath))
5702
5932
  return {
5703
5933
  ...check,
5704
5934
  status: "fail",
@@ -5706,7 +5936,7 @@ async function checkNativeTypeScriptConfig(root) {
5706
5936
  fix: "Create a tsconfig.json extending toiljs/tsconfig."
5707
5937
  };
5708
5938
  try {
5709
- const apiPath = createRequire(path6.join(root, "package.json")).resolve(
5939
+ const apiPath = createRequire2(path7.join(root, "package.json")).resolve(
5710
5940
  "typescript/unstable/sync"
5711
5941
  );
5712
5942
  const native = await import(pathToFileURL(apiPath).href);
@@ -5739,9 +5969,9 @@ async function checkNativeTypeScriptConfig(root) {
5739
5969
  }
5740
5970
  }
5741
5971
  function checkLintMigration(root) {
5742
- const pkg = readJsonObject(path6.join(root, "package.json"));
5972
+ const pkg = readJsonObject(path7.join(root, "package.json"));
5743
5973
  const script = asRecord(pkg?.scripts)?.lint;
5744
- const config = readFile(path6.join(root, "oxlint.config.ts")) ?? readFile(path6.join(root, ".oxlintrc.json")) ?? "";
5974
+ const config = readFile(path7.join(root, "oxlint.config.ts")) ?? readFile(path7.join(root, ".oxlintrc.json")) ?? "";
5745
5975
  const configured = config.includes("toiljs/oxlint") || /["']?typeAware["']?\s*:\s*true/.test(config);
5746
5976
  const scriptOk = typeof script === "string" && /\boxlint\b/.test(script) && !/\beslint\b/.test(script) && (script.includes("--type-aware") || configured);
5747
5977
  const installed = installedVersion(root, "oxlint") !== null && installedVersion(root, "oxlint-tsgolint") !== null;
@@ -5754,8 +5984,8 @@ function checkLintMigration(root) {
5754
5984
  };
5755
5985
  }
5756
5986
  function frameworkMeta() {
5757
- const pkgPath = path6.resolve(
5758
- path6.dirname(fileURLToPath3(import.meta.url)),
5987
+ const pkgPath = path7.resolve(
5988
+ path7.dirname(fileURLToPath3(import.meta.url)),
5759
5989
  "..",
5760
5990
  "..",
5761
5991
  "package.json"
@@ -5765,10 +5995,10 @@ function frameworkMeta() {
5765
5995
  const peers = pkg ? stringRecord(pkg.peerDependencies) : {};
5766
5996
  return { node: engines.node ?? ">=24.0.0", peers };
5767
5997
  }
5768
- var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"];
5998
+ var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
5769
5999
  function readEntry(clientAbsDir) {
5770
6000
  for (const name of ["toil.tsx", "toil.jsx", "main.tsx", "main.jsx"]) {
5771
- const source = readFile(path6.join(clientAbsDir, name));
6001
+ const source = readFile(path7.join(clientAbsDir, name));
5772
6002
  if (source !== null && /toiljs\/routes|\bmount\s*\(/.test(source)) return source;
5773
6003
  }
5774
6004
  return null;
@@ -5779,18 +6009,18 @@ function collectSources(root, dir, cap) {
5779
6009
  if (out2.length >= cap) return;
5780
6010
  let entries;
5781
6011
  try {
5782
- entries = fs5.readdirSync(current, { withFileTypes: true });
6012
+ entries = fs6.readdirSync(current, { withFileTypes: true });
5783
6013
  } catch {
5784
6014
  return;
5785
6015
  }
5786
6016
  for (const entry of entries) {
5787
6017
  if (out2.length >= cap) break;
5788
- const full = path6.join(current, entry.name);
6018
+ const full = path7.join(current, entry.name);
5789
6019
  if (entry.isDirectory()) {
5790
6020
  if (entry.name !== "node_modules") visit(full);
5791
6021
  } else if (/\.(tsx|jsx)$/.test(entry.name)) {
5792
6022
  const source = readFile(full);
5793
- if (source !== null) out2.push({ path: path6.relative(root, full), source });
6023
+ if (source !== null) out2.push({ path: path7.relative(root, full), source });
5794
6024
  }
5795
6025
  }
5796
6026
  };
@@ -5826,9 +6056,9 @@ function renderHuman(groups) {
5826
6056
  process.stdout.write(out2.join("\n") + "\n");
5827
6057
  }
5828
6058
  async function runDoctor(opts2) {
5829
- const root = path6.resolve(opts2.root ?? opts2.cwd);
6059
+ const root = path7.resolve(opts2.root ?? opts2.cwd);
5830
6060
  const meta = frameworkMeta();
5831
- const projectPkg = readJsonObject(path6.join(root, "package.json"));
6061
+ const projectPkg = readJsonObject(path7.join(root, "package.json"));
5832
6062
  const deps = {
5833
6063
  ...projectPkg ? stringRecord(projectPkg.dependencies) : {},
5834
6064
  ...projectPkg ? stringRecord(projectPkg.devDependencies) : {}
@@ -5840,11 +6070,11 @@ async function runDoctor(opts2) {
5840
6070
  } catch (err) {
5841
6071
  configError = err instanceof Error ? err.message : String(err);
5842
6072
  }
5843
- const clientAbsDir = cfg ? cfg.clientAbsDir : path6.join(root, "client");
5844
- const routesAbsDir = cfg ? cfg.routesAbsDir : path6.join(clientAbsDir, "routes");
5845
- const publicDir = cfg ? cfg.publicDir : path6.join(clientAbsDir, "public");
6073
+ const clientAbsDir = cfg ? cfg.clientAbsDir : path7.join(root, "client");
6074
+ const routesAbsDir = cfg ? cfg.routesAbsDir : path7.join(clientAbsDir, "routes");
6075
+ const publicDir = cfg ? cfg.publicDir : path7.join(clientAbsDir, "public");
5846
6076
  const entrySource = readEntry(clientAbsDir);
5847
- const indexHtml = readFile(path6.join(publicDir, "index.html"));
6077
+ const indexHtml = readFile(path7.join(publicDir, "index.html"));
5848
6078
  const routes = scanRoutes(routesAbsDir);
5849
6079
  const mainPatterns = routes.filter((r2) => r2.slot === void 0).map((r2) => r2.pattern);
5850
6080
  const assetIssues = findRelativeAssets(collectSources(root, clientAbsDir, 200));
@@ -5857,14 +6087,14 @@ async function runDoctor(opts2) {
5857
6087
  }
5858
6088
  const ppPkg = preprocessorImported ? PREPROCESSOR_PKG[preprocessorImported] : null;
5859
6089
  const preprocessorInstalled = ppPkg === null || ppPkg in deps;
5860
- const toilconfig = readJsonObject(path6.join(root, "toilconfig.json"));
6090
+ const toilconfig = readJsonObject(path7.join(root, "toilconfig.json"));
5861
6091
  const serverPresent = toilconfig !== null;
5862
6092
  let missingEntries = [];
5863
6093
  let toilscriptInstalled = false;
5864
6094
  let wasmExists = false;
5865
6095
  if (toilconfig) {
5866
6096
  const entries = Array.isArray(toilconfig.entries) ? toilconfig.entries.filter((e) => typeof e === "string") : [];
5867
- missingEntries = entries.filter((e) => !fs5.existsSync(path6.join(root, e)));
6097
+ missingEntries = entries.filter((e) => !fs6.existsSync(path7.join(root, e)));
5868
6098
  toilscriptInstalled = isPackageInstalled(root, "toilscript");
5869
6099
  const targets = typeof toilconfig.targets === "object" && toilconfig.targets !== null ? toilconfig.targets : {};
5870
6100
  const outFiles = [];
@@ -5874,20 +6104,20 @@ async function runDoctor(opts2) {
5874
6104
  if (typeof outFile === "string") outFiles.push(outFile);
5875
6105
  }
5876
6106
  }
5877
- wasmExists = outFiles.some((f) => fs5.existsSync(path6.join(root, f)));
6107
+ wasmExists = outFiles.some((f) => fs6.existsSync(path7.join(root, f)));
5878
6108
  if (!wasmExists && outFiles.length === 0) {
5879
6109
  try {
5880
- wasmExists = fs5.readdirSync(path6.join(root, "build", "server")).some((f) => f.endsWith(".wasm"));
6110
+ wasmExists = fs6.readdirSync(path7.join(root, "build", "server")).some((f) => f.endsWith(".wasm"));
5881
6111
  } catch {
5882
6112
  wasmExists = false;
5883
6113
  }
5884
6114
  }
5885
6115
  }
5886
6116
  const peerName = (n3) => checkPeer(n3, deps[n3] ?? null, meta.peers[n3] ?? "*");
5887
- const peerChecks = Object.keys(meta.peers).filter((n3) => n3 !== "typescript").map(peerName);
6117
+ const peerChecks = Object.keys(meta.peers).filter((n3) => n3 !== "typescript" && !Object.hasOwn(TEST_DEPENDENCIES, n3)).map(peerName);
5888
6118
  const typeScriptFix = opts2.fix ? applyTypeScriptFix(root) : null;
5889
6119
  const declaredTypeScript = (() => {
5890
- const pkg = readJsonObject(path6.join(root, "package.json"));
6120
+ const pkg = readJsonObject(path7.join(root, "package.json"));
5891
6121
  if (pkg === null) return deps.typescript ?? null;
5892
6122
  const ranges = [
5893
6123
  "dependencies",
@@ -5909,12 +6139,39 @@ async function runDoctor(opts2) {
5909
6139
  const authFacts = gatherAuthFacts(root, toilconfig);
5910
6140
  const prettierPresent = prettierPluginPresent(
5911
6141
  root,
5912
- readJsonObject(path6.join(root, "package.json"))
6142
+ readJsonObject(path7.join(root, "package.json"))
5913
6143
  );
5914
6144
  const serverTsPath = serverPresent ? serverTsconfigPath(root, toilconfig) : null;
5915
6145
  const serverTsParsed = serverTsPath ? readJsonObject(serverTsPath) : null;
5916
6146
  const serverTsPluginPresent = serverTsPath === null || serverTsParsed === null ? false : tsconfigHasToilPlugin(serverTsParsed);
5917
- const applied = [typeScriptFix, rpcFix, prettierFix, editorFix].filter(
6147
+ let testingFix = null;
6148
+ let testingChecks;
6149
+ try {
6150
+ if (opts2.fix && projectPkg !== null) testingFix = await ensureTesting(root, true);
6151
+ const testingPkg = readJsonObject(path7.join(root, "package.json"));
6152
+ testingChecks = checkTesting({
6153
+ dependencies: {
6154
+ ...stringRecord(testingPkg?.dependencies),
6155
+ ...stringRecord(testingPkg?.devDependencies)
6156
+ },
6157
+ scripts: stringRecord(testingPkg?.scripts),
6158
+ installed: Object.fromEntries(
6159
+ Object.keys(TEST_DEPENDENCIES).map((name) => [name, installedVersion(root, name)])
6160
+ ),
6161
+ configuration: await findTestConfiguration(root)
6162
+ });
6163
+ } catch (error) {
6164
+ testingChecks = [
6165
+ {
6166
+ id: "testing:setup",
6167
+ label: "Testing setup",
6168
+ status: "fail",
6169
+ detail: error instanceof Error ? error.message : String(error),
6170
+ fix: "Repair package.json or unreadable test configuration, then run `toiljs doctor --fix`."
6171
+ }
6172
+ ];
6173
+ }
6174
+ const applied = [typeScriptFix, rpcFix, prettierFix, editorFix, testingFix].filter(
5918
6175
  (fix) => fix !== null
5919
6176
  );
5920
6177
  const serverFix = applied.length > 0 ? {
@@ -5931,7 +6188,7 @@ async function runDoctor(opts2) {
5931
6188
  typeScriptCheck,
5932
6189
  checkLintMigration(root),
5933
6190
  await checkNativeTypeScriptConfig(root),
5934
- checkPackageManager(LOCKFILES.filter((f) => fs5.existsSync(path6.join(root, f))))
6191
+ checkPackageManager(LOCKFILES.filter((f) => fs6.existsSync(path7.join(root, f))))
5935
6192
  ]
5936
6193
  },
5937
6194
  {
@@ -5940,13 +6197,13 @@ async function runDoctor(opts2) {
5940
6197
  checkDir(
5941
6198
  "client-dir",
5942
6199
  "client/ directory",
5943
- fs5.existsSync(clientAbsDir),
6200
+ fs6.existsSync(clientAbsDir),
5944
6201
  "Create a client/ directory for your app."
5945
6202
  ),
5946
6203
  checkDir(
5947
6204
  "routes-dir",
5948
6205
  "routes/ directory",
5949
- fs5.existsSync(routesAbsDir),
6206
+ fs6.existsSync(routesAbsDir),
5950
6207
  "Create client/routes/ and add an index.tsx."
5951
6208
  ),
5952
6209
  checkRootElement(indexHtml),
@@ -5957,6 +6214,10 @@ async function runDoctor(opts2) {
5957
6214
  checkDevScripts(projectPkg ? stringRecord(projectPkg.scripts) : {})
5958
6215
  ]
5959
6216
  },
6217
+ {
6218
+ title: "Testing",
6219
+ checks: testingChecks
6220
+ },
5960
6221
  {
5961
6222
  title: "Config + assets",
5962
6223
  checks: [
@@ -6017,8 +6278,8 @@ function renderFix(result) {
6017
6278
 
6018
6279
  // src/cli/generate.ts
6019
6280
  import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
6020
- import fs6 from "node:fs";
6021
- import path7 from "node:path";
6281
+ import fs7 from "node:fs";
6282
+ import path8 from "node:path";
6022
6283
  import { parseEnv } from "node:util";
6023
6284
 
6024
6285
  // node_modules/@noble/hashes/_u64.js
@@ -6931,10 +7192,10 @@ var ml_kem768 = /* @__PURE__ */ (() => mk(PARAMS[768]))();
6931
7192
  var PRIVATE_KEYS = ["AUTH_SESSION_SECRET", "AUTH_OPRF_SEED", "AUTH_KEM_SK"];
6932
7193
  var PUBLIC_KEY = "VITE_AUTH_KEM_PUBLIC_KEY";
6933
7194
  function read(file) {
6934
- const stat = fs6.lstatSync(file, { throwIfNoEntry: false });
7195
+ const stat = fs7.lstatSync(file, { throwIfNoEntry: false });
6935
7196
  if (!stat) return "";
6936
7197
  if (!stat.isFile()) throw new Error(`Expected a regular file: ${file}`);
6937
- return fs6.readFileSync(file, "utf8");
7198
+ return fs7.readFileSync(file, "utf8");
6938
7199
  }
6939
7200
  function append(text2, lines) {
6940
7201
  if (!lines.length) return text2;
@@ -6957,10 +7218,10 @@ function publicKeyFor(secret) {
6957
7218
  }
6958
7219
  function runGenerate(target, opts2 = {}) {
6959
7220
  if (target !== "auth") throw new Error("Usage: toiljs generate auth [--root <dir>]");
6960
- const root = path7.resolve(opts2.root ?? process.cwd());
6961
- const envFile = path7.join(root, ".env");
6962
- const secretsFile = path7.join(root, ".env.secrets");
6963
- const ignoreFile = path7.join(root, ".gitignore");
7221
+ const root = path8.resolve(opts2.root ?? process.cwd());
7222
+ const envFile = path8.join(root, ".env");
7223
+ const secretsFile = path8.join(root, ".env.secrets");
7224
+ const ignoreFile = path8.join(root, ".gitignore");
6964
7225
  const envText = read(envFile);
6965
7226
  const secretsText = read(secretsFile);
6966
7227
  const ignoreText = read(ignoreFile);
@@ -6998,12 +7259,12 @@ function runGenerate(target, opts2 = {}) {
6998
7259
  const publicAdditions = Object.hasOwn(env, PUBLIC_KEY) ? [] : [`${PUBLIC_KEY}=${publicKey}`];
6999
7260
  const ignoreLines = new Set(ignoreText.split(/\r?\n/));
7000
7261
  const ignoreAdditions = ["/.env", "/.env.secrets"].filter((line) => !ignoreLines.has(line));
7001
- if (ignoreAdditions.length) fs6.writeFileSync(ignoreFile, append(ignoreText, ignoreAdditions));
7002
- if (fs6.existsSync(secretsFile)) fs6.chmodSync(secretsFile, 384);
7262
+ if (ignoreAdditions.length) fs7.writeFileSync(ignoreFile, append(ignoreText, ignoreAdditions));
7263
+ if (fs7.existsSync(secretsFile)) fs7.chmodSync(secretsFile, 384);
7003
7264
  if (additions.length)
7004
- fs6.writeFileSync(secretsFile, append(secretsText, additions), { mode: 384 });
7265
+ fs7.writeFileSync(secretsFile, append(secretsText, additions), { mode: 384 });
7005
7266
  if (publicAdditions.length)
7006
- fs6.writeFileSync(envFile, append(envText, publicAdditions), { mode: 384 });
7267
+ fs7.writeFileSync(envFile, append(envText, publicAdditions), { mode: 384 });
7007
7268
  process.stdout.write(
7008
7269
  [
7009
7270
  `Authentication keys ready in ${root}. Existing credentials were preserved.`,
@@ -7020,14 +7281,14 @@ function runGenerate(target, opts2 = {}) {
7020
7281
  }
7021
7282
 
7022
7283
  // src/cli/notify.ts
7023
- import fs8 from "node:fs";
7284
+ import fs9 from "node:fs";
7024
7285
  import os from "node:os";
7025
- import path9 from "node:path";
7286
+ import path10 from "node:path";
7026
7287
  import { fileURLToPath as fileURLToPath4 } from "node:url";
7027
7288
 
7028
7289
  // src/cli/update.ts
7029
- import fs7 from "node:fs";
7030
- import path8 from "node:path";
7290
+ import fs8 from "node:fs";
7291
+ import path9 from "node:path";
7031
7292
 
7032
7293
  // src/cli/updates.ts
7033
7294
  function parseVersion2(v) {
@@ -7070,14 +7331,14 @@ function buildRows(upgraded, currentDeps) {
7070
7331
 
7071
7332
  // src/cli/update.ts
7072
7333
  function detectPackageManager2(root) {
7073
- if (fs7.existsSync(path8.join(root, "pnpm-lock.yaml"))) return { name: "pnpm", ncuName: "pnpm" };
7074
- if (fs7.existsSync(path8.join(root, "yarn.lock"))) return { name: "yarn", ncuName: "yarn" };
7075
- if (fs7.existsSync(path8.join(root, "bun.lock")) || fs7.existsSync(path8.join(root, "bun.lockb")))
7334
+ if (fs8.existsSync(path9.join(root, "pnpm-lock.yaml"))) return { name: "pnpm", ncuName: "pnpm" };
7335
+ if (fs8.existsSync(path9.join(root, "yarn.lock"))) return { name: "yarn", ncuName: "yarn" };
7336
+ if (fs8.existsSync(path9.join(root, "bun.lock")) || fs8.existsSync(path9.join(root, "bun.lockb")))
7076
7337
  return { name: "bun", ncuName: "bun" };
7077
7338
  return { name: "npm", ncuName: "npm" };
7078
7339
  }
7079
7340
  function readDependencies(pkgPath) {
7080
- const parsed = JSON.parse(fs7.readFileSync(pkgPath, "utf8"));
7341
+ const parsed = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
7081
7342
  if (typeof parsed !== "object" || parsed === null) return {};
7082
7343
  const pkg = parsed;
7083
7344
  const merge = (v) => {
@@ -7105,26 +7366,26 @@ function serverDirs(root) {
7105
7366
  const dirs = /* @__PURE__ */ new Set();
7106
7367
  try {
7107
7368
  const parsed = JSON.parse(
7108
- fs7.readFileSync(path8.join(root, "toilconfig.json"), "utf8")
7369
+ fs8.readFileSync(path9.join(root, "toilconfig.json"), "utf8")
7109
7370
  );
7110
7371
  const entries = typeof parsed === "object" && parsed !== null && Array.isArray(parsed.entries) ? parsed.entries.filter(
7111
7372
  (e) => typeof e === "string"
7112
7373
  ) : [];
7113
- for (const e of entries) dirs.add(path8.dirname(path8.resolve(root, e)));
7374
+ for (const e of entries) dirs.add(path9.dirname(path9.resolve(root, e)));
7114
7375
  } catch {
7115
7376
  }
7116
- if (dirs.size === 0) dirs.add(path8.join(root, "server"));
7377
+ if (dirs.size === 0) dirs.add(path9.join(root, "server"));
7117
7378
  return [...dirs];
7118
7379
  }
7119
7380
  function ensureMigrationsDirs(root) {
7120
7381
  const created = [];
7121
7382
  for (const dir of serverDirs(root)) {
7122
- if (!fs7.existsSync(dir)) continue;
7123
- const migrations = path8.join(dir, "migrations");
7124
- if (fs7.existsSync(migrations)) continue;
7125
- fs7.mkdirSync(migrations, { recursive: true });
7126
- fs7.writeFileSync(path8.join(migrations, "README.md"), MIGRATIONS_README);
7127
- created.push(path8.relative(root, migrations) || "migrations");
7383
+ if (!fs8.existsSync(dir)) continue;
7384
+ const migrations = path9.join(dir, "migrations");
7385
+ if (fs8.existsSync(migrations)) continue;
7386
+ fs8.mkdirSync(migrations, { recursive: true });
7387
+ fs8.writeFileSync(path9.join(migrations, "README.md"), MIGRATIONS_README);
7388
+ created.push(path9.relative(root, migrations) || "migrations");
7128
7389
  }
7129
7390
  return created;
7130
7391
  }
@@ -7140,16 +7401,16 @@ function rowLine(row) {
7140
7401
  function noteWithheld(names) {
7141
7402
  note(
7142
7403
  names.map((n3) => `${dim("-")} ${n3}`).join("\n") + "\n\n" + dim(
7143
- "Held back: toiljs supports TypeScript 7 only. Compiler updates must stay within 7.x."
7404
+ "Held back: compiler updates must stay within TypeScript 7; native testing updates must stay within the supported Vitest and WebdriverIO versions."
7144
7405
  ),
7145
7406
  warn("Not upgraded")
7146
7407
  );
7147
7408
  }
7148
7409
  var TARGETS = /* @__PURE__ */ new Set(["latest", "minor", "patch", "newest", "greatest"]);
7149
7410
  async function runUpdate(opts2) {
7150
- const root = path8.resolve(opts2.root ?? opts2.cwd);
7151
- const pkgPath = path8.join(root, "package.json");
7152
- if (!fs7.existsSync(pkgPath)) {
7411
+ const root = path9.resolve(opts2.root ?? opts2.cwd);
7412
+ const pkgPath = path9.join(root, "package.json");
7413
+ if (!fs8.existsSync(pkgPath)) {
7153
7414
  throw new Error("No package.json here. Run from your project root or pass --root <dir>.");
7154
7415
  }
7155
7416
  const currentDeps = readDependencies(pkgPath);
@@ -7182,7 +7443,13 @@ async function runUpdate(opts2) {
7182
7443
  return;
7183
7444
  }
7184
7445
  const upgraded = parseNcuJson(res.stdout);
7185
- const withheld = withheldUpgrades(upgraded);
7446
+ const testConfig = await findTestConfiguration(root);
7447
+ const withheld = [
7448
+ ...withheldUpgrades(upgraded),
7449
+ ...Object.keys(upgraded).filter(
7450
+ (name) => testConfig.native && !isSupportedTestingRange(name, upgraded[name])
7451
+ )
7452
+ ];
7186
7453
  for (const name of withheld) delete upgraded[name];
7187
7454
  if (!isSupportedTypeScriptRange(currentDeps.typescript ?? "") && !upgraded.typescript) {
7188
7455
  const versions = await capture("npm", ["view", "typescript@7", "version", "--json"], root);
@@ -7199,7 +7466,20 @@ async function runUpdate(opts2) {
7199
7466
  if (rows.length === 0) {
7200
7467
  s.stop("Everything is up to date");
7201
7468
  if (withheld.length > 0) noteWithheld(withheld);
7202
- outro(success("Nothing to update."));
7469
+ if (opts2.yes) {
7470
+ const testing2 = await ensureTesting(root, true);
7471
+ if (testing2.changed.length) note(testing2.changed.join("\n"), "Testing setup");
7472
+ if (testing2.skipped.length) note(testing2.skipped.join("\n"), "Manual changes");
7473
+ if (testing2.install && !await installUpdates(pm.name, root)) return;
7474
+ outro(
7475
+ success(testing2.changed.length ? "Testing setup updated." : "Nothing to update.")
7476
+ );
7477
+ } else
7478
+ outro(
7479
+ success(
7480
+ "Nothing to update. Use `toiljs doctor --fix` or `toiljs update --yes` to add missing testing setup."
7481
+ )
7482
+ );
7203
7483
  return;
7204
7484
  }
7205
7485
  s.stop(`${String(rows.length)} update${rows.length === 1 ? "" : "s"} available`);
@@ -7234,7 +7514,17 @@ async function runUpdate(opts2) {
7234
7514
  outro(dim("No packages selected."));
7235
7515
  return;
7236
7516
  }
7237
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf8"));
7517
+ if (testConfig.native) {
7518
+ const paired = selected.find(
7519
+ (name) => name === "vitest" || name === "@vitest/coverage-v8"
7520
+ );
7521
+ if (paired) {
7522
+ const counterpart = paired === "vitest" ? "@vitest/coverage-v8" : "vitest";
7523
+ upgraded[counterpart] = upgraded[paired];
7524
+ if (!selected.includes(counterpart)) selected.push(counterpart);
7525
+ }
7526
+ }
7527
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf8"));
7238
7528
  const targetTypeScript = selected.includes("typescript") ? upgraded.typescript : currentDeps.typescript;
7239
7529
  if (!targetTypeScript || !isSupportedTypeScriptRange(targetTypeScript)) {
7240
7530
  throw new Error("Select the TypeScript 7 migration before installing updates.");
@@ -7260,23 +7550,30 @@ async function runUpdate(opts2) {
7260
7550
  pkg.devDependencies = deps;
7261
7551
  }
7262
7552
  }
7263
- fs7.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n");
7553
+ fs8.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n");
7264
7554
  s.stop("package.json updated");
7265
- note(dim(`Running ${pm.name} install\u2026`), "Install");
7555
+ const testing = await ensureTesting(root, true);
7556
+ if (testing.changed.length) note(testing.changed.join("\n"), "Testing setup");
7557
+ if (testing.skipped.length) note(testing.skipped.join("\n"), "Manual changes");
7558
+ if (!await installUpdates(pm.name, root)) return;
7559
+ outro(
7560
+ success(`Updated ${String(selected.length)} package${selected.length === 1 ? "" : "s"}.`)
7561
+ );
7562
+ }
7563
+ async function installUpdates(pm, root) {
7564
+ note(dim(`Running ${pm} install\u2026`), "Install");
7266
7565
  try {
7267
- await run(pm.name, ["install"], root, { stdio: "inherit" });
7566
+ await run(pm, ["install"], root, { stdio: "inherit" });
7268
7567
  } catch {
7269
7568
  outro(
7270
7569
  danger(
7271
- `${pm.name} install failed \u2014 package.json was updated to the new versions, but the install did not finish. Fix the error printed above, then run \`${pm.name} install\`.`
7570
+ `${pm} install failed \u2014 package.json was updated to the new versions, but the install did not finish. Fix the error printed above, then run \`${pm} install\`.`
7272
7571
  )
7273
7572
  );
7274
7573
  process.exitCode = 1;
7275
- return;
7574
+ return false;
7276
7575
  }
7277
- outro(
7278
- success(`Updated ${String(selected.length)} package${selected.length === 1 ? "" : "s"}.`)
7279
- );
7576
+ return true;
7280
7577
  }
7281
7578
 
7282
7579
  // src/cli/version-check.ts
@@ -7351,8 +7648,8 @@ function findOutdated(latest, projectVersion, cliVersion, cliIsLocal, pm) {
7351
7648
  var REGISTRY_URL = "https://registry.npmjs.org/toiljs/latest";
7352
7649
  var FETCH_TIMEOUT_MS = 2e3;
7353
7650
  function cacheFile() {
7354
- const base = process.env.XDG_CACHE_HOME ?? path9.join(os.homedir(), ".cache");
7355
- return path9.join(base, "toiljs", "update-check.json");
7651
+ const base = process.env.XDG_CACHE_HOME ?? path10.join(os.homedir(), ".cache");
7652
+ return path10.join(base, "toiljs", "update-check.json");
7356
7653
  }
7357
7654
  async function fetchLatest() {
7358
7655
  const ctrl = new AbortController();
@@ -7378,22 +7675,22 @@ async function fetchLatest() {
7378
7675
  async function resolveLatest() {
7379
7676
  const file = cacheFile();
7380
7677
  try {
7381
- const cached = parseCheckCache(fs8.readFileSync(file, "utf8"));
7678
+ const cached = parseCheckCache(fs9.readFileSync(file, "utf8"));
7382
7679
  if (cached && isCacheFresh(cached, Date.now())) return cached.latest;
7383
7680
  } catch {
7384
7681
  }
7385
7682
  const latest = await fetchLatest();
7386
7683
  try {
7387
- fs8.mkdirSync(path9.dirname(file), { recursive: true });
7388
- fs8.writeFileSync(file, JSON.stringify({ latest, checkedAt: Date.now() }));
7684
+ fs9.mkdirSync(path10.dirname(file), { recursive: true });
7685
+ fs9.writeFileSync(file, JSON.stringify({ latest, checkedAt: Date.now() }));
7389
7686
  } catch {
7390
7687
  }
7391
7688
  return latest;
7392
7689
  }
7393
7690
  function projectToiljsVersion(root) {
7394
7691
  try {
7395
- const raw = fs8.readFileSync(
7396
- path9.join(root, "node_modules", "toiljs", "package.json"),
7692
+ const raw = fs9.readFileSync(
7693
+ path10.join(root, "node_modules", "toiljs", "package.json"),
7397
7694
  "utf8"
7398
7695
  );
7399
7696
  const parsed = JSON.parse(raw);
@@ -7418,9 +7715,9 @@ async function notifyIfOutdated(rootArg) {
7418
7715
  if (env.TOILJS_NO_UPDATE_CHECK || env.NO_UPDATE_NOTIFIER || env.CI) return;
7419
7716
  const latest = await resolveLatest();
7420
7717
  if (!latest) return;
7421
- const root = path9.resolve(rootArg ?? process.cwd());
7422
- const cliDir = path9.dirname(fileURLToPath4(import.meta.url));
7423
- const cliIsLocal = cliDir.startsWith(path9.join(root, "node_modules") + path9.sep);
7718
+ const root = path10.resolve(rootArg ?? process.cwd());
7719
+ const cliDir = path10.dirname(fileURLToPath4(import.meta.url));
7720
+ const cliIsLocal = cliDir.startsWith(path10.join(root, "node_modules") + path10.sep);
7424
7721
  const rows = findOutdated(
7425
7722
  latest,
7426
7723
  projectToiljsVersion(root),
@@ -7435,62 +7732,9 @@ async function notifyIfOutdated(rootArg) {
7435
7732
  }
7436
7733
 
7437
7734
  // src/cli/test.ts
7438
- import fs9 from "node:fs/promises";
7439
- import path10 from "node:path";
7440
- import { createRequire as createRequire2 } from "node:module";
7735
+ import path11 from "node:path";
7736
+ import { createRequire as createRequire3 } from "node:module";
7441
7737
  import { spawn as spawn2 } from "node:child_process";
7442
- async function writeMissing(root, file, content) {
7443
- const target = path10.join(root, file);
7444
- await fs9.mkdir(path10.dirname(target), { recursive: true });
7445
- try {
7446
- await fs9.writeFile(target, content, { flag: "wx" });
7447
- } catch (error) {
7448
- if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") throw error;
7449
- }
7450
- }
7451
- async function ensureTesting(root) {
7452
- const pkgPath = path10.join(root, "package.json");
7453
- const raw = JSON.parse(await fs9.readFile(pkgPath, "utf8"));
7454
- if (raw === null || typeof raw !== "object" || Array.isArray(raw))
7455
- throw new Error("toiljs test: package.json must contain an object.");
7456
- const pkg = raw;
7457
- for (const field of [pkg.scripts, pkg.dependencies, pkg.devDependencies]) {
7458
- if (field !== void 0 && (field === null || typeof field !== "object" || Array.isArray(field) || Object.values(field).some((value) => typeof value !== "string")))
7459
- throw new Error("toiljs test: package scripts and dependencies must be string maps.");
7460
- }
7461
- const files = await fs9.readdir(root);
7462
- const custom = files.find((file) => /^vitest\.config\.[cm]?[jt]s$/.test(file)) ?? files.find((file) => /^vite\.config\.[cm]?[jt]s$/.test(file));
7463
- const source = custom === void 0 ? "" : await fs9.readFile(path10.join(root, custom), "utf8");
7464
- const native = custom === void 0 || source.includes("from 'toiljs/vitest'") || source.includes('from "toiljs/vitest"');
7465
- let changed = false;
7466
- let install = false;
7467
- const require2 = createRequire2(pkgPath);
7468
- for (const [name, range] of Object.entries(TEST_DEPENDENCIES)) {
7469
- if (pkg.dependencies?.[name] === void 0 && pkg.devDependencies?.[name] === void 0) {
7470
- pkg.devDependencies ??= {};
7471
- pkg.devDependencies[name] = range;
7472
- changed = true;
7473
- install = true;
7474
- }
7475
- try {
7476
- require2.resolve(name);
7477
- } catch {
7478
- install = true;
7479
- }
7480
- }
7481
- pkg.scripts ??= {};
7482
- for (const [name, script] of Object.entries(TEST_SCRIPTS)) {
7483
- if (pkg.scripts[name] !== void 0) continue;
7484
- pkg.scripts[name] = script;
7485
- changed = true;
7486
- }
7487
- if (changed) await fs9.writeFile(pkgPath, JSON.stringify(raw, null, 4) + "\n");
7488
- if (custom === void 0) {
7489
- for (const [file, content] of Object.entries(testingFiles()))
7490
- await writeMissing(root, file, content);
7491
- }
7492
- return { install, native };
7493
- }
7494
7738
  function parseTestArgs(args) {
7495
7739
  let root;
7496
7740
  let browser = false;
@@ -7512,7 +7756,7 @@ function parseTestArgs(args) {
7512
7756
  return { root, browser, all, watch, arguments: forwarded };
7513
7757
  }
7514
7758
  async function runTest(options) {
7515
- const root = path10.resolve(options.root ?? process.cwd());
7759
+ const root = path11.resolve(options.root ?? process.cwd());
7516
7760
  const setup = await ensureTesting(root);
7517
7761
  if (setup.install) {
7518
7762
  const pm = detectPackageManager2(root).name;
@@ -7522,9 +7766,9 @@ async function runTest(options) {
7522
7766
  );
7523
7767
  await run(pm, ["install"], root, { stdio: "inherit" });
7524
7768
  }
7525
- const require2 = createRequire2(path10.join(root, "package.json"));
7526
- const cli = path10.join(
7527
- path10.dirname(require2.resolve("vitest/package.json")),
7769
+ const require2 = createRequire3(path11.join(root, "package.json"));
7770
+ const cli = path11.join(
7771
+ path11.dirname(require2.resolve("vitest/package.json")),
7528
7772
  "vitest.mjs"
7529
7773
  );
7530
7774
  const args = [cli, ...options.watch ? [] : ["run"]];
@@ -7677,7 +7921,7 @@ function printHelp() {
7677
7921
  cmd("--watch", "test: watch for changes"),
7678
7922
  cmd("--server", "build: build only the server (regenerate shared/server.ts + wasm)"),
7679
7923
  cmd("--json", "doctor: machine-readable output"),
7680
- cmd("--fix", "doctor: auto-fix what it can (typed-RPC wiring)"),
7924
+ cmd("--fix", "doctor: repair testing, compiler, and server wiring"),
7681
7925
  cmd("--target <t>", "update: latest | minor | patch | newest | greatest"),
7682
7926
  cmd("-v, --version", "print the toiljs version"),
7683
7927
  "",