@mnci/cli 4.6.2 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,6 +81,7 @@ consumers point at it.
81
81
  mnci new my-repo # create a monorepo (prompts scope + registry)
82
82
  mnci new my-repo --yes --registry npm --scope @my
83
83
  mnci new my-repo --yes --registry npm --scope @my --nx-cloud # opt in to Nx Cloud
84
+ mnci new --into . # ...or bootstrap into a clone that already exists
84
85
 
85
86
  cd my-repo
86
87
  mnci add react-app web # @nx/react (Vite + Jest)
@@ -613,6 +614,23 @@ the editor. It also makes `space-before-function-paren` enforceable for the
613
614
  first time: every Prettier-compatible formatter, oxfmt included, rewrites
614
615
  `function f (a)` back to `function f(a)`.
615
616
 
617
+ **`npm run lint` checks one thing `npm run format` does not.** `lint` is
618
+ `nx run-many -t lint`; `format` is a bare `eslint . --fix --cache`. The
619
+ `@nx/dependency-checks` rule needs the Nx project graph, and outside a target it
620
+ prints `No cached ProjectGraph is available. The rule will be skipped.` So a
621
+ dependency problem shows up in `lint` and never in `format`, your editor, or a
622
+ pre-commit hook.
623
+
624
+ That asymmetry is now a safety property rather than a hazard. The rule is
625
+ **fixable**, and `format` passes `--fix`; when the graph was warm — which it is
626
+ after any `nx` command in the same workspace — a `format` run could and did
627
+ rewrite `package.json`. `@mnci/eslint-config` turns off the two checks whose
628
+ fixers do that (`checkObsoleteDependencies`, `checkVersionMismatches`), so
629
+ neither path is destructive, and the skip means `format` cannot reach a manifest
630
+ at all. Warm the graph deliberately (`npx nx show projects`) if you want the
631
+ rule evaluated in `format` too; mnci does not, because it would make every
632
+ format run pay for a graph computation to enforce what `lint` already gates.
633
+
616
634
  **Upgrading an older workspace.** `mnci upgrade` deletes every config a previous
617
635
  version could have written for a second tool — `.prettierrc`, `.prettierrc.json`,
618
636
  `.prettierrc.mjs`, `.prettierignore`, `.oxfmtrc.json`, `oxlint.config.ts` — and
@@ -623,6 +641,47 @@ formatter from the **project's** dependencies, so a stale declaration is enough
623
641
  to reformat on save against an opinion nothing checks. `mnci doctor` reports a
624
642
  workspace that has not been upgraded yet.
625
643
 
644
+ ## `--into`: bootstrapping into a repository that already exists
645
+
646
+ `create-nx-workspace <name>` creates the directory itself and exits with
647
+ `DIRECTORY_EXISTS` when one is already there. That rules out the most common
648
+ way a repository actually starts: the host creates it, you clone it, and the
649
+ clone holds a `.git` directory, a README and a licence. Doing it by hand means
650
+ generating into a temp parent, copying everything except `.git` and
651
+ `node_modules` across, and reinstalling — four steps, each of which can quietly
652
+ lose a file.
653
+
654
+ `mnci new --into <dir>` is that, done once and tested:
655
+
656
+ ```sh
657
+ git clone git@github.com:me/my-repo.git
658
+ cd my-repo
659
+ mnci new --into .
660
+ ```
661
+
662
+ The workspace name defaults to the directory's name, because the directory is
663
+ already named and retyping it is a way to get the two out of step; pass a name
664
+ argument to override it.
665
+
666
+ What it does with the files that are already there is the whole of the risk, so
667
+ every case is decided in advance:
668
+
669
+ | Already in the directory | What happens |
670
+ | --- | --- |
671
+ | `.git` | Never touched. Keeping it is the point. |
672
+ | `README.md`, `LICENSE*` | **Kept.** The generator's README is boilerplate; yours is usually the only hand-written file in the repository. |
673
+ | `.gitignore` | **Merged.** Your lines stay, and the generated ones (`.nx/cache`, `dist`, `out-tsc`, …) are appended under a labelled heading. Lines already present are not repeated, so it is idempotent. |
674
+ | Anything else the new workspace also writes | **Refused**, naming every collision, before a single file is written. |
675
+ | Anything the new workspace does not write | Left alone. |
676
+
677
+ Two checks, not one. A directory holding `package.json`, `nx.json`,
678
+ `tsconfig.base.json`, `node_modules`, `apps/`, `libs/` or `packages/` is
679
+ rejected **before** anything is generated — it is a project already, and the
680
+ answer there is `mnci upgrade`, not `mnci new`. The full collision check needs
681
+ the generated tree, so it runs afterwards, but still before the first write:
682
+ a refusal leaves the target byte-identical to how it was found, and the staging
683
+ copy is discarded.
684
+
626
685
  ## Layout convention = release scoping
627
686
 
628
687
  | Directory | Contents | Released? |
package/dist/cli.js CHANGED
@@ -147,7 +147,7 @@ function runCapture(command, arguments_, cwd) {
147
147
  return { status: result.status ?? 1, stdout: result.stdout ?? "" };
148
148
  }
149
149
  async function runCaptureAsync(command, arguments_, cwd) {
150
- return await new Promise((resolve) => {
150
+ return await new Promise((resolve2) => {
151
151
  const child = (0, import_cross_spawn.default)(command, arguments_, {
152
152
  cwd,
153
153
  timeout: CAPTURE_TIMEOUT_MS,
@@ -159,10 +159,10 @@ async function runCaptureAsync(command, arguments_, cwd) {
159
159
  stdout += chunk;
160
160
  });
161
161
  child.on("error", () => {
162
- resolve({ status: 1, stdout: "" });
162
+ resolve2({ status: 1, stdout: "" });
163
163
  });
164
164
  child.on("close", (code) => {
165
- resolve({ status: code ?? 1, stdout });
165
+ resolve2({ status: code ?? 1, stdout });
166
166
  });
167
167
  });
168
168
  }
@@ -1558,7 +1558,58 @@ updates:
1558
1558
  }
1559
1559
  return config;
1560
1560
  }
1561
- var NX_SCAFFOLDING_TO_REMOVE = [".prettierrc", ".prettierrc.json", ".vscode"];
1561
+ var NX_SCAFFOLDING_TO_REMOVE = [
1562
+ ".prettierrc",
1563
+ ".prettierrc.json",
1564
+ ".vscode",
1565
+ // See LOCAL_REGISTRY_SCAFFOLDING: the config half of a publishing mechanism
1566
+ // this workspace does not use.
1567
+ ".verdaccio",
1568
+ // The AI-agent set, in full. See the remarks above: a partial delete leaves
1569
+ // every `nx` command printing an "outdated configuration" nag.
1570
+ ".agents",
1571
+ ".claude",
1572
+ ".codex",
1573
+ ".cursor",
1574
+ ".gemini",
1575
+ ".opencode",
1576
+ ".github/agents",
1577
+ ".github/prompts",
1578
+ ".github/skills",
1579
+ "AGENTS.md",
1580
+ "CLAUDE.md",
1581
+ "opencode.json"
1582
+ ];
1583
+ function withoutKey(record, key) {
1584
+ return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key));
1585
+ }
1586
+ var LOCAL_REGISTRY_SCAFFOLDING = {
1587
+ /** The config directory, removed with the rest of {@link NX_SCAFFOLDING_TO_REMOVE}. */
1588
+ directory: ".verdaccio",
1589
+ /** The root devDependency the target runs. */
1590
+ devDependency: "verdaccio",
1591
+ /** The root Nx target wired to both of the above. */
1592
+ rootTarget: "local-registry"
1593
+ };
1594
+ function removeLocalRegistryScaffolding(workspaceRoot) {
1595
+ (0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, LOCAL_REGISTRY_SCAFFOLDING.directory), {
1596
+ recursive: true,
1597
+ force: true
1598
+ });
1599
+ const manifestPath = (0, import_node_path2.join)(workspaceRoot, "package.json");
1600
+ if (!fileExists(manifestPath)) return;
1601
+ const manifest = readJson(manifestPath);
1602
+ if (manifest.devDependencies?.[LOCAL_REGISTRY_SCAFFOLDING.devDependency] !== void 0) {
1603
+ delete manifest.devDependencies[LOCAL_REGISTRY_SCAFFOLDING.devDependency];
1604
+ if (Object.keys(manifest.devDependencies).length === 0) delete manifest.devDependencies;
1605
+ }
1606
+ if (manifest.nx?.targets?.[LOCAL_REGISTRY_SCAFFOLDING.rootTarget] !== void 0) {
1607
+ delete manifest.nx.targets[LOCAL_REGISTRY_SCAFFOLDING.rootTarget];
1608
+ if (Object.keys(manifest.nx.targets).length === 0) delete manifest.nx.targets;
1609
+ if (Object.keys(manifest.nx).length === 0) delete manifest.nx;
1610
+ }
1611
+ writeFileEnsured(manifestPath, toJson(manifest));
1612
+ }
1562
1613
  function removeNxScaffolding(workspaceRoot) {
1563
1614
  for (const entry of NX_SCAFFOLDING_TO_REMOVE) {
1564
1615
  (0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, entry), { recursive: true, force: true });
@@ -1621,12 +1672,15 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
1621
1672
  ...rootScripts()
1622
1673
  };
1623
1674
  const existingDevDeps = manifest.devDependencies;
1624
- const devDeps = withoutRetiredFormatterDependencies({
1625
- ...existingDevDeps,
1626
- ...TS_COMPILER_DEPENDENCIES,
1627
- // The preset pins `nx` itself; the ESLint plugins must match it exactly.
1628
- ...eslintToolchainDependencies(existingDevDeps?.nx ?? "latest")
1629
- });
1675
+ const devDeps = withoutKey(
1676
+ withoutRetiredFormatterDependencies({
1677
+ ...existingDevDeps,
1678
+ ...TS_COMPILER_DEPENDENCIES,
1679
+ // The preset pins `nx` itself; the ESLint plugins must match it exactly.
1680
+ ...eslintToolchainDependencies(existingDevDeps?.nx ?? "latest")
1681
+ }),
1682
+ LOCAL_REGISTRY_SCAFFOLDING.devDependency
1683
+ );
1630
1684
  const overrides = {
1631
1685
  ...manifest.overrides,
1632
1686
  ...ESLINT_PEER_OVERRIDES,
@@ -1638,11 +1692,15 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
1638
1692
  ...reactExpressPeerOverride(manifest)
1639
1693
  };
1640
1694
  const existingNx = manifest.nx;
1695
+ const existingTargets = withoutKey(
1696
+ existingNx?.targets ?? {},
1697
+ LOCAL_REGISTRY_SCAFFOLDING.rootTarget
1698
+ );
1641
1699
  const nx = {
1642
1700
  ...existingNx,
1643
1701
  includedScripts: existingNx?.includedScripts ?? [],
1644
1702
  targets: {
1645
- ...existingNx?.targets,
1703
+ ...existingTargets,
1646
1704
  lint: ROOT_LINT_TARGET
1647
1705
  }
1648
1706
  };
@@ -2937,6 +2995,7 @@ async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
2937
2995
  repairDeclarationSpecifiers(projectRoot);
2938
2996
  writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner);
2939
2997
  removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
2998
+ removeLocalRegistryScaffolding(workspaceRoot);
2940
2999
  registerProjectCommands(workspaceRoot, name, { build: true });
2941
3000
  }
2942
3001
  );
@@ -3283,6 +3342,7 @@ async function addReactLib(workspaceRoot, name, options, kindProvided, stack) {
3283
3342
  repairDeclarationSpecifiers(projectRoot);
3284
3343
  writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner);
3285
3344
  removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
3345
+ removeLocalRegistryScaffolding(workspaceRoot);
3286
3346
  registerProjectCommands(workspaceRoot, name, { build: true });
3287
3347
  }
3288
3348
  );
@@ -4838,9 +4898,86 @@ function runDoctor(workspaceRoot) {
4838
4898
  process.exitCode = 1;
4839
4899
  }
4840
4900
 
4841
- // src/workspace-creation/create-workspace.use-case.ts
4901
+ // src/workspace-creation/adopt-directory.use-case.ts
4842
4902
  var import_node_fs13 = require("fs");
4843
4903
  var import_node_path20 = require("path");
4904
+ var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
4905
+ var EXISTING_WINS = /* @__PURE__ */ new Set(["README.md", "LICENSE", "LICENSE.md", "LICENSE.txt"]);
4906
+ var PREFLIGHT_BLOCKERS = [
4907
+ "package.json",
4908
+ "nx.json",
4909
+ "tsconfig.base.json",
4910
+ "node_modules",
4911
+ "apps",
4912
+ "libs",
4913
+ "packages"
4914
+ ];
4915
+ function assertAdoptableDirectory(target) {
4916
+ if (!(0, import_node_fs13.existsSync)(target)) {
4917
+ throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
4918
+ }
4919
+ if (!(0, import_node_fs13.statSync)(target).isDirectory()) {
4920
+ throw new Error(`--into '${target}' is not a directory.`);
4921
+ }
4922
+ const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs13.existsSync)((0, import_node_path20.join)(target, entry)));
4923
+ if (blockers.length > 0) {
4924
+ throw new Error(
4925
+ `--into '${target}' already holds ${blockers.join(", ")}, so it is a project already. Use \`mnci upgrade\` to re-apply the overlay to an existing workspace, or point --into at an empty checkout.`
4926
+ );
4927
+ }
4928
+ }
4929
+ function mergeGitignore(generatedFile, targetFile) {
4930
+ const existing = (0, import_node_fs13.readFileSync)(targetFile, "utf8");
4931
+ const present = new Set(existing.split("\n").map((line) => line.trim()));
4932
+ const additions = (0, import_node_fs13.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
4933
+ const trimmed = line.trim();
4934
+ return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
4935
+ });
4936
+ if (additions.length === 0) return false;
4937
+ const separator = existing.endsWith("\n") ? "" : "\n";
4938
+ (0, import_node_fs13.writeFileSync)(
4939
+ targetFile,
4940
+ `${existing}${separator}
4941
+ # Added by mnci (Nx workspace)
4942
+ ${additions.join("\n")}
4943
+ `
4944
+ );
4945
+ return true;
4946
+ }
4947
+ function adoptGeneratedWorkspace(generatedRoot, target) {
4948
+ const entries = (0, import_node_fs13.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
4949
+ const collisions = entries.filter(
4950
+ (entry) => (0, import_node_fs13.existsSync)((0, import_node_path20.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
4951
+ );
4952
+ if (collisions.length > 0) {
4953
+ throw new Error(
4954
+ `--into '${target}' already contains ${collisions.join(", ")}, which the new workspace also writes. Move those aside and run it again; nothing has been changed.`
4955
+ );
4956
+ }
4957
+ const result = { copied: [], kept: [], mergedGitignore: false };
4958
+ for (const entry of entries) {
4959
+ const from = (0, import_node_path20.join)(generatedRoot, entry);
4960
+ const to = (0, import_node_path20.join)(target, entry);
4961
+ if (entry === ".gitignore" && (0, import_node_fs13.existsSync)(to)) {
4962
+ result.mergedGitignore = mergeGitignore(from, to);
4963
+ result.kept.push(entry);
4964
+ continue;
4965
+ }
4966
+ if ((0, import_node_fs13.existsSync)(to)) {
4967
+ result.kept.push(entry);
4968
+ continue;
4969
+ }
4970
+ (0, import_node_fs13.cpSync)(from, to, { recursive: true });
4971
+ result.copied.push(entry);
4972
+ }
4973
+ (0, import_node_fs13.rmSync)(generatedRoot, { recursive: true, force: true });
4974
+ return result;
4975
+ }
4976
+
4977
+ // src/workspace-creation/create-workspace.use-case.ts
4978
+ var import_node_fs14 = require("fs");
4979
+ var import_node_os = require("os");
4980
+ var import_node_path21 = require("path");
4844
4981
  function nxCloudProviderValue(ci) {
4845
4982
  return ci === "azure" ? "azure" : "github";
4846
4983
  }
@@ -4876,7 +5013,9 @@ async function resolveRegistry(options) {
4876
5013
  return await promptRegistry();
4877
5014
  }
4878
5015
  async function runNew(name, options) {
4879
- const workspaceName = name ?? await promptText("Workspace name");
5016
+ const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path21.resolve)(process.cwd(), options.into);
5017
+ if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
5018
+ const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path21.basename)(adoptTarget));
4880
5019
  assertValidProjectName(workspaceName, "Workspace name");
4881
5020
  const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
4882
5021
  const registry = await resolveRegistry(options);
@@ -4888,6 +5027,7 @@ async function runNew(name, options) {
4888
5027
  const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
4889
5028
  const stack = await resolveStack(options);
4890
5029
  const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
5030
+ const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs14.mkdtempSync)((0, import_node_path21.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
4891
5031
  logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
4892
5032
  runNpx(
4893
5033
  [
@@ -4899,9 +5039,17 @@ async function runNew(name, options) {
4899
5039
  nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
4900
5040
  "--no-interactive"
4901
5041
  ],
4902
- process.cwd()
5042
+ stagingParent
4903
5043
  );
4904
- const workspaceRoot = (0, import_node_path20.join)(process.cwd(), workspaceName);
5044
+ let workspaceRoot = (0, import_node_path21.join)(stagingParent, workspaceName);
5045
+ if (adoptTarget !== void 0) {
5046
+ logger.step(`Adopting ${adoptTarget} (keeping its .git)`);
5047
+ const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget);
5048
+ for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`);
5049
+ if (adoption.mergedGitignore) logger.detail("merged the generated .gitignore into the existing one");
5050
+ (0, import_node_fs14.rmSync)(stagingParent, { recursive: true, force: true });
5051
+ workspaceRoot = adoptTarget;
5052
+ }
4905
5053
  logger.step(
4906
5054
  "Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
4907
5055
  );
@@ -4910,8 +5058,8 @@ async function runNew(name, options) {
4910
5058
  { workspaceName, scope, registry, agent, variableGroup, ci, stack },
4911
5059
  logger.detail
4912
5060
  );
4913
- (0, import_node_fs13.rmSync)((0, import_node_path20.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
4914
- (0, import_node_fs13.rmSync)((0, import_node_path20.join)(workspaceRoot, "package-lock.json"), { force: true });
5061
+ (0, import_node_fs14.rmSync)((0, import_node_path21.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
5062
+ (0, import_node_fs14.rmSync)((0, import_node_path21.join)(workspaceRoot, "package-lock.json"), { force: true });
4915
5063
  logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
4916
5064
  const installStatus = runShell(
4917
5065
  "npm",
@@ -4925,7 +5073,7 @@ async function runNew(name, options) {
4925
5073
  logger.step("Formatting the workspace (eslint --fix, JavaScript Standard Style)");
4926
5074
  runFormatter(workspaceRoot);
4927
5075
  logger.success("Done. Next steps:");
4928
- logger.info(` cd ${workspaceName}`);
5076
+ if (adoptTarget === void 0) logger.info(` cd ${workspaceName}`);
4929
5077
  logger.info(" mnci add react-app web # or: react-lib, react-internal-lib, node-app,");
4930
5078
  logger.info(" # node-function-app, npm-lib, internal-lib,");
4931
5079
  logger.info(
@@ -4941,12 +5089,12 @@ async function runNew(name, options) {
4941
5089
  }
4942
5090
 
4943
5091
  // src/workspace-creation/interactive-wizard.use-case.ts
4944
- var import_node_path22 = require("path");
5092
+ var import_node_path23 = require("path");
4945
5093
  var import_prompts4 = require("@inquirer/prompts");
4946
5094
 
4947
5095
  // src/workspace-upgrade/upgrade-workspace.use-case.ts
4948
- var import_node_fs14 = require("fs");
4949
- var import_node_path21 = require("path");
5096
+ var import_node_fs15 = require("fs");
5097
+ var import_node_path22 = require("path");
4950
5098
  function resolveRegistry2(options, persisted) {
4951
5099
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
4952
5100
  const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
@@ -4975,7 +5123,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
4975
5123
  return persisted.workspaceName;
4976
5124
  }
4977
5125
  try {
4978
- const existing = (0, import_node_fs14.readdirSync)(workspaceRoot).find(
5126
+ const existing = (0, import_node_fs15.readdirSync)(workspaceRoot).find(
4979
5127
  (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
4980
5128
  );
4981
5129
  if (existing) {
@@ -4983,7 +5131,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
4983
5131
  }
4984
5132
  } catch {
4985
5133
  }
4986
- return (0, import_node_path21.basename)(workspaceRoot);
5134
+ return (0, import_node_path22.basename)(workspaceRoot);
4987
5135
  }
4988
5136
  function resolveOverlayOptions(workspaceRoot, options, persisted) {
4989
5137
  const scope = options.scope ?? persisted.scope;
@@ -5021,7 +5169,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
5021
5169
  };
5022
5170
  }
5023
5171
  function runUpgrade(workspaceRoot, options) {
5024
- if (!fileExists((0, import_node_path21.join)(workspaceRoot, "nx.json"))) {
5172
+ if (!fileExists((0, import_node_path22.join)(workspaceRoot, "nx.json"))) {
5025
5173
  throw new Error(
5026
5174
  `No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
5027
5175
  );
@@ -5060,7 +5208,7 @@ function runUpgrade(workspaceRoot, options) {
5060
5208
 
5061
5209
  // src/workspace-creation/interactive-wizard.use-case.ts
5062
5210
  async function runInteractive() {
5063
- const inWorkspace = fileExists((0, import_node_path22.join)(process.cwd(), "nx.json"));
5211
+ const inWorkspace = fileExists((0, import_node_path23.join)(process.cwd(), "nx.json"));
5064
5212
  const choiceNew = { name: "Create a new monorepo", value: "new" };
5065
5213
  const choiceAdd = { name: "Add a project to this workspace", value: "add" };
5066
5214
  const choiceUpgrade = {
@@ -5081,8 +5229,8 @@ async function runInteractive() {
5081
5229
  }
5082
5230
 
5083
5231
  // src/cli-version/check-for-update.use-case.ts
5084
- var import_node_fs15 = require("fs");
5085
- var import_node_path23 = require("path");
5232
+ var import_node_fs16 = require("fs");
5233
+ var import_node_path24 = require("path");
5086
5234
  var import_cross_spawn2 = __toESM(require("cross-spawn"));
5087
5235
  var PACKAGE_NAME = "@mnci/cli";
5088
5236
  var REGISTRY_TIMEOUT_MS = 2e3;
@@ -5138,8 +5286,8 @@ function releaseTriple(version) {
5138
5286
  }
5139
5287
  function readCliVersion(packageDirectory) {
5140
5288
  try {
5141
- const manifestPath = (0, import_node_path23.join)(packageDirectory, "..", "package.json");
5142
- const manifest = JSON.parse((0, import_node_fs15.readFileSync)(manifestPath, "utf8"));
5289
+ const manifestPath = (0, import_node_path24.join)(packageDirectory, "..", "package.json");
5290
+ const manifest = JSON.parse((0, import_node_fs16.readFileSync)(manifestPath, "utf8"));
5143
5291
  return manifest.version ?? "0.0.0";
5144
5292
  } catch {
5145
5293
  return "0.0.0";
@@ -5153,7 +5301,10 @@ function buildProgram(cliVersion) {
5153
5301
  program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option(
5154
5302
  "--agent <pool>",
5155
5303
  "CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name"
5156
- ).option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").action(async (name, options) => {
5304
+ ).option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").option(
5305
+ "--into <dir>",
5306
+ "bootstrap into an existing directory (a fresh clone holding only .git); the directory name is the workspace name unless one is given"
5307
+ ).action(async (name, options) => {
5157
5308
  await runNew(name, options);
5158
5309
  });
5159
5310
  program.command("upgrade").description(