outfitter 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +8 -5
  2. package/dist/actions.d.ts +2 -0
  3. package/dist/actions.js +34 -0
  4. package/dist/cli.js +1 -1
  5. package/dist/commands/add.d.ts +54 -0
  6. package/dist/commands/add.js +16 -0
  7. package/dist/commands/check.d.ts +91 -0
  8. package/dist/commands/check.js +14 -0
  9. package/dist/commands/demo.d.ts +21 -0
  10. package/dist/commands/demo.js +8 -0
  11. package/dist/commands/docs-module-loader.d.ts +2 -0
  12. package/dist/commands/docs-module-loader.js +8 -0
  13. package/dist/commands/doctor.d.ts +2 -0
  14. package/dist/commands/doctor.js +13 -0
  15. package/dist/commands/init.d.ts +7 -0
  16. package/dist/commands/init.js +31 -0
  17. package/dist/commands/migrate-kit.d.ts +2 -0
  18. package/dist/commands/migrate-kit.js +15 -0
  19. package/dist/commands/repo.d.ts +3 -0
  20. package/dist/commands/repo.js +9 -0
  21. package/dist/commands/scaffold.d.ts +4 -0
  22. package/dist/commands/scaffold.js +31 -0
  23. package/dist/commands/shared-deps.d.ts +36 -0
  24. package/dist/commands/shared-deps.js +10 -0
  25. package/dist/commands/upgrade-codemods.d.ts +42 -0
  26. package/dist/commands/upgrade-codemods.js +15 -0
  27. package/dist/commands/upgrade-planner.d.ts +58 -0
  28. package/dist/commands/upgrade-planner.js +8 -0
  29. package/dist/commands/upgrade-workspace.d.ts +76 -0
  30. package/dist/commands/upgrade-workspace.js +16 -0
  31. package/dist/commands/upgrade.d.ts +214 -0
  32. package/dist/commands/upgrade.js +25 -0
  33. package/dist/create/index.d.ts +5 -0
  34. package/dist/create/index.js +29 -0
  35. package/dist/create/planner.d.ts +3 -0
  36. package/dist/create/planner.js +21 -0
  37. package/dist/create/presets.d.ts +3 -0
  38. package/dist/create/presets.js +12 -0
  39. package/dist/create/types.d.ts +2 -0
  40. package/dist/create/types.js +1 -0
  41. package/dist/engine/blocks.d.ts +3 -0
  42. package/dist/engine/blocks.js +12 -0
  43. package/dist/engine/collector.d.ts +2 -0
  44. package/dist/engine/collector.js +8 -0
  45. package/dist/engine/config.d.ts +3 -0
  46. package/dist/engine/config.js +12 -0
  47. package/dist/engine/executor.d.ts +3 -0
  48. package/dist/engine/executor.js +16 -0
  49. package/dist/engine/index.d.ts +8 -0
  50. package/dist/engine/index.js +59 -0
  51. package/dist/engine/names.d.ts +2 -0
  52. package/dist/engine/names.js +16 -0
  53. package/dist/engine/post-scaffold.d.ts +3 -0
  54. package/dist/engine/post-scaffold.js +8 -0
  55. package/dist/engine/render-plan.d.ts +7 -0
  56. package/dist/engine/render-plan.js +9 -0
  57. package/dist/engine/template.d.ts +3 -0
  58. package/dist/engine/template.js +17 -0
  59. package/dist/engine/types.d.ts +2 -0
  60. package/dist/engine/types.js +8 -0
  61. package/dist/engine/workspace.d.ts +3 -0
  62. package/dist/engine/workspace.js +13 -0
  63. package/dist/index.d.ts +10 -0
  64. package/dist/index.js +1 -1
  65. package/dist/manifest.d.ts +71 -0
  66. package/dist/manifest.js +16 -0
  67. package/dist/output-mode.d.ts +2 -0
  68. package/dist/output-mode.js +10 -0
  69. package/dist/shared/{chunk-tpwtpa74.js → chunk-k59f60cp.js} +858 -396
  70. package/dist/shared/outfitter-193jvzg4.d.ts +5 -0
  71. package/dist/shared/outfitter-1dd0k853.js +194 -0
  72. package/dist/shared/outfitter-1dvma85c.js +322 -0
  73. package/dist/shared/outfitter-1h7k8xxt.js +29 -0
  74. package/dist/shared/outfitter-2ngep1h2.d.ts +5 -0
  75. package/dist/shared/outfitter-2np85etz.js +95 -0
  76. package/dist/shared/outfitter-33w361tc.d.ts +18 -0
  77. package/dist/shared/outfitter-344t1r38.js +1 -0
  78. package/dist/shared/outfitter-3weh61w7.d.ts +25 -0
  79. package/dist/shared/outfitter-4s9meh3j.js +221 -0
  80. package/dist/shared/outfitter-66b25bj8.js +125 -0
  81. package/dist/shared/outfitter-6bkqjk86.d.ts +3 -0
  82. package/dist/shared/outfitter-79vfxt6y.js +269 -0
  83. package/dist/shared/outfitter-7ha7p61k.d.ts +6 -0
  84. package/dist/shared/outfitter-7r12fj7f.js +30 -0
  85. package/dist/shared/outfitter-8y2dfx6n.js +11 -0
  86. package/dist/shared/outfitter-9x1brcmq.js +184 -0
  87. package/dist/shared/outfitter-9zqc2njf.js +859 -0
  88. package/dist/shared/outfitter-a79xrm12.d.ts +17 -0
  89. package/dist/shared/outfitter-amc4jbs1.d.ts +50 -0
  90. package/dist/shared/outfitter-ara3djt0.js +73 -0
  91. package/dist/shared/outfitter-avhm5z6w.js +82 -0
  92. package/dist/shared/outfitter-bkwpbkr9.d.ts +63 -0
  93. package/dist/shared/outfitter-bn9c8p2e.js +204 -0
  94. package/dist/shared/outfitter-bpr28y54.js +70 -0
  95. package/dist/shared/outfitter-cwq39bv4.d.ts +48 -0
  96. package/dist/shared/outfitter-d7pq7d0k.js +196 -0
  97. package/dist/shared/outfitter-dd0btgec.d.ts +40 -0
  98. package/dist/shared/outfitter-e2zz5wv7.d.ts +51 -0
  99. package/dist/shared/outfitter-ehp18x1n.js +1 -0
  100. package/dist/shared/outfitter-gdvm5c0b.d.ts +4 -0
  101. package/dist/shared/outfitter-h1mnzzd1.d.ts +14 -0
  102. package/dist/shared/outfitter-hvsaxgcp.js +1 -0
  103. package/dist/shared/outfitter-hws10ze7.js +532 -0
  104. package/dist/shared/outfitter-j833sxws.js +61 -0
  105. package/dist/shared/outfitter-j8yc7294.d.ts +22 -0
  106. package/dist/shared/outfitter-k112c427.js +21 -0
  107. package/dist/shared/outfitter-k56rmt24.d.ts +30 -0
  108. package/dist/shared/outfitter-ksa1pp4t.d.ts +4 -0
  109. package/dist/shared/outfitter-ksyvwmb5.js +191 -0
  110. package/dist/shared/outfitter-mdt37hqm.js +4 -0
  111. package/dist/shared/outfitter-mtbpabf3.js +91 -0
  112. package/dist/shared/outfitter-mxz69pgy.js +713 -0
  113. package/dist/shared/outfitter-npemy7ta.d.ts +53 -0
  114. package/dist/shared/outfitter-npyfbdmc.d.ts +6 -0
  115. package/dist/shared/outfitter-pyy1zkfh.d.ts +133 -0
  116. package/dist/shared/outfitter-q9agarmb.js +42 -0
  117. package/dist/shared/outfitter-qfh36ddg.d.ts +66 -0
  118. package/dist/shared/outfitter-qn864k6h.js +581 -0
  119. package/dist/shared/outfitter-rdc5v5ms.js +746 -0
  120. package/dist/shared/outfitter-sgtq57qr.d.ts +5 -0
  121. package/dist/shared/outfitter-ttjr95y9.js +98 -0
  122. package/dist/shared/outfitter-vh4xgb93.js +35 -0
  123. package/dist/shared/outfitter-yvksv5qb.js +322 -0
  124. package/dist/shared/outfitter-zwyvewr1.js +36 -0
  125. package/dist/targets/index.d.ts +4 -0
  126. package/dist/targets/index.js +29 -0
  127. package/dist/targets/registry.d.ts +3 -0
  128. package/dist/targets/registry.js +28 -0
  129. package/dist/targets/types.d.ts +2 -0
  130. package/dist/targets/types.js +1 -0
  131. package/package.json +21 -14
@@ -212,12 +212,9 @@ async function printDoctorResults(result, options) {
212
212
  await output(lines, { mode: "human" });
213
213
  }
214
214
  function doctorCommand(program) {
215
- program.command("doctor").description("Validate environment and dependencies").option("--json", "Output as JSON", false).action(async (_flags, command) => {
215
+ program.command("doctor").description("Validate environment and dependencies").action(async (_flags, command) => {
216
216
  const resolvedFlags = command.optsWithGlobals();
217
217
  const outputOptions = resolvedFlags.json ? { mode: "json" } : undefined;
218
- if (resolvedFlags.json) {
219
- process.env["OUTFITTER_JSON"] = "1";
220
- }
221
218
  const result = await runDoctor({ cwd: process.cwd() });
222
219
  await printDoctorResults(result, outputOptions);
223
220
  process.exit(result.exitCode);
@@ -2088,12 +2085,11 @@ function initCommand(program) {
2088
2085
  };
2089
2086
  const resolveOutputMode = (flags) => {
2090
2087
  if (flags.json) {
2091
- process.env["OUTFITTER_JSON"] = "1";
2092
2088
  return "json";
2093
2089
  }
2094
2090
  return;
2095
2091
  };
2096
- const withCommonOptions = (command) => command.option("-n, --name <name>", "Package name (defaults to directory name)").option("-b, --bin <name>", "Binary name (defaults to project name)").option("-p, --preset <preset>", "Preset to use (minimal|cli|mcp|daemon)").option("-s, --structure <mode>", "Project structure (single|workspace)").option("--workspace-name <name>", "Workspace root package name").option("-f, --force", "Overwrite existing files", false).option("--local", "Use workspace:* for @outfitter dependencies").option("--workspace", "Alias for --local").option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("-y, --yes", "Skip prompts and use defaults", false).option("--dry-run", "Preview changes without writing files", false).option("--skip-install", "Skip bun install", false).option("--skip-git", "Skip git init and initial commit", false).option("--skip-commit", "Skip initial commit only", false).option("--install-timeout <ms>", "bun install timeout in ms").option("--json", "Output as JSON", false);
2092
+ const withCommonOptions = (command) => command.option("-n, --name <name>", "Package name (defaults to directory name)").option("-b, --bin <name>", "Binary name (defaults to project name)").option("-p, --preset <preset>", "Preset to use (minimal|cli|mcp|daemon)").option("-s, --structure <mode>", "Project structure (single|workspace)").option("--workspace-name <name>", "Workspace root package name").option("-f, --force", "Overwrite existing files", false).option("--local", "Use workspace:* for @outfitter dependencies").option("--workspace", "Alias for --local").option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("-y, --yes", "Skip prompts and use defaults", false).option("--dry-run", "Preview changes without writing files", false).option("--skip-install", "Skip bun install", false).option("--skip-git", "Skip git init and initial commit", false).option("--skip-commit", "Skip initial commit only", false).option("--install-timeout <ms>", "bun install timeout in ms");
2097
2093
  withCommonOptions(init.argument("[directory]").option("-t, --template <template>", "Template to use (deprecated)")).action(async (directory, flags, command) => {
2098
2094
  const targetDir = directory ?? process.cwd();
2099
2095
  const resolvedFlags = resolveFlags(flags, command);
@@ -2904,18 +2900,16 @@ function migrateKitCommand(program) {
2904
2900
  if (migrate.commands.some((command) => command.name() === "kit")) {
2905
2901
  return;
2906
2902
  }
2907
- migrate.command("kit [directory]").description("Migrate foundation imports and dependencies to @outfitter/kit").option("--dry-run", "Preview changes without writing files", false).option("--json", "Output result as JSON", false).action(async (directory, flags) => {
2908
- if (flags.json) {
2909
- process.env["OUTFITTER_JSON"] = "1";
2910
- }
2903
+ migrate.command("kit [directory]").description("Migrate foundation imports and dependencies to @outfitter/kit").option("--dry-run", "Preview changes without writing files", false).action(async (directory, _flags, command) => {
2904
+ const resolvedFlags = command.optsWithGlobals();
2911
2905
  const result = await runMigrateKit({
2912
2906
  ...directory ? { targetDir: directory } : {},
2913
- dryRun: Boolean(flags.dryRun)
2907
+ dryRun: Boolean(resolvedFlags.dryRun)
2914
2908
  });
2915
2909
  if (result.isErr()) {
2916
2910
  throw result.error;
2917
2911
  }
2918
- await printMigrateKitResults(result.value, flags.json ? { mode: "json" } : undefined);
2912
+ await printMigrateKitResults(result.value, resolvedFlags.json ? { mode: "json" } : undefined);
2919
2913
  });
2920
2914
  }
2921
2915
 
@@ -3395,20 +3389,21 @@ async function printScaffoldResults(result, options) {
3395
3389
  await output6(lines, { mode: "human" });
3396
3390
  }
3397
3391
  function scaffoldCommand(program) {
3398
- program.command("scaffold <target> [name]").description("Add a capability to an existing project").option("-f, --force", "Overwrite existing files", false).option("--skip-install", "Skip bun install", false).option("--dry-run", "Preview changes without executing", false).option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("--local", "Use workspace:* for @outfitter dependencies").option("--install-timeout <ms>", "bun install timeout in ms").option("--json", "Output as JSON", false).action(async (target, name, flags) => {
3399
- const mode = flags.json ? "json" : undefined;
3392
+ program.command("scaffold <target> [name]").description("Add a capability to an existing project").option("-f, --force", "Overwrite existing files", false).option("--skip-install", "Skip bun install", false).option("--dry-run", "Preview changes without executing", false).option("--with <blocks>", "Comma-separated tooling blocks to add").option("--no-tooling", "Skip default tooling blocks").option("--local", "Use workspace:* for @outfitter dependencies").option("--install-timeout <ms>", "bun install timeout in ms").action(async (target, name, _flags, command) => {
3393
+ const resolvedFlags = command.optsWithGlobals();
3394
+ const mode = resolvedFlags.json ? "json" : undefined;
3400
3395
  const outputOptions = mode ? { mode } : undefined;
3401
3396
  const result = await runScaffold({
3402
3397
  target,
3403
3398
  name,
3404
- force: Boolean(flags.force),
3405
- skipInstall: Boolean(flags.skipInstall),
3406
- dryRun: Boolean(flags.dryRun),
3407
- with: flags.with,
3408
- noTooling: flags.noTooling,
3409
- local: flags.local,
3399
+ force: Boolean(resolvedFlags.force),
3400
+ skipInstall: Boolean(resolvedFlags.skipInstall),
3401
+ dryRun: Boolean(resolvedFlags.dryRun),
3402
+ with: resolvedFlags.with,
3403
+ noTooling: resolvedFlags.noTooling,
3404
+ local: resolvedFlags.local,
3410
3405
  cwd: process.cwd(),
3411
- ...flags.installTimeout !== undefined ? { installTimeout: flags.installTimeout } : {}
3406
+ ...resolvedFlags.installTimeout !== undefined ? { installTimeout: resolvedFlags.installTimeout } : {}
3412
3407
  });
3413
3408
  if (result.isErr()) {
3414
3409
  exitWithError2(result.error, outputOptions);
@@ -3419,14 +3414,22 @@ function scaffoldCommand(program) {
3419
3414
  }
3420
3415
 
3421
3416
  // src/actions.ts
3422
- import { resolve as resolve10 } from "node:path";
3417
+ import { resolve as resolve11 } from "node:path";
3423
3418
  import { output as output10 } from "@outfitter/cli";
3424
- import { cwdPreset, verbosePreset } from "@outfitter/cli/flags";
3419
+ import { actionCliPresets } from "@outfitter/cli/actions";
3420
+ import {
3421
+ booleanFlagPreset,
3422
+ cwdPreset,
3423
+ dryRunPreset,
3424
+ forcePreset,
3425
+ interactionPreset,
3426
+ verbosePreset
3427
+ } from "@outfitter/cli/flags";
3425
3428
  import {
3426
3429
  createActionRegistry,
3427
3430
  defineAction,
3428
- InternalError as InternalError4,
3429
- Result as Result16
3431
+ InternalError as InternalError5,
3432
+ Result as Result17
3430
3433
  } from "@outfitter/contracts";
3431
3434
  import { z as z2 } from "zod";
3432
3435
 
@@ -3841,14 +3844,137 @@ async function runDemo(options) {
3841
3844
  `));
3842
3845
  }
3843
3846
 
3844
- // src/commands/update.ts
3845
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
3846
- import { join as join13, resolve as resolve9 } from "node:path";
3847
+ // src/commands/upgrade.ts
3848
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
3849
+ import { join as join14, resolve as resolve10 } from "node:path";
3847
3850
  import { output as output9 } from "@outfitter/cli";
3848
- import { InternalError as InternalError3, Result as Result15 } from "@outfitter/contracts";
3851
+ import { InternalError as InternalError4, Result as Result16 } from "@outfitter/contracts";
3849
3852
  import { createTheme as createTheme3 } from "@outfitter/tui/render";
3850
3853
 
3851
- // src/commands/update-planner.ts
3854
+ // src/commands/upgrade-codemods.ts
3855
+ import { existsSync as existsSync11 } from "node:fs";
3856
+ import { isAbsolute, join as join12, relative as relative4, resolve as resolve8 } from "node:path";
3857
+ import { InternalError as InternalError2, Result as Result14 } from "@outfitter/contracts";
3858
+ var CODEMOD_PATHS = [
3859
+ "plugins/outfitter/shared/codemods",
3860
+ "node_modules/@outfitter/kit/shared/codemods"
3861
+ ];
3862
+ function findCodemodsDir(cwd, binaryDir) {
3863
+ for (const relativePath of CODEMOD_PATHS) {
3864
+ const dir = join12(cwd, relativePath);
3865
+ if (existsSync11(dir))
3866
+ return dir;
3867
+ }
3868
+ let current = resolve8(cwd);
3869
+ const root = resolve8("/");
3870
+ while (current !== root) {
3871
+ const parent = resolve8(current, "..");
3872
+ if (parent === current)
3873
+ break;
3874
+ current = parent;
3875
+ for (const relativePath of CODEMOD_PATHS) {
3876
+ const dir = join12(current, relativePath);
3877
+ if (existsSync11(dir))
3878
+ return dir;
3879
+ }
3880
+ }
3881
+ const resolvedBinaryDir = binaryDir ?? resolve8(import.meta.dir, "../../../..");
3882
+ for (const relativePath of CODEMOD_PATHS) {
3883
+ const dir = join12(resolvedBinaryDir, relativePath);
3884
+ if (existsSync11(dir))
3885
+ return dir;
3886
+ }
3887
+ return null;
3888
+ }
3889
+ function discoverCodemods(migrationsDir, codemodsDir, shortName, fromVersion, toVersion) {
3890
+ const resolvedCodemodsDir = resolve8(codemodsDir);
3891
+ const docs = (() => {
3892
+ try {
3893
+ return readMigrationDocsWithMetadata(migrationsDir, shortName, fromVersion, toVersion);
3894
+ } catch {
3895
+ return [];
3896
+ }
3897
+ })();
3898
+ const seen = new Set;
3899
+ const codemods = [];
3900
+ for (const doc of docs) {
3901
+ if (!doc.frontmatter.changes)
3902
+ continue;
3903
+ for (const change of doc.frontmatter.changes) {
3904
+ if (!change.codemod)
3905
+ continue;
3906
+ if (seen.has(change.codemod))
3907
+ continue;
3908
+ seen.add(change.codemod);
3909
+ const absolutePath = resolveCodemodPath(resolvedCodemodsDir, change.codemod);
3910
+ if (absolutePath === null)
3911
+ continue;
3912
+ if (!existsSync11(absolutePath))
3913
+ continue;
3914
+ codemods.push({
3915
+ relativePath: change.codemod,
3916
+ absolutePath
3917
+ });
3918
+ }
3919
+ }
3920
+ return codemods;
3921
+ }
3922
+ function resolveCodemodPath(codemodsDir, relativePath) {
3923
+ if (relativePath.trim().length === 0) {
3924
+ return null;
3925
+ }
3926
+ if (isAbsolute(relativePath)) {
3927
+ return null;
3928
+ }
3929
+ const resolvedPath = resolve8(codemodsDir, relativePath);
3930
+ const relPath = relative4(codemodsDir, resolvedPath);
3931
+ if (relPath === "" || relPath.startsWith("..") || isAbsolute(relPath)) {
3932
+ return null;
3933
+ }
3934
+ return resolvedPath;
3935
+ }
3936
+ async function runCodemod(codemodPath, targetDir, dryRun) {
3937
+ let mod;
3938
+ try {
3939
+ mod = await import(codemodPath);
3940
+ } catch (error) {
3941
+ return Result14.err(InternalError2.create("Failed to load codemod", {
3942
+ codemodPath,
3943
+ error: error instanceof Error ? error.message : String(error)
3944
+ }));
3945
+ }
3946
+ if (typeof mod["transform"] !== "function") {
3947
+ return Result14.err(InternalError2.create(`Codemod has no transform export: ${codemodPath}`, {
3948
+ codemodPath
3949
+ }));
3950
+ }
3951
+ const transform = mod["transform"];
3952
+ try {
3953
+ const result = await transform({ targetDir, dryRun });
3954
+ if (!isCodemodResult(result)) {
3955
+ return Result14.err(InternalError2.create("Codemod returned invalid result shape", {
3956
+ codemodPath
3957
+ }));
3958
+ }
3959
+ return Result14.ok(result);
3960
+ } catch (error) {
3961
+ return Result14.err(InternalError2.create("Codemod execution failed", {
3962
+ codemodPath,
3963
+ error: error instanceof Error ? error.message : String(error)
3964
+ }));
3965
+ }
3966
+ }
3967
+ function isCodemodResult(value) {
3968
+ if (typeof value !== "object" || value === null)
3969
+ return false;
3970
+ const candidate = value;
3971
+ return isStringArray(candidate["changedFiles"]) && isStringArray(candidate["skippedFiles"]) && isStringArray(candidate["errors"]);
3972
+ }
3973
+ function isStringArray(value) {
3974
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
3975
+ }
3976
+
3977
+ // src/commands/upgrade-planner.ts
3852
3978
  function getMajor(version) {
3853
3979
  const parts = version.split(".");
3854
3980
  return Number.parseInt(parts[0] ?? "0", 10);
@@ -3885,7 +4011,7 @@ function classify(currentVersion, latestVersion, breakingFlag) {
3885
4011
  }
3886
4012
  return "upgradableNonBreaking";
3887
4013
  }
3888
- function analyzeUpdates(installed, latest, migrationDocs) {
4014
+ function analyzeUpgrades(installed, latest, migrationDocs) {
3889
4015
  const packages = [];
3890
4016
  for (const [name, currentVersion] of installed) {
3891
4017
  const latestInfo = latest.get(name);
@@ -3916,24 +4042,24 @@ function analyzeUpdates(installed, latest, migrationDocs) {
3916
4042
  return { packages, summary };
3917
4043
  }
3918
4044
 
3919
- // src/commands/update-workspace.ts
3920
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
3921
- import { basename as basename5, dirname as dirname7, join as join12, resolve as resolve8 } from "node:path";
3922
- import { InternalError as InternalError2, Result as Result14 } from "@outfitter/contracts";
4045
+ // src/commands/upgrade-workspace.ts
4046
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
4047
+ import { basename as basename5, dirname as dirname7, join as join13, resolve as resolve9 } from "node:path";
4048
+ import { InternalError as InternalError3, Result as Result15 } from "@outfitter/contracts";
3923
4049
  function detectWorkspaceRoot2(cwd) {
3924
- let current = resolve8(cwd);
3925
- const root = resolve8("/");
4050
+ let current = resolve9(cwd);
4051
+ const root = resolve9("/");
3926
4052
  while (true) {
3927
- if (existsSync11(join12(current, "pnpm-workspace.yaml"))) {
3928
- return Result14.ok(current);
4053
+ if (existsSync12(join13(current, "pnpm-workspace.yaml"))) {
4054
+ return Result15.ok(current);
3929
4055
  }
3930
- const pkgPath = join12(current, "package.json");
3931
- if (existsSync11(pkgPath)) {
4056
+ const pkgPath = join13(current, "package.json");
4057
+ if (existsSync12(pkgPath)) {
3932
4058
  try {
3933
4059
  const raw = readFileSync9(pkgPath, "utf-8");
3934
4060
  const pkg = JSON.parse(raw);
3935
4061
  if (hasWorkspacesField3(pkg)) {
3936
- return Result14.ok(current);
4062
+ return Result15.ok(current);
3937
4063
  }
3938
4064
  } catch {}
3939
4065
  }
@@ -3946,7 +4072,7 @@ function detectWorkspaceRoot2(cwd) {
3946
4072
  }
3947
4073
  current = parent;
3948
4074
  }
3949
- return Result14.ok(null);
4075
+ return Result15.ok(null);
3950
4076
  }
3951
4077
  function hasWorkspacesField3(pkg) {
3952
4078
  const workspaces = pkg.workspaces;
@@ -3991,10 +4117,10 @@ function normalizeWorkspacePattern2(pattern) {
3991
4117
  return `${value}/package.json`;
3992
4118
  }
3993
4119
  function collectWorkspaceManifests(rootDir) {
3994
- const resolvedRoot = resolve8(rootDir);
3995
- const rootPackageJson = join12(resolvedRoot, "package.json");
3996
- if (!existsSync11(rootPackageJson)) {
3997
- return Result14.err(InternalError2.create("No package.json found at workspace root", {
4120
+ const resolvedRoot = resolve9(rootDir);
4121
+ const rootPackageJson = join13(resolvedRoot, "package.json");
4122
+ if (!existsSync12(rootPackageJson)) {
4123
+ return Result15.err(InternalError3.create("No package.json found at workspace root", {
3998
4124
  rootDir: resolvedRoot
3999
4125
  }));
4000
4126
  }
@@ -4003,7 +4129,7 @@ function collectWorkspaceManifests(rootDir) {
4003
4129
  const raw = readFileSync9(rootPackageJson, "utf-8");
4004
4130
  pkg = JSON.parse(raw);
4005
4131
  } catch {
4006
- return Result14.err(InternalError2.create("Invalid JSON in root package.json", {
4132
+ return Result15.err(InternalError3.create("Invalid JSON in root package.json", {
4007
4133
  rootDir: resolvedRoot
4008
4134
  }));
4009
4135
  }
@@ -4014,13 +4140,13 @@ function collectWorkspaceManifests(rootDir) {
4014
4140
  continue;
4015
4141
  const glob = new Bun.Glob(pattern);
4016
4142
  for (const entry of glob.scanSync({ cwd: resolvedRoot })) {
4017
- const absolute = resolve8(resolvedRoot, entry);
4018
- if (existsSync11(absolute) && basename5(absolute) === "package.json") {
4143
+ const absolute = resolve9(resolvedRoot, entry);
4144
+ if (existsSync12(absolute) && basename5(absolute) === "package.json") {
4019
4145
  files.add(absolute);
4020
4146
  }
4021
4147
  }
4022
4148
  }
4023
- return Result14.ok(Array.from(files).sort((a, b) => a.localeCompare(b)));
4149
+ return Result15.ok(Array.from(files).sort((a, b) => a.localeCompare(b)));
4024
4150
  }
4025
4151
  function extractOutfitterDeps(manifestPath2) {
4026
4152
  let pkg;
@@ -4028,7 +4154,7 @@ function extractOutfitterDeps(manifestPath2) {
4028
4154
  const raw = readFileSync9(manifestPath2, "utf-8");
4029
4155
  pkg = JSON.parse(raw);
4030
4156
  } catch {
4031
- return Result14.err(InternalError2.create("Failed to parse package.json", {
4157
+ return Result15.err(InternalError3.create("Failed to parse package.json", {
4032
4158
  path: manifestPath2
4033
4159
  }));
4034
4160
  }
@@ -4064,10 +4190,10 @@ function extractOutfitterDeps(manifestPath2) {
4064
4190
  }
4065
4191
  packages.push({ name, version: cleaned });
4066
4192
  }
4067
- return Result14.ok(packages);
4193
+ return Result15.ok(packages);
4068
4194
  }
4069
4195
  function getInstalledPackagesFromWorkspace(rootDir) {
4070
- const resolvedRoot = resolve8(rootDir);
4196
+ const resolvedRoot = resolve9(rootDir);
4071
4197
  const wsRootResult = detectWorkspaceRoot2(resolvedRoot);
4072
4198
  if (wsRootResult.isErr())
4073
4199
  return wsRootResult;
@@ -4079,9 +4205,9 @@ function getInstalledPackagesFromWorkspace(rootDir) {
4079
4205
  return manifestsResult;
4080
4206
  manifestPaths = manifestsResult.value;
4081
4207
  } else {
4082
- const rootPkg = join12(resolvedRoot, "package.json");
4083
- if (!existsSync11(rootPkg)) {
4084
- return Result14.err(InternalError2.create("No package.json found", { cwd: resolvedRoot }));
4208
+ const rootPkg = join13(resolvedRoot, "package.json");
4209
+ if (!existsSync12(rootPkg)) {
4210
+ return Result15.err(InternalError3.create("No package.json found", { cwd: resolvedRoot }));
4085
4211
  }
4086
4212
  manifestPaths = [rootPkg];
4087
4213
  }
@@ -4131,7 +4257,7 @@ function getInstalledPackagesFromWorkspace(rootDir) {
4131
4257
  }
4132
4258
  }
4133
4259
  packages.sort((a, b) => a.name.localeCompare(b.name));
4134
- return Result14.ok({
4260
+ return Result15.ok({
4135
4261
  packages,
4136
4262
  conflicts,
4137
4263
  manifestsByPackage,
@@ -4160,7 +4286,7 @@ async function applyUpdatesToWorkspace(manifestPaths, manifestsByPackage, update
4160
4286
  try {
4161
4287
  raw = readFileSync9(manifestPath2, "utf-8");
4162
4288
  } catch {
4163
- return Result14.err(InternalError2.create("Failed to read package.json for apply", {
4289
+ return Result15.err(InternalError3.create("Failed to read package.json for apply", {
4164
4290
  path: manifestPath2
4165
4291
  }));
4166
4292
  }
@@ -4168,7 +4294,7 @@ async function applyUpdatesToWorkspace(manifestPaths, manifestsByPackage, update
4168
4294
  try {
4169
4295
  pkg = JSON.parse(raw);
4170
4296
  } catch {
4171
- return Result14.err(InternalError2.create("Invalid JSON in package.json", {
4297
+ return Result15.err(InternalError3.create("Invalid JSON in package.json", {
4172
4298
  path: manifestPath2
4173
4299
  }));
4174
4300
  }
@@ -4198,13 +4324,13 @@ async function applyUpdatesToWorkspace(manifestPaths, manifestsByPackage, update
4198
4324
  `;
4199
4325
  await Bun.write(manifestPath2, updated);
4200
4326
  } catch {
4201
- return Result14.err(InternalError2.create("Failed to write updated package.json", {
4327
+ return Result15.err(InternalError3.create("Failed to write updated package.json", {
4202
4328
  path: manifestPath2
4203
4329
  }));
4204
4330
  }
4205
4331
  }
4206
4332
  }
4207
- return Result14.ok(undefined);
4333
+ return Result15.ok(undefined);
4208
4334
  }
4209
4335
  function getVersionPrefix(specifier) {
4210
4336
  if (specifier.startsWith("workspace:")) {
@@ -4224,19 +4350,20 @@ async function runInstall(cwd) {
4224
4350
  const exitCode = await proc.exited;
4225
4351
  if (exitCode !== 0) {
4226
4352
  const stderr = await new Response(proc.stderr).text();
4227
- return Result14.err(InternalError2.create("bun install failed", {
4353
+ return Result15.err(InternalError3.create("bun install failed", {
4228
4354
  cwd,
4229
4355
  exitCode,
4230
4356
  stderr: stderr.trim()
4231
4357
  }));
4232
4358
  }
4233
4359
  } catch {
4234
- return Result14.err(InternalError2.create("Failed to run bun install", { cwd }));
4360
+ return Result15.err(InternalError3.create("Failed to run bun install", { cwd }));
4235
4361
  }
4236
- return Result14.ok(undefined);
4362
+ return Result15.ok(undefined);
4237
4363
  }
4238
4364
 
4239
- // src/commands/update.ts
4365
+ // src/commands/upgrade.ts
4366
+ var FRONTMATTER_BLOCK_REGEX = /^---\r?\n[\s\S]*?\r?\n---\r?\n*/;
4240
4367
  async function getLatestVersion(name) {
4241
4368
  try {
4242
4369
  const proc = Bun.spawn(["npm", "view", name, "version"], {
@@ -4257,28 +4384,28 @@ var MIGRATION_DOC_PATHS = [
4257
4384
  "node_modules/@outfitter/kit/shared/migrations"
4258
4385
  ];
4259
4386
  function findMigrationDocsDir(cwd, binaryDir) {
4260
- for (const relative4 of MIGRATION_DOC_PATHS) {
4261
- const dir = join13(cwd, relative4);
4262
- if (existsSync12(dir))
4387
+ for (const relative5 of MIGRATION_DOC_PATHS) {
4388
+ const dir = join14(cwd, relative5);
4389
+ if (existsSync13(dir))
4263
4390
  return dir;
4264
4391
  }
4265
- let current = resolve9(cwd);
4266
- const root = resolve9("/");
4392
+ let current = resolve10(cwd);
4393
+ const root = resolve10("/");
4267
4394
  while (current !== root) {
4268
- const parent = resolve9(current, "..");
4395
+ const parent = resolve10(current, "..");
4269
4396
  if (parent === current)
4270
4397
  break;
4271
4398
  current = parent;
4272
- for (const relative4 of MIGRATION_DOC_PATHS) {
4273
- const dir = join13(current, relative4);
4274
- if (existsSync12(dir))
4399
+ for (const relative5 of MIGRATION_DOC_PATHS) {
4400
+ const dir = join14(current, relative5);
4401
+ if (existsSync13(dir))
4275
4402
  return dir;
4276
4403
  }
4277
4404
  }
4278
- const resolvedBinaryDir = binaryDir ?? resolve9(import.meta.dir, "../../../..");
4279
- for (const relative4 of MIGRATION_DOC_PATHS) {
4280
- const dir = join13(resolvedBinaryDir, relative4);
4281
- if (existsSync12(dir))
4405
+ const resolvedBinaryDir = binaryDir ?? resolve10(import.meta.dir, "../../../..");
4406
+ for (const relative5 of MIGRATION_DOC_PATHS) {
4407
+ const dir = join14(resolvedBinaryDir, relative5);
4408
+ if (existsSync13(dir))
4282
4409
  return dir;
4283
4410
  }
4284
4411
  return null;
@@ -4296,14 +4423,14 @@ function readMigrationDocs(migrationsDir, shortName, fromVersion, toVersion) {
4296
4423
  continue;
4297
4424
  if (Bun.semver.order(docVersion, toVersion) > 0)
4298
4425
  continue;
4299
- const filePath = join13(migrationsDir, entry);
4426
+ const filePath = join14(migrationsDir, entry);
4300
4427
  let content;
4301
4428
  try {
4302
4429
  content = readFileSync10(filePath, "utf-8");
4303
4430
  } catch {
4304
4431
  continue;
4305
4432
  }
4306
- const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
4433
+ const body = content.replace(FRONTMATTER_BLOCK_REGEX, "").trim();
4307
4434
  if (body) {
4308
4435
  docs.push({ version: docVersion, content: body });
4309
4436
  }
@@ -4312,8 +4439,8 @@ function readMigrationDocs(migrationsDir, shortName, fromVersion, toVersion) {
4312
4439
  return docs.map((d) => d.content);
4313
4440
  }
4314
4441
  function readMigrationBreakingFlag(migrationsDir, shortName, version) {
4315
- const filePath = join13(migrationsDir, `outfitter-${shortName}-${version}.md`);
4316
- if (!existsSync12(filePath)) {
4442
+ const filePath = join14(migrationsDir, `outfitter-${shortName}-${version}.md`);
4443
+ if (!existsSync13(filePath)) {
4317
4444
  return;
4318
4445
  }
4319
4446
  let content;
@@ -4337,23 +4464,176 @@ function readMigrationBreakingFlag(migrationsDir, shortName, version) {
4337
4464
  return false;
4338
4465
  return;
4339
4466
  }
4467
+ var VALID_CHANGE_TYPES = new Set([
4468
+ "renamed",
4469
+ "removed",
4470
+ "signature-changed",
4471
+ "moved",
4472
+ "deprecated",
4473
+ "added"
4474
+ ]);
4475
+ function parseYamlValue(raw) {
4476
+ const trimmed = raw.trim();
4477
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
4478
+ return trimmed.slice(1, -1);
4479
+ }
4480
+ return trimmed;
4481
+ }
4482
+ function parseMigrationFrontmatter(content) {
4483
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4484
+ if (!fmMatch?.[1])
4485
+ return null;
4486
+ const fmBlock = fmMatch[1];
4487
+ const lines = fmBlock.split(/\r?\n/);
4488
+ let pkg;
4489
+ let version;
4490
+ let breaking;
4491
+ let changesStartIdx = -1;
4492
+ for (let i = 0;i < lines.length; i++) {
4493
+ const line = lines[i];
4494
+ if (line === undefined)
4495
+ continue;
4496
+ const trimmed = line.trimStart();
4497
+ if (trimmed.startsWith("package:")) {
4498
+ pkg = parseYamlValue(trimmed.slice("package:".length));
4499
+ } else if (trimmed.startsWith("version:")) {
4500
+ version = parseYamlValue(trimmed.slice("version:".length));
4501
+ } else if (trimmed.startsWith("breaking:")) {
4502
+ const val = parseYamlValue(trimmed.slice("breaking:".length));
4503
+ if (val === "true")
4504
+ breaking = true;
4505
+ else if (val === "false")
4506
+ breaking = false;
4507
+ } else if (trimmed.startsWith("changes:")) {
4508
+ changesStartIdx = i + 1;
4509
+ }
4510
+ }
4511
+ if (pkg === undefined || version === undefined || breaking === undefined) {
4512
+ return null;
4513
+ }
4514
+ let changes;
4515
+ if (changesStartIdx >= 0) {
4516
+ changes = parseChangesArray(lines, changesStartIdx);
4517
+ }
4518
+ return {
4519
+ package: pkg,
4520
+ version,
4521
+ breaking,
4522
+ ...changes !== undefined ? { changes } : {}
4523
+ };
4524
+ }
4525
+ function parseChangesArray(lines, startIdx) {
4526
+ const changes = [];
4527
+ let current = null;
4528
+ for (let i = startIdx;i < lines.length; i++) {
4529
+ const line = lines[i];
4530
+ if (line === undefined)
4531
+ continue;
4532
+ if (/^\s+-\s+/.test(line)) {
4533
+ if (current !== null) {
4534
+ const change = buildChange(current);
4535
+ if (change)
4536
+ changes.push(change);
4537
+ }
4538
+ current = {};
4539
+ const afterDash = line.replace(/^\s+-\s+/, "");
4540
+ const colonIdx = afterDash.indexOf(":");
4541
+ if (colonIdx >= 0) {
4542
+ const key = afterDash.slice(0, colonIdx).trim();
4543
+ const val = parseYamlValue(afterDash.slice(colonIdx + 1));
4544
+ current[key] = val;
4545
+ }
4546
+ } else if (current !== null && /^\s{4,}\S/.test(line)) {
4547
+ const trimmed = line.trim();
4548
+ const colonIdx = trimmed.indexOf(":");
4549
+ if (colonIdx >= 0) {
4550
+ const key = trimmed.slice(0, colonIdx).trim();
4551
+ const val = parseYamlValue(trimmed.slice(colonIdx + 1));
4552
+ current[key] = val;
4553
+ }
4554
+ } else if (/^\S/.test(line)) {
4555
+ break;
4556
+ }
4557
+ }
4558
+ if (current !== null) {
4559
+ const change = buildChange(current);
4560
+ if (change)
4561
+ changes.push(change);
4562
+ }
4563
+ return changes;
4564
+ }
4565
+ function buildChange(raw) {
4566
+ const type = raw["type"];
4567
+ if (!(type && VALID_CHANGE_TYPES.has(type))) {
4568
+ return null;
4569
+ }
4570
+ return {
4571
+ type,
4572
+ ...raw["from"] ? { from: raw["from"] } : {},
4573
+ ...raw["to"] ? { to: raw["to"] } : {},
4574
+ ...raw["path"] ? { path: raw["path"] } : {},
4575
+ ...raw["export"] ? { export: raw["export"] } : {},
4576
+ ...raw["detail"] ? { detail: raw["detail"] } : {},
4577
+ ...raw["codemod"] ? { codemod: raw["codemod"] } : {}
4578
+ };
4579
+ }
4580
+ function readMigrationDocsWithMetadata(migrationsDir, shortName, fromVersion, toVersion) {
4581
+ const glob = new Bun.Glob(`outfitter-${shortName}-*.md`);
4582
+ const versionPattern = new RegExp(`^outfitter-${shortName}-(\\d+\\.\\d+\\.\\d+)\\.md$`);
4583
+ const docs = [];
4584
+ for (const entry of glob.scanSync({ cwd: migrationsDir })) {
4585
+ const match = entry.match(versionPattern);
4586
+ if (!match?.[1])
4587
+ continue;
4588
+ const docVersion = match[1];
4589
+ if (Bun.semver.order(docVersion, fromVersion) <= 0)
4590
+ continue;
4591
+ if (Bun.semver.order(docVersion, toVersion) > 0)
4592
+ continue;
4593
+ const filePath = join14(migrationsDir, entry);
4594
+ let content;
4595
+ try {
4596
+ content = readFileSync10(filePath, "utf-8");
4597
+ } catch {
4598
+ continue;
4599
+ }
4600
+ const frontmatter = parseMigrationFrontmatter(content);
4601
+ if (!frontmatter)
4602
+ continue;
4603
+ const body = content.replace(FRONTMATTER_BLOCK_REGEX, "").trim();
4604
+ docs.push({ frontmatter, body, version: docVersion });
4605
+ }
4606
+ docs.sort((a, b) => Bun.semver.order(a.version, b.version));
4607
+ return docs;
4608
+ }
4340
4609
  function buildMigrationGuides(packages, migrationsDir) {
4341
4610
  const guides = [];
4342
4611
  for (const pkg of packages) {
4343
4612
  if (!pkg.updateAvailable || pkg.latest === null)
4344
4613
  continue;
4345
4614
  let steps = [];
4615
+ let allChanges;
4346
4616
  if (migrationsDir !== null) {
4347
4617
  const shortName = pkg.name.replace("@outfitter/", "");
4348
- const docs = readMigrationDocs(migrationsDir, shortName, pkg.current, pkg.latest);
4349
- steps = docs;
4618
+ const metaDocs = readMigrationDocsWithMetadata(migrationsDir, shortName, pkg.current, pkg.latest);
4619
+ steps = metaDocs.map((doc) => doc.body);
4620
+ const changes = [];
4621
+ for (const doc of metaDocs) {
4622
+ if (doc.frontmatter.changes) {
4623
+ changes.push(...doc.frontmatter.changes);
4624
+ }
4625
+ }
4626
+ if (changes.length > 0) {
4627
+ allChanges = changes;
4628
+ }
4350
4629
  }
4351
4630
  guides.push({
4352
4631
  packageName: pkg.name,
4353
4632
  fromVersion: pkg.current,
4354
4633
  toVersion: pkg.latest,
4355
4634
  breaking: pkg.breaking,
4356
- steps
4635
+ steps,
4636
+ ...allChanges !== undefined ? { changes: allChanges } : {}
4357
4637
  });
4358
4638
  }
4359
4639
  return guides;
@@ -4367,18 +4647,18 @@ function getVersionPrefix2(specifier) {
4367
4647
  return match?.[1] ?? "";
4368
4648
  }
4369
4649
  async function applyUpdates(cwd, updates) {
4370
- const pkgPath = join13(cwd, "package.json");
4650
+ const pkgPath = join14(cwd, "package.json");
4371
4651
  let raw;
4372
4652
  try {
4373
4653
  raw = readFileSync10(pkgPath, "utf-8");
4374
4654
  } catch {
4375
- return Result15.err(InternalError3.create("Failed to read package.json for apply", { cwd }));
4655
+ return Result16.err(InternalError4.create("Failed to read package.json for apply", { cwd }));
4376
4656
  }
4377
4657
  let pkg;
4378
4658
  try {
4379
4659
  pkg = JSON.parse(raw);
4380
4660
  } catch {
4381
- return Result15.err(InternalError3.create("Invalid JSON in package.json", { cwd }));
4661
+ return Result16.err(InternalError4.create("Invalid JSON in package.json", { cwd }));
4382
4662
  }
4383
4663
  const updateMap = new Map;
4384
4664
  for (const u of updates) {
@@ -4404,7 +4684,7 @@ async function applyUpdates(cwd, updates) {
4404
4684
  `;
4405
4685
  await Bun.write(pkgPath, updated);
4406
4686
  } catch {
4407
- return Result15.err(InternalError3.create("Failed to write updated package.json", { cwd }));
4687
+ return Result16.err(InternalError4.create("Failed to write updated package.json", { cwd }));
4408
4688
  }
4409
4689
  try {
4410
4690
  const proc = Bun.spawn(["bun", "install"], {
@@ -4415,112 +4695,212 @@ async function applyUpdates(cwd, updates) {
4415
4695
  const exitCode = await proc.exited;
4416
4696
  if (exitCode !== 0) {
4417
4697
  const stderr = await new Response(proc.stderr).text();
4418
- return Result15.err(InternalError3.create("bun install failed", {
4698
+ return Result16.err(InternalError4.create("bun install failed", {
4419
4699
  cwd,
4420
4700
  exitCode,
4421
4701
  stderr: stderr.trim()
4422
4702
  }));
4423
4703
  }
4424
4704
  } catch {
4425
- return Result15.err(InternalError3.create("Failed to run bun install", { cwd }));
4426
- }
4427
- return Result15.ok(undefined);
4428
- }
4429
- async function runUpdate(options) {
4430
- const cwd = resolve9(options.cwd);
4431
- const migrationsDir = findMigrationDocsDir(cwd);
4432
- const migrationFlagsDir = findMigrationDocsDir(cwd, cwd);
4433
- const scanResult = getInstalledPackagesFromWorkspace(cwd);
4434
- if (scanResult.isErr())
4435
- return scanResult;
4436
- const scan = scanResult.value;
4437
- const installed = scan.packages;
4438
- const installRoot = scan.workspaceRoot ?? cwd;
4439
- if (installed.length === 0) {
4440
- return Result15.ok({
4441
- packages: [],
4442
- total: 0,
4443
- updatesAvailable: 0,
4444
- hasBreaking: false,
4705
+ return Result16.err(InternalError4.create("Failed to run bun install", { cwd }));
4706
+ }
4707
+ return Result16.ok(undefined);
4708
+ }
4709
+ async function runUpgrade(options) {
4710
+ const cwd = resolve10(options.cwd);
4711
+ const startedAt = new Date;
4712
+ let workspaceRoot = null;
4713
+ const emptyResult = {
4714
+ packages: [],
4715
+ total: 0,
4716
+ updatesAvailable: 0,
4717
+ hasBreaking: false,
4718
+ applied: false,
4719
+ appliedPackages: [],
4720
+ skippedBreaking: []
4721
+ };
4722
+ const writeReport = (status, result, error) => {
4723
+ writeUpgradeReportSafely(cwd, result, {
4724
+ status,
4725
+ startedAt,
4726
+ workspaceRoot,
4727
+ options,
4728
+ ...error !== undefined ? { error } : {}
4729
+ });
4730
+ };
4731
+ try {
4732
+ const migrationsDir = findMigrationDocsDir(cwd);
4733
+ const migrationFlagsDir = findMigrationDocsDir(cwd, cwd);
4734
+ const scanResult = getInstalledPackagesFromWorkspace(cwd);
4735
+ if (scanResult.isErr()) {
4736
+ writeReport("failed", emptyResult, scanResult.error);
4737
+ return scanResult;
4738
+ }
4739
+ const scan = scanResult.value;
4740
+ workspaceRoot = scan.workspaceRoot;
4741
+ const installed = scan.packages;
4742
+ const installRoot = scan.workspaceRoot ?? cwd;
4743
+ const codemodTargetDir = scan.workspaceRoot ?? cwd;
4744
+ if (installed.length === 0) {
4745
+ writeReport("no_updates", emptyResult);
4746
+ return Result16.ok(emptyResult);
4747
+ }
4748
+ const latestVersions = new Map;
4749
+ const installedMap = new Map;
4750
+ const npmFailures = new Set;
4751
+ await Promise.all(installed.map(async (pkg) => {
4752
+ installedMap.set(pkg.name, pkg.version);
4753
+ const latest = await getLatestVersion(pkg.name);
4754
+ if (latest !== null) {
4755
+ const shortName = pkg.name.replace("@outfitter/", "");
4756
+ const docBreaking = migrationFlagsDir !== null ? readMigrationBreakingFlag(migrationFlagsDir, shortName, latest) : undefined;
4757
+ latestVersions.set(pkg.name, {
4758
+ version: latest,
4759
+ ...docBreaking !== undefined ? { breaking: docBreaking } : {}
4760
+ });
4761
+ } else {
4762
+ npmFailures.add(pkg.name);
4763
+ }
4764
+ }));
4765
+ const plan = analyzeUpgrades(installedMap, latestVersions);
4766
+ const packages = plan.packages.map((action) => ({
4767
+ name: action.name,
4768
+ current: action.currentVersion,
4769
+ latest: npmFailures.has(action.name) ? null : action.latestVersion,
4770
+ updateAvailable: action.classification === "upgradableNonBreaking" || action.classification === "upgradableBreaking",
4771
+ breaking: action.breaking
4772
+ }));
4773
+ const updatesAvailable = packages.filter((p) => p.updateAvailable).length;
4774
+ const hasBreaking = packages.some((p) => p.breaking);
4775
+ const nonBreakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableNonBreaking");
4776
+ const breakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableBreaking");
4777
+ const includeBreaking = options.all === true;
4778
+ const packagesToApply = includeBreaking ? [...nonBreakingUpgradable, ...breakingUpgradable] : nonBreakingUpgradable;
4779
+ const skippedBreaking = includeBreaking ? [] : breakingUpgradable.map((a) => a.name);
4780
+ let guidesData = options.guide === true ? buildMigrationGuides(packages, migrationsDir) : undefined;
4781
+ if (guidesData !== undefined && options.guidePackages !== undefined && options.guidePackages.length > 0) {
4782
+ const filterSet = new Set(options.guidePackages);
4783
+ guidesData = guidesData.filter((g) => filterSet.has(g.packageName));
4784
+ }
4785
+ const buildResult = (overrides = {}) => ({
4786
+ packages,
4787
+ total: packages.length,
4788
+ updatesAvailable,
4789
+ hasBreaking,
4445
4790
  applied: false,
4446
4791
  appliedPackages: [],
4447
- skippedBreaking: []
4792
+ skippedBreaking,
4793
+ ...guidesData !== undefined ? { guides: guidesData } : {},
4794
+ ...overrides
4448
4795
  });
4449
- }
4450
- const latestVersions = new Map;
4451
- const installedMap = new Map;
4452
- const npmFailures = new Set;
4453
- await Promise.all(installed.map(async (pkg) => {
4454
- installedMap.set(pkg.name, pkg.version);
4455
- const latest = await getLatestVersion(pkg.name);
4456
- if (latest !== null) {
4457
- const shortName = pkg.name.replace("@outfitter/", "");
4458
- const docBreaking = migrationFlagsDir !== null ? readMigrationBreakingFlag(migrationFlagsDir, shortName, latest) : undefined;
4459
- latestVersions.set(pkg.name, {
4460
- version: latest,
4461
- ...docBreaking !== undefined ? { breaking: docBreaking } : {}
4796
+ if (options.dryRun) {
4797
+ const result = buildResult();
4798
+ writeReport("dry_run", result);
4799
+ return Result16.ok(result);
4800
+ }
4801
+ if (packagesToApply.length === 0) {
4802
+ const result = buildResult();
4803
+ writeReport("no_updates", result);
4804
+ return Result16.ok(result);
4805
+ }
4806
+ if (options.yes !== true && options.interactive !== false) {
4807
+ const { confirmDestructive } = await import("@outfitter/tui/confirm");
4808
+ const confirmed = await confirmDestructive({
4809
+ message: `Apply ${packagesToApply.length} upgrade(s)?`,
4810
+ itemCount: packagesToApply.length,
4811
+ bypassFlag: false
4462
4812
  });
4463
- } else {
4464
- npmFailures.add(pkg.name);
4465
- }
4466
- }));
4467
- const plan = analyzeUpdates(installedMap, latestVersions);
4468
- const packages = plan.packages.map((action) => ({
4469
- name: action.name,
4470
- current: action.currentVersion,
4471
- latest: npmFailures.has(action.name) ? null : action.latestVersion,
4472
- updateAvailable: action.classification === "upgradableNonBreaking" || action.classification === "upgradableBreaking",
4473
- breaking: action.breaking
4474
- }));
4475
- const updatesAvailable = packages.filter((p) => p.updateAvailable).length;
4476
- const hasBreaking = packages.some((p) => p.breaking);
4477
- const nonBreakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableNonBreaking");
4478
- const breakingUpgradable = plan.packages.filter((a) => a.classification === "upgradableBreaking");
4479
- const includeBreaking = options.apply === true && options.breaking === true;
4480
- const packagesToApply = includeBreaking ? [...nonBreakingUpgradable, ...breakingUpgradable] : nonBreakingUpgradable;
4481
- const skippedBreaking = includeBreaking ? [] : breakingUpgradable.map((a) => a.name);
4482
- let applied = false;
4483
- const appliedPackages = [];
4484
- if (options.apply && packagesToApply.length > 0) {
4485
- if (scan.workspaceRoot !== null) {
4486
- const applyResult = await applyUpdatesToWorkspace(scan.manifestPaths, scan.manifestsByPackage, packagesToApply);
4487
- if (applyResult.isErr())
4488
- return applyResult;
4489
- const installResult = await runInstall(installRoot);
4490
- if (installResult.isErr())
4491
- return installResult;
4492
- } else {
4493
- const applyResult = await applyUpdates(cwd, packagesToApply);
4494
- if (applyResult.isErr())
4495
- return applyResult;
4813
+ if (confirmed.isErr()) {
4814
+ const result = buildResult();
4815
+ writeReport("cancelled", result);
4816
+ return Result16.ok(result);
4817
+ }
4818
+ } else if (options.interactive === false && options.yes !== true) {
4819
+ const result = buildResult();
4820
+ writeReport("skipped_non_interactive", result);
4821
+ return Result16.ok(result);
4496
4822
  }
4497
- applied = true;
4498
- appliedPackages.push(...packagesToApply.map((a) => a.name));
4499
- }
4500
- let guidesData = options.guide === true ? buildMigrationGuides(packages, migrationsDir) : undefined;
4501
- if (guidesData !== undefined && options.guidePackages !== undefined && options.guidePackages.length > 0) {
4502
- const filterSet = new Set(options.guidePackages);
4503
- guidesData = guidesData.filter((g) => filterSet.has(g.packageName));
4823
+ let applied = false;
4824
+ const appliedPackages = [];
4825
+ if (packagesToApply.length > 0) {
4826
+ if (scan.workspaceRoot !== null) {
4827
+ const applyResult = await applyUpdatesToWorkspace(scan.manifestPaths, scan.manifestsByPackage, packagesToApply);
4828
+ if (applyResult.isErr()) {
4829
+ const failureResult = buildResult();
4830
+ writeReport("failed", failureResult, applyResult.error);
4831
+ return applyResult;
4832
+ }
4833
+ const installResult = await runInstall(installRoot);
4834
+ if (installResult.isErr()) {
4835
+ const failureResult = buildResult();
4836
+ writeReport("failed", failureResult, installResult.error);
4837
+ return installResult;
4838
+ }
4839
+ } else {
4840
+ const applyResult = await applyUpdates(cwd, packagesToApply);
4841
+ if (applyResult.isErr()) {
4842
+ const failureResult = buildResult();
4843
+ writeReport("failed", failureResult, applyResult.error);
4844
+ return applyResult;
4845
+ }
4846
+ }
4847
+ applied = true;
4848
+ appliedPackages.push(...packagesToApply.map((a) => a.name));
4849
+ }
4850
+ let codemodSummary;
4851
+ if (applied && options.noCodemods !== true && migrationsDir !== null) {
4852
+ const codemodsDir = findCodemodsDir(cwd);
4853
+ if (codemodsDir !== null) {
4854
+ const allChangedFiles = [];
4855
+ const allErrors = [];
4856
+ let codemodCount = 0;
4857
+ for (const pkg of packagesToApply) {
4858
+ const shortName = pkg.name.replace("@outfitter/", "");
4859
+ const codemods = discoverCodemods(migrationsDir, codemodsDir, shortName, installedMap.get(pkg.name) ?? "0.0.0", pkg.latestVersion);
4860
+ for (const codemod of codemods) {
4861
+ const codemodResult = await runCodemod(codemod.absolutePath, codemodTargetDir, false);
4862
+ codemodCount++;
4863
+ if (codemodResult.isOk()) {
4864
+ allChangedFiles.push(...codemodResult.value.changedFiles);
4865
+ allErrors.push(...codemodResult.value.errors);
4866
+ } else {
4867
+ allErrors.push(codemodResult.error.message);
4868
+ }
4869
+ }
4870
+ }
4871
+ if (codemodCount > 0) {
4872
+ codemodSummary = {
4873
+ codemodCount,
4874
+ changedFiles: allChangedFiles,
4875
+ errors: allErrors
4876
+ };
4877
+ }
4878
+ }
4879
+ }
4880
+ const finalResult = buildResult({
4881
+ applied,
4882
+ appliedPackages,
4883
+ ...codemodSummary !== undefined ? { codemods: codemodSummary } : {}
4884
+ });
4885
+ writeReport("applied", finalResult);
4886
+ return Result16.ok(finalResult);
4887
+ } catch (error) {
4888
+ const normalizedError = error && typeof error === "object" && "category" in error && "message" in error ? error : InternalError4.create("Unexpected error in outfitter upgrade", {
4889
+ cwd,
4890
+ error: error instanceof Error ? error.message : String(error)
4891
+ });
4892
+ writeReport("failed", emptyResult, normalizedError);
4893
+ return Result16.err(normalizedError);
4504
4894
  }
4505
- return Result15.ok({
4506
- packages,
4507
- total: packages.length,
4508
- updatesAvailable,
4509
- hasBreaking,
4510
- applied,
4511
- appliedPackages,
4512
- skippedBreaking,
4513
- ...guidesData !== undefined ? { guides: guidesData } : {}
4514
- });
4515
4895
  }
4516
- async function printUpdateResults(result, options) {
4896
+ async function printUpgradeResults(result, options) {
4517
4897
  const structuredMode = resolveStructuredOutputMode(options?.mode);
4518
4898
  if (structuredMode) {
4519
4899
  await output9(result, { mode: structuredMode });
4520
4900
  return;
4521
4901
  }
4522
4902
  const theme = createTheme3();
4523
- const lines = ["", "Outfitter Update", "", "=".repeat(60)];
4903
+ const lines = ["", "Outfitter Upgrade", "", "=".repeat(60)];
4524
4904
  if (result.packages.length === 0) {
4525
4905
  lines.push("No @outfitter/* packages found in package.json.");
4526
4906
  await output9(lines, { mode: "human" });
@@ -4549,39 +4929,60 @@ async function printUpdateResults(result, options) {
4549
4929
  const breakingApplied = result.appliedPackages.filter((name) => result.packages.some((p) => p.name === name && p.breaking));
4550
4930
  const nonBreakingApplied = result.appliedPackages.filter((name) => !result.packages.some((p) => p.name === name && p.breaking));
4551
4931
  if (nonBreakingApplied.length > 0) {
4552
- lines.push(theme.success(`Applied ${nonBreakingApplied.length} non-breaking update(s):`));
4932
+ lines.push(theme.success(`Applied ${nonBreakingApplied.length} non-breaking upgrade(s):`));
4553
4933
  for (const name of nonBreakingApplied) {
4554
4934
  lines.push(` - ${name}`);
4555
4935
  }
4556
4936
  lines.push("");
4557
4937
  }
4558
4938
  if (breakingApplied.length > 0) {
4559
- lines.push(theme.error(`Applied ${breakingApplied.length} breaking update(s):`));
4939
+ lines.push(theme.error(`Applied ${breakingApplied.length} breaking upgrade(s):`));
4560
4940
  for (const name of breakingApplied) {
4561
4941
  const pkg = result.packages.find((p) => p.name === name);
4562
4942
  lines.push(` - ${name} (${pkg?.current} -> ${pkg?.latest})`);
4563
4943
  }
4564
- lines.push("", theme.muted("Review migration guides: 'outfitter update --guide'"));
4944
+ lines.push("", theme.muted("Review migration guides: 'outfitter upgrade --guide'"));
4565
4945
  lines.push("");
4566
4946
  }
4567
- } else if (options?.applied !== undefined && options.applied === false) {
4568
- if (result.updatesAvailable === 0) {
4569
- lines.push(theme.success("All packages are up to date. Nothing to apply."));
4570
- } else if (result.appliedPackages.length === 0 && result.skippedBreaking.length > 0) {
4571
- lines.push(theme.muted("No non-breaking updates to apply. All available updates contain breaking changes."));
4572
- }
4573
4947
  }
4574
- if (result.skippedBreaking.length > 0 && result.applied) {
4575
- lines.push(theme.error(`Skipped ${result.skippedBreaking.length} breaking update(s):`));
4948
+ if (result.skippedBreaking.length > 0 && options?.all !== true) {
4949
+ if (result.applied) {
4950
+ lines.push(theme.error(`Skipped ${result.skippedBreaking.length} breaking upgrade(s):`));
4951
+ } else {
4952
+ lines.push(" Excluded (breaking):");
4953
+ }
4576
4954
  for (const name of result.skippedBreaking) {
4577
- lines.push(` - ${name}`);
4955
+ const pkg = result.packages.find((p) => p.name === name);
4956
+ const codemodHint = pkg?.breaking ? "(migration guide)" : "";
4957
+ lines.push(` ${name.padEnd(24)} ${(pkg?.current ?? "").padEnd(8)} -> ${(pkg?.latest ?? "").padEnd(8)} ${codemodHint}`.trimEnd());
4958
+ }
4959
+ lines.push("", theme.muted(" Use --all to include breaking changes"));
4960
+ lines.push("");
4961
+ }
4962
+ if (result.codemods !== undefined) {
4963
+ const uniqueChangedFiles = [
4964
+ ...new Set(result.codemods.changedFiles)
4965
+ ].sort();
4966
+ lines.push(theme.info(`Ran ${result.codemods.codemodCount} codemod(s).`));
4967
+ if (uniqueChangedFiles.length > 0) {
4968
+ lines.push(theme.success(`Codemods changed ${uniqueChangedFiles.length} file(s):`));
4969
+ for (const file of uniqueChangedFiles) {
4970
+ lines.push(` - ${file}`);
4971
+ }
4972
+ }
4973
+ if (result.codemods.errors.length > 0) {
4974
+ lines.push(theme.error(`Codemod errors (${result.codemods.errors.length}):`));
4975
+ for (const error of result.codemods.errors) {
4976
+ lines.push(` - ${error}`);
4977
+ }
4578
4978
  }
4579
- lines.push("", theme.muted("Use 'outfitter update --apply --breaking' to include breaking updates."));
4580
4979
  lines.push("");
4581
4980
  }
4582
4981
  if (!result.applied) {
4583
- if (result.updatesAvailable > 0) {
4584
- lines.push(theme.muted("Run 'outfitter update --guide' for migration instructions."));
4982
+ if (options?.dryRun) {
4983
+ lines.push(theme.muted("Dry run no changes applied."));
4984
+ } else if (result.updatesAvailable > 0) {
4985
+ lines.push(theme.muted("Run 'outfitter upgrade --guide' for migration instructions."));
4585
4986
  } else {
4586
4987
  lines.push(theme.success("All packages are up to date."));
4587
4988
  }
@@ -4620,6 +5021,58 @@ async function printUpdateResults(result, options) {
4620
5021
  }
4621
5022
  await output9(lines, { mode: "human" });
4622
5023
  }
5024
+ function writeUpgradeReport(cwd, result, meta) {
5025
+ const reportsDir = join14(cwd, ".outfitter", "reports");
5026
+ mkdirSync6(reportsDir, { recursive: true });
5027
+ const finishedAtIso = new Date().toISOString();
5028
+ const errorContext = meta.error !== undefined && "context" in meta.error && meta.error.context !== undefined && typeof meta.error.context === "object" ? meta.error.context : undefined;
5029
+ const report = {
5030
+ $schema: "https://outfitter.dev/reports/upgrade/v1",
5031
+ status: meta.status,
5032
+ checkedAt: finishedAtIso,
5033
+ startedAt: meta.startedAt.toISOString(),
5034
+ finishedAt: finishedAtIso,
5035
+ cwd,
5036
+ workspaceRoot: meta.workspaceRoot,
5037
+ flags: {
5038
+ dryRun: meta.options.dryRun === true,
5039
+ yes: meta.options.yes === true,
5040
+ interactive: meta.options.interactive !== false,
5041
+ all: meta.options.all === true,
5042
+ noCodemods: meta.options.noCodemods === true,
5043
+ outputMode: meta.options.outputMode ?? null
5044
+ },
5045
+ applied: result.applied,
5046
+ summary: {
5047
+ total: result.total,
5048
+ available: result.updatesAvailable,
5049
+ breaking: result.packages.filter((p) => p.breaking).length,
5050
+ applied: result.appliedPackages.length
5051
+ },
5052
+ packages: result.packages,
5053
+ excluded: {
5054
+ breaking: result.skippedBreaking
5055
+ },
5056
+ ...result.codemods !== undefined ? { codemods: result.codemods } : {},
5057
+ ...meta.error !== undefined ? {
5058
+ error: {
5059
+ message: meta.error.message,
5060
+ category: meta.error.category,
5061
+ ...errorContext !== undefined ? { context: errorContext } : {}
5062
+ }
5063
+ } : {}
5064
+ };
5065
+ writeFileSync7(join14(reportsDir, "upgrade.json"), JSON.stringify(report, null, 2));
5066
+ }
5067
+ function writeUpgradeReportSafely(cwd, result, meta) {
5068
+ try {
5069
+ writeUpgradeReport(cwd, result, meta);
5070
+ } catch (error) {
5071
+ const reason = error instanceof Error ? error.message : String(error);
5072
+ process.stderr.write(`[outfitter upgrade] Failed to write report: ${reason}
5073
+ `);
5074
+ }
5075
+ }
4623
5076
 
4624
5077
  // src/actions.ts
4625
5078
  var outputModeSchema = z2.enum(["human", "json", "jsonl"]).default("human");
@@ -4688,6 +5141,7 @@ function resolveLocalFlag(flags) {
4688
5141
  }
4689
5142
  function resolveInitOptions(context, presetOverride) {
4690
5143
  const flags = context.flags;
5144
+ const { force, dryRun, yes } = initSharedFlags.resolve(context);
4691
5145
  const targetDir = context.args[0] ?? process.cwd();
4692
5146
  const name = resolveStringFlag(flags.name);
4693
5147
  const bin = resolveStringFlag(flags.bin);
@@ -4696,11 +5150,8 @@ function resolveInitOptions(context, presetOverride) {
4696
5150
  const structure = resolveStringFlag(flags.structure);
4697
5151
  const workspaceName = resolveStringFlag(flags.workspaceName);
4698
5152
  const local = resolveLocalFlag(flags);
4699
- const force = Boolean(flags.force);
4700
5153
  const withBlocks = resolveStringFlag(flags.with);
4701
5154
  const noTooling = resolveNoToolingFlag(flags);
4702
- const yes = Boolean(flags.yes);
4703
- const dryRun = Boolean(flags.dryRun ?? context.flags["dry-run"]);
4704
5155
  const skipInstall = Boolean(flags.skipInstall ?? context.flags["skip-install"]);
4705
5156
  const skipGit = Boolean(flags.skipGit ?? context.flags["skip-git"]);
4706
5157
  const skipCommit = Boolean(flags.skipCommit ?? context.flags["skip-commit"]);
@@ -4735,6 +5186,7 @@ function resolveInitOptions(context, presetOverride) {
4735
5186
  }
4736
5187
  function resolveScaffoldOptions(context) {
4737
5188
  const flags = context.flags;
5189
+ const { force, dryRun } = scaffoldSharedFlags.resolve(context);
4738
5190
  const outputMode = resolveOutputModeFromContext(context.flags);
4739
5191
  const noTooling = resolveNoToolingFlag(flags);
4740
5192
  const local = resolveLocalFlag(flags);
@@ -4748,9 +5200,9 @@ function resolveScaffoldOptions(context) {
4748
5200
  return {
4749
5201
  target: String(context.args[0] ?? ""),
4750
5202
  name: resolveStringFlag(context.args[1]),
4751
- force: Boolean(flags.force),
5203
+ force,
4752
5204
  skipInstall: Boolean(flags.skipInstall ?? context.flags["skip-install"]),
4753
- dryRun: Boolean(flags.dryRun ?? context.flags["dry-run"]),
5205
+ dryRun,
4754
5206
  ...local !== undefined ? { local } : {},
4755
5207
  with: resolveStringFlag(flags.with),
4756
5208
  ...noTooling !== undefined ? { noTooling } : {},
@@ -4760,11 +5212,11 @@ function resolveScaffoldOptions(context) {
4760
5212
  };
4761
5213
  }
4762
5214
  function resolveMigrateKitOptions(context) {
4763
- const flags = context.flags;
5215
+ const { dryRun } = migrateKitSharedFlags.resolve(context);
4764
5216
  const outputMode = resolveOutputModeFromContext(context.flags);
4765
5217
  return {
4766
5218
  targetDir: context.args[0] ?? process.cwd(),
4767
- dryRun: Boolean(flags.dryRun || context.flags["dry-run"]),
5219
+ dryRun,
4768
5220
  outputMode
4769
5221
  };
4770
5222
  }
@@ -4777,11 +5229,6 @@ var commonInitOptions = [
4777
5229
  flags: "-b, --bin <name>",
4778
5230
  description: "Binary name (defaults to project name)"
4779
5231
  },
4780
- {
4781
- flags: "-f, --force",
4782
- description: "Overwrite existing files",
4783
- defaultValue: false
4784
- },
4785
5232
  {
4786
5233
  flags: "--local",
4787
5234
  description: "Use workspace:* for @outfitter dependencies"
@@ -4803,12 +5250,19 @@ var templateOption = {
4803
5250
  flags: "-t, --template <template>",
4804
5251
  description: "Template to use (deprecated, use --preset)"
4805
5252
  };
5253
+ var initSharedFlags = actionCliPresets(forcePreset(), dryRunPreset(), booleanFlagPreset({
5254
+ id: "initYes",
5255
+ key: "yes",
5256
+ flags: "-y, --yes",
5257
+ description: "Skip prompts and use defaults for missing values"
5258
+ }));
4806
5259
  function createInitAction(options) {
4807
5260
  const presetOption = {
4808
5261
  flags: "-p, --preset <preset>",
4809
5262
  description: "Preset to use (minimal, cli, mcp, daemon)"
4810
5263
  };
4811
5264
  const initOptions = [...commonInitOptions];
5265
+ initOptions.push(...initSharedFlags.options);
4812
5266
  initOptions.push({
4813
5267
  flags: "-s, --structure <mode>",
4814
5268
  description: "Project structure (single|workspace)"
@@ -4817,16 +5271,6 @@ function createInitAction(options) {
4817
5271
  flags: "--workspace-name <name>",
4818
5272
  description: "Workspace root package name"
4819
5273
  });
4820
- initOptions.push({
4821
- flags: "-y, --yes",
4822
- description: "Skip prompts and use defaults for missing values",
4823
- defaultValue: false
4824
- });
4825
- initOptions.push({
4826
- flags: "--dry-run",
4827
- description: "Preview changes without writing files",
4828
- defaultValue: false
4829
- });
4830
5274
  initOptions.push({
4831
5275
  flags: "--skip-install",
4832
5276
  description: "Skip bun install",
@@ -4868,16 +5312,19 @@ function createInitAction(options) {
4868
5312
  const { outputMode, ...initInput } = input;
4869
5313
  const result = await runInit(initInput);
4870
5314
  if (result.isErr()) {
4871
- return Result16.err(new InternalError4({
5315
+ return Result17.err(new InternalError5({
4872
5316
  message: result.error.message,
4873
5317
  context: { action: options.id }
4874
5318
  }));
4875
5319
  }
4876
5320
  await printInitResults(result.value, { mode: outputMode });
4877
- return Result16.ok(result.value);
5321
+ return Result17.ok(result.value);
4878
5322
  }
4879
5323
  });
4880
5324
  }
5325
+ var scaffoldSharedFlags = actionCliPresets(forcePreset(), dryRunPreset());
5326
+ var addSharedFlags = actionCliPresets(forcePreset(), dryRunPreset());
5327
+ var migrateKitSharedFlags = actionCliPresets(dryRunPreset());
4881
5328
  var createAction = defineAction({
4882
5329
  id: "create",
4883
5330
  description: "Removed - use 'outfitter init' instead",
@@ -4889,7 +5336,7 @@ var createAction = defineAction({
4889
5336
  options: [],
4890
5337
  mapInput: () => ({})
4891
5338
  },
4892
- handler: async () => Result16.err(new InternalError4({
5339
+ handler: async () => Result17.err(new InternalError5({
4893
5340
  message: [
4894
5341
  "The 'create' command has been removed.",
4895
5342
  "",
@@ -4914,21 +5361,12 @@ var scaffoldAction = defineAction({
4914
5361
  command: "scaffold <target> [name]",
4915
5362
  description: "Add a capability (cli, mcp, daemon, lib, ...) to an existing project",
4916
5363
  options: [
4917
- {
4918
- flags: "-f, --force",
4919
- description: "Overwrite existing files",
4920
- defaultValue: false
4921
- },
5364
+ ...scaffoldSharedFlags.options,
4922
5365
  {
4923
5366
  flags: "--skip-install",
4924
5367
  description: "Skip bun install",
4925
5368
  defaultValue: false
4926
5369
  },
4927
- {
4928
- flags: "--dry-run",
4929
- description: "Preview changes without executing",
4930
- defaultValue: false
4931
- },
4932
5370
  {
4933
5371
  flags: "--with <blocks>",
4934
5372
  description: "Comma-separated tooling blocks to add"
@@ -4952,13 +5390,13 @@ var scaffoldAction = defineAction({
4952
5390
  const { outputMode, ...scaffoldInput } = input;
4953
5391
  const result = await runScaffold(scaffoldInput);
4954
5392
  if (result.isErr()) {
4955
- return Result16.err(new InternalError4({
5393
+ return Result17.err(new InternalError5({
4956
5394
  message: result.error.message,
4957
5395
  context: { action: "scaffold" }
4958
5396
  }));
4959
5397
  }
4960
5398
  await printScaffoldResults(result.value, { mode: outputMode });
4961
- return Result16.ok(result.value);
5399
+ return Result17.ok(result.value);
4962
5400
  }
4963
5401
  });
4964
5402
  var demoInputSchema = z2.object({
@@ -5004,9 +5442,9 @@ var demoAction = defineAction({
5004
5442
  if (result.exitCode !== 0) {
5005
5443
  process.exit(result.exitCode);
5006
5444
  }
5007
- return Result16.ok(result);
5445
+ return Result17.ok(result);
5008
5446
  } catch (error) {
5009
- return Result16.err(new InternalError4({
5447
+ return Result17.err(new InternalError5({
5010
5448
  message: error instanceof Error ? error.message : "Failed to run demo",
5011
5449
  context: { action: "demo" }
5012
5450
  }));
@@ -5036,7 +5474,7 @@ var doctorAction = defineAction({
5036
5474
  if (result.exitCode !== 0) {
5037
5475
  process.exit(result.exitCode);
5038
5476
  }
5039
- return Result16.ok(result);
5477
+ return Result17.ok(result);
5040
5478
  }
5041
5479
  });
5042
5480
  var addInputSchema = z2.object({
@@ -5055,24 +5493,14 @@ var addAction = defineAction({
5055
5493
  group: "add",
5056
5494
  command: "<block>",
5057
5495
  description: "Add a block from the registry (claude, biome, lefthook, bootstrap, scaffolding)",
5058
- options: [
5059
- {
5060
- flags: "-f, --force",
5061
- description: "Overwrite existing files",
5062
- defaultValue: false
5063
- },
5064
- {
5065
- flags: "--dry-run",
5066
- description: "Show what would be added without making changes",
5067
- defaultValue: false
5068
- }
5069
- ],
5496
+ options: [...addSharedFlags.options],
5070
5497
  mapInput: (context) => {
5071
5498
  const outputMode = resolveOutputModeFromContext(context.flags);
5499
+ const { force, dryRun } = addSharedFlags.resolve(context);
5072
5500
  return {
5073
5501
  block: context.args[0],
5074
- force: Boolean(context.flags["force"]),
5075
- dryRun: Boolean(context.flags["dry-run"] ?? context.flags["dryRun"]),
5502
+ force,
5503
+ dryRun,
5076
5504
  cwd: process.cwd(),
5077
5505
  outputMode
5078
5506
  };
@@ -5082,13 +5510,13 @@ var addAction = defineAction({
5082
5510
  const { outputMode, ...addInput } = input;
5083
5511
  const result = await runAdd(addInput);
5084
5512
  if (result.isErr()) {
5085
- return Result16.err(new InternalError4({
5513
+ return Result17.err(new InternalError5({
5086
5514
  message: result.error.message,
5087
5515
  context: { action: "add" }
5088
5516
  }));
5089
5517
  }
5090
5518
  await printAddResults(result.value, addInput.dryRun, { mode: outputMode });
5091
- return Result16.ok(result.value);
5519
+ return Result17.ok(result.value);
5092
5520
  }
5093
5521
  });
5094
5522
  var listBlocksAction = defineAction({
@@ -5110,7 +5538,7 @@ var listBlocksAction = defineAction({
5110
5538
  handler: async (input) => {
5111
5539
  const result = listBlocks();
5112
5540
  if (result.isErr()) {
5113
- return Result16.err(new InternalError4({
5541
+ return Result17.err(new InternalError5({
5114
5542
  message: result.error.message,
5115
5543
  context: { action: "add.list" }
5116
5544
  }));
@@ -5125,7 +5553,7 @@ var listBlocksAction = defineAction({
5125
5553
  ];
5126
5554
  await output10(lines, { mode: "human" });
5127
5555
  }
5128
- return Result16.ok({ blocks: result.value });
5556
+ return Result17.ok({ blocks: result.value });
5129
5557
  }
5130
5558
  });
5131
5559
  var checkInputSchema = z2.object({
@@ -5163,7 +5591,7 @@ var checkAction = defineAction({
5163
5591
  const outputMode = resolveOutputModeFromContext(context.flags);
5164
5592
  const { verbose } = checkVerbose.resolve(context.flags);
5165
5593
  const { cwd: rawCwd } = checkCwd.resolve(context.flags);
5166
- const cwd = resolve10(process.cwd(), rawCwd);
5594
+ const cwd = resolve11(process.cwd(), rawCwd);
5167
5595
  const block = resolveStringFlag(context.flags["block"]);
5168
5596
  return {
5169
5597
  cwd,
@@ -5179,7 +5607,7 @@ var checkAction = defineAction({
5179
5607
  const effectiveMode = ci ? "json" : outputMode;
5180
5608
  const result = await runCheck(checkInput);
5181
5609
  if (result.isErr()) {
5182
- return Result16.err(new InternalError4({
5610
+ return Result17.err(new InternalError5({
5183
5611
  message: result.error.message,
5184
5612
  context: { action: "check" }
5185
5613
  }));
@@ -5191,80 +5619,100 @@ var checkAction = defineAction({
5191
5619
  if (result.value.driftedCount > 0 || result.value.missingCount > 0) {
5192
5620
  process.exit(1);
5193
5621
  }
5194
- return Result16.ok(result.value);
5622
+ return Result17.ok(result.value);
5195
5623
  }
5196
5624
  });
5197
- var updateInputSchema = z2.object({
5625
+ var upgradeInputSchema = z2.object({
5198
5626
  cwd: z2.string(),
5199
5627
  guide: z2.boolean(),
5200
5628
  guidePackages: z2.array(z2.string()).optional(),
5201
- apply: z2.boolean(),
5202
- breaking: z2.boolean(),
5629
+ dryRun: z2.boolean(),
5630
+ yes: z2.boolean(),
5631
+ interactive: z2.boolean(),
5632
+ all: z2.boolean(),
5633
+ noCodemods: z2.boolean(),
5203
5634
  outputMode: outputModeSchema
5204
5635
  });
5205
- var updateAction = defineAction({
5206
- id: "update",
5636
+ var upgradeCwd = cwdPreset();
5637
+ var upgradeDryRun = dryRunPreset();
5638
+ var upgradeInteraction = interactionPreset();
5639
+ var upgradeAll = booleanFlagPreset({
5640
+ id: "upgradeAll",
5641
+ key: "all",
5642
+ flags: "--all",
5643
+ description: "Include breaking changes in the upgrade"
5644
+ });
5645
+ var upgradeNoCodemods = booleanFlagPreset({
5646
+ id: "upgradeNoCodemods",
5647
+ key: "noCodemods",
5648
+ flags: "--no-codemods",
5649
+ description: "Skip automatic codemod execution during upgrade",
5650
+ sources: ["noCodemods", "no-codemods"],
5651
+ negatedSources: ["codemods"]
5652
+ });
5653
+ var upgradeGuide = booleanFlagPreset({
5654
+ id: "upgradeGuide",
5655
+ key: "guide",
5656
+ flags: "--guide",
5657
+ description: "Show migration instructions for available updates. Pass package names to filter."
5658
+ });
5659
+ var upgradeFlags = actionCliPresets(upgradeCwd, upgradeDryRun, upgradeInteraction, upgradeAll, upgradeNoCodemods, upgradeGuide);
5660
+ var upgradeAction = defineAction({
5661
+ id: "upgrade",
5207
5662
  description: "Check for @outfitter/* package updates and migration guidance",
5208
5663
  surfaces: ["cli"],
5209
- input: updateInputSchema,
5664
+ input: upgradeInputSchema,
5210
5665
  cli: {
5211
- command: "update [packages...]",
5666
+ command: "upgrade [packages...]",
5212
5667
  description: "Check for @outfitter/* package updates and migration guidance",
5213
- options: [
5214
- {
5215
- flags: "--guide",
5216
- description: "Show migration instructions for available updates. Pass package names to filter.",
5217
- defaultValue: false
5218
- },
5219
- {
5220
- flags: "--apply",
5221
- description: "Apply non-breaking updates to package.json and run bun install",
5222
- defaultValue: false
5223
- },
5224
- {
5225
- flags: "--breaking",
5226
- description: "Include breaking updates when used with --apply",
5227
- defaultValue: false
5228
- },
5229
- {
5230
- flags: "--cwd <path>",
5231
- description: "Working directory (defaults to current directory)"
5232
- }
5233
- ],
5668
+ options: [...upgradeFlags.options],
5234
5669
  mapInput: (context) => {
5235
5670
  const outputMode = resolveOutputModeFromContext(context.flags);
5236
- const cwd = typeof context.flags["cwd"] === "string" ? resolve10(process.cwd(), context.flags["cwd"]) : process.cwd();
5671
+ const {
5672
+ cwd: rawCwd,
5673
+ dryRun,
5674
+ interactive,
5675
+ yes,
5676
+ all,
5677
+ noCodemods,
5678
+ guide
5679
+ } = upgradeFlags.resolve(context);
5680
+ const cwd = resolve11(process.cwd(), rawCwd);
5237
5681
  const guidePackages = context.args.length > 0 ? context.args : undefined;
5238
5682
  return {
5239
5683
  cwd,
5240
- guide: Boolean(context.flags["guide"]),
5684
+ guide,
5241
5685
  ...guidePackages !== undefined ? { guidePackages } : {},
5242
- apply: Boolean(context.flags["apply"]),
5243
- breaking: Boolean(context.flags["breaking"]),
5686
+ dryRun,
5687
+ yes,
5688
+ interactive,
5689
+ all,
5690
+ noCodemods,
5244
5691
  outputMode
5245
5692
  };
5246
5693
  }
5247
5694
  },
5248
5695
  handler: async (input) => {
5249
- const { outputMode, guidePackages, ...updateInput } = input;
5250
- const result = await runUpdate({
5251
- ...updateInput,
5696
+ const { outputMode, guidePackages, ...upgradeInput } = input;
5697
+ const result = await runUpgrade({
5698
+ ...upgradeInput,
5699
+ outputMode,
5252
5700
  ...guidePackages !== undefined ? { guidePackages } : {}
5253
5701
  });
5254
5702
  if (result.isErr()) {
5255
- return Result16.err(new InternalError4({
5703
+ return Result17.err(new InternalError5({
5256
5704
  message: result.error.message,
5257
- context: { action: "update" }
5705
+ context: { action: "upgrade" }
5258
5706
  }));
5259
5707
  }
5260
- await printUpdateResults(result.value, {
5708
+ await printUpgradeResults(result.value, {
5261
5709
  mode: outputMode,
5262
- guide: updateInput.guide,
5263
- cwd: updateInput.cwd,
5264
- applied: updateInput.apply ? result.value.applied : undefined,
5265
- breaking: updateInput.breaking
5710
+ guide: upgradeInput.guide,
5711
+ cwd: upgradeInput.cwd,
5712
+ dryRun: upgradeInput.dryRun,
5713
+ all: upgradeInput.all
5266
5714
  });
5267
- return Result16.ok(result.value);
5715
+ return Result17.ok(result.value);
5268
5716
  }
5269
5717
  });
5270
5718
  var migrateKitAction = defineAction({
@@ -5276,26 +5724,20 @@ var migrateKitAction = defineAction({
5276
5724
  group: "migrate",
5277
5725
  command: "kit [directory]",
5278
5726
  description: "Migrate foundation imports and dependencies to @outfitter/kit",
5279
- options: [
5280
- {
5281
- flags: "--dry-run",
5282
- description: "Preview changes without writing files",
5283
- defaultValue: false
5284
- }
5285
- ],
5727
+ options: [...migrateKitSharedFlags.options],
5286
5728
  mapInput: resolveMigrateKitOptions
5287
5729
  },
5288
5730
  handler: async (input) => {
5289
5731
  const { outputMode, ...migrateInput } = input;
5290
5732
  const result = await runMigrateKit(migrateInput);
5291
5733
  if (result.isErr()) {
5292
- return Result16.err(new InternalError4({
5734
+ return Result17.err(new InternalError5({
5293
5735
  message: result.error.message,
5294
5736
  context: { action: "migrate.kit" }
5295
5737
  }));
5296
5738
  }
5297
5739
  await printMigrateKitResults(result.value, { mode: outputMode });
5298
- return Result16.ok(result.value);
5740
+ return Result17.ok(result.value);
5299
5741
  }
5300
5742
  });
5301
5743
  var outfitterActions = createActionRegistry().add(createAction).add(scaffoldAction).add(createInitAction({
@@ -5319,29 +5761,40 @@ var outfitterActions = createActionRegistry().add(createAction).add(scaffoldActi
5319
5761
  description: "Create a new daemon project",
5320
5762
  command: "daemon [directory]",
5321
5763
  presetOverride: "daemon"
5322
- })).add(demoAction).add(doctorAction).add(addAction).add(listBlocksAction).add(checkAction).add(migrateKitAction).add(updateAction);
5764
+ })).add(demoAction).add(doctorAction).add(addAction).add(listBlocksAction).add(checkAction).add(migrateKitAction).add(upgradeAction);
5323
5765
 
5324
5766
  // src/commands/repo.ts
5325
- import { existsSync as existsSync14 } from "node:fs";
5767
+ import { existsSync as existsSync15 } from "node:fs";
5326
5768
  import { createRequire as createRequire3 } from "node:module";
5327
- import { dirname as dirname9, join as join15, resolve as resolve11 } from "node:path";
5769
+ import { dirname as dirname9, join as join16 } from "node:path";
5770
+ import {
5771
+ booleanFlagPreset as booleanFlagPreset2,
5772
+ composePresets,
5773
+ cwdPreset as cwdPreset2,
5774
+ stringListFlagPreset
5775
+ } from "@outfitter/cli/flags";
5776
+ import {
5777
+ resolveDocsCliOptions,
5778
+ withDocsCommonOptions,
5779
+ withDocsExportOptions
5780
+ } from "@outfitter/docs";
5328
5781
  import { Command } from "commander";
5329
5782
 
5330
5783
  // src/commands/docs-module-loader.ts
5331
- import { existsSync as existsSync13 } from "node:fs";
5784
+ import { existsSync as existsSync14 } from "node:fs";
5332
5785
  import { createRequire as createRequire2 } from "node:module";
5333
- import { dirname as dirname8, join as join14 } from "node:path";
5786
+ import { dirname as dirname8, join as join15 } from "node:path";
5334
5787
  import { pathToFileURL } from "node:url";
5335
5788
  var require2 = createRequire2(import.meta.url);
5336
5789
  function resolveDocsEntrypoint() {
5337
5790
  const packageJsonPath = require2.resolve("@outfitter/docs/package.json");
5338
5791
  const packageRoot = dirname8(packageJsonPath);
5339
- const srcEntrypoint = join14(packageRoot, "src", "index.ts");
5340
- if (existsSync13(srcEntrypoint)) {
5792
+ const srcEntrypoint = join15(packageRoot, "src", "index.ts");
5793
+ if (existsSync14(srcEntrypoint)) {
5341
5794
  return srcEntrypoint;
5342
5795
  }
5343
- const distEntrypoint = join14(packageRoot, "dist", "index.js");
5344
- if (existsSync13(distEntrypoint)) {
5796
+ const distEntrypoint = join15(packageRoot, "dist", "index.js");
5797
+ if (existsSync14(distEntrypoint)) {
5345
5798
  return distEntrypoint;
5346
5799
  }
5347
5800
  throw new Error("Unable to resolve @outfitter/docs entrypoint (expected src/index.ts or dist/index.js).");
@@ -5371,21 +5824,15 @@ function getIo(options) {
5371
5824
  `))
5372
5825
  };
5373
5826
  }
5374
- function resolveCwdOption(inputCwd) {
5375
- if (typeof inputCwd !== "string" || inputCwd.length === 0) {
5376
- return process.cwd();
5377
- }
5378
- return resolve11(process.cwd(), inputCwd);
5379
- }
5380
5827
  function resolveToolingCliEntrypoint() {
5381
5828
  const packageJsonPath = require3.resolve("@outfitter/tooling/package.json");
5382
5829
  const packageRoot = dirname9(packageJsonPath);
5383
- const srcEntrypoint = join15(packageRoot, "src", "cli", "index.ts");
5384
- if (existsSync14(srcEntrypoint)) {
5830
+ const srcEntrypoint = join16(packageRoot, "src", "cli", "index.ts");
5831
+ if (existsSync15(srcEntrypoint)) {
5385
5832
  return srcEntrypoint;
5386
5833
  }
5387
- const distEntrypoint = join15(packageRoot, "dist", "cli", "index.js");
5388
- if (existsSync14(distEntrypoint)) {
5834
+ const distEntrypoint = join16(packageRoot, "dist", "cli", "index.js");
5835
+ if (existsSync15(distEntrypoint)) {
5389
5836
  return distEntrypoint;
5390
5837
  }
5391
5838
  throw new Error("Unable to resolve @outfitter/tooling CLI entrypoint (expected dist/cli/index.js or src/cli/index.ts).");
@@ -5417,97 +5864,112 @@ function applyExitCode(code) {
5417
5864
  process.exitCode = code;
5418
5865
  }
5419
5866
  }
5867
+ function applyPresetOptions(command, preset) {
5868
+ for (const option of preset.options) {
5869
+ command.option(option.flags, option.description, option.defaultValue);
5870
+ }
5871
+ return command;
5872
+ }
5420
5873
  function addDocsCheckSubcommand(command, options) {
5421
- command.command("docs").description("Check whether assembled package docs are in sync").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").action(async (cmdOptions) => {
5422
- const code = await options.runDocsCheck({
5423
- ...cmdOptions,
5424
- ...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
5425
- }, options.io);
5874
+ const docsCheckCommand = command.command("docs").description("Check whether assembled package docs are in sync");
5875
+ withDocsCommonOptions(docsCheckCommand).action(async (cmdOptions) => {
5876
+ const code = await options.runDocsCheck(resolveDocsCliOptions(cmdOptions), options.io);
5426
5877
  applyExitCode(code);
5427
5878
  });
5428
5879
  }
5429
5880
  function addDocsSyncSubcommand(command, options) {
5430
- command.command("docs").description("Assemble package docs into docs/packages").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").action(async (cmdOptions) => {
5431
- const code = await options.runDocsSync({
5432
- ...cmdOptions,
5433
- ...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
5434
- }, options.io);
5881
+ const docsSyncCommand = command.command("docs").description("Assemble package docs into docs/packages");
5882
+ withDocsCommonOptions(docsSyncCommand).action(async (cmdOptions) => {
5883
+ const code = await options.runDocsSync(resolveDocsCliOptions(cmdOptions), options.io);
5435
5884
  applyExitCode(code);
5436
5885
  });
5437
5886
  }
5438
5887
  function addDocsExportSubcommand(command, options) {
5439
- command.command("docs").description("Export docs artifacts for packages and LLM targets").option("--cwd <path>", "Workspace root to operate in").option("--packages-dir <path>", "Packages directory relative to workspace").option("--output-dir <path>", "Output directory relative to workspace").option("--mdx-mode <mode>", "MDX handling mode: strict or lossy").option("--llms-file <path>", "llms.txt output path relative to workspace").option("--llms-full-file <path>", "llms-full.txt output path relative to workspace").option("--target <target>", "Export target: packages, llms, llms-full, all", "all").action(async (cmdOptions) => {
5440
- const code = await options.runDocsExport({
5441
- ...cmdOptions,
5442
- ...cmdOptions.cwd ? { cwd: resolveCwdOption(cmdOptions.cwd) } : {}
5443
- }, options.io);
5888
+ const docsExportCommand = command.command("docs").description("Export docs artifacts for packages and LLM targets");
5889
+ withDocsExportOptions(docsExportCommand).action(async (cmdOptions) => {
5890
+ const code = await options.runDocsExport(resolveDocsCliOptions(cmdOptions), options.io);
5444
5891
  applyExitCode(code);
5445
5892
  });
5446
5893
  }
5447
5894
  function addToolingCheckSubcommands(command, runToolingCommand) {
5448
- command.command("exports").description("Validate package.json exports match source entry points").option("--json", "Output results as JSON").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5449
- const args = [];
5450
- if (cmdOptions.json) {
5451
- args.push("--json");
5452
- }
5453
- const code = await runToolingCommand({
5454
- command: "check-exports",
5455
- args,
5456
- cwd: resolveCwdOption(cmdOptions.cwd)
5457
- });
5458
- applyExitCode(code);
5895
+ const cwdFlag = cwdPreset2();
5896
+ const jsonFlag = booleanFlagPreset2({
5897
+ id: "repo-check-json",
5898
+ key: "json",
5899
+ flags: "--json",
5900
+ description: "Output results as JSON"
5459
5901
  });
5460
- command.command("readme").description("Validate README import examples match package exports").option("--json", "Output results as JSON").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5461
- const args = [];
5462
- if (cmdOptions.json) {
5463
- args.push("--json");
5464
- }
5465
- const code = await runToolingCommand({
5466
- command: "check-readme-imports",
5467
- args,
5468
- cwd: resolveCwdOption(cmdOptions.cwd)
5469
- });
5470
- applyExitCode(code);
5902
+ const skipFlag = booleanFlagPreset2({
5903
+ id: "repo-check-skip",
5904
+ key: "skip",
5905
+ flags: "-s, --skip",
5906
+ description: "Skip changeset check"
5471
5907
  });
5472
- command.command("registry").description("Validate packages with bunup --filter are registered in bunup.config.ts").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5473
- const code = await runToolingCommand({
5474
- command: "check-bunup-registry",
5475
- args: [],
5476
- cwd: resolveCwdOption(cmdOptions.cwd)
5477
- });
5478
- applyExitCode(code);
5908
+ const pathsFlag = stringListFlagPreset({
5909
+ id: "repo-check-paths",
5910
+ key: "paths",
5911
+ flags: "--paths <paths...>",
5912
+ description: "Limit check to specific paths"
5479
5913
  });
5480
- command.command("changeset").description("Validate PRs touching package source include a changeset").option("-s, --skip", "Skip changeset check").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5481
- const args = [];
5482
- if (cmdOptions.skip) {
5483
- args.push("--skip");
5484
- }
5485
- const code = await runToolingCommand({
5486
- command: "check-changeset",
5487
- args,
5488
- cwd: resolveCwdOption(cmdOptions.cwd)
5914
+ const toolingWithCwd = composePresets(cwdFlag);
5915
+ const toolingWithJsonAndCwd = composePresets(jsonFlag, cwdFlag);
5916
+ const toolingWithSkipAndCwd = composePresets(skipFlag, cwdFlag);
5917
+ const toolingWithPathsAndCwd = composePresets(pathsFlag, cwdFlag);
5918
+ function registerToolingCheckSubcommand(config) {
5919
+ const subcommand = command.command(config.name).description(config.description);
5920
+ applyPresetOptions(subcommand, config.preset);
5921
+ subcommand.action(async (cmdOptions) => {
5922
+ const resolved = config.preset.resolve(cmdOptions);
5923
+ const cwd = resolveDocsCliOptions({ cwd: resolved.cwd }).cwd || process.cwd();
5924
+ const code = await runToolingCommand({
5925
+ command: config.toolingCommand,
5926
+ args: config.buildArgs(resolved),
5927
+ cwd
5928
+ });
5929
+ applyExitCode(code);
5489
5930
  });
5490
- applyExitCode(code);
5931
+ }
5932
+ registerToolingCheckSubcommand({
5933
+ name: "exports",
5934
+ description: "Validate package.json exports match source entry points",
5935
+ toolingCommand: "check-exports",
5936
+ preset: toolingWithJsonAndCwd,
5937
+ buildArgs: (resolved) => resolved["json"] ? ["--json"] : []
5491
5938
  });
5492
- command.command("tree").description("Assert working tree is clean (no modified or untracked files)").option("--paths <paths...>", "Limit check to specific paths").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5493
- const args = [];
5494
- if (Array.isArray(cmdOptions.paths) && cmdOptions.paths.length > 0) {
5495
- args.push("--paths", ...cmdOptions.paths);
5496
- }
5497
- const code = await runToolingCommand({
5498
- command: "check-clean-tree",
5499
- args,
5500
- cwd: resolveCwdOption(cmdOptions.cwd)
5501
- });
5502
- applyExitCode(code);
5939
+ registerToolingCheckSubcommand({
5940
+ name: "readme",
5941
+ description: "Validate README import examples match package exports",
5942
+ toolingCommand: "check-readme-imports",
5943
+ preset: toolingWithJsonAndCwd,
5944
+ buildArgs: (resolved) => resolved["json"] ? ["--json"] : []
5503
5945
  });
5504
- command.command("boundary-invocations").description("Validate root/app scripts do not execute packages/*/src entrypoints directly").option("--cwd <path>", "Workspace root to operate in").action(async (cmdOptions) => {
5505
- const code = await runToolingCommand({
5506
- command: "check-boundary-invocations",
5507
- args: [],
5508
- cwd: resolveCwdOption(cmdOptions.cwd)
5509
- });
5510
- applyExitCode(code);
5946
+ registerToolingCheckSubcommand({
5947
+ name: "registry",
5948
+ description: "Validate packages with bunup --filter are registered in bunup.config.ts",
5949
+ toolingCommand: "check-bunup-registry",
5950
+ preset: toolingWithCwd,
5951
+ buildArgs: () => []
5952
+ });
5953
+ registerToolingCheckSubcommand({
5954
+ name: "changeset",
5955
+ description: "Validate PRs touching package source include a changeset",
5956
+ toolingCommand: "check-changeset",
5957
+ preset: toolingWithSkipAndCwd,
5958
+ buildArgs: (resolved) => resolved["skip"] ? ["--skip"] : []
5959
+ });
5960
+ registerToolingCheckSubcommand({
5961
+ name: "tree",
5962
+ description: "Assert working tree is clean (no modified or untracked files)",
5963
+ toolingCommand: "check-clean-tree",
5964
+ preset: toolingWithPathsAndCwd,
5965
+ buildArgs: (resolved) => Array.isArray(resolved["paths"]) && resolved["paths"].length > 0 ? ["--paths", ...resolved["paths"]] : []
5966
+ });
5967
+ registerToolingCheckSubcommand({
5968
+ name: "boundary-invocations",
5969
+ description: "Validate root/app scripts do not execute packages/*/src entrypoints directly",
5970
+ toolingCommand: "check-boundary-invocations",
5971
+ preset: toolingWithCwd,
5972
+ buildArgs: () => []
5511
5973
  });
5512
5974
  }
5513
5975
  function createRepoCommand(options) {