nx-factory-cli 2.1.29 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import { loadConfig, resolveScope, saveConfig, } from "../config.js";
4
4
  import { detectPackageManager, pmWorkspaceProtocol } from "../exec.js";
5
5
  import { pathExists, writeJson } from "../files.js";
6
6
  import { MonorepoRootNotFoundError, requireMonorepoRoot, } from "../resolve-root.js";
7
- import { appTsConfig, packageTsConfig, rootTsConfigBase, rootTsConfigSolution, typescriptPackageJson, typescriptPresets, } from "../tsconfigs.js";
7
+ import { appTsConfig, packageTsConfig, rootTsConfigBase, typescriptPackageJson, typescriptPresets, } from "../tsconfigs.js";
8
8
  import { c, createStepRunner, printError, printSection, printSuccess, q, } from "../ui.js";
9
9
  export async function migrateCommand(options) {
10
10
  // ── Resolve monorepo root ──────────────────────────────────────────────────
@@ -51,97 +51,92 @@ export async function migrateCommand(options) {
51
51
  const scope = resolveScope(cfg);
52
52
  const detectedPm = await detectPackageManager(root);
53
53
  const pm = detectedPm ?? cfg?.pkgManager ?? "pnpm";
54
- const answers = options.yes
55
- ? {
56
- proceed: true,
57
- migrateTypescriptTooling: true,
58
- packagesToMakeInternal: [],
59
- uiVisibility: "internal",
60
- removeJsExtensions: false,
61
- cleanupBackups: false,
62
- }
63
- : await inquirer
64
- .prompt([
54
+ const defaultAnswers = {
55
+ migrateTypescriptTooling: true,
56
+ packagesToMakeInternal: [],
57
+ uiVisibility: "internal",
58
+ removeJsExtensions: false,
59
+ cleanupBackups: false,
60
+ };
61
+ let answers = { ...defaultAnswers };
62
+ if (!options.yes) {
63
+ const proceedPrompt = await inquirer.prompt([
65
64
  {
66
65
  type: "confirm",
67
66
  name: "proceed",
68
67
  message: q("Apply all migrations?", "a backup of each changed file will be written as <file>.migration-backup"),
69
68
  default: true,
70
69
  },
71
- {
72
- type: "confirm",
73
- name: "migrateTypescriptTooling",
74
- message: q("Migrate TypeScript tooling package?", "move packages/typescript to tooling/typescript and refresh tsconfig presets"),
75
- default: true,
76
- when: !status.hasToolingTypescriptPackage ||
77
- status.hasLegacyTypescriptPackage ||
78
- !status.hasToolingTypescriptBaseExtendsRoot ||
79
- !status.hasToolingTypescriptTsconfigExportAlias,
80
- },
81
- {
82
- type: "checkbox",
83
- name: "packagesToMakeInternal",
84
- message: q("Select packages to make internal", "selected packages will get private:true in package.json"),
85
- choices: status.publicPackagesEligibleForInternalization.map((pkgName) => ({ name: pkgName, value: pkgName })),
86
- when: status.publicPackagesEligibleForInternalization.length > 0,
87
- },
88
- {
89
- type: "select",
90
- name: "uiVisibility",
91
- message: q("UI package visibility", "internal = workspace only · public = published to npm"),
92
- choices: [
93
- {
94
- name: "internal — private: true, workspace only",
95
- value: "internal",
96
- },
97
- { name: "public — will be published to npm", value: "public" },
98
- ],
99
- default: "internal",
100
- when: !status.hasUiPackageVisibility,
101
- },
102
- {
103
- type: "confirm",
104
- name: "removeJsExtensions",
105
- message: q("Remove .js extensions from internal package imports?", `found ${status.internalPackagesWithJsExtensions.length} package(s) with .js extensions — safe to remove for Bundler resolution`),
106
- default: status.internalPackagesWithJsExtensions.length > 0,
107
- when: status.internalPackagesWithJsExtensions.length > 0,
108
- },
109
- {
110
- type: "confirm",
111
- name: "cleanupBackups",
112
- message: q(`Delete ${status.backupFileCount} existing .migration-backup file(s)?`, "these are from a previous migration run"),
113
- default: false,
114
- when: !options.yes && status.backupFileCount > 0,
115
- },
116
- ])
117
- .catch((err) => {
118
- if (err.message?.includes("User force closed")) {
119
- console.log(c.dim("\n Migration cancelled.\n"));
120
- process.exit(0);
121
- }
122
- throw err;
123
- });
124
- // Ensure proceed is defined with proper default
125
- const proceedAnswer = answers.proceed ?? true;
126
- if (!proceedAnswer) {
127
- console.log(c.dim("\n Migration cancelled.\n"));
128
- return;
70
+ ]);
71
+ if (!proceedPrompt.proceed) {
72
+ console.log(c.dim("\n Migration cancelled.\n"));
73
+ return;
74
+ }
75
+ answers = {
76
+ ...defaultAnswers,
77
+ ...(await inquirer.prompt([
78
+ {
79
+ type: "confirm",
80
+ name: "migrateTypescriptTooling",
81
+ message: q("Migrate TypeScript tooling package?", "move packages/typescript to tooling/typescript and refresh tsconfig presets"),
82
+ default: true,
83
+ when: !status.hasToolingTypescriptPackage ||
84
+ status.hasLegacyTypescriptPackage ||
85
+ !status.hasToolingTypescriptBaseExtendsRoot ||
86
+ !status.hasToolingTypescriptTsconfigExportAlias,
87
+ },
88
+ {
89
+ type: "checkbox",
90
+ name: "packagesToMakeInternal",
91
+ message: q("Select packages to make internal", "selected packages will get private:true in package.json"),
92
+ choices: status.publicPackagesEligibleForInternalization.map((pkgName) => ({ name: pkgName, value: pkgName })),
93
+ when: status.publicPackagesEligibleForInternalization.length > 0,
94
+ },
95
+ {
96
+ type: "select",
97
+ name: "uiVisibility",
98
+ message: q("UI package visibility", "internal = workspace only · public = published to npm"),
99
+ choices: [
100
+ {
101
+ name: "internal — private: true, workspace only",
102
+ value: "internal",
103
+ },
104
+ { name: "public — will be published to npm", value: "public" },
105
+ ],
106
+ default: "internal",
107
+ when: !status.hasUiPackageVisibility,
108
+ },
109
+ {
110
+ type: "confirm",
111
+ name: "removeJsExtensions",
112
+ message: q("Remove .js extensions from internal package imports?", `found ${status.internalPackagesWithJsExtensions.length} package(s) with .js extensions — safe to remove for Bundler resolution`),
113
+ default: status.internalPackagesWithJsExtensions.length > 0,
114
+ when: status.internalPackagesWithJsExtensions.length > 0,
115
+ },
116
+ {
117
+ type: "confirm",
118
+ name: "cleanupBackups",
119
+ message: q(`Delete ${status.backupFileCount} existing .migration-backup file(s)?`, "these are from a previous migration run"),
120
+ default: false,
121
+ when: status.backupFileCount > 0,
122
+ },
123
+ ])),
124
+ };
129
125
  }
130
- const uiVisibility = (answers.uiVisibility ??
131
- "internal");
132
- const shouldMigrateTypescriptTooling = (answers.migrateTypescriptTooling ?? true) &&
126
+ const uiVisibility = answers.uiVisibility;
127
+ const shouldMigrateTypescriptTooling = answers.migrateTypescriptTooling &&
133
128
  (!status.hasToolingTypescriptPackage ||
134
129
  status.hasLegacyTypescriptPackage ||
135
130
  !status.hasToolingTypescriptBaseExtendsRoot ||
136
131
  !status.hasToolingTypescriptTsconfigExportAlias);
137
- const packagesToMakeInternal = answers.packagesToMakeInternal ?? [];
138
- const removeJsExtensions = answers.removeJsExtensions ?? false;
139
- const cleanupBackupsNow = options.yes
140
- ? false
141
- : (answers.cleanupBackups ?? false);
132
+ const packagesToMakeInternal = answers.packagesToMakeInternal;
133
+ const removeJsExtensions = answers.removeJsExtensions;
134
+ const cleanupBackupsNow = options.yes ? false : answers.cleanupBackups;
142
135
  // ── Count steps dynamically ───────────────────────────────────────────────
143
136
  let totalSteps = 0;
144
- if (!status.hasTsConfigBase)
137
+ if (status.hasLegacyRootTsConfig)
138
+ totalSteps++;
139
+ else if (!status.hasTsConfigBase)
145
140
  totalSteps++;
146
141
  if (shouldMigrateTypescriptTooling)
147
142
  totalSteps++;
@@ -161,7 +156,6 @@ export async function migrateCommand(options) {
161
156
  totalSteps++;
162
157
  if (!status.hasConfig || !status.hasUiPackageVisibility)
163
158
  totalSteps++;
164
- totalSteps += 1; // always update solution tsconfig.json
165
159
  if (removeJsExtensions && status.internalPackagesWithJsExtensions.length > 0)
166
160
  totalSteps++;
167
161
  if (cleanupBackupsNow && status.backupFileCount > 0)
@@ -170,12 +164,42 @@ export async function migrateCommand(options) {
170
164
  totalSteps = 1;
171
165
  printSection(`${options.dryRun ? "[dry run] " : ""}Migrating workspace at ${root}`);
172
166
  const step = createStepRunner(totalSteps, options.dryRun);
173
- // ── Step 1: Write tsconfig.base.json ──────────────────────────────────────
174
- if (!status.hasTsConfigBase) {
175
- await step("Write tsconfig.base.json", async () => {
167
+ // ── Step 1: Consolidate or Write tsconfig.json ────────────────────────────
168
+ if (status.hasLegacyRootTsConfig) {
169
+ await step("Consolidate tsconfig.base.json into tsconfig.json", async () => {
170
+ const { default: fs } = await import("fs-extra");
176
171
  const tsBasePath = path.join(root, "tsconfig.base.json");
172
+ const tsConfigPath = path.join(root, "tsconfig.json");
173
+ let baseContent = {};
174
+ try {
175
+ baseContent = await fs.readJson(tsBasePath);
176
+ }
177
+ catch {
178
+ // ignore
179
+ }
177
180
  await backupIfExists(tsBasePath, options.dryRun);
178
- await writeJson(tsBasePath, rootTsConfigBase(scope));
181
+ await backupIfExists(tsConfigPath, options.dryRun);
182
+ const defaultBase = rootTsConfigBase(scope);
183
+ const merged = {
184
+ ...defaultBase,
185
+ compilerOptions: {
186
+ ...defaultBase.compilerOptions,
187
+ ...(baseContent.compilerOptions ?? {}),
188
+ paths: {
189
+ ...(defaultBase.compilerOptions?.paths ?? {}),
190
+ ...(baseContent.compilerOptions?.paths ?? {}),
191
+ },
192
+ },
193
+ };
194
+ await writeJson(tsConfigPath, merged);
195
+ await fs.remove(tsBasePath);
196
+ });
197
+ }
198
+ else if (!status.hasTsConfigBase) {
199
+ await step("Write root tsconfig.json", async () => {
200
+ const tsConfigPath = path.join(root, "tsconfig.json");
201
+ await backupIfExists(tsConfigPath, options.dryRun);
202
+ await writeJson(tsConfigPath, rootTsConfigBase(scope));
179
203
  });
180
204
  }
181
205
  // ── Step 1b: Migrate tooling/typescript package ─────────────────────────
@@ -230,14 +254,22 @@ export async function migrateCommand(options) {
230
254
  if (uiVisibility === "internal") {
231
255
  pkg.private = true;
232
256
  delete pkg.publishConfig;
257
+ delete pkg.exports;
258
+ delete pkg.main;
259
+ delete pkg.types;
260
+ delete pkg.files;
261
+ if (pkg.scripts) {
262
+ const scripts = pkg.scripts;
263
+ delete scripts.build;
264
+ delete scripts["build:watch"];
265
+ }
233
266
  }
234
267
  else {
235
268
  delete pkg.private;
236
269
  pkg.publishConfig = { access: "public" };
237
270
  pkg.files = ["dist", "styles"];
238
- }
239
- // Ensure exports field is present
240
- if (!pkg.exports) {
271
+ pkg.main = "./dist/index.js";
272
+ pkg.types = "./dist/index.d.ts";
241
273
  pkg.exports = {
242
274
  ".": { import: "./dist/index.js", types: "./dist/index.d.ts" },
243
275
  "./styles": "./styles/globals.css",
@@ -246,6 +278,12 @@ export async function migrateCommand(options) {
246
278
  types: "./dist/components/*.d.ts",
247
279
  },
248
280
  };
281
+ if (!pkg.scripts)
282
+ pkg.scripts = {};
283
+ const scripts = pkg.scripts;
284
+ scripts.build = "tsc -p tsconfig.json";
285
+ scripts["build:watch"] = "tsc -p tsconfig.json --watch";
286
+ scripts.typecheck = "tsc --noEmit";
249
287
  }
250
288
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
251
289
  }
@@ -316,29 +354,6 @@ export async function migrateCommand(options) {
316
354
  }
317
355
  });
318
356
  }
319
- // ── Step 4b: Update/create root solution tsconfig.json ────────────────────
320
- await step("Update root tsconfig.json solution file", async () => {
321
- const { default: fs } = await import("fs-extra");
322
- const pkgNames = [];
323
- const appNames = [];
324
- try {
325
- const pkgDir = path.join(root, "packages");
326
- if (await pathExists(pkgDir))
327
- pkgNames.push(...(await fs.readdir(pkgDir)));
328
- }
329
- catch {
330
- /* ok */
331
- }
332
- try {
333
- const appsDir = path.join(root, "apps");
334
- if (await pathExists(appsDir))
335
- appNames.push(...(await fs.readdir(appsDir)));
336
- }
337
- catch {
338
- /* ok */
339
- }
340
- await writeJson(path.join(root, "tsconfig.json"), rootTsConfigSolution(pkgNames, appNames));
341
- });
342
357
  // ── Step 5: Update nx-factory.config.json ─────────────────────────────────
343
358
  if (!status.hasConfig || !status.hasUiPackageVisibility) {
344
359
  await step("Update nx-factory.config.json", async () => {
@@ -403,7 +418,20 @@ async function analyseWorkspace(root) {
403
418
  const { default: fs } = await import("fs-extra");
404
419
  const cfg = await loadConfig();
405
420
  const scope = resolveScope(cfg);
406
- const hasTsConfigBase = await pathExists(path.join(root, "tsconfig.base.json"));
421
+ const hasLegacyRootTsConfig = await pathExists(path.join(root, "tsconfig.base.json"));
422
+ let hasTsConfigBase = false;
423
+ const rootTsConfigPath = path.join(root, "tsconfig.json");
424
+ if (await pathExists(rootTsConfigPath)) {
425
+ try {
426
+ const tsConfig = await fs.readJson(rootTsConfigPath);
427
+ if (tsConfig.compilerOptions && !tsConfig.references) {
428
+ hasTsConfigBase = true;
429
+ }
430
+ }
431
+ catch {
432
+ // ignore
433
+ }
434
+ }
407
435
  const hasToolingTypescriptPackage = await pathExists(path.join(root, "tooling", "typescript", "tsconfig.internal.json"));
408
436
  const hasToolingTypescriptBaseExtendsRoot = await toolingTypescriptBaseExtendsRoot(root);
409
437
  const hasToolingTypescriptTsconfigExportAlias = await toolingTypescriptPackageJsonHasTsconfigAlias(root);
@@ -413,16 +441,24 @@ async function analyseWorkspace(root) {
413
441
  let uiPackageDir = null;
414
442
  const packagesDir = path.join(root, "packages");
415
443
  if (await pathExists(packagesDir)) {
416
- const entries = await fs.readdir(packagesDir);
417
- for (const e of entries) {
418
- if (await pathExists(path.join(packagesDir, e, "components.json"))) {
419
- uiPackageDir = path.join(packagesDir, e);
420
- break;
444
+ if (cfg?.uiPackage) {
445
+ const candidate = path.join(packagesDir, cfg.uiPackage);
446
+ if (await pathExists(candidate)) {
447
+ uiPackageDir = candidate;
421
448
  }
422
449
  }
423
- // Fallback to first package if no components.json found
424
- if (!uiPackageDir && entries.length > 0) {
425
- uiPackageDir = path.join(packagesDir, entries[0]);
450
+ if (!uiPackageDir) {
451
+ const entries = await fs.readdir(packagesDir);
452
+ for (const e of entries) {
453
+ if (await pathExists(path.join(packagesDir, e, "components.json"))) {
454
+ uiPackageDir = path.join(packagesDir, e);
455
+ break;
456
+ }
457
+ }
458
+ // Fallback to first package if no components.json found
459
+ if (!uiPackageDir && entries.length > 0) {
460
+ uiPackageDir = path.join(packagesDir, entries[0]);
461
+ }
426
462
  }
427
463
  }
428
464
  // Find packages with outdated tsconfigs (missing composite / still on bundler resolution)
@@ -450,11 +486,17 @@ async function analyseWorkspace(root) {
450
486
  ? `@${scope}/typescript/tsconfig.internal.json`
451
487
  : `@${scope}/typescript/tsconfig.package.json`;
452
488
  const extendsValue = String(tsJson.extends ?? "");
453
- // Outdated if package still extends workspace base directly or doesn't match
454
- // the scoped tooling preset required by visibility.
489
+ const co = (tsJson.compilerOptions ?? {});
490
+ const paths = (co.paths ?? {});
491
+ const hasPaths = co.baseUrl === "." &&
492
+ `@${scope}/${e}` in paths &&
493
+ `@${scope}/${e}/*` in paths;
494
+ // Outdated if package still extends workspace base directly, doesn't match
495
+ // the scoped tooling preset required by visibility, or lacks custom self-referencing path aliases.
455
496
  const isOutdated = !extendsValue ||
456
497
  extendsValue.includes("tsconfig.base.json") ||
457
- extendsValue !== expectedExtends;
498
+ extendsValue !== expectedExtends ||
499
+ !hasPaths;
458
500
  if (isOutdated)
459
501
  packagesWithBadTsConfig.push(e);
460
502
  }
@@ -527,6 +569,7 @@ async function analyseWorkspace(root) {
527
569
  return {
528
570
  hasConfig: !!cfg,
529
571
  configVersion: cfg?.version ?? null,
572
+ hasLegacyRootTsConfig,
530
573
  hasTsConfigBase,
531
574
  hasToolingTypescriptPackage,
532
575
  hasToolingTypescriptBaseExtendsRoot,
@@ -546,6 +589,7 @@ async function analyseWorkspace(root) {
546
589
  }
547
590
  function isFullyMigrated(s) {
548
591
  return (s.hasConfig &&
592
+ !s.hasLegacyRootTsConfig &&
549
593
  s.hasTsConfigBase &&
550
594
  s.hasToolingTypescriptPackage &&
551
595
  s.hasToolingTypescriptBaseExtendsRoot &&
@@ -566,10 +610,16 @@ function printAnalysis(s) {
566
610
  const cross = c.yellow("✗");
567
611
  console.log();
568
612
  console.log(` ${s.hasConfig ? tick : cross} nx-factory.config.json ${s.hasConfig ? c.dim(`(v${s.configVersion ?? "unknown"})`) : c.yellow("missing")}`);
569
- console.log(` ${s.hasTsConfigBase ? tick : cross} tsconfig.base.json ${s.hasTsConfigBase ? "" : c.yellow("missing — will create")}`);
613
+ if (s.hasLegacyRootTsConfig) {
614
+ console.log(` ${cross} legacy tsconfig.base.json detected ${c.yellow("will consolidate into tsconfig.json")}`);
615
+ }
616
+ else {
617
+ console.log(` ${s.hasTsConfigBase ? tick : cross} root tsconfig.json base configuration ${s.hasTsConfigBase ? "" : c.yellow("missing — will create")}`);
618
+ }
570
619
  console.log(` ${s.hasToolingTypescriptPackage ? tick : cross} tooling/typescript presets ${s.hasToolingTypescriptPackage ? "" : c.yellow("missing — will migrate")}`);
571
620
  if (s.hasToolingTypescriptPackage) {
572
- console.log(` ${s.hasToolingTypescriptBaseExtendsRoot ? tick : cross} tooling/typescript/tsconfig.base.json extends ../../tsconfig.base.json ${s.hasToolingTypescriptBaseExtendsRoot ? "" : c.yellow("missing — will refresh presets")}`);
621
+ const expectedFile = s.hasLegacyRootTsConfig ? "tsconfig.base.json" : "tsconfig.json";
622
+ console.log(` ${s.hasToolingTypescriptBaseExtendsRoot ? tick : cross} tooling/typescript/tsconfig.base.json extends ../../${expectedFile} ${s.hasToolingTypescriptBaseExtendsRoot ? "" : c.yellow("missing — will refresh presets")}`);
573
623
  console.log(` ${s.hasToolingTypescriptTsconfigExportAlias ? tick : cross} tooling/typescript/package.json exports ./tsconfig.json ${s.hasToolingTypescriptTsconfigExportAlias ? "" : c.yellow("missing — will refresh package.json")}`);
574
624
  }
575
625
  if (s.hasLegacyTypescriptPackage) {
@@ -633,7 +683,9 @@ async function migrateTypescriptToolingPackage(root, scope) {
633
683
  }
634
684
  await fs.ensureDir(toolingDir);
635
685
  await writeJson(path.join(toolingDir, "package.json"), typescriptPackageJson(scope));
636
- const presets = typescriptPresets();
686
+ const hasBase = await pathExists(path.join(root, "tsconfig.base.json"));
687
+ const rootTsConfigName = hasBase ? "tsconfig.base.json" : "tsconfig.json";
688
+ const presets = typescriptPresets(rootTsConfigName);
637
689
  for (const [filename, content] of Object.entries(presets)) {
638
690
  await writeJson(path.join(toolingDir, filename), content);
639
691
  }
@@ -664,7 +716,9 @@ async function toolingTypescriptBaseExtendsRoot(root) {
664
716
  return false;
665
717
  try {
666
718
  const tsBase = (await fs.readJson(tsBasePath));
667
- return tsBase.extends === "../../tsconfig.base.json";
719
+ const hasBase = await pathExists(path.join(root, "tsconfig.base.json"));
720
+ const expectedExtends = hasBase ? "../../tsconfig.base.json" : "../../tsconfig.json";
721
+ return tsBase.extends === expectedExtends;
668
722
  }
669
723
  catch {
670
724
  return false;
@@ -831,6 +885,15 @@ async function makePackagesInternal(root, packageNames, dryRun) {
831
885
  const pkgJson = (await fs.readJson(pkgPath));
832
886
  pkgJson.private = true;
833
887
  delete pkgJson.publishConfig;
888
+ delete pkgJson.exports;
889
+ delete pkgJson.main;
890
+ delete pkgJson.types;
891
+ delete pkgJson.files;
892
+ if (pkgJson.scripts) {
893
+ const scripts = pkgJson.scripts;
894
+ delete scripts.build;
895
+ delete scripts["build:watch"];
896
+ }
834
897
  await fs.writeJson(pkgPath, pkgJson, { spaces: 2 });
835
898
  }
836
899
  }
@@ -842,9 +905,15 @@ async function removeExportsFromPackages(root, packageNames, dryRun) {
842
905
  continue;
843
906
  await backupIfExists(pkgPath, dryRun);
844
907
  const pkgJson = (await fs.readJson(pkgPath));
845
- if (pkgJson.exports === undefined)
846
- continue;
847
908
  delete pkgJson.exports;
909
+ delete pkgJson.main;
910
+ delete pkgJson.types;
911
+ delete pkgJson.files;
912
+ if (pkgJson.scripts) {
913
+ const scripts = pkgJson.scripts;
914
+ delete scripts.build;
915
+ delete scripts["build:watch"];
916
+ }
848
917
  await fs.writeJson(pkgPath, pkgJson, { spaces: 2 });
849
918
  }
850
919
  }
@@ -972,8 +1041,11 @@ async function countBackupFiles(root) {
972
1041
  async function walk(dir) {
973
1042
  const entries = await fs.readdir(dir, { withFileTypes: true });
974
1043
  for (const entry of entries) {
975
- if (entry.name === "node_modules" || entry.name === ".git")
1044
+ if (entry.name === "node_modules" ||
1045
+ entry.name === ".git" ||
1046
+ entry.name === ".migration-backups") {
976
1047
  continue;
1048
+ }
977
1049
  const full = path.join(dir, entry.name);
978
1050
  if (entry.isDirectory()) {
979
1051
  await walk(full);
@@ -984,6 +1056,11 @@ async function countBackupFiles(root) {
984
1056
  }
985
1057
  }
986
1058
  await walk(root);
1059
+ // Also count the .migration-backups directory if it exists
1060
+ const migrationBackupsDir = path.join(root, ".migration-backups");
1061
+ if (await pathExists(migrationBackupsDir)) {
1062
+ count++;
1063
+ }
987
1064
  return count;
988
1065
  }
989
1066
  /**
@@ -997,8 +1074,11 @@ export async function cleanupMigrationBackups(root, dryRun) {
997
1074
  try {
998
1075
  const entries = await fs.readdir(dir, { withFileTypes: true });
999
1076
  for (const entry of entries) {
1000
- if (entry.name === "node_modules" || entry.name === ".git")
1077
+ if (entry.name === "node_modules" ||
1078
+ entry.name === ".git" ||
1079
+ entry.name === ".migration-backups") {
1001
1080
  continue;
1081
+ }
1002
1082
  const full = path.join(dir, entry.name);
1003
1083
  if (entry.isDirectory()) {
1004
1084
  try {
@@ -1029,6 +1109,19 @@ export async function cleanupMigrationBackups(root, dryRun) {
1029
1109
  }
1030
1110
  }
1031
1111
  await walk(root);
1112
+ // Also delete the .migration-backups directory itself
1113
+ const migrationBackupsDir = path.join(root, ".migration-backups");
1114
+ if (await pathExists(migrationBackupsDir)) {
1115
+ deleted++;
1116
+ if (!dryRun) {
1117
+ try {
1118
+ await fs.remove(migrationBackupsDir);
1119
+ }
1120
+ catch {
1121
+ deleted--;
1122
+ }
1123
+ }
1124
+ }
1032
1125
  return deleted;
1033
1126
  }
1034
1127
  //# sourceMappingURL=migrate.js.map