@mnci/cli 4.13.1 → 4.14.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/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/main.ts","../src/project-scaffolding/add-project.use-case.ts","../src/nx-workspace/nx.client.ts","../src/terminal/logger.client.ts","../src/terminal/prompts.client.ts","../src/file-system/file-system.client.ts","../src/project-name/project-name.validator.ts","../src/project-scaffolding/csharp.use-case.ts","../src/workspace-overlay/overlay.use-case.ts","../src/project-scaffolding/post-generation.use-case.ts","../src/project-scaffolding/flutter.use-case.ts","../src/project-scaffolding/go.use-case.ts","../src/project-scaffolding/node.use-case.ts","../src/project-scaffolding/npm-lib.use-case.ts","../src/rollup-library/repair-publishable-manifest.use-case.ts","../src/rollup-library/repair-rollup-config.use-case.ts","../src/rollup-library/rollup-config.algorithm.ts","../src/rollup-library/rollup-config.repository.ts","../src/project-scaffolding/python.use-case.ts","../src/project-scaffolding/react-app.use-case.ts","../src/project-scaffolding/react-lib.use-case.ts","../src/project-scaffolding/vscode-extension.use-case.ts","../src/dependency-management/sync-dependencies.use-case.ts","../src/dependency-management/manifest.repository.ts","../src/dependency-management/semver.algorithm.ts","../src/dependency-management/update-dependencies.use-case.ts","../src/dependency-management/registry.client.ts","../src/workspace-diagnostics/check-invariants.use-case.ts","../src/workspace-creation/adopt-directory.use-case.ts","../src/workspace-creation/create-workspace.use-case.ts","../src/workspace-creation/interactive-wizard.use-case.ts","../src/workspace-upgrade/upgrade-workspace.use-case.ts","../src/cli-version/check-for-update.use-case.ts"],"sourcesContent":["import { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './project-scaffolding'\nimport { runDoctor } from './workspace-diagnostics'\nimport { runInteractive } from './workspace-creation'\nimport { runNew, type NewOptions } from './workspace-creation'\nimport { runSync, type SyncOptions } from './dependency-management'\nimport { runUp, type UpOptions } from './dependency-management'\nimport { runUpgrade, type UpgradeOptions } from './workspace-upgrade'\nimport { logger } from './terminal'\nimport { checkForUpdate, readCliVersion } from './cli-version'\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Six commands: `new`, `add`, `upgrade` (re-applies the overlay to an existing\n * workspace), `doctor` (read-only invariant check), `sync` (converge dependency\n * ranges and TypeScript project references) and `up` (report and apply\n * available upgrades). Everything else a generated repo needs day-to-day\n * (build/test/lint/release) is plain Nx, so the CLI deliberately has no wrapper\n * commands for those.\n *\n * `sync` and `up` exist because Nx covers neither: `nx sync` runs the\n * workspace's sync generators, and the only one registered is\n * `@nx/js:typescript-sync` — TypeScript project references, no opinion at all\n * about dependency versions. And npm has no `catalog:`, so one version per\n * workspace is a convention nothing enforces.\n *\n * @param cliVersion - The current CLI version (from package.json).\n * @returns The configured commander program.\n * @throws Never - wiring only; execution errors surface when commands run.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function buildProgram (cliVersion: string): Command {\n const program = new Command()\n\n program\n .name('mnci')\n .description('MoNecromanCI — a thin CLI over official Nx plugins')\n .version(cliVersion, '-v, --version', 'display the version')\n\n program\n .command('new')\n .argument('[name]', 'workspace name')\n .description('Create a new monorepo (Nx TS preset + release/pipeline overlay)')\n .option('-y, --yes', 'accept defaults for anything not passed as a flag')\n .option('--scope <scope>', 'npm scope for publishable packages (e.g. @demo)')\n .option('--registry <kind>', 'azure-artifacts | npm')\n .option('--organization <name>', 'Azure DevOps organization')\n .option('--project <name>', 'Azure DevOps project')\n .option('--artifacts-feed <name>', 'Azure Artifacts feed')\n .option(\n '--agent <pool>',\n 'CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name',\n )\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option(\n '--npm-auth <mode>',\n 'how npm authenticates to an Azure Artifacts feed: pat (default) | build-identity (npmAuthenticate@0, no PAT; --ci azure only)',\n )\n .option('--ci <provider>', 'CI provider: azure | github | both')\n .option('--test-runner <runner>', 'unit-test runner: jest | vitest')\n .option('--nx-cloud', 'connect the workspace to Nx Cloud (remote caching + CI insights)')\n .option(\n '--into <dir>',\n 'bootstrap into an existing directory (a fresh clone holding only .git); the directory name is the workspace name unless one is given',\n )\n .action(async (name: string | undefined, options: NewOptions) => {\n await runNew(name, options)\n })\n\n program\n .command('upgrade')\n .description(\n 'Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)',\n )\n .option('--scope <scope>', 'npm scope for publishable packages (overrides the persisted value)')\n .option('--registry <kind>', 'azure-artifacts | npm (overrides the persisted value)')\n .option('--organization <name>', 'Azure DevOps organization')\n .option('--project <name>', 'Azure DevOps project')\n .option('--artifacts-feed <name>', 'Azure Artifacts feed')\n .option('--agent <pool>', 'CI build agent (overrides the persisted value)')\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option(\n '--npm-auth <mode>',\n 'how npm authenticates to an Azure Artifacts feed: pat (default) | build-identity (npmAuthenticate@0, no PAT; --ci azure only)',\n )\n .option('--ci <provider>', 'CI provider: azure | github | both (overrides the persisted value)')\n .option(\n '--test-runner <runner>',\n 'unit-test runner: jest | vitest (overrides the persisted value)',\n )\n .action((options: UpgradeOptions) => {\n runUpgrade(process.cwd(), options)\n })\n\n program\n .command('doctor')\n .description(\n 'Check this workspace against the invariants mnci maintains (one ESLint config, no stray .prettierrc, the eslint plugin registered, the resolved eslint major, .npmrc vs the recorded registry, versionActions overrides, nx sync) — read-only; exits non-zero if anything failed',\n )\n .action(() => {\n runDoctor(process.cwd())\n })\n\n program\n .command('sync')\n .description(\n \"Make every project agree: converge external dependency ranges declared at more than one version, then run 'nx sync' for TypeScript project references. Go is a no-op — one root go.mod means one version of every module\",\n )\n .option('--check', 'report drift and exit non-zero without writing anything')\n .option('--ecosystem <name>', 'restrict to one ecosystem: npm | pip | pub | go')\n .action((options: SyncOptions) => {\n runSync(process.cwd(), options)\n })\n\n program\n .command('up')\n .description(\n 'Show every dependency with a newer published release — grouped patch/minor/major/non-semver, with the projects declaring each one — and interactively update the ones you pick',\n )\n .option('--check', 'report only; never prompt and never write (the default when piped)')\n .option('-y, --yes', 'select every available update without prompting')\n .option('--ecosystem <name>', 'restrict to one ecosystem: npm | pip | pub | go')\n .option('--no-install', 'update the manifests but skip the reinstall step')\n .action(async (options: UpOptions) => {\n await runUp(process.cwd(), options)\n })\n\n program\n .command('add')\n // choices() rejects an unrecognized kind with a clean commander usage\n // error (and lists the valid ones in --help) before runAdd ever runs —\n // instead of a typo silently doing nothing but reporting \"success\".\n .addArgument(new Argument('[kind]', 'project kind').choices(PROJECT_KINDS))\n .argument('[name]', 'project name')\n .description('Add a project by delegating to the matching Nx plugin generator')\n .option('--scope <scope>', 'npm scope for a publishable lib (defaults to @<workspace name>)')\n .option(\n '--framework <framework>',\n 'node-app only: express | fastify | koa | nest | none (default: none)',\n )\n .option(\n '--lib <name>',\n 'python-vendor only: the internal Python library (libs/<name>) to vendor into <name>',\n )\n .option(\n '--publisher <id>',\n 'vscode-extension only: the Marketplace publisher id (defaults to the workspace scope without @)',\n )\n .option(\n '--sidecar <go-app>',\n 'vscode-extension only: a go-app whose six-platform build ships in the extension, one .vsix per platform',\n )\n .action(\n async (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions) => {\n await runAdd(kind, name, options)\n },\n )\n\n // Bare `mnci` (no subcommand) launches the guided wizard; commander runs\n // this default action only when no subcommand is given (-v/--help still win).\n program.action(async () => {\n await runInteractive()\n })\n\n return program\n}\n\n/**\n * CLI entry point: parse arguments and surface failures as exit code 1.\n *\n * @remarks\n * Exported so tests can drive the program without spawning a process.\n * Runs a non-blocking version check in the background (fire-and-forget), and\n * prints a `mnci vX.Y.Z` banner for interactive use — gated on stdout being a\n * TTY (like {@link checkForUpdate}) so CI/piped invocations see nothing extra,\n * and skipped when `-v`/`--version` was passed explicitly since commander\n * already prints the bare version for that case; a banner above it would just\n * be a redundant duplicate.\n *\n * @param None - this function takes no parameters.\n * @returns A promise that resolves when the invoked command completes.\n * @throws Never - failures are logged and turned into a non-zero exit code.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function main (): Promise<void> {\n try {\n const cliVersion = readCliVersion(__dirname)\n const program = buildProgram(cliVersion)\n\n // Check for updates in the background (non-blocking).\n checkForUpdate(cliVersion)\n\n const arguments_ = new Set(process.argv.slice(2))\n const requestedVersionFlag = arguments_.has('-v') || arguments_.has('--version')\n if (!requestedVersionFlag && process.stdout.isTTY) {\n logger.info(`mnci v${cliVersion}`)\n }\n\n await program.parseAsync(process.argv)\n } catch (error) {\n logger.error(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n}\n\n// Only self-execute when run as a binary, not when imported by tests.\n//\n// `void` rather than `await`/`.catch()`: `main` cannot reject — it wraps its whole\n// body in a try/catch that logs and sets a non-zero exit code — so there is no\n// rejection to handle, and there is nothing after this to sequence. The operator\n// states that deliberately instead of leaving a bare floating promise that reads\n// like an oversight.\nif (require.main === module) {\n void main()\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { runFormatter, runNx } from '../nx-workspace'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\nimport { assertValidProjectName } from '../project-name'\nimport { addCsharpApp, addCsharpFunctionApp, addCsharpInternalLib, addCsharpLib } from './csharp.use-case'\nimport { addFlutterApp, addFlutterInternalLib, addFlutterLib } from './flutter.use-case'\nimport { addGoApp, addGoFunctionApp, addGoInternalLib, addGoLib } from './go.use-case'\nimport { addNodeApp, addNodeFunctionApp } from './node.use-case'\nimport { addNpmLib } from './npm-lib.use-case'\nimport {\n addPythonApp,\n addPythonFunctionApp,\n addPythonInternalLib,\n addPythonLib,\n addPythonVendor,\n} from './python.use-case'\nimport { addReactApp } from './react-app.use-case'\nimport { addReactInternalLib, addReactLib } from './react-lib.use-case'\nimport { addVscodeExtension } from './vscode-extension.use-case'\nimport { syncProjectReferences } from '../dependency-management'\nimport {\n ensureNxPeerOverrides,\n markPrivate,\n renameScaffoldPlaceholder,\n registerProjectCommands,\n relocateRootRuntimeDependencies,\n removeGeneratedEslintConfig,\n rootRuntimeDependencies,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\nexport type { AddOptions } from './post-generation.use-case'\n\n/**\n * The project kinds this CLI can add — deliberately just twenty-three.\n *\n * @remarks\n * Each maps to an official (or established first-party) Nx plugin generator;\n * this CLI itself writes no project files (bar thin overlays). Layout convention\n * drives release scoping: `apps/` (never released, except a `vscode-extension`,\n * matched by its tag), `packages/` (publishable\n * npm, released by `nx release`), `libs/` (internal, never released),\n * `python-packages/` (publishable Python, published by `twine`).\n *\n * The TS/JS kinds use the official `@nx/*` generators only — `node-app` and\n * `node-function-app` are both the plain `@nx/node:application` (no\n * third-party Azure Functions plugin; `node-function-app` is that generator\n * plus a hand-written Azure Functions v4 file overlay). The Python kinds use\n * **`@mnci/nx-python-pip`** (`libs/nx-python-pip` in this same monorepo) — a\n * real Nx plugin this project built and maintains, since no maintained\n * Nx-23-compatible plugin supports pip (every one found ships uv/Poetry\n * only). Its generators write `pyproject.toml` + `project.json` + a sample\n * module/tests around **pip + Ruff + pytest + the standard PyPA\n * `build`/`twine`** — the industry-standard, uv-free Python toolchain — and\n * follow the identical app/function-app split. Every kind builds to its own\n * Nx-default output location — no post-generation build-output redirection.\n * Each kind's generation logic lives in its own module under `add/` — see\n * `add/reactApp.ts`, `add/reactLib.ts`, `add/node.ts`, `add/npmLib.ts` and\n * `add/python.ts` (internal-lib is small enough to stay inline below).\n *\n * The React family covers all three shapes: `react-app` (Vite SPA),\n * `react-lib` (publishable component library) and `react-internal-lib`\n * (private). The two library kinds were missing for a long time, which meant a\n * shared component library could not be built at all — `npm-lib` and\n * `internal-lib` are both `@nx/js:lib`, with no JSX support.\n *\n * The Go kinds use **`@nx-go/nx-go`** — an established third-party plugin,\n * validated empirically against a real Nx 23.1.0 workspace (it declares\n * `@nx/devkit \">= 20 < 23\"` as a plain dependency, so npm nests its own\n * devkit copy and everything still works). Go follows the same\n * root-manifest model as TS and Python: `add/go.ts` bootstraps a **single\n * root `go.mod`** on the first Go add (via the plugin's `init` +\n * `convert-to-one-mod`), so every Go project shares one module and a library\n * is imported as `<module>/libs/<name>` — no per-project manifests, no\n * `replace` directives. Because that single-module layout has no per-project\n * `go.mod`, the plugin's inference produces no targets, so mnci writes\n * build/test/lint explicitly (as it does for most kinds anyway). Lint is\n * `golangci-lint` — the executor's own default is plain `go fmt`, which only\n * reformats. Go needs no publish-time dependency injection at all: `go build`\n * links statically.\n *\n * The Flutter kinds use **`@mnci/nx-flutter`** (`packages/nx-flutter` in this\n * same monorepo) — the second real Nx plugin this project builds and\n * maintains, for the same reason as the Python one: no maintained,\n * Nx-23-compatible Flutter plugin exists (`@nxrocks/nx-flutter` cannot even\n * load on Nx 23 — it imports `@nx/workspace/src/utilities/fileutils`, removed\n * in 23). Its generators delegate scaffolding to the official Flutter CLI\n * (`flutter create`), so no template is hand-maintained against SDK releases.\n *\n * Flutter follows the same root-manifest model as the rest: a **Dart pub\n * workspace**, with one root `pubspec.yaml` listing every project and each\n * project carrying `resolution: workspace`, so a single `flutter pub get` at\n * the root resolves the whole graph into one `pubspec.lock`. The payoff is\n * that a project depending on an internal lib declares a **plain version\n * constraint with no `path:`** — pub resolves it locally because it is a\n * workspace member. That is also why Flutter needs no vendoring step (unlike\n * `python-vendor` below): there is nothing to weave in at build time. Apps\n * build for **web** only, which keeps the Android SDK off every build agent.\n *\n * `vscode-extension` is `@nx/node:application` turned into a Marketplace\n * extension: bundled with `vscode` external, packaged by `vsce` into one `.vsix`\n * (or one per platform with a `--sidecar` Go app's binaries in it), published by\n * `nx release` when `VSCE_PAT` is set. It lives in `apps/` and is released anyway,\n * through the `type:vscode-extension` tag in `release.projects`.\n *\n * `python-vendor` is the one kind that generates nothing: plain pip has no\n * bundled-local-dependency feature, so wiring an internal Python library\n * into a consumer's built wheel is a hand-edit of the consumer's\n * `pyproject.toml` (see `@mnci/nx-python-pip`'s README) — this kind\n * automates exactly that edit, idempotently, instead of delegating to a\n * generator. `name` is the consumer; the library is `--lib <name>`.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type ProjectKind =\n | 'react-app' |\n 'react-lib' |\n 'react-internal-lib' |\n 'node-app' |\n 'node-function-app' |\n 'npm-lib' |\n 'internal-lib' |\n 'python-app' |\n 'python-function-app' |\n 'python-lib' |\n 'python-internal-lib' |\n 'python-vendor' |\n 'go-app' |\n 'go-function-app' |\n 'go-lib' |\n 'go-internal-lib' |\n 'flutter-app' |\n 'flutter-lib' |\n 'flutter-internal-lib' |\n 'csharp-app' |\n 'csharp-function-app' |\n 'csharp-lib' |\n 'csharp-internal-lib' |\n 'vscode-extension'\n\n/**\n * Every kind {@link runAdd} accepts, in menu order.\n *\n * @remarks\n * Also drives the interactive kind picker shown when `add` is run bare. The\n * React family first, then the rest of the TS/JS kinds, then Python, Go,\n * Flutter and C#.\n */\nexport const PROJECT_KINDS: ProjectKind[] = [\n 'react-app',\n 'react-lib',\n 'react-internal-lib',\n 'node-app',\n 'node-function-app',\n 'npm-lib',\n 'internal-lib',\n 'python-app',\n 'python-function-app',\n 'python-lib',\n 'python-internal-lib',\n 'python-vendor',\n 'go-app',\n 'go-function-app',\n 'go-lib',\n 'go-internal-lib',\n 'flutter-app',\n 'flutter-lib',\n 'flutter-internal-lib',\n 'csharp-app',\n 'csharp-function-app',\n 'csharp-lib',\n 'csharp-internal-lib',\n 'vscode-extension',\n]\n\n/**\n * Adds a project to the workspace by delegating to the matching Nx generator.\n *\n * @remarks\n * A thin dispatcher — the actual generation logic for each kind lives in its\n * own module under `add/` (imported above), so this function only resolves\n * the shared inputs (kind, name, the workspace's stack) and routes to the\n * right one. Pure delegation throughout — no post-generation file rewriting\n * beyond each kind's own thin overlay. Known gap: a *publishable* lib\n * importing a *private internal* lib cannot be published as-is; internal\n * libs are for apps and other internal libs.\n *\n * @param kind - The project kind, prompted for when omitted.\n * @param name - The project name, prompted for when omitted.\n * @param options - The CLI flags.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when run outside a workspace root or a generator fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runAdd (\n kind: ProjectKind | undefined,\n name: string | undefined,\n options: AddOptions,\n): Promise<void> {\n const workspaceRoot = process.cwd()\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error('No nx.json found here. Run `add` from the workspace root.')\n }\n\n // The stack chosen at `mnci new` lives in nx.json; every generator (and the\n // hand-built function app) is wired to match it.\n const stack = readWorkspaceStack(workspaceRoot)\n\n // When the kind was not passed, the user is on the bare/interactive path, so\n // fill in every configuration — including the npm-lib scope (below) that the\n // flag path defaults silently.\n const kindProvided = kind !== undefined\n const resolvedKind =\n kind ??\n (await select<ProjectKind>({\n message: 'What kind of project?',\n choices: PROJECT_KINDS.map(value => ({ name: value, value })),\n }))\n const resolvedName = name ?? (await promptText('Project name'))\n // Fails fast, before any install or generator call: the name becomes a\n // directory, an argv token and (for Python kinds) a module identifier — and\n // an explicitly empty `name` argument bypasses promptText's own non-empty\n // check, which only fires on the prompted path.\n assertValidProjectName(resolvedName, 'Project name')\n\n // Snapshotted before any generator runs, so whatever appears in the root's\n // runtime dependencies afterwards can be attributed to this project and moved\n // into its own manifest. See relocateRootRuntimeDependencies for why the root\n // is the wrong place for one.\n const rootDependenciesBefore = rootRuntimeDependencies(workspaceRoot)\n\n // Before the switch, because every generator below installs as it scaffolds\n // and the override has to already be on disk when it does. See\n // ensureNxPeerOverrides.\n ensureNxPeerOverrides(workspaceRoot)\n\n switch (resolvedKind) {\n case 'react-app': {\n addReactApp(workspaceRoot, resolvedName, stack)\n break\n }\n case 'react-lib': {\n await addReactLib(workspaceRoot, resolvedName, options, kindProvided, stack)\n break\n }\n case 'react-internal-lib': {\n addReactInternalLib(workspaceRoot, resolvedName, stack)\n break\n }\n case 'node-app': {\n addNodeApp(workspaceRoot, resolvedName, stack, options.framework)\n break\n }\n case 'node-function-app': {\n addNodeFunctionApp(workspaceRoot, resolvedName, stack)\n break\n }\n case 'npm-lib': {\n await addNpmLib(workspaceRoot, resolvedName, options, kindProvided, stack)\n break\n }\n case 'internal-lib': {\n // tsc (not none): the default @nx/enforce-module-boundaries rule forbids\n // buildable libraries (every npm-lib) from importing non-buildable ones,\n // so internal libs must be buildable — just never published (private).\n runNx(\n [\n 'g',\n '@nx/js:lib',\n `libs/${resolvedName}`,\n '--bundler=tsc',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n markPrivate(join(workspaceRoot, 'libs', resolvedName, 'package.json'))\n // Same @nx/js:lib placeholder, same role-suffix rule — see\n // renameScaffoldPlaceholder.\n renameScaffoldPlaceholder(join(workspaceRoot, 'libs', resolvedName), resolvedName)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${resolvedName}`)\n registerProjectCommands(workspaceRoot, resolvedName, { build: true })\n break\n }\n case 'python-app': {\n addPythonApp(workspaceRoot, resolvedName)\n break\n }\n case 'python-function-app': {\n addPythonFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'python-lib': {\n addPythonLib(workspaceRoot, resolvedName)\n break\n }\n case 'python-internal-lib': {\n addPythonInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'go-app': {\n addGoApp(workspaceRoot, resolvedName)\n break\n }\n case 'go-function-app': {\n addGoFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'go-lib': {\n addGoLib(workspaceRoot, resolvedName)\n break\n }\n case 'go-internal-lib': {\n addGoInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-app': {\n addFlutterApp(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-lib': {\n addFlutterLib(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-internal-lib': {\n addFlutterInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-app': {\n addCsharpApp(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-function-app': {\n addCsharpFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-lib': {\n await addCsharpLib(workspaceRoot, resolvedName, options, kindProvided)\n break\n }\n case 'csharp-internal-lib': {\n addCsharpInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'vscode-extension': {\n addVscodeExtension(workspaceRoot, resolvedName, stack, {\n publisher: options.publisher,\n sidecar: options.sidecar,\n })\n break\n }\n case 'python-vendor': {\n await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided)\n // `resolvedName` is the consumer, not something newly created — the\n // generic \"Added ... 'name'\" success message below reads wrong for\n // this kind, so it returns early with its own message instead.\n syncProjectReferences(workspaceRoot)\n runFormatter(workspaceRoot)\n\n return\n }\n default: {\n // Unreachable while every ProjectKind has a case above: `exhaustive`\n // being `never` makes adding a new kind without a matching case a\n // *compile-time* error, not just a runtime gap. The CLI itself already\n // rejects an unrecognized value before this ever runs (cli.ts's\n // Argument#choices()); this is the last line of defense for any other\n // caller of runAdd (e.g. a future programmatic use).\n const exhaustive: never = resolvedKind\n throw new Error(\n `Unknown project kind '${exhaustive as string}'. Expected one of: ${PROJECT_KINDS.join(', ')}.`,\n )\n }\n }\n\n relocateRootRuntimeDependencies(workspaceRoot, resolvedName, rootDependenciesBefore)\n\n syncProjectReferences(workspaceRoot)\n\n // Nx's generators write in their own style (semicolons, double quotes), and\n // `nx sync` plus the root-manifest/`.code-workspace` edits above touch files\n // outside the new project — so this formats the workspace, not just\n // `<projectRoot>`. Keeps `npm run format:check` green after every add.\n runFormatter(workspaceRoot)\n\n logger.success(`Added ${resolvedKind} '${resolvedName}'.`)\n}\n\n/**\n * The workspace stack, read back from the `nx.json` `mnci.stack` block `new` wrote.\n *\n * @remarks\n * How a one-time `mnci new` choice reaches `add`: `mnci.stack` (written by\n * `mnciConfig` in `overlay.ts`) is the single source of truth — a dedicated\n * block, not inferred from one of Nx's own (three, always-identical)\n * generator-default blocks, so there's no \"stay in lockstep\" invariant to\n * silently drift. `add` passes the result back to the `@nx/*`\n * generators explicitly (predictable regardless of Nx's own default\n * resolution). The return shape is generator-facing: `linter` is `eslint`, or `none` when the\n * workspace chose oxlint (oxlint is not an Nx linter). Missing/blank (e.g. a\n * workspace generated before this field existed) falls back to the default\n * opinion (eslint + jest).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The linter and test runner to apply.\n * @throws Propagates any `fs`/JSON error reading `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction readWorkspaceStack (workspaceRoot: string): WorkspaceStack {\n const nxJson = readJson<{ mnci?: { stack?: { testRunner?: string; linter?: string } } }>(\n join(workspaceRoot, 'nx.json'),\n )\n const stack = nxJson.mnci?.stack\n\n return {\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest',\n }\n}\n","import spawn from 'cross-spawn'\nimport { logger } from '../terminal'\n\n/**\n * Runs a command synchronously, inheriting stdio so its output streams live.\n *\n * @remarks\n * Shells out for everything — `create-nx-workspace`, `nx add`, `nx g` — so\n * the CLI stays version-decoupled from whatever Nx is current, and the plugin\n * generators' own interactive output reaches the user unmodified.\n *\n * Uses `cross-spawn` (not `node:child_process`'s `spawnSync` with\n * `shell: true`) specifically so arguments are never interpreted by a shell:\n * `cross-spawn` is a drop-in `spawnSync` replacement that resolves Windows\n * `.cmd`/`.bat` shims itself, safely, from the `(command, args, options)`\n * array form — no string concatenation, so a value containing shell\n * metacharacters (semicolons, backticks, `$()`, …) is passed through as one\n * literal argv entry, never executed. Every argument we pass (workspace/\n * project name, scope, Azure coordinates, …) can originate from user input, so\n * this matters: the previous `[command, ...args].join(' ')` + `shell: true`\n * design let a crafted name run arbitrary shell commands.\n *\n * @param command - The executable to run (e.g. `npx`).\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns The child process exit status (`0` on success); `1` when the process\n * was terminated by a signal or never produced a status (e.g. spawn failure).\n * @throws Never - spawn failures surface through the returned status, not a throw.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runShell (command: string, arguments_: string[], cwd: string): number {\n const result = spawn.sync(command, arguments_, { stdio: 'inherit', cwd })\n\n return result.status ?? 1\n}\n\n/**\n * The captured outcome of a command run through {@link runCapture}.\n *\n * @remarks\n * `stdout` is always a string — never `null` — so callers can parse it without\n * a nullish guard at every site. A spawn that never started yields status `1`\n * and an empty string, which is indistinguishable from a command that failed\n * silently; that is deliberate, because every caller here treats both as \"no\n * answer available\" rather than branching on the difference.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface CaptureResult {\n /** The child process exit status; `1` when it never produced one. */\n status: number\n /** Everything the child wrote to stdout, decoded as UTF-8. */\n stdout: string\n}\n\n/**\n * How long a captured command may run before it is killed, in milliseconds.\n *\n * @remarks\n * Generous compared with the update checker's 2s registry budget, because the\n * commands routed through here are not all network calls: the Go and Dart\n * \"what is outdated\" queries each resolve a whole dependency graph, and a cold\n * `flutter pub` run on a large workspace genuinely takes tens of seconds. A\n * timeout tuned for a single registry lookup would abort those before they ever\n * answered.\n */\nconst CAPTURE_TIMEOUT_MS = 60_000\n\n/**\n * Runs a command and captures its stdout instead of streaming it.\n *\n * @remarks\n * The counterpart to {@link runShell}, which inherits stdio and therefore hands\n * the caller nothing but an exit code. Commands whose *output* is the answer —\n * `npm view`, `go list -m -u -json all`, `flutter pub outdated --json` — need\n * this one.\n *\n * Same `cross-spawn` argv-array contract as {@link runShell}, for the same\n * reason: no shell interprets the arguments, so a package name originating from\n * a manifest can never be executed. `execSync` with a shell string would\n * reintroduce exactly the injection hazard `runShell`'s docblock describes, and\n * its POSIX-only redirection would fail outright on a Windows agent.\n *\n * stderr is discarded (`'ignore'`) rather than piped: every caller parses\n * stdout, and npm in particular writes progress and audit noise to stderr that\n * would otherwise have to be filtered out at each site. A failing command is\n * detected by its status, not by what it printed.\n *\n * @param command - The executable to run (e.g. `npm`).\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns The exit status and the captured stdout.\n * @throws Never - a spawn failure or timeout surfaces as a non-zero status.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runCapture (command: string, arguments_: string[], cwd: string): CaptureResult {\n const result = spawn.sync(command, arguments_, {\n cwd,\n encoding: 'utf8',\n timeout: CAPTURE_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n // `result.error` covers the spawn that never started (an absent toolchain,\n // most often) — there `status` is null and stdout is null too.\n if (result.error) {\n return { status: 1, stdout: '' }\n }\n\n return { status: result.status ?? 1, stdout: result.stdout ?? '' }\n}\n\n/**\n * Runs a command asynchronously and captures its stdout.\n *\n * @remarks\n * The async twin of {@link runCapture}, and it exists for exactly one reason:\n * `mnci up` asks the registry about every declared package, and a synchronous\n * spawn per package makes that a serial wait of roughly half a second times the\n * dependency count — half a minute on a workspace of any size. Callers pool\n * these, so the same work overlaps.\n *\n * Same argv-array, no-shell contract as every other helper here. The promise\n * never rejects: a spawn error resolves as status `1` with empty output, so a\n * pool of these needs no per-item `catch` and one absent toolchain cannot take\n * the whole run down.\n *\n * @param command - The executable to run.\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns A promise of the exit status and captured stdout.\n * @throws Never - failures resolve as a non-zero status.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runCaptureAsync (\n command: string,\n arguments_: string[],\n cwd: string,\n): Promise<CaptureResult> {\n return await new Promise<CaptureResult>(resolve => {\n const child = spawn(command, arguments_, {\n cwd,\n timeout: CAPTURE_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n let stdout = ''\n child.stdout?.setEncoding('utf8')\n child.stdout?.on('data', (chunk: string) => {\n stdout += chunk\n })\n child.on('error', () => {\n resolve({ status: 1, stdout: '' })\n })\n child.on('close', code => {\n resolve({ status: code ?? 1, stdout })\n })\n })\n}\n\n/**\n * Runs an async job over every input, at most `limit` at a time.\n *\n * @remarks\n * A pool rather than `Promise.all` over the whole list: a workspace can declare\n * a hundred packages, and spawning a hundred `npm view` processes at once is\n * how a laptop runs out of file descriptors and a CI agent starts timing out.\n * Results come back in input order regardless of completion order, so a report\n * built from them is deterministic.\n *\n * @param items - The inputs to process.\n * @param limit - Maximum number of jobs in flight at once.\n * @param job - The work to run for one input.\n * @returns A promise of the results, in the same order as `items`.\n * @throws Propagates the first rejection from `job`.\n * @typeParam TIn - The input element type.\n * @typeParam TOut - The result element type.\n */\nexport async function pool<TIn, TOut> (\n items: readonly TIn[],\n limit: number,\n job: (item: TIn) => Promise<TOut>,\n): Promise<TOut[]> {\n const results: TOut[] = Array.from({ length: items.length })\n let next = 0\n\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next\n next += 1\n results[index] = await job(items[index])\n }\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => await worker()))\n\n return results\n}\n\n/**\n * Runs `npx nx <args>` in the given workspace, failing loudly on error.\n *\n * @remarks\n * Every generator/plugin invocation funnels through here so tests can assert\n * the exact delegation and the error contract stays in one place.\n *\n * @param arguments_ - The Nx CLI arguments (e.g. `['g', '@nx/react:app', …]`).\n * @param cwd - The workspace root to run in.\n * @returns Nothing. Throws instead of returning a status.\n * @throws Error when the Nx process exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNx (arguments_: string[], cwd: string): void {\n const status = runShell('npx', ['nx', ...arguments_], cwd)\n if (status !== 0) {\n throw new Error(`nx ${arguments_.join(' ')} failed with exit code ${status}`)\n }\n}\n\n/**\n * Runs `npx <args>` in the given directory, failing loudly on error.\n *\n * @remarks\n * Used for the one non-Nx invocation: `create-nx-workspace` itself, which by\n * definition runs *outside* any workspace.\n *\n * @param arguments_ - The npx arguments (e.g. `['create-nx-workspace@latest', …]`).\n * @param cwd - The directory to run in.\n * @returns Nothing. Throws instead of returning a status.\n * @throws Error when the process exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNpx (arguments_: string[], cwd: string): void {\n const status = runShell('npx', arguments_, cwd)\n if (status !== 0) {\n throw new Error(`npx ${arguments_.join(' ')} failed with exit code ${status}`)\n }\n}\n\n/**\n * Runs the workspace's formatter over a freshly generated workspace or project.\n *\n * @remarks\n * Nx's own generators emit semicolons, double quotes and trailing commas —\n * the opposite of the JavaScript Standard Style mnci configures. Without this\n * pass a workspace fails its own `lint` the moment it is created, which is a\n * poor first impression and, worse, buries every real formatting change under a\n * wall of generator noise on the first commit. Running it here means what mnci\n * hands back is already normalised.\n *\n * **There is no formatter to choose any more, and that is what makes this\n * safe.** This function used to take the workspace's linter choice and pick a\n * binary from it, because getting that wrong was a real bug: running\n * `npx prettier --write .` in a workspace with no Prettier config does not\n * fail, it silently reformats everything against its own defaults — semicolons, double\n * quotes, trailing commas — over files mnci had just written correctly. Now\n * ESLint is the only tool, so the config that decides the style and the binary\n * that applies it cannot disagree: there is only one of each.\n *\n * The ignore list comes from `eslint.config.mjs` for the same reason, rather\n * than from a separate `.prettierignore` that a second tool had to be taught to\n * read.\n *\n * Deliberately non-fatal. The project has already been generated and wired by\n * the time this runs; aborting on a formatter hiccup would leave a usable\n * workspace behind an error message. A warning names the exact command to\n * re-run instead.\n *\n * @param cwd - The workspace root to run in.\n * @param target - What to format, relative to `cwd`. Defaults to the whole\n * workspace (`.`); `add` passes the new project's root to keep the pass\n * proportional to what actually changed.\n * @returns Nothing.\n * @throws Never - a non-zero exit is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runFormatter (cwd: string, target = '.'): void {\n // ESLint IS the formatter now, so formatting a generated project is\n // `eslint --fix` rather than a separate binary. Nx's generators emit\n // semicolons and double quotes, so without this pass a fresh workspace fails\n // its own `lint` before the user has written a line.\n //\n // No `--cache` here: this runs once on freshly written files, where a cache\n // can only cost a write. The lint TARGETS carry it, which is where repeat\n // runs actually happen.\n const status = runShell('npx', ['eslint', target, '--fix'], cwd)\n if (status !== 0) {\n logger.warn(\n `eslint could not format '${target}' (exit code ${status}). ` +\n \"The project was generated; run 'npm run format' to normalise it.\",\n )\n }\n}\n","/**\n * Minimal namespaced console logger used across the CLI.\n *\n * @remarks\n * Each method prefixes `message` with a small glyph to distinguish severity\n * when scanning terminal output.\n */\nexport const logger = {\n info (message: string): void {\n console.log(message)\n },\n step (message: string): void {\n console.log(`→ ${message}`)\n },\n /**\n * A sub-step under the most recent {@link logger.step}, indented to read as detail.\n *\n * `this: void` because this one is handed to `applyOverlay` as a progress callback,\n * detached from the object. It reads no instance state, and the annotation is what\n * says so — without it every such call is an unbound-method error.\n */\n detail (this: void, message: string): void {\n console.log(` · ${message}`)\n },\n success (message: string): void {\n console.log(`✓ ${message}`)\n },\n warn (message: string): void {\n console.warn(`! ${message}`)\n },\n error (message: string): void {\n console.error(`✗ ${message}`)\n },\n}\n","import { confirm, input, select } from '@inquirer/prompts'\nimport type { CiProvider, RegistryConfig, StackConfig } from '../workspace-overlay'\n\n/**\n * Prompts for a non-empty trimmed string with an optional default.\n *\n * @remarks\n * Wraps `@inquirer/prompts`'s `input`, enforcing a non-empty result.\n *\n * @param message - The prompt message to display.\n * @param fallback - Optional default value pre-filled in the prompt.\n * @returns The trimmed, non-empty string the user entered.\n * @throws Propagates any error `@inquirer/prompts`'s `input` raises (e.g. when\n * stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptText (message: string, fallback?: string): Promise<string> {\n const value = await input({\n message,\n default: fallback,\n validate: (value: string) => value.trim().length > 0 || 'A value is required',\n })\n\n return value.trim()\n}\n\n/**\n * Prompts for the publish registry (Azure Artifacts coordinates, or npm).\n *\n * @remarks\n * Azure Artifacts needs three coordinates (organization/project/feed) to build\n * the registry URL; public npm needs nothing further.\n *\n * @param fallbackOrganization - Default Azure DevOps organization, if any.\n * @returns The resolved registry configuration.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. when stdin is\n * not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptRegistry (fallbackOrganization?: string): Promise<RegistryConfig> {\n const kind = await select<RegistryConfig['kind']>({\n message: 'Package registry for publishable libraries',\n choices: [\n { name: 'Azure Artifacts', value: 'azure-artifacts' },\n { name: 'Public npm', value: 'npm' },\n ],\n })\n\n if (kind === 'npm') {\n return { kind }\n }\n\n return {\n kind,\n organization: await promptText('Azure DevOps organization', fallbackOrganization),\n project: await promptText('Azure DevOps project'),\n artifactsFeed: await promptText('Artifacts feed name'),\n }\n}\n\n/**\n * Prompts for which CI provider(s) to write a pipeline file for.\n *\n * @remarks\n * Azure Pipelines is listed first (and stays the `--yes` default) since it\n * is the long-standing default; GitHub Actions and \"both\" let a\n * GitHub-hosted workspace skip the unused Azure file, or carry both while\n * migrating between the two.\n *\n * @param None - this function takes no parameters.\n * @returns The chosen CI provider.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptCi (): Promise<CiProvider> {\n return await select<CiProvider>({\n message: 'CI provider',\n choices: [\n { name: 'Azure Pipelines', value: 'azure' },\n { name: 'GitHub Actions', value: 'github' },\n { name: 'Both', value: 'both' },\n ],\n })\n}\n\n/**\n * Prompts whether to connect the new workspace to Nx Cloud.\n *\n * @remarks\n * Defaults to `false` (declined) — an external service enrollment is not\n * something to opt a user into silently. Only reached on the interactive\n * path; `--yes`/an explicit `--nx-cloud` flag skip straight past this.\n *\n * @param None - this function takes no parameters.\n * @returns Whether to connect to Nx Cloud.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptNxCloud (): Promise<boolean> {\n return await confirm({\n message: 'Connect this workspace to Nx Cloud (remote caching + CI insights)?',\n default: false,\n })\n}\n\n/**\n * Prompts for the stack: unit-test runner and linter.\n *\n * @remarks\n * The two knobs asked up front at `mnci new`. TypeScript is fixed (the\n * `--preset=ts` premise, pinned to the TS 6 that Nx 23 supports).\n *\n * The linter choice carries the formatter with it, which is why it is one\n * question and not two: ESLint owns linting and formatting alike, and both\n * are the same seven JavaScript Standard Style options. Choosing is about speed\n * and toolchain, never about how the code ends up looking.\n *\n * The oxlint choice is deliberately labelled as a hybrid rather than as a\n * replacement, because that is what it is — see {@link StackConfig}. Presenting\n * it as \"oxlint instead of ESLint\" would be a false description of what the\n * workspace gets, and would make the ESLint config it still writes look like a\n * bug.\n *\n * @param None - this function takes no parameters.\n * @returns The resolved stack configuration.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptStack (): Promise<StackConfig> {\n const testRunner = await select<StackConfig['testRunner']>({\n message: 'Unit-test runner',\n choices: [\n { name: 'Jest', value: 'jest' },\n { name: 'Vitest', value: 'vitest' },\n ],\n })\n\n return { testRunner }\n}\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\n/**\n * Reads and parses a JSON file.\n *\n * @remarks\n * No safety net by design: a missing or malformed file is a real error the\n * caller should see, not silently default away.\n *\n * @param path - Absolute path to the JSON file.\n * @returns The parsed value, typed by the caller.\n * @throws Propagates any Node.js `fs` error (e.g. file not found) or JSON\n * parse error raised by the underlying read.\n * @typeParam T - The expected shape of the parsed JSON.\n */\nexport function readJson<T> (path: string): T {\n return JSON.parse(readFileSync(path, 'utf8')) as T\n}\n\n/**\n * Strips `//` and `/* *\\/` comments from a JSONC document, leaving every\n * string literal untouched.\n *\n * @remarks\n * A single-pass tokenizer rather than a regex, because a regex cannot tell a\n * `//` inside a string (a URL in a settings value, say) from a real comment\n * without tracking string state — and getting that wrong corrupts the value\n * instead of merely failing to strip a comment. String state itself has to\n * track escape sequences (`\\\"` does not end the string, `\\\\` does not escape\n * the character after it) or an escaped backslash immediately before a\n * closing quote is misread as escaping the quote.\n *\n * Comments are dropped rather than replaced with whitespace: the result is\n * only ever fed to `JSON.parse`, never written back verbatim, so preserving\n * line numbers or layout has no reader to matter to.\n *\n * @param text - The JSONC document's raw text.\n * @returns The same text with every comment removed.\n * @throws Never - performs a pure character scan.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction stripJsonComments (text: string): string {\n let result = ''\n let inString = false\n let inLineComment = false\n let inBlockComment = false\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i]\n\n if (inLineComment) {\n if (char === '\\n') {\n inLineComment = false\n result += char\n }\n continue\n }\n\n const next = text[i + 1]\n\n if (inBlockComment) {\n if (char === '*' && next === '/') {\n inBlockComment = false\n i++\n }\n continue\n }\n if (inString) {\n result += char\n if (char === '\\\\') {\n result += next\n i++\n continue\n }\n if (char === '\"') {\n inString = false\n }\n continue\n }\n if (char === '\"') {\n inString = true\n result += char\n continue\n }\n if (char === '/' && next === '/') {\n inLineComment = true\n i++\n continue\n }\n if (char === '/' && next === '*') {\n inBlockComment = true\n i++\n continue\n }\n result += char\n }\n\n return result\n}\n\n/**\n * Reads a `.code-workspace` file, tolerating the JSONC dialect VS Code writes:\n * comments and trailing commas.\n *\n * @remarks\n * `.code-workspace` is VS Code's own JSONC dialect, not strict JSON — VS Code\n * itself writes trailing commas into it, and a hand-maintained file routinely\n * carries `//`/`/* *\\/` comments explaining a setting, since VS Code's own\n * editor accepts them there. `JSON.parse` rejects both outright, so reading\n * this file strictly means mnci breaks on exactly the workspace file a real\n * user has actually touched.\n *\n * Comments used to be left unstripped, on the reasoning that \"mnci never\n * writes one, and a file with hand-added comments predates this handling\n * regardless\" — true, and beside the point: predating this function's\n * handling does not stop a workspace from HAVING one. A single comment\n * anywhere in the file made `JSON.parse` throw, which every caller treats as\n * \"nothing to preserve\" and merges against `{}` — silently discarding\n * `folders`, `settings`, `extensions` and `launch` wholesale, and every task\n * `registerProjectCommands` had ever registered, the first time `mnci add` or\n * `mnci upgrade` touched a workspace whose `.code-workspace` had a comment in\n * it. Reproduced on a file with a single `// eslint settings` comment line.\n *\n * Comments are stripped before the trailing-comma pass, deliberately: a line\n * comment on its own line right before a closing brace leaves a trailing\n * comma behind once removed, which the second pass still needs to catch.\n *\n * Shared by the two layers that both have to read this file: `applyOverlay`\n * (which must preserve the `tasks` array when it rewrites the rest) and\n * `registerProjectCommands` (which appends to it). One implementation, because\n * two would drift on exactly the dialect quirk that motivated it.\n *\n * @param path - Absolute path to the `.code-workspace` file.\n * @returns The parsed value, or `undefined` when the file is missing or\n * unparseable — callers treat both as \"nothing to preserve\".\n * @throws Never - an unreadable or malformed file yields `undefined`.\n * @typeParam T - The expected shape of the parsed JSON.\n */\nexport function readCodeWorkspace<T> (path: string): T | undefined {\n try {\n const withoutComments = stripJsonComments(readFileSync(path, 'utf8'))\n\n return JSON.parse(withoutComments.replaceAll(/,(\\s*[}\\]])/g, '$1')) as T\n } catch {\n return undefined\n }\n}\n\n/**\n * Serialises a value as 2-space-indented JSON with a trailing newline.\n *\n * @remarks\n * Matches the formatting `create-nx-workspace` itself emits, so patched files\n * stay diff-friendly.\n *\n * @param value - The value to serialise.\n * @returns The JSON text.\n * @throws Propagates any `JSON.stringify` error (e.g. circular references).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function toJson (value: unknown): string {\n return `${JSON.stringify(value, undefined, 2)}\\n`\n}\n\n/**\n * Writes a file, creating any missing parent directories first.\n *\n * @remarks\n * Used for every overlay write, so callers never worry about directory\n * existence (e.g. `.husky/` on a fresh workspace).\n *\n * @param path - Absolute path to write.\n * @param content - The file content.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error (e.g. permission denied).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function writeFileEnsured (path: string, content: string): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n\n/**\n * Marks a file as executable (0o755) — used for git hooks.\n *\n * @remarks\n * Git only runs hooks with the executable bit set; a plain write loses it.\n *\n * @param path - Absolute path of the file to mark.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error (e.g. file not found).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markExecutable (path: string): void {\n chmodSync(path, 0o755)\n}\n\n/**\n * Whether a path exists on disk.\n *\n * @remarks\n * Thin wrapper kept for symmetry with the other helpers (and mockability).\n *\n * @param path - Absolute path to test.\n * @returns `true` when the path exists.\n * @throws Never - `existsSync` swallows errors.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function fileExists (path: string): boolean {\n return existsSync(path)\n}\n","/**\n * Validates a workspace or project name before it's used anywhere.\n *\n * @remarks\n * A name becomes, without any transformation, a filesystem path segment\n * (`apps/<name>`), an argv token passed straight to Nx generators, and — for\n * Python and Dart kinds, once hyphens and dots become underscores — a module or\n * package identifier. The charset is what keeps it safe in all of those roles at\n * once: lowercase letters, digits, hyphens and dots, starting with a letter.\n *\n * **Dots are permitted but constrained**, and the constraints are the load-bearing\n * part. A dot is safe in most roles a name plays — a path segment, an npm package\n * name (`socket.io` is precedent), an Nx project name, and the `<kind>-<name>.zip`\n * drop basename CI derives build tags from — but four forms are not, so each is\n * rejected here rather than left to fail later and further away:\n *\n * - a **leading** dot would make a hidden directory (and `.`/`..` are the\n * traversal segments themselves)\n * - a **trailing** dot cannot appear in a git ref, which `nx release`'s\n * `{projectName}@{version}` tag is\n * - **consecutive** dots are likewise forbidden in a git ref\n * - a name that is *only* dots has no identifier to derive at all\n *\n * The regex encodes this by requiring at least one `[a-z0-9-]` after every dot,\n * which rules out all four without a separate check per case.\n *\n * Note that the two kinds whose ecosystems reject dots outright do **not** get a\n * narrower charset here — they transform instead: `pythonModuleDirectory` and\n * `dartPackageName` map `.` to `_` exactly as they already do for `-`, since a\n * dot is Python's package separator and pub rejects it. Go needs nothing: Nx's\n * own `names()` helper already treats a dot as a word separator, so\n * `@nx-go/nx-go`'s `package` clause for `my.lib` is a valid `mylib` (verified\n * empirically against the installed plugin, not assumed).\n *\n * Applied uniformly regardless of where the name came from (a CLI flag/\n * positional argument, or an interactive prompt) — `promptText`'s own\n * non-empty check only runs on the *prompted* path, so an explicitly empty\n * flag (`mnci add react-app \"\"`) previously slipped straight through, since\n * `name ?? await promptText(...)` only substitutes on `null`/`undefined`, not\n * `''`. Calling this right after resolving the name closes that gap too — an\n * empty string fails the same charset check as any other invalid name.\n *\n * @param name - The candidate workspace or project name.\n * @param label - What the name is for, used in the error message (e.g.\n * `'Workspace name'`, `'Project name'`).\n * @returns Nothing.\n * @throws Error when the name is empty, contains a character outside\n * `[a-z0-9-.]`, doesn't start with a lowercase letter, or uses a dot in one of\n * the four rejected positions described above.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function assertValidProjectName (name: string, label: string): void {\n if (!/^[a-z][a-z0-9-]*(?:\\.[a-z0-9-]+)*$/.test(name)) {\n throw new Error(\n `${label} '${name}' is invalid — use lowercase letters, digits, hyphens and dots, starting with a letter, with no leading, trailing or repeated dot (e.g. 'my-project', 'my.service').`,\n )\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport { DOTNET_SDK_VERSION, NUGET_AZURE_SOURCE, nugetConfigContent, readMnciConfig } from '../workspace-overlay'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addProjectJsonTargets,\n defaultScope,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n type AddOptions,\n} from './post-generation.use-case'\n\n/**\n * The `dotnet new` template mnci passes straight through to the .NET CLI.\n *\n * @remarks\n * Mirrors `NodeFramework`: mnci has no opinion of its own here, it is a thin\n * pass-through to the underlying tool's own template names, the same way\n * `node.ts` passes `--framework` straight to `@nx/node:application` without\n * any framework-specific logic of its own. `console` is the default — the\n * least opinionated shape, matching `node-app`'s `none` default. `webapi` is\n * ASP.NET Core's REST API template; `worker` is the long-running\n * background-service template (the .NET analogue of a Node daemon).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type DotnetTemplate = 'console' | 'webapi' | 'worker'\n\n/**\n * Fails fast, with an install hint, when the .NET SDK is not on the PATH.\n *\n * @remarks\n * Mirrors `ensureGo` in `add/go.ts`: probed before any scaffold or plugin\n * install so a missing toolchain surfaces as one clear sentence rather than\n * an opaque `dotnet: command not found` from deep inside a shelled-out call.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `dotnet` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureDotnet (workspaceRoot: string): void {\n if (runShell('dotnet', ['--version'], workspaceRoot) !== 0) {\n throw new Error('.NET SDK not found. Install .NET 8+ first: https://dotnet.microsoft.com/download')\n }\n}\n\n/**\n * The target framework moniker `dotnet new` scaffolds every C# project\n * against, derived from {@link DOTNET_SDK_VERSION}.\n *\n * @remarks\n * Derived rather than hardcoded a second time, so bumping the SDK constant\n * reaches every newly scaffolded project for free — the same drift\n * `NODE_VERSION` already exists to prevent for Node, applied here before it\n * has the chance to happen twice.\n *\n * @returns A TFM string (e.g. `net10.0`).\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction targetFramework (): string {\n const [major, minor] = DOTNET_SDK_VERSION.split('.', 2)\n\n return `net${major}.${minor}`\n}\n\n/**\n * Converts mnci's kebab-case project name to the PascalCase .NET convention\n * for a project/namespace/assembly identity.\n *\n * @remarks\n * The directory stays kebab-case (`apps/<name>`, matching every other kind);\n * only the `.csproj`'s own name — and the C# namespace `dotnet new` derives\n * from it — changes shape. Mirrors `pythonModuleDirectory`'s kebab-to\n * -language-convention conversion in `python.ts` (there: kebab to\n * `snake_case`; here: kebab to `PascalCase`), each language kind converting\n * to whatever its own ecosystem actually expects rather than mnci imposing\n * one casing convention workspace-wide.\n *\n * @param name - The kebab-case project name (already validated).\n * @returns The PascalCase equivalent `dotnet new` writes as `-n`.\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pascalCase (name: string): string {\n return name\n .split('-')\n .map(part => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\n/**\n * Scaffolds a .NET project with the real `dotnet new` CLI.\n *\n * @remarks\n * `@nx/dotnet` ships no scaffolding generator at all — verified against the\n * real published package (23.2.0), not assumed from documentation: it is\n * purely a project-graph inference plugin, discovering targets from whatever\n * `.csproj` already exists on disk via a bundled MSBuild analyzer. Its own\n * README documents the workflow as `dotnet new <template> -n <Name>`, then\n * `nx build <project>` — there is no Nx generator standing in for the step\n * other kinds delegate to Nx entirely, so this shells out directly instead.\n * The same pattern `@mnci/nx-flutter`'s own generators already use\n * internally for `flutter create`, run from a `GeneratorCallback` because it\n * writes to the real filesystem rather than an Nx `Tree` — here there is no\n * Tree at all, since there is no generator wrapping the call.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - Workspace-relative directory (e.g. `apps/<name>`).\n * @param identity - The project/namespace/assembly identity `dotnet new`\n * writes as `-n` — PascalCase for an app, `<PascalScope>.<PascalName>` for a\n * publishable lib (see {@link addCsharpLib}).\n * @param template - The `dotnet new` template. A bare `string`, not\n * {@link DotnetTemplate}: that type is the user-facing app choice\n * (mirroring `NodeFramework`), while a lib's template (`classlib`) is fixed\n * and internal — never something `csharp-lib` exposes a flag for.\n * @returns Nothing.\n * @throws Error when the underlying `dotnet new` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction scaffoldDotnetProject (\n workspaceRoot: string,\n projectRoot: string,\n identity: string,\n template: string,\n): void {\n if (\n runShell(\n 'dotnet',\n ['new', template, '-n', identity, '-o', projectRoot, '--framework', targetFramework()],\n workspaceRoot,\n ) !== 0\n ) {\n throw new Error(`dotnet new ${template} failed for ${projectRoot}`)\n }\n}\n\n/**\n * The `package` target for a C# app: publish, then zip, into the drop.\n *\n * @remarks\n * Deliberately runs its own `dotnet publish` rather than depending on\n * `@nx/dotnet`'s inferred `publish` target and zipping wherever it happened\n * to write. That target's exact executor and output layout are decided\n * inside the plugin's bundled MSBuild analyzer — a compiled `.dll` this\n * package cannot introspect the way it inspects the plugin's own JS, and\n * this environment has no live .NET SDK to run it against and observe. An\n * explicit, mnci-controlled output path sidesteps that uncertainty entirely\n * — the same reasoning {@link goPackageTarget} already applies by zipping a\n * fixed `dist/apps/<name>` rather than trusting `@nx-go/nx-go`'s own build\n * target to land somewhere predictable.\n *\n * **Unverified without a real SDK — the first thing to confirm once the\n * gated e2e (which skips this section entirely where `dotnet` is absent) can\n * actually run it.** If `@nx/dotnet`'s inferred `publish` target turns out\n * reliable in practice, depending on it and dropping this project's own\n * `dotnet publish` call would be the simplification to make.\n *\n * @param tag - The drop basename prefix (`csharp-app` or `csharp-function-app`).\n * @param projectRoot - Workspace-relative project directory.\n * @param name - The C# app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpAppPackageTarget (\n tag: string,\n projectRoot: string,\n name: string,\n): Record<string, unknown> {\n const outDir = `dist/apps/${name}`\n const zip = `dist/drop/${tag}-${name}.zip`\n const command = `node -e \"const cp=require('node:child_process');const r=cp.spawnSync('dotnet',['publish','${projectRoot}','-c','Release','-o','${outDir}'],{stdio:'inherit',shell:true});if(r.status!==0)process.exit(r.status??1);const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('${outDir}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The `start` target for a C# app: `dotnet run`, locally.\n *\n * @remarks\n * `dotnet run` builds and runs from source in one step — unlike\n * {@link csharpAppPackageTarget}'s `publish`, no separate build/`dependsOn`\n * is needed. `continuous: true` marks it as a long-running dev task, the\n * same shape every other kind's custom `start` target uses (see\n * `goStartTarget`).\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpAppStartTarget (projectRoot: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'dotnet run', cwd: projectRoot },\n }\n}\n\n/**\n * Adds a C# app under `apps/`, scaffolded with the real `dotnet new` CLI.\n *\n * @remarks\n * Ensures the toolchain and the `@nx/dotnet` inference plugin are present,\n * scaffolds the project directly (see {@link scaffoldDotnetProject} for why\n * there is no Nx generator to delegate to), then layers mnci's own\n * `package`/`start` targets on top via a `project.json` this call creates —\n * `@nx/dotnet` writes none, since `build`/`test`/`restore`/etc. are all\n * inferred from the `.csproj` alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param template - The `dotnet new` template (defaults to a bare console app,\n * mirroring `node-app`'s `none`-by-default framework choice).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpApp (\n workspaceRoot: string,\n name: string,\n template: DotnetTemplate = 'console',\n): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n ensureAdmZip(workspaceRoot)\n\n const projectRoot = `apps/${name}`\n scaffoldDotnetProject(workspaceRoot, projectRoot, pascalCase(name), template)\n addProjectJsonTargets(join(workspaceRoot, projectRoot, 'project.json'), {\n package: csharpAppPackageTarget('csharp-app', projectRoot, name),\n start: csharpAppStartTarget(projectRoot),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Converts a workspace scope (`@demo`, `@my-org`) to the PascalCase prefix a\n * NuGet `PackageId` conventionally uses (`Demo`, `MyOrg`).\n *\n * @remarks\n * NuGet has no `@scope/name` convention the way npm does — package IDs are\n * flat, dot-separated strings (`Company.Product`), so the workspace scope is\n * folded into the project's own identity at `dotnet new` time\n * (`<PascalScope>.<PascalName>`) rather than written into any manifest field\n * afterwards. `dotnet new classlib -n Demo.Sdk` sets the assembly name, the\n * root namespace AND the default `PackageId` all in one step, which is also\n * why {@link addCsharpLib} needs no post-generation manifest repair the way\n * `npm-lib` does — there is no separate \"importPath\" field to get wrong.\n *\n * @param scope - The workspace scope, e.g. `@demo`.\n * @returns The PascalCase prefix, e.g. `Demo`.\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pascalScope (scope: string): string {\n return pascalCase(scope.replace(/^@/, ''))\n}\n\n/**\n * The workspace-relative path every publishable `csharp-lib`'s\n * `release.version.versionActions` points at.\n *\n * @remarks\n * Not a package specifier, deliberately: Nx resolves that config value in\n * two ways (`nx/dist/.../version-actions.js`, `resolveVersionActionsPath`,\n * read directly rather than assumed) — first as a module specifier via\n * `require.resolve(path)`, then, when that fails, as\n * `require.resolve(join(workspaceRoot, path))`. The second form is exactly\n * what {@link CSHARP_VERSION_ACTIONS} needs and nothing more: a shared,\n * hand-written file `mnci add csharp-lib` writes straight into the\n * generated workspace, with no sixth mnci package to scaffold, publish and\n * keep versioned just to carry one class — unlike `@mnci/nx-python-pip` and\n * `@mnci/nx-flutter`, which exist because a whole generator/executor surface\n * was missing, not one release hook.\n */\nconst CSHARP_VERSION_ACTIONS_PATH = 'tools/csharp-version-actions.cjs'\n\n/**\n * The `tools/csharp-version-actions.cjs` file every `csharp-lib` shares.\n *\n * @remarks\n * `@nx/dotnet` supplies build/test/pack inference but no `versionActions` at\n * all — verified against the real published package, the same way its\n * generator-less shape was verified (see {@link scaffoldDotnetProject}'s\n * remarks). Without this, `nx release` falls back to its npm default, which\n * looks for a `package.json` a `.csproj` project does not have, and aborts\n * while building the release graph — the same failure mode already fixed\n * for `go-lib` (see `RELEASE_CONFIG`'s `!tag:type:go-lib` exclusion in\n * `overlay.ts`). Exclusion is the wrong fix here, unlike Go: a `go-lib` has\n * no independent version because mnci puts every Go project in one shared\n * `go.mod`, but every `csharp-lib` is its own `.csproj` with its own\n * independently published NuGet identity — excluding it would silently stop\n * versioning packages the user explicitly asked to publish.\n *\n * **The `.csproj` filename varies per project** (the PascalCase scope+name\n * identity, not a fixed name the way `pubspec.yaml`/`package.json` are), so\n * this globs the project root for the first `*.csproj` rather than\n * `nx/release`'s built-in `validManifestFilenames` auto-discovery, which\n * only matches an exact filename — the reason `validate()` is overridden\n * too, rather than left to the base class's default (which would silently\n * find nothing to validate, since it drives off that same exact-match list).\n *\n * **`readCurrentVersionFromRegistry` is never actually called for these\n * projects** — verified by reading `nx`'s own release source\n * (`resolve-current-version.js`), not assumed: `RELEASE_CONFIG` sets\n * `version.conventionalCommits: true`, which makes Nx default\n * `currentVersionResolver` to `'git-tag'`, and that path reads the version\n * straight off the matching `{projectName}@{version}` tag — it only calls\n * into `readCurrentVersionFromSourceManifest` as the disk fallback when no\n * tag exists yet (the first release) or a clone has no tags fetched (see\n * `mnci-details.md`'s \"run `git fetch --tags` first\" troubleshooting entry).\n * So a real NuGet registry query here would be dead code today; overridden\n * anyway, with a null-and-explain return, purely to satisfy the base\n * class's contract — the exact shape `DartVersionActions` already uses for\n * the same reason (Dart's Flutter packages are git-tag-only for a different\n * cause, no pub.dev feed at all, but the resulting method body is\n * identical in spirit).\n *\n * Written as `.cjs`, not `.ts` or a bare `.js`: Nx `require()`s this file\n * directly (`requireWithTsconfigFallback`, not `loadTsFile` — that branch is\n * keyed on a `.ts`/`.cts`/`.mts` extension), and `.cjs` resolves as\n * CommonJS regardless of whichever `\"type\"` the generated root\n * `package.json` declares, so there is nothing here to get wrong by an\n * ESM/CJS mismatch two files can drift on independently.\n *\n * **Unverified without a real SDK/Nx release run** — the same caveat\n * {@link csharpAppPackageTarget} and `csharpFunctionAppCsproj` already carry.\n * `nx release --dry-run` against a real generated workspace with a real\n * `csharp-lib` is what the gated e2e (tracked separately) exists to confirm.\n */\nexport const CSHARP_VERSION_ACTIONS = `'use strict'\n\nconst { posix } = require('node:path')\nconst { VersionActions } = require('nx/release')\n\nconst VERSION_TAG = /<Version>([^<]*)<\\\\/Version>/\n\n/*\n * posix.join, never plain join. An Nx Tree path is workspace-relative and\n * forward-slashed on EVERY platform, while join emits a backslashed path on\n * Windows. This value is handed back to nx release for changelog and manifest\n * bookkeeping, and interpolated into the user-facing error below, so it was\n * wrong on every Windows machine. The same bug was already fixed in the\n * Python and Flutter plugins' release actions; this third copy was missed.\n */\nfunction findCsproj (tree, root) {\n const name = tree.children(root).find((entry) => entry.endsWith('.csproj'))\n\n return name ? posix.join(root, name) : null\n}\n\n/**\n * Hand-written Nx release VersionActions for a publishable C# library: reads\n * and writes the <Version> element of the project's own .csproj. Written by\n * \\`mnci add csharp-lib\\` — see that command's source for why this is a\n * workspace file rather than an npm package.\n */\nclass CsharpVersionActions extends VersionActions {\n validManifestFilenames = ['*.csproj']\n\n async readCurrentVersionFromSourceManifest (tree) {\n const manifestPath = findCsproj(tree, this.projectGraphNode.data.root)\n if (!manifestPath) {\n return null\n }\n const contents = tree.read(manifestPath, 'utf8') ?? ''\n const match = VERSION_TAG.exec(contents)\n if (!match || !match[1]) {\n throw new Error(\\`Could not find a <Version> element in \\${manifestPath}\\`)\n }\n\n return { currentVersion: match[1], manifestPath }\n }\n\n async readCurrentVersionFromRegistry (_tree, _currentVersionResolverMetadata) {\n return {\n currentVersion: null,\n logText: 'C# libraries resolve their current version from git tags, not a registry lookup',\n }\n }\n\n async readCurrentVersionOfDependency (_tree, _projectGraph, _dependencyProjectName) {\n return { currentVersion: null, dependencyCollection: null }\n }\n\n async updateProjectVersion (tree, newVersion) {\n const manifestPath = findCsproj(tree, this.projectGraphNode.data.root)\n if (!manifestPath) {\n throw new Error(\\`No .csproj found in \\${this.projectGraphNode.data.root}\\`)\n }\n const contents = tree.read(manifestPath, 'utf8') ?? ''\n if (!VERSION_TAG.test(contents)) {\n throw new Error(\\`Could not find a <Version> element in \\${manifestPath}\\`)\n }\n tree.write(manifestPath, contents.replace(VERSION_TAG, () => \\`<Version>\\${newVersion}</Version>\\`))\n\n return [\\`Updated \\${manifestPath} to version \\${newVersion}\\`]\n }\n\n async updateProjectDependencies (_tree, _projectGraph, _dependenciesToUpdate) {\n return []\n }\n\n async validate (tree) {\n if (!findCsproj(tree, this.projectGraphNode.data.root)) {\n throw new Error(\\`The project \"\\${this.projectGraphNode.name}\" does not have a .csproj file available in \\${this.projectGraphNode.data.root}\\`)\n }\n }\n}\n\nmodule.exports = CsharpVersionActions\n`\n\n/**\n * Writes the shared `tools/csharp-version-actions.cjs` file, idempotently.\n *\n * @remarks\n * Identical content on every call, so an unconditional overwrite on each\n * `mnci add csharp-lib` is safe — the same idempotency every other\n * mnci-owned file in a generated workspace already relies on.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction writeCsharpVersionActions (workspaceRoot: string): void {\n writeFileEnsured(join(workspaceRoot, CSHARP_VERSION_ACTIONS_PATH), CSHARP_VERSION_ACTIONS)\n}\n\n/**\n * Points a project's `project.json` at {@link CSHARP_VERSION_ACTIONS_PATH}\n * for `nx release`.\n *\n * @remarks\n * Mirrors `addProjectJsonTargets` in `shared.ts` — same tolerant-of-a-missing-file\n * read, same merge-not-replace shape — but merges into `release.version`\n * rather than `targets`, so it stays a small sibling function rather than a\n * parameter added to that one to cover a single caller.\n *\n * @param projectJsonPath - Absolute path to the project's `project.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addProjectJsonReleaseVersionActions (projectJsonPath: string): void {\n const project = fileExists(projectJsonPath)\n ? readJson<Record<string, unknown>>(projectJsonPath)\n : {}\n const release = (project.release as Record<string, unknown> | undefined) ?? {}\n const version = (release.version as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(\n projectJsonPath,\n toJson({\n ...project,\n release: { ...release, version: { ...version, versionActions: CSHARP_VERSION_ACTIONS_PATH } },\n }),\n )\n}\n\n/**\n * Adds an explicit `<Version>0.1.0</Version>` to a freshly scaffolded\n * `.csproj`, inside its first `<PropertyGroup>`.\n *\n * @remarks\n * `dotnet new classlib` writes no `<Version>` element at all — NuGet\n * defaults an absent one to `1.0.0` implicitly, which is a fine publish\n * default but a bad *first* one for {@link CSHARP_VERSION_ACTIONS} to read:\n * an explicit starting value here is what lets that class read/write a real\n * element from the very first `nx release`, rather than special-casing a\n * project whose manifest declares no version yet — the same reason `npm-lib`\n * and `python-lib` both start their manifests with an explicit version\n * rather than leaving it to a generator default.\n *\n * @param csprojPath - Absolute path to the freshly scaffolded `.csproj`.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addInitialVersion (csprojPath: string): void {\n const contents = readFileSync(csprojPath, 'utf8')\n writeFileEnsured(csprojPath, contents.replace('<PropertyGroup>', '<PropertyGroup>\\n <Version>0.1.0</Version>'))\n}\n\n/**\n * The `nx-release-publish` target for a `csharp-lib`: `dotnet pack` then\n * `dotnet nuget push` every produced `.nupkg`.\n *\n * @remarks\n * `nx release publish` requires the target literally named\n * `nx-release-publish` — confirmed by reading Nx's own release source\n * (`publish.js`), not assumed — and skips a project silently if it lacks\n * one, UNLESS every matched project lacks it, which throws for the whole\n * `nx release publish` run. So this target is always present, never\n * conditioned on the registry choice at generation time — the choice is\n * read at RUNTIME instead, from whether `NUGET_PAT` is set (only true when\n * {@link nugetPublishEnvFragment} in `overlay.ts` exported it, which only\n * happens for the Azure Artifacts registry). That keeps this target free of\n * any {@link RegistryConfig} of its own, the same way Python's publish\n * target carries no registry specifics — its `TWINE_*` env vars are\n * injected only at CI release time too.\n *\n * Runs its own `dotnet pack` into an mnci-controlled `dist/` directory\n * rather than depending on `@nx/dotnet`'s inferred `pack` target's output\n * location — the same reasoning {@link csharpAppPackageTarget} already\n * applies for `dotnet publish`, unverifiable here without a live SDK.\n *\n * `--source` names {@link NUGET_AZURE_SOURCE}, the fixed key\n * {@link nugetConfigContent} registers the feed under — `--api-key` is a\n * required-but-ignored placeholder for an Azure Artifacts feed (confirmed\n * from Microsoft's own \"Publish NuGet packages with dotnet CLI\" guide:\n * \"you can use any string as its value\"); the real auth is the\n * `packageSourceCredentials` entry keyed to that same source.\n *\n * **Unverified without a real SDK/Azure feed** — the same caveat every\n * other C# kind carries; the gated e2e (tracked separately) is what\n * confirms it.\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpLibPublishTarget (projectRoot: string): Record<string, unknown> {\n const outDir = `${projectRoot}/dist`\n const command = `node -e \"if(!process.env.NUGET_PAT){console.log('NuGet publish is not configured for this registry choice (no NUGET_PAT) - skipping. Regenerate with --registry azure-artifacts, or run dotnet nuget push manually.');process.exit(0)}const cp=require('node:child_process');const pack=cp.spawnSync('dotnet',['pack','${projectRoot}','-c','Release','-o','${outDir}'],{stdio:'inherit',shell:true});if(pack.status!==0)process.exit(pack.status??1);const fs=require('node:fs');for(const pkg of fs.globSync('${outDir}/*.nupkg')){const push=cp.spawnSync('dotnet',['nuget','push',pkg,'--source','${NUGET_AZURE_SOURCE}','--api-key','AZ'],{stdio:'inherit',shell:true});if(push.status!==0)process.exit(push.status??1)}\"`\n\n return { executor: 'nx:run-commands', options: { command } }\n}\n\n/**\n * Adds a publishable C# library under `packages/`: `dotnet new classlib`.\n *\n * @remarks\n * The scope is resolved exactly the way `addNpmLib` resolves it: an explicit\n * `--scope` wins; otherwise the flag path (`kindProvided`) defaults it\n * silently, while the interactive/bare path prompts for it (with the\n * workspace's own scope as the default) — one shared UX across every\n * publishable-lib kind, not a C#-specific decision.\n *\n * No `package`/zip target, unlike {@link addCsharpApp}: a publishable lib's\n * distribution path is `nx release` (NuGet publish), the same as\n * `npm-lib`/`python-lib`, never the `dist/drop` zip convention that exists\n * for apps. `@nx/dotnet` already infers a `pack` target from the `.csproj`\n * alone, so there is nothing extra to wire here.\n *\n * Wires `nx release` itself: an explicit `<Version>0.1.0</Version>` (see\n * {@link addInitialVersion}) plus a project-level `release.version.versionActions`\n * override pointing at the shared {@link CSHARP_VERSION_ACTIONS_PATH} (see\n * that constant's remarks for why a `.csproj` lib needs one at all, and why\n * it is a workspace file rather than a sixth mnci package), and an\n * `nx-release-publish` target (see {@link csharpLibPublishTarget}) so\n * `nx release` actually pushes the package, not just versions and tags it.\n *\n * Also (re)writes the shared root `nuget.config` from the workspace's\n * persisted registry choice ({@link readMnciConfig}) — the same \"written by\n * `mnci add` on the first project of that kind\" lifecycle\n * `requirements-dev.txt` already has for Python, since a JS-only or\n * Go/Flutter-only workspace has no reason to carry a NuGet config file at\n * all. Falls back to the public npm registry when an older, pre-registry\n * workspace's `nx.json` carries no persisted choice, rather than assuming\n * Azure Artifacts credentials that were never collected.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @returns A promise that resolves when the scaffold has finished.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addCsharpLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n): Promise<void> {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('NuGet package scope for the published library', defaultScope(workspaceRoot)))\n\n const projectRoot = `packages/${name}`\n const identity = `${pascalScope(scope)}.${pascalCase(name)}`\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'classlib')\n addInitialVersion(join(workspaceRoot, projectRoot, `${identity}.csproj`))\n writeCsharpVersionActions(workspaceRoot)\n addProjectJsonReleaseVersionActions(join(workspaceRoot, projectRoot, 'project.json'))\n addProjectJsonTargets(join(workspaceRoot, projectRoot, 'project.json'), {\n 'nx-release-publish': csharpLibPublishTarget(projectRoot),\n })\n\n const registry = readMnciConfig(workspaceRoot).registry ?? { kind: 'npm' }\n writeFileEnsured(join(workspaceRoot, 'nuget.config'), nugetConfigContent(registry, scope))\n\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds an internal (never-published) C# library under `libs/`.\n *\n * @remarks\n * No scope prefix, unlike {@link addCsharpLib}: a `PackageId` only means\n * anything for something that gets published, and this never does — the\n * same reasoning `internal-lib`'s plain `@nx/js:lib --bundler=tsc` needs no\n * `--importPath` either. `build: false` in the `registerProjectCommands`\n * call for the same reason: consistent with `go-internal-lib`, an\n * internal-only library gets no root `<name>:build` script of its own.\n *\n * **Consuming it is a manual step, deliberately not automated here.**\n * `mnci add`'s own signature has no \"consumer\" argument for this kind (that\n * is `python-vendor`'s narrower job, wiring one named consumer at a time) —\n * every other internal-lib kind resolves automatically once generated\n * (TS via `node_modules` symlinks, Go via one shared module, Dart via the\n * pub workspace's plain version constraint), but C#'s `<ProjectReference>`\n * has no such implicit resolution: it is an explicit edit to the consuming\n * `.csproj`, which `dotnet add <consumer> reference <lib>` makes in one\n * command. The step is named for the user rather than skipped silently, the\n * same courtesy {@link addGoInternalLib}'s `goModulePath` message already\n * extends for Go's import path.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpInternalLib (workspaceRoot: string, name: string): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n\n const projectRoot = `libs/${name}`\n const identity = pascalCase(name)\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'classlib')\n registerProjectCommands(workspaceRoot, name, { build: false })\n logger.step(\n `Reference it from a consumer with: dotnet add <consumer>.csproj reference ${projectRoot}/${identity}.csproj`,\n )\n}\n\n/**\n * The `host.json` every generated Azure Functions app writes, C# included.\n *\n * @remarks\n * Identical to `NODE_FUNCTION_APP_HOST_JSON` — the extension bundle format is\n * the Functions runtime schema, not a language concern — duplicated rather\n * than imported across a `node.ts`/`csharp.ts` boundary that would otherwise\n * read as one language depending on another's internals for something\n * neither actually owns.\n */\nconst CSHARP_FUNCTION_APP_HOST_JSON = `{\n \"version\": \"2.0\",\n \"extensionBundle\": {\n \"id\": \"Microsoft.Azure.Functions.ExtensionBundle\",\n \"version\": \"[4.*, 5.0.0)\"\n }\n}\n`\n\n/**\n * The isolated-worker `.csproj`, overwriting whatever the base `console`\n * scaffold wrote.\n *\n * @remarks\n * `dotnet new` ships no Azure Functions template of its own — verified\n * against Microsoft's own current (2026) isolated-worker guide, not assumed:\n * a Functions app is an ordinary console app whose project file opts into\n * the `Azure.Functions.Sdk` MSBuild project SDK, the same \"scaffold the base\n * shape, then overlay the Functions-specific files\" split every other\n * `*-function-app` kind already uses (see `addNodeFunctionApp`).\n *\n * `Azure.Functions.Sdk` is the CURRENT recommended project shape (superseding\n * the older explicit `Microsoft.Azure.Functions.Worker.Sdk` package\n * reference plus hand-set `AzureFunctionsVersion`/`OutputType` properties):\n * it auto-configures both, is shorter, and is what Microsoft's own migration\n * guide moves existing projects TO — so new projects should start there\n * rather than at the thing that guide migrates away from.\n *\n * **The `FrameworkReference` is load-bearing, not a performance nicety —\n * found from a real `dotnet build` failure on the gated Windows e2e, then\n * confirmed by installing a real .NET 10 SDK and reproducing the exact\n * failure end to end (real `mnci add csharp-function-app` +\n * `nx run-many -t build`), not reasoned out from docs alone.** Without it,\n * `dotnet build` fails with a `CS0234` error naming the missing `AspNetCore`\n * namespace for every `using Microsoft.AspNetCore.Http;`/\n * `Microsoft.AspNetCore.Mvc;` line in {@link csharpFunctionAppHello} — with\n * both package references below already present. The reason: those types\n * live in the ASP.NET Core SHARED FRAMEWORK (`Microsoft.AspNetCore.App`),\n * not in any NuGet package. `Microsoft.NET.Sdk.Web`-based projects reference\n * that framework implicitly; `Azure.Functions.Sdk` does not, and neither\n * Functions package pulls it in as a transitive dependency — confirmed\n * against Microsoft's own docs, where the line only turns up in a separate\n * \"Performance optimizations\" section, framed as a cold-start improvement,\n * while the 4-step \"To enable ASP.NET Core integration for HTTP\" guide never\n * mentions it. It is both a perf knob and, for this SDK, a compile-time\n * requirement. Adding this line alone was not sufficient by itself — see\n * {@link addCsharpFunctionApp}'s remarks for the second, independent defect\n * (a stale restore) the same reproduction found.\n *\n * The package versions below are current as measured against Microsoft's\n * own docs and confirmed to exist via a direct NuGet API query\n * (`api.nuget.org/v3-flatcontainer/.../index.json`); the whole `.csproj`,\n * `Program.cs` and `Hello.cs` combination was then built end to end against\n * a real .NET 10.0.401 SDK, restore and all, not merely syntax-checked.\n *\n * @returns The `.csproj` XML content.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppCsproj (): string {\n return `<Project Sdk=\"Azure.Functions.Sdk/1.0.0\">\n <PropertyGroup>\n <TargetFramework>${targetFramework()}</TargetFramework>\n </PropertyGroup>\n\n <ItemGroup>\n <FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n <PackageReference Include=\"Microsoft.Azure.Functions.Worker\" Version=\"2.52.0\" />\n <PackageReference Include=\"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore\" Version=\"2.1.0\" />\n </ItemGroup>\n</Project>\n`\n}\n\n/**\n * The isolated-worker `Program.cs`, overwriting the base scaffold's `Main`.\n *\n * @remarks\n * `FunctionsApplication.CreateBuilder` + `ConfigureFunctionsWebApplication`\n * is the current `IHostApplicationBuilder` pattern (requires the 2.x worker\n * package, which {@link csharpFunctionAppCsproj} pins) — the ASP.NET Core\n * integration that lets a trigger use the ordinary `HttpRequest`/\n * `IActionResult` types in {@link csharpFunctionAppHello} rather than the\n * isolated-worker-specific `HttpRequestData`/`HttpResponseData` pair.\n */\nconst CSHARP_FUNCTION_APP_PROGRAM = `using Microsoft.Azure.Functions.Worker.Builder;\nusing Microsoft.Extensions.Hosting;\n\nvar builder = FunctionsApplication.CreateBuilder(args);\n\nbuilder.ConfigureFunctionsWebApplication();\n\nbuilder.Build().Run();\n`\n\n/**\n * The HTTP-triggered sample function written into a generated C# function app.\n *\n * @remarks\n * Mirrors `NODE_FUNCTION_APP_HELLO`'s role: a minimal, real handler so the\n * generated app is runnable rather than an empty shell, using the\n * ASP.NET-Core-integrated `HttpRequest`/`IActionResult` shape ASP.NET Core\n * integration.\n *\n * @param identity - The project's PascalCase identity, used as the namespace.\n * @returns The C# source for the sample function class.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppHello (identity: string): string {\n return `using Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Azure.Functions.Worker;\n\nnamespace ${identity};\n\npublic class Hello\n{\n [Function(\"Hello\")]\n public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, \"get\")] HttpRequest req)\n {\n return new OkObjectResult(\"Hello from mnci.\");\n }\n}\n`\n}\n\n/**\n * The `start` target for a C# function app: `dotnet run`, locally.\n *\n * @remarks\n * `Azure.Functions.Sdk` wires `dotnet run` to start the Functions host\n * directly when Azure Functions Core Tools (`func`) is installed — the same\n * assumption `nodeFunctionAppStartTarget`'s `func start` already makes, just\n * invoked through `dotnet` rather than `func` itself, since that is what\n * the project's own tooling now integrates with.\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppStartTarget (projectRoot: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'dotnet run', cwd: projectRoot },\n }\n}\n\n/**\n * Adds a C# Azure Function app: an isolated-worker overlay on the base\n * console scaffold.\n *\n * @remarks\n * Structurally the same split as `addNodeFunctionApp`/`addPythonFunctionApp`:\n * scaffold the plain app, then overwrite/add exactly the files a Functions\n * app needs (`.csproj`, `Program.cs`, one sample HTTP trigger, `host.json`).\n * No scope/`PackageId` concept, unlike {@link addCsharpLib} — a function app\n * is never NuGet-published, matching every other `*-function-app` kind.\n *\n * **The `dotnet restore` after overwriting `.csproj` is load-bearing, found\n * from a real, gated-e2e `dotnet build` failure, not written defensively.**\n * {@link scaffoldDotnetProject} restores once, but against the ORIGINAL\n * `console` template `Microsoft.NET.Sdk` project — the restore that\n * succeeds is for a project shape this function immediately throws away.\n * Nothing restores again after the overwrite, and `@nx/dotnet`'s own\n * inferred `build` target runs `dotnet build --no-restore --no-dependencies`\n * (verified by reading its actual command from a real `nx build` run), on\n * the assumption restore already happened for the CURRENT project content.\n * It hadn't: `Microsoft.Azure.Functions.Worker`/`.Extensions.Http.AspNetCore`\n * were never fetched, so the first real build failed with `CS0246`s for\n * `Function`/`HttpTrigger`/`AuthorizationLevel` — reproduced against a real\n * SDK installed for this fix, not assumed from the CI log alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin/scaffold/restore fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpFunctionApp (workspaceRoot: string, name: string): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n ensureAdmZip(workspaceRoot)\n\n const projectRoot = `apps/${name}`\n const identity = pascalCase(name)\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'console')\n\n const absoluteRoot = join(workspaceRoot, projectRoot)\n writeFileEnsured(join(absoluteRoot, `${identity}.csproj`), csharpFunctionAppCsproj())\n writeFileEnsured(join(absoluteRoot, 'Program.cs'), CSHARP_FUNCTION_APP_PROGRAM)\n writeFileEnsured(join(absoluteRoot, 'Hello.cs'), csharpFunctionAppHello(identity))\n writeFileEnsured(join(absoluteRoot, 'host.json'), CSHARP_FUNCTION_APP_HOST_JSON)\n\n if (runShell('dotnet', ['restore', projectRoot], workspaceRoot) !== 0) {\n throw new Error(`dotnet restore failed for ${projectRoot}`)\n }\n\n addProjectJsonTargets(join(absoluteRoot, 'project.json'), {\n package: csharpAppPackageTarget('csharp-function-app', projectRoot, name),\n start: csharpFunctionAppStartTarget(projectRoot),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n","import { existsSync, globSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport {\n fileExists,\n markExecutable,\n readCodeWorkspace,\n readJson,\n toJson,\n writeFileEnsured,\n} from '../file-system'\n\n/**\n * Where a generated monorepo publishes its npm packages.\n *\n * @remarks\n * Supports Azure Artifacts and the public npm registry. GitHub Packages is\n * out of scope for this cut.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type RegistryConfig =\n | { kind: 'azure-artifacts'; organization: string; project: string; artifactsFeed: string } |\n { kind: 'npm' }\n\n/**\n * Which CI provider(s) {@link applyOverlay} writes a pipeline file for.\n *\n * @remarks\n * `azure` (the default) writes only `azure-pipelines.yml`; `github` writes\n * only `.github/workflows/ci.yml`; `both` writes both — so a GitHub-hosted\n * repo can pick the provider it actually runs on instead of carrying an\n * unused Azure Pipelines file.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type CiProvider = 'azure' | 'github' | 'both'\n\n/**\n * The stack chosen at `mnci new` — asked up front, honoured by every `add`.\n *\n * @remarks\n * TypeScript is not a knob: every workspace runs the **dual compiler**\n * ({@link TS_COMPILER_DEPENDENCIES}) — TypeScript 6 for the programmatic API\n * (Nx's graph/plugins, Vite, typescript-eslint, the editor) and TypeScript 7's\n * native `tsc` for the `typecheck`/`build` tasks. The only stack knob is the\n * unit-test runner, persisted as Nx **generator defaults** in `nx.json`.\n * Linting AND formatting are ESLint (always) — there is no separate formatter.\n *\n * @typeParam None - this type has no generic type parameters.\n */\n/**\n * The parts of the toolchain a user chooses at `mnci new`.\n *\n * @remarks\n * Two knobs, deliberately. Everything else in an mnci workspace is fixed by the\n * `--preset=ts` premise, and each additional choice multiplies the matrix the\n * e2e has to cover — so a knob earns its place only when neither answer is\n * defensible for everyone. The test runner qualifies (Jest and Vitest are both\n * first-class in Nx) and so does the linter, now that the Rust toolchain is a\n * real alternative rather than an experiment.\n *\n * Persisted into `nx.json`'s `mnci` block, so `mnci upgrade` re-applies the\n * overlay for the stack the workspace actually chose instead of reverting it to\n * the defaults.\n */\nexport interface StackConfig {\n /** Unit-test runner (both Nx-native for the plugin kinds). */\n testRunner: 'jest' | 'vitest'\n}\n\n/**\n * The `--yes` / flagless defaults — the current opinionated stack.\n *\n * @remarks\n * Jest: the test runner existing generated repos (and the e2e suite)\n * already assume, so defaulting to it keeps behaviour unchanged when\n * the stack is not chosen explicitly. Linting and formatting are always ESLint.\n */\nexport const DEFAULT_STACK: StackConfig = { testRunner: 'jest' }\n\n/**\n * The dual TypeScript compiler stamped into every workspace's `devDependencies`.\n *\n * @remarks\n * TypeScript 7 is the native (Go) compiler: much faster, but it ships no\n * programmatic API yet, so tools that import `typescript` (Nx's\n * `@nx/js/typescript` plugin and project graph, Vite, typescript-eslint, the\n * editor language service) still need TypeScript 6. The\n * [Nx TS 7 guide](https://nx.dev/docs/technologies/typescript/guides/typescript-7)\n * solves this with two npm aliases: `typescript` resolves to a TS 6 package\n * (API intact, and its binary is `tsc6`, not `tsc`), while `@typescript/native`\n * provides the TS 7 `tsc`. The `@nx/js/typescript` plugin's inferred\n * `typecheck`/`build` tasks then run `tsc` = TS 7, while Nx analyses config\n * through the TS 6 API — automatically, with no target rewiring. Frozen per\n * repo by the committed lockfile, so `npm ci` reproduces it.\n */\nexport const TS_COMPILER_DEPENDENCIES: Record<string, string> = {\n '@typescript/native': 'npm:typescript@^7.0.2',\n 'typescript': 'npm:@typescript/typescript6@^6.0.2',\n}\n\n/**\n * How an Azure Artifacts workspace authenticates npm against its feed.\n *\n * @remarks\n * - `pat` — a base64 personal access token from the pipeline's variable group,\n * read by the root `.npmrc`'s `username`/`_password` block. The default, and the\n * only mode that works on every CI provider.\n * - `build-identity` — `npmAuthenticate@0` injects the build service identity's\n * token into `.npmrc` at build time, so the file carries **no credentials** and\n * there is no PAT to store, encode, rotate or let expire. Azure Pipelines only:\n * GitHub Actions has no equivalent task.\n *\n * The two are not interchangeable spellings of one thing. The feed's publish\n * endpoint advertises a Bearer scheme bound to `login.windows.net`, which wants an\n * **Entra ID access token**; a PAT is not one and is rejected as Bearer. A PAT\n * therefore has to go through Basic (`_password`), while the build identity's\n * token is Entra-issued and is what Bearer wants. See {@link npmrcContent}.\n */\nexport type NpmAuthMode = 'pat' | 'build-identity'\n\n/**\n * The auth modes, for validating a flag value.\n *\n * @remarks\n * One list, so the flag's error message and the validator cannot disagree about\n * what is accepted.\n */\nexport const NPM_AUTH_MODES: readonly NpmAuthMode[] = ['pat', 'build-identity']\n\n/**\n * Resolves the npm auth mode a workspace should be written with, or refuses one\n * its CI cannot deliver.\n *\n * @remarks\n * `build-identity` is `npmAuthenticate@0`, an Azure Pipelines task: GitHub Actions\n * has nothing equivalent, so a workspace whose CI includes GitHub would be written\n * a credential-free `.npmrc` that authenticates nowhere on that provider. That is\n * refused up front rather than generated, because the failure it would cause -\n * a publish rejected as an authentication error - names neither the mode nor the\n * flag. `--ci=both` has no single right answer, so it is refused too rather than\n * guessed at.\n *\n * It also only means anything for an Azure Artifacts feed: public npm has no\n * identity for the task to borrow.\n *\n * @param requested - The `--npm-auth` flag value, if one was passed.\n * @param registry - The workspace's registry.\n * @param ci - The workspace's CI provider(s).\n * @param fallback - What to use when no flag was passed: the persisted value, or\n * the mode detected in an existing pipeline.\n * @returns The mode to write, or `undefined` when none was requested anywhere -\n * which leaves the workspace on the default.\n * @throws Error when the flag is not a known mode, or when `build-identity` is\n * requested for a registry or CI provider that cannot use it.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveNpmAuth (\n requested: string | undefined,\n registry: RegistryConfig,\n ci: CiProvider,\n fallback?: NpmAuthMode,\n): NpmAuthMode | undefined {\n if (requested !== undefined && !NPM_AUTH_MODES.includes(requested as NpmAuthMode)) {\n throw new Error(`--npm-auth must be one of: ${NPM_AUTH_MODES.join(', ')} (got '${requested}').`)\n }\n const mode = (requested as NpmAuthMode | undefined) ?? fallback\n if (mode !== 'build-identity') {\n return mode\n }\n if (registry.kind !== 'azure-artifacts') {\n throw new Error(\n '--npm-auth build-identity needs an Azure Artifacts feed: public npm has no build identity to borrow. Use --registry azure-artifacts, or drop the flag.',\n )\n }\n if (ci !== 'azure') {\n throw new Error(\n `--npm-auth build-identity needs --ci azure (this workspace is '${ci}'): it is npmAuthenticate@0, an Azure Pipelines task, and GitHub Actions has no equivalent. Use --npm-auth pat, or drop the GitHub pipeline.`,\n )\n }\n\n return mode\n}\n\n/**\n * Returns the npm registry URL for a registry config.\n *\n * @remarks\n * Public npm needs no scoped registry, so it returns `undefined`.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The registry URL, or `undefined` for the public npm registry.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function registryUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/npm/registry/`\n }\n\n return undefined\n}\n\n/**\n * Builds the `.npmrc` body for a registry configuration.\n *\n * @remarks\n * The two registry kinds get deliberately **different** files, because the honest\n * answer differs. This replaced a comment-only placeholder that left\n * `npm publish` unable to authenticate in every generated workspace, while the CI\n * dutifully exported a token nothing consumed.\n *\n * **Azure Artifacts: scope routing plus auth.** `@<scope>:registry` sends both\n * resolution *and* `npm publish` of `@<scope>/*` to the feed, because npm prefers\n * a scope's registry over the global one when publishing a scoped package. That\n * makes it real protection — a package named `@<scope>/x` cannot reach npmjs.org\n * by accident. Verified against a real registry rather than read from docs: npm\n * reports `Publishing to <feed>` with only the scope line set.\n *\n * Only the scope is routed, and that is a choice. A global `registry=` would send\n * every install through the feed too, so `npm ci` would need feed auth just to\n * fetch public packages — verified: with only the scope routed, installing a\n * public dependency still succeeds with no token present at all.\n *\n * **Public npm: auth only, no scope routing.** npmjs.org is already the default\n * registry, so a `@<scope>:registry` line pointing there changes nothing — and\n * calling it protection against an accidental public publish would be false,\n * since the public registry *is* the intended target. That specific false claim\n * is why this function is worth reading twice: an earlier version of this file\n * asserted exactly that safety property in the README while emitting no routing\n * line at all, and `overlay.test.ts` asserted the line's absence. Do not\n * reintroduce a protection that the configuration cannot provide.\n *\n * **The one PAT, two encodings.** `_password` takes the base64 value Azure\n * Artifacts' own \"Connect to feed\" instructions hand you, so it is used as-is.\n * `twine` wants the *raw* token and the release guard decodes it there\n * ({@link releaseGuard}). Easy to get backwards; check before wiring a third\n * protocol.\n *\n * An unset `${PAT}`/`${NODE_AUTH_TOKEN}` does not break anything locally —\n * verified that `npm install` of a public dependency still succeeds with the\n * variable absent, so a developer needs no token to work in the workspace.\n *\n * **Build identity: routing only.** With {@link NpmAuthMode} `build-identity` the\n * Azure file carries the scope line and no credentials at all. `npmAuthenticate@0`\n * appends the build identity's Entra token to it in the pipeline (the generated\n * `azure-pipelines.yml` runs it before `npm ci`). The trade is stated in the file\n * rather than discovered: a developer no longer inherits a credential from it, so\n * a local authenticated command needs `npx vsts-npm-auth -config .npmrc` on\n * Windows or a hand-added entry. Public npm ignores the mode — there is nothing to\n * inject.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`), used for the routing line.\n * @param npmAuth - How npm authenticates against an Azure Artifacts feed.\n * @returns The full text of the generated `.npmrc`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function npmrcContent (\n registry: RegistryConfig,\n scope: string,\n npmAuth: NpmAuthMode = 'pat',\n): string {\n if (registry.kind === 'npm') {\n return `; Publish authentication for the public npm registry.\n;\n; NODE_AUTH_TOKEN is exported by the generated CI's release step. Nothing needs\n; it for day-to-day work here - installing, building and testing never\n; authenticate.\n;\n; It is NOT harmless the moment you DO want to authenticate from this directory,\n; which an earlier version of this comment claimed. A project .npmrc wins over\n; your user one, so with NODE_AUTH_TOKEN unset this line hands npm an empty\n; token and overrides 'npm login': inside this repo 'npm whoami' fails with a\n; 401 and any write is refused. The refusal is easy to misread, because for a\n; scoped package the registry answers a write you are not allowed to make with\n;\n; npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fthing\n;\n; and everything above it is npm listing what it intended to send, so a long\n; healthy-looking run still changes nothing.\n;\n; To publish or deprecate by hand, run it from outside this directory, or export\n; a real token first.\n//registry.npmjs.org/:_authToken=\\${NODE_AUTH_TOKEN}\n\n; There is deliberately NO '${scope}:registry' line here. npmjs.org is already\n; the default registry, so routing the scope to it would change nothing — and\n; presenting that as protection against an accidental public publish would be\n; false, because the public registry is the intended target. (An earlier version\n; of this file made exactly that claim while emitting no routing line at all.)\n; To keep a scoped package off npmjs.org, use a private feed: generate with\n; --registry azure-artifacts, which does route the scope.\n`\n }\n\n const feedUrl = registryUrl(registry) as string\n if (npmAuth === 'build-identity') {\n return `; Publish + resolution routing for this workspace's own scope.\n;\n; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the\n; feed: npm prefers a scope's registry over the global one when publishing a\n; scoped package, so a ${scope}/* package cannot reach npmjs.org by accident.\n;\n; Only the scope is routed, on purpose. A global 'registry=' would send every\n; install through the feed as well, so 'npm ci' would need feed auth just to fetch\n; public packages.\n${scope}:registry=${feedUrl}\n\n; NO credentials are written here. The 'npmAuthenticate@0' task in\n; azure-pipelines.yml injects them into this file at build time, using the build\n; service identity - so there is no PAT to store, rotate or encode.\n;\n; That is forced by what the feed accepts. An unauthenticated PUT to the publish\n; endpoint answers with:\n; www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,\n; Basic realm=\"...\", TFS-Federated\n; so its Bearer scheme wants an ENTRA ID access token, which is what the task\n; supplies. A PAT is not one: npm sends _authToken verbatim as a Bearer header, so\n; a PAT there is rejected with \"Unable to authenticate, your authentication token\n; seems to be invalid\". A PAT can only authenticate through the Basic scheme\n; (username + base64 _password), which is what 'mnci upgrade --npm-auth pat' writes.\n;\n; The cost is local: nothing in this file authenticates a developer's machine, so\n; publishing or installing a private package by hand needs\n; 'npx vsts-npm-auth -config .npmrc' (Windows) or a credential you add yourself.\n; Building and testing never authenticate.\n`\n }\n\n // npm keys per-registry credentials by the URL with the protocol stripped.\n const feedKey = feedUrl.replace(/^https:/, '')\n // npm matches credentials by URL prefix and walks only UP the path, so an entry\n // on '/npm/registry/' is never found for a request to '/npm/'. Both are keyed.\n const feedShortKey = feedKey.replace('npm/registry/', 'npm/')\n\n return `; Publish + resolution routing for this workspace's own scope.\n;\n; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the\n; feed: npm prefers a scope's registry over the global one when publishing a\n; scoped package, so a ${scope}/* package cannot reach npmjs.org by accident.\n;\n; Only the scope is routed, on purpose. A global 'registry=' would send every\n; install through the feed as well, so 'npm ci' would need feed auth just to fetch\n; public packages.\n${scope}:registry=${feedUrl}\n\n; Feed credentials. PAT is the BASE64 value Azure Artifacts' \"Connect to feed\"\n; instructions give you, which is exactly what _password expects, so it is used\n; as-is. (twine wants the RAW token; the CI release step decodes it there.)\n; Azure ignores the username, and npm requires an email it never uses.\n;\n; NOT _authToken, and that is measured rather than assumed. The feed answers an\n; unauthenticated PUT to the publish endpoint with:\n; www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,\n; Basic realm=\"...\", TFS-Federated\n; so its Bearer scheme wants an Entra ID access token, NOT a PAT. npm sends\n; _authToken verbatim as a Bearer header, so a PAT there is rejected with\n; \"Unable to authenticate, your authentication token seems to be invalid\". A PAT\n; authenticates through the Basic scheme, which is username/_password. Note the\n; Packaging REST API DOES accept a PAT as Bearer - do not generalise from it.\n;\n; Both path forms are keyed because npm walks only UP a URL when matching.\n${feedKey}:username=AzureArtifacts\n${feedKey}:_password=\\${PAT}\n${feedKey}:email=npm-requires-this-and-never-uses-it\n${feedShortKey}:username=AzureArtifacts\n${feedShortKey}:_password=\\${PAT}\n${feedShortKey}:email=npm-requires-this-and-never-uses-it\n`\n}\n\n/**\n * The Nx tag a `vscode-extension` project carries, and what `release.projects` matches it by.\n *\n * @remarks\n * An extension lives in `apps/`, which no release glob covers, and a path added to\n * `release.projects` by hand would be lost on the next `mnci upgrade`, which\n * rewrites the array (#229). A tag matcher needs no array merging: every upgrade\n * writes the same list, and it matches every extension there will ever be.\n */\nexport const VSCODE_EXTENSION_TAG = 'type:vscode-extension'\n\n/**\n * Builds the `release` block merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * The tag-only model: versions are computed from conventional commits since\n * each package's last release tag, the bump is\n * **never committed** (`git.commit: false`) — only the tag is created and\n * pushed — so a protected `main` never rejects a release, and future runs\n * resolve versions from tag names, not from a committed `package.json`.\n * `fallbackCurrentVersionResolver: 'disk'` keeps a brand-new package (no tag\n * yet) from hard-erroring.\n *\n * The git options live under a top-level `git` (not `version.git`): the\n * guarded CI release step and the generated `release:preview` script both run\n * the combined `nx release` command (never the bare `nx release version`\n * subcommand), and Nx hard-errors that combined command when git options are\n * granular (`version.git`/`changelog.git`) instead of top-level — the reverse\n * of the bare `version` subcommand's own requirement, which is why the two\n * forms aren't interchangeable (verified empirically).\n *\n * **GitHub Releases (`ci === 'github'` only).** Verified empirically against\n * the pinned Nx version (23.1.0, real `--dry-run` runs, and the installed\n * `release.js` source) that the combined `nx release` command now tags\n * *before* it pushes — an earlier version of this comment described a bug\n * where Nx's internal push fired before the tag existed, silently losing\n * every tag; that ordering bug no longer reproduces on this pinned version.\n * Nx itself also refuses to enable `createRelease` while `git.push: false`\n * (hard error: \"createRelease... cannot be enabled when git push is\n * explicitly disabled\"), so `push: true` here is required, not optional, once\n * `createRelease` is on. `changelog.projectChangelogs.file: false` sends the\n * generated changelog content straight into the GitHub Release body without\n * writing an unpushable `CHANGELOG.md` (`git.commit` stays `false`, so a\n * written file would just be silently discarded at the end of every CI run).\n * `workspaceChangelog` stays `false` for every provider: projects release\n * independently (see below), so only per-project changelogs/releases make\n * sense — a single workspace-wide changelog would conflate unrelated\n * packages' histories. `GITHUB_TOKEN` (GitHub Actions' own built-in token,\n * already sufficient under the `contents: write` permission the workflow\n * already grants) is exported in {@link githubActionsYaml}'s release step;\n * once it's pushing anyway, {@link githubActionsYaml} drops its separate\n * explicit `git push origin --tags` step as redundant.\n *\n * **Azure-only and `both`** deliberately keep today's `push: false` /\n * no-`createRelease` behaviour. GitHub Releases only make sense when the repo\n * is actually hosted on GitHub, which `ci` alone cannot confirm for `'azure'`\n * (Azure Pipelines can build a GitHub-hosted repo too) or safely guarantee\n * for `'both'` (both pipeline files exist; whichever one actually executes\n * might be the Azure one, which has no `GITHUB_TOKEN` to give Nx) — so this\n * scope is intentionally limited to the one case where a `GITHUB_TOKEN` is\n * guaranteed to exist: GitHub Actions as the *only* configured provider.\n * {@link azurePipelinesYaml} is unchanged and keeps its own explicit\n * `git push origin --tags` step for exactly this reason.\n *\n * Two directories are released: `packages/*` (publishable **npm** libraries)\n * and `python-packages/*` (publishable **Python** packages) — deliberately one\n * flat project list, not two named `release.groups`: Nx hard-errors\n * `nx release` entirely (every group, not just the empty one) when any\n * explicit group matches zero projects — a real failure mode for a workspace\n * that has added Python packages but no npm ones yet, or vice versa (verified\n * empirically). A flat list has no such all-or-nothing requirement: it stays\n * releasable as soon as *either* glob matches something. Each project's own\n * `versionActions` (npm's default, or `@mnci/nx-python-pip`'s\n * `PythonVersionActions` — stamped onto every publishable Python lib's own\n * `project.json` by that plugin's own `library` generator, not by anything\n * here) reads/writes the right manifest (`package.json` vs `pyproject.toml`)\n * — project-level config wins over the group's, so both kinds coexist in the\n * one group correctly. Publishable **Flutter/Dart** packages also live in\n * `packages/*` and are covered by the same flat list;\n * `@mnci/nx-flutter`'s own `library` generator stamps its\n * `DartVersionActions` on for `pubspec.yaml`.\n * Internal libraries live in `libs/` and apps in `apps/`, so release scoping\n * needs no tags for them.\n *\n * `!tag:type:go-lib` is the one exception, and it is a **bug fix**, not\n * fine-tuning. A `go-lib` also lands in `packages/`, but it has no\n * per-project manifest at all — mnci puts every Go project in one root\n * `go.mod` — so Nx falls back to its default `versionActions`, looks for a\n * `packages/<name>/package.json` that does not exist, and aborts. Because\n * that happens while building the release graph, it takes down the release of\n * **every** project in the workspace, not just the Go one: before this\n * exclusion, a single `mnci add go-lib` made `nx release` exit 1 for the\n * whole repo (verified empirically, then re-verified green with the\n * exclusion in place).\n *\n * Excluding rather than teaching Go a `versionActions` is the semantically\n * correct fix: in a single-module layout there is exactly one Go module, so\n * its packages have no independent versions to bump: a consumer running\n * `go get` on one of them resolves against the *module's* tag, so a\n * per-project version would be a fiction. Go's \"publishing\" is that\n * repo-level tag, which is not a per-project release concern.\n *\n * @param ci - Which CI provider(s) the workspace generates a pipeline for —\n * only `'github'` (GitHub Actions and nothing else) turns on GitHub Release\n * creation; see the remarks above for why `'azure'` and `'both'` do not.\n * @returns The object to merge onto `nx.json`'s `release` key.\n * @throws Never - builds a plain object with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function releaseConfig (ci: CiProvider): Record<string, unknown> {\n const githubReleases = ci === 'github'\n\n return {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*', `tag:${VSCODE_EXTENSION_TAG}`, '!tag:type:go-lib'],\n releaseTag: { pattern: '{projectName}@{version}' },\n git: { commit: false, tag: true, push: githubReleases },\n version: {\n conventionalCommits: true,\n fallbackCurrentVersionResolver: 'disk',\n // Build only what is being released. Without this, @nx/js:lib's generator\n // defaults the pre-version command to building EVERY project, so a broken\n // (or merely slow) app build would block releasing unrelated packages.\n // Set here at `new` time it wins: the generator only fills this in when\n // absent (it spreads the existing release.version over its default). Both\n // globs are listed; `nx run-many` no-ops cleanly when one matches nothing.\n preVersionCommand: `npx nx run-many -t build --projects=packages/*,python-packages/*,tag:${VSCODE_EXTENSION_TAG}`,\n // The lock file resync is OFF, and the reason is that it cannot succeed\n // on the one release where it would matter.\n //\n // It is not `preVersionCommand` that does this, despite the name it gets\n // reported under: `@nx/js`'s `afterAllProjectsVersioned` hook shells\n // `npm install --package-lock-only` once every project has been versioned.\n // That command resolves the whole tree against the registry, so the first\n // release of two NEW interdependent packages 404s — package A declares a\n // dependency on package B at the version this very run is about to\n // publish, and B does not exist on the registry yet. Measured on a real\n // workspace releasing `@scope/cli` and `@scope/studio` together.\n //\n // Turning it off costs nothing here, because mnci releases are tag-only:\n // `git.commit` is `false`, so the refreshed lock file is written into an\n // ephemeral CI checkout and then thrown away without ever being committed.\n // It was never protecting anything. That it goes stale is not a new\n // condition either — every manifest on disk is already stale by design for\n // exactly the same reason, which is why `fallbackCurrentVersionResolver`\n // is `'disk'` and why a tagless clone makes a dry run silently wrong.\n //\n // A workspace that DOES want the lock file refreshed should commit the\n // bump too (`git.commit: true`), at which point this can be dropped.\n versionActionsOptions: { skipLockFileUpdate: true },\n },\n changelog: githubReleases\n ? {\n workspaceChangelog: false,\n projectChangelogs: { createRelease: 'github', file: false },\n // A GitHub Release needs a changelog, and a changelog needs a ref to start\n // from. On the first release of a repository there is no tag to start from,\n // and nx release dies after versioning (#243, measured). With this set it\n // starts from the first commit; once a tag exists it is never consulted.\n automaticFromRef: true,\n }\n : { workspaceChangelog: false },\n } as const\n}\n\n/**\n * The `sync` block merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * `--preset=ts` already registers `@nx/js:typescript-sync` on the `build` and\n * `typecheck` targets (via the `@nx/js/typescript` plugin), so Nx already\n * detects a stale TypeScript project reference — e.g. after hand-editing a\n * file to add a new cross-project import — on the next `build`/`typecheck`/\n * `affected` run. Without this block that detection only **prompts**\n * (\"Would you like to sync the identified changes?\"): easy to miss, and it\n * blocks any non-interactive run. `applyChanges: true` makes Nx fix it\n * silently instead, locally, every time — no more `nx sync` run by hand.\n *\n * This is deliberately safe in CI: per Nx's own docs, a non-interactive run\n * (CI) always runs sync generators in dry-run mode and **fails** instead of\n * applying, regardless of this setting — so a forgotten local sync still\n * surfaces as a clear CI failure ({@link azurePipelinesYaml}'s explicit\n * `nx sync:check` step gives that failure early and unambiguously) rather\n * than silently patching an ephemeral CI checkout that never gets committed.\n */\nexport const SYNC_CONFIG = { applyChanges: true } as const\n\n/**\n * The `@nx/eslint/plugin` registration merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * This plugin is what gives every project its `lint` target: it maps ESLint\n * config *directories* onto the project roots beneath them, so the single root\n * config mnci writes covers the whole workspace and no project needs one of its\n * own.\n *\n * mnci registers it because mnci owns linting. Nx would otherwise add it as a\n * side effect of the first `nx g … --linter=eslint`, which is both invisible\n * and no longer true: the generators are invoked with `--linter=none` (see\n * `add/shared.ts`), precisely so they stop scaffolding a per-project config —\n * and, more pressingly, stop dragging in `eslint-plugin-import@2.31.0`, whose\n * peer range caps at ESLint 9 and made `mnci add react-app` fail outright on\n * the ESLint 10 toolchain this workspace installs.\n *\n * A consequence worth stating: `npm run lint` now works in a workspace with\n * zero projects, which it previously did only by accident.\n */\nexport const ESLINT_PLUGIN_CONFIG = {\n plugin: '@nx/eslint/plugin',\n options: { targetName: 'lint' },\n} as const\n\n/**\n * The plugin name of an `nx.json` `plugins` entry.\n *\n * @remarks\n * Nx accepts both the bare-string form (`\"@nx/eslint/plugin\"`) and the object\n * form (`{ plugin, options }`), and a real workspace can hold a mix.\n *\n * @param entry - One element of `nx.json`'s `plugins` array.\n * @returns The plugin name, or `undefined` for an entry in neither form.\n * @throws Never - pure property read.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pluginName (entry: unknown): string | undefined {\n return typeof entry === 'string' ? entry : (entry as { plugin?: string }).plugin\n}\n\n/**\n * Returns a copy of an `nx.json` object with `@nx/eslint/plugin` registered.\n *\n * @remarks\n * Idempotent, so `mnci upgrade` cannot accumulate duplicate entries — and a\n * workspace where Nx already added the plugin (generated before this existed)\n * keeps its own entry, options included, rather than having them overwritten.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns A new object whose `plugins` array contains the ESLint plugin.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withEslintPlugin (nxJson: Record<string, unknown>): Record<string, unknown> {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n const registered = plugins.some(entry => pluginName(entry) === ESLINT_PLUGIN_CONFIG.plugin)\n\n return registered\n ? { ...nxJson, plugins }\n : { ...nxJson, plugins: [...plugins, ESLINT_PLUGIN_CONFIG] }\n}\n\n/**\n * The workspace-wide files every project's verification depends on.\n *\n * @remarks\n * `nx affected` walks the project graph, and a root config file is in no\n * project — so without this, changing one marks only the root pseudo-project,\n * which has no `lint`/`typecheck`/`test`/`build` target. Measured on a real\n * workspace before the fix: a PR touching `tsconfig.base.json` alone verified\n * **nothing at all** and reported green.\n *\n * Each entry is a file that can change every project's result:\n * `eslint.config.mjs` is the whole linting opinion, `tsconfig.base.json` is\n * what every project's tsconfig extends, and the root `package.json` holds\n * every devDependency version and the curated scripts.\n *\n * BOTH ESLint files are listed. `eslint.config.mnci.mjs` carries every rule and\n * `eslint.config.mjs` is the thin file that imports it and holds the\n * workspace's own blocks — either can change every project's result, and\n * listing only one would make a change to the other invisible to\n * `nx affected`, which is the exact failure this input exists to prevent.\n *\n * No formatter config is listed because none exists: ESLint is the formatter,\n * and `eslint.config.mjs` is already the first entry. When Prettier owned\n * formatting, `.prettierrc.json` was excluded on the grounds that\n * `format:check` swept the whole tree on every run regardless, so listing it\n * would invalidate every project's cache and verify nothing new. Both the file\n * and that step are gone. `package-lock.json` is absent too — Nx already\n * marks projects affected from lockfile changes through its external-dependency\n * nodes (verified: a lockfile-only edit marks every project).\n */\nexport const SHARED_GLOBAL_INPUTS = [\n '{workspaceRoot}/eslint.config.mjs',\n '{workspaceRoot}/eslint.config.mnci.mjs',\n '{workspaceRoot}/tsconfig.base.json',\n '{workspaceRoot}/package.json',\n] as const\n\n/**\n * Returns a copy of an `nx.json` object whose `sharedGlobals` named input\n * covers the mnci-owned root config files.\n *\n * @remarks\n * Idempotent and additive: entries a workspace already has are kept in place\n * and never duplicated, so `mnci upgrade` can run repeatedly and a workspace\n * that added its own shared globals does not lose them. `sharedGlobals` is\n * referenced by the preset's `default` input, which `production` extends, so\n * one list reaches every target.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns A new object whose `namedInputs.sharedGlobals` includes every entry\n * of {@link SHARED_GLOBAL_INPUTS}.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withSharedGlobals (nxJson: Record<string, unknown>): Record<string, unknown> {\n const namedInputs = (nxJson.namedInputs as Record<string, unknown> | undefined) ?? {}\n const existing = (namedInputs.sharedGlobals as unknown[] | undefined) ?? []\n const missing = SHARED_GLOBAL_INPUTS.filter(entry => !existing.includes(entry))\n\n return {\n ...nxJson,\n namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] },\n }\n}\n\n/**\n * The `nx.json` `plugins` entry for `@mnci/nx-python-pip`'s dependency graph.\n *\n * @remarks\n * A bare string rather than the `{ plugin, options }` form: it takes no\n * options, and the string form is what Nx's own docs show for a plugin that\n * contributes only `createDependencies`.\n */\nexport const PYTHON_GRAPH_PLUGIN = '@mnci/nx-python-pip/graph'\n\n/**\n * Returns a copy of an `nx.json` object with the Python graph plugin\n * registered, when the workspace actually has the plugin.\n *\n * @remarks\n * The GATE is the point, and it is not a nicety. Nx resolves every entry in\n * `plugins` while constructing the project graph, and a name it cannot resolve\n * is a hard failure - so registering this unconditionally would break every\n * generated workspace that has no Python in it, which is most of them. The\n * plugin arrives only when `mnci add python-*` installs it, so that is the\n * signal this reads.\n *\n * Without the registration the plugin still supplies generators, executors and\n * release actions - those resolve by plain module lookup through\n * `generators.json`/`executors.json` - but contributes NO graph edges, because\n * `createDependencies` is only called for a registered plugin. That is the\n * difference between `nx affected` knowing a Python library's consumers and\n * silently testing none of them.\n *\n * Idempotent and additive, like {@link withEslintPlugin}: a workspace that\n * registered it by hand keeps its own entry, and `mnci upgrade` cannot\n * accumulate duplicates.\n *\n * @param nxJson - The parsed `nx.json`.\n * @param pluginInstalled - Whether the workspace declares\n * `@mnci/nx-python-pip`.\n * @returns A new object, with the plugin registered when it is installed.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withPythonGraphPlugin (\n nxJson: Record<string, unknown>,\n pluginInstalled: boolean,\n): Record<string, unknown> {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n if (!pluginInstalled || plugins.some(entry => pluginName(entry) === PYTHON_GRAPH_PLUGIN)) {\n return nxJson.plugins === undefined ? nxJson : { ...nxJson, plugins }\n }\n\n return { ...nxJson, plugins: [...plugins, PYTHON_GRAPH_PLUGIN] }\n}\n\n/**\n * Narrows to a mergeable object: not null, and not an array.\n *\n * @remarks\n * Arrays are deliberately NOT mergeable. `release.projects` is mnci's own\n * list, and element-wise merging it would leave a workspace unable to drop an\n * entry mnci once emitted.\n *\n * @param value - The value to test.\n * @returns Whether `value` is a plain object.\n * @throws Never - pure predicate.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isMergeableObject (value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Deep-merges mnci's owned config over whatever the workspace already had,\n * so keys mnci does not emit survive.\n *\n * @remarks\n * mnci wins for every key it emits — those encode the tag-only release model,\n * and a workspace silently flipping `git.commit` to `true` would be a worse\n * bug than the one this fixes. Everything else is the workspace's and is kept.\n *\n * The trade, stated: a key mnci USED to emit and no longer does now lingers\n * instead of being cleaned up. That is the right side to err on - a stale key\n * nx ignores costs nothing, and deleting a key the workspace meant to keep has\n * already cost a repo two days of silently failing releases.\n *\n * @param existing - The workspace's current value, of any shape.\n * @param owned - The config mnci generates.\n * @returns The merged object.\n * @throws Never - pure recursive merge.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction preservingUnknownKeys (\n existing: unknown,\n owned: Record<string, unknown>,\n): Record<string, unknown> {\n if (!isMergeableObject(existing)) {\n return owned\n }\n\n const merged: Record<string, unknown> = { ...existing }\n for (const [key, value] of Object.entries(owned)) {\n const previous = merged[key]\n merged[key] =\n isMergeableObject(value) && isMergeableObject(previous)\n ? preservingUnknownKeys(previous, value)\n : value\n }\n\n return merged\n}\n\n/**\n * Returns a copy of an `nx.json` object with the release block applied.\n *\n * @remarks\n * Pure read-modify-write on the object the Nx preset generated — this never\n * templates whole config files, it only patches in the one opinion Nx has no\n * default for.\n *\n * The release block is **merged, not replaced**, and that distinction is the\n * whole point. It used to be `release: releaseConfig(ci)`, which meant every\n * `mnci upgrade` deleted any key a workspace had added under `release` — and\n * `release` is the one block a workspace legitimately has to extend, because\n * nx exposes settings there that mnci has no opinion about and cannot\n * enumerate in advance.\n *\n * Observed, not hypothetical. A workspace had deliberately set\n * `version.preserveMatchingDependencyRanges: false` - without it nx REFUSES to\n * release when an internal dependency range cannot absorb the bump, which\n * under this tag-only model is always, since `git.commit` is `false` and the\n * on-disk manifests are permanently the scaffold version. An upgrade dropped\n * it, and every release from that day on failed in the version phase. Nothing\n * else noticed: lint, typecheck, test and build all stayed green, because the\n * only broken path was the release, and that runs on the default branch alone.\n *\n * `sharedGlobals` ({@link withSharedGlobals}) and `plugins`\n * ({@link withEslintPlugin}) were already additive, and `generators`, `sync`\n * and `mnci` are all spread-merged where `nx.json` is written. `release` was\n * the one wholesale replacement, and the only one whose value nests, so a\n * shallow spread would still have wiped `version.*`.\n *\n * @param nxJson - The parsed `nx.json` produced by `create-nx-workspace`.\n * @param ci - Which CI provider(s) the workspace generates a pipeline for —\n * forwarded to {@link releaseConfig} to decide whether GitHub Release\n * creation is turned on.\n * @returns A new object with `release` (and `defaultBase: 'main'`) set.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withReleaseConfig (\n nxJson: Record<string, unknown>,\n ci: CiProvider,\n): Record<string, unknown> {\n return {\n ...nxJson,\n defaultBase: 'main',\n release: preservingUnknownKeys(nxJson.release, releaseConfig(ci)),\n }\n}\n\n/**\n * The commitlint config written into generated workspaces.\n *\n * @remarks\n * Conventional commits are the release mechanism's input, so they are\n * enforced at commit time — the one piece Nx itself does not provide.\n */\nexport const COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventional'] }\n`\n\n/**\n * The husky `commit-msg` hook body that runs commitlint.\n *\n * @remarks\n * `--no` keeps npx from installing anything at commit time — commitlint is a\n * devDependency installed by `mnci new`.\n */\nexport const COMMIT_MSG_HOOK = `npx --no -- commitlint --edit \"$1\"\n`\n\n/**\n * The `@mnci/eslint-config` version generated workspaces depend on.\n *\n * @remarks\n * NOT a caret range, deliberately — `^` on a pre-1.0 package is minor-locked:\n * `^0.1.0` means `>=0.1.0 <0.2.0`, so `npm update` can never cross into 0.2.0\n * or later. That defeats the one reason this ships as a package rather than a\n * template string in this file: \"`npm update` carries lint-rule improvements\n * into existing workspaces without an `mnci upgrade`\" was never actually true\n * under `^0.1.0`. Found in a real generated workspace still declaring\n * `^0.1.0` after 20+ published releases — `mnci upgrade` was also stamping\n * this constant back over any manual bump on every run (see\n * {@link eslintToolchainDependencies}'s call site), so there was no way out\n * except editing the range by hand and never running `mnci upgrade` again.\n *\n * `>=0.1.0 <1.0.0` floats across every 0.x release without an upper bound\n * inside that line, while still requiring an explicit bump of this constant\n * (an intentional `mnci upgrade`-worthy decision) to cross into a stable 1.0.\n */\nexport const ESLINT_CONFIG_VERSION = '>=0.1.0 <1.0.0'\n\n/**\n * The `@mnci/eslint-config` spec to write into a generated workspace's manifest.\n *\n * @remarks\n * Reads `MNCI_ESLINT_CONFIG_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `packages/eslint-config`) instead of the published\n * registry package — without it, `npm install` in a freshly generated workspace\n * 404s until the package has been released at least once. Same escape hatch\n * `add/python.ts` and `add/flutter.ts` already use for their plugins\n * (`MNCI2_PYTHON_PIP_SPEC`, `MNCI_NX_FLUTTER_SPEC`).\n *\n * {@link ESLINT_CONFIG_VERSION} is the default and the only value a real\n * `mnci new` ever writes.\n *\n * @returns The dependency spec (a semver range, or a path/URL when overridden).\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function eslintConfigSpec (): string {\n return process.env.MNCI_ESLINT_CONFIG_SPEC ?? ESLINT_CONFIG_VERSION\n}\n\n/**\n * The `eslint` version generated workspaces depend on.\n *\n * @remarks\n * `@mnci/eslint-config` peers on `eslint`, so it never installs one itself.\n *\n * ESLint **10**. What held the stack at 9 was never ESLint — it was\n * `eslint-plugin-react`, which has no 10 release at all and has since been\n * replaced by `@eslint-react/eslint-plugin`. See\n * {@link ESLINT_PEER_OVERRIDES} for the one holdout that remains.\n */\nexport const ESLINT_VERSION = '^10.8.0'\n\n/**\n * The `.devcontainer/devcontainer.json` written into generated workspaces.\n *\n * @remarks\n * mnci's toolchain matrix is Node + Python + Go + Flutter, and until this existed\n * only **CI** had all four: the pipeline installs the Flutter SDK itself and\n * assumes CPython and Go are on the agent, while locally a contributor was on\n * their own. A devcontainer is what makes the local environment the same one CI\n * verifies, which is the whole \"just works\" promise applied to development rather\n * than to the build.\n *\n * Four decisions worth keeping:\n *\n * - **The Node major comes from {@link NODE_VERSION}**, the same constant the\n * workflow's `setup-node` step reads. Hardcoding it twice is exactly the drift\n * this file is supposed to remove.\n * - **`postCreateCommand` reuses the pipeline's own guards** rather than\n * reimplementing them — {@link PYTHON_INSTALL_GUARD} and friends via the\n * `python:install` root script, plus the same `golangci-lint` and Flutter SDK\n * one-liners the pipelines run. Each is already idempotent and already\n * no-ops when the workspace has no project of that kind (no `go.mod`, no\n * `pubspec.yaml`), so a JS-only workspace pays almost nothing and a polyglot\n * one gets exactly what CI gets. Reimplementing them would create a third\n * copy to keep in sync.\n * - **Go, Python and .NET arrive as devcontainer *features*, not as a custom\n * image.** A Dockerfile would be a second thing to maintain against\n * upstream, and features are the mechanism the ecosystem maintains for\n * precisely this. Unlike CI's install steps, a feature runs once at\n * container build time rather than on every job, so there is no reason to\n * gate it on the workspace actually having a project of that kind — Python\n * and Go have never been gated either.\n * - **`ghcr.io/devcontainers/features/dotnet:2`'s `version` option takes\n * `X.Y`/`X.Y.Z`, not {@link DOTNET_SDK_VERSION}'s `X.Y.x` verbatim** — that\n * suffix is specifically what `actions/setup-dotnet` and `UseDotNet@2`\n * expect (see its remarks), and the feature's own schema (checked directly\n * against `devcontainer-feature.json`, proposals `latest`/`lts`/`10.0`/…)\n * documents no `.x` form at all. The `.x` is stripped rather than a second\n * constant maintained in parallel, so there is still exactly one source of\n * truth to bump.\n * - **Flutter is NOT a feature**, because no maintained one exists — the same\n * reason `@mnci/nx-flutter` had to be written. The SDK guard clones a pinned\n * tag into the home directory, which is what CI does, so the version matches\n * by construction.\n *\n * @param workspaceName - The workspace name, used as the container's label.\n * @returns The JSON string for `.devcontainer/devcontainer.json`.\n * @throws Never - performs pure string formatting with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function devcontainerJson (workspaceName: string): string {\n return `${toJson({\n name: workspaceName,\n image: `mcr.microsoft.com/devcontainers/typescript-node:${NODE_VERSION}-bookworm`,\n features: {\n 'ghcr.io/devcontainers/features/python:1': { version: '3.12' },\n 'ghcr.io/devcontainers/features/go:1': { version: 'latest' },\n 'ghcr.io/devcontainers/features/dotnet:2': { version: DOTNET_SDK_VERSION.replace(/\\.x$/, '') },\n },\n // `npm ci` first: every guard after it runs through the workspace's own\n // scripts and Nx, which do not exist until the install completes.\n postCreateCommand: [\n // Same pin the workflow applies, for the same reason — a container whose\n // npm major differs from CI's resolves dependencies differently, which is\n // precisely the drift this file exists to remove.\n `npm install -g npm@${NPM_VERSION}`,\n 'npm ci',\n 'npm run python:install',\n GOLANGCI_LINT_INSTALL_GUARD,\n FLUTTER_SDK_INSTALL_GUARD,\n ].join(' && '),\n // The same recommendations the `.code-workspace` file carries, so opening\n // the folder in a container suggests the identical toolset.\n customizations: {\n vscode: { extensions: VSCODE_RECOMMENDED_EXTENSIONS },\n },\n })}\\n`\n}\n\n/**\n * The `lint` target mnci puts on a generated workspace's ROOT project.\n *\n * @remarks\n * Without it, root-level files are linted by **nothing**. `npm run lint` is\n * `nx run-many -t lint`, and every other `lint` target belongs to a project and\n * runs `eslint .` with that project as its cwd — so `.github/workflows/*.yml`,\n * `azure-pipelines.yml`, the root JSON and Markdown, `eslint.config.mjs` and\n * `commitlint.config.mjs` were covered by no target at all.\n *\n * **The ignore patterns are CLI flags on purpose, not config `ignores`.** In flat\n * config, `ignores` are relative to the config file, and every project's `lint`\n * resolves this same root `eslint.config.mjs` — so ignoring `packages/**` there\n * would switch linting off *inside* the packages too. A CLI flag applies to this\n * invocation alone. Each project already lints its own tree, so this target adds\n * coverage rather than duplicating it.\n *\n * `includedScripts: []` goes alongside it, and is load-bearing: the root manifest's\n * scripts are the `nx run-many` aggregators, so letting Nx infer targets from them\n * would make `lint` invoke `nx run-many -t lint` — itself.\n */\nexport const ROOT_LINT_TARGET = {\n executor: 'nx:run-commands',\n cache: true,\n options: {\n command: [\n // `--cache` is the one ESLint speed option that actually pays here:\n // measured best-of-3 on this repo, an unchanged re-run drops from 9,546ms\n // to 2,835ms (3.4x). `--concurrency=auto` was measured too and REJECTED —\n // it came out 8% SLOWER, because per-worker TypeScript type-service\n // startup costs more than the parallelism saves. The cache file is\n // per-machine and gitignored.\n 'eslint . --cache',\n '--ignore-pattern \"apps/**\"',\n '--ignore-pattern \"libs/**\"',\n '--ignore-pattern \"packages/**\"',\n '--ignore-pattern \"python-packages/**\"',\n '--ignore-pattern package-lock.json',\n ].join(' '),\n cwd: '.',\n },\n} as const\n\n/**\n * npm `overrides` for `@nx/*` peer ranges that are narrower than what they support.\n *\n * @remarks\n * `@nx/react@23.1.2` **added** `express: '^4.21.2'` as an optional peer in a\n * PATCH release — 23.1.0 and 23.1.1 declare no express peer at all. mnci's own\n * `node-app --framework express` installs express 5, so the two are\n * irreconcilable and `npm install` fails outright:\n *\n * ```\n * npm error Could not resolve dependency:\n * npm error peerOptional express@\"^4.21.2\" from @nx/react@23.2.1\n * npm error Conflicting peer dependency: express@4.22.3\n * ```\n *\n * `optional: true` is the trap: it means \"you needn't install it\", NOT \"any\n * version is fine if you do\". Once express is present the range is enforced.\n *\n * **The range is `@nx/node`'s own, not a number invented here.**\n * `@nx/node@23.2.1` declares `peerOptional express@'>=4.0.0 <6.0.0'`, and\n * `@nx/node` is the package that actually scaffolds the express app. So the\n * override says: resolve `@nx/react`'s express peer to the range the Nx\n * package that owns express already supports. Both majors satisfy it.\n *\n * **Unconditional, and that is the fix.** This used to be a function returning\n * `{ '@nx/react': { express: '$express' } }` only when the root manifest already\n * declared express, because `$express` is a dangling reference otherwise. Every\n * value was measured against a real workspace:\n *\n * | override value | express 5 | express 4 | react-only |\n * | -------------------- | --------- | --------- | ---------- |\n * | `'$express'` | works | works | **FAILS** — `Unable to resolve reference $express` |\n * | `'*'` | FAILS | works | works |\n * | `'^5.1.0'` | works | FAILS | works |\n * | `'>=4.0.0 <6.0.0'` | works | works | works |\n *\n * The last row is why this can be static, and being static is the whole point.\n * The conditional form could only be written **after** a generator had put\n * express in the manifest — but `nx g @nx/node:application --framework=express`\n * adds express and runs `npm install` in the SAME invocation, so the conflict\n * fired inside that generator, before any mnci post-generation step could write\n * the override. The old comment said \"the very next add would otherwise be the\n * one that fails\"; measured, it is THAT add that fails. A static entry written\n * at `mnci new` is already in place before any generator installs anything, and\n * it covers the reverse order too — a react-app added to a workspace that\n * already has express, where `nx add @nx/react` is the install that would die.\n *\n * Nothing in a generated workspace uses `@nx/react`'s express peer: react apps\n * build with Vite, and the peer exists for Nx's own module-federation/SSR\n * dev-server path, which mnci does not scaffold.\n */\nexport const NX_PEER_OVERRIDES = {\n '@nx/react': { express: '>=4.0.0 <6.0.0' },\n} as const\n\n/**\n * npm `overrides` a generated workspace needs for its ESLint toolchain to install.\n *\n * @remarks\n * `eslint-plugin-jsx-a11y@6.10.2` — the latest release — peers on\n * `eslint: ^3 … ^9`, so `npm install` fails outright with `ERESOLVE` on ESLint\n * 10. That cap is **stale rather than real**, and this was measured, not\n * assumed: with this override in place on `eslint@10.8.0`, the plugin installs\n * and its rules work — a missing `alt` reports `jsx-a11y/alt-text`, and\n * `alt=\"a picture\"` reports `img-redundant-alt`.\n *\n * `overrides` has to live in the **root** manifest; npm ignores it anywhere\n * else, which is why a config package cannot carry its own fix and mnci writes\n * this instead. `$eslint` resolves to the workspace's own `eslint` spec, so the\n * override never pins a version of its own.\n *\n * The trade, stated rather than glossed: mnci deleted `legacy-peer-deps` from\n * the generated `.npmrc` precisely for weakening dependency resolution. This is\n * far narrower — one named package, one peer, with evidence that the real\n * constraint is satisfied — but it is the same kind of decision, so it should\n * be removed the moment `jsx-a11y` ships a release declaring ESLint 10.\n *\n * **`nx` → `brace-expansion` is a security fix, not a resolution fix, and it is\n * here because a generated workspace shipped six HIGH advisories without it.**\n * Measured by the audit step running inside a freshly generated workspace for the\n * first time: `brace-expansion` carries a high advisory, and `nx`, `@nx/js`,\n * `@nx/eslint`, `@nx/eslint-plugin` and `@nx/workspace` all inherit it. npm\n * reports the fix as **semver-major**, meaning `npm audit fix` would try to bump\n * `nx` itself — unacceptable in a scaffold — while a targeted override on the one\n * vulnerable transitive is both sufficient and safe.\n *\n * This repo has carried exactly this override for its own tree the whole time,\n * which is why its audit reads 0 while generated workspaces read 6. Same\n * dogfooding drift as the missing audit step: fixed here, never shipped. That is\n * the argument for the audit step being blocking — it found this the first time\n * it ever ran somewhere that mattered.\n *\n * **`nx` → `smol-toml` is the same drift, recurring.** `smol-toml <=1.7.0`\n * (GHSA-7w5x-hrqm-74c2, a parser DoS on malformed TOML) is a second instance of\n * \"nx's own transitive dependency\", fixed on this repo's own tree by nesting\n * `smol-toml` under the SAME `nx` entry already here for `brace-expansion` — and,\n * exactly like that fix, never carried over to what `mnci new` ships. A generated\n * workspace's own audit step caught it the moment C#/.NET support shipped an e2e\n * failure into the `flutter` section too: the SDK-resolution crash the broken\n * `nuget.config` XML caused (fixed separately, see `nugetConfigContent`) corrupted\n * the whole run's project graph, but the npm audit block on a *fresh* `demo`\n * workspace — generated **before** any C# kind ever touches `nuget.config` — was\n * failing independently, for this reason alone. Unlike `brace-expansion`, ONE\n * entry nested under `nx` is enough: `smol-toml` is a dependency of `nx` itself,\n * not independently required by each `@nx/*` package the audit names, so there is\n * no sibling edge for a per-parent override to reach — verified by running the\n * real `NPM_AUDIT_STEP` against a workspace carrying only this one entry.\n *\n * **`nx` → `axios` is the third instance, on the same day mnci fixed it for\n * itself.** `nx@23.2.x` depends on `axios ^1.18.1`, and every `axios` below\n * `1.20.0` carries twelve high advisories (prototype-pollution gadgets, ReDoS,\n * proxy and redirect bypasses; the widest, GHSA-9fr6-4gfg-395g and\n * GHSA-j8rh-479h-cp32, cover `>=1.0.0 <1.20.0`). This repo's own lockfile moved\n * to `1.20.0` in a8a27bb; a workspace generated an hour later still resolved\n * `1.18.1` and failed its first CI run, with `nx` and four `@nx/*` packages\n * flagged only for inheriting it. `axios` is reached through `nx` alone (in a\n * fresh workspace, `npm ls axios` shows one path), so it nests under the `nx`\n * entry exactly as `smol-toml` does. Found bootstrapping Lore Master\n * (russoedu/MarkDoc). Check `npm ls axios` before assuming a second path ever\n * appears, and drop the entry once the `nx` mnci installs requires `^1.20.0`.\n */\nexport const ESLINT_PEER_OVERRIDES = {\n 'eslint-plugin-jsx-a11y': { eslint: '$eslint' },\n // Scoped to EACH named parent, not top-level and not `nx` alone.\n //\n // `nx` alone was the first attempt and it did not work: the e2e's audit step\n // still reported all six advisories in a freshly generated workspace, because\n // `@nx/js`, `@nx/eslint`, `@nx/eslint-plugin` and `@nx/workspace` are their own\n // dependents there, and an override scoped to `nx` never reaches a sibling's\n // dependencies. Those four are exactly the packages npm audit named.\n //\n // Top-level would be worse than wrong. A tree with `minimatch@3` legitimately\n // carries `brace-expansion@1.1.18` (and `@2.1.4` elsewhere) — this repo has\n // both — and forcing those to v5 breaks them. So the blast radius is one\n // dependency edge per named parent, and a test asserts there is no top-level\n // entry.\n 'nx': { 'brace-expansion': '^5.0.9', 'smol-toml': '^1.7.1', 'axios': '^1.20.0' },\n '@nx/js': { 'brace-expansion': '^5.0.9' },\n '@nx/eslint': { 'brace-expansion': '^5.0.9' },\n '@nx/eslint-plugin': { 'brace-expansion': '^5.0.9' },\n '@nx/workspace': { 'brace-expansion': '^5.0.9' },\n // `postcss` → `nanoid` reaches a generated workspace through `@nx/rollup`,\n // which every publishable `npm-lib` gets. GHSA-2v37-7h3g-55p8 is high, fixed in\n // 3.3.18, and the fix is a patch — so unlike the entry above this one is not\n // even a trade, just a version nobody had bumped yet.\n 'postcss': { nanoid: '^3.3.18' },\n} as const\n\n/**\n * Security overrides a generated workspace needs to pass its OWN npm audit gate.\n *\n * @remarks\n * Measured, not anticipated: a workspace straight out of `mnci new` plus one\n * `mnci add npm-lib` reported **8 high-severity advisories with zero lines of\n * user code**, and every one of them is actionable, so `NPM_AUDIT_STEP` exits 1\n * and CI is red on the first push. A scaffold that cannot pass the gate it\n * ships is the worst version of that gate — it teaches people to switch it off.\n *\n * All 8 are one advisory. `brace-expansion` GHSA-rgw5-rvv9-x895 (DoS via\n * unbounded intermediate arrays) covers `4.0.0 - 5.0.8`; `nx` and six `@nx/*`\n * packages are flagged only for depending on it. npm's own suggested remedy is\n * `nx@22.6.5` marked `isSemVerMajor` — a **downgrade of the build tool** by a\n * major, which nobody would take, and which is exactly why the audit step\n * reports `fixAvailable` rather than running `npm audit fix`.\n *\n * Two details make this override safe rather than a blunt pin:\n *\n * - It is keyed by the **vulnerable range**, not the bare package name. A plain\n * `\"brace-expansion\": \"^5.0.9\"` would drag the `1.1.18` and `2.1.4` copies\n * also present in the tree up to 5.x across a major API change, to fix an\n * advisory neither of them has. Keyed this way, `npm install` removed exactly\n * one package and left both others alone (verified).\n * - The fixed version was already resolved elsewhere in the same tree, so this\n * deduplicates onto something npm had installed anyway rather than\n * introducing a version nothing else uses.\n *\n * **Remove it when it stops being needed, and check rather than assume.** The\n * precedent is this repo's own `@verdaccio/config`/`js-yaml` pin, which read as\n * fixed while the advisory range had quietly been extended to include the\n * pinned version. The condition here is narrow: once the `nx` release this\n * scaffold installs no longer resolves a `brace-expansion` inside the\n * vulnerable range, this entry is dead weight. `npm audit` on a freshly\n * generated workspace is the check, and it is the only one that means anything.\n */\nexport const SECURITY_OVERRIDES = {\n 'brace-expansion@4.0.0 - 5.0.8': '^5.0.9',\n} as const\n\n/**\n * The ESLint toolchain a generated workspace needs as real devDependencies.\n *\n * @remarks\n * Declaring these is load-bearing, and the reason is easy to miss: `eslint`\n * ends up in `node_modules` anyway (hoisted via `@mnci/eslint-config`), but\n * Nx's generators resolve it from the workspace **manifest**, not from disk.\n * Without the declaration, `mnci add npm-lib` fails outright with \"Unable to\n * find `eslint`. Ensure a valid `eslint` version is installed\" — verified\n * against a real generated workspace.\n *\n * `create-nx-workspace --preset=ts` does not install any of these; they used\n * to arrive incidentally, whenever the first `nx add @nx/react`-style plugin\n * install happened to pull them in. Now that mnci owns the root ESLint config\n * it owns the toolchain that config needs, rather than relying on that\n * accident.\n *\n * `@nx/eslint` (the `lint` target's executor and the inference plugin) and\n * `@nx/eslint-plugin` (the `@nx/dependency-checks` rule) are pinned to the\n * workspace's own Nx version — a mismatched pair breaks target inference.\n *\n * **The formatter is deliberately not here.** Every entry above is shared by\n * both linter modes — the hybrid keeps ESLint for the file types oxlint cannot\n * parse — whereas the formatter is exactly what the choice swaps. It is picked\n * in one place, at the call site, next to {@link oxlintToolchainDependencies}\n * and {@link LINTER_ONLY_DEPENDENCIES}.\n *\n * @param nxVersion - The `nx` version already in the workspace manifest.\n * @returns The devDependency entries to merge in.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function eslintToolchainDependencies (nxVersion: string): Record<string, string> {\n return {\n 'eslint': ESLINT_VERSION,\n '@nx/eslint': nxVersion,\n '@nx/eslint-plugin': nxVersion,\n '@mnci/eslint-config': eslintConfigSpec(),\n }\n}\n\n/**\n * Config files a previous mnci version wrote for a second tool, now deleted.\n *\n * @remarks\n * mnci has shipped three formatter arrangements: Prettier (`.prettierrc.json`,\n * later `.prettierrc.mjs`), an oxlint/oxfmt mode (`oxlint.config.ts`,\n * `.oxfmtrc.json`), and now none at all — ESLint formats. `applyOverlay`\n * removes every one of these on each run, and that is not tidiness.\n *\n * A leftover config is INERT from the command line, because nothing invokes\n * those binaries any more, and that is exactly what makes it dangerous: a\n * globally installed `esbenp.prettier-vscode` or `oxc.oxc-vscode` still\n * resolves it and still reformats on save. The editor then quietly undoes\n * Standard — no semicolons become semicolons, `function f (a)` loses its space\n * — while `npm run lint` reports nothing, because the file was reformatted\n * after the last check. The `.prettierrc` precedence bug in its third costume.\n */\nexport const RETIRED_FORMATTER_FILES = [\n '.prettierrc',\n '.prettierrc.json',\n '.prettierrc.mjs',\n '.prettierignore',\n 'oxlint.config.ts',\n '.oxfmtrc.json',\n] as const\n\n/**\n * Drops the devDependencies of every formatter and linter mnci has retired.\n *\n * @remarks\n * The declaration is the point, not the install. `@mnci/eslint-config` no\n * longer depends on Prettier, so it leaves `node_modules` on its own — but a\n * workspace generated before the swap still DECLARES it, and the VS Code\n * extension resolves a formatter from the **project's** dependencies. Leaving\n * the declaration is what lets a globally installed extension find a real\n * `prettier` and reformat against an opinion nothing checks.\n *\n * Runs on `mnci upgrade` as well as `mnci new`, because a pre-swap workspace is\n * exactly the case that needs it — a fresh one has none of these to begin with.\n *\n * @param devDeps - The merged devDependencies for the root manifest.\n * @returns The same map without any retired tool.\n * @throws Never - pure object construction.\n */\nexport function withoutRetiredFormatterDependencies (\n devDeps: Record<string, string>,\n): Record<string, string> {\n const retired = new Set([\n 'prettier',\n 'eslint-config-prettier',\n 'oxlint',\n 'oxfmt',\n '@mnci/oxlint-config',\n ])\n\n return Object.fromEntries(Object.entries(devDeps).filter(([name]) => !retired.has(name)))\n}\n\n/**\n * The block-by-block inventory written into the generated `eslint.config.mjs`.\n *\n * @remarks\n * Moving every rule into `@mnci/eslint-config` bought a generated workspace one\n * root config and cost it discoverability: a three-line file gives no hint that\n * twenty tools are behind it, and someone looking at a rule they disagree with\n * has nothing to grep. This names each block, what supplies it, and what it\n * covers — keyed by the `name` every block carries, which is what\n * `eslint --inspect-config` reports and what an override targets.\n *\n * **It is not free text.** A test in `overlay.test.ts` resolves the real config\n * and fails if a name here is missing from it, or if a block in it is missing\n * here. A stale inventory is worse than none, since it sends the reader to a\n * block that no longer exists — and nothing about generating a workspace would\n * notice.\n *\n * A separate constant from {@link ESLINT_MNCI_CONFIG} so that test can pull the\n * `mnci/…` names out of it without also matching the override example further\n * down, which names a `local/…` block that deliberately does not exist.\n *\n * Some entries end in `*`, and that is deliberate rather than lazy:\n * `mnci/yaml/recommended` and `mnci/toml/base` are multi-block upstream presets,\n * and `mnci/typescript`/`mnci/type-aware` each carry a `/declarations` sibling.\n * How many blocks those split into is not a user-facing fact, so enumerating\n * them here would make the table fail on an upstream release that changes\n * nothing anyone cares about.\n */\nexport const ESLINT_BLOCK_INVENTORY = `// WHAT IS IN HERE. Each line is one config block, by the \\`name\\` it carries.\n//\n// mnci/ignores paths never linted (dist, coverage, .venv, …)\n// mnci/base JS/TS correctness — @eslint/js, eslint-plugin-unicorn,\n// -promise, -n, -unused-imports\n// typescript-eslint/* typescript-eslint's own recommended blocks\n// mnci/typescript* TS rules on top of them, no type information needed\n// mnci/type-aware* the rules that DO read types (no-floating-promises and\n// friends), scoped to {apps,libs,packages}/*/src\n// mnci/import-graph import cycles — eslint-plugin-import-x\n// mnci/react JSX/TSX — @eslint-react/eslint-plugin,\n// eslint-plugin-react-hooks, -react-refresh, -jsx-a11y\n// mnci/regexp* regex correctness — eslint-plugin-regexp\n// mnci/json mnci/jsonc mnci/json5\n// eslint-plugin-jsonc — comments are allowed in .jsonc\n// and tsconfig.json, forbidden in plain .json\n// mnci/yaml* eslint-plugin-yml — your CI pipeline files\n// mnci/toml/base* eslint-plugin-toml, PARSER ONLY: a malformed\n// pyproject.toml is a syntax error, nothing is styled\n// mnci/markdown @eslint/markdown\n// mnci/css @eslint/css\n// mnci/html @html-eslint/eslint-plugin\n// mnci/tests *.spec/*.test relaxations — eslint-plugin-jest\n// (Vitest's globals too; the two stacks share them)\n// mnci/tests/mock-aware specs in a project: eslint-plugin-jest's unbound-method,\n// which knows expect(mock.method) has no this to lose\n// mnci/nx-dependency-checks @nx/eslint-plugin, on publishable packages' manifests\n// mnci/standard JavaScript Standard Style as ESLint rules — the\n// whole formatting opinion, a faithful port of\n// neostandard\n// mnci/house-style the deliberate departures from Standard:\n// trailing commas, aligned object values,\n// consistent-as-needed quote-props, a blank line\n// before return. Composed LAST, on purpose:\n// nothing may follow that disables it.\n//\n// To list them as ESLint actually resolves them: npx eslint --inspect-config\n`\n\n/**\n * The mnci-owned half of the ESLint configuration.\n *\n * @remarks\n * Rewritten by every `mnci upgrade`, which is why it is not the file ESLint\n * loads: {@link ESLINT_USER_FILENAME} is, and it imports this one.\n */\nexport const ESLINT_MNCI_FILENAME = 'eslint.config.mnci.mjs'\n\n/**\n * The workspace's own half, and ESLint's actual entry point.\n *\n * @remarks\n * Written once and then never touched again, so a block appended to it\n * survives every upgrade. The name is ESLint's own default rather than a\n * choice — a flat config is found at `eslint.config.mjs`, so the file the tool\n * looks for has to be the one the workspace owns.\n */\nexport const ESLINT_USER_FILENAME = 'eslint.config.mjs'\n\n/**\n * The rules, in the file mnci owns.\n *\n * @remarks\n * This is the half that `mnci upgrade` rewrites, and it is deliberately NOT\n * the file ESLint loads. ESLint's entry point is `eslint.config.mjs`, which\n * imports this one and belongs to the workspace — see {@link ESLINT_USER_CONFIG}\n * for why the two are separate at all.\n *\n * Deliberately one import: every rule lives in `@mnci/eslint-config`, so the\n * twenty-odd plugins are that package's dependencies instead of twenty-odd\n * devDependencies in every generated workspace.\n *\n * `workspaceRoot` enables the `@nx/dependency-checks` block for `packages/*`\n * and `libs/*` — it has to scan for `private: true` manifests, which is why it\n * needs the path rather than deriving one. `import.meta.dirname` is correct\n * here because both ESLint files sit at the workspace root.\n *\n * It exports a FUNCTION rather than the resolved array, and that is the one\n * part of the split that is not obvious. `@mnci/eslint-config` takes options —\n * `verticalSlices` most notably — and a workspace that wants one has nowhere\n * to put it if this file hands back an array it may not edit. mnci's own\n * repository is the proof: it passes `verticalSlices` scoped to the CLI, and\n * the first draft of this split would have had nowhere for that to live.\n *\n * Everything else in the file is comment: {@link ESLINT_BLOCK_INVENTORY}, then\n * the rules about formatters. Both are there because the alternative to\n * documenting a three-line config is a user editing `node_modules`.\n */\nexport const ESLINT_MNCI_CONFIG = `// GENERATED BY mnci. Every \\`mnci upgrade\\` overwrites this file.\n//\n// Do not edit it: put your own blocks in eslint.config.mjs next to it, which\n// imports this one and which mnci never touches once it exists.\nimport mnci from '@mnci/eslint-config'\n\n${ESLINT_BLOCK_INVENTORY}//\n// FORMATTING IS LINTING HERE. There is no Prettier, no oxfmt and no\n// \\`format:check\\` — \\`npm run lint\\` reports indentation, quotes and spacing as\n// ordinary errors, and \\`npm run format\\` is \\`eslint . --fix\\`. So do not add a\n// formatter: whichever one you pick will disagree with the \\`mnci/standard\\`\n// block above, and because a formatter runs on save it wins silently, leaving\n// \\`lint\\` to fail on files you never edited by hand.\n//\n// That also applies to the editor. Installing a Prettier or oxfmt extension is\n// enough on its own — neither needs a config file, and with none present they\n// format against their own defaults (semicolons, double quotes), which is the\n// inverse of Standard.\n//\n// Exported as a FUNCTION, not as the resolved array. @mnci/eslint-config takes\n// options — \\`verticalSlices\\` is the one a workspace is most likely to want —\n// and an already-resolved array has nowhere to receive them. Calling it from\n// eslint.config.mjs is what keeps every option reachable from the file you own.\n//\n// \\`workspaceRoot\\` is resolved HERE because both files sit at the root, and it\n// is what enables the @nx/dependency-checks block.\n//\n// A named function declaration, exported inline. Both shapes matter and both\n// are enforced by this very config: \\`unicorn/no-anonymous-default-export\\`\n// rejects an anonymous arrow, and \\`unicorn/default-export-style\\` rejects\n// declaring one above the export and referring to it. Either mistake fails a\n// generated workspace's own lint on its first run.\nexport default function mnciConfig (options = {}) {\n return mnci({ workspaceRoot: import.meta.dirname, ...options })\n}\n`\n\n/**\n * The workspace's own ESLint config: the file ESLint loads, written ONCE.\n *\n * @remarks\n * The reason this file exists at all is that the previous layout lost work.\n * `eslint.config.mjs` was mnci-owned and rewritten wholesale on every\n * `mnci upgrade`, so any block a workspace had appended to it — the documented\n * way to override a rule, in a comment mnci itself wrote three lines above —\n * was deleted without a word. Worse, `upgrade` then tells the user to run\n * `npm run format`, so the first thing that happens after the rules change is\n * that every file in the repository is rewritten against them.\n *\n * So the file is split. `eslint.config.mnci.mjs` carries the rules and stays\n * mnci's; this one carries the workspace's and is written only when it does\n * not already exist. {@link isUnmodifiedMnciEslintConfig} is what lets an\n * existing workspace cross over safely: an `eslint.config.mjs` still on the old\n * single-file layout is replaced only when it provably holds nothing of the\n * user's, and is otherwise left exactly as it is for `mnci doctor` to report.\n *\n * The spread rather than a re-export is deliberate: `...mnci()` puts the blocks\n * in an array the reader can append to, and appending is the whole point.\n */\nexport const ESLINT_USER_CONFIG = `// This file is YOURS. mnci writes it once and never touches it again, so\n// anything you add here survives \\`mnci upgrade\\`.\n//\n// The rules live in ./eslint.config.mnci.mjs, which mnci DOES rewrite on every\n// upgrade — so put your changes here, not there.\nimport mnci from './${ESLINT_MNCI_FILENAME}'\n\n// TO CONFIGURE the shared rules, pass options to mnci() below — e.g.\n// \\`...mnci({ verticalSlices: ['packages/*/src/**/*.ts'] })\\`.\n//\n// TO OVERRIDE a rule, append a block AFTER the spread — later blocks win, so\n// one of your own beats anything above it. Give it a name, so\n// \\`npx eslint --inspect-config\\` shows where the change came from:\n//\n// {\n// name: 'local/legacy-app-allows-any',\n// files: ['apps/legacy/**/*.ts'],\n// rules: { '@typescript-eslint/no-explicit-any': 'off' }\n// }\n//\n// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it\n// is a dependency, so \\`npm update\\` brings rule fixes in the way it brings any\n// other. An override here survives that; an edit to the package does not.\nexport default [\n ...mnci(),\n]\n`\n\n/**\n * Whether an existing `eslint.config.mjs` is unmodified mnci output.\n *\n * @remarks\n * The question this answers is narrow and the answer has to be certain:\n * *may this file be replaced without losing anything a human wrote?* It is\n * asked once, when a workspace on the old single-file layout is upgraded.\n *\n * So it does not guess. Every comment and blank line is dropped, and what is\n * left has to be exactly the two statements mnci has always generated — the\n * import of `@mnci/eslint-config` and the default export of one call to it.\n * Anything else at all, including a block appended in the documented way,\n * makes this false and the file is left alone.\n *\n * Comments are recognised as lines whose first non-space characters are `//`,\n * which is the only comment form the generated file has ever used. A file\n * using another form simply fails to match, which is the safe direction: the\n * cost of a false negative is a `mnci doctor` finding, and the cost of a false\n * positive is deleted work.\n *\n * @param content - The file's current contents.\n * @returns Whether replacing it can lose nothing.\n * @throws Never - any content that does not match reads as modified.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isUnmodifiedMnciEslintConfig (content: string): boolean {\n const code = content\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line !== '' && !line.startsWith('//'))\n\n return (\n code.length === 2 &&\n code[0] === \"import mnci from '@mnci/eslint-config'\" &&\n code[1] === 'export default mnci({ workspaceRoot: import.meta.dirname })'\n )\n}\n\n/**\n * Writes `eslint.config.mjs` only when doing so cannot lose anything.\n *\n * @remarks\n * Three cases, and they are all the cases there are:\n *\n * - **Absent** — a new workspace, or one that has never had the split. Write\n * it.\n * - **Present and unmodified mnci output** — a workspace on the old\n * single-file layout that never overrode anything. Replacing it moves it\n * onto the split and loses only comments mnci wrote itself, so write it.\n * - **Present and modified** — either already on the split, or on the old\n * layout with the user's own blocks in it. Leave it. `mnci doctor` reports\n * the second of those, because a config that no longer reaches\n * `eslint.config.mnci.mjs` silently lints against nothing but its own blocks.\n *\n * @param workspaceRoot - Absolute path of the workspace root.\n * @param onProgress - Receives one line per file, for the CLI's output.\n * @returns Nothing.\n */\nfunction writeEslintEntryPoint (workspaceRoot: string, onProgress: (line: string) => void): void {\n const path = join(workspaceRoot, ESLINT_USER_FILENAME)\n if (existsSync(path) && !isUnmodifiedMnciEslintConfig(readFileSync(path, 'utf8'))) {\n onProgress(`${ESLINT_USER_FILENAME} — kept as it is, it is yours`)\n\n return\n }\n\n onProgress(`${ESLINT_USER_FILENAME} — the entry point, yours to edit from now on`)\n writeFileEnsured(path, ESLINT_USER_CONFIG)\n}\n\n/**\n * The editor extensions both the `.code-workspace` file and the devcontainer\n * recommend.\n *\n * @remarks\n * Shared so the two cannot drift: opening the workspace in a container should\n * suggest the same toolset as opening it directly.\n *\n * **`esbenp.prettier-vscode` is deliberately absent, and its absence is the\n * point.** mnci recommended it for as long as Prettier owned formatting, and\n * kept recommending it for a while after ESLint took over — which made mnci the\n * thing that installed its own hazard. The extension does not need a config\n * file to act: with none present it formats against Prettier's own defaults,\n * semicolons and double quotes, the exact inverse of Standard. It reformats on\n * save, so the damage lands *after* every gate has run and `lint` stays green\n * until the next time someone looks. {@link RETIRED_FORMATTER_FILES} exists to\n * clean up after precisely that, and recommending the extension alongside it\n * was the two halves of one decision disagreeing.\n */\nexport const VSCODE_RECOMMENDED_EXTENSIONS = [\n 'dbaeumer.vscode-eslint',\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner',\n] as const\n\n/**\n * Every language whose formatter mnci pins to the ESLint extension.\n *\n * @remarks\n * Not a convenience list — see {@link vscodeSettings} for why the general\n * `editor.defaultFormatter` cannot be relied on. Each entry is a language\n * `@mnci/eslint-config` actually has a parser and rules for, so that\n * format-on-save routes to a tool with an opinion about the file rather than to\n * whatever the user happens to have installed.\n *\n * `typescript` and `typescriptreact` are the two that matter most and were once\n * the two missing, which is the bug {@link vscodeSettings} documents.\n *\n * This list used to be justified as \"everything **both** formatters handle\",\n * measured against the real Prettier and oxfmt binaries. Both are retired, so\n * the membership test is now a single question — does the shared ESLint config\n * cover this language — and nothing has to be kept in agreement with a second\n * tool.\n */\nexport const FORMATTED_LANGUAGES = [\n 'javascript',\n 'javascriptreact',\n 'typescript',\n 'typescriptreact',\n 'json',\n 'jsonc',\n 'yaml',\n 'markdown',\n 'css',\n 'html',\n // TOML is here for PARSING, not formatting, and the distinction is measured\n // rather than assumed: `eslint --fix` leaves a badly-laid-out `.toml`\n // byte-identical (the TOML block is `flat/base`, parser only — `flat/standard`\n // reported six errors on the `pyproject.toml` mnci itself generates), but it\n // DOES report `Parsing error: ...` on a malformed one.\n //\n // So the entry buys two things. `eslint.validate` gains toml, so a broken\n // `pyproject.toml` is flagged in the editor rather than at the next CI run.\n // And pinning the formatter to ESLint means format-on-save does nothing to the\n // file instead of handing it to whichever TOML formatter the user happens to\n // have installed — the same reasoning as every other entry here: nothing\n // reformats against an opinion no gate checks.\n //\n // Impossible under the Prettier stack, which is why it is only here now:\n // `npx prettier` on a `.toml` exits with \"No parser could be inferred\".\n 'toml',\n] as const\n\n/**\n * The editor settings that depend on the linter choice.\n *\n * @remarks\n * Two things change together, and they have to: which extension formats, and\n * which languages ESLint is asked to validate.\n *\n * `eslint.validate` lists every language in {@link FORMATTED_LANGUAGES},\n * because ESLint owns all of them now. There is no second formatter to hand any\n * of them to, and so no narrowed variant of this list to keep in step.\n *\n * @returns The `settings` block for the `.code-workspace` file.\n * @throws Never - performs pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeSettings (): Record<string, unknown> {\n // ESLint is the formatter now, so `editor.defaultFormatter` points at the\n // ESLint extension and `source.fixAll.eslint` is what actually reformats on\n // save. There is no second formatter to disagree with it.\n const formatter = 'dbaeumer.vscode-eslint'\n const eslintLanguages = [...FORMATTED_LANGUAGES]\n\n return {\n 'eslint.validate': eslintLanguages,\n 'editor.codeActionsOnSave': {\n 'source.fixAll.eslint': 'explicit',\n },\n 'editor.defaultFormatter': formatter,\n 'editor.formatOnSave': true,\n // Every language spelled out, and the global default above is NOT enough on\n // its own — which is the bug this fixes rather than a belt-and-braces habit.\n //\n // VS Code resolves a language-specific setting ahead of a general one, and it\n // does that comparison BEFORE scope. So a `[typescript]` block in someone's\n // USER settings — left over from any other project — outranks this file's\n // workspace-level `editor.defaultFormatter`, and format-on-save quietly uses\n // that other formatter instead — and nothing reports it, because the gate\n // and the editor are looking at different tools. That is how it was found:\n // reported from a real workspace, not deduced from the docs.\n //\n // Reported from a real workspace where `.ts` files were not being formatted\n // while `.json`/`.jsonc`/`.yaml` were — exactly the three that had explicit\n // entries here and nothing else did.\n ...Object.fromEntries(\n FORMATTED_LANGUAGES.map(language => [\n `[${language}]`,\n { 'editor.defaultFormatter': formatter },\n ]),\n ),\n }\n}\n\n/**\n * Prefix marking a launch configuration as mnci-owned.\n *\n * @remarks\n * `vscodeWorkspace` replaces every configuration carrying this prefix and carries\n * every other one through untouched, so a hand-written debug config survives\n * `mnci upgrade`. Renaming it would orphan the previous generation's entries,\n * leaving a workspace with two of each.\n */\nexport const LAUNCH_CONFIG_PREFIX = 'mnci: '\n\n/**\n * The verify targets exposed in VS Code's **Run and Debug** panel.\n *\n * @remarks\n * Tasks alone were not enough. A `tasks` entry is only reachable through\n * *Terminal -\\> Run Task*; the Run and Debug dropdown reads `launch`, so a workspace\n * with tasks and no launch section offers nothing there at all. These four are the\n * same targets the root `affected` script runs.\n *\n * **They drive npm scripts, not the Nx binary.** Pointing `program` at\n * `node_modules/nx/bin/nx.js` would be wrong: Nx ships its bin at\n * `dist/bin/nx.js`, and that path is version-dependent. `npm run \\<script\\>` is stable,\n * needs no path into `node_modules`, and tracks whatever the root script does — so a\n * change to `ROOT_SCRIPTS` reaches these for free.\n *\n * **`node-terminal`, not `node`, and that is the load-bearing choice.** It runs the\n * command in VS Code's JS Debug Terminal, which instruments **child** processes as\n * they spawn. `nx run-many` executes every target in a child process, so a plain\n * `node` launch would attach to the Nx parent alone and a breakpoint inside a spec\n * would never bind. It also needs no `console` setting: a plain `node` launch\n * defaults to `internalConsole`, which renders none of Nx's progress output, so a\n * build there looks like a hang.\n */\nexport const LAUNCH_CONFIGURATIONS = ['build', 'test', 'lint', 'typecheck'] as const\n\n/**\n * Builds the `launch.configurations` array for a generated workspace.\n *\n * @remarks\n * One entry per {@link LAUNCH_CONFIGURATIONS} target, each named with\n * {@link LAUNCH_CONFIG_PREFIX} so an upgrade can tell its own entries from a\n * user's. Order matches the verify order the root `affected` script runs.\n *\n * @param workspaceName - The workspace name, used to scope `${workspaceFolder}`.\n * @returns One configuration per entry in {@link LAUNCH_CONFIGURATIONS}.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function launchConfigurations (workspaceName: string): Record<string, unknown>[] {\n return LAUNCH_CONFIGURATIONS.map((script, index) => ({\n type: 'node-terminal',\n request: 'launch',\n name: `${LAUNCH_CONFIG_PREFIX}${script}`,\n command: `npm run ${script}`,\n // Scoped by folder name rather than a bare ${workspaceFolder}: that variable is\n // ambiguous once a second folder is added to the workspace, and VS Code then\n // refuses to resolve it.\n cwd: `\\${workspaceFolder:${workspaceName}}`,\n // One shared group keeps the four together in the dropdown; a per-entry group\n // would make four groups of one. `order` holds them in verify order.\n presentation: { group: 'mnci', order: index + 1 },\n }))\n}\n\n/**\n * VS Code workspace file template for generated monorepos.\n *\n * @remarks\n * Creates a single .code-workspace file that configures the entire monorepo:\n * folder structure, recommended extensions (ESLint, Nx, Jest), workspace\n * settings, and a `tasks` array. The array starts empty; `add/*.ts`'s\n * `registerProjectCommands` (`commands/add/shared.ts`) appends a\n * `build`/`qa`/`start` task per project as it is added, so this template\n * itself carries no project-specific content — it must stay generic across\n * every `mnci new`-generated workspace, not just this repo's own dogfooded\n * root.\n * It also carries a `launch` section, because a `tasks` entry is reachable only\n * through *Terminal -\\> Run Task* while the **Run and Debug** panel reads `launch` —\n * a workspace with tasks alone offers nothing there. See\n * {@link LAUNCH_CONFIGURATIONS}. Unlike `tasks`, which is carried through wholesale,\n * the launch array is merged: mnci replaces only its own `mnci: *` entries.\n * Users open this file in VS Code (`File > Open Workspace from File`).\n *\n * **`existingTasks` is what keeps `mnci upgrade` non-destructive**, and the need\n * for it was masked by another bug. The overlay owns this file's folders,\n * settings and extensions, but the `tasks` array is per-project state written by\n * `mnci add`, not by the overlay — so regenerating the file wholesale destroys\n * every registered task. That never surfaced only because upgrade used to write\n * to `undefined.code-workspace` and leave the real file untouched; fixing the\n * filename exposed it immediately (verified: a workspace with three projects lost\n * all five of its tasks). Tasks are carried through verbatim rather than\n * regenerated, since the overlay has no idea which projects exist.\n *\n * @param workspaceName - The workspace name.\n * @param existingTasks - The `tasks` object read from a file already on disk, to\n * carry through unchanged. Omitted for a fresh `mnci new`, where there is none.\n * @returns The JSON string for a .code-workspace file.\n * @throws Never - performs pure string formatting with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeWorkspace (\n workspaceName: string,\n existingTasks?: { version?: string; tasks?: Record<string, unknown>[] },\n existingLaunch?: { version?: string; configurations?: Record<string, unknown>[] },\n existingSettings?: Record<string, unknown>,\n): string {\n // Additive, like nx.json's sharedGlobals: mnci replaces only the configurations it\n // owns (named `mnci: *`) and carries every other one through, so a hand-written\n // debug config survives `mnci upgrade`. Tasks are carried through wholesale\n // instead, because `mnci add` — not the overlay — is what writes them.\n const userConfigurations = (existingLaunch?.configurations ?? []).filter(\n (configuration) => !String(configuration.name ?? '').startsWith(LAUNCH_CONFIG_PREFIX),\n )\n // Settings are MERGED, with mnci winning on the keys it owns. Replacing them\n // wholesale destroyed every setting a workspace had added for itself — measured on\n // this repo's own file, where an upgrade would have deleted a 1,179-entry\n // `cSpell.words` dictionary that nothing else stores. mnci overwrites only the\n // keys it actually sets, so its opinion still lands on every upgrade while\n // anything it has no opinion about survives.\n const settings = { ...existingSettings, ...vscodeSettings() }\n\n // `toJson`, like every other JSON file mnci writes: it ends with a newline.\n // Bare JSON.stringify did not, while `mnci new`'s format pass adds one, so\n // every `mnci upgrade` showed the same one-character diff on this file.\n return toJson(\n {\n folders: [{ path: '.', name: workspaceName }],\n settings,\n extensions: { recommendations: VSCODE_RECOMMENDED_EXTENSIONS },\n tasks: {\n version: existingTasks?.version ?? '2.0.0',\n tasks: existingTasks?.tasks ?? [],\n },\n launch: {\n version: existingLaunch?.version ?? '0.2.0',\n configurations: [...launchConfigurations(workspaceName), ...userConfigurations],\n },\n },\n )\n}\n\n/**\n * The curated npm scripts stamped into a generated workspace's root manifest.\n *\n * @remarks\n * Each one is a single cross-platform Nx (or husky) invocation — the everyday\n * entry points, nothing more. `affected` compares against `main`\n * (`defaultBase` in `nx.json`); `release:preview` shows what `nx release`\n * would do on CI without touching anything.\n *\n * `typecheck` is its own script because nothing else type-checks. Under\n * `--preset=ts` the `@nx/js/typescript` plugin infers a `typecheck` target for\n * every project, but a bundler-built project's `build` does not type-check at\n * all — esbuild and swc strip types without reading them. So a workspace can\n * build, test and lint green while carrying real type errors, which is exactly\n * what happened in mnci's own repo: `mnci upgrade` shipped a bug that\n * TypeScript had already flagged, because CI never ran this.\n */\nexport const ROOT_SCRIPTS = {\n 'build': 'nx run-many -t build',\n 'lint': 'nx run-many -t lint',\n 'test': 'nx run-many -t test',\n 'typecheck': 'nx run-many -t typecheck',\n 'affected': 'nx affected -t lint,typecheck,test,build',\n 'graph': 'nx graph',\n 'release:preview': 'nx release --dry-run',\n 'prepare': 'husky',\n} as const\n\n/**\n * The curated scripts, with ESLint doing both the linting and the formatting.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`) covering code\n * quality, type-aware correctness *and* JavaScript Standard Style formatting.\n * `npm run format` is `eslint . --fix`; there is no `format:check`, because\n * `lint` already reports a formatting mistake as an ordinary error.\n *\n * Every stack also gets `python:install`, chaining the same two guards CI runs\n * ({@link PYTHON_INSTALL_GUARD} then {@link PYTHON_WORKSPACE_INSTALL_GUARD}) —\n * the fixed dev toolchain (ruff/pytest/build/twine) plus an editable install of\n * every workspace Python project, so a fresh clone's `pip install` step is one\n * command instead of \"read the CI pipeline to find the right invocation\". Both\n * guards already no-op cleanly on a workspace with no Python projects, so it is\n * safe to stamp unconditionally rather than gating on whether one exists yet.\n *\n * @returns The root scripts object to stamp into the manifest.\n * @throws Never - pure mapping.\n */\nexport function rootScripts (): Record<string, string> {\n return {\n ...ROOT_SCRIPTS,\n // ESLint is the formatter, so `format` is `--fix` and there is no separate\n // `format:check`: `lint` already reports formatting as ordinary errors.\n // Carrying a second script that ran the same tool twice would just make a\n // CI run slower for no extra coverage.\n 'format': 'eslint . --fix --cache',\n 'python:install': `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}`,\n }\n}\n\n/**\n * The Nx `generators` defaults patched into `nx.json` from the chosen stack.\n *\n * @remarks\n * Lets a user's own **direct** `nx g @nx/react:app ...` (outside `mnci add`)\n * pick up the workspace's chosen test runner automatically, and — via\n * `linter: 'none'` — keep the workspace's single root ESLint config intact\n * instead of scaffolding a competing per-project one.\n *\n * `mnci add` itself does **not** read this back — see {@link mnciConfig} for\n * the dedicated, single-source-of-truth block it reads instead. The two used\n * to be conflated (`add` inferred the stack from one of these three identical\n * blocks), an implicit \"all three stay in lockstep\" invariant nothing enforced.\n *\n * @param stack - The chosen stack.\n * @returns The `generators` object for `nx.json`.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function generatorDefaults (stack: StackConfig): Record<string, unknown> {\n const shared = {\n // `none`, not `eslint`, and the workspace is still fully linted. The root\n // config plus `@nx/eslint/plugin` ({@link ESLINT_PLUGIN_CONFIG}) give every\n // project its `lint` target; `eslint` here would only make the generator\n // scaffold a per-project config mnci deletes anyway — and drag in\n // `eslint-plugin-import@2.31.0`, which peer-caps at ESLint 9 and breaks the\n // install outright on this workspace's ESLint 10.\n linter: 'none',\n unitTestRunner: stack.testRunner,\n }\n\n return {\n '@nx/react:application': shared,\n '@nx/react:library': shared,\n '@nx/js:library': shared,\n }\n}\n\n/**\n * The `mnci` block patched into `nx.json` from the options a `new`/`upgrade`\n * call resolved.\n *\n * @remarks\n * Two independent readers trust this one block: `mnci add`'s\n * `readWorkspaceStack` (`add.ts`) reads only `.stack`, and `mnci upgrade`\n * (`readMnciConfig`, below) reads the whole thing back as the defaults for\n * everything an explicit flag does not override — the only reason `scope`/\n * `registry`/`agent`/`variableGroup`/`ci` are persisted at all, since nothing\n * else in a generated workspace records them. Deliberately separate from\n * {@link generatorDefaults}, which serves Nx's own generator-default\n * mechanism instead (a real, independent feature: it makes a user's own\n * direct `nx g` pick up the right defaults too).\n *\n * @param options - The resolved overlay options (a `new`/`upgrade` call).\n * @returns The `mnci` object for `nx.json`.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function mnciConfig (options: OverlayOptions): Record<string, unknown> {\n return {\n // Persisted so `mnci upgrade` can name the `<name>.code-workspace` file it\n // rewrites. Its absence is why upgrade used to write a file literally called\n // `undefined.code-workspace` and therefore never refreshed the real one —\n // see `resolveWorkspaceName` in `commands/upgrade.ts`, which still needs a\n // fallback chain for workspaces generated before this field existed.\n workspaceName: options.workspaceName,\n scope: options.scope,\n registry: options.registry,\n agent: options.agent,\n variableGroup: options.variableGroup,\n ci: options.ci,\n stack: { testRunner: options.stack.testRunner },\n // Only when chosen: a workspace on the default keeps the block it always had,\n // so an upgrade does not add a field nobody asked for.\n ...(options.npmAuth !== undefined && { npmAuth: options.npmAuth }),\n }\n}\n\n/**\n * Reads back whatever a previous `new`/`upgrade` call persisted via\n * {@link mnciConfig}.\n *\n * @remarks\n * The read-side counterpart `mnci upgrade` (`commands/upgrade.ts`) uses to\n * resolve options: an explicit flag wins, otherwise the persisted value here\n * is the default, so a plain `mnci upgrade` with no flags re-applies the\n * exact same overlay the workspace already has, just regenerated from\n * today's `overlay.ts`. A workspace generated before this was persisted (or\n * hand-edited to remove it) simply has fewer fields here — `upgrade` reports\n * exactly which ones are missing rather than guessing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Whatever subset of {@link OverlayOptions} is persisted in\n * `nx.json`'s `mnci` block (empty object when there is none).\n * @throws Propagates any Node.js `fs`/JSON error reading `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function readMnciConfig (workspaceRoot: string): Partial<OverlayOptions> {\n const nxJson = readJson<Record<string, unknown>>(join(workspaceRoot, 'nx.json'))\n\n return (nxJson.mnci as Partial<OverlayOptions> | undefined) ?? {}\n}\n\n/**\n * The Python package registry's `twine upload` URL for a registry config.\n *\n * @remarks\n * Azure Artifacts feeds are **multi-protocol**: the same org/project/feed that\n * serves npm also serves Python, so the pypi upload URL is derived from the same\n * {@link RegistryConfig} — no separate Python registry prompt at `new`. Public\n * npm has no Python analogue wired in this cut (publishing to public PyPI needs\n * a PyPI token, a separate mechanism), so it returns `undefined` and the CI\n * Python-publish step is omitted.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The pypi upload URL for Azure Artifacts, or `undefined` for npm.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pythonPublishUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/pypi/upload/`\n }\n\n return undefined\n}\n\n/**\n * The Azure Artifacts NuGet v3 feed URL for a registry config.\n *\n * @remarks\n * Same multi-protocol feed {@link pythonPublishUrl} already reads — one\n * org/project/feed serves npm, Python **and** NuGet — so this is the third\n * reader of the same {@link RegistryConfig}, not a separate prompt. Public\n * npm has no NuGet analogue wired in this cut, the same gap\n * {@link pythonPublishUrl}'s own remarks document for PyPI: publishing to\n * public nuget.org needs a nuget.org-issued API key, a credential mnci\n * collects nowhere, so this returns `undefined` and `csharp-lib` is\n * versioned + tagged but not auto-published — see {@link addCsharpLib}.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The NuGet v3 service index URL for Azure Artifacts, or\n * `undefined` for npm.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function nugetFeedUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/nuget/v3/index.json`\n }\n\n return undefined\n}\n\n/**\n * The fixed `nuget.config` source key mnci registers the Azure Artifacts\n * NuGet feed under.\n *\n * @remarks\n * Deliberately a constant, not `registry.artifactsFeed` — the real feed\n * name. `packageSourceCredentials` attaches to a source by its registered\n * KEY, not its URL, so the publish target in `add/csharp.ts`\n * (`dotnet nuget push --source ...`) needs to name the SAME key\n * {@link nugetConfigContent} registers. Fixing it here means that target\n * needs no {@link RegistryConfig} of its own at generation time — it just\n * references this constant — which is what keeps `addCsharpLib` from\n * needing to re-derive the workspace's registry choice per `add`, the way\n * Python's `nx-release-publish` target also carries no registry specifics\n * of its own (its `TWINE_*` env vars are injected only at CI release time).\n */\nexport const NUGET_AZURE_SOURCE = 'AzureArtifacts'\n\n/**\n * Builds the `nuget.config` body for a registry configuration.\n *\n * @remarks\n * Mirrors {@link npmrcContent}'s split, but the underlying auth mechanics\n * genuinely differ — verified against NuGet's own docs\n * (`nuget.config` file reference, and Azure Artifacts' \"Publish NuGet\n * packages with dotnet CLI\" guide), not assumed from the `.npmrc` case:\n *\n * - **NuGet's env-var substitution is `%VAR%` on every platform, never\n * `${VAR}` or `$VAR`.** Confirmed from Microsoft's own compatibility\n * table: `$MY_VAR` resolves on none of `nuget.exe`/`dotnet.exe`, Windows or\n * Mac. Getting this backwards is the exact class of trap the `.npmrc`\n * Bearer-vs-Basic saga already cost this repo once — a config that\n * *parses* but silently never substitutes anything.\n * - **`packageSourceCredentials` attaches to a REGISTERED source by key**,\n * not to the URL passed to `--source` at push time — confirmed by Azure's\n * own guide, which registers the feed under `packageSources` even though\n * `dotnet nuget push --source <url>` alone would also resolve the URL.\n * Skipping registration would leave the credentials with nothing to\n * attach to. {@link NUGET_AZURE_SOURCE} is that key.\n * - **`packageSourceMapping` scopes the private feed to this workspace's own\n * `<PascalScope>.*` packages**, the direct analogue of `.npmrc`'s\n * scope-only routing and for the identical reason: an unscoped\n * `<packageSources>` entry would have every `dotnet restore` — including a\n * contributor's local one, with no PAT set — query the private feed for\n * packages that were never going to be found there, on every build.\n * - **Public nuget.org needs no credentials at all** — restore is anonymous,\n * and {@link nugetFeedUrl}'s remarks explain why publish is left\n * unconfigured for that choice rather than half-wired.\n *\n * The one PAT is the same **raw** value `twine` already uses (see\n * {@link pythonPublishEnvFragment}), not the base64 form `.npmrc`'s\n * `_password` takes — NuGet's Basic auth is handled by the HTTP client\n * itself from a plain `Username`/`ClearTextPassword` pair, so no manual\n * base64 step belongs here.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`); its PascalCase form is the\n * `packageSourceMapping` pattern, matching {@link addCsharpLib}'s own\n * `<PascalScope>.<PascalName>` NuGet identity.\n * @returns The full text of the generated `nuget.config`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function nugetConfigContent (registry: RegistryConfig, scope: string): string {\n if (registry.kind === 'npm') {\n return `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Publish authentication for NuGet is deliberately UNCONFIGURED for the\n public npm registry choice: publishing to public nuget.org needs a\n nuget.org-issued API key, a credential mnci collects nowhere (the same\n gap Python's PyPI publish has for this same registry choice). A\n csharp-lib is still versioned and tagged; run \"dotnet nuget push\"\n yourself with your own key, or regenerate choosing the azure-artifacts\n registry instead. -->\n<configuration>\n <packageSources>\n <clear />\n <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n </packageSources>\n</configuration>\n`\n }\n\n const pascalScope = scope\n .replace(/^@/, '')\n .split('-')\n .map(part => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n const feedUrl = nugetFeedUrl(registry) as string\n\n return `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- NuGet's own environment-variable syntax is '%VAR%' on every platform —\n never '\\${VAR}'/'$VAR', which resolve on none of them. NUGET_PAT is the\n RAW PAT (not base64 — unlike .npmrc's _password, NuGet's HTTP client\n handles Basic auth itself from Username + ClearTextPassword), exported\n only by the CI release step (see nugetPublishEnvFragment in mnci); an\n ordinary build/test/lint step never needs it, since packageSourceMapping\n below scopes this feed to ${pascalScope}.* packages only. -->\n<configuration>\n <packageSources>\n <clear />\n <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n <add key=\"${NUGET_AZURE_SOURCE}\" value=\"${feedUrl}\" />\n </packageSources>\n <packageSourceCredentials>\n <${NUGET_AZURE_SOURCE}>\n <add key=\"Username\" value=\"AzureArtifacts\" />\n <add key=\"ClearTextPassword\" value=\"%NUGET_PAT%\" />\n </${NUGET_AZURE_SOURCE}>\n </packageSourceCredentials>\n <packageSourceMapping>\n <clear />\n <packageSource key=\"${NUGET_AZURE_SOURCE}\">\n <package pattern=\"${pascalScope}.*\" />\n </packageSource>\n <packageSource key=\"nuget.org\">\n <package pattern=\"*\" />\n </packageSource>\n </packageSourceMapping>\n</configuration>\n`\n}\n\n/**\n * The portable `node -e` one-liner that installs the fixed Python toolchain\n * (`ruff`/`pytest`/`build`/`twine`) from `requirements-dev.txt`.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}\n * — one guard script, so the two providers can never drift on what \"install\n * Python deps\" means. Skips cleanly on a workspace with no Python projects\n * (no `requirements-dev.txt`, written by `add/python.ts` on the first\n * Python `add`).\n *\n * Resolves `python` vs `python3` at run time via `process.platform`, not a\n * hard-coded name: the standard python.org Windows installer registers only\n * `python.exe`, so a build agent on a `windows-latest` (or self-hosted\n * Windows) runner hard-fails immediately on a hard-coded `python3` with\n * \"'python3' is not recognized as an internal or external command\" — every\n * POSIX system (the assumed default) registers `python3`. Same resolution\n * {@link PYTHON_WORKSPACE_INSTALL_GUARD} and every `@mnci/nx-python-pip`\n * executor use (that package's own `pythonCommand` helper); this guard is a\n * plain generated string, not TypeScript, so it inlines the identical check\n * rather than importing it.\n */\nconst PYTHON_INSTALL_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'requirements-dev.txt\\')){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';process.exit(require(\\'node:child_process\\').spawnSync(py+\\' -m pip install -r requirements-dev.txt\\',{stdio:\\'inherit\\',shell:true}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that editable-installs every Python\n * project into one shared environment, so cross-project imports resolve at\n * lint/test/dev time — the pip-world counterpart of `npm install` hoisting\n * every workspace package into one root `node_modules`.\n *\n * @remarks\n * Pip has no native workspace protocol (no hoisting, no auto-symlinking of\n * sibling packages), so this is hand-built rather than something pip does on\n * its own: every project with a `pyproject.toml` (`apps/*`, `python-packages/*`,\n * `libs/*` — apps, publishable libs, and internal libs alike) is\n * `pip install -e`'d, and every Azure Function app (`requirements.txt`, no\n * `pyproject.toml` — the shape `@mnci/nx-python-pip`'s `function-application`\n * generator writes) gets `pip install -r`'d, all in **one** `pip install`\n * invocation (not one per project) so the resolver sees every requirement\n * together, same as one `npm install` at the root.\n *\n * This is deliberately broader than the `test` executor's own per-project\n * `pip install -e .` (`@mnci/nx-python-pip`'s `installEditable` option,\n * which only installs the project under test, not what it imports): an\n * internal lib is normally only woven into a consumer at **build** time (the\n * `build` executor's vendoring copy step — see `@mnci/nx-python-pip`'s\n * README), so without this step a project that imports an internal lib\n * cannot resolve that import at test/dev time, only at the final wheel. This\n * step editable-installs the internal lib too, so the import resolves\n * everywhere it is written, not just in the built artifact. It does not\n * change what a published wheel contains — vendoring at build time is\n * unaffected, since pip has no registry-time equivalent of installing an\n * unpublished workspace-only package.\n *\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Skips cleanly when the workspace has no Python projects. Runs after\n * {@link PYTHON_INSTALL_GUARD} (the fixed dev toolchain), before `sync:check`.\n * Resolves `python` vs `python3` at run time the same way\n * {@link PYTHON_INSTALL_GUARD} does — see its remarks.\n */\nconst PYTHON_WORKSPACE_INSTALL_GUARD = 'node -e \"const fs=require(\\'node:fs\\'),path=require(\\'node:path\\');const editableDirs=[...fs.globSync(\\'apps/*/pyproject.toml\\'),...fs.globSync(\\'python-packages/*/pyproject.toml\\'),...fs.globSync(\\'libs/*/pyproject.toml\\')].map((p)=>path.dirname(p));const requirementsFiles=fs.globSync(\\'apps/*/requirements.txt\\');if(editableDirs.length===0&&requirementsFiles.length===0){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const args=[\\'-m\\',\\'pip\\',\\'install\\',\\'--quiet\\',...editableDirs.flatMap((d)=>[\\'-e\\',d]),...requirementsFiles.flatMap((f)=>[\\'-r\\',f])];const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';process.exit(require(\\'node:child_process\\').spawnSync(py,args,{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that runs `pip-audit` against the shared\n * Python environment, non-blocking (warn-only).\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Runs after {@link PYTHON_WORKSPACE_INSTALL_GUARD}, so the environment it\n * scans already has every project's real dependencies installed (not just\n * the fixed toolchain) — a bare `pip-audit` with no arguments audits\n * whatever is currently installed, which by this point in the pipeline is\n * the workspace's actual dependency set. Skips cleanly when the workspace\n * has no Python projects (same `requirements-dev.txt` check every other\n * Python guard here uses).\n *\n * **Deliberately non-blocking**: `pip-audit`'s own exit code is discarded\n * (`process.exit(0)` always) rather than failing the build. An upstream-only\n * advisory with no user-actionable fix (a transitive dependency of a pinned\n * tool, not patchable by editing this workspace's own manifest) would\n * otherwise turn every build red for a problem nobody here can fix.\n *\n * **This no longer matches its npm sibling, and the asymmetry is a limitation\n * rather than a preference.** {@link NPM_AUDIT_STEP} now fails on an advisory\n * that has a published fix and passes the rest, because `npm audit --json`\n * reports `fixAvailable` per advisory. `pip-audit`'s output carries no\n * equivalent field, so the actionable/unactionable line cannot be drawn here\n * without resolving each advisory's fixed version by hand. Until that is\n * built, report-only is the honest choice: the alternative is a gate that goes\n * red on findings nobody can act on. Do not \"align\" the two by making this one\n * blocking — that trades a weak gate for a false one.\n */\nconst PIP_AUDIT_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'requirements-dev.txt\\')){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';require(\\'node:child_process\\').spawnSync(py,[\\'-m\\',\\'pip_audit\\'],{stdio:\\'inherit\\'});process.exit(0)\"'\n\n/**\n * The portable `node -e` one-liner that downloads the workspace's Go module\n * dependencies.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml},\n * and gated on the workspace-root `go.mod` that `add/go.ts` writes on the\n * first `mnci add go-*` — so a workspace with no Go projects skips cleanly,\n * exactly like the Python guards skip on a missing `requirements-dev.txt`.\n *\n * One root `go.mod` is the whole story here: mnci generates Go projects into\n * a single module (see `add/go.ts`), so there is no per-project manifest to\n * walk and no `go.work` to keep in step — a plain `go mod download` at the\n * root fetches everything every Go project needs.\n *\n * Strictly speaking this step is optional, since `go build` and `go test`\n * both fetch on demand. It is here so a network failure surfaces as an\n * obvious \"download dependencies\" failure rather than as a confusing error\n * inside the build, matching what the Python install steps do.\n */\nconst GO_MODULE_DOWNLOAD_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'go\\',[\\'mod\\',\\'download\\'],{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The golangci-lint version every generated workspace installs.\n *\n * @remarks\n * Pinned, like {@link FLUTTER_SDK_VERSION}, for two reasons measured on a real\n * generated workspace (russoedu/MoNecromanCi#239):\n *\n * - **Reproducibility.** The guard used to `go install …@latest`, so a new\n * golangci-lint release could add or tighten linters and turn every\n * workspace's CI red overnight with no change on its side, while a developer's\n * local copy disagreed with CI.\n * - **Speed.** A pinned version has a published prebuilt binary to download,\n * instead of a compile. See {@link GOLANGCI_LINT_INSTALL_GUARD}.\n *\n * Bump it deliberately: check the release notes for new default linters, then\n * run the e2e's Go section. Exported so tests and this repo's own workflow can\n * assert it.\n */\nexport const GOLANGCI_LINT_VERSION = '2.14.0'\n\n/**\n * The portable `node -e` one-liner that installs `golangci-lint` when the\n * workspace has Go projects and the agent does not already provide it.\n *\n * @remarks\n * Needed because mnci's generated Go `lint` target pins\n * `linter: golangci-lint` — `@nx-go/nx-go`'s own default is `go fmt`, which\n * only reformats and would make a green lint step meaningless. Hosted agents\n * ship Go but not golangci-lint, so CI has to supply it.\n *\n * **Downloads the project's prebuilt release instead of compiling it.** The\n * previous `go install …@latest` built golangci-lint from source on every run:\n * 68 s of a ~2 min job on Lore Master's first CI run, more than install, lint,\n * test and build together. The prebuilt archive (≈14 MB) downloads and extracts\n * in about a second. In order:\n *\n * 1. Map `process.platform`/`process.arch` to the release asset name.\n * 2. Fetch the release's `checksums.txt` and the archive, and refuse the archive\n * unless its SHA-256 matches, so a tampered or truncated download is never\n * executed.\n * 3. Extract it with `tar -xf`. On Windows that is the absolute\n * `%SystemRoot%\\System32\\tar.exe` (bsdtar, shipped with Windows 10 and\n * later), which reads the `.zip` the Windows builds come as. Not the bare\n * `tar`: on an agent with Git installed the one that wins on `PATH` can be\n * GNU tar, which cannot read zip files. Not PowerShell either, which the\n * pipeline avoids by design.\n * 4. Copy the binary into `GOPATH/bin`, the directory the \"Add Go tool bin to\n * PATH\" step already publishes, so nothing downstream changes.\n *\n * Any failure (an unmapped platform, a network error, a checksum mismatch, a\n * failed extract) falls back to `go install` **at the same pinned version**, so\n * an outage of GitHub's release CDN costs speed, never the build. The reason is\n * printed first.\n *\n * Skips when `golangci-lint` is already resolvable, so a self-hosted agent that\n * pre-installs it pays nothing.\n *\n * Constraints every guard here shares: one line, single quotes inside, and no\n * colon-space, no space-hash, no backtick, `$` or `%`, so the same text is a\n * valid YAML plain scalar and survives bash, PowerShell and `cmd.exe` quoting.\n */\nconst GOLANGCI_LINT_INSTALL_GUARD = 'node -e \"' +\n \"const fs=require('node:fs'),cp=require('node:child_process'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');\" +\n \"if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}\" +\n \"if(cp.spawnSync('golangci-lint',['--version'],{stdio:'ignore'}).status===0){console.log('golangci-lint already installed - skipping.');process.exit(0)}\" +\n `const v='${GOLANGCI_LINT_VERSION}';` +\n \"const fallback=reason=>{console.log('Prebuilt golangci-lint '+v+' unavailable ('+reason+') - building it with go install instead.');process.exit(cp.spawnSync('go',['install','github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v'+v],{stdio:'inherit'}).status ?? 1)};\" +\n \"const plat={linux:'linux',darwin:'darwin',win32:'windows'}[process.platform],arch={x64:'amd64',arm64:'arm64'}[process.arch];\" +\n \"if(!plat||!arch)fallback('no prebuilt release for '+process.platform+'/'+process.arch);\" +\n \"const gopath=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(gopath.status!==0)fallback('go env GOPATH failed');\" +\n \"const name='golangci-lint-'+v+'-'+plat+'-'+arch,file=name+(plat==='windows'?'.zip':'.tar.gz'),base='https://github.com/golangci/golangci-lint/releases/download/v'+v+'/';\" +\n \"const get=async u=>{const r=await fetch(u,{signal:AbortSignal.timeout(60000)});if(!r.ok)throw new Error('HTTP '+r.status+' for '+u);return Buffer.from(await r.arrayBuffer())};\" +\n '(async()=>{' +\n String.raw`const sums=(await get(base+'golangci-lint-'+v+'-checksums.txt')).toString('utf8').split('\\n').map(l=>l.trim().split(' ').filter(Boolean));` +\n \"const entry=sums.find(p=>p[1]===file);if(!entry)throw new Error(file+' is not in the release checksums');\" +\n 'const archive=await get(base+file);' +\n \"if(crypto.createHash('sha256').update(archive).digest('hex')!==entry[0])throw new Error('checksum mismatch for '+file);\" +\n \"const tmp=fs.mkdtempSync(path.join(os.tmpdir(),'golangci-lint-')),archivePath=path.join(tmp,file);fs.writeFileSync(archivePath,archive);\" +\n \"const tar=plat==='windows'?path.join(process.env.SystemRoot||'','System32','tar.exe'):'tar';\" +\n \"const x=cp.spawnSync(tar,['-xf',archivePath,'-C',tmp],{stdio:'inherit'});\" +\n \"if(x.status!==0)throw new Error('could not extract '+file);\" +\n \"const exe='golangci-lint'+(plat==='windows'?'.exe':''),bin=path.join(gopath.stdout.trim(),'bin');fs.mkdirSync(bin,{recursive:true});\" +\n 'fs.copyFileSync(path.join(tmp,name,exe),path.join(bin,exe));fs.chmodSync(path.join(bin,exe),0o755);fs.rmSync(tmp,{recursive:true,force:true});' +\n \"console.log('golangci-lint '+v+' installed from its checksum-verified prebuilt release into '+bin)\" +\n '})().catch(error=>fallback(error.message))\"'\n\n/**\n * The shared prelude that resolves `GOPATH/bin` — where\n * {@link GOLANGCI_LINT_INSTALL_GUARD} puts the linter (whether downloaded or built).\n *\n * @remarks\n * Not a step on its own: {@link GO_TOOL_PATH_AZURE} and\n * {@link GO_TOOL_PATH_GITHUB} each append the one line that publishes the\n * directory to the rest of the job, because the two providers use entirely\n * different mechanisms for that (a logging command vs a file). Everything up\n * to that point — the skip-when-no-Go check and reading `go env GOPATH` —\n * is identical, so it lives here once.\n */\nconst GO_TOOL_PATH_PRELUDE = 'const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(!fs.existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}const r=cp.spawnSync(\\'go\\',[\\'env\\',\\'GOPATH\\'],{encoding:\\'utf8\\'});if(r.status!==0){console.log(\\'Could not resolve GOPATH - skipping.\\');process.exit(0)}const bin=require(\\'node:path\\').join(r.stdout.trim(),\\'bin\\');'\n\n/**\n * Azure Pipelines: publishes `GOPATH/bin` to later steps in the job.\n *\n * @remarks\n * Uses the `task.prependpath` logging command — Azure's supported way for a\n * step to alter `PATH` for the steps that follow it. Skips cleanly when the\n * workspace has no Go projects, so this is inert in a JS-only repo.\n */\nconst GO_TOOL_PATH_AZURE = `node -e \"${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)\"`\n\n/**\n * GitHub Actions: publishes `GOPATH/bin` to later steps in the job.\n *\n * @remarks\n * Appends to the file named by `GITHUB_PATH`, the documented equivalent of\n * Azure's `prependpath` logging command. Skips cleanly both when the\n * workspace has no Go projects and when `GITHUB_PATH` is unset (i.e. when\n * the same script is run outside Actions).\n */\nconst GO_TOOL_PATH_GITHUB = `node -e \"${GO_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\\n'`})\"`\n\n/**\n * The Flutter SDK version the generated pipeline installs.\n *\n * @remarks\n * Pinned, like {@link GOLANGCI_LINT_VERSION}, because the Flutter version\n * *determines the Dart version*, and Dart is what has the hard floor here:\n * pub workspaces — the whole basis of mnci's central-dependency model for\n * Dart — need Dart 3.6+. A floating `stable` could in principle move the\n * toolchain under a workspace without warning, so the version is explicit and\n * bumped deliberately.\n *\n * `3.44.8` ships Dart 3.12.2. Exported so tests can assert it and so bumping\n * it is a one-line change.\n */\nexport const FLUTTER_SDK_VERSION = '3.44.8'\n\n/**\n * The Node major a generated workspace is built and tested against.\n *\n * @remarks\n * Read by both the GitHub workflow's `setup-node` step and the devcontainer's\n * base image, so the environment a contributor develops in cannot silently\n * diverge from the one CI verifies. That drift is the whole reason\n * {@link devcontainerJson} exists, so it must not be reintroduced by hardcoding\n * the number twice.\n *\n * Azure deliberately does not pin it: its pipeline uses whatever Node the agent\n * image ships, which is the existing behaviour and outside this change's scope.\n */\nexport const NODE_VERSION = '24'\n\n/**\n * The major of each GitHub Action the generated workflow pins.\n *\n * @remarks\n * Pinned here, in one place, because Dependabot cannot see them anywhere else.\n * It keeps THIS repo's `.github/workflows/ci.yml` current — that file is real\n * YAML it can parse — but these are string literals inside a TypeScript\n * generator, invisible to it. So every bump it lands here has to be carried\n * across by hand, and three of the four had not been: this repo ran\n * `checkout@v7`, `setup-node@v7` and `upload-artifact@v7` while every workspace\n * mnci generated still got `@v4`. Only `setup-dotnet` matched, because someone\n * noticed once and ported it (PR #189).\n *\n * The v7 majors are not a guess: they are exactly what this repo's own CI runs\n * green today, on a pipeline whose `run:` steps are asserted to be a superset of\n * the generated one's. `pipeline-drift.integration.spec.ts` now fails when this\n * map falls behind that workflow, so the carry-across is enforced rather than\n * remembered.\n */\nexport const ACTION_VERSIONS = {\n 'actions/checkout': 'v7',\n 'actions/setup-node': 'v7',\n 'actions/setup-dotnet': 'v6',\n 'actions/upload-artifact': 'v7',\n 'azure/login': 'v3',\n} as const\n\n/**\n * The npm major a generated workspace is built and tested against.\n *\n * @remarks\n * Pinned because npm's own behaviour is part of the contract, not an\n * implementation detail beneath it. npm 11 changed when `overrides` are\n * applied: given a tree that is already installed, it reuses it rather than\n * re-resolving, so an `overrides` entry added after the first install does\n * nothing. Measured on nx 23.1.1 with the same manifest and the same sequence:\n * npm 10.9.7 reported **0** advisories and npm 11.19.0 reported **6**.\n *\n * That divergence is what made the bug expensive. `setup-node` installs\n * whatever npm the Node image happens to bundle, so CI and a contributor's\n * machine could — and did — resolve differently, which meant a local check\n * could pass while the thing it was checking was broken for every user.\n *\n * **The MAJOR is pinned, not an exact version.** The risk this guards is a\n * major behavioural change, which is what actually happened; an exact pin would\n * add a version nothing bumps, and a pin that reads as current while being\n * stale is a failure mode this repo has already paid for once (the\n * `@verdaccio/config` → `js-yaml` entry that read as fixed and was not).\n */\nexport const NPM_VERSION = '11'\n\n/**\n * The .NET SDK version a generated workspace's C# projects are built and\n * tested against.\n *\n * @remarks\n * `@nx/dotnet` (the official Nx plugin mnci delegates to for C#) requires SDK\n * 8.0+; `10.0.x` is pinned rather than 8.0 because .NET's even-numbered\n * majors are the LTS line (8.0 released Nov 2023, 10.0 Nov 2025) and 10.0 is\n * the current one — the same reasoning `NODE_VERSION` already applies to\n * Node. **Verify the current LTS against the real .NET release notes before\n * this ships**, the way `FLUTTER_SDK_VERSION` was pinned against a real\n * `flutter --version` rather than assumed.\n *\n * The `.x` keeps the range open to patch releases the way `actions/setup-dotnet`\n * and `UseDotNet@2` both expect it (`dotnet-version: '10.0.x'` /\n * `version: '10.0.x'`) — unlike {@link NODE_VERSION}, which is a bare major\n * because `setup-node` resolves majors on its own; .NET's own tooling wants\n * the `.x` suffix explicit.\n *\n * Exported so tests can assert it and both CI providers' install steps and\n * the devcontainer's `dotnet` feature read the same value — the exact drift\n * {@link NODE_VERSION} already exists to prevent for Node.\n */\nexport const DOTNET_SDK_VERSION = '10.0.x'\n\n/**\n * Detects whether the workspace has any C# project — `apps/*\\/*.csproj`,\n * `packages/*\\/*.csproj`, `libs/*\\/*.csproj`, the same three roots\n * {@link PACK_APPS_GUARD} and `add/csharp.ts` already scan — and publishes\n * the result as an Azure Pipelines variable\n * (`##vso[task.setvariable variable=hasDotnetProjects]`).\n *\n * @remarks\n * Unlike Python, Go and Flutter's provisioning, .NET SDK install is not a\n * portable `node -e` one-liner: `UseDotNet@2` / `actions/setup-dotnet` are\n * each provider's own maintained, cached installer, and hand-rolling a\n * cross-platform equivalent of either would be strictly worse than using\n * them — the same trade already made for Node/npm setup itself, which is\n * fully provider-specific (`actions/setup-node` + `cache: npm` vs. Azure's\n * pre-installed image Node plus a separate `Cache@2` task). So this constant\n * exists only to give the Azure *task* something to gate on: unlike a\n * `script:` step, `UseDotNet@2` cannot self-gate by exiting early, and\n * Azure's `condition:` expression language has no glob-matching function —\n * where GitHub Actions' `hashFiles()` covers the same case inline (see\n * {@link githubActionsYaml}), Azure needs this detection step to run first\n * and hand the answer to `condition:` through a variable.\n *\n * Both the pattern list and the `##vso` syntax are copied from\n * {@link PACK_APPS_GUARD}'s sibling `build.addbuildtag` usage further down\n * this file — the same \"publish a fact for a later step\" mechanism, just\n * read by `condition:` instead of by a human.\n */\nconst DOTNET_DETECT_AZURE = 'node -e \"const fs=require(\\'node:fs\\');const has=[...fs.globSync(\\'apps/*/*.csproj\\'),...fs.globSync(\\'packages/*/*.csproj\\'),...fs.globSync(\\'libs/*/*.csproj\\')].length>0;console.log(\\'##vso[task.setvariable variable=hasDotnetProjects]\\'+has)\"'\n\n/**\n * The shared expression that resolves where the Flutter SDK is installed.\n *\n * @remarks\n * Deliberately **outside** the workspace, under the agent's home directory —\n * not in a workspace-local `.flutter-sdk`. Two reasons, and the second is the\n * serious one:\n *\n * 1. Nothing needs adding to `.gitignore` for it. The overlay still owns no\n * `.gitignore` of its own — `create-nx-workspace` does — but it does now\n * append the one entry a decision made here needs (see\n * {@link ensureEslintCacheIgnored}); this SDK path was chosen specifically\n * so it would never need a second one.\n * 2. The Flutter SDK ships **its own `pubspec.yaml` files** — dozens of them,\n * across `packages/flutter`, `packages/flutter_test` and the rest. Cloning\n * it inside the workspace would drop those into the pub workspace's own\n * tree, where `pub` treats a stray nested pubspec as an error to resolve\n * around, and would give Nx thousands of extra files to glob for its\n * project graph.\n *\n * Keyed by version, so bumping {@link FLUTTER_SDK_VERSION} provisions a fresh\n * clone instead of leaving a stale checkout on a cached agent. Computed\n * identically by the install guard and the PATH guards so they always agree.\n */\nconst FLUTTER_SDK_DIRECTORY_EXPRESSION = `require('node:path').join(require('node:os').homedir(),'.mnci-flutter-${FLUTTER_SDK_VERSION}')`\n\n/**\n * The portable `node -e` one-liner that installs the Flutter SDK.\n *\n * @remarks\n * Flutter is the one toolchain the pipeline genuinely has to install: Python\n * and Go both ship on every hosted agent image, Flutter does not. So unlike\n * the Python and Go guards — which only fetch *dependencies* — this one\n * fetches the SDK itself.\n *\n * Installed by shallow `git clone` at a pinned tag, which is Flutter's own\n * documented install method and the only one that is genuinely uniform across\n * agents: the release archives differ by platform (`.tar.xz` on Linux,\n * `.zip` on macOS/Windows), which would force a platform switch and an\n * extractor into what is meant to be one portable line. `git` is already a\n * hard requirement of this pipeline, so nothing new is assumed.\n * `--depth 1` keeps it to a single revision.\n *\n * Two levels of skip, mirroring {@link GOLANGCI_LINT_INSTALL_GUARD}: nothing\n * happens without a workspace-root `pubspec.yaml` (so a JS-only repo pays\n * nothing), and nothing happens when `flutter` already resolves — so a\n * self-hosted agent with a preinstalled SDK, or a second run on a warm\n * workspace, skips the download entirely.\n */\nconst FLUTTER_SDK_INSTALL_GUARD = `node -e \"const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}if(cp.spawnSync('flutter',['--version'],{stdio:'ignore',shell:process.platform==='win32'}).status===0){console.log('Flutter SDK already on PATH - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(fs.existsSync(sdk)){console.log('Flutter SDK already installed at '+sdk+' - skipping.');process.exit(0)}process.exit(cp.spawnSync('git',['clone','--depth','1','--branch','${FLUTTER_SDK_VERSION}','https://github.com/flutter/flutter.git',sdk],{stdio:'inherit'}).status ?? 1)\"`\n\n/**\n * The shared prelude that resolves the Flutter SDK's `bin` directory.\n *\n * @remarks\n * Not a step on its own — {@link FLUTTER_TOOL_PATH_AZURE} and\n * {@link FLUTTER_TOOL_PATH_GITHUB} each append the one line that publishes the\n * directory, exactly as the Go pair does. Skips when there are no Flutter\n * projects, and also when the SDK was never cloned because one was already on\n * `PATH` (in which case there is nothing to publish).\n */\nconst FLUTTER_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),path=require('node:path');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(!fs.existsSync(sdk)){console.log('Flutter SDK was not installed by mnci (already on PATH) - skipping.');process.exit(0)}const bin=path.join(sdk,'bin');`\n\n/**\n * Azure Pipelines: publishes the Flutter SDK's `bin` to later steps.\n *\n * @remarks\n * Uses the `task.prependpath` logging command, the same mechanism\n * {@link GO_TOOL_PATH_AZURE} uses.\n */\nconst FLUTTER_TOOL_PATH_AZURE = `node -e \"${FLUTTER_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)\"`\n\n/**\n * GitHub Actions: publishes the Flutter SDK's `bin` to later steps.\n *\n * @remarks\n * Appends to the file named by `GITHUB_PATH`, mirroring\n * {@link GO_TOOL_PATH_GITHUB}, and skips cleanly when run outside Actions.\n */\nconst FLUTTER_TOOL_PATH_GITHUB = `node -e \"${FLUTTER_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\\n'`})\"`\n\n/**\n * The portable `node -e` one-liner that resolves every Dart dependency.\n *\n * @remarks\n * **This is the dependency-injection step for Flutter**, and it is a single\n * command for the whole workspace by design. Because every project is a\n * member of the root pub workspace (`workspace:` in the root `pubspec.yaml`,\n * `resolution: workspace` in each member), one `flutter pub get` at the root\n * resolves **internal and external dependencies together** into one\n * `pubspec.lock` and one `.dart_tool/package_config.json` — pub even deletes\n * any stale per-package copies.\n *\n * That is why there is no second, workspace-wide step here of the kind Python\n * needs ({@link PYTHON_WORKSPACE_INSTALL_GUARD} exists only because pip has no\n * workspace protocol and every project must be editable-installed by hand).\n * Dart has a real workspace protocol, so this one line is the whole story —\n * and a project importing an internal lib resolves it with a plain version\n * constraint, no `path:` and no vendoring.\n *\n * Runs with `shell: true` on Windows only, where `flutter` is a `.bat` shim\n * that `spawnSync` cannot execute directly.\n */\nconst FLUTTER_PUB_GET_GUARD = 'node -e \"const fs=require(\\'node:fs\\');if(!fs.existsSync(\\'pubspec.yaml\\')){console.log(\\'No Flutter projects - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'flutter\\',[\\'pub\\',\\'get\\'],{stdio:\\'inherit\\',shell:process.platform===\\'win32\\'}).status ?? 1)\"'\n\n/**\n * The `npm audit` step: blocks on an **actionable** advisory, reports the rest.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n *\n * **This replaced a warn-only step, and the reason is that the step's own\n * justification had stopped being true.** It used to be\n * `npm audit --audit-level=high || echo ...`, documented as non-blocking\n * because \"every flagged vulnerability traced back to `nx`'s and `verdaccio`'s\n * own bundled transitive dependencies ... nothing an edit to *this* workspace's\n * manifest could fix\". That was measured, and it has since inverted: a real\n * audit of this monorepo reported **9 advisories, 9 of them with\n * `fixAvailable`**, and every one was fixed by a targeted `overrides` entry —\n * exactly the edit the old note said was impossible. Same shape as the stale\n * `js-yaml` pin that audit run uncovered: a decision resting on a measurement\n * nothing re-checks.\n *\n * So the split is **actionable vs not**, which is a property `npm audit --json`\n * reports per advisory (`fixAvailable`) rather than a severity guess:\n *\n * - A published, **applyable** fix exists, at `moderate` or above → **exit 1**.\n * The remedy is a reviewed `overrides` entry, and leaving it to a human who\n * never sees a red build is how a fix sits unapplied for weeks.\n * - No fix exists upstream → printed, exit 0. This preserves the whole of the\n * original concern: going red for something nobody in this workspace can fix\n * is a gate that only teaches people to ignore it.\n * - **A fix npm marks `isSemVerMajor` → printed, exit 0.** `npm audit` sets\n * `fixAvailable` to a `{name,version,isSemVerMajor:true}` object even when the\n * only \"fix\" is a semver-**major** change to a *parent* that drops the\n * dependency path — frequently a downgrade, not a patch to the vulnerable\n * package at all (measured: `braces`/`http-cache-semantics` advisories whose\n * leaf was already at the newest published version, \"fixed\" by downgrading\n * `verdaccio` and `@swc/cli`). Blocking on that taught the gate to fire on\n * advisories nobody can actually clear with an override. A semver-major upgrade\n * is a deliberate, reviewed bump, never an automatic CI gate; it is reported so\n * it is not forgotten, but it does not go red.\n *\n * Three deliberate choices worth not undoing:\n *\n * - **Dev dependencies are included.** Measured: `npm audit --omit=dev` on the\n * tree that had those 9 advisories reports **0**. Every one arrived through a\n * devDependency (verdaccio, ts-jest's istanbul chain, eslint-plugin-tsdoc,\n * commitlint, Vite). Omitting them would have reported nothing and verified\n * nothing.\n * - **The threshold is `moderate`, not `high`.** The old `--audit-level=high`\n * would have missed the `postcss` advisory outright, which was moderate and\n * had a fix.\n * - **A broken audit does not fail the build.** No parseable JSON (registry\n * outage, npm change) exits 0 with the reason printed. A gate that cannot read\n * its input should say so, not guess.\n *\n * Verified by execution against two real dependency trees, not by reading it:\n * this monorepo's fixed tree exits 0, and the pre-fix tree exits 1 listing all\n * nine. The unit tests drive the remaining branches with a stub `npm` on PATH.\n */\nconst NPM_AUDIT_STEP = 'node -e \"const cp=require(\\'node:child_process\\');const r=cp.spawnSync(\\'npm\\',[\\'audit\\',\\'--json\\'],{encoding:\\'utf8\\',shell:process.platform===\\'win32\\',maxBuffer:33554432});let d;try{d=JSON.parse(r.stdout)}catch{console.log(\\'npm audit produced no JSON (exit \\'+r.status+\\') - not blocking on a broken audit.\\');process.exit(0)}const all=Object.values(d.vulnerabilities||{});const BLOCK=[\\'critical\\',\\'high\\',\\'moderate\\'];const major=v=>v.fixAvailable&&typeof v.fixAvailable===\\'object\\'&&v.fixAvailable.isSemVerMajor;const act=all.filter(v=>v.fixAvailable&&BLOCK.includes(v.severity)&&!major(v));const rest=all.filter(v=>!act.includes(v));for(const v of rest)console.log(\\' note [\\'+v.severity+\\'] \\'+v.name+(!v.fixAvailable?\\' - NO fix available upstream, nothing to do here\\':major(v)?\\' - only a semver-major change would remove it (often a parent downgrade), not applied automatically\\':\\' - fix available, below the blocking threshold\\'));if(act.length===0){console.log(\\'npm audit - \\'+all.length+\\' advisory(ies), none actionable at moderate or above.\\');process.exit(0)}for(const v of act)console.log(\\' BLOCKING [\\'+v.severity+\\'] \\'+v.name+\\' - fix available\\');console.log(\\'Each has a published fix. Add a targeted overrides entry in package.json rather than npm audit fix --force.\\');process.exit(1)\"'\n\n/**\n * The Nx targets every CI run verifies.\n *\n * @remarks\n * `typecheck` is not covered by `build`: a bundler-built project (esbuild, swc)\n * strips types without reading them, so a workspace can be green on\n * lint+test+build while carrying real type errors.\n */\nconst VERIFY_TARGETS = 'lint,typecheck,test,build'\n\n/**\n * The portable `node -e` one-liner that verifies the workspace: only the\n * **affected** projects on a pull request, **every** project otherwise.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml},\n * which matters more here than for the other guards: the two providers detect a\n * pull request through different environment variables, and a mechanism that\n * drifted would change *what CI verifies* rather than merely how it is spelled.\n *\n * **Every failure path falls back to `run-many`, never to nothing.** That is the\n * whole safety design, and it is deliberate rather than defensive habit. Getting\n * the base *too wide* costs a few minutes; getting it *too narrow* means CI runs\n * almost nothing, passes green, and has verified nothing — a silently weakened\n * gate, which is far worse than a slow one. So a missing target ref, an\n * unresolvable merge-base (a shallow clone, a missing remote branch) and a\n * non-PR run all take the full path.\n *\n * `main` needs no special case for the same reason: neither provider sets a\n * pull-request target branch on a push, so a release run always verifies\n * everything before publishing. That falls out of the fallback rather than being\n * a second condition to keep in step.\n *\n * The base is a real `git merge-base`, not the provider's \"base SHA\" field. Both\n * providers expose something base-ish, but those drift once the target branch\n * moves ahead of where the PR started, and a merge-base is correct in both by\n * construction — one mechanism instead of two.\n *\n * It resolves the merge-base against `origin/<target>` first and, only if that\n * ref is absent, **fetches the target branch once** and retries against\n * `FETCH_HEAD`. Both providers are configured for a full-depth clone, so\n * `origin/<target>` is normally there — but if it ever is not, the bare fallback\n * would make every run take the full path while still reporting success, so this\n * step would look like it worked and verify nothing selectively, forever. One\n * fetch is a cheap way to not depend on that.\n *\n * Uses a plain string `replace` rather than a regex for the `refs/heads/` prefix\n * (Azure sends the full ref, GitHub the bare branch name): this whole command\n * has to survive quoting under both `cmd.exe` and POSIX `sh`, and a regex\n * literal would drag backslashes into that.\n */\nconst AFFECTED_OR_ALL_GUARD = `node -e \"const cp=require('node:child_process');const T='${VERIFY_TARGETS}';const all=()=>process.exit(cp.spawnSync('npx nx run-many -t '+T,{stdio:'inherit',shell:true}).status ?? 1);const ref=process.env.GITHUB_BASE_REF||process.env.SYSTEM_PULLREQUEST_TARGETBRANCH||'';if(!ref){console.log('Not a pull request - verifying EVERY project.');all()}const target=ref.replace('refs/heads/','');const mergeBase=r=>{const o=cp.spawnSync('git',['merge-base',r,'HEAD'],{encoding:'utf8'});return o.status===0?o.stdout.trim():''};let base=mergeBase('origin/'+target);if(!base){console.log('No origin/'+target+' ref - fetching it to resolve a merge-base.');cp.spawnSync('git',['fetch','--no-tags','origin',target],{stdio:'inherit'});base=mergeBase('FETCH_HEAD')}if(!base){console.log('Could not resolve a merge-base with '+target+' - verifying EVERY project.');all()}console.log('Pull request against '+target+' - verifying projects affected since '+base);process.exit(cp.spawnSync('npx nx affected -t '+T+' --base='+base,{stdio:'inherit',shell:true}).status ?? 1)\"`\n\n/**\n * The portable `node -e` one-liner that packs every app into\n * `dist/drop/<type>-<name>.zip` via each app's own `package` target.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Skips cleanly when the workspace has no apps yet.\n *\n * **Detects a project the way Nx itself does — `apps/*\\/project.json`, an\n * `apps/*\\/package.json` carrying an `nx` key, OR an `apps/*\\/*.csproj` — not\n * `project.json` alone.** `@nx/node:application` and `@nx/react:application`\n * write no `project.json` at all: their targets are inferred, and\n * `add/*.ts`'s `addNxTargets` layers the `package` target on through the\n * manifest's own `nx.targets` field (see its doc comment) specifically to\n * avoid the project-name clash a second `project.json` would risk in a\n * TS-solution workspace. `@nx/dotnet` (C#) is inference-only for the same\n * reason but has no manifest at all to carry an `nx` key on — verified\n * against the real published package, whose own `createNodes` glob is keyed\n * on `.csproj` directly — so `csharp.ts` creates a minimal `project.json`\n * itself purely to carry mnci's own `package` target, which the plain\n * `hasProjectJson` check already covers once that file exists. The\n * `*.csproj` branch below exists for the case a user later drops that\n * `project.json`, or for a `.csproj` this CLI never generated at all: a\n * `project.json`-only check would go back to never seeing that project. A\n * `project.json`-only check historically never saw a `node-app` or\n * `react-app` project at all either: the step silently logs \"No apps to pack\n * - skipping\", `dist/drop` stays empty, and every step downstream\n * (`PublishBuildArtifacts`, the per-app build tag) has nothing to work with —\n * a green run with no artifact. Go, Python and Flutter apps all get a real\n * generator-written `project.json`, so they were never affected.\n */\nconst PACK_APPS_GUARD = 'node -e \"const fs=require(\\'node:fs\\');fs.mkdirSync(\\'dist/drop\\',{recursive:true});const hasProjectJson=fs.globSync(\\'apps/*/project.json\\').length>0;const hasInlineNx=fs.globSync(\\'apps/*/package.json\\').some((f)=>{try{return Boolean(JSON.parse(fs.readFileSync(f,\\'utf8\\')).nx)}catch{return false}});const hasCsproj=fs.globSync(\\'apps/*/*.csproj\\').length>0;if(!hasProjectJson&&!hasInlineNx&&!hasCsproj){console.log(\\'No apps to pack - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'npx nx run-many -t package\\',{stdio:\\'inherit\\',shell:true}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that fails the run when the checkout is a\n * shallow clone, before `nx release` ever gets to run.\n *\n * @remarks\n * `release.version.fallbackCurrentVersionResolver: 'disk'` ({@link releaseConfig})\n * exists so a brand-new, never-tagged package's first release does not hard-error\n * the whole release graph — nx's own recommended mechanism for that case\n * (`--first-release`) is a one-shot CLI flag, not something a fixed, non-interactive\n * CI command can apply selectively to only the projects that actually need it, so\n * the config-level fallback is the only option that keeps `mnci add npm-lib` (or\n * `python-lib`, `flutter-lib`, `csharp-lib`) followed by an ordinary release working\n * out of the box.\n *\n * That fallback is dangerous for every OTHER project, the ones already published:\n * with `git.commit: false`, a manifest's on-disk version is permanently whatever the\n * generator scaffolded (`0.0.1`/`1.0.0`), so if nx cannot resolve a project's tag for\n * ANY reason it silently falls back to that stale disk value and proposes a version\n * relative to it — which reads as a normal release and can propose (and, if nothing\n * stops it, publish) a downgrade. Reproduced against a real multi-package workspace:\n * a dry run with the release tags unreachable from the current branch proposed\n * `0.2.0` against a published `0.7.0`.\n *\n * The generated CI is safe today ONLY because both providers unconditionally fetch\n * full history and tags before releasing, and release only ever runs from `main` —\n * but nothing in `nx.json` expresses that this is load-bearing, so a well-meaning\n * future edit to the checkout step (shallower fetch, a cache shortcut) would\n * silently reintroduce the downgrade path with no error, anywhere. This guard turns\n * that implicit dependency into an explicit, checked precondition: `nx release`\n * NEVER runs against a shallow checkout, full stop — a hard, loud, immediate failure\n * naming the fix, instead of a quiet wrong version discovered after the fact.\n *\n * @returns The full `node -e` shallow-clone guard one-liner.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\n/**\n * Proves npm will accept this workspace's token BEFORE `nx release` does\n * anything irreversible.\n *\n * @remarks\n * `nx release` versions, tags, pushes the tag, and only THEN publishes. So a\n * missing or rejected token does not simply fail the run - it leaves a version\n * tagged with nothing on the registry, and because the next release resolves\n * the current version from the newest tag, that version number is skipped\n * FOREVER. A real workspace lost `0.0.2` exactly that way, which is where this\n * guard comes from.\n *\n * `npm whoami` is the cheapest thing that exercises the whole auth path at\n * once: the secret reaching the step, `.npmrc`'s `${NODE_AUTH_TOKEN}`\n * expanding, and the registry accepting the token. Checking the variable alone\n * would pass for an expired or under-scoped token, which fails at exactly the\n * same late moment.\n *\n * The empty-token branch is separate from the rejected-token branch on\n * purpose: an unset secret renders as blank in the step's env log rather than\n * as `***`, so naming that case explicitly turns the most common setup mistake\n * into one sentence instead of a registry error to interpret.\n *\n * PUBLIC NPM ONLY. An Azure Artifacts feed answers reads anonymously, so\n * `whoami` there proves nothing about whether a WRITE would be accepted - and\n * its auth story is Basic-with-a-PAT, which this repo has already been wrong\n * about once. A guard that passes without testing anything is worse than no\n * guard, so the Azure case deliberately gets none.\n */\nconst NPM_AUTH_PREFLIGHT = 'node -e \"const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(fs.globSync(\\'packages/*/package.json\\').length===0){console.log(\\'No npm packages to release - skipping.\\');process.exit(0)}if(!process.env.NODE_AUTH_TOKEN){console.error(\\'NPM_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly NPM_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret. Use an npm Automation token - a Publish token is refused by 2FA in CI.\\');process.exit(1)}const r=cp.spawnSync(\\'npm\\',[\\'whoami\\',\\'--registry=https://registry.npmjs.org/\\'],{encoding:\\'utf8\\',shell:process.platform===\\'win32\\'});if(r.status!==0){console.error(\\'NPM_TOKEN is set but the registry rejected it - \\'+((r.stderr||\\'\\')+(r.stdout||\\'\\')).trim()+\\'. Check that it has not expired and that it grants publish rights on this scope.\\');process.exit(1)}console.log(\\'npm auth OK as \\'+r.stdout.trim())\"'\n\n/**\n * The preflight step body for a registry kind, or `''` where none applies.\n *\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` command, or `''` for a non-npm registry.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmAuthPreflight (registryKind: RegistryConfig['kind']): string {\n return registryKind === 'npm' ? NPM_AUTH_PREFLIGHT : ''\n}\n\n/**\n * Names the PyPI projects a release would have to CREATE, before it tags.\n *\n * @remarks\n * **There is deliberately no token check here**, and that is the whole reason\n * this guard looks nothing like {@link NPM_AUTH_PREFLIGHT}. PyPI has no\n * `whoami`: the only endpoint that authenticates is the upload itself, and a\n * bare `POST` to it answers `405` whether the credentials are good, bad or\n * absent - measured against the live endpoint. Anything calling itself a PyPI\n * auth preflight would therefore pass without testing anything, which is the\n * same reason the Azure case already gets no npm preflight. What CAN be\n * checked without publishing is the token's SHAPE: every PyPI API token\n * begins with `pypi-`, so a password or a truncated paste is caught here\n * rather than at upload time, after nx release has tagged.\n *\n * What it does test is the failure that actually happens. PyPI rate limits\n * NEW PROJECT creation per account, and `nx release` tags first and publishes\n * last, so a `429` lands after the tags are pushed: those versions are then\n * tagged with nothing published, and skipped for ever, because the next run\n * resolves the current version from the newest tag. The public JSON API says\n * which projects do not exist yet, with no credentials at all, so the step\n * names them up front and says what to do when it happens.\n *\n * It never fails on that count - a first publish has to create the project,\n * and blocking it would block every new package for ever - nor when PyPI\n * cannot be reached, since a release must not hinge on this step's own\n * network.\n *\n * Written as a `String.raw` template rather than the single-quoted style its\n * npm counterpart uses. The body is one long `node -e` script full of single\n * quotes, and it splits on a newline escape - hand-escaping both kinds is how\n * a guard acquires a silent syntax error that only a real release would\n * reveal. A plain template literal would turn that newline escape into a real\n * newline in the middle of a string literal, which is a parse error in the\n * script node then runs. `String.raw` passes both through untouched, so what\n * is written here is exactly what runs.\n *\n * NO COLON-SPACE ANYWHERE IN THE MESSAGES. The command is emitted as an\n * unquoted YAML plain scalar after `run:`/`script:`, so a `: ` inside it reads\n * as a nested mapping and the whole pipeline file stops parsing. Every message\n * here uses ` - ` where prose wants a colon; `https://` is fine, since only\n * colon-SPACE ends a scalar. `pipeline-drift.integration.spec.ts` parses both\n * generated files, so this is caught, but it is caught as an unhelpful\n * `bad indentation of a mapping entry`.\n *\n * PUBLIC PYPI ONLY, mirroring {@link npmAuthPreflight}. An Azure Artifacts\n * feed takes a PAT rather than a `pypi-` token and answers reads\n * anonymously, so neither half would mean anything there.\n */\nconst PYPI_RELEASE_PREFLIGHT = String.raw`node -e \"const fs=require('node:fs');const files=fs.globSync('python-packages/*/pyproject.toml');if(files.length===0){console.log('No Python packages to release - skipping.');process.exit(0)}if(!process.env.PYPI_TOKEN){console.error('PYPI_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly PYPI_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret.');process.exit(1)}if(!process.env.PYPI_TOKEN.startsWith('pypi-')){console.error('PYPI_TOKEN does not look like a PyPI API token - every one of them begins with pypi-. A password or a truncated paste is rejected only at upload time, which is AFTER nx release has tagged.');process.exit(1)}const names=files.map(f=>{const t=fs.readFileSync(f,'utf8');const b=t.slice(t.indexOf('[project]'));const l=b.split('\\n').find(x=>x.trim().startsWith('name'));return l?l.split('=')[1].replace(/[^a-zA-Z0-9._-]/g,''):''}).filter(Boolean);(async()=>{const fresh=[];for(const n of names){try{const r=await fetch('https://pypi.org/pypi/'+n.toLowerCase().replace(/[-_.]+/g,'-')+'/json',{signal:AbortSignal.timeout(10000)});if(r.status===404)fresh.push(n)}catch{console.log('Could not reach PyPI to check '+n+' - continuing.')}}if(fresh.length===0){console.log('Every Python package already exists on PyPI - this release creates none.');return}console.log('NOTE - this release may CREATE '+fresh.length+' new PyPI project(s) - '+fresh.join(', ')+'. Project creation is rate limited per account, and a 429 there arrives AFTER nx release has tagged - leaving those versions tagged with nothing published, and skipped forever, because the next run resolves the current version from the newest tag. If that happens, delete the tags for the versions that did not publish before releasing again. PyPI offers no way to check the limit, or the token, in advance.')})()\"`\n\n/**\n * The PyPI preflight step body for a registry kind, or `''` where none applies.\n *\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` command, or `''` for a non-public registry.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pypiReleasePreflight (registryKind: RegistryConfig['kind']): string {\n return registryKind === 'npm' ? PYPI_RELEASE_PREFLIGHT : ''\n}\n\nconst SHALLOW_CLONE_GUARD = 'node -e \"const r=require(\\'node:child_process\\').spawnSync(\\'git\\',[\\'rev-parse\\',\\'--is-shallow-repository\\'],{encoding:\\'utf8\\'});if(r.status!==0){console.error(\\'Could not determine whether this checkout has full history (git rev-parse --is-shallow-repository failed) - refusing to release. \\'+(r.stderr||\\'\\').trim());process.exit(1)}if(r.stdout.trim()===\\'true\\'){console.error(\\'This checkout is a shallow clone. nx release resolves the current version of each package from its git tag, and silently falls back to the permanently-stale on-disk version when a tag cannot be found - which can propose, and publish, a version DOWNGRADE. Fetch full history before releasing - set fetchDepth (Azure) or fetch-depth (GitHub) to 0.\\');process.exit(1)}\"'\n\n/**\n * Builds the portable `node -e` one-liner that versions, tags and publishes\n * every releasable project — `packages/*` (npm, C#, Dart) and\n * `python-packages/*` (Python) — via `nx release`.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}\n * — `pythonPublishEnv` is the only provider-specific fragment (both providers\n * decode the same base64 `PAT` env var, so the fragment itself is identical\n * too; only the caller decides whether to inject it). Skips cleanly when\n * there is nothing to release (`nx release` hard-errors on an empty scope).\n *\n * **Every manifest shape `release.projects` can match must be counted here**,\n * and a Dart one is the reason that is stated rather than assumed. The four\n * globs mirror `releaseConfig`'s `['packages/*', 'python-packages/*']`: npm\n * (`package.json`), C# (`*.csproj`), Python (`pyproject.toml`) and Dart\n * (`pubspec.yaml`). A publishable `flutter-lib` lands in `packages/` with a\n * `pubspec.yaml` and **no `package.json`** (see `@mnci/nx-flutter`'s library\n * generator), so while it was missing here a Flutter-only workspace logged\n * \"Nothing to release - skipping\" and exited 0 on every single release run —\n * green, and never releasing. Dart is also the one kind with no publish step\n * at all (Azure Artifacts has no pub feed, so publishing IS the git tag),\n * which is exactly why nothing downstream would ever have surfaced the miss.\n *\n * A `vscode-extension` is the one releasable project outside those folders: it\n * lives in `apps/` and is in scope through its tag (#229), so it is counted by the\n * same tag, read from its `package.json`. Without it an extension-only workspace\n * would log \"Nothing to release\" for ever, the Dart failure again.\n *\n * The same count also feeds the `RELEASE_SPECIFIER` keyword check below, so\n * under-counting weakened that guard too: one npm lib plus one flutter lib\n * counted as 1, and a bare keyword — the input that silently under-bumps\n * interdependent packages — was accepted for a workspace with 2.\n *\n * @param pythonPublishEnv - A `node -e`-fragment that exports `TWINE_*` when\n * there are Python packages and a configured feed, or `''` to export nothing.\n * @param nugetPublishEnv - A `node -e`-fragment that exports `NUGET_PAT` when\n * there are C# packages and a configured feed, or `''` to export nothing.\n * @returns The full `node -e` release one-liner.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction releaseGuard (pythonPublishEnv: string, nugetPublishEnv: string): string {\n return String.raw`node -e \"const fs=require('node:fs'),cp=require('node:child_process');const npmCount=fs.globSync('packages/*/package.json').length;const csharpCount=fs.globSync('packages/*/*.csproj').length;const pythonCount=fs.globSync('python-packages/*/pyproject.toml').length;const dartCount=fs.globSync('packages/*/pubspec.yaml').length;const vscodeCount=fs.globSync('apps/*/package.json').filter(p=>{try{return(JSON.parse(fs.readFileSync(p,'utf8')).nx?.tags||[]).includes('${VSCODE_EXTENSION_TAG}')}catch{return false}}).length;const hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;const hasDart=dartCount>0;const hasVscode=vscodeCount>0;if(!hasNpm&&!hasPython&&!hasCsharp&&!hasDart&&!hasVscode){console.log('Nothing to release - skipping.');process.exit(0)}const specifier=process.env.RELEASE_SPECIFIER||'';let releaseCmd='npx nx release --yes';if(specifier){if(!/^(major|minor|patch|\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?)$/.test(specifier)){console.error('RELEASE_SPECIFIER value \\''+specifier+'\\' is invalid - use major, minor, patch, or an exact version like 1.2.3.');process.exit(1)}const releaseProjectCount=npmCount+csharpCount+pythonCount+dartCount+vscodeCount;if(/^(major|minor|patch)$/.test(specifier)&&releaseProjectCount>1){console.error('RELEASE_SPECIFIER is a keyword (\\''+specifier+'\\') but this workspace has '+releaseProjectCount+' releasable packages - a keyword under-bumps interdependent packages, because nx computes the dependency-bump pass from a stale cached version. Set RELEASE_SPECIFIER to an exact version instead, or clear it.');process.exit(1)}releaseCmd='npx nx release '+specifier+' --yes'}const env={...process.env};${pythonPublishEnv}${nugetPublishEnv}process.exit(cp.spawnSync(releaseCmd,{stdio:'inherit',shell:true,env}).status ?? 1)\"`\n}\n\n/**\n * The env var each registry kind carries a PyPI-side credential in.\n *\n * @remarks\n * Deliberately NOT the same variable as npm's. Azure Artifacts' feed is\n * multi-protocol, so one PAT authenticates both; public npm and public PyPI\n * are unrelated services with unrelated credentials, and conflating them is\n * how a workspace ends up sending its npm automation token to PyPI.\n */\nexport const PYPI_TOKEN_VARIABLE = 'PYPI_TOKEN'\n\n/**\n * The env var an Azure Artifacts workspace carries its base64-encoded PAT in.\n *\n * @remarks\n * Named once and shared by {@link npmAuthEnvVariable},\n * {@link pythonPublishEnvFragment} and {@link nugetPublishEnvFragment}, for the\n * same \"cannot drift between providers\" reason {@link PYPI_TOKEN_VARIABLE} is a\n * constant rather than three string literals.\n */\nexport const AZURE_PAT_VARIABLE = 'PAT'\n\n/**\n * The fail-fast an Azure Artifacts publish fragment opens with, so a missing\n * PAT stops the release instead of being decoded into an empty credential.\n *\n * @remarks\n * The same reasoning {@link pythonPublishEnvFragment} documents for public\n * PyPI, applied to the feed that had no check at all. Both Azure fragments used\n * to call `Buffer.from(process.env.PAT, 'base64')` unguarded, and an\n * unconfigured secret does not arrive as `undefined` - **both providers\n * substitute the empty string** - so the decode SUCCEEDED and produced `''`.\n *\n * For C# that was silent: `csharpLibPublishTarget()` self-gates on a falsy\n * `NUGET_PAT`, printing \"NuGet publish is not configured\" and exiting 0. Under\n * a tag-only release model that is the worst possible outcome - `nx release`\n * versions and TAGS the package, the publish step reports success, and nothing\n * reaches the feed. The next run resolves the current version from that tag and\n * bumps past it, so the skipped version is never retried and never exists.\n *\n * Checked here, before `nx release` runs, rather than inside the publish\n * target: once versioning has tagged, refusing to publish is already too late.\n *\n * @param countExpression - The in-fragment variable holding the package count.\n * @param subject - How the message names this ecosystem's packages.\n * @returns The `node -e` fragment fail-fast, for use inside an `if(has…){}`.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction azurePatPreflight (countExpression: string, subject: string): string {\n return `if(!process.env.${AZURE_PAT_VARIABLE}){console.error('This workspace has '+${countExpression}+' ${subject} package(s) to publish to Azure Artifacts but ${AZURE_PAT_VARIABLE} is empty. Add your base64-encoded Azure DevOps PAT as a secret named ${AZURE_PAT_VARIABLE}, or remove the ${subject} packages from release.projects.');process.exit(1)}`\n}\n\n/**\n * The extra `env:` line the release step needs to carry a PyPI token.\n *\n * @remarks\n * Only for the public-npm registry kind. An Azure Artifacts feed publishes\n * Python through the SAME multi-protocol feed and the same PAT the step\n * already exports, so adding a second variable there would suggest a second\n * credential exists when it does not.\n *\n * Rendered from one function for both providers, so the variable name cannot\n * drift between them - the same reason {@link npmAuthEnvVariable} exists.\n *\n * @param registryKind - The workspace's registry kind.\n * @param variableReference - Renders a named secret in the calling provider's\n * own syntax (Azure `$(NAME)`, GitHub `${{ secrets.NAME }}`).\n * @param indent - The indentation the provider's `env:` block uses.\n * @returns The rendered line, or `''` when no PyPI token applies.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pypiTokenEnvLine (\n registryKind: RegistryConfig['kind'],\n variableReference: (name: string) => string,\n indent: string,\n): string {\n return registryKind === 'npm'\n ? `\n${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}`\n : ''\n}\n\n/**\n * Injected into {@link releaseGuard}: exports twine publish credentials when\n * the workspace has Python packages.\n *\n * @remarks\n * TWO genuinely different mechanisms, which is why this is not one template\n * with a swapped URL.\n *\n * Azure Artifacts is a multi-protocol feed: the same org/project/feed that\n * serves npm serves Python, so the upload URL is derived and the credential is\n * the same PAT, decoded from the base64 value both providers read.\n *\n * Public PyPI takes an API token under the literal username `__token__` - that\n * spelling is PyPI's own convention, not a placeholder - and needs NO\n * repository URL, since twine's default already points at\n * `upload.pypi.org/legacy/`. Writing one would be a chance to get it wrong for\n * no gain. `TWINE_NON_INTERACTIVE` is set because twine otherwise PROMPTS for\n * a missing password, and a prompt on a CI agent is a build that hangs until\n * it times out rather than one that fails.\n *\n * The empty-token check is a fail-fast rather than a credential probe. A HEAD\n * against the upload endpoint was considered and rejected: `twine check` only\n * validates the artefact, and an authenticated probe against the real registry\n * means a release step that can fail for reasons unrelated to this workspace.\n * The failure worth catching cheaply is the common one - the secret was never\n * configured - and that is knowable without a network call. A token that is\n * present but wrong still fails at upload, loudly, which is correct.\n *\n * @param pythonPublishUrl - The twine upload URL for an Azure Artifacts feed,\n * or `undefined` for any other registry.\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` fragment for the workspace's registry kind.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonPublishEnvFragment (\n pythonPublishUrl: string | undefined,\n registryKind: RegistryConfig['kind'],\n): string {\n if (pythonPublishUrl !== undefined) {\n return `if(hasPython){${azurePatPreflight('pythonCount', 'Python')}env.TWINE_REPOSITORY_URL='${pythonPublishUrl}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}`\n }\n if (registryKind !== 'npm') {\n return ''\n }\n\n return `if(hasPython){if(!process.env.${PYPI_TOKEN_VARIABLE}){console.error('This workspace has '+pythonCount+' Python package(s) to publish but ${PYPI_TOKEN_VARIABLE} is empty. Add a PyPI API token as a secret named ${PYPI_TOKEN_VARIABLE}, or remove the Python packages from release.projects.');process.exit(1)}env.TWINE_USERNAME='__token__';env.TWINE_PASSWORD=process.env.${PYPI_TOKEN_VARIABLE};env.TWINE_NON_INTERACTIVE='1'}`\n}\n\n/**\n * Injected into {@link releaseGuard}: when there are C# packages and a\n * configured Azure feed, export the raw PAT `nuget.config`'s `%NUGET_PAT%`\n * placeholder reads (see {@link nugetConfigContent}'s remarks for why NuGet\n * takes the raw value, unlike `.npmrc`'s base64 `_password`).\n *\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @returns The `node -e` fragment, or `''` when there is no NuGet feed.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nugetPublishEnvFragment (nugetFeedUrl?: string): string {\n return nugetFeedUrl\n ? `if(hasCsharp){${azurePatPreflight('csharpCount', 'C#')}env.NUGET_PAT=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}`\n : ''\n}\n\n/**\n * The env var name + value pair that authenticates `npm ci`/`nx release publish`,\n * keyed by registry kind — two genuinely different secrets, never conflated.\n *\n * @remarks\n * Azure Artifacts' `.npmrc` (`npmrcContent`) reads a base64-encoded PAT via\n * `${PAT}`; public npm's reads a raw npm automation token via\n * `${NODE_AUTH_TOKEN}`. Before this, both {@link azurePipelinesYaml} and\n * {@link githubActionsYaml} always exported `PAT` regardless of registry —\n * harmless for Azure Artifacts, but silently non-functional for public npm\n * (nothing ever populated `NODE_AUTH_TOKEN`, so a public-npm workspace's CI\n * could build and version but never actually authenticate a publish).\n *\n * @param registryKind - The workspace's registry kind.\n * @param variableReference - Renders a named secret/variable in the calling\n * provider's own syntax (Azure `$(NAME)`, GitHub `${{ secrets.NAME }}`).\n * @returns A `[envVarName, value]` pair to render under the step's `env:` block.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmAuthEnvVariable (\n registryKind: RegistryConfig['kind'],\n variableReference: (name: string) => string,\n): [string, string] {\n return registryKind === 'npm'\n ? ['NODE_AUTH_TOKEN', variableReference('NPM_TOKEN')]\n : [AZURE_PAT_VARIABLE, variableReference(AZURE_PAT_VARIABLE)]\n}\n\n/**\n * Renders the `pool:` block body for a chosen build agent.\n *\n * @remarks\n * One CLI value drives it: Microsoft-hosted images start `ubuntu-`/`windows-`/\n * `macos-` (`ubuntu-latest`, `windows-2022`, `macos-13`, …) → `vmImage`;\n * anything else is treated as a self-hosted pool name → `name`. Either way the\n * pipeline's steps are OS-agnostic, so it runs unchanged on the chosen agent.\n *\n * @param agent - The vmImage or self-hosted pool name.\n * @returns The two-space-indented `pool:` child line.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function poolBlock (agent: string): string {\n return /^(?:ubuntu|windows|macos)-/i.test(agent) ? ` vmImage: ${agent}` : ` name: ${agent}`\n}\n\n/**\n * Builds the generated workspace's whole CI: one short Azure Pipelines file.\n *\n * @remarks\n * Runs unchanged on ANY agent OS (Linux, macOS, Windows): no bash, no\n * PowerShell — every step is a built-in task or a single-line\n * `git`/`npm`/`npx`/`node` command `cmd.exe` and `sh` execute identically.\n *\n * Every run first checks `nx sync:check` — a fast, explicit failure when\n * someone forgot to run `nx sync` (and commit the result) after adding a\n * cross-project import. `sync.applyChanges` in `nx.json` ({@link SYNC_CONFIG})\n * means that locally this almost never happens: Nx auto-applies the fix on the\n * next build/typecheck instead of just prompting.\n *\n * On `main` (non-PR) the pipeline: **packs every app** into `dist/drop/` as one\n * zip per app named `<type>-<name>.zip` (each app owns an `nx` `package`\n * target — {@link runAdd}), publishes `dist/drop` as the **`drop`** artifact,\n * emits one **build tag per app** (`##vso[build.addbuildtag]<type>-<name>`,\n * derived from the zip filenames so it is *exactly* the zip name — the classic\n * release/CD pipeline keys off it), then `nx release`s: **publish packages +\n * tag main** (versions from conventional commits, tag-only push).\n *\n * npm auth is one of two modes ({@link NpmAuthMode}). By default it is the base64\n * PAT from the `variableGroup` (default `Build`): the group exposes `$(PAT)`,\n * mapped as env on the npm steps and read by the root `.npmrc`'s `_password`\n * block, and `npmAuthenticate@0` is NOT used because it would overwrite that\n * password. With `build-identity` the file holds no password, so the task is added\n * before `npm ci` and injects the build service identity's token instead.\n *\n * Hard-won Azure lessons carried over:\n * - `checkout: self` detaches HEAD; re-attach with `git checkout -B` first or\n * `nx release` cannot push tags.\n * - Fetch all refs + tags up front (version resolution needs the tags).\n * - A git identity is required to create annotated tags on CI.\n * - One-time grants (project admin): *Project Collection Build Service* needs\n * *Contribute* on the repo (tag push); the PAT's owner needs feed *publish*.\n *\n * `nx release` versions BOTH `packages/*` (npm) and `python-packages/*`\n * (Python) from conventional commits and tags each — one unified release. When\n * `pythonPublishUrl` is set (Azure Artifacts — {@link pythonPublishUrl}), the\n * release step also exports `TWINE_*` so `nx release` publishes the Python\n * packages with `twine`, reusing the base64 `PAT` decoded to the raw token\n * twine/pypi basic-auth needs (no second secret; Azure accepts any username).\n * For the public-npm registry that env is omitted, so a Python package there\n * is still versioned + tagged but its publish needs user-provided `TWINE_*`.\n * Before any Python target runs, one guarded step installs the fixed toolchain\n * (`ruff`/`pytest`/`build`/`twine`) from the workspace's `requirements-dev.txt`\n * — written by `add/python.ts` on the first Python `add` — and a second\n * editable-installs every Python project into that same environment (the\n * pip-world counterpart of `npm install` hoisting every workspace package\n * into one root `node_modules`); both are skipped cleanly on a workspace with\n * no Python projects.\n *\n * @param agent - The build agent (vmImage or self-hosted pool name).\n * @param variableGroup - The Library variable group holding the base64 `PAT`.\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @param registryKind - The workspace's registry kind — selects `PAT` vs\n * `NPM_TOKEN` for the npm-authenticating steps.\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @param npmAuth - `build-identity` adds the `npmAuthenticate@0` step before\n * `npm ci` and drops the `PAT` mapping from that step; `pat` keeps both as they were.\n * @returns The full text of `azure-pipelines.yml`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function azurePipelinesYaml (\n agent: string,\n variableGroup: string,\n pythonPublishUrl?: string,\n registryKind: RegistryConfig['kind'] = 'azure-artifacts',\n nugetFeedUrl?: string,\n npmAuth: NpmAuthMode = 'pat',\n): string {\n // ENUMERATED CI reasons, never \"not a pull request\" — the Azure half of the\n // fix #22 made for GitHub, and the more exposed of the two.\n //\n // `ne(Build.Reason, 'PullRequest')` admits every reason that is not a PR, and\n // Azure documents EIGHT of those: Manual, Schedule, IndividualCI, BatchedCI,\n // BuildCompletion, ResourceTrigger, ValidateShelveset and CheckInShelveset. So\n // clicking *Run pipeline* on `main` — an ordinary Azure workflow, not an\n // exotic one — would publish packages and push release tags. GitHub's\n // equivalent needed someone to add a trigger first; this one never did.\n //\n // BOTH CI reasons are listed, and dropping either is a silent failure in the\n // opposite direction — releases would simply stop, with a green pipeline.\n // `BatchedCI` is the one that matters here: the trigger above sets\n // `batch: true`, and Azure documents BatchedCI as the reason for \"a Git push\n // ... and the Batch changes was selected\". `IndividualCI` stays because\n // batching only applies once a run is already in progress, so an unbatched\n // push is still the ordinary case.\n //\n // Verified against Microsoft's own docs rather than assumed: `in()` is\n // \"Evaluates True if left parameter is equal to any right parameter\", min 1 /\n // max N — and Azure defines a step's own `succeeded()` as\n // `in(variables['Agent.JobStatus'], ...)`, so it is valid in a step condition.\n const onMain = 'and(succeeded(), in(variables[\\'Build.Reason\\'], \\'IndividualCI\\', \\'BatchedCI\\'), eq(variables[\\'Build.SourceBranchName\\'], \\'main\\'))'\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, name => `$(${name})`)\n // Build-identity auth only exists for an Azure Artifacts feed; public npm has\n // nothing for the task to inject, so the mode is ignored there.\n const buildIdentity = npmAuth === 'build-identity' && registryKind === 'azure-artifacts'\n const npmAuthenticateStep = buildIdentity\n ? ` # Injects feed credentials into .npmrc using the BUILD SERVICE IDENTITY, so no\n # PAT is stored, rotated or encoded anywhere. This is what the feed accepts: its\n # publish endpoint advertises a Bearer scheme bound to login.windows.net, which\n # wants an Entra ID access token, and a PAT is not one. Must run before 'npm ci'.\n # Needs the build identity to be a Feed Publisher (Contributor) on the feed.\n - task: npmAuthenticate@0\n displayName: Authenticate npm against the feed (build identity, no PAT)\n inputs:\n workingFile: .npmrc\n\n`\n : ''\n const npmCiEnv = buildIdentity\n ? ''\n : `\n env:\n ${npmAuthName}: ${npmAuthValue}`\n\n return `name: monorepo-ci-$(Date:yyyyMMdd)$(Rev:.r)\n\n# Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'\n# versions from conventional commits and pushes ONLY a tag to main, and each\n# app is packed into dist/drop/<type>-<name>.zip by its own 'package' target.\n#\n# Cross-platform by construction: no bash, no PowerShell. Every step is a\n# built-in task or a single-line git/npm/npx/node command, so the pipeline\n# runs unchanged on Linux, macOS and Windows agents.\n\ntrigger:\n # While a run is in progress, queue further pushes and run them together rather\n # than concurrently. This is the closest thing Azure Pipelines YAML has to\n # GitHub's concurrency group, and it is load-bearing on main for the same\n # reason: two concurrent 'nx release' runs would race to create the same tag.\n batch: true\n branches:\n include:\n - main\n # EVERY other branch too, and that breadth is a correction rather than\n # generosity. On Azure Repos Git a YAML 'pr:' block does nothing at all\n # (see the note below), so a CI trigger is the only pre-merge\n # verification this file can switch on by itself. Listing main alone —\n # which is what this pipeline used to do, next to a 'pr:' block that\n # looked like it covered the rest — means a workspace verifies NOTHING\n # until a change has already reached the branch it was supposed to\n # protect.\n #\n # Release stays gated on main (the 'condition:' on every release step\n # below), so a topic-branch run installs, audits, syncs, formats,\n # verifies and packs, and publishes nothing.\n - '*'\n\n# PR validation is deliberately NOT configured here, because on Azure Repos Git\n# it CANNOT be: \"For an Azure Repos Git repo, you cannot configure a PR trigger\n# in the YAML file. You need to use branch policies.\" A 'pr:' block here is not\n# an error — it is silently ignored, which is worse, because the file then\n# documents a gate that does not exist. (The same block IS honoured for GitHub\n# and Bitbucket repos, which is why .github/workflows/ci.yml keeps its\n# 'pull_request:' trigger.)\n#\n# To get PR validation, configure it once in the UI: Project Settings ->\n# Repositories -> <repo> -> Policies -> <branch> -> Build Validation, pointing\n# at this pipeline. That is also the only thing that sets\n# System.PullRequest.TargetBranch, which the verify step below reads to scope\n# itself to affected projects — without a policy that variable is never set and\n# the step verifies every project instead. Correct either way, just slower.\n#\n# Cancelling a superseded PR run lives in that same policy (\"automatically\n# cancel\"), and likewise has no YAML expression.\npr: none\n\npool:\n${poolBlock(agent)}\n\nvariables:\n # Holds the npm auth secret the root .npmrc reads: the base64-encoded \\`PAT\\`\n # for an Azure Artifacts feed, or a raw npm automation token as \\`NPM_TOKEN\\`\n # for public npm. Mark it secret in Library. Add app build vars here too if needed.\n - group: ${variableGroup}\n # Relocates npm's cache inside the pipeline workspace so Cache@2 can restore it\n # (the default ~/.npm is outside the cacheable area on hosted agents). npm reads\n # this as an ordinary env var, so it needs no config file and works on every\n # agent OS.\n - name: npm_config_cache\n value: $(Pipeline.Workspace)/.npm\n # Overrides the bump nx would compute from conventional commits for one\n # release run — 'major', 'minor', 'patch', or an exact version ('1.2.3').\n # Defined here (rather than left unset) so $(RELEASE_SPECIFIER) always\n # expands: an Azure Pipelines macro that names an undefined variable is\n # left as the literal, unexpanded text '$(RELEASE_SPECIFIER)' rather than\n # empty, which would otherwise reach the release guard below as a bogus\n # value. Override it per run in Pipelines -> Run pipeline -> Variables, or\n # set a repo-level default in Pipeline -> Edit -> Variables. See the\n # release step below for why a bare keyword is unreliable once more than\n # one package is releasable.\n - name: RELEASE_SPECIFIER\n value: ''\n\nsteps:\n - checkout: self\n # Lets later steps push release tags back to the repo. The Project\n # Collection Build Service account needs Contribute permission on this\n # repo (Project Settings -> Repositories -> Security) — one-time grant.\n persistCredentials: true\n fetchDepth: 0\n\n # checkout leaves a detached HEAD; nx release needs a real branch.\n - script: git checkout -B $(Build.SourceBranchName)\n displayName: Attach HEAD to the source branch\n\n # Version resolution needs the release tags.\n - script: git fetch --all --prune --tags\n displayName: Fetch branches and release tags\n\n - script: git config user.name \"Azure Pipelines\" && git config user.email \"pipeline@dev.azure.com\"\n displayName: Set the git identity used for release tags\n\n - task: UseNode@1\n inputs:\n version: 24.x\n\n # The Azure counterpart of \\`cache: npm\\` on actions/setup-node: restores npm's\n # download cache so \\`npm ci\\` does not re-fetch every tarball on every run.\n # Keyed on the lockfile, so a dependency change misses and anything else hits;\n # restoreKeys falls back to the newest cache for this OS on a miss, which still\n # avoids a fully cold install. Agent.OS is in the key because a cached native\n # module built for one OS is not reusable on another.\n - task: Cache@2\n displayName: Cache npm packages\n inputs:\n key: 'npm | \"$(Agent.OS)\" | package-lock.json'\n restoreKeys: |\n npm | \"$(Agent.OS)\"\n path: $(npm_config_cache)\n\n${npmAuthenticateStep} - script: npm ci\n displayName: Install dependencies${npmCiEnv}\n\n # Fails ONLY on an advisory that has a published fix, at moderate or above;\n # anything upstream has not fixed is printed and passes. See NPM_AUDIT_STEP's\n # remarks for why that split replaced a warn-only step.\n - script: ${NPM_AUDIT_STEP}\n displayName: npm audit (fails on an actionable advisory)\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine, pip-audit)\n # — written by 'mnci add' to requirements-dev.txt on the first Python\n # project. Plain pip, no uv/Poetry: portable guard skips cleanly on a\n # workspace with none.\n - script: ${PYTHON_INSTALL_GUARD}\n displayName: Install Python dependencies (ruff, pytest, build, twine, pip-audit)\n\n # Editable-installs every Python project into one shared environment, the\n # pip-world counterpart of 'npm install' hoisting every workspace package\n # into one root node_modules — so a project that imports an internal lib\n # (normally vendored only at build time) can resolve that import at\n # lint/test time too. Portable guard skips cleanly on a workspace with none.\n - script: ${PYTHON_WORKSPACE_INSTALL_GUARD}\n displayName: Install Python project dependencies (editable, workspace-wide)\n\n # Non-blocking, same reasoning as the npm audit step above. Runs after the\n # workspace-wide install so it scans the workspace's actual dependency set,\n # not just the fixed toolchain.\n - script: ${PIP_AUDIT_GUARD}\n displayName: pip-audit (non-blocking)\n\n # Go, if the workspace has any: hosted agents ship the toolchain, so only\n # the module cache and the linter need seeding. Both guards skip cleanly on\n # a workspace with no root go.mod.\n - script: ${GO_MODULE_DOWNLOAD_GUARD}\n displayName: Download Go module dependencies\n\n # golangci-lint is what the generated Go lint target actually runs (the\n # plugin's own default is 'go fmt', which only reformats). The pinned,\n # checksum-verified prebuilt release lands in GOPATH/bin (go install at the\n # same version is the fallback), which is not on PATH by default on a hosted\n # agent — so prepend it for every later step in the job.\n - script: ${GOLANGCI_LINT_INSTALL_GUARD}\n displayName: Install golangci-lint\n\n - script: ${GO_TOOL_PATH_AZURE}\n displayName: Add Go tool bin to PATH\n\n # Flutter, if the workspace has any. Unlike Python and Go, hosted agents do\n # NOT ship the Flutter SDK, so this installs it (shallow git clone at a\n # pinned tag) and puts it on PATH. All three guards skip cleanly on a\n # workspace with no root pubspec.yaml, and the install also skips when an\n # SDK is already on PATH.\n - script: ${FLUTTER_SDK_INSTALL_GUARD}\n displayName: Install the Flutter SDK (${FLUTTER_SDK_VERSION})\n\n - script: ${FLUTTER_TOOL_PATH_AZURE}\n displayName: Add the Flutter SDK to PATH\n\n # One command resolves EVERY Dart dependency, internal and external, for the\n # whole workspace: the projects are pub workspace members, so this writes a\n # single root pubspec.lock they all share.\n - script: ${FLUTTER_PUB_GET_GUARD}\n displayName: Resolve Dart dependencies (one pub get for the whole workspace)\n\n # .NET, if the workspace has any. Azure's 'condition:' expression language\n # has no glob function, so this script step detects C# projects first and\n # hands the answer to the install task below through a pipeline variable —\n # see DOTNET_DETECT_AZURE's remarks for why this differs from every other\n # toolchain's self-gating 'node -e' guard.\n - script: ${DOTNET_DETECT_AZURE}\n displayName: Detect .NET projects\n\n - task: UseDotNet@2\n displayName: Install the .NET SDK (${DOTNET_SDK_VERSION})\n condition: eq(variables['hasDotnetProjects'], 'true')\n inputs:\n version: ${DOTNET_SDK_VERSION}\n\n # Fails fast, with an unambiguous message, when a stale TypeScript project\n # reference (or another sync generator's drift) was never synced+committed\n # locally — sync.applyChanges (nx.json) only auto-applies interactively, so\n # CI still needs its own explicit, early check rather than surfacing this as\n # a confusing failure buried inside the build step below.\n - script: npx nx sync:check\n displayName: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)\n\n # The one verify step, and deliberately the only one: affected projects on a\n # pull request, EVERY project on anything else (a push to main included, so a\n # release is always verified in full). Every fallback — no target branch, an\n # unresolvable merge-base — takes the full path, because a run that verifies\n # too little still reports green.\n #\n # 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets\n # below, so adding it back as its own step would only duplicate work — and on\n # a pull request it would re-lint every project, discarding the point of this.\n - script: ${AFFECTED_OR_ALL_GUARD}\n displayName: Verify (affected on a PR, every project on main)\n\n # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'\n # target. Portable guard: skip cleanly when the workspace has no apps yet.\n - script: ${PACK_APPS_GUARD}\n displayName: Pack all apps (one zip per app -> dist/drop)\n condition: ${onMain}\n\n - task: PublishBuildArtifacts@1\n displayName: Publish the drop (one zip per app)\n condition: ${onMain}\n inputs:\n PathtoPublish: $(Build.SourcesDirectory)/dist/drop\n ArtifactName: drop\n\n # One build tag per packed app, EXACTLY the zip name (type-name), so the\n # classic release pipeline knows which app to run for. Derived from the zip\n # filenames so the tag can never drift from the artifact.\n - script: node -e \"const fs=require('node:fs');const path=require('node:path');for(const f of fs.globSync('dist/drop/*.zip')){console.log('##vso[build.addbuildtag]'+path.basename(f,'.zip'))}\"\n displayName: Tag the run per app (type-name)\n condition: ${onMain}\n\n # nx release resolves each package's current version from its git tag, and\n # falls back to the (permanently stale, since git.commit is false) on-disk\n # version when a tag cannot be resolved — which happens whenever this\n # checkout lacks full history. This is currently guaranteed by fetchDepth 0\n # above plus the explicit fetch below, but neither is expressed as a\n # dependency anywhere else, so this makes it a hard, loud precondition\n # instead of a silently wrong version discovered after release.\n - script: ${SHALLOW_CLONE_GUARD}\n displayName: Verify this is a full checkout (nx release needs the real tag history)\n condition: ${onMain}\n${\n npmAuthPreflight(registryKind)\n ? `\n # Prove npm will accept us BEFORE anything irreversible happens. nx release\n # tags first and publishes last, so a rejected token leaves a version tagged\n # with nothing on the registry - and that number is then skipped forever,\n # because the next run resolves the current version from the newest tag.\n - script: ${npmAuthPreflight(registryKind)}\n displayName: Preflight — npm must accept the token before anything is tagged\n condition: ${onMain}\n env:\n NODE_AUTH_TOKEN: $(NPM_TOKEN)\n`\n : ''\n}${\n pypiReleasePreflight(registryKind)\n ? `\n # PyPI cannot be asked whether a token is good - the only endpoint that\n # authenticates is the upload itself - so this checks the token's SHAPE and\n # names the projects this release would have to CREATE. Project creation is\n # rate limited per account, and a 429 arrives AFTER nx release has tagged.\n - script: ${pypiReleasePreflight(registryKind)}\n displayName: Preflight — name the PyPI projects this release would create\n condition: ${onMain}\n env:\n PYPI_TOKEN: $(PYPI_TOKEN)\n`\n : ''\n}\n # Version + tag + publish, in one release, for npm (packages/*), Python\n # (python-packages/*) AND C# (packages/*/*.csproj) — conventional commits,\n # tag-only push. Portable guard: nx release errors on an empty scope, so\n # skip cleanly when there is nothing to release. When there are Python or\n # C# packages and an Azure feed, twine/NuGet publish credentials are\n # exported (raw PAT, decoded from the base64 variable).\n #\n # RELEASE_SPECIFIER (the pipeline variable above) overrides the bump nx\n # would compute from conventional commits for THIS run. In a workspace with\n # more than one releasable package, a bare keyword ('major'/'minor'/'patch')\n # is unreliable — nx versions interdependent packages in two passes (a\n # dependency-bump pass, then the specifier pass), and the second pass\n # computes from a version cached before the first pass ran, silently\n # landing back on the same patch bump either way. An exact version ('1.2.3')\n # does not have this problem, since both passes apply it verbatim — prefer\n # it whenever more than one package is releasable. The guard below fails\n # the run rather than under-bumping silently: clear the variable back to\n # '' once the override is no longer needed.\n - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl, registryKind), nugetPublishEnvFragment(nugetFeedUrl))}\n displayName: Release — version, tag and publish (npm + Python + C# + VS Code)\n condition: ${onMain}\n env:\n ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, name => `$(${name})`, ' '.repeat(6))}\n RELEASE_SPECIFIER: $(RELEASE_SPECIFIER)\n # A VS Code extension publishes with it; tools/vscode-extension.cjs skips the\n # Marketplace when it is unset, including when Azure leaves it as '$(VSCE_PAT)'.\n VSCE_PAT: $(VSCE_PAT)\n\n # nx release's own git push (release.git.push) is deliberately left off: it\n # only runs when a remote GitHub/GitLab Release is configured, which this\n # pipeline never does, so it would never push the tag the step above just\n # created. Pushed explicitly, unconditionally (a no-op when nothing released)\n # once tagging is guaranteed to have already happened.\n - script: git push origin --tags\n displayName: Push release tags (nx release's own push never runs without a remote Release configured)\n condition: ${onMain}\n`\n}\n\n/**\n * Builds the generated workspace's whole CI as a GitHub Actions workflow —\n * the GitHub-hosted equivalent of {@link azurePipelinesYaml}.\n *\n * @remarks\n * Same pipeline, same shared guard scripts ({@link PYTHON_INSTALL_GUARD},\n * {@link PYTHON_WORKSPACE_INSTALL_GUARD}, {@link PIP_AUDIT_GUARD},\n * {@link NPM_AUDIT_STEP}, {@link PACK_APPS_GUARD}, {@link releaseGuard}) —\n * only the provider syntax differs, so the two YAML files can never drift on\n * what CI actually does.\n * Two steps from the Azure version are dropped, both for reasons already\n * documented there:\n * - **Attach HEAD to a branch**: `actions/checkout` (unlike Azure's\n * `checkout: self`) already leaves a push-triggered run on the real branch,\n * not a detached HEAD, so there is nothing to re-attach.\n * - **Tag the run per app**: `##vso[build.addbuildtag]` is an Azure classic\n * Release-pipeline mechanism with no GitHub Actions equivalent; the `drop`\n * artifact (one zip per app inside it) is the portable substitute.\n *\n * Auth is a single repository (or environment) secret — `PAT` for an Azure\n * Artifacts feed, or `NPM_TOKEN` (a raw npm automation token) for public npm\n * — GitHub has no \"variable group\" concept, so unlike the Azure version this\n * needs no CLI-collected name, just a secret the user creates once in the\n * repo settings, read here as `secrets.PAT`/`secrets.NPM_TOKEN`.\n * `permissions: contents: write` is what lets the checkout's own token push\n * the release tag back (no `persistCredentials` step to opt into — GitHub's\n * checkout wires this up from the job's `permissions` automatically).\n *\n * @param agent - The build agent — reused as-is for `runs-on:` (GitHub's\n * hosted runner labels, e.g. `ubuntu-latest`, already match the common Azure\n * vmImage names; anything else is passed through as a self-hosted runner\n * label).\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @param registryKind - The workspace's registry kind — selects `PAT` vs\n * `NPM_TOKEN` for the npm-authenticating steps.\n * @param ci - Which CI provider(s) the workspace generates a pipeline for.\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @returns The full text of `.github/workflows/ci.yml`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function githubActionsYaml (\n agent: string,\n pythonPublishUrl?: string,\n registryKind: RegistryConfig['kind'] = 'azure-artifacts',\n ci: CiProvider = 'github',\n nugetFeedUrl?: string,\n): string {\n // `== 'push'`, not `!= 'pull_request'`. Identical today — the generated workflow\n // has exactly two triggers, `push` and `pull_request` — but the negative form\n // says \"anything that is not a PR\", which quietly means \"and any trigger anyone\n // adds later\". Add a `workflow_dispatch` or a `schedule` to a workflow written\n // the negative way and clicking *Run workflow* starts publishing packages and\n // pushing release tags, with nothing in the file hinting that it would.\n //\n // Not hypothetical: mnci's own workflow hand-added `workflow_dispatch` for its\n // Windows e2e job and inherited exactly that hazard. The positive form states\n // the actual intent — release on a push to main — and cannot be widened by\n // accident.\n const onMain = 'github.event_name == \\'push\\' && github.ref_name == \\'main\\''\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(\n registryKind,\n name => `\\${{ secrets.${name} }}`,\n )\n // Matches releaseConfig(ci)'s own condition exactly — GitHub Release\n // creation (and therefore Nx's own tag push) is only ever on when GitHub\n // Actions is the *only* configured provider; see releaseConfig's remarks.\n const githubReleases = ci === 'github'\n\n return `name: CI\n\n# Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'\n# versions from conventional commits and pushes ONLY a tag to main, and each\n# app is packed into dist/drop/<type>-<name>.zip by its own 'package' target.\n# The GitHub Actions equivalent of azure-pipelines.yml — see there for the\n# fuller rationale; both stay hand-kept in lockstep, there is no shared template.\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n # One run at a time per ref, but \\`cancel-in-progress\\` is an **expression** on\n # purpose rather than a flat \\`true\\`.\n #\n # A superseded PR run is pure waste, so cancel it. A run on \\`main\\` must never\n # be cancelled: it publishes packages and pushes release tags, and killing it\n # part-way can leave a tag pushed with the publish only half done — a state no\n # rerun repairs cleanly, because the version is then already tagged. Those runs\n # queue instead, which also stops two \\`nx release\\` invocations racing to create\n # the same tag when two commits land close together.\n group: \\${{ github.workflow }}-\\${{ github.ref }}\n cancel-in-progress: \\${{ github.event_name == 'pull_request' }}\n\npermissions:\n # Lets the release step push the version tag nx release creates back to main,\n # and (github-only provider) create the GitHub Release itself.\n contents: write\n # Lets azure/login exchange this job's OIDC token for a Microsoft Entra ID one,\n # which is how a VS Code extension reaches the Marketplace with no stored secret.\n # A token minted on a pull request cannot use it: the federated credential is\n # pinned to refs/heads/main.\n id-token: write\n\njobs:\n ci:\n runs-on: ${agent}\n steps:\n - uses: actions/checkout@${ACTION_VERSIONS['actions/checkout']}\n with:\n fetch-depth: 0\n\n # Version resolution needs the release tags.\n - run: git fetch --all --prune --tags\n name: Fetch branches and release tags\n\n - run: git config user.name \"github-actions[bot]\" && git config user.email \"github-actions[bot]@users.noreply.github.com\"\n name: Set the git identity used for release tags\n\n - uses: actions/setup-node@${ACTION_VERSIONS['actions/setup-node']}\n with:\n node-version: ${NODE_VERSION}\n # Caches ~/.npm keyed on package-lock.json, so \\`npm ci\\` restores from\n # the local cache instead of re-downloading every tarball on every run.\n # Nothing to invalidate by hand: the action keys on the lockfile, so a\n # dependency change misses the cache and a no-op change hits it.\n cache: npm\n\n # setup-node installs whatever npm the Node image bundles, and npm's major\n # is part of the contract: npm 11 reuses an already-installed tree instead\n # of re-resolving, so it applies \\`overrides\\` differently from npm 10. See\n # NPM_VERSION's remarks for the measurement.\n - run: npm install -g npm@${NPM_VERSION}\n name: Pin npm to the major mnci verifies against\n\n - run: npm ci\n name: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Fails ONLY on an advisory that has a published fix, at moderate or\n # above; anything upstream has not fixed is printed and passes. See\n # NPM_AUDIT_STEP's remarks for why that split replaced a warn-only step.\n - run: ${NPM_AUDIT_STEP}\n name: npm audit (fails on an actionable advisory)\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine,\n # pip-audit) — written by 'mnci add' to requirements-dev.txt on the\n # first Python project. Plain pip, no uv/Poetry: portable guard skips\n # cleanly on a workspace with none.\n - run: ${PYTHON_INSTALL_GUARD}\n name: Install Python dependencies (ruff, pytest, build, twine, pip-audit)\n\n # Editable-installs every Python project into one shared environment, the\n # pip-world counterpart of 'npm install' hoisting every workspace package\n # into one root node_modules — so a project that imports an internal lib\n # (normally vendored only at build time) can resolve that import at\n # lint/test time too. Portable guard skips cleanly on a workspace with none.\n - run: ${PYTHON_WORKSPACE_INSTALL_GUARD}\n name: Install Python project dependencies (editable, workspace-wide)\n\n # Non-blocking, same reasoning as the npm audit step above. Runs after\n # the workspace-wide install so it scans the workspace's actual\n # dependency set, not just the fixed toolchain.\n - run: ${PIP_AUDIT_GUARD}\n name: pip-audit (non-blocking)\n\n # Go, if the workspace has any: hosted runners ship the toolchain, so\n # only the module cache and the linter need seeding. Both guards skip\n # cleanly on a workspace with no root go.mod.\n - run: ${GO_MODULE_DOWNLOAD_GUARD}\n name: Download Go module dependencies\n\n # golangci-lint is what the generated Go lint target actually runs (the\n # plugin's own default is 'go fmt', which only reformats). The pinned,\n # checksum-verified prebuilt release lands in GOPATH/bin (go install at the\n # same version is the fallback), which is not on PATH by default on a\n # hosted runner — so publish it for every later step in the job.\n - run: ${GOLANGCI_LINT_INSTALL_GUARD}\n name: Install golangci-lint\n\n - run: ${GO_TOOL_PATH_GITHUB}\n name: Add Go tool bin to PATH\n\n # Flutter, if the workspace has any. Unlike Python and Go, hosted runners\n # do NOT ship the Flutter SDK, so this installs it (shallow git clone at\n # a pinned tag) and puts it on PATH. All three guards skip cleanly on a\n # workspace with no root pubspec.yaml, and the install also skips when an\n # SDK is already on PATH.\n - run: ${FLUTTER_SDK_INSTALL_GUARD}\n name: Install the Flutter SDK (${FLUTTER_SDK_VERSION})\n\n - run: ${FLUTTER_TOOL_PATH_GITHUB}\n name: Add the Flutter SDK to PATH\n\n # One command resolves EVERY Dart dependency, internal and external, for\n # the whole workspace: the projects are pub workspace members, so this\n # writes a single root pubspec.lock they all share.\n - run: ${FLUTTER_PUB_GET_GUARD}\n name: Resolve Dart dependencies (one pub get for the whole workspace)\n\n # .NET, if the workspace has any. actions/setup-dotnet is the\n # maintained, cached installer GitHub itself ships, so this uses it\n # directly rather than a hand-rolled 'node -e' guard — see\n # DOTNET_DETECT_AZURE's remarks for the full reasoning. hashFiles()\n # gates it inline, so a JS-only workspace pays nothing.\n - uses: actions/setup-dotnet@${ACTION_VERSIONS['actions/setup-dotnet']}\n if: \\${{ hashFiles('apps/*/*.csproj', 'packages/*/*.csproj', 'libs/*/*.csproj') != '' }}\n with:\n dotnet-version: ${DOTNET_SDK_VERSION}\n\n # Fails fast, with an unambiguous message, when a stale TypeScript project\n # reference (or another sync generator's drift) was never synced+committed\n # locally — sync.applyChanges (nx.json) only auto-applies interactively, so\n # CI still needs its own explicit, early check rather than surfacing this as\n # a confusing failure buried inside the build step below.\n - run: npx nx sync:check\n name: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)\n\n # The one verify step, and deliberately the only one: affected projects on\n # a pull request, EVERY project on anything else (a push to main included,\n # so a release is always verified in full). Every fallback — no target\n # branch, an unresolvable merge-base — takes the full path, because a run\n # that verifies too little still reports green.\n #\n # 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets\n # below, so adding it back as its own step would only duplicate work — and\n # on a pull request it would re-lint every project, discarding the point\n # of this.\n - run: ${AFFECTED_OR_ALL_GUARD}\n name: Verify (affected on a PR, every project on main)\n\n # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'\n # target. Portable guard: skip cleanly when the workspace has no apps yet.\n - run: ${PACK_APPS_GUARD}\n name: Pack all apps (one zip per app -> dist/drop)\n if: \\${{ ${onMain} }}\n\n - uses: actions/upload-artifact@${ACTION_VERSIONS['actions/upload-artifact']}\n if: \\${{ ${onMain} }}\n with:\n name: drop\n path: dist/drop\n if-no-files-found: ignore\n\n # nx release resolves each package's current version from its git tag,\n # and falls back to the (permanently stale, since git.commit is false)\n # on-disk version when a tag cannot be resolved — which happens\n # whenever this checkout lacks full history. This is currently\n # guaranteed by fetch-depth 0 above plus the explicit fetch below, but\n # neither is expressed as a dependency anywhere else, so this makes it\n # a hard, loud precondition instead of a silently wrong version\n # discovered after release.\n - run: ${SHALLOW_CLONE_GUARD}\n name: Verify this is a full checkout (nx release needs the real tag history)\n if: \\${{ ${onMain} }}\n${\n npmAuthPreflight(registryKind)\n ? `\n # Prove npm will accept us BEFORE anything irreversible happens. nx\n # release tags first and publishes last, so a rejected token leaves a\n # version tagged with nothing on the registry - and that number is then\n # skipped forever, because the next run resolves the current version\n # from the newest tag.\n - run: ${npmAuthPreflight(registryKind)}\n name: Preflight — npm must accept the token before anything is tagged\n if: \\${{ ${onMain} }}\n env:\n NODE_AUTH_TOKEN: \\${{ secrets.NPM_TOKEN }}\n`\n : ''\n}${\n pypiReleasePreflight(registryKind)\n ? `\n # PyPI cannot be asked whether a token is good - the only endpoint that\n # authenticates is the upload itself - so this checks the token's SHAPE\n # and names the projects this release would have to CREATE. Project\n # creation is rate limited per account, and a 429 arrives AFTER nx\n # release has tagged.\n - run: ${pypiReleasePreflight(registryKind)}\n name: Preflight — name the PyPI projects this release would create\n if: \\${{ ${onMain} }}\n env:\n PYPI_TOKEN: \\${{ secrets.PYPI_TOKEN }}\n`\n : ''\n}\n # Version + tag + publish, in one release, for npm (packages/*), Python\n # (python-packages/*) AND C# (packages/*/*.csproj) — conventional\n # commits, tag-only push. Portable guard: nx release errors on an empty\n # scope, so skip cleanly when there is nothing to release. When there\n # are Python or C# packages and an Azure feed, twine/NuGet publish\n # credentials are exported (raw PAT, decoded from the base64 secret).\n #\n # RELEASE_SPECIFIER (repository variable, Settings -> Secrets and\n # variables -> Actions -> Variables) overrides the bump nx would compute\n # from conventional commits for THIS run: 'major', 'minor', 'patch', or\n # an exact version ('1.2.3'). In a workspace with more than one\n # releasable package, a bare keyword is unreliable — nx versions\n # interdependent packages in two passes (a dependency-bump pass, then\n # the specifier pass), and the second pass computes from a version\n # cached before the first pass ran, silently landing back on the same\n # patch bump either way. An exact version does not have this problem,\n # since both passes apply it verbatim — prefer it whenever more than one\n # package is releasable. The guard below fails the run rather than\n # under-bumping silently: unset or clear the variable once the override\n # is no longer needed.${\n githubReleases\n ? `\n # This provider also creates a per-project GitHub Release (changelog\n # generated from conventional commits since that project's last tag),\n # which is why 'nx release' pushes the tag itself here — see\n # releaseConfig's remarks for why that is safe on this Nx version and\n # why every other provider combination keeps the explicit push step below.`\n : ''\n }\n # Signs in to Microsoft Entra ID as the Marketplace publishing identity (an\n # app registration with a federated credential for this repository's main\n # branch), so the release step publishes VS Code extensions with\n # 'vsce publish --azure-credential' and no token. Runs only when the\n # AZURE_CLIENT_ID and AZURE_TENANT_ID repository variables exist and the\n # workspace has an extension (every one ships a .vscodeignore). No Azure\n # subscription is needed. Setup: README, 'VS Code extensions'.\n - uses: azure/login@${ACTION_VERSIONS['azure/login']}\n name: Sign in to Microsoft Entra ID (VS Code Marketplace)\n if: \\${{ ${onMain} && vars.AZURE_CLIENT_ID != '' && hashFiles('apps/*/.vscodeignore') != '' }}\n with:\n client-id: \\${{ vars.AZURE_CLIENT_ID }}\n tenant-id: \\${{ vars.AZURE_TENANT_ID }}\n allow-no-subscriptions: true\n\n - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl, registryKind), nugetPublishEnvFragment(nugetFeedUrl))}\n name: Release — version, tag${githubReleases ? ', publish and GitHub Release' : ' and publish'} (npm + Python + C# + VS Code)\n if: \\${{ ${onMain} }}\n env:\n ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, name => `\\${{ secrets.${name} }}`, ' '.repeat(10))}\n RELEASE_SPECIFIER: \\${{ vars.RELEASE_SPECIFIER }}\n # A VS Code extension publishes with Entra ID when the sign-in step\n # above ran (VSCE_AUTH=entra), else with the VSCE_PAT token;\n # tools/vscode-extension.cjs skips the Marketplace with neither.\n VSCE_AUTH: \\${{ vars.AZURE_CLIENT_ID != '' && 'entra' || '' }}\n VSCE_PAT: \\${{ secrets.VSCE_PAT }}${\n githubReleases\n ? `\n GITHUB_TOKEN: \\${{ secrets.GITHUB_TOKEN }}`\n : ''\n }\n${\n githubReleases\n ? ''\n : `\n # nx release's own git push (release.git.push) is deliberately left off: it\n # only runs when a remote GitHub/GitLab Release is configured, which this\n # workflow never does, so it would never push the tag the step above just\n # created. Pushed explicitly, unconditionally (a no-op when nothing released)\n # once tagging is guaranteed to have already happened.\n - run: git push origin --tags\n name: Push release tags (nx release's own push never runs without a remote Release configured)\n if: \\${{ ${onMain} }}\n`\n}`\n}\n\n/** Where a `pip` project's manifest can live, relative to the workspace root. */\nconst PIP_PROJECT_GLOBS = ['apps', 'python-packages', 'libs'] as const\n/** Where a `pub` project's manifest can live, relative to the workspace root. */\nconst PUB_PROJECT_GLOBS = ['apps', 'packages', 'libs'] as const\n/** Manifest filenames that mark a directory as belonging to each ecosystem. */\nconst ECOSYSTEM_MANIFESTS = {\n pip: ['pyproject.toml', 'requirements.txt'],\n pub: ['pubspec.yaml'],\n} as const\n\n/**\n * Whether the workspace has at least one project of the given ecosystem.\n *\n * @remarks\n * Detection rather than assumption, and that distinction is the whole point of\n * {@link dependabotConfig} — see there.\n *\n * @param workspaceRoot - The workspace to scan.\n * @param roots - The directories whose immediate children are projects.\n * @param manifests - Filenames that mark a project of this ecosystem.\n * @returns `true` when any project directory holds one of the manifests.\n * @throws Never - a missing directory reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction hasEcosystemProject (\n workspaceRoot: string,\n roots: readonly string[],\n manifests: readonly string[],\n): boolean {\n return roots.some(root => {\n const base = join(workspaceRoot, root)\n if (!existsSync(base)) {\n return false\n }\n\n return readdirSync(base, { withFileTypes: true }).some(\n entry =>\n entry.isDirectory() &&\n manifests.some(manifest => existsSync(join(base, entry.name, manifest))),\n )\n })\n}\n\n/**\n * One `updates:` entry, covering a whole ecosystem by glob.\n *\n * @param ecosystem - The `package-ecosystem` value.\n * @param roots - Directories whose immediate children hold the manifests.\n * @returns The YAML block, with a leading blank line.\n * @throws Never - string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction dependabotBlock (ecosystem: string, roots: readonly string[]): string {\n return (\n `\\n - package-ecosystem: ${ecosystem}\\n directories:\\n` +\n roots.map(root => ` - \"/${root}/*\"\\n`).join('') +\n ' schedule:\\n interval: weekly\\n'\n )\n}\n\n/**\n * The `.github/dependabot.yml` written for GitHub-hosted workspaces.\n *\n * @remarks\n * Cheap, high-signal hygiene every generated workspace gets automatically —\n * this very repo shipped without one and GitHub had to flag vulnerabilities\n * after the fact on every push instead of proposing update PRs proactively.\n * Dependabot is a GitHub-native feature (no app install, unlike Renovate), so\n * it is written only for `github`/`both` workspaces.\n *\n * `npm` and `github-actions` are unconditional, both at the workspace root:\n * `npm ci` installs every `packages/*` project from the one root lockfile, and\n * the actions entry patches the workflow {@link githubActionsYaml} writes.\n *\n * **`pip` and `pub` are emitted only when such a project actually exists, and\n * that is a bug fix rather than a refinement.** These blocks used to be written\n * unconditionally, on the stated reasoning that \"a glob matching nothing yet is\n * not an error — Dependabot simply finds no manifest there yet\". That claim was\n * false, and nothing ever re-checked it. Measured against this repo's own\n * Dependabot Updates history: the `pip` job failed on EVERY weekly run for over\n * a month, and `pub` on every run since it was added, while `npm_and_yarn` and\n * `github_actions` succeeded alongside them. An ecosystem entry whose globs\n * match no manifest is a hard failure, so every generated workspace without a\n * Python or Dart project produced two red Dependabot runs a week, for the whole\n * of its life, over projects it does not have.\n *\n * The globs are kept for the ecosystems that ARE emitted, so a second Python\n * project needs no rewrite. What changed is only whether the block appears at\n * all. `registerProjectCommands` re-runs this after every `mnci add`, so the\n * block appears the moment the first such project lands, and `mnci upgrade`\n * back-fills an existing workspace.\n *\n * @param workspaceRoot - The workspace to scan for Python and Dart projects.\n * @returns The YAML to write to `.github/dependabot.yml`.\n * @throws Never - a missing directory reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function dependabotConfig (workspaceRoot: string): string {\n let config = `# Generated by MoNecromanCI. Weekly dependency-update PRs so vulnerable or\n# stale dependencies surface as a reviewable PR instead of only a push-time\n# warning.\n#\n# pip and pub appear here only once the workspace HAS a Python or Dart project.\n# An ecosystem entry whose directories match no manifest is a hard Dependabot\n# failure, not a no-op, so emitting them unconditionally cost every workspace\n# two red runs a week over projects it does not have.\nversion: 2\nupdates:\n - package-ecosystem: npm\n directory: \"/\"\n schedule:\n interval: weekly\n\n - package-ecosystem: github-actions\n directory: \"/\"\n schedule:\n interval: weekly\n`\n if (hasEcosystemProject(workspaceRoot, PIP_PROJECT_GLOBS, ECOSYSTEM_MANIFESTS.pip)) {\n config += dependabotBlock('pip', PIP_PROJECT_GLOBS)\n }\n // Dart projects are pub workspace members, so each declares its own\n // dependencies even though they all resolve through one root pubspec.lock —\n // hence per-project directories rather than just \"/\".\n if (hasEcosystemProject(workspaceRoot, PUB_PROJECT_GLOBS, ECOSYSTEM_MANIFESTS.pub)) {\n config += dependabotBlock('pub', PUB_PROJECT_GLOBS)\n }\n\n return config\n}\n\n/**\n * Options for {@link applyOverlay}.\n *\n * @remarks\n * Collected by `mnci new`'s flags or prompts.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface OverlayOptions {\n /** The monorepo workspace name. */\n workspaceName: string\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope: string\n /** Where publishable packages are released to. */\n registry: RegistryConfig\n /** The CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent: string\n /** The Library variable group holding the base64 npm `PAT` (e.g. `Build`). */\n variableGroup: string\n /** Which CI provider(s) to write a pipeline file for. */\n ci: CiProvider\n /** The stack (TS major, linter, test runner) chosen at `new`. */\n stack: StackConfig\n /**\n * How npm authenticates against an Azure Artifacts feed. Absent means `pat`, so\n * a workspace generated before this existed keeps the file it has.\n */\n npmAuth?: NpmAuthMode\n}\n\n/**\n * Files `create-nx-workspace` scaffolds that mnci deliberately replaces.\n *\n * @remarks\n * Each entry duplicates something mnci owns, and leaving it in place is not\n * merely untidy — in the `.prettierrc` case it silently WINS:\n *\n * - **`.prettierrc`** — Nx writes `{ \"singleQuote\": true }`. Prettier resolves\n * `.prettierrc` before `.prettierrc.json`, so every option in\n * mnci's own formatting opinion was being ignored in every generated workspace.\n * Deleting it is what makes mnci's formatting opinion take effect at all.\n * - **`.vscode/`** — `extensions.json` lists the same recommendations the\n * `<workspace>.code-workspace` file already carries, so VS Code shows the\n * prompt twice and the two drift apart. mnci owned this file once\n * (`.vscode/extensions.json`), then moved to the single-file workspace and\n * never cleaned up the old location.\n * - **the AI-agent scaffolding** — `create-nx-workspace` 23.x writes ten\n * entries of it, and three copies of its `monitor-ci` scripts fail\n * `@mnci/eslint-config` with 36 errors (`unicorn/prefer-number-coercion`,\n * `unicorn/prefer-simple-condition-first`). So `mnci new` ended with\n * \"eslint could not format '.' (exit code 1)\" and `npm run lint` was RED on\n * a pristine workspace. Measured on 23.2.1.\n *\n * On that last one, two things were tried first and are recorded so nobody\n * repeats them:\n *\n * 1. `--aiAgents=none`, which `create-nx-workspace --help` documents (\"Use\n * \\\"none\\\" to skip\"). It does nothing on 23.2.1: a workspace generated with\n * it holds byte-identical scaffolding to one generated without it, in both\n * the `=none` and the space-separated array form. Verified against the real\n * binary, three times.\n * 2. Ignoring the files in `mnci/ignores` instead. That leaves files in the\n * repository the workspace's own linter disowns, which is exactly the\n * fragmentation the single root config exists to end.\n *\n * The list has to be COMPLETE, and that is the subtle part. Nx prints \"Your AI\n * agent configuration is outdated\" after a task run when an agent has both an\n * MCP config and rules present but would change if regenerated. Delete some of\n * the set and that condition still holds, so every `nx` command nags forever;\n * delete all of it and nothing is detected at all. `.github/agents` and\n * `.github/prompts` are the two easiest to miss — verified by deleting the full\n * set from a real workspace and confirming `nx show projects` and\n * `nx run-many -t build` print nothing.\n *\n * A user who wants them back runs `nx configure-ai-agents`, which is the\n * supported way in and is what the nag itself recommends.\n *\n * Removal is idempotent and safe on a workspace where they are already gone,\n * which is what makes `mnci upgrade` able to repair an existing workspace.\n */\nconst NX_SCAFFOLDING_TO_REMOVE = [\n '.prettierrc',\n '.prettierrc.json',\n '.vscode',\n // See LOCAL_REGISTRY_SCAFFOLDING: the config half of a publishing mechanism\n // this workspace does not use.\n '.verdaccio',\n // The AI-agent set, in full. See the remarks above: a partial delete leaves\n // every `nx` command printing an \"outdated configuration\" nag.\n '.agents',\n '.codex',\n '.cursor',\n '.gemini',\n '.opencode',\n '.github/agents',\n '.github/prompts',\n '.github/skills',\n 'opencode.json',\n // `.claude`, `AGENTS.md` and `CLAUDE.md` are NOT here. See\n // {@link NX_AUTHORED_AGENT_FILES} and {@link removeNxAuthoredAgentFiles}:\n // all three are paths a user writes by hand, so they are removed by what\n // they CONTAIN rather than by where they are.\n] as const\n\n/**\n * The marker Nx wraps its own agent rules in, and the file it owns outright.\n *\n * @remarks\n * `nx configure-ai-agents` writes its rules into `AGENTS.md` and `CLAUDE.md`\n * between a start and an end comment, and tells the reader to leave them\n * alone so the block can be updated in place. That marker is the only honest\n * way to tell Nx's text from the user's: both files are, by convention, where\n * a person writes instructions for their own repository. This project's own\n * `CLAUDE.md` is five hundred lines of hand-written guide with no marker in\n * it, and the previous behaviour - deleting both files by path - would have\n * taken all of it.\n *\n * `.claude/settings.json` is different: Nx writes the whole file, to register\n * its plugin marketplace, so it is removed outright. The rest of `.claude` -\n * `agents/`, `commands/`, anything else a user puts there - is theirs.\n */\nconst NX_AGENT_RULES_MARKER = '<!-- nx configuration start-->'\n\n/** Where Nx's own agent rules live, alongside whatever the user wrote there. */\nconst NX_AUTHORED_AGENT_FILES = ['AGENTS.md', 'CLAUDE.md'] as const\n\n/** The one file under `.claude` that Nx writes in full. */\nconst NX_CLAUDE_SETTINGS = '.claude/settings.json'\n\n/**\n * One key removed from a record, without mutating the original.\n *\n * @remarks\n * Exists because the destructuring form of this needs a computed key, which\n * reads badly enough that the lint config rejects it.\n *\n * @param record - The record to copy.\n * @param key - The key to leave out.\n * @returns A new record without that key.\n * @throws Never - pure object construction.\n * @typeParam T - The record's value type.\n */\nfunction withoutKey<T> (record: Record<string, T>, key: string): Record<string, T> {\n return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key))\n}\n\n/**\n * The local-registry story `@nx/js:lib --publishable` scaffolds, in the three\n * places it puts itself.\n *\n * @remarks\n * Generating a publishable library drops a `.verdaccio/config.yml`, a\n * `verdaccio` devDependency at the root, and a root `local-registry` target\n * wired to both. It is Nx's own way of rehearsing a publish against a\n * throwaway registry.\n *\n * mnci's way is different and already written down: `nx release` is tag-only\n * and publishes from CI on a push to `main`, and `npm run release:preview`\n * rehearses it without a registry at all. So this is a second publishing\n * mechanism arriving unasked, that nothing in the workspace refers to, and\n * bringing a devDependency with its own transitive tree and its own advisories\n * for a feature nobody chose.\n *\n * Deleted rather than documented, for the same reason as `.vscode/` and the\n * retired formatter configs: it re-appears on every `mnci add npm-lib`, so\n * leaving it is not a one-time cost. Anyone who wants a local registry can add\n * one deliberately; what they cannot do is notice it arriving.\n */\nexport const LOCAL_REGISTRY_SCAFFOLDING = {\n /** The config directory, removed with the rest of {@link NX_SCAFFOLDING_TO_REMOVE}. */\n directory: '.verdaccio',\n /** The root devDependency the target runs. */\n devDependency: 'verdaccio',\n /** The root Nx target wired to both of the above. */\n rootTarget: 'local-registry',\n} as const\n\n/**\n * Deletes the local-registry scaffolding from a workspace that already has it.\n *\n * @remarks\n * `applyOverlay` handles `mnci new` and `mnci upgrade` on its own — it removes\n * the directory with the rest of the Nx scaffolding, and drops the\n * devDependency and the target while rewriting the root manifest. This is for\n * `mnci add`, which scaffolds a publishable library without going near the\n * overlay.\n *\n * Edits in place rather than rebuilding from a spread, so every untouched key\n * keeps its position: a rebuild moves `devDependencies` and `nx` to the end of\n * the file, which is a diff in every generated workspace for no reason.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeLocalRegistryScaffolding (workspaceRoot: string): void {\n rmSync(join(workspaceRoot, LOCAL_REGISTRY_SCAFFOLDING.directory), {\n recursive: true,\n force: true,\n })\n\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!fileExists(manifestPath)) return\n\n const manifest = readJson<{\n devDependencies?: Record<string, string>\n nx?: Record<string, unknown> & { targets?: Record<string, unknown> }\n } & Record<string, unknown>>(manifestPath)\n\n if (manifest.devDependencies?.[LOCAL_REGISTRY_SCAFFOLDING.devDependency] !== undefined) {\n delete manifest.devDependencies[LOCAL_REGISTRY_SCAFFOLDING.devDependency]\n if (Object.keys(manifest.devDependencies).length === 0) delete manifest.devDependencies\n }\n if (manifest.nx?.targets?.[LOCAL_REGISTRY_SCAFFOLDING.rootTarget] !== undefined) {\n delete manifest.nx.targets[LOCAL_REGISTRY_SCAFFOLDING.rootTarget]\n // Emptied containers go entirely: nothing else in a generated manifest\n // carries an empty object, so a stray one would read as deliberate.\n if (Object.keys(manifest.nx.targets).length === 0) delete manifest.nx.targets\n if (Object.keys(manifest.nx).length === 0) delete manifest.nx\n }\n\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * Deletes the `create-nx-workspace` scaffolding mnci replaces.\n *\n * @remarks\n * See {@link NX_SCAFFOLDING_TO_REMOVE} for why each entry has to go. This is\n * the first thing `applyOverlay` deletes rather than overwrites, so\n * `mnci upgrade`'s \"review with `git diff`\" advice now covers deletions too.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeNxScaffolding (workspaceRoot: string): void {\n for (const entry of NX_SCAFFOLDING_TO_REMOVE) {\n rmSync(join(workspaceRoot, entry), { recursive: true, force: true })\n }\n removeNxAuthoredAgentFiles(workspaceRoot)\n removeProjectEslintConfigs(workspaceRoot)\n}\n\n/**\n * Strips Nx's agent rules from `AGENTS.md` and `CLAUDE.md`, keeping the rest.\n *\n * @remarks\n * The rules sit between {@link NX_AGENT_RULES_MARKER} and its closing\n * comment, which Nx puts there precisely so the block can be replaced without\n * touching what surrounds it. Everything outside the block is the user's and\n * survives; the file is deleted only when the block WAS the whole file, which\n * is the case `create-nx-workspace` produces.\n *\n * A file with no marker is Nx-unaware and is left completely alone - this\n * project's own `CLAUDE.md` is exactly that, and the previous by-path deletion\n * would have removed it in full.\n *\n * Suppressing Nx's \"your AI agent configuration is outdated\" nag needs the\n * rules gone, not the file gone: the nag fires on rules that would change if\n * regenerated, and an excised block leaves none. Verified against a real\n * workspace with `nx show projects` and `nx run-many -t build`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction removeNxAuthoredAgentFiles (workspaceRoot: string): void {\n // Nx writes this one in full, to register its plugin marketplace. The rest\n // of `.claude` - agents, commands - is the user's and is left alone.\n rmSync(join(workspaceRoot, NX_CLAUDE_SETTINGS), { force: true })\n // ...but when settings.json was all there was, the directory goes too. An\n // empty `.claude/` is still AI-agent scaffolding to every tool that probes\n // for the directory, and the e2e's \"no AI-agent scaffolding: .claude\" check\n // failed on every fresh workspace because of exactly this leftover.\n const claudeDirectory = join(workspaceRoot, dirname(NX_CLAUDE_SETTINGS))\n if (existsSync(claudeDirectory) && readdirSync(claudeDirectory).length === 0) {\n rmSync(claudeDirectory, { recursive: true, force: true })\n }\n\n for (const name of NX_AUTHORED_AGENT_FILES) {\n const path = join(workspaceRoot, name)\n if (!existsSync(path)) continue\n\n const kept = withoutNxAgentRules(readFileSync(path, 'utf8'))\n if (kept === undefined) continue\n\n if (kept.trim() === '') rmSync(path, { force: true })\n else writeFileEnsured(path, kept)\n }\n}\n\n/**\n * One agent-rules file with Nx's marked block taken out.\n *\n * @remarks\n * Returns `undefined` when there is no block to remove, so the caller can\n * leave a hand-written file untouched rather than rewriting it byte-for-byte\n * and showing a spurious diff.\n *\n * The end marker is matched by its opening rather than spelled out in full:\n * Nx has changed the words inside it before, and a missed match would silently\n * mean \"no block\", which is the failure that loses nothing but fixes nothing.\n *\n * @param content - The file's current text.\n * @returns What should remain, or `undefined` when the file carries no block.\n * @throws Never - pure string manipulation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction withoutNxAgentRules (content: string): string | undefined {\n const start = content.indexOf(NX_AGENT_RULES_MARKER)\n if (start === -1) return undefined\n\n const endMarker = '<!-- nx configuration end'\n const end = content.indexOf(endMarker, start)\n if (end === -1) return undefined\n\n const afterEnd = content.indexOf('>', end)\n const rest = afterEnd === -1 ? '' : content.slice(afterEnd + 1)\n\n // `[\\r\\n]`, not `\\n`: a repository checked out on Windows has CRLF line\n // endings, and trimming only the LF leaves the carriage returns behind as\n // blank lines at the top of the file - which is most of this project's own\n // users, since Windows is the platform its e2e runs on.\n return `${content.slice(0, start)}${rest}`.replace(/^[\\r\\n]+/, '')\n}\n\n/**\n * Deletes one path if it exists, quietly.\n *\n * @remarks\n * For the config files that belong to the linter mode the workspace did **not**\n * choose. Leaving them behind is not cosmetic: two formatter configs means the\n * editor and CI can disagree about which one applies, which is the same class of\n * silent failure as the `.prettierrc` precedence bug — a config that is present,\n * valid, and ignored.\n *\n * `force: true` makes a missing path a no-op, which is the common case: a fresh\n * `mnci new` has neither mode's files yet.\n *\n * @param path - Absolute path to remove.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeIfPresent (path: string): void {\n rmSync(path, { recursive: true, force: true })\n}\n\n/**\n * Adds `.eslintcache` to `.gitignore` if it is not already there.\n *\n * @remarks\n * `mnci` never writes `.gitignore` itself — `create-nx-workspace` owns that\n * file, and nothing here needed adding to it, historically. That stopped\n * being true the moment the root `format`/`lint` scripts adopted\n * `eslint --cache` (measured 3.4x faster on an unchanged re-run): the very\n * first `npm run format` a fresh workspace runs — during `mnci new` itself —\n * writes a root `.eslintcache` file that `create-nx-workspace`'s own\n * `.gitignore` template has no reason to know about. Confirmed end to end: a\n * freshly generated workspace's very first `git add -A` (the command\n * `mnci new`'s own \"Next steps\" output tells the user to run) stages it.\n *\n * A plain string check, not a `.gitignore` parser: `.eslintcache` is written\n * here as a single bare line, so a literal line match is exactly as correct\n * as parsing gitignore syntax would be, without pulling in a parser for one\n * pattern. Idempotent — `mnci upgrade` runs this on every existing workspace,\n * and a workspace that already has the line (added by hand, or by a previous\n * `mnci upgrade`) is left untouched.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureEslintCacheIgnored (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n const current = readFileSync(gitignorePath, 'utf8')\n if (current.split('\\n').some(line => line.trim() === '.eslintcache')) {\n return\n }\n const withoutTrailingBlankLines = current.replace(/\\n+$/, '')\n const separator = withoutTrailingBlankLines.length > 0 ? '\\n\\n' : ''\n writeFileEnsured(\n gitignorePath,\n `${withoutTrailingBlankLines}${separator}# Added by MoNecromanCI: \\`npm run format\\`/\\`lint\\` run \\`eslint --cache\\`.\\n.eslintcache\\n`,\n )\n}\n\n/**\n * Lines every Python project needs `.gitignore` to carry, and nothing more.\n *\n * @remarks\n * Deliberately just the bytecode. Running `lint`, `typecheck` and `test` on a\n * generated `python-lib` writes five directories - `__pycache__/` twice,\n * `.mypy_cache/`, `.pytest_cache/` and `.ruff_cache/` - and the last three each\n * write their own `.gitignore` containing `*`, so they need nothing from here.\n * CPython does not, which is why `__pycache__/` is the one that leaks.\n *\n * Measured on a real generated workspace rather than assumed: after\n * `nx run-many -t lint,typecheck,test`, `git status` named exactly the two\n * `__pycache__/` directories and nothing else.\n *\n * `.venv/` is NOT here. mnci invokes the Python toolchain as `python3 -m <tool>`\n * and never creates a virtualenv, so ignoring one would be a guess about how\n * someone works rather than a fact about what this generates.\n */\nconst PYTHON_ARTEFACT_IGNORES = ['__pycache__/', '*.py[cod]'] as const\n\n/**\n * Adds the Python bytecode ignores when the workspace has a Python project.\n *\n * @remarks\n * `create-nx-workspace`'s `.gitignore` is a JavaScript template: `dist`, `tmp`,\n * `out-tsc`, `node_modules`, and not one line about any other language. That was\n * fine while mnci only scaffolded JavaScript, and has been wrong since it\n * scaffolded Python - the first `git add -A` after running a Python project's\n * own targets commits its bytecode.\n *\n * Found from the outside: a repository built with this CLI needed a hand-written\n * commit to add these lines, and `.gitignore` is the one file mnci appends to\n * without owning (see {@link ensureEslintCacheIgnored}, and `react-app`'s\n * `.env` negation) - so appending here is the established shape rather than a\n * new one.\n *\n * Conditional on a Python project existing, unlike `.eslintcache`, which every\n * workspace produces. A pure-JavaScript workspace has no business carrying\n * ignores for a language it does not use.\n *\n * Idempotent: `mnci upgrade` runs this on every existing workspace, and lines\n * already present - added by hand or by a previous upgrade - are left alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensurePythonArtefactsIgnored (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n // The same three roots every other Python-aware guard here scans.\n const hasPython =\n globSync(\n ['python-packages/*/pyproject.toml', 'apps/*/pyproject.toml', 'libs/*/pyproject.toml'],\n { cwd: workspaceRoot },\n ).length > 0\n if (!hasPython) {\n return\n }\n\n const current = readFileSync(gitignorePath, 'utf8')\n const present = new Set(current.split('\\n').map(line => line.trim()))\n const missing = PYTHON_ARTEFACT_IGNORES.filter(line => !present.has(line))\n if (missing.length === 0) {\n return\n }\n\n const withoutTrailingBlankLines = current.replace(/\\n+$/u, '')\n const separator = withoutTrailingBlankLines.length > 0 ? '\\n\\n' : ''\n const heading = [\n '# Added by MoNecromanCI: Python bytecode. The tool caches (.mypy_cache,',\n '# .pytest_cache, .ruff_cache) write their own .gitignore; CPython does not.',\n ].join('\\n')\n writeFileEnsured(\n gitignorePath,\n `${withoutTrailingBlankLines}${separator}${heading}\\n${missing.join('\\n')}\\n`,\n )\n}\n\n/**\n * Deletes every per-project ESLint config in the workspace.\n *\n * @remarks\n * An mnci workspace has exactly ONE ESLint config, at the root. `mnci add`\n * already deletes the one its generator just wrote\n * (`removeGeneratedEslintConfig` in `add/shared.ts`), but that only helps\n * projects created from now on.\n *\n * This is the migration path for workspaces generated **before** mnci owned\n * linting, which is the case that matters: they carry a config in every\n * `apps/*`, `libs/*` and `packages/*` directory, and without this an\n * `mnci upgrade` would install the root config while leaving each project\n * still linting itself against its own stale rules — the exact fragmentation\n * the root config exists to end. Verified against a real workspace: an\n * upgrade fixed every root file and left `packages/sdk/eslint.config.mjs`\n * behind until this was added.\n *\n * Only the three conventional project directories are swept, never the whole\n * tree, so a config a user deliberately placed elsewhere is left alone. The\n * root config is never matched — these globs are all one level deep inside a\n * project directory.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeProjectEslintConfigs (workspaceRoot: string): void {\n const matches = globSync('{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}', {\n cwd: workspaceRoot,\n })\n for (const match of matches) {\n rmSync(join(workspaceRoot, match), { force: true })\n }\n}\n\n/**\n * Applies MoNecromanCI's opinions on top of a freshly generated workspace.\n *\n * @remarks\n * This is the ONLY file-writing this CLI does — everything else in the\n * workspace is the untouched output of Nx's own generators. Writes: the\n * `nx.json` release patch, `eslint.config.mjs`, `.npmrc`,\n * `commitlint.config.mjs`, the husky\n * `commit-msg` hook, the `<workspace>.code-workspace` file and\n * the chosen CI provider's pipeline file(s) — `azure-pipelines.yml` and/or\n * `.github/workflows/ci.yml`, per `options.ci`. It also DELETES the Nx\n * scaffolding it replaces ({@link removeNxScaffolding}). Dependency\n * installation (`husky`, `@commitlint/*`) is the caller's job — it shells out\n * to real `npm install` so versions resolve at generation time instead of\n * being pinned here.\n *\n * @param workspaceRoot - Absolute path to the generated workspace.\n * @param options - The scope, registry, CI agent/variable group and provider chosen.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error raised while reading or writing.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function applyOverlay (\n workspaceRoot: string,\n options: OverlayOptions,\n onProgress: (message: string) => void = () => {},\n): void {\n // Patch nx.json with the release opinion, the stack generator defaults, the\n // shared global inputs (so `nx affected` on a PR is not blind to the root\n // config files — see SHARED_GLOBAL_INPUTS) and sync.applyChanges (so a stale\n // TS project reference — e.g. from hand-adding a cross-project import — is\n // fixed automatically on the next build/typecheck, not just flagged with a\n // prompt). Both `nx release` and every later `nx g`/`mnci add` see the\n // generator defaults.\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const generators = {\n ...(nxJson.generators as Record<string, unknown> | undefined),\n ...generatorDefaults(options.stack),\n }\n const sync = { ...(nxJson.sync as Record<string, unknown> | undefined), ...SYNC_CONFIG }\n const mnci = { ...(nxJson.mnci as Record<string, unknown> | undefined), ...mnciConfig(options) }\n /*\n * Read from the root manifest rather than assumed: the Python plugin arrives\n * only when `mnci add python-*` installs it, and registering a plugin Nx\n * cannot resolve is a hard failure of the whole project graph.\n */\n const rootManifest = readJson<{\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }>(join(workspaceRoot, 'package.json'))\n const hasPythonPlugin =\n (rootManifest.devDependencies ?? {})['@mnci/nx-python-pip'] !== undefined ||\n (rootManifest.dependencies ?? {})['@mnci/nx-python-pip'] !== undefined\n const withRelease = withReleaseConfig(nxJson, options.ci)\n const patched = withPythonGraphPlugin(\n withSharedGlobals(withEslintPlugin(withRelease)),\n hasPythonPlugin,\n )\n onProgress('nx.json — release, sync, generators, shared inputs, mnci block')\n writeFileEnsured(nxJsonPath, toJson({ ...patched, generators, sync, mnci }))\n\n // The preset names the root package a placeholder ('@org/source'); stamp the\n // chosen scope so `add npm-lib` can derive the default import path from it,\n // the curated everyday scripts (each a single cross-platform command, with\n // `lint` bound to the chosen linter), and the dual TS compiler — the alias\n // for `typescript` replaces the plain TS 6 the preset installed, and\n // `@typescript/native` adds the TS 7 `tsc`. The caller's `npm install`\n // materialises them.\n onProgress('package.json — root scripts, TS compilers, ESLint toolchain, overrides')\n const manifestPath = join(workspaceRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const scripts = {\n ...(manifest.scripts as Record<string, string> | undefined),\n ...rootScripts(),\n }\n const existingDevDeps = manifest.devDependencies as Record<string, string> | undefined\n // Dropped here as well as in `removeLocalRegistryScaffolding`, because this\n // merge is what carries an existing workspace's devDependencies across an\n // upgrade - so without this `mnci upgrade` would delete `.verdaccio/` and\n // leave the dependency that populates it.\n const devDeps = withoutKey(\n withoutRetiredFormatterDependencies({\n ...existingDevDeps,\n ...TS_COMPILER_DEPENDENCIES,\n // The preset pins `nx` itself; the ESLint plugins must match it exactly.\n ...eslintToolchainDependencies(existingDevDeps?.nx ?? 'latest'),\n }),\n LOCAL_REGISTRY_SCAFFOLDING.devDependency,\n )\n // Merged, never replaced: a workspace's own overrides must survive an upgrade.\n const overrides = {\n ...(manifest.overrides as Record<string, unknown> | undefined),\n ...ESLINT_PEER_OVERRIDES,\n ...SECURITY_OVERRIDES,\n // Static, so it is present before any generator can run an install — see\n // NX_PEER_OVERRIDES for why the conditional form could never be.\n ...NX_PEER_OVERRIDES,\n }\n // The root project's own Nx config. Merged the same way, so a workspace that\n // added root targets of its own keeps them — see ROOT_LINT_TARGET for why\n // `includedScripts` must stay empty.\n const existingNx = manifest.nx as Record<string, unknown> | undefined\n // Same reason as the devDependency above: the merge is what would otherwise\n // carry the target across an upgrade, pointing at a config file this run has\n // just deleted.\n const existingTargets = withoutKey(\n (existingNx?.targets as Record<string, unknown> | undefined) ?? {},\n LOCAL_REGISTRY_SCAFFOLDING.rootTarget,\n )\n const nx = {\n ...existingNx,\n includedScripts: (existingNx?.includedScripts as unknown[] | undefined) ?? [],\n targets: {\n ...existingTargets,\n lint: ROOT_LINT_TARGET,\n },\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n name: `${options.scope}/source`,\n scripts,\n devDependencies: devDeps,\n overrides,\n nx,\n }),\n )\n\n onProgress(\n `.npmrc — ${\n options.registry.kind === 'azure-artifacts'\n ? 'Azure Artifacts feed routing and credentials'\n : 'public npm registry auth'\n }`,\n )\n writeFileEnsured(\n join(workspaceRoot, '.npmrc'),\n npmrcContent(options.registry, options.scope, options.npmAuth),\n )\n onProgress('commitlint.config.mjs and .husky/commit-msg — conventional commit enforcement')\n writeFileEnsured(join(workspaceRoot, 'commitlint.config.mjs'), COMMITLINT_CONFIG)\n const hookPath = join(workspaceRoot, '.husky/commit-msg')\n writeFileEnsured(hookPath, COMMIT_MSG_HOOK)\n markExecutable(hookPath)\n // ESLint handles code quality, type-aware correctness AND formatting\n // (JavaScript Standard Style) — one tool, at the root. `add` deletes the\n // per-project configs Nx generators write.\n //\n // TWO files, and only the first is ours. `eslint.config.mnci.mjs` carries\n // every rule and is rewritten here on every upgrade; `eslint.config.mjs` is\n // what ESLint actually loads, imports that one, and holds the workspace's\n // own blocks — so it is written once and then left alone forever. Rewriting\n // it is what used to delete a user's overrides on upgrade, silently, having\n // told them in a comment that appending was how overriding worked.\n onProgress(`${ESLINT_MNCI_FILENAME} — the shared lint AND formatting opinion`)\n writeFileEnsured(join(workspaceRoot, ESLINT_MNCI_FILENAME), ESLINT_MNCI_CONFIG)\n writeEslintEntryPoint(workspaceRoot, onProgress)\n // `format`/`lint`'s `--cache` flag writes a root .eslintcache that\n // create-nx-workspace's own .gitignore template has no reason to know\n // about — see ensureEslintCacheIgnored.\n ensureEslintCacheIgnored(workspaceRoot)\n // Same file, same reason, different language: create-nx-workspace's template\n // says nothing about Python because it is a JavaScript template.\n ensurePythonArtefactsIgnored(workspaceRoot)\n // Every config a previous mnci version could have written for a second tool\n // has to be REMOVED, not merely left unwritten. A stale `.prettierrc.mjs` or\n // `.oxfmtrc.json` does nothing on its own now that neither binary runs, but a\n // globally installed `esbenp.prettier-vscode` or `oxc.oxc-vscode` resolves it\n // and reformats on save against an opinion no gate checks — silently undoing\n // Standard in the editor while CI stays green. `mnci upgrade` runs this same\n // path, so migrating an existing workspace is one command.\n for (const retired of RETIRED_FORMATTER_FILES) {\n removeIfPresent(join(workspaceRoot, retired))\n }\n // Makes a local environment match the one CI verifies — see devcontainerJson.\n onProgress('.devcontainer/devcontainer.json — a local toolchain matching CI')\n writeFileEnsured(\n join(workspaceRoot, '.devcontainer/devcontainer.json'),\n devcontainerJson(options.workspaceName),\n )\n removeNxScaffolding(workspaceRoot)\n // VS Code workspace file with folder structure, extensions, and settings. The\n // `tasks` array is read back first and carried through: it is per-project state\n // written by `mnci add`, not overlay-owned, so regenerating it wholesale would\n // wipe every registered build/qa/start task on `mnci upgrade`.\n onProgress(\n `${options.workspaceName}.code-workspace — settings, extensions, launch configs`,\n )\n const codeWorkspacePath = join(workspaceRoot, `${options.workspaceName}.code-workspace`)\n const existing = readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n launch?: { version?: string; configurations?: Record<string, unknown>[] }\n settings?: Record<string, unknown>\n }>(codeWorkspacePath)\n writeFileEnsured(\n codeWorkspacePath,\n vscodeWorkspace(\n options.workspaceName,\n existing?.tasks,\n existing?.launch,\n existing?.settings,\n ),\n )\n // Repairs mnci's own past bug rather than tidying: `mnci upgrade` used to pass\n // no `workspaceName` at all, so this write landed on the literal filename\n // `undefined.code-workspace` and the workspace's real one was never refreshed.\n // Any workspace upgraded before that fix still carries the junk file, and only\n // an upgrade can clear it. Guarded on the name so a workspace genuinely called\n // `undefined` does not delete its own file.\n if (options.workspaceName !== 'undefined') {\n rmSync(join(workspaceRoot, 'undefined.code-workspace'), { force: true })\n }\n // Either or both, per the chosen provider — a GitHub-hosted repo can skip\n // the unused Azure file entirely instead of carrying dead CI config.\n const publishUrl = pythonPublishUrl(options.registry)\n const nugetUrl = nugetFeedUrl(options.registry)\n if (options.ci === 'azure' || options.ci === 'both') {\n onProgress('azure-pipelines.yml — build, verify, pack and release')\n writeFileEnsured(\n join(workspaceRoot, 'azure-pipelines.yml'),\n azurePipelinesYaml(\n options.agent,\n options.variableGroup,\n publishUrl,\n options.registry.kind,\n nugetUrl,\n options.npmAuth,\n ),\n )\n }\n if (options.ci === 'github' || options.ci === 'both') {\n onProgress('.github/workflows/ci.yml and dependabot.yml')\n writeFileEnsured(\n join(workspaceRoot, '.github/workflows/ci.yml'),\n githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci, nugetUrl),\n )\n writeFileEnsured(join(workspaceRoot, '.github/dependabot.yml'), dependabotConfig(workspaceRoot))\n }\n}\n","import { existsSync, readFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { NX_PEER_OVERRIDES, dependabotConfig, ensurePythonArtefactsIgnored } from '../workspace-overlay'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * The HTTP frameworks `@nx/node:application` can scaffold a `node-app` with.\n *\n * @remarks\n * The generator's own `--framework` choices (verified empirically against a\n * real Nx 23.1.0 workspace: `nx g @nx/node:application --help`), passed\n * straight through — `node.ts` adds no framework-specific logic of its own.\n * `none` (the default) is a bare Node app with no HTTP framework opinion.\n * `node-function-app` never accepts this: the Azure Functions v4 programming\n * model (`app.http(...)` registration) runs its own request lifecycle, so a\n * full HTTP server framework doesn't apply there.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type NodeFramework = 'express' | 'fastify' | 'koa' | 'nest' | 'none'\n\n/**\n * Options accepted by `runAdd`.\n *\n * @remarks\n * Mirrors the CLI's flags. Defined here (not in `add.ts`) because every\n * per-kind module (`react-app.ts`, `function-app.ts`, `npm-lib.ts`) needs it;\n * `add.ts` re-exports it so its existing public import path\n * (`import { type AddOptions } from './commands/add'`, used by `cli.ts`)\n * keeps working unchanged.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface AddOptions {\n /** npm scope for a publishable lib's import path (defaults to `@<workspace name>`). */\n scope?: string\n /** `node-app` only: the HTTP framework `@nx/node:application` scaffolds (defaults to `none`, a bare Node app). */\n framework?: NodeFramework\n /** `python-vendor` only: the internal Python library (under `libs/`) to vendor into `name`. */\n lib?: string\n /** `vscode-extension` only: the Marketplace publisher id (defaults to the workspace scope without `@`). */\n publisher?: string\n /** `vscode-extension` only: a `go-app` whose six-platform build ships inside the extension. */\n sidecar?: string\n}\n\n/**\n * The workspace stack, generator-facing shape (what `readWorkspaceStack` in\n * `add.ts` resolves and every plugin-generated kind consumes).\n *\n * @remarks\n * Only testRunner is configurable; linting and formatting are always ESLint.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface WorkspaceStack {\n testRunner: 'jest' | 'vitest'\n}\n\n/**\n * Whether a plugin package is already declared in the workspace's manifest.\n *\n * @remarks\n * Keeps repeat `add` calls fast by skipping the install step.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/react`).\n * @returns `true` when the package is a dependency or devDependency.\n * @throws Propagates any `fs`/JSON error reading the root manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasPlugin (workspaceRoot: string, packageName: string): boolean {\n const manifest = readJson<{\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }>(join(workspaceRoot, 'package.json'))\n const installed = { ...manifest.dependencies, ...manifest.devDependencies }\n\n return Object.hasOwn(installed, packageName)\n}\n\n/**\n * Ensures an Nx plugin is installed in the workspace, installing it on first use.\n *\n * @remarks\n * `nx add` installs the package and runs its init generator — the Nx-native way\n * to bring a plugin into an existing workspace. Shared by every kind whose\n * generator lives in a plugin the workspace may not have yet (`react-app`,\n * `react-lib`, `react-internal-lib`).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/react`).\n * @returns Nothing.\n * @throws Error when the underlying `nx add` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensurePlugin (workspaceRoot: string, packageName: string): void {\n if (hasPlugin(workspaceRoot, packageName)) {\n return\n }\n logger.step(`Installing Nx plugin ${packageName}`)\n runNx(['add', packageName], workspaceRoot)\n}\n\n/**\n * Runs an Nx generator, then applies mnci's own post-generation repairs even\n * when the generator's underlying install step failed.\n *\n * @remarks\n * `@nx/js:lib --bundler=rollup`/`@nx/react:library` both fetch and install a\n * plugin package (`@nx/rollup`, `@nx/vitest`, …) as part of the generator run\n * the FIRST time a workspace uses that bundler or test runner, via Nx's own\n * `installPackagesTask`. That install can fail for reasons that have nothing\n * to do with the generator's own correctness — reproduced end to end with a\n * real npm 10.9.7 arborist bug on this exact dependency tree\n * (`Cannot read properties of null (reading 'edgesOut')`, gone on npm 11) —\n * and when it does, `runNx` throws AFTER the generator has already written\n * every scaffold file to disk. Without this wrapper, every caller's own\n * post-generation repairs (`repairPublishableManifest`,\n * `repairDeclarationSpecifiers`, `registerProjectCommands`, …) never run,\n * silently, because the exception unwinds straight out of `addNpmLib`/\n * `addReactLib` — leaving a project with the broken `types` path and the\n * unrepaired source-map/declaration-extension defects those repairs exist to\n * fix, and `mnci doctor` had nothing that could point at it: the target\n * files exist, they are just wrong. Reproduced and fixed together.\n *\n * The distinguishing signal is `markerPath`: a file the generator itself\n * writes (its manifest) as one of its very first acts, well before the\n * install step runs. If it is missing, the generator failed before writing\n * anything repairable, and this rethrows the original error unchanged rather\n * than running repairs against a directory that does not exist. If it is\n * present, the scaffold is real regardless of whether install succeeded, so\n * `repair` runs unconditionally — and if install did fail, the original\n * error is rethrown afterwards, with a clearer, actionable message, so the\n * command still exits non-zero rather than reporting a false success.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param nxArguments - The Nx CLI arguments, exactly as `runNx` would take them.\n * @param markerPath - Absolute path to a file the generator writes early,\n * used to tell \"failed before writing anything\" apart from \"failed after\".\n * @param repair - Every post-generation repair this kind normally runs on\n * success; now run whenever the scaffold exists, success or not.\n * @returns Nothing.\n * @throws The original `runNx` error, unchanged, when `markerPath` was never\n * written. A new, clearer error wrapping it, after `repair` has still run,\n * when `markerPath` exists but the generator's install step failed anyway.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runGeneratorAndRepair (\n workspaceRoot: string,\n nxArguments: string[],\n markerPath: string,\n repair: () => void,\n): void {\n let installFailure: Error | undefined\n try {\n runNx(nxArguments, workspaceRoot)\n } catch (error) {\n installFailure = error instanceof Error ? error : new Error(String(error))\n }\n\n if (!fileExists(markerPath)) {\n throw installFailure ?? new Error(`unreachable: runNx did not throw and ${markerPath} is missing`)\n }\n\n repair()\n\n if (installFailure) {\n throw new Error(\n 'The project was generated and mnci\\'s own repairs were applied, but the ' +\n `underlying Nx generator's install step still failed (${installFailure.message}). ` +\n \"Fix the install error above (commonly resolved by 'npm install' with a newer \" +\n \"npm major), then verify with 'mnci doctor'.\",\n )\n }\n}\n\n/**\n * Sets `\"private\": true` in a package manifest.\n *\n * @remarks\n * One of the deliberate post-generation touches: it makes an internal library\n * structurally unpublishable, no matter what future config drifts. Shared by\n * every private-lib kind (`internal-lib`, `react-internal-lib`).\n *\n * @param manifestPath - Absolute path to the lib's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markPrivate (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }))\n}\n\n/**\n * Sets `publishConfig.access: \"public\"` in a package manifest.\n *\n * @remarks\n * npm treats every scoped package (`@scope/name` — what every publishable lib's\n * `importPath` always is) as private by default: an unmodified first publish\n * fails with `402 Payment Required — You must sign up for private packages`\n * (verified empirically against the real registry), not with anything a dry-run\n * surfaces, since dry-runs never call the registry. This is the one\n * post-generation touch that makes a freshly added publishable lib publishable\n * as-is. Shared by `npm-lib` and `react-lib`.\n *\n * @param manifestPath - Absolute path to the lib's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markPublic (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: 'public' } }))\n}\n\n/**\n * Replaces the stock README the Nx generators write.\n *\n * @remarks\n * Theirs credits Nx, which did not generate this project - mnci did, delegating one\n * step to an Nx generator. It also names the project by its directory rather than by\n * the package name the workspace actually publishes.\n *\n * The directory form is NOT broken: Nx resolves `nx build secrets` to `@auto/secrets`\n * happily. Checked, because the opposite was assumed first. Naming the package is\n * simply the less ambiguous of two working forms, and the one that matches what\n * `nx show projects` prints.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @param projectName - The Nx project name, which is the package name.\n * @param testRunner - The workspace test runner, named in the test command.\n * @returns Nothing.\n * @throws Propagates any `fs` error raised while writing.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function writeProjectReadme (\n projectRoot: string,\n projectName: string,\n testRunner: WorkspaceStack['testRunner'],\n): void {\n const runner = testRunner === 'vitest' ? '[Vitest](https://vitest.dev)' : '[Jest](https://jestjs.io)'\n writeFileEnsured(\n join(projectRoot, 'README.md'),\n [\n `# ${projectName}`,\n '',\n 'Generated by [MoNecromanCI](https://github.com/russoedu/MoNecromanCi).',\n '',\n '## Building',\n '',\n `Run \\`nx build ${projectName}\\` to build this project.`,\n '',\n '## Running unit tests',\n '',\n `Run \\`nx test ${projectName}\\` to execute the unit tests via ${runner}.`,\n '',\n ].join('\\n'),\n )\n}\n\n/**\n * Removes the `.gitkeep` a directory no longer needs.\n *\n * @remarks\n * `create-nx-workspace` drops one into `packages/` and `libs/` so git tracks them\n * while empty. Once a real project lands there the file is not just redundant, it is\n * misleading - it says \"this directory is empty\" in a directory that is not.\n *\n * Swept across every scaffold directory rather than the one an `add` just wrote to,\n * so a workspace that predates this picks up the tidy on its next add regardless of\n * which kind was added.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a missing file is the expected case on every later add.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeStaleGitkeeps (workspaceRoot: string): void {\n for (const scaffold of ['apps', 'libs', 'packages']) {\n const directory = join(workspaceRoot, scaffold)\n try {\n const holdsAProject = readdirSync(directory, { withFileTypes: true }).some((entry) =>\n entry.isDirectory(),\n )\n if (holdsAProject) {\n rmSync(join(directory, '.gitkeep'), { force: true })\n }\n } catch {\n // The scaffold directory does not exist in this workspace; nothing to sweep.\n }\n }\n}\n\n/**\n * Ensures the `adm-zip` packager is a workspace devDependency.\n *\n * @remarks\n * Each app's `package` target zips its build output with `adm-zip` (pure JS,\n * cross-platform, no native build) so CI can pack apps on any agent OS. Shared\n * by every app kind that packages its own output (react-app, the Python\n * kinds); the function-app path folds the same install into its larger one.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureAdmZip (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, 'adm-zip')) {\n return\n }\n logger.step('Installing the app packager (adm-zip)')\n if (\n runShell(\n 'npm',\n ['install', '--save-dev', 'adm-zip', '--no-audit', '--no-fund'],\n workspaceRoot,\n ) !== 0\n ) {\n throw new Error('npm install of adm-zip failed')\n }\n}\n\n/**\n * Derives the default npm scope from the workspace's root package name.\n *\n * @remarks\n * Shared by the function-app and npm-lib kinds — both fall back to the\n * workspace's own scope when no `--scope` is given.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The scope, e.g. `@demo` for a workspace named `demo` (or\n * `@demo/source`-style names produced by some presets).\n * @throws Propagates any `fs`/JSON error reading the root manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function defaultScope (workspaceRoot: string): string {\n const { name } = readJson<{ name: string }>(join(workspaceRoot, 'package.json'))\n const base = (name.startsWith('@') ? name.slice(1) : name).split('/', 1)[0]\n\n return `@${base}`\n}\n\n/**\n * Merges extra Nx targets into an inference-only app via its manifest `nx` field.\n *\n * @remarks\n * Apps generated by `@nx/react:app`/`@nx/node:application` have no\n * `project.json` (targets are inferred), so extra targets (e.g. per-environment\n * builds, a `package` target) are attached through the package.json `nx`\n * field — merged with the inferred targets, and free of the project-name\n * clash a second `project.json` would risk in a TS-solution workspace. Shared\n * by every inference-only app kind that adds targets this way (react-app,\n * node-app, node-function-app).\n *\n * @param manifestPath - Absolute path to the app's `package.json`.\n * @param newTargets - The targets to merge in.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNxTargets (manifestPath: string, newTargets: Record<string, unknown>): void {\n // The generator always writes this manifest first (runAdd throws otherwise);\n // defaulting to {} only guards the pathological missing-file case.\n const manifest = fileExists(manifestPath) ? readJson<Record<string, unknown>>(manifestPath) : {}\n const nx = (manifest.nx as Record<string, unknown> | undefined) ?? {}\n const targets = (nx.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(\n manifestPath,\n toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } }),\n )\n}\n\n/**\n * Merges extra targets into a project's `project.json`, creating it if the\n * plugin that generated the project wrote none.\n *\n * @remarks\n * Was identical, hand-duplicated code in `go.ts`, `python.ts` and\n * `flutter.ts` — each of those plugins writes a real `project.json` (`go`'s\n * with an empty `targets` map, since `@nx-go/nx-go`'s own inference needs a\n * per-project `go.mod` mnci's single-root-module layout does not have; Python\n * and Flutter's carry their own lint/test/build already). Extracted once a\n * fourth caller needed it: `@nx/dotnet` is inference-only and writes no\n * `project.json` at all, so `csharp.ts` needs this to also CREATE one — the\n * one behavioural difference from the three hand-duplicated originals, which\n * could assume the file already existed. Tolerating a missing file is a\n * strict widening: the three existing callers are unaffected, since their\n * generators always write the file first.\n *\n * @param projectJsonPath - Absolute path to the project's `project.json`.\n * @param newTargets - The targets to merge in.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addProjectJsonTargets (\n projectJsonPath: string,\n newTargets: Record<string, unknown>,\n): void {\n const project = fileExists(projectJsonPath)\n ? readJson<Record<string, unknown>>(projectJsonPath)\n : {}\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\n}\n\n/**\n * Every ESLint flat-config filename an `@nx/*` generator might write.\n *\n * @remarks\n * Nx picks the extension from the project's module type, so all of these are\n * reachable across the kinds mnci generates.\n */\nconst ESLINT_CONFIG_FILENAMES = [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.cts',\n] as const\n\n/**\n * Deletes the per-project ESLint config an `@nx/*` generator just wrote, and\n * any `.vscode/` directory it re-created.\n *\n * @remarks\n * An mnci workspace has exactly ONE ESLint config, at the root\n * (`@mnci/eslint-config`). Every `@nx/*` generator nevertheless drops an\n * `eslint.config.mjs` into the project it creates, which re-fragments the\n * config the moment a project is added.\n *\n * Deleting them is safe, and that was verified rather than assumed: with no\n * per-project config a project still gets its inferred `lint` target from\n * `@nx/eslint/plugin` (which maps config directories to the project roots\n * beneath them), `nx lint <project>` still runs, and it still reports real\n * violations from the root config. The e2e asserts both halves permanently,\n * because a future Nx change to that inference is the one thing that would\n * silently turn linting off across a whole workspace.\n *\n * `.vscode/` is handled here too: `@nx/node` re-creates `launch.json` on every\n * `add`, so removing it once at `mnci new` would not be enough.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - The new project's path, relative to the workspace root\n * (e.g. `apps/web`).\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeGeneratedEslintConfig (workspaceRoot: string, projectRoot: string): void {\n for (const filename of ESLINT_CONFIG_FILENAMES) {\n rmSync(join(workspaceRoot, projectRoot, filename), { force: true })\n }\n rmSync(join(workspaceRoot, '.vscode'), { recursive: true, force: true })\n}\n\n/**\n * The local-dev commands a newly added project actually has, for {@link registerProjectCommands}.\n *\n * @remarks\n * `qa` (lint then test) is unconditional — every kind has both — so it is not\n * a field here. `build` and `start` vary by kind: several (`go-lib`,\n * `python-internal-lib`, `flutter-lib`, ...) have no build target at all, and\n * only kinds with a genuine local dev-server story get `start` — never a\n * library, and not `go-function-app` (no Azure Functions custom-handler\n * wiring exists for Go yet, so a `func start` script would just fail).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface ProjectCommands {\n /** Whether this kind has a `build` Nx target — adds `<name>:build` when true. */\n build: boolean\n /**\n * The exact command for `<name>:start` (e.g. `nx run <name>:serve`,\n * `nx run <name>:start`) — omitted entirely when the kind has no local\n * dev-server story.\n */\n start?: string\n}\n\n/**\n * Finds the workspace's single `<name>.code-workspace` file.\n *\n * @remarks\n * Its filename is the workspace name, which `add/*.ts` call sites don't\n * otherwise track past `mnci new` — cheaper to look it up by extension than\n * to thread the name through every kind module.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The absolute path, or `undefined` when none exists (a workspace\n * predating this file, or a test fixture that never wrote one).\n * @throws Never - a missing/unreadable directory yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findCodeWorkspaceFile (workspaceRoot: string): string | undefined {\n try {\n const entry = readdirSync(workspaceRoot).find(file => file.endsWith('.code-workspace'))\n\n return entry ? join(workspaceRoot, entry) : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * One VS Code task for a project's script, matching its root `package.json` entry.\n *\n * @remarks\n * `start` tasks run a dev server that never exits on its own, so they are\n * marked `isBackground` (VS Code won't wait for them to finish) rather than\n * given a `group` — `build`/`qa` do exit, so they get the matching group\n * VS Code's Command Palette/Tasks menu groups them under.\n *\n * @param name - The project name.\n * @param kind - Which of the three commands this task runs.\n * @returns The VS Code task object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction projectTask (name: string, kind: 'build' | 'qa' | 'start'): Record<string, unknown> {\n const script = `${name}:${kind}`\n const base = { label: `${name}: ${kind}`, type: 'npm', script, problemMatcher: [] }\n\n return kind === 'start' ? { ...base, isBackground: true } : { ...base, group: kind }\n}\n\n/**\n * Renames `@nx/js:lib`'s placeholder to a name mnci's own lint rule accepts.\n *\n * @remarks\n * `@nx/js:lib` scaffolds `src/lib/<name>.ts` — a bare name with no role suffix —\n * and `vertical-slices/file-role`, which ships in `@mnci/eslint-config`, rejects\n * it on the first lint:\n *\n * ```\n * A production file ends in its role - .handler .use-case .algorithm ... -\n * so \"studio.ts\" says what it is vertical-slices/file-role\n * ```\n *\n * So mnci's own opinionated config failed mnci's own generator output, every\n * time, for any workspace that turned the slice rules on. Reproduced on a\n * freshly generated workspace: of the 14 problems `eslint` reports on a new\n * `npm-lib`, 13 are stylistic and `--fix` away — this one cannot, because the\n * only fix is a rename.\n *\n * `use-case` is the role: it is the generic one for a library's public\n * behaviour, and it is what the placeholder is standing in for.\n *\n * **Unconditional, not gated on the rule being active.** Detecting that would\n * mean parsing the workspace's own `eslint.config.mjs`, which is user-owned\n * arbitrary JavaScript, and mnci has twice shipped a correct conditional that\n * could never fire — see `NX_PEER_OVERRIDES` and `ensurePythonArtefactsIgnored`.\n * A role-suffixed placeholder costs nothing when the rule is off, and the\n * scaffold then demonstrates by example the convention the config enforces.\n *\n * Both the spec and the barrel's re-export are repointed, so the scaffold still\n * builds and its sample test still runs. A no-op when the placeholder is absent\n * or has already been renamed.\n *\n * **Both specifier forms are repointed, and the `.js` suffix is kept.** The\n * rollup `npm-lib` scaffold writes `'./lib/<name>'`, but the `tsc` `internal-lib`\n * scaffold writes ESM specifiers, `'./lib/<name>.js'`. Matching only the first\n * moved the file and left the barrel and the spec pointing at nothing, so a\n * freshly generated internal-lib failed its own `build` and `test`. The unit\n * test had faked the barrel in the bare form, and the e2e rewrote the missing file\n * itself, so neither noticed.\n *\n * `react-lib` is deliberately NOT covered: `@nx/react:library` scaffolds a\n * component, and `component` is not one of the shipped roles — renaming it to\n * `use-case` would make the name lie. A workspace linting React with these\n * rules passes `roles: ['component']` itself.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @param name - The project name the generator used for the placeholder.\n * @returns Nothing.\n * @throws Propagates any `fs` error renaming or rewriting the files.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function renameScaffoldPlaceholder (projectRoot: string, name: string): void {\n const placeholder = join(projectRoot, 'src', 'lib', `${name}.ts`)\n if (!existsSync(placeholder)) {\n return\n }\n renameSync(placeholder, join(projectRoot, 'src', 'lib', `${name}.use-case.ts`))\n\n const spec = join(projectRoot, 'src', 'lib', `${name}.spec.ts`)\n if (existsSync(spec)) {\n const renamed = join(projectRoot, 'src', 'lib', `${name}.use-case.spec.ts`)\n renameSync(spec, renamed)\n writeFileEnsured(\n renamed,\n repointSpecifier(readFileSync(renamed, 'utf8'), `./${name}`, `./${name}.use-case`),\n )\n }\n\n // The barrel re-exports `./lib/<name>`; without this the package has no\n // entry point at all, which the build would catch but only after the fact.\n const barrel = join(projectRoot, 'src', 'index.ts')\n if (existsSync(barrel)) {\n writeFileEnsured(\n barrel,\n repointSpecifier(readFileSync(barrel, 'utf8'), `./lib/${name}`, `./lib/${name}.use-case`),\n )\n }\n}\n\n/**\n * Repoints a module specifier in generated source, in both forms a scaffold uses.\n *\n * @remarks\n * `'<from>'` (rollup scaffolds) and `'<from>.js'` (the `tsc` scaffold's ESM\n * specifiers) both move to `<to>`, and the `.js` suffix is kept when it was\n * there, because it is what ESM resolution needs. The closing quote is part of\n * the match, so a sibling such as `'<from>-extra'` is left alone, and a specifier\n * already repointed does not match again.\n *\n * @param source - The generated file's text.\n * @param from - The specifier to replace, without quote or suffix (e.g. `./lib/utils`).\n * @param to - The specifier to use instead.\n * @returns The rewritten text.\n * @throws Never - pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repointSpecifier (source: string, from: string, to: string): string {\n return source\n .replaceAll(`${from}'`, () => `${to}'`)\n .replaceAll(`${from}.js'`, () => `${to}.js'`)\n}\n\n/**\n * Writes the `@nx/*` peer overrides into the root manifest before any generator runs.\n *\n * @remarks\n * Called at the top of every `mnci add`, before the switch that invokes a\n * generator, because the generators install as they scaffold: the express node\n * app's generator adds express to the root manifest and runs `npm install` in\n * one invocation, and adding the React plugin installs it into a workspace that\n * may already have express. Either one is the install that hits the stale\n * express peer, so the override has to be on disk first — writing it afterwards,\n * as mnci used to, is always too late.\n *\n * This is what makes `mnci add` work on a workspace generated before the fix,\n * without making the user run `mnci upgrade` first. Merged, never replaced, so a\n * workspace's own overrides survive. Idempotent: re-running writes the same\n * bytes, and the manifest is left untouched when nothing would change, so an\n * `add` that needs no override does not dirty the file.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureNxPeerOverrides (workspaceRoot: string): void {\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!existsSync(manifestPath)) {\n return\n }\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const existing = (manifest.overrides as Record<string, unknown> | undefined) ?? {}\n const overrides = { ...existing, ...NX_PEER_OVERRIDES }\n if (JSON.stringify(existing) === JSON.stringify(overrides)) {\n return\n }\n writeFileEnsured(manifestPath, toJson({ ...manifest, overrides }))\n}\n\n/**\n * Registers a newly added project's local-dev commands: root `package.json`\n * scripts, and matching VS Code tasks in the workspace's `.code-workspace` file.\n *\n * @remarks\n * Every `add/*.ts` kind function calls this once it has finished generating\n * and target-wiring a project, so `npm run <name>:build`/`:qa`/`:start` (and\n * the equivalent VS Code Command Palette entries) work immediately — no\n * separate `mnci upgrade` needed, since these are per-project entries, not\n * one of the fixed files `applyOverlay()` regenerates.\n *\n * `<name>:qa` (`nx run <name>:lint && nx run <name>:test`) is unconditional.\n * `<name>:build`/`<name>:start` are added only when {@link ProjectCommands}\n * says the kind actually has them. Idempotent: repeat calls for the same\n * `name` (a second `add` of the same project) overwrite rather than\n * duplicate, in both the manifest scripts and the `.code-workspace` tasks\n * array. A workspace with no `.code-workspace` file (predates it, or a test\n * fixture) still gets the `package.json` scripts — only the VS Code half is\n * skipped.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The newly added project's name.\n * @param commands - Which commands this kind actually has.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing either file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function registerProjectCommands (\n workspaceRoot: string,\n name: string,\n commands: ProjectCommands,\n): void {\n // Every `add` kind ends here, which makes this the one place the scaffold\n // `.gitkeep` files can be swept without wiring 30 call sites.\n removeStaleGitkeeps(workspaceRoot)\n // Same reason, and it has to be here rather than only in `applyOverlay`:\n // `ensurePythonArtefactsIgnored` is conditional on a Python project existing,\n // and at `mnci new` — the only other time the overlay runs — none does yet.\n // So a workspace that gained its first Python project through `mnci add` never\n // got the bytecode ignores, and the first `git add -A` after running that\n // project's targets committed its `__pycache__/`. Re-evaluated after every add\n // because this add may be the one that created the first Python project.\n // Idempotent, and a no-op for a workspace that has none.\n ensurePythonArtefactsIgnored(workspaceRoot)\n const scripts: Record<string, string> = {\n [`${name}:qa`]: `nx run ${name}:lint && nx run ${name}:test`,\n }\n if (commands.build) {\n scripts[`${name}:build`] = `nx run ${name}:build`\n }\n if (commands.start) {\n scripts[`${name}:start`] = commands.start\n }\n\n // Re-derived after every add, because whether the pip/pub blocks belong\n // depends on what projects now EXIST, and this add may have created the\n // first one. Cheap (a directory scan) and idempotent. Skipped when the\n // workspace has no dependabot.yml, i.e. it was generated with --ci=azure.\n const dependabotPath = join(workspaceRoot, '.github/dependabot.yml')\n if (existsSync(dependabotPath)) {\n writeFileEnsured(dependabotPath, dependabotConfig(workspaceRoot))\n }\n\n const manifestPath = join(workspaceRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const existingScripts = (manifest.scripts as Record<string, string> | undefined) ?? {}\n // No override sync here any more: NX_PEER_OVERRIDES is static and\n // `ensureNxPeerOverrides` writes it BEFORE the generator runs, which is the\n // only moment early enough to matter. Spreading `manifest` carries whatever\n // overrides the workspace already has.\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n scripts: { ...existingScripts, ...scripts },\n }),\n )\n\n const codeWorkspacePath = findCodeWorkspaceFile(workspaceRoot)\n if (!codeWorkspacePath) {\n return\n }\n const workspaceFile =\n readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n }>(codeWorkspacePath) ?? {}\n const label = (task: Record<string, unknown>): string => (task.label as string | undefined) ?? ''\n const existingTasks = (workspaceFile.tasks?.tasks ?? []).filter(\n task => !label(task).startsWith(`${name}: `),\n )\n const newTasks = [\n projectTask(name, 'qa'),\n ...(commands.build ? [projectTask(name, 'build')] : []),\n ...(commands.start ? [projectTask(name, 'start')] : []),\n ]\n writeFileEnsured(\n codeWorkspacePath,\n toJson({\n ...workspaceFile,\n tasks: {\n version: workspaceFile.tasks?.version ?? '2.0.0',\n tasks: [...existingTasks, ...newTasks],\n },\n }),\n )\n}\n\n/** Where a generated project's own manifest can live, in resolution order. */\nconst PROJECT_MANIFEST_ROOTS = ['apps', 'packages', 'libs'] as const\n\n/**\n * Reads the root manifest's runtime dependencies.\n *\n * @remarks\n * Snapshotted before a generator runs so {@link relocateRootRuntimeDependencies}\n * can attribute what it added.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The `dependencies` block, or an empty object when unreadable.\n * @throws Never - an unreadable manifest reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rootRuntimeDependencies (workspaceRoot: string): Record<string, string> {\n try {\n return (\n readJson<{ dependencies?: Record<string, string> }>(join(workspaceRoot, 'package.json'))\n .dependencies ?? {}\n )\n } catch {\n return {}\n }\n}\n\n/**\n * Moves runtime dependencies a generator hoisted to the root into the project.\n *\n * @remarks\n * **The root/project policy, applied rather than only checked.** `mnci doctor`\n * gained a `no runtime dependencies in the root manifest` check, and a freshly\n * generated workspace failed it: `@nx/react:application` puts `react` and\n * `react-dom` in the ROOT manifest, `--framework=express` puts `express` there,\n * and `add node-function-app` installs `@azure/functions` the same way. So mnci\n * generated workspaces its own doctor rejected — the \"a gate that fails on day\n * one\" shape this repo has hit before.\n *\n * The reason the policy matters is `@nx/rollup`, which externalises exactly what\n * a project's OWN manifest declares: a dependency left at the root is not shared,\n * it is **inlined as a private copy** into that project's published bundle. The\n * root is also `private` and never published, so a consumer of `@scope/lib` can\n * never resolve it.\n *\n * **Attribution is by diff, not by guessing what a project imports.** Whatever\n * appeared in the root's `dependencies` while this generator ran belongs to the\n * project it just generated — accurate by construction, and it needs no import\n * analysis and no per-kind list to keep in sync as kinds are added.\n *\n * A value the project already declares **wins**: `add node-function-app` stamps\n * `@azure/functions` into the app manifest at the exact installed version, which\n * is more precise than the root's range.\n *\n * The lockfile is refreshed, because moving a dependency between manifests\n * leaves it stale. Measured: npm 10.9.7 runs `npm ci` against the stale lock\n * without complaint, which is leniency rather than correctness — the lock still\n * recorded the dependency against the root — and this repo has been bitten by\n * npm 10/11 divergence three times. A failure here warns rather than throws: the\n * project is already generated, and the remedy is one `npm install`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectName - The project just generated.\n * @param before - The root `dependencies` snapshot taken before the generator ran.\n * @returns Nothing.\n * @throws Never - an unreadable or absent project manifest leaves the root alone.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function relocateRootRuntimeDependencies (\n workspaceRoot: string,\n projectName: string,\n before: Record<string, string>,\n): void {\n const after = rootRuntimeDependencies(workspaceRoot)\n const added = Object.keys(after).filter(name => !Object.hasOwn(before, name))\n if (added.length === 0) {\n return\n }\n\n const manifestPath = PROJECT_MANIFEST_ROOTS.map(root =>\n join(workspaceRoot, root, projectName, 'package.json'),\n ).find(candidate => fileExists(candidate))\n if (manifestPath === undefined) {\n // Nothing to move them INTO — a Python, Go or Dart project has no npm\n // manifest. Leaving the root untouched is the honest outcome: dropping the\n // declaration would break resolution for whatever does need it.\n return\n }\n\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const owned = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n // `owned` is spread LAST so a value the project already declares wins, and\n // that ordering is the only mechanism enforcing it — filtering `moved` as\n // well would be a second, redundant guard that makes neither testable.\n const moved = Object.fromEntries(added.map(name => [name, after[name]]))\n writeFileEnsured(manifestPath, toJson({ ...manifest, dependencies: { ...moved, ...owned } }))\n\n const remaining = Object.fromEntries(\n Object.entries(after).filter(([name]) => !added.includes(name)),\n )\n const rootManifest = readJson<Record<string, unknown>>(join(workspaceRoot, 'package.json'))\n const { dependencies: _dropped, ...rest } = rootManifest\n writeFileEnsured(\n join(workspaceRoot, 'package.json'),\n toJson(Object.keys(remaining).length > 0 ? { ...rest, dependencies: remaining } : rest),\n )\n logger.step(`Moved ${added.join(', ')} into ${projectName}'s own manifest`)\n\n if (\n runShell(\n 'npm',\n ['install', '--package-lock-only', '--no-audit', '--no-fund'],\n workspaceRoot,\n ) !== 0\n ) {\n logger.warn(\n `Could not refresh package-lock.json after moving ${added.join(', ')}. Run 'npm install'.`,\n )\n }\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { logger } from '../terminal'\nimport { addProjectJsonTargets, ensureAdmZip, hasPlugin, registerProjectCommands } from './post-generation.use-case'\n\n/**\n * The `start` target for a Flutter app: `flutter run -d chrome`, locally.\n *\n * @remarks\n * Web is the only platform this plugin builds for ({@link addFlutterApp}), so\n * `-d chrome` matches — hot-reload against the same target the `build`\n * executor produces. `continuous: true` marks it as a long-running dev-server\n * task, the same shape every other kind's custom `start` target uses.\n *\n * @param name - The Flutter app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction flutterAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'flutter run -d chrome', cwd: `apps/${name}` },\n }\n}\n\n/**\n * Fails fast, with an install hint, when the Flutter SDK is not on the PATH.\n *\n * @remarks\n * Mirrors `ensureGo` in `add/go.ts` and `ensurePython` in `add/python.ts`:\n * probed before any install or generator call, so a missing toolchain\n * surfaces as one clear sentence rather than an opaque generator crash\n * halfway through scaffolding.\n *\n * This probe matters more here than for the other stacks: Python and Go both\n * ship on every hosted CI image, whereas Flutter does not — it is the one\n * toolchain the generated pipeline has to install itself.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `flutter` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureFlutter (workspaceRoot: string): void {\n if (runShell('flutter', ['--version'], workspaceRoot) !== 0) {\n throw new Error(\n 'Flutter not found. Install the Flutter SDK first (3.27+, for Dart 3.6+ pub workspaces): https://docs.flutter.dev/get-started/install',\n )\n }\n}\n\n/**\n * The `@mnci/nx-flutter` package spec to install.\n *\n * @remarks\n * Reads `MNCI_NX_FLUTTER_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `packages/nx-flutter` in this same monorepo)\n * instead of the published registry package — the published spec is the\n * default for every real `mnci add flutter-*` call. Same escape hatch\n * `add/python.ts` uses via `MNCI2_PYTHON_PIP_SPEC`.\n *\n * @returns The npm spec to install.\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxFlutterPluginSpec (): string {\n return process.env.MNCI_NX_FLUTTER_SPEC ?? '@mnci/nx-flutter'\n}\n\n/**\n * Ensures the `@mnci/nx-flutter` Nx plugin is installed.\n *\n * @remarks\n * Like `@mnci/nx-python-pip`, this plugin needs no `nx.json` `plugins`\n * registration: its generators and executors are explicit, resolved by plain\n * Node module lookup against its `generators.json`/`executors.json`.\n * Registration in `plugins` is only for inference plugins that scan the\n * filesystem, which this is not.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureNxFlutterPlugin (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@mnci/nx-flutter')) {\n return\n }\n const spec = nxFlutterPluginSpec()\n logger.step(`Installing the Flutter toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @mnci/nx-flutter failed')\n }\n}\n\n/** Shared preflight for every Flutter kind: toolchain, then plugin. */\nfunction prepareFlutter (workspaceRoot: string): void {\n ensureFlutter(workspaceRoot)\n ensureNxFlutterPlugin(workspaceRoot)\n}\n\n/**\n * Adds a Flutter web application under `apps/`.\n *\n * @remarks\n * Pure delegation to `@mnci/nx-flutter:application`, which scaffolds the app\n * via the official `flutter create`, registers it as a member of the root pub\n * workspace, and writes the lint/test/build/package targets. `adm-zip` is\n * installed here rather than by the plugin because the `package` (zip\n * into `dist/drop/`) convention is mnci's own CI contract, not something a\n * generic Nx plugin should assume.\n *\n * Web is the only platform scaffolded — see the plugin's `application`\n * generator for why (an Android build would drag the whole Android SDK onto\n * every build agent).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterApp (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:application', name, '--no-interactive'], workspaceRoot)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n start: flutterAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a publishable Flutter/Dart package under `packages/`.\n *\n * @remarks\n * \"Publishable\" means the same thing as it does for `go-lib`: there is **no\n * registry upload**. Azure Artifacts has no pub/Dart feed type, so publishing\n * is the git tag `nx release` creates and consumers depend on that tag.\n *\n * The plugin's generator stamps a project-level `versionActions` override so\n * `nx release` versions `pubspec.yaml`. That is not cosmetic: without it Nx\n * looks for a `package.json` a Dart package does not have and fails the\n * release for the *entire* workspace.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterLib (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:library', name, '--no-interactive'], workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n\n/**\n * Adds a private Flutter/Dart package under `libs/`.\n *\n * @remarks\n * Never released — `libs/` sits outside `release.projects`. Consumers depend\n * on it with a **plain version constraint and no `path:`**: being a member of\n * the root pub workspace is what makes pub resolve it locally. That is the\n * whole point of the pub-workspace model, and it means Flutter needs no\n * vendoring step at all (contrast `mnci add python-vendor`, which exists only\n * because pip cannot bundle an unpublished sibling into a wheel).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterInternalLib (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:internal-library', name, '--no-interactive'], workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n","import { readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport { addProjectJsonTargets, ensureAdmZip, hasPlugin, registerProjectCommands } from './post-generation.use-case'\n\n/**\n * Fails fast, with an install hint, when Go is not on the PATH.\n *\n * @remarks\n * Mirrors `ensurePython` in `add/python.ts`: probed before any install or\n * generator call so a missing toolchain surfaces as one clear sentence\n * rather than an opaque generator crash.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `go` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureGo (workspaceRoot: string): void {\n if (runShell('go', ['version'], workspaceRoot) !== 0) {\n throw new Error('Go not found. Install Go 1.21+ first: https://go.dev/dl/')\n }\n}\n\n/**\n * Warns (without failing) when `golangci-lint` is not on the PATH.\n *\n * @remarks\n * Deliberately a warning, not an error: the generated `lint` target uses\n * `golangci-lint`, but a developer who only wants to build and test locally\n * should not be blocked at `add` time — CI installs it as its own step. Note\n * `@nx-go/nx-go`'s `lint` executor defaults to plain `go fmt` (formatting,\n * not linting), which is why mnci pins `golangci-lint` explicitly in the\n * target it writes.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Never - a missing linter only produces a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction warnIfNoGolangciLint (workspaceRoot: string): void {\n if (runShell('golangci-lint', ['--version'], workspaceRoot) !== 0) {\n logger.warn(\n 'golangci-lint not found — the generated lint target needs it. Install: https://golangci-lint.run/welcome/install/',\n )\n }\n}\n\n/**\n * The `@nx-go/nx-go` package spec to install.\n *\n * @remarks\n * Reads `MNCI_NX_GO_SPEC` so the e2e suite can pin or redirect the plugin\n * (e.g. to a local tarball) without touching this code; the published\n * package is the default for every real `mnci add go-*` call.\n *\n * Pinned to no particular version on purpose: `4.1.1` declares\n * `@nx/devkit \">= 20 < 23\"` while `4.1.0` declares `\">= 20 < 24\"`, but that\n * range is a plain dependency rather than a peer, so npm simply nests its\n * own devkit copy. Verified empirically against a real Nx 23.1.0 workspace:\n * generators, `build`, `test` and `lint` all work under 4.1.1.\n *\n * @returns The npm spec to install.\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxGoPluginSpec (): string {\n return process.env.MNCI_NX_GO_SPEC ?? '@nx-go/nx-go'\n}\n\n/**\n * Ensures the `@nx-go/nx-go` Nx plugin is installed.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureNxGoPlugin (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@nx-go/nx-go')) {\n return\n }\n const spec = nxGoPluginSpec()\n logger.step(`Installing the Go toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @nx-go/nx-go failed')\n }\n}\n\n/**\n * Idempotently bootstraps the workspace's single root `go.mod`.\n *\n * @remarks\n * This is the Go half of mnci's root-manifest model — the direct analogue of\n * the root `package.json` for TS and `requirements-dev.txt` for Python. Every\n * Go project in the workspace shares one module, so a library is imported as\n * `<module>/libs/<name>` with no per-project manifest and no replace\n * directives.\n *\n * The sequence is exact and order-sensitive, established empirically against\n * a real Nx 23.1.0 workspace:\n *\n * 1. `init` writes `go.work` and registers the plugin in `nx.json`.\n * 2. `convert-to-one-mod` deletes `go.work` and writes the root `go.mod`.\n *\n * Step 2 refuses to run once `go.work` contains any `use` line, so it must\n * happen before the first Go project exists — hence bootstrapping here, on\n * the first `mnci add go-*`, rather than lazily later. Skipped entirely once\n * `go.mod` exists, so repeat adds are cheap and a user's own edits (extra\n * `require` lines) survive.\n *\n * The multi-module `go.work` alternative was rejected deliberately: besides\n * splitting dependencies across per-project manifests, a single stale `use`\n * entry (a project directory removed by hand) makes `go list -m -json` fail,\n * which breaks the entire Nx project graph — not just the Go projects.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when either generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureGoModule (workspaceRoot: string): void {\n if (fileExists(join(workspaceRoot, 'go.mod'))) {\n return\n }\n logger.step('Bootstrapping the workspace Go module (single root go.mod)')\n runNx(['g', '@nx-go/nx-go:init', '--no-interactive'], workspaceRoot)\n runNx(['g', '@nx-go/nx-go:convert-to-one-mod', '--no-interactive'], workspaceRoot)\n}\n\n/**\n * The workspace's Go module path, read from the root `go.mod`.\n *\n * @remarks\n * Needed to tell a user the import path of a library that was just added.\n * `convert-to-one-mod` derives it from the root package.json name.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The module path, or `undefined` when `go.mod` is unreadable.\n * @throws Never - returns `undefined` rather than propagating a read error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goModulePath (workspaceRoot: string): string | undefined {\n try {\n // go.mod is not JSON, so it is read directly rather than via readJson.\n const contents = readFileSync(join(workspaceRoot, 'go.mod'), 'utf8')\n\n return /^module\\s+(\\S+)/m.exec(contents)?.[1]\n } catch {\n return undefined\n }\n}\n\n/**\n * The `test` target every Go project gets (`go test`).\n *\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goTestTarget (): Record<string, unknown> {\n return { executor: '@nx-go/nx-go:test' }\n}\n\n/**\n * The `lint` target every Go project gets (`golangci-lint run`).\n *\n * @remarks\n * The executor's own default is `go fmt`, which only reformats — pinning\n * `linter` to `golangci-lint` is what makes this an actual linter.\n *\n * **`parallelism: false` is not a performance knob, it is a correctness fix.**\n * `golangci-lint` takes a machine-global lock and refuses to run beside another\n * copy of itself, exiting non-zero with `parallel golangci-lint is running`. Nx\n * runs `lint` across projects concurrently by default, so a workspace with two\n * or more Go projects failed `nx run-many -t lint` at random — one project\n * reporting `0 issues` while a sibling died on the lock. Nothing to do with the\n * Go code, and the failure moves between projects from run to run, which is what\n * makes it so unpleasant to diagnose from a CI log.\n *\n * Found the first time the e2e ever ran this assertion: `golangci-lint` had\n * never been installed on the runner, so the whole check reported SKIPPED and\n * this shipped unnoticed. Go lint is now serialised across projects; the other\n * targets are untouched and still run in parallel.\n *\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goLintTarget (): Record<string, unknown> {\n return {\n executor: '@nx-go/nx-go:lint',\n parallelism: false,\n options: { linter: 'golangci-lint', args: ['run'] },\n }\n}\n\n/**\n * The `build` target for a Go executable (`go build`).\n *\n * @remarks\n * Builds to the workspace-root `dist/apps/<name>/` **directory**, with the\n * binary inside it, rather than to the executor's own default of a bare file\n * at `dist/apps/<name>`. That default cannot be declared as an Nx `outputs`\n * entry: Nx scans each declared output to cache it, and scanning a file\n * raises `ENOTDIR`. Building into a directory keeps the root-`dist`\n * convention, makes the output cacheable, and gives `package` a folder to\n * zip — verified end-to-end against a real workspace.\n *\n * `outputPath` is resolved relative to the executor's cwd (the project root),\n * hence the `../../` offset back to the workspace root.\n *\n * @param name - The project name, used for the project root in `outputs`.\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goBuildTarget (name: string): Record<string, unknown> {\n return {\n executor: '@nx-go/nx-go:build',\n outputs: [`{workspaceRoot}/dist/apps/${name}`],\n options: { outputPath: `../../dist/apps/${name}/${name}` },\n }\n}\n\n/**\n * The `package` target for a Go app: zip its built binary into the drop.\n *\n * @remarks\n * Same cross-platform `adm-zip` one-liner used by every other packaged kind,\n * writing `dist/drop/<tag>-<name>.zip` — basename exactly `<tag>-<name>`,\n * the string CI turns into the per-app build tag. A Go binary is statically\n * linked, so unlike the Python kinds there is nothing else to inject.\n *\n * Zips the whole `dist/apps/<name>/` directory {@link goBuildTarget} writes,\n * so the same one-liner works whether the binary inside is `<name>` or\n * `<name>.exe` on a Windows agent.\n *\n * @param tag - The drop basename prefix (`go-app` or `go-function-app`).\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goPackageTarget (tag: string, name: string): Record<string, unknown> {\n const zip = `dist/drop/${tag}-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('dist/apps/${name}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The platforms a Go app is cross-compiled for by its `build-all` target.\n *\n * @remarks\n * Every desktop OS on both CPU families, the set an editor extension or a CLI that\n * ships its own binary has to cover. Go builds all of them from one machine without\n * cgo, which is why the targets below need no matrix of CI agents.\n */\nexport const GO_PLATFORMS = [\n 'windows/amd64', 'windows/arm64', 'linux/amd64', 'linux/arm64', 'darwin/amd64', 'darwin/arm64',\n] as const\n\n/**\n * The `build-all` target for a Go app: one static binary per {@link GO_PLATFORMS}.\n *\n * @remarks\n * Writes `dist/platforms/<name>/<goos>-<goarch>/<name>[.exe]` with `CGO_ENABLED=0`,\n * `-trimpath` and `-ldflags \"-s -w -X main.version=<VERSION>\"`, `VERSION` coming from\n * the environment (`dev` when unset), so a release stamps its tag without editing\n * anything.\n *\n * Deliberately NOT under `dist/apps/<name>/`, though that is where `build` writes: Nx\n * clears a target's declared outputs before restoring them from cache, so a cached\n * `build` would delete the cross-compiled binaries nested inside its directory.\n *\n * `VERSION` is an input, so a release never reuses a binary stamped `dev`; the root\n * `go.mod`/`go.sum` are inputs because a dependency bump changes every binary.\n *\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goBuildAllTarget (name: string): Record<string, unknown> {\n const platforms = JSON.stringify(GO_PLATFORMS).replaceAll('\"', \"'\")\n const command = `node -e \"const{spawnSync}=require('node:child_process');const v=process.env.VERSION||'dev';for(const p of ${platforms}){const[os,arch]=p.split('/');const out='../../dist/platforms/${name}/'+os+'-'+arch+'/${name}'+(os==='windows'?'.exe':'');const r=spawnSync('go',['build','-trimpath','-ldflags','-s -w -X main.version='+v,'-o',out,'.'],{cwd:'apps/${name}',stdio:'inherit',env:{...process.env,CGO_ENABLED:'0',GOOS:os,GOARCH:arch}});if(r.status!==0)process.exit(r.status??1)}\"`\n\n return {\n executor: 'nx:run-commands',\n inputs: ['default', '^default', '{workspaceRoot}/go.mod', '{workspaceRoot}/go.sum', { env: 'VERSION' }],\n outputs: [`{workspaceRoot}/dist/platforms/${name}`],\n options: { command },\n }\n}\n\n/**\n * The `package-all` target for a Go app: one zip per platform {@link goBuildAllTarget}\n * built.\n *\n * @remarks\n * `dist/drop/<tag>-<name>-<goos>-<goarch>.zip`, each holding that platform's binary,\n * the same `adm-zip` one-liner as {@link goPackageTarget}. The single-platform\n * `package` target keeps its name, so CI's drop handling is unchanged.\n *\n * @param tag - The drop basename prefix (`go-app` or `go-function-app`).\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goPackageAllTarget (tag: string, name: string): Record<string, unknown> {\n const command = `node -e \"const fs=require('node:fs');const A=require('adm-zip');fs.mkdirSync('dist/drop',{recursive:true});for(const d of fs.readdirSync('dist/platforms/${name}')){const z=new A();z.addLocalFolder('dist/platforms/${name}/'+d);z.writeZip('dist/drop/${tag}-${name}-'+d+'.zip')}\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build-all'],\n outputs: [`{workspaceRoot}/dist/drop/${tag}-${name}-*.zip`],\n options: { command },\n }\n}\n\n/**\n * Gives every Go app in the workspace its `build-all` and `package-all` targets when it\n * lacks them: the `mnci upgrade` path for apps added before the targets existed.\n *\n * @remarks\n * Only adds; a target the user already has, under either name, is never touched.\n * Idempotent, so a repeat upgrade is a no-op.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The `project.json` files it changed, workspace-relative.\n * @throws Error when a Go app's `project.json` is not valid JSON.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoPlatformTargets (workspaceRoot: string): string[] {\n const changed: string[] = []\n const apps = join(workspaceRoot, 'apps')\n if (!fileExists(apps)) {\n return changed\n }\n for (const name of readdirSync(apps)) {\n const projectJsonPath = join(apps, name, 'project.json')\n if (!fileExists(projectJsonPath)) {\n continue\n }\n const project = JSON.parse(readFileSync(projectJsonPath, 'utf8')) as { tags?: string[], targets?: Record<string, unknown> }\n const tag = (project.tags ?? []).find(each => each === 'type:go-app' || each === 'type:go-function-app')?.slice('type:'.length)\n if (tag === undefined) {\n continue\n }\n const missing: Record<string, unknown> = {}\n if (project.targets?.['build-all'] === undefined) {\n missing['build-all'] = goBuildAllTarget(name)\n }\n if (project.targets?.['package-all'] === undefined) {\n missing['package-all'] = goPackageAllTarget(tag, name)\n }\n if (Object.keys(missing).length > 0) {\n addProjectJsonTargets(projectJsonPath, missing)\n changed.push(`apps/${name}/project.json`)\n }\n }\n\n return changed\n}\n\n/**\n * The `start` target for a Go app: `go run .`, locally.\n *\n * @remarks\n * `go run` compiles and runs from source in one step — unlike\n * {@link goBuildTarget}, no separate build/`dependsOn` is needed.\n * `continuous: true` marks it as a long-running dev task, the same shape\n * every other kind's custom `start` target uses.\n *\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'go run .', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The Go identifiers `@nx-go/nx-go:library` derives from a project name.\n *\n * @remarks\n * Matches the plugin's own `normalizeOptions` for hyphenated names: the\n * package clause is `names(projectName).propertyName.toLowerCase()` and the\n * sample function is `names(projectName).className`. Re-derived here rather\n * than imported because `@nx/devkit` is not a runtime dependency of the CLI.\n * Project names are validated to lowercase letters, digits, `-` and `.`\n * (`project-name.validator.ts`), and splitting on every non-alphanumeric\n * character also covers the dotted names the plugin leaves as an invalid\n * package clause (`my.lib` → `mylib`, not `my.lib`).\n *\n * `fileStem` is the snake-case form used for the role-suffixed file names\n * (`markdown-workspace` → `markdown_workspace`), the Go spelling of the\n * `<kebab>.<role>.ts` convention.\n *\n * @param projectName - The validated project name.\n * @returns The package name, the exported function name and the file stem.\n * @throws Never - pure string computation.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function goLibraryIdentifiers (projectName: string): {\n packageName: string\n functionName: string\n fileStem: string\n} {\n const words = projectName.split(/[^a-z0-9]+/i).filter(word => word.length > 0)\n\n return {\n packageName: words.join('').toLowerCase(),\n functionName: words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''),\n fileStem: words.join('_').toLowerCase(),\n }\n}\n\n/**\n * Reshapes a freshly generated Go library into a capability with one slice.\n *\n * @remarks\n * `@nx-go/nx-go:library` writes `<projectName>.go` and `<projectName>_test.go`\n * at the project root, so the root package IS the library. That contradicts\n * the vertical-slice shape mnci follows everywhere else (capability → flat\n * slice → role-suffixed files, with only an entry point at the root), and a\n * library that grows from it grows as one flat package that never splits.\n * Found while bootstrapping Lore Master, whose three Go libraries are each\n * several slice packages (russoedu/MoNecromanCi#227).\n *\n * After this runs, the root holds only `doc.go` (the capability's package\n * comment) and the generator's sample code lives in one starter slice:\n *\n * ```\n * libs/markdown-workspace/\n * ├── doc.go package markdownworkspace\n * ├── project.json\n * └── markdownworkspace/\n * ├── doc.go\n * ├── markdown_workspace_use_case.go func MarkdownWorkspace(name string) string\n * └── markdown_workspace_use_case_test.go\n * ```\n *\n * `use_case` is the role for the same reason the TypeScript placeholder is\n * renamed to `.use-case.ts` (`renameScaffoldPlaceholder`): it is the generic\n * role for a library's public behaviour. Unconditional for the same reason\n * too — whether a workspace \"uses\" slices is not knowable from the files, and\n * the shape costs nothing when ignored.\n *\n * The project's `test` and `lint` targets need no change: the plugin's\n * executors run `go test ./...` and `<linter> run ./...` from the project\n * root, so the slice package below it is covered (russoedu/MoNecromanCi#233).\n *\n * Idempotent: the root placeholders are removed with `force`, and the slice\n * files are only written when absent, so a user's edits survive a re-run.\n *\n * @param projectRoot - Absolute path to the generated library.\n * @param projectName - The project name the generator used for its files.\n * @returns The import path suffix of the starter slice, relative to the module.\n * @throws Propagates any `fs` error writing the new files.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reshapeGoLibraryScaffold (projectRoot: string, projectName: string): string {\n const { packageName, functionName, fileStem } = goLibraryIdentifiers(projectName)\n\n rmSync(join(projectRoot, `${projectName}.go`), { force: true })\n rmSync(join(projectRoot, `${projectName}_test.go`), { force: true })\n\n const files: ReadonlyArray<readonly [string, string]> = [\n [\n 'doc.go',\n `// Package ${packageName} is the ${projectName} capability. Its code lives in the\\n` +\n '// slice packages below this directory, one package per outcome.\\n' +\n `package ${packageName}\\n`,\n ],\n [\n join(packageName, 'doc.go'),\n `// Package ${packageName} is the starter slice of ${projectName}: rename it after\\n` +\n '// the outcome it delivers, and add one package per further outcome.\\n' +\n `package ${packageName}\\n`,\n ],\n [\n join(packageName, `${fileStem}_use_case.go`),\n `package ${packageName}\\n\\n` +\n `// ${functionName} is the generator's sample behaviour, kept so the slice builds and tests.\\n` +\n `func ${functionName}(name string) string {\\n` +\n `\\treturn \"${functionName} \" + name\\n` +\n '}\\n',\n ],\n [\n join(packageName, `${fileStem}_use_case_test.go`),\n `package ${packageName}\\n\\n` +\n 'import \"testing\"\\n\\n' +\n `func Test${functionName}(t *testing.T) {\\n` +\n `\\tif got := ${functionName}(\"works\"); got != \"${functionName} works\" {\\n` +\n '\\t\\tt.Fatalf(\"got %q\", got)\\n' +\n '\\t}\\n' +\n '}\\n',\n ],\n ]\n for (const [relativePath, content] of files) {\n const path = join(projectRoot, relativePath)\n if (!fileExists(path)) {\n writeFileEnsured(path, content)\n }\n }\n\n return packageName\n}\n\n/** Shared preflight for every Go kind: toolchain, plugin and root module. */\nfunction prepareGo (workspaceRoot: string): void {\n ensureGo(workspaceRoot)\n warnIfNoGolangciLint(workspaceRoot)\n ensureNxGoPlugin(workspaceRoot)\n ensureGoModule(workspaceRoot)\n}\n\n/**\n * Adds a Go executable app under `apps/`.\n *\n * @remarks\n * Delegates project generation to `@nx-go/nx-go:application`, then writes the\n * build/test/lint targets (nothing is inferred in single-module mode) plus\n * mnci's own `package` zip convention.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoApp (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:application',\n `apps/${name}`,\n `--name=${name}`,\n '--tags=type:go-app',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n 'build': goBuildTarget(name),\n 'test': goTestTarget(),\n 'lint': goLintTarget(),\n 'package': goPackageTarget('go-app', name),\n 'build-all': goBuildAllTarget(name),\n 'package-all': goPackageAllTarget('go-app', name),\n 'start': goStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a Go serverless function app under `apps/`.\n *\n * @remarks\n * Structurally the same as {@link addGoApp} — a Go function app is an\n * ordinary executable whose `main` is the platform's handler entry point —\n * so this shares the generator and targets, differing only in its tag and\n * its drop basename (`go-function-app-<name>`). The handler body itself is\n * left to the user: AWS Lambda, Google Cloud Functions and Azure each want a\n * different signature, and mnci does not pick one for you.\n *\n * No `start` target, unlike `node-function-app`/`python-function-app`:\n * `func start` needs a `host.json` (and, for the custom-handler model Go\n * would use, a matching `customHandler` config), and this kind writes\n * neither — an honest known gap, not an oversight papered over with a\n * command that would just fail. `go-app`'s `go run .` doesn't apply either,\n * since there is no Functions host to dispatch triggers to it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoFunctionApp (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:application',\n `apps/${name}`,\n `--name=${name}`,\n '--tags=type:go-function-app',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n 'build': goBuildTarget(name),\n 'test': goTestTarget(),\n 'lint': goLintTarget(),\n 'package': goPackageTarget('go-function-app', name),\n 'build-all': goBuildAllTarget(name),\n 'package-all': goPackageAllTarget('go-function-app', name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds a publishable Go library under `packages/`.\n *\n * @remarks\n * \"Publishable\" means something different in Go than in npm or PyPI, and it\n * is worth being precise: there is no registry upload step. The whole\n * repository is one module, so consumers depend on this library by its\n * import path at a repo-level version tag —\n * `go get <module>/packages/<name>@v1.2.3`. Publishing is therefore the git\n * tag `nx release` already creates; no `nx-release-publish` target is\n * written, because there is nothing to push. The only real difference from\n * an internal library is intent, recorded in the `type:go-lib` tag and the\n * `packages/` location.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoLib (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:library',\n `packages/${name}`,\n `--name=${name}`,\n '--tags=type:go-lib',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'packages', name, 'project.json'), {\n test: goTestTarget(),\n lint: goLintTarget(),\n })\n const slice = reshapeGoLibraryScaffold(join(workspaceRoot, 'packages', name), name)\n registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import its starter slice as ${module}/packages/${name}/${slice}`)\n }\n}\n\n/**\n * Adds a private Go library under `libs/`.\n *\n * @remarks\n * Identical machinery to {@link addGoLib} minus the publishable intent: same\n * single root module, imported as `<module>/libs/<name>`. Test and lint\n * targets only — a Go package that is not `main` produces no binary, so\n * there is no build target to write.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoInternalLib (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:library',\n `libs/${name}`,\n `--name=${name}`,\n '--tags=type:go-internal-lib',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'libs', name, 'project.json'), {\n test: goTestTarget(),\n lint: goLintTarget(),\n })\n const slice = reshapeGoLibraryScaffold(join(workspaceRoot, 'libs', name), name)\n registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import its starter slice as ${module}/libs/${name}/${slice}`)\n }\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addNxTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type NodeFramework,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Ensures an Nx plugin is installed in the workspace, installing it on first use.\n *\n * @remarks\n * `nx add` installs the package and runs its init generator — the Nx-native\n * way to bring a plugin into an existing workspace. Same pattern as\n * `add/reactApp.ts`'s `ensurePlugin`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/node`).\n * @returns Nothing.\n * @throws Error when the underlying `nx add` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePlugin (workspaceRoot: string, packageName: string): void {\n if (hasPlugin(workspaceRoot, packageName)) {\n return\n }\n logger.step(`Installing Nx plugin ${packageName}`)\n runNx(['add', packageName], workspaceRoot)\n}\n\n/**\n * Generates a Node app with the official `@nx/node:application` generator.\n *\n * @remarks\n * Plain delegation — no framework-specific logic here at all, `framework` is\n * passed straight through to the generator. `--bundler=esbuild` is requested\n * either way, but verified empirically that `--framework=nest` silently\n * overrides it: NestJS needs its own webpack build (decorator/DI metadata\n * emission esbuild's transform-only approach can't produce), so a\n * `nest`-flavoured app builds to a single webpack-bundled `dist/main.js`\n * instead of the esbuild non-bundled mirrored-tree + shim the other\n * frameworks (and `none`) produce. {@link nodeAppPackageTarget} needs no\n * framework branch regardless — either shape's runnable entry is\n * `dist/main.js`, so zipping the whole `dist` folder works unchanged. Both\n * `node-app` and `node-function-app` generate identically (function apps\n * always pass `'none'` — see {@link addNodeFunctionApp}) — the function app\n * is this plus an Azure Functions v2 file overlay, the same split already\n * used for `python-app`/`python-function-app` (`add/python.ts`).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @param framework - The HTTP framework to scaffold (`none` for a bare app).\n * @returns Nothing.\n * @throws Error when the generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNodeApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n framework: NodeFramework,\n): void {\n ensurePlugin(workspaceRoot, '@nx/node')\n runNx(\n [\n 'g',\n '@nx/node:application',\n `apps/${name}`,\n '--bundler=esbuild',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--e2eTestRunner=none',\n `--framework=${framework}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n const appRoot = join(workspaceRoot, 'apps', name)\n separateDeclarationOutput(appRoot)\n repairAssetMapping(appRoot, name)\n}\n\n/**\n * Moves the app's declaration output out of the folder its own build empties.\n *\n * @remarks\n * `@nx/node:application --bundler=esbuild` writes `outDir: 'dist'` and\n * `tsBuildInfoFile: 'dist/...'` into `tsconfig.app.json`, while the build it\n * generates has `outputPath: 'apps/<name>/dist'` and empties that folder before\n * writing. Nx runs `typecheck` and `build` in parallel, so the build's clean\n * deletes the declarations `tsc --build` just wrote, and the spec project —\n * which references `tsconfig.app.json` — then fails with TS6305, reporting that\n * the output file in `dist` has not been built from its source file.\n *\n * Reproduced on a freshly generated workspace before changing anything: run the\n * app's typecheck, delete the `.d.ts` files the build's clean removes, run it\n * again, and TS6305 arrives every time. With the declarations in `out-tsc/app`\n * the same sequence passes.\n *\n * `out-tsc` is already in `tsconfig.app.json`'s `exclude` and in the generated\n * `.gitignore`, so nothing else has to move. The esbuild executor takes its\n * output from `outputPath`, never from the tsconfig, so `dist` is unchanged\n * apart from the tsbuildinfo no longer landing there.\n *\n * @param appRoot - Absolute path to the generated app's directory.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the tsconfig.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction separateDeclarationOutput (appRoot: string): void {\n const tsconfigPath = join(appRoot, 'tsconfig.app.json')\n if (!fileExists(tsconfigPath)) {\n return\n }\n const tsconfig = readJson<{ compilerOptions?: Record<string, unknown> }>(tsconfigPath)\n const compilerOptions = tsconfig.compilerOptions ?? {}\n if (compilerOptions.outDir !== 'dist') {\n return\n }\n writeFileEnsured(\n tsconfigPath,\n toJson({\n ...tsconfig,\n compilerOptions: {\n ...compilerOptions,\n outDir: 'out-tsc/app',\n tsBuildInfoFile: 'out-tsc/app/tsconfig.app.tsbuildinfo',\n },\n }),\n )\n}\n\n/**\n * Rewrites the generated asset mapping so assets land where the app reads them.\n *\n * @remarks\n * The generator writes the **string** form, `assets: ['apps/<name>/src/assets']`.\n * Under `@nx/esbuild` 23 that does not put the files at `dist/assets/`; measured\n * on a real build, a file placed in `src/assets` does not reach `dist` **at\n * all**. The generated `src/assets/.gitkeep` hides it until someone adds a real\n * asset, and then code reading `new URL('../assets/...', import.meta.url)` from\n * `dist/functions/*.js` simply finds nothing.\n *\n * The object form states input, glob and output explicitly, and with it the same\n * file lands at `dist/assets/<file>` — verified on a generated workspace.\n *\n * @param appRoot - Absolute path to the generated app's directory.\n * @param name - The app's project name, used to build the workspace-relative input.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repairAssetMapping (appRoot: string, name: string): void {\n const manifestPath = join(appRoot, 'package.json')\n if (!fileExists(manifestPath)) {\n return\n }\n const manifest = readJson<{\n nx?: { targets?: { build?: { options?: Record<string, unknown> } } }\n }>(manifestPath)\n const options = manifest.nx?.targets?.build?.options\n if (!options || !Array.isArray(options.assets)) {\n return\n }\n options.assets = options.assets.map(asset =>\n (typeof asset === 'string'\n ? { input: asset, glob: '**/*', output: asset.replace(`apps/${name}/src/`, '') }\n : asset),\n )\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * The `package` target for a plain Node app: zip its build output into the drop.\n *\n * @remarks\n * `@nx/esbuild:esbuild --bundle=false` mirrors the full workspace-relative\n * source tree into `apps/<name>/dist` (e.g. `dist/apps/<name>/src/main.js`)\n * plus a `dist/main.js` shim that `require`s it — so zipping the whole `dist`\n * folder is the complete, runnable output. Basename exactly `node-app-<name>`,\n * the string CI turns into the per-app build tag. Same cross-platform\n * `adm-zip` one-liner used throughout `add`.\n *\n * @param name - The Node app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/node-app-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('apps/${name}/dist');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Adds a plain Node app: `@nx/node:application` plus a packaging target.\n *\n * @remarks\n * Pure delegation to the official generator (esbuild, non-bundled unless\n * `framework` forces its own build — see {@link runNodeApp}) — no custom\n * build rewiring, no relocated output; every kind builds to its own\n * Nx-default location. {@link nodeAppPackageTarget} is the one thing the\n * generator doesn't do: zip the build into the CI drop.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @param framework - The HTTP framework to scaffold (defaults to `none`, a bare Node app).\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNodeApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n framework: NodeFramework = 'none',\n): void {\n runNodeApp(workspaceRoot, name, stack, framework)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(workspaceRoot, 'apps', name, 'package.json'), {\n package: nodeAppPackageTarget(name),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:serve` })\n}\n\n/**\n * Ensures `@azure/functions` is installed at the workspace root.\n *\n * @remarks\n * Unlike the removed `@nxazure/func` plugin (whose generators pulled this in\n * as a side effect), a plain `@nx/node:application` app has no Azure\n * Functions dependency by default, so `add node-function-app` installs it\n * for real — the version then gets read back and stamped into the app's own\n * manifest (see {@link repairNodeFunctionAppManifest}) for Azure's deploy-time\n * `npm install` (Oryx) to resolve.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureAzureFunctionsPackage (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@azure/functions')) {\n return\n }\n logger.step('Installing @azure/functions')\n if (\n runShell('npm', ['install', '@azure/functions', '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @azure/functions failed')\n }\n}\n\n/**\n * The bundle entry point written into every generated Node function app.\n *\n * @remarks\n * `@nx/esbuild:esbuild` only includes what is reachable from `main.ts` — the\n * same convention the removed hand-rolled function app used — so add one\n * import per function file you create under `src/functions/`. The import\n * runs `app.http(...)` (or another trigger registration) as a side effect;\n * nothing needs to be re-exported.\n */\nexport const NODE_FUNCTION_APP_MAIN = `// esbuild only includes what is reachable from here, so add one import per\n// function file you create under src/functions/.\nimport './functions/hello'\n`\n\n/**\n * The `host.json` written into a generated Node function app.\n *\n * @remarks\n * Identical shape to the Python function app's `host.json` (written by\n * `@mnci/nx-python-pip:function-application`, the plugin `add/python.ts`\n * delegates to) — the schema is language-agnostic. The v4 extension bundle\n * is what the Functions host uses to resolve bindings; `version: 2.0` is the\n * runtime schema. Deliberately minimal.\n */\nexport const NODE_FUNCTION_APP_HOST_JSON = `{\n \"version\": \"2.0\",\n \"extensionBundle\": {\n \"id\": \"Microsoft.Azure.Functions.ExtensionBundle\",\n \"version\": \"[4.*, 5.0.0)\"\n }\n}\n`\n\n/**\n * The Azure Functions v4 HTTP trigger written into a generated Node function app.\n *\n * @remarks\n * The Node v4 programming model: `app.http(...)` registers a route at import\n * time. The handler is thin — the testable logic lives in\n * {@link NODE_FUNCTION_APP_GREETING} (imported here) — so the sample spec\n * needs no `@azure/functions` mocking. Anonymous auth keeps the sample\n * runnable locally with `func start`.\n */\nexport const NODE_FUNCTION_APP_HELLO = `import { app } from '@azure/functions'\nimport type { HttpRequest, HttpResponseInit } from '@azure/functions'\nimport { buildGreeting } from './greeting'\n\nasync function hello(request: HttpRequest): Promise<HttpResponseInit> {\n const name = request.query.get('name') ?? 'world'\n return { body: buildGreeting(name) }\n}\n\napp.http('hello', {\n methods: ['GET'],\n authLevel: 'anonymous',\n route: 'hello',\n handler: hello,\n})\n`\n\n/**\n * A sample pure helper written into every generated Node function app.\n *\n * @remarks\n * Gives the app a genuinely testable unit (the HTTP handler would need\n * `@azure/functions` mocking), so the generator's own jest/vitest wiring has\n * a real passing test out of the box. Delete it once you have your own.\n */\nexport const NODE_FUNCTION_APP_GREETING = `export function buildGreeting (name: string): string {\n return 'Hello, ' + name + '!'\n}\n`\n\n/**\n * The sample spec proving the function app's test target runs.\n *\n * @remarks\n * Deliberately dependency-free (no Azure SDK), so it passes on a bare\n * workspace under either jest or vitest — `@nx/node:application` wires the\n * chosen runner natively (unlike the removed hand-rolled function app, which\n * carried no test setup of its own).\n */\nexport const NODE_FUNCTION_APP_GREETING_SPEC = `import { buildGreeting } from './greeting'\n\ndescribe('buildGreeting', () => {\n it('greets a name', () => {\n expect(buildGreeting('world')).toBe('Hello, world!')\n })\n})\n`\n\n/**\n * Repairs a Node function app's manifest for an Azure deploy: `main` + the real dependency.\n *\n * @remarks\n * `@nx/esbuild:esbuild --bundle=false` writes a `dist/main.js` shim that\n * `require`s the compiled entry (verified empirically) — so `main` is\n * `dist/main.js`, resolved relative to wherever this manifest sits. That\n * makes the source layout (`host.json` + `package.json` + `dist/main.js`,\n * once built, all under `apps/<name>`) directly `func start`-able, which\n * matters: unlike `python-function-app`, there is no pre-build step Azure\n * Functions Core Tools does for you, so the local `start` target\n * ({@link nodeFunctionAppStartTarget}) depends on `build` and then runs\n * `func start` right here, no staging copy needed.\n * {@link nodeFunctionAppPackageTarget}\n * mirrors this same relative layout inside the deploy zip (nesting `dist/`\n * rather than flattening it), so one `main` value is correct for both local\n * dev and the Azure deploy — verified against a real generated app that\n * `main.js` sitting flat (the old shape) would have broken `func start`\n * entirely (`dist/main.js` never exists at `apps/<name>/main.js`).\n * `@azure/functions` is added for real (not just referenced): Azure's\n * deploy-time Oryx build reads this manifest's `dependencies` and runs\n * `npm install` there — verified empirically that a plain `npm install` in a\n * simulated deploy folder (no bundled `node_modules`) resolves and runs\n * correctly once the dependency is declared, mirroring exactly how\n * `python-function-app` already relies on Azure's Python Oryx build\n * installing `requirements.txt` at deploy time.\n *\n * @param nodeFunctionAppRoot - Absolute path to the function app's directory.\n * @param workspaceRoot - Absolute path to the workspace (to read the\n * installed `@azure/functions` version).\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repairNodeFunctionAppManifest (nodeFunctionAppRoot: string, workspaceRoot: string): void {\n const manifestPath = join(nodeFunctionAppRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const azureFunctionsVersion = readJson<{ version: string }>(\n join(workspaceRoot, 'node_modules/@azure/functions/package.json'),\n ).version\n const dependencies = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n main: 'dist/main.js',\n dependencies: { ...dependencies, '@azure/functions': `^${azureFunctionsVersion}` },\n }),\n )\n}\n\n/**\n * The `package` target for a Node Azure Function: zip the deployable folder.\n *\n * @remarks\n * Zips `apps/<name>/dist` (the esbuild output — the `main.js` shim plus the\n * mirrored source tree) **nested under `dist/`**, alongside `host.json` and\n * the repaired `package.json`, into `dist/drop/node-function-app-<name>.zip`\n * — the exact same relative layout as the source directory, so the deployed\n * `package.json`'s `main: 'dist/main.js'` resolves identically whether run\n * locally (`func start` in `apps/<name>`) or from the unzipped deploy\n * artifact (see {@link repairNodeFunctionAppManifest}). No `node_modules`\n * bundled: Azure's Oryx build installs real dependencies from the zipped\n * `package.json` at deploy time. Basename exactly `node-function-app-<name>`\n * (the CI build tag).\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/node-function-app-${name}.zip`\n const root = `apps/${name}`\n // The third argument to addLocalFolder is a filter; declarations are dropped\n // here rather than at build time. `declaration: false` on the esbuild target\n // is not available: it collides with the `composite`/`declarationMap` the\n // workspace's own `typecheck` needs, and the build then dies on TS5069\n // (measured, both alone and with declarationMap turned off beside it). An app\n // has no consumers for declarations, so the deploy artifact simply omits them.\n const command = String.raw`node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('${root}/dist','dist',(e)=>!/\\.d\\.ts(\\.map)?$/.test(e));z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Replaces Nx's no-op `prune` with one that carries the root `overrides` across.\n *\n * @remarks\n * `@nx/js:prune-lockfile` cuts `dist/package-lock.json` from the root lockfile,\n * so the pruned lockfile already has every `overrides` entry applied — while the\n * `dist/package.json` written beside it has no `overrides` key at all. When an\n * override changes the version of a package **inside the pruned tree**, the two\n * disagree and the container build's `npm ci --omit=dev` refuses:\n * it reports that the manifest and the lock file are not in sync, naming the\n * package whose version the override moved as missing from the lock file.\n *\n * Conditional, not universal: an override that misses the app's runtime tree\n * changes nothing, which is why a generated workspace's own SECURITY_OVERRIDES\n * (dev tooling — brace-expansion, smol-toml, nanoid) do not trigger it and a\n * plain `npm ci` in `dist` succeeds. It bites the moment a workspace pins\n * something the app actually ships.\n *\n * Copying every root entry rather than only the intersecting ones is deliberate:\n * npm accepts `overrides` naming packages absent from the tree, and computing\n * the intersection here would need the resolved tree this step does not have.\n *\n * Nx's own `prune` is `nx:noop` over `prune-lockfile` + `copy-workspace-modules`,\n * so the same `dependsOn` is kept and only the body changes.\n *\n * @param name - The app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppPruneTarget (name: string): Record<string, unknown> {\n const pruned = `apps/${name}/dist/package.json`\n const command = String.raw`node -e \"const fs=require('node:fs');const root=JSON.parse(fs.readFileSync('package.json','utf8'));const o=root.overrides;if(!o||Object.keys(o).length===0){console.log('No root overrides - nothing to carry into the pruned manifest.');process.exit(0)}const p='${pruned}';if(!fs.existsSync(p)){console.error('Pruned manifest not found at '+p+' - run the prune-lockfile target first.');process.exit(1)}const m=JSON.parse(fs.readFileSync(p,'utf8'));m.overrides={...o,...m.overrides};fs.writeFileSync(p,JSON.stringify(m,null,2)+'\\n');console.log('Carried '+Object.keys(o).length+' root override(s) into '+p)\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['prune-lockfile', 'copy-workspace-modules'],\n options: { command },\n }\n}\n\n/**\n * The `start` target for a Node Azure Function: `func start`, locally.\n *\n * @remarks\n * Depends on `build` so `dist/main.js` exists before Core Tools tries to load\n * it via the manifest's `main` field ({@link repairNodeFunctionAppManifest}).\n * `continuous: true` marks it as a long-running dev-server task, the same\n * shape `@nx/js:node`'s own inferred `serve` target uses — Nx does not try to\n * cache or wait out a process that never exits on its own. Requires Azure\n * Functions Core Tools locally (never a prerequisite for\n * `add node-function-app` itself — see `ensureAzureFunctionsPackage`'s docs).\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n continuous: true,\n options: { command: 'func start', cwd: `apps/${name}` },\n }\n}\n\n/**\n * Adds a Node Azure Function: `@nx/node:application` plus the Azure Functions v4 shape.\n *\n * @remarks\n * Same generator as `node-app` — the difference is purely the overlay:\n * `@azure/functions` installed for real, an HTTP-trigger sample\n * ({@link NODE_FUNCTION_APP_HELLO}) + a pure, test-covered helper, `host.json`,\n * and the manifest repair the deploy needs. The `func` CLI is never invoked —\n * unlike the removed `@nxazure/func` plugin, nothing here shells out to it,\n * so it isn't a prerequisite for `add node-function-app` (only for local\n * `func start`, same as `python-function-app`). Always `--framework=none` —\n * unlike `node-app`, no framework choice: the Azure Functions v4 programming\n * model registers its own routes and runs its own request lifecycle\n * ({@link NODE_FUNCTION_APP_HELLO}'s `app.http(...)`), so a full HTTP server\n * framework (Express/Fastify/Koa/Nest) doesn't apply here.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNodeFunctionApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n): void {\n runNodeApp(workspaceRoot, name, stack, 'none')\n ensureAzureFunctionsPackage(workspaceRoot)\n const nodeFunctionAppRoot = join(workspaceRoot, 'apps', name)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/main.ts'), NODE_FUNCTION_APP_MAIN)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/functions/hello.ts'), NODE_FUNCTION_APP_HELLO)\n writeFileEnsured(\n join(nodeFunctionAppRoot, 'src/functions/greeting.ts'),\n NODE_FUNCTION_APP_GREETING,\n )\n writeFileEnsured(\n join(nodeFunctionAppRoot, 'src/functions/greeting.spec.ts'),\n NODE_FUNCTION_APP_GREETING_SPEC,\n )\n writeFileEnsured(join(nodeFunctionAppRoot, 'host.json'), NODE_FUNCTION_APP_HOST_JSON)\n repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(nodeFunctionAppRoot, 'package.json'), {\n package: nodeFunctionAppPackageTarget(name),\n prune: nodeFunctionAppPruneTarget(name),\n start: nodeFunctionAppStartTarget(name),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n","import { join } from 'node:path'\nimport { promptText } from '../terminal'\nimport { repairDeclarationSpecifiers, repairPublishableManifest } from '../rollup-library'\nimport { removeLocalRegistryScaffolding } from '../workspace-overlay'\nimport {\n defaultScope,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n renameScaffoldPlaceholder,\n runGeneratorAndRepair,\n writeProjectReadme,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Adds a publishable npm library: `@nx/js:lib` as a rollup bundle.\n *\n * @remarks\n * rollup (not tsc): a bundler is what lets a published package depend on\n * private internal libs. `@nx/rollup`'s `withNx` externalizes exactly the\n * manifest's `dependencies`/`peerDependencies` — so imported internal libs\n * (never declared in the manifest, npm workspaces links them regardless) are\n * compiled INTO the bundle from source, and the private name never reaches\n * the published `package.json`.\n *\n * The scope is resolved here: an explicit `--scope` wins; otherwise the flag\n * path (`kindProvided`) defaults it silently, while the interactive/bare path\n * prompts for it (with the workspace's own scope as the default).\n *\n * Routed through {@link runGeneratorAndRepair} rather than calling `runNx`\n * directly: this generator's `@nx/rollup`/vitest plugin install can fail for\n * reasons unrelated to the generator itself (reproduced with a real npm 10\n * arborist bug on this exact dependency tree) after every file below has\n * already been written, and without the wrapper every repair — the `types`\n * fix, the declaration/source-map fixes, `registerProjectCommands` — would\n * silently never run, leaving a broken, half-configured project that\n * `mnci doctor` could not fully catch.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @param stack - The workspace's chosen test runner.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its install step\n * failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addNpmLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n stack: WorkspaceStack,\n): Promise<void> {\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n const projectRoot = join(workspaceRoot, 'packages', name)\n const manifestPath = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/js:lib',\n `packages/${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n '--bundler=rollup',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n manifestPath,\n () => {\n markPublic(manifestPath)\n // @nx/js:lib --bundler=rollup writes types: './dist/index.esm.d.ts', a file its\n // own build never emits, so every TypeScript consumer of the published package\n // would get `any`. See repairPublishableManifest.\n repairPublishableManifest(manifestPath)\n // The build writes dist/index.d.ts as a stub whose specifier @nx/rollup builds\n // with path.relative(), so on Windows it is not a valid module specifier at all.\n repairDeclarationSpecifiers(projectRoot)\n // The @nx/dependency-checks exclusions this kind needs now live in the ROOT\n // config (@mnci/eslint-config's dependencyChecks block), so the generator's\n // per-project config is deleted rather than overwritten.\n // Nx writes a README crediting itself; mnci generated this project.\n writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner)\n // @nx/js:lib's `src/lib/<name>.ts` has no role suffix, so mnci's own\n // vertical-slices/file-role rule rejects it on the first lint.\n renameScaffoldPlaceholder(projectRoot, name)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n // `--publishable` also scaffolds a whole local-registry story (verdaccio\n // config, devDependency, root target) that mnci's tag-only release model\n // has no use for.\n removeLocalRegistryScaffolding(workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n","/**\n * Repoints a generated publishable library's manifest at the declaration file its\n * build actually emits, and keeps declaration maps out of the tarball.\n *\n * @remarks\n * Applies to both `npm-lib` and `react-lib`, at generation time and again from\n * `mnci upgrade` for libraries generated before the repair existed.\n */\n\nimport { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { readJson, toJson, writeFileEnsured } from '../file-system'\n\n/**\n * The declaration path the rollup-bundled library generators write, which their\n * own build never produces.\n */\nconst WRONG_TYPES_PATH = './dist/index.esm.d.ts'\n\n/**\n * The real declaration file, pointed at directly rather than through the stub.\n *\n * @remarks\n * `@nx/rollup`'s `dts-bundle` plugin emits declarations at `dist/src/index.d.ts` and\n * then writes a stub `dist/index.d.ts` that re-exports from them. The obvious target\n * is that stub, and it is the wrong one: the plugin builds the specifier with\n * `path.relative()`, which returns an OS-NATIVE path, so on a Windows agent the stub\n * reads\n *\n * ```\n * export * from \"./src\\\\\\\\index\";\n * ```\n *\n * A module specifier is URL-style, not a filesystem path - `/` is correct on every\n * platform and `\\\\` is correct on none. It resolves on Windows only because the\n * resolver normalises separators there; on Linux and macOS a backslash is an ordinary\n * filename character, so the package is untyped for those consumers. Confirmed in a\n * real published tarball built on a Windows CI pool.\n *\n * Both library kinds share the layout, so this path is right for both:\n * `@nx/rollup`'s configuration generator writes `main: './src/index.ts'` for every\n * project it configures, and `@nx/js:lib` and `@nx/react:library` both route through\n * it.\n *\n * The stub still ships and is simply unused. Upstream fix tracked in ROADMAP 7c.\n */\nconst ACTUAL_TYPES_PATH = './dist/src/index.d.ts'\n\n/**\n * Keeps declaration source maps out of the published tarball.\n *\n * @remarks\n * `declarationMap` is on workspace-wide (`create-nx-workspace` writes it into\n * `tsconfig.base.json`) and is genuinely useful INSIDE the monorepo, where\n * go-to-definition across projects lands on the original `.ts`. It is dead weight\n * in a published package, because `files: [\"dist\"]` ships no sources for the maps\n * to point at: measured on a real published library, every `.d.ts.map` carried\n * `sources: [\"../src/<name>.ts\"]`, a path outside the tarball. They were also HALF\n * the package - 32 of its 67 files - so an editor following one lands on nothing\n * while every consumer downloads them.\n *\n * Excluded from `files` rather than by switching `declarationMap` off, so in-repo\n * navigation keeps working.\n */\nconst DECLARATION_MAP_EXCLUSION = '!**/*.d.ts.map'\n\n/**\n * Keeps JavaScript source maps out of the published tarball.\n *\n * @remarks\n * The counterweight to building them unconditionally. A `.js.map` carries the\n * whole of `sourcesContent` - every line of the package's TypeScript - so\n * publishing them would multiply the tarball for a benefit only this\n * workspace's own debugger collects. Same reasoning as\n * {@link DECLARATION_MAP_EXCLUSION}.\n */\nconst SOURCE_MAP_EXCLUSION = '!**/*.js.map'\n\n/**\n * Repairs the manifest of a generated publishable library: repoints its `types`\n * entries at the declaration file the build actually emits, and keeps declaration\n * maps out of the tarball.\n *\n * @remarks\n * Works around a real inconsistency in the `--bundler=rollup` library generators,\n * not a preference: they write `types: './dist/index.esm.d.ts'` (and the same path\n * under `exports['.']`), while the rollup build emits `dist/index.d.ts`. The\n * referenced file therefore never exists, so **every TypeScript consumer of the\n * published package gets `any`**, failing with\n * `TS7016: Could not find a declaration file for module '@scope/name'`.\n *\n * Applies to BOTH `@nx/js:lib` (`npm-lib`) and `@nx/react:library` (`react-lib`).\n * It lived in `reactLib.ts` alone for a while, which is how `npm-lib` shipped the\n * defect: the bug was found, diagnosed and fixed on one code path while the other\n * called the same generator family with the same flag. Measured on a real published\n * workspace — a consumer importing `@auto/env` failed `TS7016`, and repointing\n * `types` alone made it resolve, with a deliberate type error then reported\n * correctly.\n *\n * `main`/`module` are left alone: they correctly point at `index.esm.js`, which IS\n * emitted. Only the declaration paths are wrong.\n *\n * Guarded on the exact wrong value, so if Nx corrects this upstream the repair\n * quietly stops applying instead of overwriting a now-correct path.\n *\n * @param manifestPath - Absolute path to the library's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairPublishableManifest (manifestPath: string): void {\n const manifest = readJson<{\n types?: string\n files?: string[]\n exports?: Record<string, string | { types?: string }>\n }>(manifestPath)\n\n if (manifest.types === WRONG_TYPES_PATH) {\n manifest.types = ACTUAL_TYPES_PATH\n }\n const dot = manifest.exports?.['.']\n if (typeof dot === 'object' && dot.types === WRONG_TYPES_PATH) {\n dot.types = ACTUAL_TYPES_PATH\n }\n for (const exclusion of [DECLARATION_MAP_EXCLUSION, SOURCE_MAP_EXCLUSION]) {\n if (manifest.files && !manifest.files.includes(exclusion)) {\n manifest.files.push(exclusion)\n }\n }\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * Sweeps every project's manifest through {@link repairPublishableManifest}.\n *\n * @remarks\n * Called by `mnci upgrade`. `repairPublishableManifest` itself only runs at\n * `add` time (from `addNpmLib`/`addReactLib`/`addReactInternalLib`), so a\n * project `add`ed before this repair existed — or one whose manifest was\n * later hand-edited back to the wrong `types` path — never gets revisited.\n * `repairPublishableManifest` is already unconditional and idempotent (it\n * only changes a field that is actually wrong, and only adds a `files`\n * exclusion that is actually missing), so it is safe to call on every\n * project's manifest regardless of kind: a manifest with none of the wrong\n * values is read and re-serialised unchanged.\n *\n * Scoped to `packages/*` and `libs/*` for the same reason\n * {@link repairRollupSourceMaps} is: those are the only places a JS/TS\n * project's `package.json` lives.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairPublishableManifests (workspaceRoot: string): string[] {\n const changed: string[] = []\n const manifests = globSync(['packages/*/package.json', 'libs/*/package.json'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of manifests) {\n const manifestPath = join(workspaceRoot, relativePath)\n const before = readFileSync(manifestPath, 'utf8')\n repairPublishableManifest(manifestPath)\n const after = readFileSync(manifestPath, 'utf8')\n if (after !== before) {\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n","/**\n * Rewrites the rollup configs of already-generated libraries so they emit usable\n * source maps and declaration specifiers.\n *\n * @remarks\n * The same repairs `mnci add` applies at generation time, applied after the\n * fact: `mnci upgrade` runs them across an existing workspace, and a freshly\n * generated project gets the declaration fix on the spot.\n */\n\nimport { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport {\n ROLLUP_CONFIG_PLACEHOLDER,\n ROLLUP_CONFIG_WITH_DTS_FIX,\n withRollupSourceMaps,\n withUpgradedDeclarationSpecifierPlugin,\n} from './rollup-config.algorithm'\n\n/**\n * Sweeps every publishable project's rollup config, enabling source maps.\n *\n * @remarks\n * Called by `mnci upgrade`, so a workspace generated before this shipped stops\n * being undebuggable without anyone editing a config by hand. Scoped to\n * `packages/*` and `libs/*` because those are the only places mnci puts a\n * rollup-built project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairRollupSourceMaps (workspaceRoot: string): string[] {\n const changed: string[] = []\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of configs) {\n const configPath = join(workspaceRoot, relativePath)\n const before = readFileSync(configPath, 'utf8')\n const after = withRollupSourceMaps(before)\n if (after !== before) {\n writeFileEnsured(configPath, after)\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n\n/**\n * Sweeps every publishable project's rollup config, upgrading an existing\n * declaration-specifier plugin to the current version.\n *\n * @remarks\n * Called by `mnci upgrade`, the same way {@link repairRollupSourceMaps} is —\n * scoped to `packages/*` and `libs/*` for the same reason: those are the\n * only places mnci puts a rollup-built project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function upgradeDeclarationSpecifierPlugins (workspaceRoot: string): string[] {\n const changed: string[] = []\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of configs) {\n const configPath = join(workspaceRoot, relativePath)\n const before = readFileSync(configPath, 'utf8')\n const after = withUpgradedDeclarationSpecifierPlugin(before)\n if (after !== before) {\n writeFileEnsured(configPath, after)\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n\n/**\n * Adds a rollup plugin that repairs the declaration stub the build emits.\n *\n * @remarks\n * `@nx/rollup` writes `dist/index.d.ts` as a stub re-exporting the real\n * declarations, building the specifier with `path.relative()` — an OS-native path.\n * On a Windows agent that yields `export * from \"./src\\\\index\"`, which is not a\n * valid module specifier on any platform: a specifier is URL-style, so `/` is right\n * everywhere and a backslash nowhere. It resolves on Windows only because the\n * resolver normalises separators there, so a package built there is untyped on Linux\n * and macOS. Confirmed in a real published tarball.\n *\n * mnci already points `types` past the stub ({@link repairPublishableManifest}), so\n * nothing depends on it — this makes the emitted artifact correct rather than merely\n * bypassed. The two repairs are deliberately independent: the manifest one keeps\n * consumers working even if this config is later hand-edited.\n *\n * The same plugin also appends `.js` to every bare relative specifier across\n * `dist/**\\/*.d.ts` — not just the stub. `tsconfig.lib.json` declares under\n * `moduleResolution: \"bundler\"`, where an extensionless relative import is\n * valid, so the REAL declarations (what `types` points at) carry the same bare\n * specifiers as the source. A consumer resolving under the workspace default,\n * `nodenext`, requires an explicit extension on every relative specifier;\n * without it `tsc` reports `TS2834`, and with `skipLibCheck` masking that, the\n * entry point's re-export fails to resolve at all and the package appears to\n * export nothing. Confirmed against a real packed tarball: zero exports\n * visible under `nodenext` before this, correct resolution after.\n *\n * Guarded on the exact placeholder the generators write, so a change to their\n * template makes this a no-op rather than corrupting the config.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @returns Nothing.\n * @throws Propagates any `fs` error raised while rewriting the config.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairDeclarationSpecifiers (projectRoot: string): void {\n const configPath = join(projectRoot, 'rollup.config.cjs')\n if (!fileExists(configPath)) {\n return\n }\n const config = readFileSync(configPath, 'utf8')\n if (!config.includes(ROLLUP_CONFIG_PLACEHOLDER)) {\n return\n }\n // Declaration stub FIRST, source maps second, and the order is load-bearing:\n // `withRollupSourceMaps` anchors on the `},` / `{` boundary between withNx's\n // two arguments and rewrites the `{` line, which is the same line the\n // placeholder starts with. Run the other way round it silently matches\n // nothing and the dts plugin is never written.\n const withDtsFix = config.split(ROLLUP_CONFIG_PLACEHOLDER).join(ROLLUP_CONFIG_WITH_DTS_FIX)\n writeFileEnsured(configPath, withRollupSourceMaps(withDtsFix))\n}\n","/**\n * The rollup config text a generated library carries: what it looks like when it\n * is correct, and what to change when it is not.\n *\n * @remarks\n * Every transform here is pure and deterministic - it takes config text and\n * returns config text, and touches no filesystem. The reads and writes live in\n * the sibling repository and use-case files.\n */\n\n/**\n * The empty second-argument slot `@nx/js:lib --bundler=rollup` writes verbatim.\n *\n * @remarks\n * Matched exactly, as the generator emits it, and used as the anchor every\n * declaration repair splices into. A config that no longer carries it has\n * either been repaired already or been edited by hand, and in both cases the\n * repair must leave it alone rather than guess at a new insertion point.\n */\nexport const ROLLUP_CONFIG_PLACEHOLDER = [\n ' {',\n ' // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options',\n ' // e.g.',\n ' // output: { sourcemap: true },',\n ' }',\n].join('\\n')\n\n/**\n * The compiler `@nx/js:lib --bundler=rollup` hardcodes, and what mnci swaps it for.\n *\n * @remarks\n * **Without this swap the source maps are emitted but empty, so none of the\n * rest of this works.** `@nx/rollup`'s own configuration generator defaults\n * `compiler` to `babel`; `@nx/js:lib` passes `compiler: 'swc'` explicitly and\n * unconditionally, so every publishable library mnci generates is built with\n * swc. And `@nx/rollup`'s swc plugin calls swc's `transform()` **without**\n * `sourceMaps: true`, so it returns no map at all. A rollup transform hook that\n * returns no map breaks the chain: the output map comes out structurally valid\n * and semantically empty - `sources: []`, every mapping segment blank - which\n * is indistinguishable from a working build until a breakpoint refuses to bind.\n *\n * Measured on a real package in this repo: swc gave `sources: []`; the same\n * package on babel gave 9 sources, all resolving, with `sourcesContent`.\n *\n * Swapping the compiler is the fix rather than shipping a plugin that re-runs\n * swc with maps on, because two transform hooks would both compile the same\n * source and the second would see the first's output. Revert this the moment\n * `@nx/rollup` passes `sourceMaps` through - the upstream fix is one option in\n * `plugins/swc.js`.\n *\n * A regex, not a plain string, for the same reason {@link hasRollupSourceMaps}\n * is one: `@stylistic/key-spacing` (aligned on value) pads every property in\n * this object out to the longest key's column the moment `eslint --fix` runs\n * over it - which every mnci-generated file gets, and `outputPath`/`tsConfig`\n * both outrun `compiler`, so the padding fires on effectively every real\n * config. A literal `\" compiler: 'swc',\"` only matches the pristine,\n * never-formatted generator output; on a workspace where the repair is\n * reached late (a failed `add` finished by a later `mnci upgrade`, or an\n * older mnci version's already-formatted output) the line reads\n * `compiler: 'swc',` and the literal silently fails to match, leaving swc's\n * empty source maps in place while `mnci upgrade` reports success. Confirmed\n * end to end: a real crashed `add npm-lib` left `rollup.config.cjs`\n * unrepaired, one `npm run format` key-spacing-aligned it, and the literal\n * swap then no-op'd on the following `mnci upgrade`.\n */\nconst GENERATED_COMPILER_PATTERN = /^( {4})compiler\\s*:\\s*'swc',$/m\n\n/**\n * Builds the source-map-capable compiler line (plus its explaining comment)\n * at the given indent, matched from {@link GENERATED_COMPILER_PATTERN}'s\n * capture so the replacement lines up whatever the original indentation was.\n */\nfunction sourceMapCapableCompiler (indent: string): string {\n return [\n `${indent}// Swapped from swc by MoNecromanCI. @nx/rollup runs swc without`,\n `${indent}// sourceMaps, so it returns no map and the bundle's map comes out empty -`,\n `${indent}// valid-looking, and useless for debugging. See ROADMAP.`,\n `${indent}compiler: 'babel',`,\n ].join('\\n')\n}\n\n/**\n * The end of `withNx`'s FIRST argument, with source maps switched on.\n *\n * @remarks\n * `sourceMap` has to be set here and nowhere else. The obvious spot is\n * `output: { sourcemap: true }` in the second argument - the generator's own\n * placeholder comment even suggests it - and it silently does nothing:\n * `withNx` spreads the caller's `output` and *then* assigns\n * `sourcemap: options.sourceMap`, so its own (undefined) value always wins.\n *\n * Unconditional rather than gated behind a dev flag. Maps are what make a\n * breakpoint in a `.ts` file bind, so a build without them is undebuggable, and\n * every way of gating it costs something a generated workspace should not pay:\n * an env var is not portable across npm scripts without a fourth runtime\n * dependency, and a second build target is one more thing to remember at\n * exactly the moment you are already debugging. The maps are always built and\n * never *published* instead - {@link repairPublishableManifest} keeps them out\n * of `files`, the same trade this project already made for `.d.ts.map`.\n */\nconst ROLLUP_ARG_ONE_BOUNDARY = [' },', ' {', ''].join('\\n')\n\n/** The same boundary, with the source-map flag appended to argument one. */\nconst ROLLUP_ARG_ONE_WITH_SOURCE_MAPS = [\n ' // Added by MoNecromanCI: without this rollup emits no .js.map at all, so',\n ' // a breakpoint in a .ts file can never bind. Not published - see `files`.',\n ' sourceMap: true',\n ' },',\n ' {',\n '',\n].join('\\n')\n\n/**\n * Whether a rollup config already carries the source-map wiring.\n *\n * @remarks\n * The idempotence guard for both entry points, and what `mnci doctor` asks so\n * its finding and `mnci upgrade`'s edit can never disagree about what \"already\n * fixed\" means.\n *\n * A literal `config.includes('sourceMap: true')` broke on both halves of that\n * contract at once: `@mnci/eslint-config`'s `@stylistic/key-spacing` (aligned\n * on value) is entitled to rewrite `sourceMap: true,` to\n * `sourceMap: true,` to line up with whatever the object's\n * longest key is, and `eslint --fix` runs on every mnci-generated file. Tested\n * with a regex rather than a plain substring so any amount of horizontal\n * whitespace around the colon - however a formatter chooses to lay it out -\n * still reads as \"already on\".\n *\n * @param config - The config file's text.\n * @returns `true` when source maps are already switched on.\n * @throws Never - performs a regex test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasRollupSourceMaps (config: string): boolean {\n return /sourceMap\\s*:\\s*true\\b/.test(config)\n}\n\n/**\n * Whether `withRollupSourceMaps` can repair this config.\n *\n * @remarks\n * The write-time twin of {@link hasRollupSourceMaps}'s idempotence test:\n * `withRollupSourceMaps` anchors its edit on the `},` / `{` boundary between\n * `withNx`'s two arguments, so a config that never has that boundary in its\n * OWN text cannot be repaired no matter how many times `mnci upgrade` runs -\n * most commonly a one-line delegation to a shared base file, e.g.\n * `module.exports = require('../../rollup.base.cjs')()`, which mnci does not\n * own and has nothing to anchor on. `mnci doctor` uses this to decide whether\n * recommending `mnci upgrade` would actually fix anything, rather than\n * pointing the user at a command that silently no-ops.\n *\n * @param config - The config file's own text (not resolved through `require()`).\n * @returns `true` when `withRollupSourceMaps` has a boundary to anchor on.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function canRepairRollupConfig (config: string): boolean {\n return config.includes(ROLLUP_ARG_ONE_BOUNDARY)\n}\n\n/**\n * Switches source maps on in a rollup config, whatever shape it is in.\n *\n * @remarks\n * Deliberately not anchored on the generator's placeholder comment. That\n * comment survives only until mnci replaces it at `add` time, so a placeholder\n * anchor would work for a brand-new project and silently no-op for every\n * existing one - which is the whole population `mnci upgrade` exists to reach.\n * The `},` / `{` boundary between `withNx`'s two arguments is present in both\n * shapes, so it anchors both.\n *\n * Idempotent: a config that already has the flag is returned untouched, so\n * running `mnci upgrade` twice changes nothing the second time. That guard is\n * {@link hasRollupSourceMaps} rather than a second literal, deliberately -\n * two independent \"is it already on\" checks are two things that can disagree,\n * and disagreeing here means inserting a second `sourceMap: true` into a\n * config `eslint --fix` had only reformatted, not left off.\n *\n * @param config - The config file's text.\n * @returns The config with source maps enabled, or unchanged when already so.\n * @throws Never - an unrecognised config is returned unchanged.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withRollupSourceMaps (config: string): string {\n if (hasRollupSourceMaps(config) || !canRepairRollupConfig(config)) {\n return config\n }\n const withCompiler = config.replace(GENERATED_COMPILER_PATTERN, (_match, indent: string) =>\n sourceMapCapableCompiler(indent),\n )\n const withFlag = withCompiler.replace(\n ROLLUP_ARG_ONE_BOUNDARY,\n () => ROLLUP_ARG_ONE_WITH_SOURCE_MAPS,\n )\n\n return withFlag.includes('sourcemapPathTransform')\n ? withFlag\n : withFlag.replace(\n ROLLUP_ARG_ONE_WITH_SOURCE_MAPS,\n () => `${ROLLUP_ARG_ONE_WITH_SOURCE_MAPS}${SOURCEMAP_PATH_TRANSFORM}`,\n )\n}\n\n/** The `output` block that repairs rollup's wrong sourcemap source paths. */\nconst SOURCEMAP_PATH_TRANSFORM = [\n ' // Added by MoNecromanCI. rollup hands sourcemapPathTransform an OS-NATIVE',\n ' // path with one parent segment too many, so `sources` resolve to nothing',\n ' // and no breakpoint can bind. Separators are normalised too: a sources',\n ' // entry is URL-style, so a backslash is wrong on every platform.',\n ' output: {',\n ' sourcemapPathTransform: relativeSourcePath =>',\n ' relativeSourcePath',\n \" .replaceAll(String.fromCodePoint(92), '/')\",\n \" .replace(/^([.][.][/])+/, '../')\",\n ' },',\n '',\n].join('\\n')\n\n/**\n * A unique substring of {@link DECLARATION_SPECIFIER_PLUGIN}'s `name`,\n * present whenever the plugin exists in a rollup config at all — including a\n * version written before the `.js`-extension capability below existed.\n */\nconst DECLARATION_SPECIFIER_PLUGIN_MARKER = \"name: 'mnci-normalise-declaration-specifiers'\"\n\n/**\n * A unique identifier {@link DECLARATION_SPECIFIER_PLUGIN} only contains once\n * it also appends `.js` to bare relative specifiers — absent from the\n * earlier version that only normalised the stub's backslashes.\n */\nconst DECLARATION_SPECIFIER_EXTENSION_MARKER = 'bareRelativeSpecifier'\n\n/**\n * A unique identifier {@link DECLARATION_SPECIFIER_PLUGIN} only contains once\n * it resolves a bare specifier's suffix against what rollup actually emitted\n * — absent from the earlier version that appended `.js` unconditionally.\n *\n * @remarks\n * That earlier version could not tell a file specifier from a directory\n * barrel: `./scan-session` needs `/index.js`, not `.js` (a file that was\n * never emitted), and ESM resolution has no directory-index fallback — so an\n * affected import silently degraded the whole module to `any` under\n * `skipLibCheck`, the default in most consumers. Confirmed against a real\n * published tarball.\n */\nconst DECLARATION_SPECIFIER_DIRECTORY_MARKER = 'resolveSpecifierSuffix'\n\n/**\n * Whether a rollup config carries the declaration-specifier plugin at all.\n *\n * @remarks\n * Exported as a predicate rather than exporting the marker string itself, so\n * `mnci doctor` asks this module the question instead of re-implementing the\n * detection and drifting from it — the same contract\n * {@link hasRollupSourceMaps} already has with that caller.\n *\n * @param config - The config file's text, resolved through any shared base.\n * @returns `true` when the plugin is present, current or not.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasDeclarationSpecifierPlugin (config: string): boolean {\n return config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER)\n}\n\n/**\n * Whether that plugin resolves a bare specifier against what was emitted,\n * rather than appending `.js` unconditionally.\n *\n * @remarks\n * The distinction is the whole point of checking: a config carrying the\n * EARLIER plugin looks healthy by every other measure — the plugin is there,\n * the build succeeds, the package publishes — and still ships `any` for every\n * export behind a directory barrel, because `./scan-session` was rewritten to\n * `./scan-session.js`, a file rollup never emitted. Nothing reports it:\n * `skipLibCheck` (the default in most consumers) swallows the unresolved\n * import, and runtime is unaffected because the bundle never goes through\n * those specifiers. Measured in a real consuming workspace, 9 of 11 published\n * packages carried exactly that generation.\n *\n * Separate from {@link hasDeclarationSpecifierPlugin} so a missing plugin and\n * a stale one can be reported as the different findings they are, even though\n * `mnci upgrade` is the remedy for both.\n *\n * @param config - The config file's text, resolved through any shared base.\n * @returns `true` when the plugin is the current, directory-aware version.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasDirectoryAwareDeclarationSpecifiers (config: string): boolean {\n return config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER)\n}\n\n/**\n * The declaration-specifier plugin object, exactly as written into\n * `plugins: [ … ]`.\n *\n * @remarks\n * Its own text is the source of truth for both call sites that need it:\n * {@link ROLLUP_CONFIG_WITH_DTS_FIX} (a fresh `add`) and\n * {@link withUpgradedDeclarationSpecifierPlugin} (upgrading an existing one\n * in place), so the two can never drift into writing different plugin\n * bodies for the same generator version.\n */\nconst DECLARATION_SPECIFIER_PLUGIN = [\n ' {',\n \" name: 'mnci-normalise-declaration-specifiers',\",\n ' writeBundle (outputOptions) {',\n \" const { existsSync, readdirSync, readFileSync, writeFileSync } = require('node:fs')\",\n \" const { join } = require('node:path')\",\n \" const dir = outputOptions.dir ?? './dist'\",\n \" const stub = join(dir, 'index.d.ts')\",\n ' let source',\n ' try {',\n \" source = readFileSync(stub, 'utf8')\",\n ' } catch {',\n ' return',\n ' }',\n ' // The stub carries a TWO-character escape (JSON.stringify escaped one',\n ' // backslash), so this must not match a single one - that would turn',\n String.raw` // \"./src\\index\" into \"./src//index\". Built from char codes so there is`,\n ' // no escaping in this file to get wrong.',\n ' const separator = String.fromCodePoint(92, 92)',\n \" const normalised = source.replaceAll(separator, '/')\",\n ' if (normalised !== source) writeFileSync(stub, normalised)',\n '',\n String.raw` const ${DECLARATION_SPECIFIER_EXTENSION_MARKER} = /from(\\s+)(['\"])(\\.[^'\"]+?)\\2/g`,\n String.raw` const hasExtension = /\\.(?:mjs|cjs|jsx?|json)$/`,\n ' let entries',\n ' try {',\n ' entries = readdirSync(dir, { recursive: true, withFileTypes: true })',\n ' } catch {',\n ' return',\n ' }',\n ' for (const entry of entries) {',\n \" if (!entry.name.endsWith('.d.ts')) continue\",\n ' const from = entry.parentPath ?? entry.path',\n ' const filePath = join(from, entry.name)',\n ' let declaration',\n ' try {',\n \" declaration = readFileSync(filePath, 'utf8')\",\n ' } catch {',\n ' continue',\n ' }',\n ' // A bare specifier may name a FILE or a DIRECTORY BARREL - resolved',\n ' // against what rollup actually emitted next to this file, never',\n ' // guessed. A directory needs /index.js, not .js (a file that was',\n ' // never written); anything neither form matches is left alone rather',\n ' // than rewritten to a specifier that cannot resolve.',\n ` const ${DECLARATION_SPECIFIER_DIRECTORY_MARKER} = (specifier) => {`,\n ' if (hasExtension.test(specifier)) return null',\n \" if (existsSync(join(from, specifier + '.d.ts'))) return specifier + '.js'\",\n \" if (existsSync(join(from, specifier, 'index.d.ts'))) return specifier + '/index.js'\",\n ' return null',\n ' }',\n ' const withExtensions = declaration.replace(',\n ` ${DECLARATION_SPECIFIER_EXTENSION_MARKER},`,\n ' (match, space, quote, specifier) => {',\n ` const resolved = ${DECLARATION_SPECIFIER_DIRECTORY_MARKER}(specifier)`,\n ' return resolved === null ? match : `from${space}${quote}${resolved}${quote}`',\n ' },',\n ' )',\n ' if (withExtensions !== declaration) writeFileSync(filePath, withExtensions)',\n ' }',\n ' }',\n ' }',\n].join('\\n')\n\n/**\n * The same slot, carrying a plugin that repairs the declaration stub.\n *\n * @remarks\n * What {@link ROLLUP_CONFIG_PLACEHOLDER} is replaced with: the mnci-owned\n * rollup plugin, inline in the generated config rather than imported from a\n * package, so a generated workspace needs nothing published to build.\n */\nexport const ROLLUP_CONFIG_WITH_DTS_FIX = [\n ' {',\n \" // Added by MoNecromanCI. @nx/rollup's dts-bundle plugin writes dist/index.d.ts\",\n ' // as a stub re-exporting the real declarations, and builds that specifier with',\n ' // path.relative() - an OS-NATIVE path. On Windows it emits',\n String.raw` // export * from \"./src\\\\index\";`,\n ' // which is not a valid module specifier on ANY platform: a specifier is',\n ' // URL-style, so / is correct everywhere and a backslash nowhere. It resolves on',\n ' // Windows only because the resolver normalises separators there, leaving the',\n ' // package untyped on Linux and macOS.',\n ' //',\n ' // mnci also points `types` past this stub, so nothing depends on it being',\n ' // correct; this makes the emitted file correct too. Remove once Nx fixes the',\n ' // plugin - its own devkit already exports normalizePath for exactly this.',\n ' //',\n ' // The second half fixes a separate defect in the REAL declarations `types`',\n ' // points at: tsconfig.lib.json declares under moduleResolution \"bundler\",',\n ' // where a bare relative specifier (\"./lib/align\") is valid, so every emitted',\n ' // .d.ts keeps the source\\'s own extensionless imports verbatim. A consumer on',\n ' // \"moduleResolution\": \"nodenext\" - the workspace root default - requires an',\n ' // explicit extension on every relative specifier and gets TS2834 (or,',\n ' // combined with skipLibCheck, a silent zero-export module) instead. Declared',\n ' // extensions are left untouched; only a bare relative specifier gets .js',\n ' // appended, matching what tsc itself emits under node16/nodenext.',\n ' plugins: [',\n DECLARATION_SPECIFIER_PLUGIN,\n ' ]',\n ' }',\n].join('\\n')\n\n/**\n * Finds the `{ … }` span enclosing the first occurrence of `needle`, by\n * brace-counting rather than parsing.\n *\n * @remarks\n * Safe here specifically because {@link DECLARATION_SPECIFIER_PLUGIN} is\n * mnci's own generated content and contains no string or regex literal with\n * an unmatched `{`/`}` — checked by hand, and any future addition to that\n * plugin body must preserve it. `needle` is found first, then the span's\n * start is the nearest `{` before it (by construction, nothing but that\n * brace and whitespace can sit between them in a `{ name: … }` object\n * literal), and the end is wherever forward brace-counting from there first\n * returns to depth zero.\n *\n * @param text - The text to search.\n * @param needle - A substring known to appear inside the object to find.\n * @returns The `[start, end)` character span, `end` exclusive of nothing\n * (it points just past the closing `}`), or `undefined` when `needle` is\n * absent, has no preceding `{`, or the braces never balance.\n * @throws Never.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findEnclosingBraceSpan (text: string, needle: string): [number, number] | undefined {\n const needleIndex = text.indexOf(needle)\n if (needleIndex === -1) {\n return undefined\n }\n const start = text.lastIndexOf('{', needleIndex)\n if (start === -1) {\n return undefined\n }\n let depth = 0\n for (let index = start; index < text.length; index += 1) {\n if (text[index] === '{') {\n depth += 1\n } else if (text[index] === '}') {\n depth -= 1\n if (depth === 0) {\n return [start, index + 1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Upgrades an already-written declaration-specifier plugin in place, so a\n * project `add`ed before the `.js`-extension capability existed picks it up.\n *\n * @remarks\n * {@link repairDeclarationSpecifiers} writes this plugin exactly once,\n * anchored on the generator's own placeholder — so a project `add`ed before\n * a later capability shipped keeps running the OLD plugin body forever, and\n * `mnci upgrade` never revisited it (unlike the source-map flag, which\n * {@link withRollupSourceMaps} does sweep). This closes that gap.\n *\n * Deliberately NOT a literal full-body text match: the plugin's own body is\n * ordinary JS in a `.cjs` file, so `eslint --fix` is free to reformat it\n * (quotes, semicolons, spacing) between when `add` wrote it and when\n * `upgrade` next runs — matching an exact prior version's text is exactly\n * the class of bug the compiler-swap fix above exists to prevent. Instead\n * this locates the plugin object by brace-counting from its own unique\n * `name` ({@link findEnclosingBraceSpan}) and replaces the WHOLE object with\n * the current version whenever {@link DECLARATION_SPECIFIER_DIRECTORY_MARKER}\n * is missing from it — regardless of what the old body's text actually was.\n * Checking the directory-aware marker rather than\n * {@link DECLARATION_SPECIFIER_EXTENSION_MARKER} is load-bearing: a project\n * `add`ed after the `.js`-extension capability shipped but before the\n * directory-barrel fix already carries the extension marker, so checking\n * that one would leave it stuck silently publishing untyped packages forever.\n *\n * Idempotent: a plugin that already carries the marker is left untouched.\n *\n * @param config - The rollup config's text.\n * @returns The config with the plugin upgraded in place, or unchanged when\n * there is no plugin to upgrade, it is already current, or its span could\n * not be found.\n * @throws Never — an unrecognised shape is returned unchanged.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withUpgradedDeclarationSpecifierPlugin (config: string): string {\n if (\n !config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER) ||\n config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER)\n ) {\n return config\n }\n const span = findEnclosingBraceSpan(config, DECLARATION_SPECIFIER_PLUGIN_MARKER)\n if (!span) {\n return config\n }\n const [start, end] = span\n\n return `${config.slice(0, start)}${DECLARATION_SPECIFIER_PLUGIN}${config.slice(end)}`\n}\n","/**\n * Reads the rollup config a generated library actually builds with, following the\n * `require()` chain into any shared base config it delegates to.\n *\n * @remarks\n * A project's own `rollup.config.cjs` is not always the whole story: the\n * generators emit configs that `require()` a workspace-level base, so a check\n * reading only the project file reports a repair as missing when it is simply\n * one file further out. This is the read side of the slice; the transforms it\n * feeds live in `rollup-config.algorithm.ts`.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileExists } from '../file-system'\n\n/**\n * Resolves a relative `require()` specifier to a real file.\n *\n * @remarks\n * Tries the specifier as given first, then the extensions a `.cjs` rollup\n * config (or a shared base it delegates to) is realistically written with -\n * `require()` itself resolves the same way, this just needs to agree with it\n * without actually loading the module.\n *\n * @param fromDir - The directory the `require()` call is made from.\n * @param specifier - The relative specifier passed to `require()`.\n * @returns The resolved absolute path, or `undefined` when none of the\n * candidates exist.\n * @throws Never - only checks the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveRequireTarget (fromDir: string, specifier: string): string | undefined {\n const base = join(fromDir, specifier)\n\n return [base, `${base}.cjs`, `${base}.js`].find(candidate => fileExists(candidate))\n}\n\n/**\n * Reads a rollup config's text, following local `require()` delegation so a\n * config hoisted into a shared base file is read as if it were inlined.\n *\n * @remarks\n * A workspace that pulls the `withNx(...)` call out into one root\n * `rollup.base.cjs` and leaves each project as\n * `module.exports = require('../../rollup.base.cjs')()` has no\n * `sourceMap: true` text of its own to find - {@link hasRollupSourceMaps}\n * reading only the project's own file would report every such project as\n * missing source maps even when every one genuinely has them, because the\n * flag lives one file away. Text-based rather than an actual `require()` of\n * the config, deliberately: `mnci doctor` is read-only, and evaluating a\n * user's build config as code to answer a yes/no question is a far larger\n * blast radius than reading more of its text.\n *\n * Only RELATIVE requires (`./…`, `../…`) are followed - an npm package's\n * installed source (e.g. `@nx/rollup/with-nx`) is never something this needs\n * to read, and grepping into node_modules would be both slow and pointless.\n * Each file is read at most once, so a require cycle terminates rather than\n * recursing forever.\n *\n * @param configPath - Absolute path to the rollup config being inspected.\n * @param visited - File paths already read, to stop a require cycle. Callers\n * should omit this; it is populated by recursive calls.\n * @returns The config's own text, followed by the text of every local file it\n * `require()`s, transitively.\n * @throws Never - an unreadable file or unresolvable require target is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveRollupConfigText (\n configPath: string,\n visited: Set<string> = new Set(),\n): string {\n if (visited.has(configPath)) {\n return ''\n }\n visited.add(configPath)\n\n let text: string\n try {\n text = readFileSync(configPath, 'utf8')\n } catch {\n return ''\n }\n\n const specifiers = Array.from(\n text.matchAll(/require\\((['\"])(\\.[^'\"]*)\\1\\)/g),\n match => match[2],\n )\n const requiredText = specifiers\n .map(specifier => resolveRequireTarget(dirname(configPath), specifier))\n .filter((target): target is string => target !== undefined)\n .map(target => resolveRollupConfigText(target, visited))\n\n return [text, ...requiredText].join('\\n')\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { PYTHON_GRAPH_PLUGIN, withPythonGraphPlugin } from '../workspace-overlay'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addProjectJsonTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n type AddOptions,\n} from './post-generation.use-case'\n\n/**\n * Fails fast, with an install hint, when Python is not on the PATH.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when neither `python3` nor `python` can be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePython (workspaceRoot: string): void {\n const hasPython =\n runShell('python3', ['--version'], workspaceRoot) === 0 ||\n runShell('python', ['--version'], workspaceRoot) === 0\n if (!hasPython) {\n throw new Error(\n 'Python not found. Install Python 3.9+ first: https://www.python.org/downloads/',\n )\n }\n}\n\n/**\n * The `@mnci/nx-python-pip` package spec to install.\n *\n * @remarks\n * Reads `MNCI2_PYTHON_PIP_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `libs/nx-python-pip` in this same monorepo)\n * instead of the published registry package — the real published spec is\n * the default for every real `mnci add python-*` call.\n */\nfunction pythonPipPluginSpec (): string {\n return process.env.MNCI2_PYTHON_PIP_SPEC ?? '@mnci/nx-python-pip'\n}\n\n/**\n * Ensures the `@mnci/nx-python-pip` Nx plugin is installed AND registered.\n *\n * @remarks\n * The generators and executors need no `nx.json` `plugins` registration —\n * they resolve by plain Node module lookup against the plugin's\n * `generators.json`/`executors.json`, so a plain `npm install` is the whole\n * story for them.\n *\n * Its DEPENDENCY GRAPH does need it. Nx calls `createDependencies` only for a\n * registered plugin, so without the entry a Python project has no edges: `nx\n * affected` cannot know that changing a Python library should retest the\n * projects that consume it, and an affected-scoped verify run passes having\n * checked none of them.\n *\n * Registered HERE rather than only in the overlay because this is where the\n * plugin arrives. `applyOverlay` registers it too, for a workspace that\n * already had Python before this existed, but it runs at `new`/`upgrade` time\n * — which is never during the `mnci add python-*` that first installs the\n * plugin.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePythonPipPlugin (workspaceRoot: string): void {\n if (!hasPlugin(workspaceRoot, '@mnci/nx-python-pip')) {\n const spec = pythonPipPluginSpec()\n logger.step(`Installing the Python toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @mnci/nx-python-pip failed')\n }\n }\n registerPythonGraphPlugin(workspaceRoot)\n}\n\n/**\n * Registers the Python dependency-graph plugin in `nx.json`.\n *\n * @remarks\n * Idempotent, and written only when it changes something — `mnci add` already\n * rewrites more of `nx.json` than a reviewer wants to read, and a no-op write\n * here would add another line of noise to every Python add after the first.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a missing or unreadable `nx.json` is left alone.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction registerPythonGraphPlugin (workspaceRoot: string): void {\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n if (!fileExists(nxJsonPath)) {\n return\n }\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const patched = withPythonGraphPlugin(nxJson, true)\n if ((patched.plugins as unknown[] | undefined)?.includes(PYTHON_GRAPH_PLUGIN) !== true) {\n return\n }\n if ((nxJson.plugins as unknown[] | undefined)?.includes(PYTHON_GRAPH_PLUGIN) === true) {\n return\n }\n logger.step(`Registering ${PYTHON_GRAPH_PLUGIN} so nx affected sees Python dependencies`)\n writeFileEnsured(nxJsonPath, toJson(patched))\n}\n\n/**\n * The fixed toolchain every Python project shares, written once to the\n * workspace root as `requirements-dev.txt`.\n *\n * @remarks\n * Ruff and pytest are the industry standard; `build` and `twine` are the\n * standard PyPA build/publish frontends that `@mnci/nx-python-pip`'s\n * executors shell out to; `pip-audit` is the PyPA vulnerability scanner the\n * generated CI's audit step (`overlay.ts`'s `PIP_AUDIT_GUARD`) runs against\n * the shared environment. No lock file — plain pip has none, matching the\n * company's standard toolchain (no uv, no Poetry). mnci writes this file\n * (not the plugin): the plugin is a generic Nx plugin with no opinion on how\n * its own runtime dependencies get onto a machine.\n */\nexport const PYTHON_REQUIREMENTS_DEV = `build\ntwine\nruff\nmypy\npytest\npip-audit\n`\n\n/**\n * Idempotently writes `requirements-dev.txt` to the workspace root.\n *\n * @remarks\n * Lazy, like {@link ensureAdmZip}: written on the first Python `add` of any\n * kind, not unconditionally by `mnci new` — a pure-JS/TS workspace never\n * gains this file. Only written when absent, so a user's own edits (extra\n * dev tools) survive repeat `add` calls.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error raised while writing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureRequirementsDev (workspaceRoot: string): void {\n const requirementsDevPath = join(workspaceRoot, 'requirements-dev.txt')\n if (!fileExists(requirementsDevPath)) {\n writeFileEnsured(requirementsDevPath, PYTHON_REQUIREMENTS_DEV)\n }\n}\n\n/**\n * A Python project's module directory name, derived from its Nx project name.\n *\n * @remarks\n * Mirrors `@mnci/nx-python-pip`'s own derivation (Python identifiers cannot\n * contain hyphens) — needed here only to name the module folder inside the\n * function app's source zip.\n *\n * @param name - The project name.\n * @returns The module directory's basename (e.g. `my_svc`).\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonModuleDirectory (name: string): string {\n return name.replaceAll('-', '_')\n}\n\n/**\n * The runnable entry point written into every generated Python app.\n *\n * @remarks\n * `@mnci/nx-python-pip:application` scaffolds a module (`<name>/__init__.py`)\n * shaped like a library — no `if __name__ == '__main__':` block, since the\n * plugin has no opinion on what \"running\" an app means. This file is what\n * gives `mnci add python-app` a real local-dev entry point for\n * {@link pythonAppStartTarget} to run, independent of whatever the sample\n * module's own function happens to be named — so it survives that code being\n * deleted or rewritten.\n *\n * @param name - The project name (used only in the printed message).\n * @returns The `main.py` file contents.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pythonAppMain (name: string): string {\n return `def main() -> None:\n print('${name} is running')\n\n\nif __name__ == '__main__':\n main()\n`\n}\n\n/**\n * The `start` target for a Python app: `python3 main.py`, locally.\n *\n * @remarks\n * `continuous: true` marks it as a long-running dev task, matching the shape\n * every other kind's custom `start` target uses — Nx does not try to cache or\n * wait out a process that never exits on its own (a Python \"app\" here is\n * whatever `main.py` does; many will exit immediately, but the target shape\n * stays uniform rather than guessing).\n *\n * @param name - The Python app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'python3 main.py', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The `start` target for a Python Azure Function: `func start`, locally.\n *\n * @remarks\n * Unlike the Node function app, this needs no prior `build` — Python function\n * apps deploy as source ({@link pythonFunctionAppPackageTarget}), so\n * `function_app.py` + `host.json` already sit together in the project root\n * exactly as Core Tools expects. Requires Azure Functions Core Tools (and the\n * Python worker) locally — never a prerequisite for\n * `add python-function-app` itself.\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonFunctionAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'func start', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The `package` target for a Python app: zip its built wheel into the drop.\n *\n * @remarks\n * Zips `apps/<name>/dist` (the plugin's `build` executor's wheel + sdist)\n * into `dist/drop/python-app-<name>.zip` — basename exactly\n * `python-app-<name>`, the string CI turns into the per-app build tag.\n * `dependsOn: build` guarantees the wheel exists; same cross-platform\n * `adm-zip` one-liner used throughout `add`.\n *\n * @param name - The Python app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/python-app-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('apps/${name}/dist');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The `package` target for a Python Azure Function: zip the deploy folder.\n *\n * @remarks\n * An Azure Functions Python app is deployed as **source** (the host installs\n * `requirements.txt` and runs `function_app.py`), not as a wheel — so this\n * zips `function_app.py` + `host.json` + `requirements.txt` + the module\n * package (all written by the plugin's `function-application` generator)\n * into `dist/drop/python-function-app-<name>.zip`. Basename exactly\n * `python-function-app-<name>` (the CI build tag). No `build` dependency (the\n * artifact is source), same `adm-zip` one-liner.\n *\n * @param name - The function app's project name.\n * @param moduleDirectory - The app's Python module directory basename.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonFunctionAppPackageTarget (\n name: string,\n moduleDirectory: string,\n): Record<string, unknown> {\n const zip = `dist/drop/python-function-app-${name}.zip`\n const root = `apps/${name}`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFile('${root}/function_app.py');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/requirements.txt');z.addLocalFolder('${root}/${moduleDirectory}','${moduleDirectory}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Adds a Python app: `@mnci/nx-python-pip:application` plus mnci's own zip packaging.\n *\n * @remarks\n * Pure delegation to the plugin generator, same shape as every other kind\n * (`react-app`, `node-app`, `npm-lib`) — the plugin owns `pyproject.toml` +\n * `project.json` (lint/test/build); mnci only layers its own CI packaging\n * convention on top, via {@link pythonAppPackageTarget}.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonApp (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n ['g', '@mnci/nx-python-pip:application', name, `--directory=apps/${name}`, '--no-interactive'],\n workspaceRoot,\n )\n writeFileEnsured(join(workspaceRoot, 'apps', name, 'main.py'), pythonAppMain(name))\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n package: pythonAppPackageTarget(name),\n start: pythonAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a Python Azure Function: `@mnci/nx-python-pip:function-application` plus mnci's own zip packaging.\n *\n * @remarks\n * The plugin generator writes the Azure Functions v2 shape (`function_app.py`\n * + `host.json` + `requirements.txt` + a tested pure helper); mnci only adds\n * the `package` target zipping that source into the drop.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonFunctionApp (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:function-application',\n name,\n `--directory=apps/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n const moduleDirectory = pythonModuleDirectory(name)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n package: pythonFunctionAppPackageTarget(name, moduleDirectory),\n start: pythonFunctionAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: false, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a publishable Python library under `python-packages/`.\n *\n * @remarks\n * `@mnci/nx-python-pip:library` wires the whole publishable shape itself —\n * `nx-release-publish` (twine) and the project-level\n * `release.version.versionActions` override — so no post-generation merge is\n * needed here at all, unlike the app kinds.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonLib (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:library',\n name,\n `--directory=python-packages/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds a private Python library under `libs/`.\n *\n * @remarks\n * Not publishable — `@mnci/nx-python-pip:internal-library` writes lint/test\n * targets only, no build/publish. Consumers vendor its module directly into\n * their own wheel at build time by hand-adding a `vendor` entry (under\n * `[tool.mnci-python-pip]`) to their own `pyproject.toml`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonInternalLib (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:internal-library',\n name,\n `--directory=libs/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n\n/**\n * The directories a Python project (app, publishable lib, internal lib) can\n * live under — mirrors the directory convention every Python `add*` function\n * above already writes to.\n */\nconst PYTHON_PROJECT_DIRS = ['apps', 'python-packages', 'libs'] as const\n\n/**\n * Finds which of the three Python project directories holds `name`'s\n * `pyproject.toml`.\n *\n * @remarks\n * A Python function app has no `pyproject.toml` at all (source deploy, no\n * wheel — see {@link addPythonFunctionApp}), so it is never found here; that\n * is intentional, not a gap — there is nothing to vendor into.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name to look up.\n * @returns The directory (`apps`, `python-packages`, or `libs`) the project\n * lives under, or `undefined` when no matching `pyproject.toml` exists.\n * @throws Never - pure filesystem probing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findPythonProjectDirectory (\n workspaceRoot: string,\n name: string,\n): (typeof PYTHON_PROJECT_DIRS)[number] | undefined {\n return PYTHON_PROJECT_DIRS.find(directory =>\n fileExists(join(workspaceRoot, directory, name, 'pyproject.toml')),\n )\n}\n\n/**\n * Extracts the vendored internal-lib project names from a `pyproject.toml`'s\n * `[tool.mnci-python-pip] vendor = [...]` table.\n *\n * @remarks\n * Mirrors `@mnci/nx-python-pip`'s own `parseVendorEntries` (`internal/vendor.ts`,\n * not part of that package's public API) — kept as a small, independent\n * regex here rather than adding a runtime dependency on that package for one\n * function: this CLI already owns the exact shape it writes (via\n * {@link addPythonVendor}), the same reasoning that justifies the plugin's\n * own copy owning the shape it reads.\n *\n * @param pyprojectToml - The `pyproject.toml` file contents.\n * @returns The vendored project names, or `[]` when the project vendors nothing.\n * @throws Never - a missing/malformed table simply yields an empty array.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction parseVendorEntries (pyprojectToml: string): string[] {\n const match = /^\\s*vendor\\s*=\\s*\\[([^\\]]*)\\]/m.exec(pyprojectToml)\n if (!match) {\n return []\n }\n\n return match[1]\n .split(',')\n .map(entry => entry.trim().replaceAll(/^[\"']|[\"']$/g, ''))\n .filter(Boolean)\n}\n\n/**\n * Adds `lib` to a `pyproject.toml`'s `[tool.mnci-python-pip] vendor = [...]`\n * table, creating the table when absent.\n *\n * @remarks\n * Every Python project's `pyproject.toml` this CLI generates (via\n * `@mnci/nx-python-pip`'s `pythonPyprojectToml`) ends with a fixed\n * `[tool.pytest.ini_options]` table — a reliable, always-present anchor to\n * insert the new table right before, matching the exact shape a hand-edit\n * would produce (see `@mnci/nx-python-pip`'s README). Caller ensures `lib`\n * is not already present ({@link parseVendorEntries}) before calling this.\n *\n * @param pyprojectToml - The consumer's current `pyproject.toml` contents.\n * @param lib - The internal-lib project name to add.\n * @returns The patched `pyproject.toml` contents.\n * @throws Never - pure string manipulation; an existing table without the\n * `[tool.pytest.ini_options]` anchor is left with a new table appended at\n * the end instead.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addVendorEntry (pyprojectToml: string, lib: string): string {\n const existing = parseVendorEntries(pyprojectToml)\n if (existing.length > 0) {\n const merged = [...existing, lib].map(name => `\"${name}\"`).join(', ')\n\n return pyprojectToml.replace(\n /^(\\s*vendor\\s*=\\s*)\\[[^\\]]*\\]/m,\n (_match, prefix: string) => `${prefix}[${merged}]`,\n )\n }\n const table = `[tool.mnci-python-pip]\\nvendor = [\"${lib}\"]\\n\\n`\n\n return pyprojectToml.includes('[tool.pytest.ini_options]')\n ? pyprojectToml.replace('[tool.pytest.ini_options]', () => `${table}[tool.pytest.ini_options]`)\n : `${pyprojectToml}\\n${table}`\n}\n\n/**\n * Wires an internal Python library into a consumer's built wheel, by\n * hand-adding a `[tool.mnci-python-pip] vendor = [...]` entry to the\n * consumer's `pyproject.toml`.\n *\n * @remarks\n * Automates the one step `@mnci/nx-python-pip`'s README documents as always\n * hand-added (plain pip has no bundled-local-dependency feature — see that\n * package's \"Internal-lib vendoring\" section): this function does the exact\n * same text edit a developer would make by hand, just validated and\n * idempotent. Nothing is regenerated or reinstalled — the vendored module is\n * only copied into the consumer's wheel at the consumer's next `build` (the\n * plugin's `build` executor resolves `lib`'s root via the real Nx project\n * graph at that point, not anything this function writes).\n *\n * `lib` is resolved the same way `addNpmLib` resolves `--scope`: taken from\n * `options.lib` when given, prompted for on the bare/interactive path\n * (`kindProvided` false), and a clear error otherwise — there is no sensible\n * default library to fall back to silently.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param consumer - The project name to vendor into (an app, publishable lib,\n * or another internal lib — anything with a `pyproject.toml`).\n * @param options - The CLI flags (`--lib`).\n * @param kindProvided - Whether `python-vendor` was passed explicitly (flag\n * path) rather than picked interactively — gates the `--lib` prompt.\n * @returns A promise that resolves once the edit is written.\n * @throws Error when no library was given/prompted, `consumer` has no\n * `pyproject.toml`, the library is not an internal library under `libs/`, or\n * a project is asked to vendor itself.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addPythonVendor (\n workspaceRoot: string,\n consumer: string,\n options: AddOptions,\n kindProvided: boolean,\n): Promise<void> {\n const lib =\n options.lib ??\n (kindProvided\n ? undefined\n : await promptText('Internal Python library to vendor (an existing libs/<name>)'))\n if (!lib) {\n throw new Error(\n '`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library).',\n )\n }\n\n const consumerDirectory = findPythonProjectDirectory(workspaceRoot, consumer)\n if (!consumerDirectory) {\n throw new Error(\n `No Python project named '${consumer}' with a pyproject.toml found (checked apps/, python-packages/, libs/). A Python function app has no pyproject.toml — there is nothing to vendor into.`,\n )\n }\n if (!fileExists(join(workspaceRoot, 'libs', lib, 'pyproject.toml'))) {\n throw new Error(\n `No internal Python library named '${lib}' found at libs/${lib}/pyproject.toml. Only an internal library (added via 'mnci add python-internal-lib') can be vendored.`,\n )\n }\n if (consumerDirectory === 'libs' && consumer === lib) {\n throw new Error(`'${consumer}' cannot vendor itself.`)\n }\n\n const pyprojectPath = join(workspaceRoot, consumerDirectory, consumer, 'pyproject.toml')\n const pyprojectToml = readFileSync(pyprojectPath, 'utf8')\n if (parseVendorEntries(pyprojectToml).includes(lib)) {\n logger.info(`'${consumer}' already vendors '${lib}' — nothing to do.`)\n\n return\n }\n\n writeFileEnsured(pyprojectPath, addVendorEntry(pyprojectToml, lib))\n logger.success(\n `'${consumer}' now vendors '${lib}' — its wheel will include '${lib}''s module on the next build.`,\n )\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx } from '../nx-workspace'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport {\n addNxTargets,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * The deploy environments each React app is built for.\n *\n * @remarks\n * A React SPA bakes its `VITE_*` config in at build time, so one build per\n * environment is needed. These are the three the generator scaffolds.\n */\nexport const REACT_ENVIRONMENTS = ['dev', 'uat', 'prod'] as const\n\n/**\n * The `.env.<environment>` file scaffolded into a React app.\n *\n * @remarks\n * `vite build --mode <environment>` loads this file and compiles its `VITE_`\n * vars into that environment's bundle. Because those values ship to the\n * browser they are inherently public, so committing them (rather than sourcing\n * secrets) is correct — real secrets never belong in a client bundle.\n *\n * @param environment - The environment name (`dev` | `uat` | `prod`).\n * @returns The `.env.<environment>` file contents.\n * @throws Never - pure string build.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactEnvironmentFile (environment: string): string {\n return `# Vite compiles VITE_-prefixed vars into the '${environment}' bundle at build time.\n# They ship to the browser, so keep only PUBLIC config here — never secrets.\nVITE_ENVIRONMENT=${environment}\nVITE_API_URL=https://api.${environment}.example.com\n`\n}\n\n/**\n * The per-environment build + packaging targets for a React app.\n *\n * @remarks\n * One `build-<env>` target per {@link REACT_ENVIRONMENTS} entry runs\n * `vite build --mode <env> --outDir dist-<env>`, so each environment gets its\n * own compiled-in `VITE_*` config. The `package` target depends on all three\n * and zips each `dist-<env>` into `dist/drop/react-app-<name>-<env>.zip` — one\n * artifact per environment. CI needs no change: the \"tag per app\" step derives\n * `##vso[build.addbuildtag]react-app-<name>-<env>` straight from the zip\n * filenames, so a classic release pipeline can deploy each environment from its\n * own artifact + tag. The inference-only `build` (default mode) stays for local\n * `nx build` and the CI verify step — every kind builds to its own Nx-default\n * location, no post-generation output-path redirection.\n *\n * @param name - The React app's project name.\n * @returns The Nx targets to merge into the app manifest's `nx` field.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactAppTargets (name: string): Record<string, unknown> {\n const targets: Record<string, unknown> = {}\n for (const environment of REACT_ENVIRONMENTS) {\n targets[`build-${environment}`] = {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],\n options: {\n command: `vite build --mode ${environment} --outDir dist-${environment}`,\n cwd: `apps/${name}`,\n },\n }\n }\n const zipStatements = REACT_ENVIRONMENTS.map(\n environment =>\n `z=new A();z.addLocalFolder('apps/${name}/dist-${environment}');z.writeZip('dist/drop/react-app-${name}-${environment}.zip')`,\n ).join(';')\n targets.package = {\n executor: 'nx:run-commands',\n dependsOn: REACT_ENVIRONMENTS.map(environment => `build-${environment}`),\n outputs: REACT_ENVIRONMENTS.map(\n environment => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`,\n ),\n options: {\n command: `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}\"`,\n },\n }\n\n return targets\n}\n\n/**\n * Ensures the committed per-environment `.env.<env>` files are not gitignored.\n *\n * @remarks\n * The `.env.<env>` files hold public config and are meant to be versioned, but\n * some presets ignore `.env*`. This appends an idempotent allow-block to the\n * root `.gitignore` so `apps/<app>/.env.<env>` stays tracked regardless (a\n * no-op when nothing ignored them). File-level negation works because the\n * parent `apps/<app>` directory is never ignored.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction allowEnvFiles (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n const marker =\n '# MoNecromanCI: keep the committed per-environment Vite config (public VITE_ values)'\n const current = readFileSync(gitignorePath, 'utf8')\n if (current.includes(marker)) {\n return\n }\n const allow = REACT_ENVIRONMENTS.map(environment => `!apps/*/.env.${environment}`).join('\\n')\n writeFileEnsured(gitignorePath, `${current.replace(/\\n*$/, '\\n')}\\n${marker}\\n${allow}\\n`)\n}\n\n/**\n * Adds a React app: `@nx/react:app` (Vite) plus per-environment build/package targets.\n *\n * @remarks\n * Pure delegation to the generator, then the one thing it doesn't do: a React\n * SPA bakes its `VITE_` config in at build time, so it needs one build per\n * environment. Scaffolds a `.env.<env>` per {@link REACT_ENVIRONMENTS} and\n * attaches the per-env build + packaging targets (inference-only app, so via\n * the manifest's `nx` field). Each env produces its own dist/drop zip + tag.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addReactApp (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n ensurePlugin(workspaceRoot, '@nx/react')\n runNx(\n [\n 'g',\n '@nx/react:app',\n `apps/${name}`,\n '--bundler=vite',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--style=css',\n '--e2eTestRunner=none',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n ensureAdmZip(workspaceRoot)\n const reactAppRoot = join(workspaceRoot, 'apps', name)\n for (const environment of REACT_ENVIRONMENTS) {\n writeFileEnsured(join(reactAppRoot, `.env.${environment}`), reactEnvironmentFile(environment))\n }\n allowEnvFiles(workspaceRoot)\n addNxTargets(join(reactAppRoot, 'package.json'), reactAppTargets(name))\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n // Vite's inferred 'serve' target (the dev server) is what @nx/react:app\n // already wires — no per-env variant needed for local dev, unlike build.\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:serve` })\n}\n","import { join } from 'node:path'\nimport { promptText } from '../terminal'\nimport { repairDeclarationSpecifiers, repairPublishableManifest } from '../rollup-library'\nimport { removeLocalRegistryScaffolding } from '../workspace-overlay'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n runGeneratorAndRepair,\n writeProjectReadme,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * The bundler both React library kinds are generated with.\n *\n * @remarks\n * `@nx/react:library` defaults to `none`, and that default is wrong for both\n * kinds here, for two different reasons:\n *\n * - For a **publishable** lib, a bundler is what lets a published package depend\n * on a private internal lib at all: `@nx/rollup`'s `withNx` externalizes\n * exactly the manifest's `dependencies`/`peerDependencies`, so an imported\n * internal lib (never declared, since npm workspaces links it regardless) is\n * compiled INTO the bundle and its private name never reaches the published\n * `package.json`. Same reasoning as `npm-lib`.\n * - For an **internal** lib, it has to be *buildable*: the default\n * `@nx/enforce-module-boundaries` rule forbids a buildable library (every\n * publishable lib) from importing a non-buildable one, so a non-buildable\n * React lib could not be consumed by a `react-lib` or `npm-lib` at all.\n *\n * `rollup` rather than `vite` because these are libraries, not apps — and it\n * keeps both React kinds on the same bundler as `npm-lib`, so there is one\n * externalization story to reason about rather than two. Note `tsc` is not an\n * option here: unlike `@nx/js:lib`, this generator's enum is\n * `none | vite | rollup` (verified against the real 23.1.0 schema).\n */\nconst REACT_LIB_BUNDLER = 'rollup'\n\n/**\n * Adds a publishable React component library under `packages/`.\n *\n * @remarks\n * The React counterpart of `npm-lib`, and the kind that was missing entirely —\n * before this, a shared component library could not be built at all, because\n * `npm-lib` and `internal-lib` both use `@nx/js:lib`, which has no JSX support.\n *\n * Pure delegation to the official `@nx/react:library`, plus the same two\n * post-generation touches `npm-lib` needs: {@link markPublic} (npm treats every\n * scoped package as private, so a first publish 402s without it) and deleting the\n * per-project ESLint config the generator writes.\n *\n * The scope is resolved the same way `npm-lib` resolves it: an explicit\n * `--scope` wins; otherwise the flag path defaults it silently while the\n * interactive path prompts, with the workspace's own scope as the default.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @param stack - The workspace's chosen test runner.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its plugin install\n * step failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addReactLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n stack: WorkspaceStack,\n): Promise<void> {\n ensurePlugin(workspaceRoot, '@nx/react')\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n const projectRoot = join(workspaceRoot, 'packages', name)\n const publishableManifest = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/react:library',\n `packages/${name}`,\n `--name=${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n `--bundler=${REACT_LIB_BUNDLER}`,\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n publishableManifest,\n () => {\n markPublic(publishableManifest)\n repairPublishableManifest(publishableManifest)\n repairDeclarationSpecifiers(projectRoot)\n writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n // `--publishable` also scaffolds a whole local-registry story (verdaccio\n // config, devDependency, root target) that mnci's tag-only release model\n // has no use for.\n removeLocalRegistryScaffolding(workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n\n/**\n * Adds a private React component library under `libs/`.\n *\n * @remarks\n * Same generator as {@link addReactLib} minus the publishable intent: it lands in\n * `libs/` (never released — the directory *is* the release scoping) and is marked\n * `private` so it is structurally unpublishable no matter what config drifts\n * later. This is the common case for a shared component library consumed by apps\n * in the same monorepo.\n *\n * Still built with {@link REACT_LIB_BUNDLER} rather than left non-buildable —\n * see that constant for why `enforce-module-boundaries` makes that load-bearing.\n *\n * No `start` command: a library has no dev-server story, so\n * {@link registerProjectCommands} gets `build` and the unconditional `qa` only.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen test runner.\n * @returns Nothing.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its plugin install\n * step failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addReactInternalLib (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n): void {\n ensurePlugin(workspaceRoot, '@nx/react')\n const projectRoot = join(workspaceRoot, 'libs', name)\n const privateManifest = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/react:library',\n `libs/${name}`,\n `--name=${name}`,\n `--bundler=${REACT_LIB_BUNDLER}`,\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n privateManifest,\n () => {\n markPrivate(privateManifest)\n repairPublishableManifest(privateManifest)\n repairDeclarationSpecifiers(projectRoot)\n writeProjectReadme(projectRoot, name, stack.testRunner)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n","import { readFileSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport { VSCODE_EXTENSION_TAG } from '../workspace-overlay'\nimport { runNodeApp } from './node.use-case'\nimport {\n addNxTargets,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Where the packaging and publishing script lives in a generated workspace.\n *\n * @remarks\n * A workspace-relative file rather than a seventh mnci package, the same call as\n * `tools/csharp-version-actions.cjs`: the targets need real logic (a loop over the\n * Marketplace platforms, a staging copy of the sidecar, a publish gate), the\n * workspace has no mnci runtime dependency to put it in, and a one-liner holding\n * all of it would be unreadable. mnci owns it: `mnci add vscode-extension` writes\n * it and `mnci upgrade` rewrites it whenever an extension exists.\n */\nexport const VSCODE_EXTENSION_SCRIPT_PATH = 'tools/vscode-extension.cjs'\n\n/**\n * The Marketplace targets a sidecar build packages, and the Go platform each takes its binary from.\n *\n * @remarks\n * The right-hand side is the directory a `go-app`'s `build-all` writes under\n * `dist/platforms/<app>/` (#226). Alpine reuses the Linux binaries: they are static\n * (`CGO_ENABLED=0`), so musl needs nothing of its own, and without an Alpine\n * package the Marketplace offers an Alpine user nothing at all, since a sidecar\n * build publishes no universal package to fall back on. `linux-armhf` and `web`\n * are left out: there is no Go build for the first, and a browser cannot run a\n * native binary.\n */\nexport const VSCODE_SIDECAR_TARGETS: Readonly<Record<string, string>> = {\n 'win32-x64': 'windows-amd64',\n 'win32-arm64': 'windows-arm64',\n 'linux-x64': 'linux-amd64',\n 'linux-arm64': 'linux-arm64',\n 'alpine-x64': 'linux-amd64',\n 'alpine-arm64': 'linux-arm64',\n 'darwin-x64': 'darwin-amd64',\n 'darwin-arm64': 'darwin-arm64',\n}\n\n/**\n * Renders a string map as an object literal in the house style.\n *\n * @remarks\n * The script is linted by the workspace it is written into (#249), and\n * `mnci upgrade` rewrites it without formatting, so it must be clean as written:\n * `JSON.stringify` would give double quotes and no spacing.\n *\n * @param map - The keys and values to render.\n * @returns A one-line object literal with single-quoted keys and values.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction scriptObjectLiteral (map: Readonly<Record<string, string>>): string {\n return `{ ${Object.entries(map).map(([key, value]) => `'${key}': '${value}'`).join(', ')} }`\n}\n\n/**\n * The `tools/vscode-extension.cjs` script: `package` and `publish` for every extension.\n *\n * @remarks\n * - **No shell, no path into `node_modules`.** `vsce` and `nx` are resolved through\n * their own `package.json` `bin` field and run with the current `node`, so a\n * workspace path with a space in it works on Windows and an upgrade of either\n * tool that moves its entry file changes nothing here.\n * - **`--no-dependencies`** is mandatory in npm workspaces: dependencies are hoisted\n * to the root, so `vsce`'s own `npm list` walk fails, and the build is bundled\n * anyway (`thirdParty: true`), so there is nothing to ship.\n * - **The sidecar is stamped with the extension's version.** `build-all` is run from\n * here with `VERSION` set to the manifest's version rather than declared as a\n * `dependsOn`, which cannot pass a different environment to one dependency. In a\n * release that is the version `nx release` just wrote, so the engine and the\n * extension it ships in always agree.\n * - **`publish` is gated on a credential**, the same pattern as the C# NuGet\n * target: the target must exist even when nothing is configured, because Nx\n * throws when no project in a release group carries `nx-release-publish`.\n * `VSCE_AUTH=entra` (set by the GitHub release step when the `AZURE_CLIENT_ID`\n * variable exists, after `azure/login`) publishes with `--azure-credential`:\n * Microsoft Entra ID through OIDC, no stored secret (#253). It wins over\n * `VSCE_PAT`, a Marketplace personal access token, which remains the fallback.\n * Azure Pipelines leaves an undefined `$(VSCE_PAT)` macro as that literal text,\n * so that reads as unset too. The signal is explicit rather than \"is\n * `AZURE_CLIENT_ID` set\": `azure/login` exports no variable, and exporting\n * `AZURE_CLIENT_ID` would steer `@azure/identity` towards a managed identity the\n * runner does not have.\n * - **A `#!/usr/bin/env node` line**, though it is always run as `node <file>`:\n * it is how `unicorn/no-process-exit` recognises a command-line program, the\n * one place an exit code is the interface.\n * - **A dry run never publishes.** `nx release --dry-run --yes` still runs every\n * `nx-release-publish` target, handing it `--dryRun=true` and `NX_DRY_RUN`\n * (measured); the script prints what it would publish and stops, even with a\n * token present.\n * - **`--skip-duplicate`** makes a re-run of a release that already reached the\n * Marketplace a no-op rather than a failure, the role `--skip-existing` plays for\n * twine.\n */\nexport const VSCODE_EXTENSION_SCRIPT = String.raw`#!/usr/bin/env node\n// Written by mnci: 'mnci add vscode-extension' creates it and 'mnci upgrade'\n// rewrites it, so local edits do not survive an upgrade.\n//\n// Packages and publishes a VS Code extension project. Its Nx targets call it:\n// node tools/vscode-extension.cjs package apps/<name> [--sidecar <go-app>]\n// node tools/vscode-extension.cjs publish apps/<name> [--sidecar <go-app>]\n// Without --sidecar: one universal dist/drop/<project>.vsix.\n// With --sidecar: one dist/drop/<project>-<target>.vsix per Marketplace target, each\n// carrying that platform's binary from the Go app's build-all in bin/.\n'use strict'\nconst { spawnSync } = require('node:child_process')\nconst { cpSync, existsSync, mkdirSync, readFileSync, rmSync } = require('node:fs')\nconst { basename, dirname, join, resolve } = require('node:path')\n\nconst TARGETS = ${scriptObjectLiteral(VSCODE_SIDECAR_TARGETS)}\nconst DROP = resolve('dist/drop')\nconst VSCE_FLAGS = ['--no-dependencies', '--skip-license', '--allow-missing-repository']\n\nfunction fail (message) {\n console.error(message)\n process.exit(1)\n}\n\nfunction bin (packageName, command) {\n const manifestPath = require.resolve(packageName + '/package.json', { paths: [process.cwd()] })\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))\n const entry = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin[command]\n\n return join(dirname(manifestPath), entry)\n}\n\nfunction run (packageName, command, args, options) {\n const result = spawnSync(process.execPath, [bin(packageName, command), ...args], { stdio: 'inherit', ...options })\n if (result.status !== 0) process.exit(result.status ?? 1)\n}\n\nfunction vsixFiles (name, sidecar) {\n return sidecar\n ? Object.keys(TARGETS).map(target => join(DROP, name + '-' + target + '.vsix'))\n : [join(DROP, name + '.vsix')]\n}\n\nfunction packageExtension (projectRoot, manifest, sidecar) {\n mkdirSync(DROP, { recursive: true })\n if (!sidecar) {\n run('@vscode/vsce', 'vsce', ['package', ...VSCE_FLAGS, '--out', vsixFiles(project)[0]], { cwd: projectRoot })\n\n return\n }\n run('nx', 'nx', ['run', sidecar + ':build-all'], { env: { ...process.env, VERSION: manifest.version } })\n const staging = join(projectRoot, 'bin')\n try {\n for (const [target, platform] of Object.entries(TARGETS)) {\n const source = join('dist/platforms', sidecar, platform)\n if (!existsSync(source)) fail('No ' + platform + ' build of ' + sidecar + ' at ' + source + ' - its build-all target did not write one.')\n rmSync(staging, { recursive: true, force: true })\n cpSync(source, staging, { recursive: true })\n run('@vscode/vsce', 'vsce', ['package', '--target', target, ...VSCE_FLAGS, '--out', join(DROP, project + '-' + target + '.vsix')], { cwd: projectRoot })\n }\n } finally {\n rmSync(staging, { recursive: true, force: true })\n }\n}\n\nfunction publishExtension (manifest, sidecar, dryRun) {\n if (dryRun) {\n console.log('Dry run - would publish ' + vsixFiles(project, sidecar).join(', ') + ' to the Marketplace.')\n\n return\n }\n const entra = process.env.VSCE_AUTH === 'entra'\n const token = process.env.VSCE_PAT ?? ''\n if (!entra && (token === '' || /^\\$\\(.*\\)$/.test(token))) {\n console.log('No Marketplace credential - skipping the Marketplace publish of ' + manifest.name + '. Set the AZURE_CLIENT_ID and AZURE_TENANT_ID variables (Microsoft Entra ID) or the VSCE_PAT secret to publish.')\n\n return\n }\n const files = vsixFiles(project, sidecar)\n const missing = files.filter(file => !existsSync(file))\n if (missing.length > 0) fail('Nothing to publish: ' + missing.join(', ') + ' not found - run the package target first.')\n run('@vscode/vsce', 'vsce', ['publish', ...(entra ? ['--azure-credential'] : []), '--packagePath', ...files, '--skip-duplicate'])\n}\n\nconst [command, projectRoot, ...rest] = process.argv.slice(2)\nconst sidecarIndex = rest.indexOf('--sidecar')\nconst sidecar = sidecarIndex === -1 ? undefined : rest[sidecarIndex + 1]\nif (!projectRoot || !existsSync(join(projectRoot, 'package.json'))) fail('Usage: node tools/vscode-extension.cjs package|publish <project root> [--sidecar <go-app>]')\nconst manifest = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'))\n// Packages are named after the project folder, never the manifest name, which is\n// free to change (the Marketplace id need not match the folder).\nconst project = basename(resolve(projectRoot))\n// nx release --dry-run hands the publish target --dryRun=true and sets NX_DRY_RUN\n// (measured on Nx 23), so a dry run never reaches the Marketplace, token or not.\nconst dryRun = rest.some(argument => /^--dry-?run(?:=true)?$/i.test(argument)) || process.env.NX_DRY_RUN === 'true'\nif (command === 'package') packageExtension(projectRoot, manifest, sidecar)\nelse if (command === 'publish') publishExtension(manifest, sidecar, dryRun)\nelse fail('Unknown command ' + command + ' - expected package or publish.')\n`\n\n/**\n * The `.vscodeignore` written into every extension.\n *\n * @remarks\n * The build is bundled into `dist/main.js`, so everything else in the project\n * folder is source or tooling. `**\\/*.d.ts` matters: declarations cannot be turned\n * off at build time (`declaration: false` collides with the workspace's\n * `composite`/`declarationMap` and dies on TS5069, measured), so they are dropped\n * here instead. `bin/` is deliberately NOT ignored: it is where a sidecar is staged.\n */\nexport const VSCODE_IGNORE = `src/**\ntest/**\nout-tsc/**\ntest-output/**\n**/*.map\n**/*.d.ts\n**/*.spec.*\ntsconfig*.json\njest.config.*\nvitest.config.*\n.spec.swcrc\nnode_modules/**\n`\n\n/**\n * The sample entry point: `activate` registers one command.\n *\n * @remarks\n * `src/main.ts`, not VS Code's customary `extension.ts`: the slice rules allow only\n * `index` and `main` at the root of `src`. `activationEvents` stays empty because VS\n * Code (since 1.74) activates on the contributed command by itself.\n *\n * @param name - The project name, which prefixes the command id.\n * @returns The file content.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionMain (name: string): string {\n return `import * as vscode from 'vscode'\n\nexport function activate (context: vscode.ExtensionContext): void {\n context.subscriptions.push(\n vscode.commands.registerCommand('${name}.hello', () => {\n void vscode.window.showInformationMessage('Hello from ${name}')\n }),\n )\n}\n\nexport function deactivate (): void {}\n`\n}\n\n/**\n * The sample spec, which runs `activate` against the `vscode` stub.\n *\n * @remarks\n * Written against the real `vscode` types and runner-neutral (`describe`/`it`/\n * `expect` are globals under both Jest and Vitest here), so the same file serves\n * either stack and typechecks without knowing the stub exists.\n *\n * @param name - The project name, which prefixes the command id.\n * @returns The file content.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionMainSpec (name: string): string {\n return `import * as vscode from 'vscode'\nimport { activate } from './main'\n\ndescribe('activate', () => {\n it('registers the ${name}.hello command', async () => {\n const context = { subscriptions: [] } as unknown as vscode.ExtensionContext\n\n activate(context)\n\n expect(context.subscriptions).toHaveLength(1)\n expect(await vscode.commands.getCommands()).toContain('${name}.hello')\n })\n})\n`\n}\n\n/**\n * Stands in for the `vscode` module in unit tests.\n *\n * @remarks\n * `vscode` exists only inside the extension host: it is injected at runtime and no\n * package by that name is ever installed (`@types/vscode` carries only types). So\n * a spec that imports anything importing it fails to resolve. The test runner maps\n * the specifier here instead (Jest `moduleNameMapper`, Vitest `alias`). It covers\n * what the sample uses and nothing more; it grows with the extension.\n */\nexport const VSCODE_STUB = `// Stands in for the 'vscode' module in unit tests: that module exists only\n// inside the extension host. Extend it as the extension reaches for more API.\nconst registered = new Map<string, (...arguments_: unknown[]) => unknown>()\n\nexport const commands = {\n registerCommand (id: string, handler: (...arguments_: unknown[]) => unknown): { dispose: () => void } {\n registered.set(id, handler)\n\n return { dispose: () => { registered.delete(id) } }\n },\n getCommands (): Promise<string[]> {\n return Promise.resolve(registered.keys().toArray())\n },\n executeCommand (id: string, ...arguments_: unknown[]): Promise<unknown> {\n return Promise.resolve(registered.get(id)?.(...arguments_))\n },\n}\n\nexport const window = {\n showInformationMessage (_message: string): Promise<undefined> {\n return Promise.resolve(undefined)\n },\n}\n`\n\n/** Where {@link VSCODE_STUB} is written, relative to the project root. */\nconst VSCODE_STUB_PATH = 'test/vscode.stub.ts'\n\n/**\n * Points the project's test runner at {@link VSCODE_STUB} for the `vscode` specifier.\n *\n * @remarks\n * Edits the config the generator just wrote at a line it always writes\n * (`testEnvironment: 'node',` for Jest, `environment: 'node',` for Vitest), and\n * throws if that anchor is gone rather than leaving a project whose first test of\n * real extension code fails to resolve `vscode` with no hint why.\n *\n * @param projectRoot - Absolute path to the extension project.\n * @param testRunner - The workspace's test runner.\n * @returns Nothing.\n * @throws Error when the generated config no longer has the expected anchor line.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction mapVscodeToStub (projectRoot: string, testRunner: WorkspaceStack['testRunner']): void {\n const [file, anchor, mapping] = testRunner === 'jest'\n ? ['jest.config.cts', \"testEnvironment: 'node',\", `moduleNameMapper: { '^vscode$': '<rootDir>/${VSCODE_STUB_PATH}' },`]\n : ['vitest.config.mts', \"environment: 'node',\", `alias: { vscode: \\`\\${import.meta.dirname}/${VSCODE_STUB_PATH}\\` },`]\n const path = join(projectRoot, file)\n const content = fileExists(path) ? readFileSync(path, 'utf8') : ''\n const line = content.split('\\n').find(candidate => candidate.trim() === anchor)\n if (line === undefined) {\n throw new Error(`${file} has no \"${anchor}\" line to map the vscode module after; map 'vscode' to ${VSCODE_STUB_PATH} by hand.`)\n }\n const indent = line.slice(0, line.indexOf(anchor))\n // A replacer function, not a string: the Jest mapping holds `'^vscode$'`, and in a\n // replacement STRING `$'` means \"the text after the match\", which spliced the rest\n // of the file into the middle of the line (caught by the spec).\n writeFileEnsured(path, content.replace(line, () => `${line}\\n${indent}${mapping}`))\n}\n\n/**\n * Installs the extension toolchain at the workspace root.\n *\n * @remarks\n * Root devDependencies, per the shared-dev-packages invariant: `@types/vscode` for\n * the API types and `@vscode/vsce` for packaging and publishing. Skipped when both\n * are already declared, so a second extension installs nothing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureVscodeToolchain (workspaceRoot: string): void {\n const missing = ['@types/vscode', '@vscode/vsce'].filter(name => !hasPlugin(workspaceRoot, name))\n if (missing.length === 0) {\n return\n }\n logger.step(`Installing the VS Code extension toolchain (${missing.join(', ')})`)\n if (runShell('npm', ['install', '--save-dev', ...missing, '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\n throw new Error(`npm install of ${missing.join(', ')} failed`)\n }\n}\n\n/**\n * The publisher a new extension gets when `--publisher` is not given.\n *\n * @remarks\n * The workspace's npm scope without the `@`, which is usually the organisation's\n * name and so a sensible first guess. It only has to be right before the first\n * publish: the Marketplace rejects a publisher id the token does not own.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns A publisher id.\n * @throws Never - an unreadable or unscoped root name falls back to the bare name.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction defaultPublisher (workspaceRoot: string): string {\n const { name } = readJson<{ name?: string }>(join(workspaceRoot, 'package.json'))\n\n return (name ?? 'publisher').replace(/^@/, '').split('/', 1)[0]\n}\n\n/**\n * Reshapes the generated manifest into an extension manifest and rewires its build.\n *\n * @remarks\n * - `name` loses its scope: `vsce` rejects scoped names. `nx.name` pins the Nx\n * project name to the folder, so `name` and `displayName` are free to change\n * afterwards (the Marketplace id need not match the folder, #247); without it Nx\n * takes the project name from `name`, and renaming the extension would orphan\n * every root script, task and launch entry that names the project.\n * - `engines.vscode` is pinned to the installed `@types/vscode` (`vsce` refuses a\n * package whose types are newer than its engine range).\n * - The build bundles (`bundle`, `thirdParty`): a `.vsix` ships no `node_modules`,\n * so the generator's un-bundled mirror of the source tree cannot run in the\n * extension host. `vscode` stays external, it is the host's.\n * `generatePackageJson: false`, because a `dist/package.json` confuses `vsce`.\n * - The generator's `serve` and prune targets go: an extension is not started with\n * `node`, and it is never deployed with an installed `node_modules`.\n *\n * @param projectRoot - Absolute path to the extension project.\n * @param name - The project name.\n * @param publisher - The Marketplace publisher id.\n * @param typesVersion - The installed `@types/vscode` version.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reshapeManifest (projectRoot: string, name: string, publisher: string, typesVersion: string): void {\n const manifestPath = join(projectRoot, 'package.json')\n const manifest = readJson<Record<string, unknown> & {\n nx?: { tags?: string[]; targets?: Record<string, { options?: Record<string, unknown> }> }\n }>(manifestPath)\n const { nx = {}, ...rest } = manifest\n const targets = { ...nx.targets }\n for (const target of ['serve', 'prune', 'prune-lockfile', 'copy-workspace-modules']) {\n delete targets[target]\n }\n const build = targets.build ?? {}\n targets.build = {\n ...build,\n options: {\n ...build.options,\n bundle: true,\n thirdParty: true,\n external: ['vscode'],\n generatePackageJson: false,\n },\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...rest,\n name,\n displayName: name,\n version: rest.version ?? '0.0.1',\n publisher,\n engines: { vscode: `^${typesVersion}` },\n categories: ['Other'],\n main: './dist/main.js',\n activationEvents: [],\n contributes: { commands: [{ command: `${name}.hello`, title: `${name}: Hello` }] },\n nx: { ...nx, name, tags: [...new Set([...(nx.tags ?? []), VSCODE_EXTENSION_TAG])], targets },\n }),\n )\n}\n\n/**\n * Adds the `vscode` types to a project tsconfig.\n *\n * @remarks\n * Both the app and the spec config need them: a `types` list stops TypeScript from\n * loading every `@types` package, and `vscode` is declared only by `@types/vscode`'s\n * ambient module, so without the entry `import * as vscode from 'vscode'` does not\n * resolve at all.\n *\n * @param tsconfigPath - Absolute path to the tsconfig.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addVscodeTypes (tsconfigPath: string): void {\n if (!fileExists(tsconfigPath)) {\n return\n }\n const tsconfig = readJson<{ compilerOptions?: { types?: string[] } }>(tsconfigPath)\n const compilerOptions = tsconfig.compilerOptions ?? {}\n const types = compilerOptions.types ?? []\n if (types.includes('vscode')) {\n return\n }\n writeFileEnsured(tsconfigPath, toJson({ ...tsconfig, compilerOptions: { ...compilerOptions, types: [...types, 'vscode'] } }))\n}\n\n/**\n * The `package` target: one universal `.vsix`, or one per Marketplace target with a sidecar.\n *\n * @remarks\n * Outputs are listed one per file rather than as a glob, so Nx caches and restores\n * exactly the packages this project writes and never another project's in the\n * shared `dist/drop`. The work is in {@link VSCODE_EXTENSION_SCRIPT}.\n *\n * @param name - The project name.\n * @param sidecar - The `go-app` whose binaries ship in `bin/`, when any.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionPackageTarget (name: string, sidecar?: string): Record<string, unknown> {\n const outputs = sidecar\n ? Object.keys(VSCODE_SIDECAR_TARGETS).map(target => `{workspaceRoot}/dist/drop/${name}-${target}.vsix`)\n : [`{workspaceRoot}/dist/drop/${name}.vsix`]\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs,\n options: { command: `node ${VSCODE_EXTENSION_SCRIPT_PATH} package apps/${name}${sidecar ? ` --sidecar ${sidecar}` : ''}` },\n }\n}\n\n/**\n * The `nx-release-publish` target: publish the packaged `.vsix` files to the Marketplace.\n *\n * @remarks\n * Depends on `package`, so it packages the version `nx release` has just written,\n * never a stale one. Gated on a Marketplace credential (Entra ID or `VSCE_PAT`) inside the script (see\n * {@link VSCODE_EXTENSION_SCRIPT}).\n *\n * @param name - The project name.\n * @param sidecar - The `go-app` whose binaries ship in `bin/`, when any.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionPublishTarget (name: string, sidecar?: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n dependsOn: ['package'],\n options: { command: `node ${VSCODE_EXTENSION_SCRIPT_PATH} publish apps/${name}${sidecar ? ` --sidecar ${sidecar}` : ''}` },\n }\n}\n\n/**\n * Adds a `<name>: debug` launch configuration and the task it builds with.\n *\n * @remarks\n * `extensionHost` opens a second VS Code window with the extension loaded from\n * `apps/<name>`. The pre-launch task builds the `development` configuration, which\n * keeps source maps, so breakpoints in `src/` bind. Named `<name>: debug`, never\n * `mnci: …`: the overlay replaces every launch entry with that prefix on\n * `mnci upgrade`, and this one is per-project state, like the tasks.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addDebugLaunch (workspaceRoot: string, name: string): void {\n const file = readdirSync(workspaceRoot).find(entry => entry.endsWith('.code-workspace'))\n if (file === undefined) {\n return\n }\n const path = join(workspaceRoot, file)\n const workspace = readCodeWorkspace<{\n folders?: { name?: string }[]\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n launch?: { version?: string; configurations?: Record<string, unknown>[] }\n }>(path) ?? {}\n const folder = `\\${workspaceFolder:${workspace.folders?.[0]?.name ?? file.replace(/\\.code-workspace$/, '')}}`\n const taskLabel = `${name}: build (development)`\n const launchName = `${name}: debug`\n const tasks = (workspace.tasks?.tasks ?? []).filter(task => task.label !== taskLabel)\n const configurations = (workspace.launch?.configurations ?? []).filter(configuration => configuration.name !== launchName)\n writeFileEnsured(\n path,\n toJson({\n ...workspace,\n tasks: {\n version: workspace.tasks?.version ?? '2.0.0',\n tasks: [...tasks, { label: taskLabel, type: 'shell', command: `npx nx run ${name}:build:development`, problemMatcher: [] }],\n },\n launch: {\n version: workspace.launch?.version ?? '0.2.0',\n configurations: [\n ...configurations,\n {\n name: launchName,\n type: 'extensionHost',\n request: 'launch',\n args: [`--extensionDevelopmentPath=${folder}/apps/${name}`],\n outFiles: [`${folder}/apps/${name}/dist/**/*.js`],\n preLaunchTask: taskLabel,\n },\n ],\n },\n }),\n )\n}\n\n/**\n * Rewrites {@link VSCODE_EXTENSION_SCRIPT_PATH} when the workspace has an extension.\n *\n * @remarks\n * Called by `mnci upgrade`, so a fix to the script reaches every workspace that\n * uses it, and a workspace with no extension never gains the file.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Whether the file changed (an up-to-date script is left alone).\n * @throws Propagates any `fs` error writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function refreshVscodeExtensionScript (workspaceRoot: string): boolean {\n const apps = join(workspaceRoot, 'apps')\n let entries: string[]\n try {\n entries = readdirSync(apps)\n } catch {\n return false\n }\n const hasExtension = entries.some((entry) => {\n const manifestPath = join(apps, entry, 'package.json')\n if (!fileExists(manifestPath)) {\n return false\n }\n const tags = readJson<{ nx?: { tags?: string[] } }>(manifestPath).nx?.tags ?? []\n\n return tags.includes(VSCODE_EXTENSION_TAG)\n })\n const path = join(workspaceRoot, VSCODE_EXTENSION_SCRIPT_PATH)\n if (!hasExtension || (fileExists(path) && readFileSync(path, 'utf8') === VSCODE_EXTENSION_SCRIPT)) {\n return false\n }\n writeFileEnsured(path, VSCODE_EXTENSION_SCRIPT)\n\n return true\n}\n\n/**\n * Pins `nx.name` on every extension that lacks it.\n *\n * @remarks\n * Extensions added by `@mnci/cli` 4.12.0 have no `nx.name`, so their Nx project\n * name still comes from the manifest `name` (#247). Pinning it to that same value\n * changes nothing today and makes a later rename of the extension safe.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative manifests that were updated.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pinVscodeExtensionProjectNames (workspaceRoot: string): string[] {\n const apps = join(workspaceRoot, 'apps')\n let entries: string[]\n try {\n entries = readdirSync(apps)\n } catch {\n return []\n }\n const updated: string[] = []\n for (const entry of entries) {\n const manifestPath = join(apps, entry, 'package.json')\n if (!fileExists(manifestPath)) {\n continue\n }\n const manifest = readJson<{ name?: string; nx?: { name?: string; tags?: string[] } }>(manifestPath)\n if (!manifest.nx?.tags?.includes(VSCODE_EXTENSION_TAG) || manifest.nx.name !== undefined || manifest.name === undefined) {\n continue\n }\n writeFileEnsured(manifestPath, toJson({ ...manifest, nx: { ...manifest.nx, name: manifest.name } }))\n updated.push(`apps/${entry}/package.json`)\n }\n\n return updated\n}\n\n/**\n * What `mnci add vscode-extension` takes beyond the name.\n *\n * @remarks\n * Both come from CLI flags (`--publisher`, `--sidecar`); neither is prompted for,\n * because both have a sensible absence: a default publisher and no sidecar.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface VscodeExtensionOptions {\n /** The Marketplace publisher id; defaults to the workspace scope without `@`. */\n publisher?: string\n /** A `go-app` whose six-platform build ships inside the extension, in `bin/`. */\n sidecar?: string\n}\n\n/**\n * Re-syncs `package-lock.json` with the reshaped manifest.\n *\n * @remarks\n * `apps/*` is an npm workspace, so the lock records each app under its manifest\n * `name`. The generator installed under its scoped name, and\n * {@link reshapeManifest} unscopes it, which `vsce` requires. Left alone, the lock\n * still names the old package, and CI's `npm ci` refuses it (\"Missing: <name> from\n * lock file\", #251, measured on a real workspace). A failure here only warns, as\n * the root-dependency relocation does: the project exists, and `npm install` fixes\n * the lock.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name, for the message.\n * @returns Nothing.\n * @throws Never - a failed refresh is reported, not thrown.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction refreshLockFile (workspaceRoot: string, name: string): void {\n if (runShell('npm', ['install', '--package-lock-only', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\n logger.warn(`Could not refresh package-lock.json after reshaping ${name}'s manifest. Run 'npm install' before committing.`)\n }\n}\n\n/**\n * Adds a VS Code extension: `@nx/node:application`, bundled, packaged with `vsce`.\n *\n * @remarks\n * The generator gives a TypeScript app with the workspace's test runner; this turns\n * it into an extension (see {@link reshapeManifest}) and adds what a Marketplace\n * extension needs: `package` (`.vsix` into `dist/drop`, per platform with a\n * `--sidecar`), `nx-release-publish`, a `vscode` stub for unit tests, a\n * `.vscodeignore`, and an Extension Development Host launch entry. Tagged\n * {@link VSCODE_EXTENSION_TAG}, which puts it in `nx release` scope.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's test runner.\n * @param options - Publisher and sidecar.\n * @returns Nothing.\n * @throws Error when the sidecar is not a Go app, or a generator or install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addVscodeExtension (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n options: VscodeExtensionOptions = {},\n): void {\n const { sidecar } = options\n if (sidecar !== undefined && !fileExists(join(workspaceRoot, 'apps', sidecar, 'project.json'))) {\n throw new Error(`--sidecar ${sidecar}: no Go app at apps/${sidecar}. Add it first with \\`mnci add go-app ${sidecar}\\`.`)\n }\n runNodeApp(workspaceRoot, name, stack, 'none')\n ensureVscodeToolchain(workspaceRoot)\n const projectRoot = join(workspaceRoot, 'apps', name)\n const typesVersion = readJson<{ version: string }>(join(workspaceRoot, 'node_modules/@types/vscode/package.json')).version\n reshapeManifest(projectRoot, name, options.publisher ?? defaultPublisher(workspaceRoot), typesVersion)\n addVscodeTypes(join(projectRoot, 'tsconfig.app.json'))\n addVscodeTypes(join(projectRoot, 'tsconfig.spec.json'))\n writeFileEnsured(join(projectRoot, 'src/main.ts'), vscodeExtensionMain(name))\n writeFileEnsured(join(projectRoot, 'src/main.spec.ts'), vscodeExtensionMainSpec(name))\n writeFileEnsured(join(projectRoot, VSCODE_STUB_PATH), VSCODE_STUB)\n mapVscodeToStub(projectRoot, stack.testRunner)\n writeFileEnsured(join(projectRoot, '.vscodeignore'), VSCODE_IGNORE)\n writeFileEnsured(join(workspaceRoot, VSCODE_EXTENSION_SCRIPT_PATH), VSCODE_EXTENSION_SCRIPT)\n addNxTargets(join(projectRoot, 'package.json'), {\n 'package': vscodeExtensionPackageTarget(name, sidecar),\n 'nx-release-publish': vscodeExtensionPublishTarget(name, sidecar),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n addDebugLaunch(workspaceRoot, name)\n refreshLockFile(workspaceRoot, name)\n}\n","import { join } from 'node:path'\nimport {\n ECOSYSTEMS,\n PEER_SECTION,\n ROOT_LABEL,\n collectInventory,\n hasEcosystem,\n isWorkspaceProject,\n resolvedVersion,\n rewriteSpec,\n type DependencySite,\n type Ecosystem,\n} from './manifest.repository'\nimport { highestSpec, rangeOperator, specVersion } from './semver.algorithm'\nimport { runFormatter, runShell } from '../nx-workspace'\nimport { fileExists } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * Options accepted by `mnci sync`.\n *\n * @remarks\n * Mirrors `nx sync` / `nx sync:check`: the bare command writes, `--check`\n * reports and fails. A CI step wants the second, a developer the first.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface SyncOptions {\n /** Report drift and exit non-zero without writing anything. */\n check?: boolean\n /** Restrict the run to one ecosystem. */\n ecosystem?: string\n}\n\n/**\n * One package declared at two or more incompatible versions.\n *\n * @remarks\n * `fixable` and `blocked` are separate lists rather than one list with a flag\n * because they need different endings: the first is rewritten, the second is\n * reported as a warning naming the spec that could not be understood. Collapsing\n * them is how a `git:` dependency quietly gets a version number written over it.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Drift {\n /** The package name. */\n name: string\n /** Which ecosystem it belongs to. */\n ecosystem: Ecosystem\n /** The spec every site should converge on. */\n target: string\n /** Why that spec won, for the report. */\n reason: 'resolved' | 'highest'\n /** The sites that disagree with `target` and can be rewritten. */\n fixable: DependencySite[]\n /** Sites that disagree but whose spec shape cannot safely be edited. */\n blocked: DependencySite[]\n}\n\n/**\n * Resolves the `--ecosystem` flag to the list of ecosystems to process.\n *\n * @remarks\n * Throws on an unknown value rather than silently processing nothing: a typo in\n * `--ecosystem` would otherwise produce a confident \"everything agrees\" over a\n * workspace that was never inspected.\n *\n * Shared with `mnci up` so the two commands cannot drift on what they accept.\n *\n * @param requested - The raw flag value, if any.\n * @returns The ecosystems to process.\n * @throws Error when the flag names something that is not an ecosystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveEcosystems (requested: string | undefined): readonly Ecosystem[] {\n if (!requested) {\n return ECOSYSTEMS\n }\n const match = ECOSYSTEMS.find(ecosystem => ecosystem === requested)\n if (!match) {\n throw new Error(\n `Unknown ecosystem '${requested}'. Expected one of: ${ECOSYSTEMS.join(', ')}.`,\n )\n }\n\n return [match]\n}\n\n/**\n * Finds every package declared at more than one version across the workspace.\n *\n * @remarks\n * The npm ecosystem has no `catalog:` — pnpm's one-version-per-workspace\n * mechanism — so keeping two projects on the same range is a convention nothing\n * enforces. This is the enforcement, and it deliberately extends to pip and pub\n * for the same reason.\n *\n * **Go is absent by construction, not by omission.** mnci's Go layout is a\n * single root `go.mod`, so one module cannot hold two versions of a dependency;\n * `runSync` says so explicitly rather than letting a silent zero-findings run\n * read as \"checked and fine\".\n *\n * **Peer ranges are excluded outright, not reported as blocked.** A peer range\n * says which versions a published package is compatible with, so two packages\n * declaring different ones are not in conflict — they are making different,\n * equally valid statements. Reporting them would be noise, and converging them\n * would narrow a published package's supported range. This exclusion exists\n * because the first run of this command against mnci's own repo reported six\n * findings, five of which were peers.\n *\n * The winning spec is the one matching what is actually **resolved** —\n * `node_modules` for npm, `pubspec.lock` for pub, the interpreter for pip.\n * That is the same source `@nx/dependency-checks` pins a drifted range to when\n * it auto-fixes, so this command and that lint rule converge on one answer\n * instead of overwriting each other. When nothing is installed, the highest\n * declared range wins instead — a fallback, and the report says which was used.\n *\n * Read-only: it never touches the workspace, so `runSync` can offer `--check`\n * by simply declining to call {@link applyDrift}.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to inspect.\n * @returns Every package whose declarations disagree.\n * @throws Never - unreadable manifests are skipped by the inventory.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectDrift (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[] = ECOSYSTEMS,\n): Drift[] {\n // Go has one manifest, so it can never disagree with itself.\n const reconcilable = ecosystems.filter(ecosystem => ecosystem !== 'go')\n const inventory = collectInventory(workspaceRoot, reconcilable)\n const drift: Drift[] = []\n\n for (const [name, allSites] of inventory) {\n const sites = allSites.filter(site => site.section !== PEER_SECTION)\n // A workspace-internal package is linked, not resolved from a registry;\n // its version is owned by `nx release`, not by this command.\n if (sites.length < 2 || isWorkspaceLocal(workspaceRoot, sites, name)) {\n continue\n }\n const distinct = new Set(sites.map(site => site.spec))\n if (distinct.size < 2) {\n continue\n }\n\n const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, name)\n const target = chooseTarget(sites, installed)\n if (!target) {\n continue\n }\n\n const disagreeing = sites.filter(site => site.spec !== target)\n drift.push({\n name,\n ecosystem: sites[0].ecosystem,\n target,\n reason: installed ? 'resolved' : 'highest',\n fixable: disagreeing.filter(site => site.rewritable),\n blocked: disagreeing.filter(site => !site.rewritable),\n })\n }\n\n return drift.toSorted((left, right) => left.name.localeCompare(right.name))\n}\n\n/**\n * Whether a package is one of this workspace's own projects.\n *\n * @remarks\n * A thin, ecosystem-guarded reading of `isWorkspaceProject`: only npm declares\n * an internal library by a name that maps to a directory, so pip and pub short-\n * circuit rather than testing a path that could accidentally match.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param sites - The declarations for one package name.\n * @param name - The package name.\n * @returns `true` when the package is a project in this workspace.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isWorkspaceLocal (\n workspaceRoot: string,\n sites: readonly DependencySite[],\n name: string,\n): boolean {\n // pip internal libs are vendored and pub ones are workspace members, so\n // neither is declared under a name this check could resolve.\n return sites[0].ecosystem === 'npm' && isWorkspaceProject(workspaceRoot, name)\n}\n\n/**\n * Picks the spec every site should converge on.\n *\n * @remarks\n * When something is installed, the target keeps the range operator the majority\n * of sites already use rather than imposing a caret — a workspace that pins\n * exactly stays pinned. Ties go to the caret, which is what every Nx generator\n * writes.\n *\n * @param sites - Every declaration of one package.\n * @param installed - The resolved version, when one exists.\n * @returns The spec to converge on, or `undefined` when none can be derived.\n * @throws Never - unparseable specs yield `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction chooseTarget (\n sites: readonly DependencySite[],\n installed: string | undefined,\n): string | undefined {\n if (!installed) {\n return highestSpec(sites.map(site => site.spec))\n }\n\n const counts = new Map<string, number>()\n for (const site of sites) {\n if (!specVersion(site.spec)) {\n continue\n }\n const operator = rangeOperator(site.spec)\n counts.set(operator, (counts.get(operator) ?? 0) + 1)\n }\n if (counts.size === 0) {\n return undefined\n }\n\n const [operator] = [...counts].toSorted(\n (left, right) => right[1] - left[1] || (left[0] === '^' ? -1 : 1),\n )[0]\n\n return `${operator}${installed}`\n}\n\n/**\n * Writes every fixable drift back to its manifest.\n *\n * @remarks\n * Deduplicates by manifest path, because one file commonly holds several of the\n * packages being converged and the caller reports what it changed.\n *\n * @param drift - The findings to apply.\n * @returns The manifest paths that actually changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function applyDrift (drift: readonly Drift[]): string[] {\n const changed = new Set<string>()\n for (const finding of drift) {\n for (const site of finding.fixable) {\n if (rewriteSpec(site, finding.target)) {\n changed.add(site.manifestPath)\n }\n }\n }\n\n return [...changed]\n}\n\n/**\n * Regenerates the workspace's TypeScript project references via `nx sync`.\n *\n * @remarks\n * The `--preset=ts` model resolves cross-project imports through TypeScript\n * project references, and those references are maintained by `nx sync` — not\n * by the generators. Without this, a freshly added project's references are\n * stale, so an editor (and a plain `tsc`) cannot resolve `@scope/lib` imports\n * between projects until the user runs `nx sync` by hand.\n *\n * Worth being precise about what this does and does not cover, because the\n * names invite the wrong assumption: `nx sync` runs the workspace's **sync\n * generators**, and the only one a generated workspace registers is\n * `@nx/js:typescript-sync`. It reconciles `tsconfig` project references and\n * nothing else — it has no opinion whatsoever about dependency versions. That\n * is what {@link collectDrift} is for, and why `mnci sync` does both.\n *\n * Non-fatal: `mnci add` calls this after a project is already generated, so a\n * sync failure only warns (with the manual command) rather than failing the run.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a non-zero `nx sync` is reported as a warning, not thrown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function syncProjectReferences (workspaceRoot: string): void {\n logger.step('Syncing TypeScript project references (nx sync)')\n if (runShell('npx', ['nx', 'sync'], workspaceRoot) !== 0) {\n logger.warn(\n 'nx sync did not complete — run `npx nx sync` yourself so cross-project imports resolve in your editor.',\n )\n }\n}\n\n/**\n * Describes one drift finding as a single report line.\n *\n * @param finding - The finding to describe.\n * @returns The formatted line.\n * @throws Never - performs string formatting only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction describe (finding: Drift): string {\n const sites = [...finding.fixable, ...finding.blocked]\n .map(site => `${site.project === ROOT_LABEL ? ROOT_LABEL : site.project} ${site.spec}`)\n .join(', ')\n\n return `${finding.name} → ${finding.target} (${finding.reason}); disagreeing: ${sites}`\n}\n\n/**\n * Aligns every project's dependency ranges, then syncs TypeScript references.\n *\n * @remarks\n * Two jobs under one command because they are the two halves of \"the workspace\n * agrees with itself\", and a user should not have to know which mechanism owns\n * which half.\n *\n * The formatter pass at the end is not cosmetic: `eslint --fix` is what runs\n * `@nx/dependency-checks`' own auto-fix, so anything this command converged is\n * immediately re-checked by the rule that guards the same property — and any\n * range still drifted from what is installed gets pinned there rather than left\n * for CI to report.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param options - Parsed CLI options.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an Nx workspace, or the\n * `--ecosystem` flag names something unknown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runSync (workspaceRoot: string, options: SyncOptions): void {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — run 'mnci sync' from the workspace root.`,\n )\n }\n\n const ecosystems = resolveEcosystems(options.ecosystem)\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n logger.info(`⊘ SKIPPED ${ecosystem} — no ${ecosystem} project in this workspace.`)\n }\n }\n if (ecosystems.includes('go') && hasEcosystem(workspaceRoot, 'go')) {\n logger.success('go — nothing to sync; one root go.mod means one version of every module.')\n }\n\n const drift = collectDrift(workspaceRoot, ecosystems)\n\n if (drift.length === 0) {\n logger.success('Every project agrees on every shared dependency version.')\n } else {\n for (const finding of drift) {\n logger.info(` ${describe(finding)}`)\n }\n }\n\n if (options.check) {\n if (drift.length > 0) {\n logger.error(\n `${drift.length} package(s) declared at more than one version. Run 'mnci sync' to converge them.`,\n )\n process.exitCode = 1\n }\n\n // --check never runs `nx sync` either: `nx sync:check` is the read-only\n // command for that half, and mnci doctor already calls it.\n return\n }\n\n const changed = applyDrift(drift)\n for (const path of changed) {\n logger.detail(`updated ${path}`)\n }\n\n const blocked = drift.flatMap(finding => finding.blocked)\n for (const site of blocked) {\n logger.warn(\n `${site.name} in ${site.project} is declared as '${site.spec}', which is not a plain version range — left unchanged.`,\n )\n }\n\n syncProjectReferences(workspaceRoot)\n\n if (changed.length > 0) {\n logger.step('Formatting the changed manifests (eslint --fix)')\n runFormatter(workspaceRoot)\n }\n\n logger.success('Done. Review the changes with `git diff` before committing.')\n}\n","import { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runCapture } from '../nx-workspace'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\n\n/**\n * The five dependency ecosystems a generated workspace can contain.\n *\n * @remarks\n * The set is closed on purpose — it mirrors the project kinds `mnci add`\n * generates, so a new ecosystem here is a deliberate decision rather than\n * something that appears because a stray manifest was found.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type Ecosystem = 'npm' | 'pip' | 'pub' | 'nuget' | 'go'\n\n/**\n * Every ecosystem, in the order commands report them.\n *\n * @remarks\n * npm first because it is the only one present in every workspace; Go last\n * because it is the one with nothing to reconcile — every other ecosystem,\n * NuGet included, has a real per-project manifest whose versions can drift.\n */\nexport const ECOSYSTEMS: readonly Ecosystem[] = ['npm', 'pip', 'pub', 'nuget', 'go']\n\n/**\n * The label shown for the workspace root, which is not a project.\n *\n * @remarks\n * Parenthesised so it can never collide with a real directory name in the\n * projects column — a workspace could legitimately contain `apps/root`.\n */\nexport const ROOT_LABEL = '(root)'\n\n/**\n * One place a package is declared.\n *\n * @remarks\n * A single package name typically has several of these — the same external\n * dependency declared by two projects, or by a project and the root. That list\n * is the whole point: it is what `mnci sync` reconciles and what `mnci up`\n * prints beside each row so the reader can see who is affected before choosing.\n *\n * `rewritable` is separate from the spec itself because \"understood well enough\n * to report\" and \"understood well enough to edit\" are different bars. A pub\n * dependency expressed as a `git:` map, or an npm one pointing at a tarball\n * URL, is worth showing and must never be rewritten.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface DependencySite {\n /** The package name, as the manifest spells it. */\n name: string\n /** Which ecosystem's manifest this came from. */\n ecosystem: Ecosystem\n /** Workspace-relative project directory, or {@link ROOT_LABEL}. */\n project: string\n /** Absolute path to the manifest declaring it. */\n manifestPath: string\n /** Short section label for display: `dep`, `devDep`, `peerDep`, … */\n section: string\n /** The declared spec, verbatim. */\n spec: string\n /** Whether {@link rewriteSpec} can safely edit this entry. */\n rewritable: boolean\n}\n\n/**\n * Every declaration in the workspace, keyed by package name.\n *\n * @remarks\n * Keyed by name rather than by project because both commands ask the same\n * cross-cutting question — \"who declares this, and do they agree?\" — which a\n * project-keyed structure answers only after a second pass.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type Inventory = Map<string, DependencySite[]>\n\n/** Where each ecosystem's per-project manifests live, relative to the root. */\nconst MANIFEST_GLOBS: Record<Ecosystem, string[]> = {\n // `libs/*` carries private internal libs; `packages/*` the publishable ones.\n npm: ['apps/*/package.json', 'libs/*/package.json', 'packages/*/package.json'],\n // Matches PYTHON_PROJECT_DIRS in commands/add/python.ts.\n pip: ['apps/*/pyproject.toml', 'python-packages/*/pyproject.toml', 'libs/*/pyproject.toml'],\n pub: ['apps/*/pubspec.yaml', 'packages/*/pubspec.yaml', 'libs/*/pubspec.yaml'],\n // Unlike every other ecosystem, the filename itself varies (the project's own\n // PascalCase identity, not a fixed name) — the same three roots\n // PACK_APPS_GUARD and add/csharp.ts already scan.\n nuget: ['apps/*/*.csproj', 'packages/*/*.csproj', 'libs/*/*.csproj'],\n // Single root module by design — there are no per-project Go manifests.\n go: [],\n}\n\n/**\n * The section label for a peer dependency.\n *\n * @remarks\n * Singled out because a peer range is a **compatibility declaration**, not a\n * version choice, and neither command may rewrite one. `@mnci/nx-python-pip`\n * peers `@nx/devkit` at `>=21.0.0` deliberately, so it loads on Nx 21, 22 and\n * 23 alike; converging that on the 23.x this repo resolves would silently drop\n * support for two majors of consumers. Found by running `mnci sync --check`\n * against this repo, where five of six findings were exactly that mistake.\n */\nexport const PEER_SECTION = 'peerDep'\n\n/** npm manifest blocks, mapped to the short label shown in a report. */\nconst NPM_SECTIONS: Record<string, string> = {\n dependencies: 'dep',\n devDependencies: 'devDep',\n peerDependencies: 'peerDep',\n optionalDependencies: 'optionalDep',\n}\n\n/**\n * Normalises a `globSync` result to forward slashes.\n *\n * @remarks\n * Load-bearing on Windows, where `globSync` returns `packages\\name\\file`. The\n * project label derived from these paths is both a display string and the key\n * a user matches against, so a backslash here would make the same project read\n * as two different ones between platforms. This mirrors `toPosix` in\n * `commands/doctor.ts`, whose docblock records the bug that motivated it.\n *\n * @param path - A workspace-relative path from `globSync`.\n * @returns The same path with forward slashes.\n * @throws Never - performs a pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function toPosix (path: string): string {\n return path.replaceAll('\\\\', '/')\n}\n\n/**\n * The project directory a manifest belongs to.\n *\n * @param manifestRelativePath - Workspace-relative path to the manifest.\n * @returns The directory holding it, forward-slashed.\n * @throws Never - performs string manipulation only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction projectOf (manifestRelativePath: string): string {\n const posix = toPosix(manifestRelativePath)\n const lastSlash = posix.lastIndexOf('/')\n\n return lastSlash === -1 ? ROOT_LABEL : posix.slice(0, lastSlash)\n}\n\n/**\n * Whether a workspace contains any project of the given ecosystem.\n *\n * @remarks\n * Every command here reports an absent ecosystem as a loud SKIPPED rather than\n * silently omitting it — the pattern the e2e already uses for Go and Flutter,\n * adopted for the same reason: an ecosystem that is quietly dropped stays\n * uncovered for months without anyone noticing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem to test for.\n * @returns `true` when at least one manifest of that ecosystem exists.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasEcosystem (workspaceRoot: string, ecosystem: Ecosystem): boolean {\n // Go is the only one with no per-project manifest to fall back on.\n if (ecosystem === 'go') {\n return fileExists(join(workspaceRoot, 'go.mod'))\n }\n // NuGet has no root-level marker at all, unlike the other three: nuget.config\n // is publish auth, not a dependency declaration, and mnci writes it only once\n // a csharp-lib exists anyway. The per-project glob is the only signal.\n if (ecosystem === 'nuget') {\n return globSync(MANIFEST_GLOBS.nuget, { cwd: workspaceRoot }).length > 0\n }\n const rootMarker: Record<Exclude<Ecosystem, 'go' | 'nuget'>, string> = {\n npm: 'package.json',\n pip: 'requirements-dev.txt',\n pub: 'pubspec.yaml',\n }\n\n // Either marker counts. A root file alone is the usual case, but a workspace\n // mid-migration can have projects before it has the root file — reporting\n // nothing there would be the silent-skip failure this whole feature avoids.\n return (\n fileExists(join(workspaceRoot, rootMarker[ecosystem])) ||\n globSync(MANIFEST_GLOBS[ecosystem], { cwd: workspaceRoot }).length > 0\n )\n}\n\n/**\n * Reads every npm declaration in the workspace.\n *\n * @remarks\n * Includes the root manifest, because the root is exactly where a shared dev\n * tool legitimately lives and where a runtime dependency legitimately does not\n * — both facts a report is useless without.\n *\n * A spec that is not a plain version range (a `file:`/`git+`/`https:` target,\n * npm's `npm:pkg@range` alias, or a `workspace:` protocol) is recorded with\n * `rewritable: false`. The alias form matters here specifically: mnci's own\n * root manifest pins the dual TypeScript compiler that way\n * (`typescript: npm:@typescript/typescript6@^6.0.2`), and rewriting it as a\n * plain version would silently swap the compiler.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every npm declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectNpm (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n const relativePaths = ['package.json', ...globSync(MANIFEST_GLOBS.npm, { cwd: workspaceRoot })]\n\n for (const relativePath of relativePaths) {\n const manifestPath = join(workspaceRoot, relativePath)\n try {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n sites.push(...npmManifestSites(manifest, relativePath, manifestPath))\n } catch {\n // A malformed manifest is the workspace's problem to fix, not a reason\n // for the whole report to fail.\n }\n }\n\n return sites\n}\n\n/**\n * Reads every dependency block of one npm manifest.\n *\n * @remarks\n * Split out of {@link collectNpm} so the block loop is not nested inside the\n * manifest loop — three levels of iteration in one function is where a\n * `continue` stops obviously belonging to the loop the reader thinks it does.\n *\n * @param manifest - The parsed manifest.\n * @param relativePath - Its workspace-relative path, for the project label.\n * @param manifestPath - Its absolute path, for a later rewrite.\n * @returns Every declaration in the manifest.\n * @throws Never - a manifest with no dependency block yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmManifestSites (\n manifest: Record<string, unknown>,\n relativePath: string,\n manifestPath: string,\n): DependencySite[] {\n const sites: DependencySite[] = []\n const project = projectOf(relativePath)\n\n for (const [block, section] of Object.entries(NPM_SECTIONS)) {\n const declared = (manifest[block] ?? {}) as Record<string, string>\n for (const [name, spec] of Object.entries(declared)) {\n sites.push({\n name,\n ecosystem: 'npm',\n project,\n manifestPath,\n section,\n spec,\n // Two independent reasons to refuse a rewrite. A `file:`/`git+`/\n // `https:` target, a `workspace:` protocol or npm's `npm:pkg@range`\n // alias is a shape this cannot safely edit; a peer range is a shape it\n // could edit and must not (see PEER_SECTION).\n rewritable: section !== PEER_SECTION && /^[\\^~>=<]*\\d/.test(spec.trim()),\n })\n }\n }\n\n return sites\n}\n\n/**\n * Splits a PEP 508 requirement into its package name and version spec.\n *\n * @remarks\n * Regex rather than a TOML/PEP 508 parser, following the precedent set by\n * `parseVendorEntries` in `@mnci/nx-python-pip` — pip has no manifest parser in\n * the standard library reachable from Node, and the shapes mnci generates are\n * narrow. Extras (`requests[socks]>=2`) are stripped from the name and folded\n * into the spec, so an extras-carrying entry is reported but never rewritten.\n *\n * @param requirement - One requirement line or array entry.\n * @returns The name and spec, or `undefined` when the line declares nothing.\n * @throws Never - an unrecognised line yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function parseRequirement (\n requirement: string,\n): { name: string, spec: string, rewritable: boolean } | undefined {\n const comment = requirement.indexOf('#')\n const text = (comment === -1 ? requirement : requirement.slice(0, comment)).trim()\n if (!text || text.startsWith('-')) {\n return undefined\n }\n // Anchored, greedy, and followed by nothing — so it cannot backtrack. The\n // rest of the line is sliced off rather than matched, which is what keeps\n // this linear on a pathological input.\n const name = /^[a-z0-9][\\w.-]*/i.exec(text)?.[0]\n if (!name) {\n return undefined\n }\n const spec = text.slice(name.length).trim()\n\n return {\n name,\n spec,\n // Extras, environment markers and compound ranges are reportable only.\n rewritable: !spec.startsWith('[') && !spec.includes(';') && !spec.includes(','),\n }\n}\n\n/**\n * Reads every pip declaration in the workspace.\n *\n * @remarks\n * Three manifest shapes, and the split between them IS the root/project policy:\n * `requirements-dev.txt` at the root holds the shared toolchain and nothing\n * else, each `pyproject.toml` holds one project's runtime dependencies, and a\n * Python function app has no `pyproject.toml` at all — only its own\n * `requirements.txt`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every pip declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectPip (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const push = (relativePath: string, section: string, requirement: string): void => {\n const parsed = parseRequirement(requirement)\n if (!parsed) {\n return\n }\n sites.push({\n name: parsed.name,\n ecosystem: 'pip',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section,\n spec: parsed.spec,\n rewritable: parsed.rewritable && parsed.spec !== '',\n })\n }\n\n const pyprojectPaths = globSync(MANIFEST_GLOBS.pip, { cwd: workspaceRoot })\n for (const relativePath of pyprojectPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n const declared = pyprojectDependencies(content)\n for (const entry of declared) {\n push(relativePath, 'dep', entry)\n }\n }\n\n const requirementsPaths = [\n 'requirements-dev.txt',\n ...globSync('apps/*/requirements.txt', { cwd: workspaceRoot }),\n ]\n for (const relativePath of requirementsPaths) {\n if (!fileExists(join(workspaceRoot, relativePath))) {\n continue\n }\n // The root file is the shared toolchain; an app's own file is its runtime.\n const section = relativePath === 'requirements-dev.txt' ? 'devDep' : 'dep'\n const lines = readFileSync(join(workspaceRoot, relativePath), 'utf8').split(/\\r?\\n/)\n for (const line of lines) {\n push(relativePath, section, line)\n }\n }\n\n return sites\n}\n\n/**\n * Extracts the entries of a `pyproject.toml`'s `[project] dependencies` array.\n *\n * @remarks\n * Scoped to the `[project]` table deliberately: a `pyproject.toml` can carry a\n * `dependencies` key under other tables (build backends and tool sections both\n * do), and a whole-file regex would sweep those in as runtime requirements.\n *\n * @param content - The file's text.\n * @returns The raw string entries, unquoted.\n * @throws Never - a file with no such array yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pyprojectDependencies (content: string): string[] {\n const entries: string[] = []\n let inProjectTable = false\n let inArray = false\n\n for (const line of content.split(/\\r?\\n/)) {\n const text = line.trim()\n if (text.startsWith('[')) {\n // A new table ends both the scope and any array left open inside it.\n inProjectTable = text === '[project]'\n inArray = false\n continue\n }\n if (!inProjectTable) {\n continue\n }\n if (!inArray && /^dependencies\\s*=\\s*\\[/.test(text)) {\n inArray = true\n }\n if (!inArray) {\n continue\n }\n for (const quoted of text.matchAll(/\"([^\"]*)\"|'([^']*)'/g)) {\n entries.push(quoted[1] ?? quoted[2])\n }\n if (text.includes(']')) {\n inArray = false\n }\n }\n\n return entries\n}\n\n/**\n * Reads every pub declaration in the workspace.\n *\n * @remarks\n * The root `pubspec.yaml` is deliberately NOT read for dependencies: in a Dart\n * pub workspace it carries only the member list and an SDK floor, and mnci\n * writes it with no `dependencies:` block at all. Reading it would invent a\n * root/project conflict that cannot exist.\n *\n * A dependency whose value is a nested map — `git:`, `path:`, `hosted:`, or the\n * bare `flutter:` SDK reference every generated project carries — is recorded\n * with an empty spec and `rewritable: false`. Those are shown in a report and\n * never edited.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every pub declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectPub (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const pubspecPaths = globSync(MANIFEST_GLOBS.pub, { cwd: workspaceRoot })\n for (const relativePath of pubspecPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n for (const block of ['dependencies', 'dev_dependencies'] as const) {\n for (const entry of pubspecBlockEntries(content, block)) {\n sites.push({\n name: entry.name,\n ecosystem: 'pub',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section: block === 'dependencies' ? 'dep' : 'devDep',\n spec: entry.spec,\n rewritable: entry.spec !== '',\n })\n }\n }\n }\n\n return sites\n}\n\n/**\n * Reads one top-level block of a `pubspec.yaml` as name/spec pairs.\n *\n * @remarks\n * A line scanner rather than a YAML parser, the same technique\n * `@mnci/nx-flutter`'s `pubspec.ts` and `workspace.ts` already use to edit\n * these files. It reads only the block's direct children — a nested map\n * (`git:` with an indented `url:` under it) yields an empty spec, which is the\n * signal never to rewrite it.\n *\n * Handles CRLF explicitly. `flutter create` writes CRLF on Windows, and a\n * pattern that assumed LF is precisely how the Flutter internal-dependency\n * injection silently did nothing for months.\n *\n * @param content - The pubspec text.\n * @param block - The top-level key to read.\n * @returns The direct children of that block.\n * @throws Never - an absent block yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pubspecBlockEntries (\n content: string,\n block: string,\n): Array<{ name: string, spec: string }> {\n const lines = content.split(/\\r?\\n/)\n const start = lines.findIndex(line => line.trimEnd() === `${block}:`)\n if (start === -1) {\n return []\n }\n\n const entries: Array<{ name: string, spec: string }> = []\n let indent: number | undefined\n\n const body = lines.slice(start + 1)\n for (const line of body) {\n if (line.trim() === '' || line.trimStart().startsWith('#')) {\n continue\n }\n const lineIndent = line.length - line.trimStart().length\n if (lineIndent === 0) {\n break\n }\n indent ??= lineIndent\n if (lineIndent > indent) {\n // A nested map's contents — belongs to the entry above, not a new one.\n continue\n }\n if (lineIndent < indent) {\n break\n }\n // Split on the first colon rather than matching the whole line: a value can\n // contain anything, and a pattern that tried to describe it is exactly the\n // shape that backtracks badly on a long line.\n const colon = line.indexOf(':')\n if (colon === -1) {\n continue\n }\n const key = line.slice(0, colon).trim()\n if (/^[a-z_][\\w-]*$/i.test(key)) {\n entries.push({ name: key, spec: stripQuotes(line.slice(colon + 1).trim()) })\n }\n }\n\n return entries\n}\n\n/**\n * Removes a single layer of surrounding quotes.\n *\n * @param value - The raw scalar.\n * @returns The value without its surrounding quotes.\n * @throws Never - performs string manipulation only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction stripQuotes (value: string): string {\n return /^(['\"]).*\\1$/.test(value) ? value.slice(1, -1) : value\n}\n\n/**\n * Reads every NuGet declaration in the workspace.\n *\n * @remarks\n * The `.csproj` itself is never read for the project's OWN identity or\n * version — same boundary `collectNpm`/`collectPub`/`collectPip` already keep\n * for `package.json`'s `name`/`version` fields — only `<PackageReference>`\n * entries are external dependencies.\n *\n * One section only (`dep`): SDK-style `.csproj` has no first-class\n * dependencies-vs-devDependencies split the way npm/pip do — a test-only\n * package is just a `PackageReference` in whichever project needs it (often\n * a dedicated `*.Tests` project), not a separate block on a shared manifest.\n * Reporting an invented split would be less honest than reporting none.\n *\n * An internal C# library is never expressed as a `PackageReference` at all —\n * it is a `<ProjectReference>`, a structurally different element this never\n * scans — so unlike npm there is no \"is this actually a workspace project\"\n * ambiguity to resolve for a name found here.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every NuGet declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectNuget (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const csprojPaths = globSync(MANIFEST_GLOBS.nuget, { cwd: workspaceRoot })\n for (const relativePath of csprojPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n for (const entry of csprojPackageReferences(content)) {\n sites.push({\n name: entry.name,\n ecosystem: 'nuget',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section: 'dep',\n spec: entry.spec,\n // A plain SemVer string is rewritable; an MSBuild property reference\n // ($(SharedVersion)) or an interval-notation range ([1.0,2.0)) is\n // reported only — neither is a version this can safely overwrite.\n rewritable: /^\\d/.test(entry.spec),\n })\n }\n }\n\n return sites\n}\n\n/**\n * Extracts every `<PackageReference Include=\"…\" Version=\"…\" />` from a\n * `.csproj`'s text.\n *\n * @remarks\n * Two passes, not one combined regex — the same discipline\n * `pubspecBlockEntries` already applies (find the block, then read its\n * children): the outer pattern finds a bounded `<PackageReference … />` tag\n * (anchored on `/>` so it cannot run past the tag it started in), and a\n * second pass reads `Include`/`Version` out of the captured attribute text in\n * either order — `dotnet add package` always writes `Include` first, but\n * nothing enforces that once a human edits the file by hand.\n *\n * Deliberately narrow: the nested-element form\n * (`<PackageReference Include=\"X\"><Version>1.0</Version></PackageReference>`)\n * is valid MSBuild but is not what `dotnet new`/`dotnet add package` emit —\n * verified against every `.csproj` this CLI itself generates — so it is left\n * unrecognised rather than guessed at.\n *\n * @param content - The `.csproj` text.\n * @returns Every package reference found, name and version spec.\n * @throws Never - a file with no matching tag yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function csprojPackageReferences (\n content: string,\n): Array<{ name: string, spec: string }> {\n const entries: Array<{ name: string, spec: string }> = []\n\n for (const tag of content.matchAll(/<PackageReference([^>]*)\\/>/g)) {\n const attributes = tag[1]\n const name = /\\bInclude\\s*=\\s*\"([^\"]*)\"/.exec(attributes)?.[1]\n const spec = /\\bVersion\\s*=\\s*\"([^\"]*)\"/.exec(attributes)?.[1]\n if (name && spec) {\n entries.push({ name, spec })\n }\n }\n\n return entries\n}\n\n/**\n * Reads the root `go.mod`'s require block.\n *\n * @remarks\n * Read-only, and there is exactly one manifest by design: mnci's Go layout is a\n * single root module, so a package cannot have two versions in the workspace\n * and there is nothing for `mnci sync` to reconcile. Collected anyway so\n * `mnci up` can report Go modules alongside everything else.\n *\n * Indirect requirements are labelled rather than dropped — `go mod tidy` owns\n * them, so offering one for a direct upgrade would be misleading.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every module the root `go.mod` requires.\n * @throws Never - an absent or unreadable `go.mod` yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectGo (workspaceRoot: string): DependencySite[] {\n const manifestPath = join(workspaceRoot, 'go.mod')\n if (!fileExists(manifestPath)) {\n return []\n }\n let content: string\n try {\n content = readFileSync(manifestPath, 'utf8')\n } catch {\n return []\n }\n\n const sites: DependencySite[] = Array.from(content.matchAll(\n /^\\s*(?:require\\s+)?([\\w.~-]+(?:\\/[\\w.~-]+)+)\\s+(v\\S+)(\\s*\\/\\/\\s*indirect)?/gm,\n ), match => ({\n name: match[1],\n ecosystem: 'go',\n project: ROOT_LABEL,\n manifestPath,\n section: match[3] ? 'indirect' : 'module',\n spec: match[2],\n rewritable: false,\n }))\n\n return sites\n}\n\n/**\n * Builds the workspace-wide dependency inventory.\n *\n * @remarks\n * Pure with respect to the workspace — it only reads — so both `mnci sync` and\n * `mnci up` can build it, and tests can assert against it, without any command\n * having run.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to read. Defaults to all four.\n * @returns Every declaration, keyed by package name.\n * @throws Never - unreadable manifests are skipped individually.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectInventory (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[] = ECOSYSTEMS,\n): Inventory {\n const readers: Record<Ecosystem, (root: string) => DependencySite[]> = {\n npm: collectNpm,\n pip: collectPip,\n pub: collectPub,\n nuget: collectNuget,\n go: collectGo,\n }\n\n const inventory: Inventory = new Map()\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n continue\n }\n const sites = readers[ecosystem](workspaceRoot)\n for (const site of sites) {\n const existing = inventory.get(site.name)\n if (existing) {\n existing.push(site)\n } else {\n inventory.set(site.name, [site])\n }\n }\n }\n\n return inventory\n}\n\n/**\n * Rewrites one declaration's spec in place, leaving the rest of the file alone.\n *\n * @remarks\n * Minimal edits rather than parse-and-re-emit for the two text formats: a\n * `pyproject.toml` and a `pubspec.yaml` both carry comments mnci wrote and the\n * user may have added, and round-tripping either through a serialiser would\n * silently discard them. npm manifests are re-emitted through `toJson`, which\n * is what every other mnci write already does and is lossless because JSON has\n * no comments.\n *\n * A site with `rewritable: false` is refused rather than best-effort edited.\n *\n * @param site - The declaration to change.\n * @param spec - The new spec to write.\n * @returns `true` when the file was changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rewriteSpec (site: DependencySite, spec: string): boolean {\n if (!site.rewritable || spec === site.spec) {\n return false\n }\n const { name } = site\n\n if (site.ecosystem === 'npm') {\n const manifest = readJson<Record<string, Record<string, string>>>(site.manifestPath)\n const block = Object.entries(NPM_SECTIONS).find(([, label]) => label === site.section)?.[0]\n if (!block || manifest[block]?.[name] === undefined) {\n return false\n }\n manifest[block][name] = spec\n writeFileEnsured(site.manifestPath, toJson(manifest))\n\n return true\n }\n\n const before = readFileSync(site.manifestPath, 'utf8')\n const after = ((): string => {\n switch (site.ecosystem) {\n case 'pip': {\n return replacePipSpec(before, name, spec)\n }\n case 'pub': {\n return replacePubSpec(before, name, spec)\n }\n case 'nuget': {\n return replaceNugetSpec(before, name, spec)\n }\n // 'go' entries are never rewritable (see collectGo), so this is\n // unreachable in practice — returning the text unchanged is the safe\n // no-op if that invariant is ever broken, rather than guessing at a\n // rewrite for a format this branch was never meant to handle.\n default: {\n return before\n }\n }\n })()\n if (after === before) {\n return false\n }\n writeFileEnsured(site.manifestPath, after)\n\n return true\n}\n\n/**\n * Replaces a requirement's spec in a `pyproject.toml` or `requirements.txt`.\n *\n * @remarks\n * Anchored on the package name so only that entry moves. The name is escaped\n * before it reaches the pattern: a package name can legally contain `.` and\n * `-`, and an unescaped `.` would match any character, which is how a rewrite\n * of `foo.bar` could land on `fooxbar`.\n *\n * @param content - The file's current text.\n * @param name - The package name.\n * @param spec - The new spec.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replacePipSpec (content: string, name: string, spec: string): string {\n const escaped = escapeForRegex(name)\n\n return content.replaceAll(\n new RegExp(String.raw`(^|[\"'\\s])(${escaped})\\s*(?:[<>=!~^][^\"'\\n,]*)?(?=[\"'\\n]|$)`, 'gm'),\n (match, lead: string, matched: string) => `${lead}${matched}${spec}`,\n )\n}\n\n/**\n * Replaces a dependency's constraint in a `pubspec.yaml`.\n *\n * @remarks\n * Rewrites only a line whose value is a scalar — a line ending in a bare `:`\n * introduces a nested map and is left untouched, which is the write-side half\n * of the `rewritable: false` contract {@link pubspecBlockEntries} sets.\n *\n * @param content - The file's current text.\n * @param name - The dependency name.\n * @param spec - The new constraint.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replacePubSpec (content: string, name: string, spec: string): string {\n return content.replaceAll(\n new RegExp(String.raw`^(\\s+${escapeForRegex(name)}:[^\\S\\n]*)\\S[^\\n]*$`, 'gm'),\n (match, lead: string) => `${lead}${spec}`,\n )\n}\n\n/**\n * Replaces a `<PackageReference>`'s `Version` attribute in a `.csproj`.\n *\n * @remarks\n * Anchored on `Include=\"<name>\"` first, then rewrites only the `Version=`\n * attribute of that SAME tag — not the next occurrence of `Version=`\n * anywhere in the file, which a simpler name-then-version scan could land on\n * if two `PackageReference` tags for different packages sit close together.\n * The name is escaped for the same reason {@link replacePipSpec} escapes\n * one: a NuGet package ID can contain `.` (`Microsoft.Extensions.Http`), and\n * an unescaped `.` in a regex matches any character.\n *\n * @param content - The file's current text.\n * @param name - The package ID, exactly as `Include=` spells it.\n * @param spec - The new version.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replaceNugetSpec (content: string, name: string, spec: string): string {\n const escaped = escapeForRegex(name)\n const includePattern = new RegExp(String.raw`\\bInclude\\s*=\\s*\"${escaped}\"`)\n\n // Same two-pass shape as csprojPackageReferences: find the bounded tag\n // first, then edit only inside it — so a Version= attribute belonging to a\n // DIFFERENT package's tag is never touched, and every replacement value is\n // a function's return, never a `$1`-style template a version string could\n // collide with.\n return content.replaceAll(/<PackageReference([^>]*)\\/>/g, (tag: string, attributes: string) => {\n if (!includePattern.test(attributes)) {\n return tag\n }\n\n return tag.replace(/\\bVersion\\s*=\\s*\"[^\"]*\"/, () => `Version=\"${spec}\"`)\n })\n}\n\n/**\n * Escapes a string for literal use inside a regular expression.\n *\n * @param value - The literal text.\n * @returns The escaped text.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction escapeForRegex (value: string): string {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`)\n}\n\n/**\n * Whether a package name belongs to a project inside this workspace.\n *\n * @remarks\n * An internal `@scope/lib` is symlinked by npm and versioned by `nx release`;\n * its declared range is deliberately loose (often `*`) so both the workspace\n * link and the tagged version satisfy it. Neither command may touch one:\n * converging it would break on the next release, and offering to \"upgrade\" it\n * to whatever is published would replace the local link with a registry copy.\n *\n * npm only. A pip internal lib is vendored and a pub one is a workspace member,\n * so neither is declared under a name this could resolve to a directory.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The package name as declared.\n * @returns `true` when the package is a project in this workspace.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isWorkspaceProject (workspaceRoot: string, name: string): boolean {\n const short = name.includes('/') ? name.split('/').at(-1) : name\n\n return ['apps', 'libs', 'packages'].some(directory =>\n fileExists(join(workspaceRoot, directory, short ?? name, 'package.json')),\n )\n}\n\n/**\n * Whether a manifest key resolves to a differently-named package.\n *\n * @remarks\n * npm's `npm:pkg@range` alias lets a manifest install one package under another\n * name, and mnci's own root manifest does exactly that for the dual TypeScript\n * compiler: `typescript` resolves to `@typescript/typescript6`. Asking the\n * registry about the KEY then answers about a different package entirely — the\n * first `mnci up` run on this repo offered \"typescript 6.0.2 to 7.0.2\", which\n * is real TypeScript's version, not the aliased package's.\n *\n * Detected from the installed tree rather than by pattern-matching the spec,\n * because that is the fact that actually matters: whatever the spec says, the\n * name on disk is what a registry query would have to match.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The package name as declared.\n * @returns `true` when the installed package reports a different name.\n * @throws Never - an uninstalled package reads as not aliased.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isAliasedInstall (workspaceRoot: string, name: string): boolean {\n try {\n const installed = readJson<{ name?: string }>(\n join(workspaceRoot, 'node_modules', name, 'package.json'),\n )\n\n return installed.name !== undefined && installed.name !== name\n } catch {\n return false\n }\n}\n\n/**\n * The version of a package actually resolved in the workspace.\n *\n * @remarks\n * This is what `mnci sync` reconciles towards, and the choice is deliberate:\n * `@nx/dependency-checks` pins a drifted range to the *installed* version when\n * it auto-fixes, so resolving against the same source is what makes the lint\n * rule and this command agree instead of fighting over the same manifest.\n *\n * npm reads `node_modules`, pub reads the single root `pubspec.lock`, and pip\n * has to ask the interpreter — plain pip has no lockfile, which is why its\n * answer is the weakest of the three and why an absent one falls back to the\n * highest declared range rather than failing.\n *\n * NuGet has no single answer to give: each `.csproj` restores into its OWN\n * `obj/project.assets.json`, so the same package could genuinely resolve to\n * different versions in different projects — unlike pub's one shared\n * workspace lockfile. Reporting one of those arbitrarily would be a wrong\n * answer dressed as a precise one, so this returns `undefined`, the same\n * \"nothing to resolve here\" contract Go already has for a different reason.\n * An absent resolved version falls back to the highest declared range, same\n * as pip.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - Which ecosystem the package belongs to.\n * @param name - The package name.\n * @returns The resolved version, or `undefined` when nothing is installed.\n * @throws Never - every lookup failure yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolvedVersion (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n name: string,\n): string | undefined {\n if (ecosystem === 'npm') {\n try {\n return readJson<{ version?: string }>(\n join(workspaceRoot, 'node_modules', name, 'package.json'),\n ).version\n } catch {\n return undefined\n }\n }\n\n if (ecosystem === 'pub') {\n const lockPath = join(workspaceRoot, 'pubspec.lock')\n if (!fileExists(lockPath)) {\n return undefined\n }\n try {\n const match = new RegExp(\n String.raw`^\\s{2}${escapeForRegex(name)}:[^]*?^\\s+version:\\s*[\"']?([^\"'\\s]+)`,\n 'm',\n ).exec(readFileSync(lockPath, 'utf8'))\n\n return match?.[1]\n } catch {\n return undefined\n }\n }\n\n if (ecosystem === 'pip') {\n const python = process.platform === 'win32' ? 'python' : 'python3'\n const result = runCapture(python, ['-m', 'pip', 'show', name], workspaceRoot)\n if (result.status !== 0) {\n return undefined\n }\n\n return /^Version:\\s*(\\S+)/m.exec(result.stdout)?.[1]\n }\n\n // nuget: no single workspace-wide resolved state to read (see the remarks\n // above). go: the root go.mod IS the resolved state — one module, one\n // version — so there is nothing further to resolve either.\n return undefined\n}\n","/**\n * How large an update is, in the buckets `npm-check` groups its output by.\n *\n * @remarks\n * `non-semver` is not \"unparseable\" — it is `npm-check`'s own fourth bucket,\n * *\"Versions less than 1.0.0, caution\"*. A `0.x` release makes no compatibility\n * promise, so a `0.1.15` to `0.3.4` bump is neither safely-minor nor\n * loudly-major; grouping it separately is what tells the reader to look. A\n * version this module cannot parse at all lands here too, for the same reason:\n * it deserves attention rather than a confident label.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type UpdateKind = 'patch' | 'minor' | 'major' | 'non-semver'\n\n/**\n * The order sections are printed in, matching `npm-check -u`.\n *\n * @remarks\n * Also the sort key for a report: rows are ordered by this array's index, so\n * the least risky upgrades appear first and the reader meets the safe bulk\n * before the majors.\n */\nexport const UPDATE_KINDS: readonly UpdateKind[] = ['patch', 'minor', 'major', 'non-semver']\n\n/**\n * The heading and blurb `npm-check` prints above each section.\n *\n * @remarks\n * Reproduced verbatim rather than reworded. `mnci up` is meant to be\n * recognisable to anyone who has run `npm-check -u`, and the wording is the\n * cheapest half of that.\n */\nexport const UPDATE_KIND_LABELS: Record<UpdateKind, { title: string, blurb: string }> = {\n 'patch': { title: 'Patch Update', blurb: 'Backwards-compatible bug fixes.' },\n 'minor': { title: 'Minor Update', blurb: 'New backwards-compatible features.' },\n 'major': { title: 'Major Update', blurb: 'Potentially breaking API changes. Use caution.' },\n 'non-semver': { title: 'Non-Semver', blurb: 'Versions less than 1.0.0, caution.' },\n}\n\n/**\n * A parsed release triple plus any prerelease suffix.\n *\n * @remarks\n * Build metadata is deliberately absent: nothing here compares it, and semver\n * says it must be ignored when ordering versions anyway.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface ParsedVersion {\n /** The major segment. */\n major: number\n /** The minor segment. */\n minor: number\n /** The patch segment. */\n patch: number\n /** The prerelease suffix without its leading dash, or `undefined`. */\n prerelease?: string\n}\n\n/**\n * The range operators this module understands, longest first.\n *\n * @remarks\n * Order matters: `>=` has to be tested before `>`, or every `>=1.2.3` would be\n * read as `>` with a version of `=1.2.3`. The empty string is not listed — it\n * is the fallback when nothing else matches, i.e. an exact pin.\n */\nconst RANGE_OPERATORS = ['>=', '<=', '^', '~', '>', '<', '='] as const\n\n/**\n * Parses a bare version string into its numeric triple.\n *\n * @remarks\n * Deliberately narrower than full semver: build metadata is dropped and a\n * prerelease is kept only as an opaque string, because the only decisions this\n * module drives are \"is it newer\" and \"which bucket\". Anything with fewer than\n * three numeric segments is rejected rather than defaulted, so a calendar\n * version or a git ref reports as unparseable instead of silently comparing as\n * zero.\n *\n * @param raw - The version text, e.g. `1.2.3` or `2.0.0-beta.1`.\n * @returns The parsed version, or `undefined` when it is not a numeric triple.\n * @throws Never - malformed input yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function parseVersion (raw: string): ParsedVersion | undefined {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.-]+))?/.exec(raw.trim())\n if (!match) {\n return undefined\n }\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4],\n }\n}\n\n/**\n * Reads the leading range operator off a dependency spec.\n *\n * @remarks\n * Returned so a rewrite can preserve whatever the project already chose: a\n * package pinned exactly stays pinned, a caret range stays a caret range. Every\n * write path in this feature routes through here rather than assuming a caret,\n * because silently widening an exact pin is a behaviour change the user never\n * asked for.\n *\n * @param spec - The declared dependency spec.\n * @returns The operator, or an empty string for an exact pin.\n * @throws Never - performs string matching only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rangeOperator (spec: string): string {\n const trimmed = spec.trim()\n\n return RANGE_OPERATORS.find(operator => trimmed.startsWith(operator)) ?? ''\n}\n\n/**\n * Strips a range operator off a dependency spec, leaving the bare version.\n *\n * @remarks\n * Handles the single-constraint specs that actually appear in a manifest mnci\n * generates (`^1.2.3`, `~1.2`, `2.0.0`). A compound range, a URL, a git ref, a\n * `workspace:` alias or npm's `npm:pkg@range` form returns `undefined`, which\n * every caller treats as \"leave this one alone\" — rewriting a spec whose shape\n * is not understood is how a manifest gets corrupted.\n *\n * @param spec - The declared dependency spec.\n * @returns The bare version text, or `undefined` when the spec is not a single\n * operator-plus-version constraint.\n * @throws Never - an unrecognised spec yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function specVersion (spec: string): string | undefined {\n const bare = spec.trim().slice(rangeOperator(spec).length).trim()\n\n return /^\\d+\\.\\d+(?:\\.\\d+)?(?:-[\\w.-]+)?$/.test(bare) ? bare : undefined\n}\n\n/**\n * Compares two version strings.\n *\n * @remarks\n * A release always sorts above its own prereleases (`1.2.3` beats\n * `1.2.3-beta.1`), which is semver's rule; two prereleases of the same triple\n * are compared as plain strings, which is narrower than semver's\n * segment-by-segment rule but only ever decides which of two prereleases is\n * offered, never whether a release is newer than one.\n *\n * @param left - The first version.\n * @param right - The second version.\n * @returns A negative number when `left` is older, positive when newer, `0`\n * when equal or when either side is unparseable.\n * @throws Never - unparseable input compares equal.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function compareVersions (left: string, right: string): number {\n const a = parseVersion(left)\n const b = parseVersion(right)\n if (!a || !b) {\n return 0\n }\n if (a.major !== b.major) {\n return a.major - b.major\n }\n if (a.minor !== b.minor) {\n return a.minor - b.minor\n }\n if (a.patch !== b.patch) {\n return a.patch - b.patch\n }\n if (a.prerelease === b.prerelease) {\n return 0\n }\n if (!a.prerelease) {\n return 1\n }\n if (!b.prerelease) {\n return -1\n }\n\n return a.prerelease < b.prerelease ? -1 : 1\n}\n\n/**\n * Whether `candidate` is strictly newer than `current`.\n *\n * @remarks\n * A thin reading of {@link compareVersions}, named so call sites read as the\n * question they are actually asking.\n *\n * @param candidate - The version offered.\n * @param current - The version in hand.\n * @returns `true` when `candidate` is strictly newer.\n * @throws Never - unparseable input yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isNewer (candidate: string, current: string): boolean {\n return compareVersions(candidate, current) > 0\n}\n\n/**\n * Buckets an upgrade the way `npm-check` groups its report.\n *\n * @remarks\n * The `0.x` test is applied to the CURRENT version, not the latest: a package\n * the workspace is on `0.1.15` of is unpredictable to upgrade regardless of\n * where it is going, whereas one already past `1.0.0` has made a promise the\n * major/minor/patch split can be trusted to describe.\n *\n * @param current - The version in use.\n * @param latest - The version available.\n * @returns Which section the upgrade belongs in.\n * @throws Never - unparseable input yields `non-semver`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function classify (current: string, latest: string): UpdateKind {\n const from = parseVersion(current)\n const to = parseVersion(latest)\n if (!from || !to || from.major === 0) {\n return 'non-semver'\n }\n if (from.major !== to.major) {\n return 'major'\n }\n\n return from.minor === to.minor ? 'patch' : 'minor'\n}\n\n/**\n * Picks the highest of a set of declared specs.\n *\n * @remarks\n * The fallback `mnci sync` uses when nothing is installed to resolve against.\n * Specs whose shape {@link specVersion} does not understand\n * are skipped rather than treated as zero, so an unparseable entry can never\n * win and drag every other project down to it.\n *\n * @param specs - The declared specs to choose between.\n * @returns The highest spec, or `undefined` when none is parseable.\n * @throws Never - an all-unparseable set yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function highestSpec (specs: readonly string[]): string | undefined {\n let best: string | undefined\n let bestVersion: string | undefined\n for (const spec of specs) {\n const version = specVersion(spec)\n if (!version) {\n continue\n }\n if (!bestVersion || isNewer(version, bestVersion)) {\n best = spec\n bestVersion = version\n }\n }\n\n return best\n}\n","import { join } from 'node:path'\nimport { Separator, checkbox } from '@inquirer/prompts'\nimport {\n PEER_SECTION,\n collectInventory,\n hasEcosystem,\n isAliasedInstall,\n isWorkspaceProject,\n resolvedVersion,\n rewriteSpec,\n type DependencySite,\n type Ecosystem,\n} from './manifest.repository'\nimport { latestVersions } from './registry.client'\nimport {\n UPDATE_KINDS,\n UPDATE_KIND_LABELS,\n classify,\n highestSpec,\n isNewer,\n rangeOperator,\n specVersion,\n type UpdateKind,\n} from './semver.algorithm'\nimport { runFormatter, runShell } from '../nx-workspace'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\nimport { resolveEcosystems, syncProjectReferences } from './sync-dependencies.use-case'\n\n/**\n * Options accepted by `mnci up`.\n *\n * @remarks\n * `install` is inverted by commander: the flag is `--no-install`, so the field\n * is `true` unless the user asked to skip the reinstall. Compared against\n * `false` explicitly at the call site for that reason.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface UpOptions {\n /** Report only; never prompt and never write. */\n check?: boolean\n /** Select every available update without prompting. */\n yes?: boolean\n /** Restrict the run to one ecosystem. */\n ecosystem?: string\n /** Commander sets this to `false` for `--no-install`. */\n install?: boolean\n}\n\n/**\n * One package with a newer release available.\n *\n * @remarks\n * `current` is one version even though `sites` may declare several ranges: it\n * is what the workspace actually resolved, which is the only version an upgrade\n * is meaningfully measured from. A workspace whose sites genuinely disagree is\n * `mnci sync`'s problem, not this command's.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Outdated {\n /** The package name. */\n name: string\n /** Which ecosystem it belongs to. */\n ecosystem: Ecosystem\n /** The version currently in use. */\n current: string\n /** The newest published version. */\n latest: string\n /** Which `npm-check` section this upgrade belongs in. */\n kind: UpdateKind\n /** Every place it is declared — the column `npm-check` does not have. */\n sites: DependencySite[]\n}\n\n/**\n * The version of a package the workspace is effectively on.\n *\n * @remarks\n * Prefers what is resolved over what is declared, because a range says what\n * would be acceptable while the resolved version says what is actually running\n * — and an upgrade report about the former would be about nothing the user has.\n * Falls back to the highest declared range for a package nothing has installed.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param sites - Every declaration of one package.\n * @returns The current version, or `undefined` when it cannot be determined.\n * @throws Never - every lookup failure yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction currentVersion (\n workspaceRoot: string,\n sites: readonly DependencySite[],\n): string | undefined {\n const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, sites[0].name)\n if (installed) {\n return installed\n }\n if (sites[0].ecosystem === 'go') {\n // The root go.mod pins exactly and declares each module once, so its spec\n // IS the current version — but it carries a leading `v`, which every\n // version comparison here would otherwise reject as unparseable, silently\n // reporting the whole Go ecosystem as having no updates.\n return bare(sites[0].spec)\n }\n const highest = highestSpec(sites.map(site => site.spec))\n\n return highest ? specVersion(highest) : undefined\n}\n\n/**\n * Finds every package with a newer release, across every ecosystem present.\n *\n * @remarks\n * Indirect Go modules are excluded: `go mod tidy` owns them, so offering one\n * for a direct upgrade would be offering an edit the toolchain undoes.\n *\n * So is a package declared **only** as a peer dependency. A peer range states\n * which versions a published package supports, not which one this workspace\n * uses, so there is no upgrade to offer — and writing one would narrow the\n * package's compatibility. A package that is both a peer somewhere and a real\n * dependency elsewhere is still offered; `applyUpdate` then rewrites the real\n * declarations and leaves the peer range alone.\n *\n * An ecosystem whose toolchain is missing yields nothing rather than failing —\n * `latestVersions` resolves an empty map — and `runUp` reports that as a loud\n * SKIPPED so an absent Flutter SDK is never mistaken for \"everything is current\".\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to inspect.\n * @returns A promise of every outdated package, newest section first.\n * @throws Never - unreadable manifests and dead toolchains are skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function collectOutdated (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[],\n): Promise<Outdated[]> {\n const present = ecosystems.filter(ecosystem => hasEcosystem(workspaceRoot, ecosystem))\n const inventory = collectInventory(workspaceRoot, present)\n\n const outdated: Outdated[] = []\n for (const ecosystem of present) {\n const forEcosystem = [...inventory].filter(([name, sites]) =>\n offerable(workspaceRoot, ecosystem, name, sites),\n )\n const latest = await latestVersions(\n ecosystem,\n forEcosystem.map(([name]) => name),\n workspaceRoot,\n )\n outdated.push(...upgradable(workspaceRoot, ecosystem, forEcosystem, latest))\n }\n\n return outdated.toSorted(\n (left, right) =>\n UPDATE_KINDS.indexOf(left.kind) - UPDATE_KINDS.indexOf(right.kind) ||\n left.name.localeCompare(right.name),\n )\n}\n\n/**\n * Whether a package is one this command may offer an upgrade for.\n *\n * @remarks\n * Four exclusions, each one a wrong row the first real run produced or would\n * have produced:\n *\n * - **A different ecosystem's entry**, since the registry answer being matched\n * against was fetched for this one.\n * - **An indirect Go module**, which `go mod tidy` owns.\n * - **A package declared only as a peer**, whose range states which versions a\n * published package supports rather than which one this workspace uses.\n * - **A workspace project**, which `nx release` versions, and an **aliased\n * install**, whose manifest key names a different package than the one on\n * disk — the registry answer for that key is about something else entirely.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem currently being processed.\n * @param name - The package name.\n * @param sites - Every declaration of it.\n * @returns `true` when an upgrade may be offered.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction offerable (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n name: string,\n sites: readonly DependencySite[],\n): boolean {\n if (sites[0].ecosystem !== ecosystem || sites[0].section === 'indirect') {\n return false\n }\n if (sites.every(site => site.section === PEER_SECTION)) {\n return false\n }\n if (ecosystem !== 'npm') {\n return true\n }\n\n return !isWorkspaceProject(workspaceRoot, name) && !isAliasedInstall(workspaceRoot, name)\n}\n\n/**\n * Drops Go's leading `v` so a version can be compared like any other.\n *\n * @remarks\n * `v1.2.3` and `1.2.3` are the same release. Without this, every Go module\n * would read as unparseable and the whole ecosystem would silently report no\n * updates — the quiet-failure shape this codebase keeps running into.\n *\n * @param version - The version as its ecosystem spells it.\n * @returns The version without a leading `v`.\n * @throws Never - performs a string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction bare (version: string): string {\n return version.replace(/^v/, '')\n}\n\n/**\n * Selects the packages in one ecosystem that have a newer release.\n *\n * @remarks\n * Split out of {@link collectOutdated} so the per-package loop is not nested\n * inside the per-ecosystem one. Go's `v` prefix is stripped on both sides before\n * comparing: `v1.2.3` and `1.2.3` are the same release, and a comparison that\n * did not know that would report every Go module as unparseable.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem being processed.\n * @param declared - Its inventory entries, as name/sites pairs.\n * @param latest - The latest version per package name.\n * @returns The packages with an upgrade available.\n * @throws Never - a package with no known latest version is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction upgradable (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n declared: ReadonlyArray<[string, DependencySite[]]>,\n latest: ReadonlyMap<string, string>,\n): Outdated[] {\n const outdated: Outdated[] = []\n\n for (const [name, sites] of declared) {\n const newest = latest.get(name)\n const current = currentVersion(workspaceRoot, sites)\n if (!newest || !current || !isNewer(bare(newest), bare(current))) {\n continue\n }\n outdated.push({\n name,\n ecosystem,\n current,\n latest: newest,\n kind: classify(bare(current), bare(newest)),\n sites,\n })\n }\n\n return outdated\n}\n\n/**\n * Renders one report row, column-aligned against its siblings.\n *\n * @remarks\n * Hand-rolled padding rather than a table dependency: the CLI ships three\n * runtime dependencies on purpose, and this is four `padEnd` calls.\n *\n * @param entry - The package to render.\n * @param widths - The column widths computed across the whole section.\n * @returns The formatted row.\n * @throws Never - performs string formatting only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction row (entry: Outdated, widths: { name: number, section: number, current: number }): string {\n const sections = [...new Set(entry.sites.map(site => site.section))].join('/')\n const projects = [...new Set(entry.sites.map(site => site.project))].join(', ')\n\n return [\n entry.name.padEnd(widths.name),\n sections.padEnd(widths.section),\n `${entry.current.padStart(widths.current)} › ${entry.latest}`,\n projects,\n ].join(' ')\n}\n\n/**\n * Computes the column widths for a set of rows.\n *\n * @param entries - The rows about to be rendered.\n * @returns The width of each padded column.\n * @throws Never - performs arithmetic only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction columnWidths (entries: readonly Outdated[]): {\n name: number\n section: number\n current: number\n} {\n return {\n name: widthOf(entries.map(entry => entry.name)),\n section: widthOf(\n entries.map(entry => [...new Set(entry.sites.map(site => site.section))].join('/')),\n ),\n current: widthOf(entries.map(entry => entry.current)),\n }\n}\n\n/**\n * Longest string in a set, or zero for an empty one.\n *\n * @param values - The strings to measure.\n * @returns The greatest length.\n * @throws Never - performs arithmetic only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction widthOf (values: readonly string[]): number {\n return Math.max(0, ...values.map(value => value.length))\n}\n\n/**\n * Prints the grouped report `npm-check` prints, plus the projects column.\n *\n * @remarks\n * Also what a non-TTY invocation gets instead of a prompt, so a piped or\n * scripted run reports rather than hanging on a question nobody can answer.\n *\n * @param outdated - Everything with an update available.\n * @returns Nothing.\n * @throws Never - writes to stdout only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reportOutdated (outdated: readonly Outdated[]): void {\n const widths = columnWidths(outdated)\n for (const kind of UPDATE_KINDS) {\n const section = outdated.filter(entry => entry.kind === kind)\n if (section.length === 0) {\n continue\n }\n const { title, blurb } = UPDATE_KIND_LABELS[kind]\n logger.info('')\n logger.info(`${title} ${blurb}`)\n for (const entry of section) {\n logger.info(` ${row(entry, widths)}`)\n }\n }\n}\n\n/**\n * Prompts for which updates to apply, grouped the way `npm-check -u` groups them.\n *\n * @param outdated - Everything with an update available.\n * @returns A promise of the selected packages.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function promptForUpdates (outdated: readonly Outdated[]): Promise<Outdated[]> {\n const widths = columnWidths(outdated)\n const choices: Array<Separator | { name: string, value: Outdated }> = []\n\n for (const kind of UPDATE_KINDS) {\n const section = outdated.filter(entry => entry.kind === kind)\n if (section.length === 0) {\n continue\n }\n const { title, blurb } = UPDATE_KIND_LABELS[kind]\n choices.push(new Separator(`\\n${title} ${blurb}`))\n for (const entry of section) {\n choices.push({ name: row(entry, widths), value: entry })\n }\n }\n\n return await checkbox({\n message: 'Choose which packages to update.',\n choices,\n pageSize: 20,\n })\n}\n\n/**\n * Applies one selected update to every manifest that declares the package.\n *\n * @remarks\n * Every site, not just the first: the whole reason this command shows a\n * projects column is that a package usually has several declarations, and\n * updating one of them is how a workspace acquires the version drift\n * `mnci sync` then has to repair.\n *\n * Go is the exception and is handled by the caller — its version lives in the\n * root `go.mod`'s require block, which is the toolchain's file to write.\n *\n * @param entry - The selected package.\n * @returns The manifest paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction applyUpdate (entry: Outdated): string[] {\n const changed: string[] = []\n for (const site of entry.sites) {\n if (!site.rewritable) {\n continue\n }\n // Keep whatever operator this site already used, so an exact pin is never\n // silently widened into a range.\n const spec = `${rangeOperator(site.spec)}${entry.latest}`\n if (rewriteSpec(site, spec)) {\n changed.push(site.manifestPath)\n }\n }\n\n return changed\n}\n\n/**\n * Reinstalls each ecosystem whose manifests changed.\n *\n * @remarks\n * Editing a manifest without reinstalling leaves the workspace in the one state\n * this whole feature exists to prevent: declared and resolved disagreeing.\n *\n * The Python step goes through the workspace's own `python:install` root script\n * rather than a bare `pip install`, because that script is what installs every\n * project editable in one resolver pass — a per-project install would resolve\n * each project's requirements in isolation.\n *\n * The NuGet step is `nx run-many -t restore`, never a bare `dotnet restore`:\n * `@nx/dotnet`'s inference gives every `.csproj` its own `restore` target,\n * but there is no root `.sln`/`.csproj` for a bare `dotnet restore` at the\n * workspace root to act on — it would fail outright with \"the current\n * working directory does not contain a project or solution file\".\n * `nx run-many` is what reaches every project the way `flutter pub get`/\n * `go mod tidy` reach every dependency in one call for their own ecosystems.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - The ecosystems whose manifests were edited.\n * @returns Nothing.\n * @throws Never - a failed install is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reinstall (workspaceRoot: string, ecosystems: ReadonlySet<Ecosystem>): void {\n const commands: Array<[Ecosystem, string, string[]]> = [\n ['npm', 'npm', ['install']],\n ['pip', 'npm', ['run', 'python:install']],\n ['pub', 'flutter', ['pub', 'get']],\n ['nuget', 'npx', ['nx', 'run-many', '-t', 'restore']],\n ['go', 'go', ['mod', 'tidy']],\n ]\n\n for (const [ecosystem, command, arguments_] of commands) {\n if (!ecosystems.has(ecosystem)) {\n continue\n }\n if (ecosystem === 'pip' && !hasPythonInstallScript(workspaceRoot)) {\n logger.warn(\n \"This workspace has no 'python:install' script — run 'mnci upgrade' to add it, then reinstall by hand.\",\n )\n continue\n }\n logger.step(`Reinstalling ${ecosystem} dependencies (${command} ${arguments_.join(' ')})`)\n if (runShell(command, arguments_, workspaceRoot) !== 0) {\n logger.warn(\n `${command} ${arguments_.join(' ')} failed — the manifests were updated, so re-run it once the cause is fixed.`,\n )\n }\n }\n}\n\n/**\n * Whether the workspace's root manifest carries the `python:install` script.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns `true` when the script exists.\n * @throws Never - an unreadable manifest yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction hasPythonInstallScript (workspaceRoot: string): boolean {\n try {\n const manifest = readJson<{ scripts?: Record<string, string> }>(\n join(workspaceRoot, 'package.json'),\n )\n\n return manifest.scripts?.['python:install'] !== undefined\n } catch {\n return false\n }\n}\n\n/**\n * Upgrades Go modules through the toolchain rather than by editing `go.mod`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param entries - The selected Go modules.\n * @returns Nothing.\n * @throws Never - a failed `go get` is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction applyGoUpdates (workspaceRoot: string, entries: readonly Outdated[]): void {\n for (const entry of entries) {\n logger.step(`go get ${entry.name}@${entry.latest}`)\n if (runShell('go', ['get', `${entry.name}@${entry.latest}`], workspaceRoot) !== 0) {\n logger.warn(`go get ${entry.name}@${entry.latest} failed — left at ${entry.current}.`)\n }\n }\n}\n\n/**\n * Reports every outdated dependency across the workspace and offers to update.\n *\n * @remarks\n * Modelled on `npm-check -u`: the same four sections in the same order, the\n * same interactive multiselect. The addition is the last column — every project\n * that declares the package — which is the question `npm-check` cannot answer\n * in a monorepo and the one that actually decides whether an upgrade is safe.\n *\n * `--check` is not just a flag: it is also the automatic behaviour when stdout\n * is not a TTY, so a CI step or a piped invocation reports instead of hanging\n * on a prompt no one can answer.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param options - Parsed CLI options.\n * @returns A promise that resolves when the run completes.\n * @throws Error when `workspaceRoot` is not an Nx workspace, or the\n * `--ecosystem` flag names something unknown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runUp (workspaceRoot: string, options: UpOptions): Promise<void> {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(`No nx.json found in ${workspaceRoot} — run 'mnci up' from the workspace root.`)\n }\n\n const ecosystems = resolveEcosystems(options.ecosystem)\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n logger.info(`⊘ SKIPPED ${ecosystem} — no ${ecosystem} project in this workspace.`)\n }\n }\n\n logger.step('Querying registries for the latest published versions')\n const outdated = await collectOutdated(workspaceRoot, ecosystems)\n\n if (outdated.length === 0) {\n logger.success('Every dependency is on its latest published version.')\n\n return\n }\n\n const readOnly = options.check ?? !process.stdout.isTTY\n if (readOnly) {\n reportOutdated(outdated)\n logger.info('')\n logger.info(`${outdated.length} package(s) have a newer release. Run 'mnci up' to update them.`)\n\n return\n }\n\n const selected = options.yes ? [...outdated] : await promptForUpdates(outdated)\n if (selected.length === 0) {\n logger.info('Nothing selected; no manifest was changed.')\n\n return\n }\n\n const goSelections = selected.filter(entry => entry.ecosystem === 'go')\n const changed = selected\n .filter(entry => entry.ecosystem !== 'go')\n .flatMap(entry => applyUpdate(entry))\n\n const changedManifests = new Set(changed)\n for (const path of changedManifests) {\n logger.detail(`updated ${path}`)\n }\n applyGoUpdates(workspaceRoot, goSelections)\n\n if (options.install !== false) {\n reinstall(workspaceRoot, new Set(selected.map(entry => entry.ecosystem)))\n }\n\n if (changed.length > 0) {\n syncProjectReferences(workspaceRoot)\n logger.step('Formatting the changed manifests (eslint --fix)')\n runFormatter(workspaceRoot)\n }\n\n logger.success(\n `Updated ${selected.length} package(s). Review the changes with \\`git diff\\` before committing.`,\n )\n}\n","import { pool, runCaptureAsync } from '../nx-workspace'\nimport type { Ecosystem } from './manifest.repository'\n\n/**\n * How many registry lookups run at once.\n *\n * @remarks\n * Each one is a process, not a socket, so the ceiling is about file descriptors\n * and CPU rather than politeness to the registry. Eight keeps a hundred-package\n * workspace under ten seconds without saturating a two-core CI agent.\n */\nconst LOOKUP_CONCURRENCY = 8\n\n/**\n * Latest published version per package name.\n *\n * @remarks\n * A package the registry does not know is simply absent from the map rather\n * than present with an empty value, so a caller reads \"no answer\" and \"no\n * update\" the same way — as nothing to offer.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type LatestVersions = Map<string, string>\n\n/**\n * Asks npm for the latest published version of each package.\n *\n * @remarks\n * `npm view` per package rather than a raw HTTPS request to\n * `registry.npmjs.org`, and that is the load-bearing choice in this file.\n * mnci generates workspaces that publish to **Azure Artifacts**, where the feed\n * is reached through a scoped `@scope:registry` line and authenticated with\n * Basic credentials from the workspace `.npmrc`. `npm view` resolves both for\n * free. Hand-rolling that is how the `_authToken`-as-Bearer trap gets walked\n * into a second time: Azure Artifacts answers a PAT sent as a Bearer token with\n * a 401, and only Basic auth works — a distinction npm already knows about and\n * this command has no business re-implementing.\n *\n * `npm outdated --json` was rejected for a different reason: it is\n * workspace-shallow and omits any package whose declared range already covers\n * the latest release, so the patch/minor/major grouping built on it would be\n * silently incomplete.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root, so npm reads the workspace `.npmrc`.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - a package the registry does not know is simply absent.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestNpmVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync('npm', ['view', name, 'version', '--json'], cwd)\n if (result.status !== 0) {\n return\n }\n try {\n // `--json` yields a quoted string for a single field, and an array when\n // the spec matched several versions; the last is the newest.\n const parsed: unknown = JSON.parse(result.stdout)\n const version = Array.isArray(parsed) ? (parsed.at(-1) as unknown) : parsed\n\n return typeof version === 'string' ? ([name, version] as const) : undefined\n } catch {\n return\n }\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Asks PyPI (or whichever index pip is configured for) for latest versions.\n *\n * @remarks\n * `pip index versions` is the same call `@mnci/nx-python-pip`'s `VersionActions`\n * already makes to resolve a published version, reused here rather than hitting\n * `pypi.org/pypi/<name>/json` directly for the same reason npm goes through\n * `npm view`: a workspace may be pointed at a private index, and pip's own\n * configuration is the only thing that knows.\n *\n * The command is marked experimental by pip and prints a warning saying so —\n * on stderr, which {@link runCaptureAsync} discards.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - an unavailable index yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestPipVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const python = process.platform === 'win32' ? 'python' : 'python3'\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync(python, ['-m', 'pip', 'index', 'versions', name], cwd)\n if (result.status !== 0) {\n return\n }\n // First line is `<name> (<latest>)`; the second lists every version.\n const match = /\\(([^)]+)\\)/.exec(result.stdout)\n\n return match ? ([name, match[1]] as const) : undefined\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Asks the Go toolchain which required modules have a newer release.\n *\n * @remarks\n * One call for the whole module graph, because `go list` already reports the\n * available update per module in its `Update` field — there is no per-package\n * query to pool. `-json` emits a stream of concatenated objects rather than a\n * JSON array, so the output is split on the object boundary before parsing.\n *\n * @param cwd - The workspace root, which holds the single `go.mod`.\n * @returns A promise of the latest version per module with an update.\n * @throws Never - an absent Go toolchain yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestGoVersions (cwd: string): Promise<LatestVersions> {\n const result = await runCaptureAsync('go', ['list', '-m', '-u', '-json', 'all'], cwd)\n if (result.status !== 0) {\n return new Map()\n }\n\n const latest: LatestVersions = new Map()\n for (const chunk of result.stdout.split(/^\\}\\s*$/m)) {\n const text = `${chunk.trim()}${chunk.trim() ? '}' : ''}`\n if (!text.startsWith('{')) {\n continue\n }\n try {\n const module_ = JSON.parse(text) as { Path?: string, Update?: { Version?: string } }\n if (module_.Path && module_.Update?.Version) {\n latest.set(module_.Path, module_.Update.Version)\n }\n } catch {\n // A partial chunk at the tail of the stream is not an error worth raising.\n }\n }\n\n return latest\n}\n\n/**\n * Asks pub which workspace dependencies have a newer release.\n *\n * @remarks\n * One call, like Go: `flutter pub outdated --json` reports the whole pub\n * workspace in a single pass, which is what a shared `pubspec.lock` means.\n *\n * `latest` is read rather than `resolvable`: `resolvable` is capped by every\n * other constraint in the workspace, so reporting it would hide exactly the\n * upgrades a user runs this command to discover.\n *\n * @param cwd - The workspace root, which holds the root `pubspec.yaml`.\n * @returns A promise of the latest version per package.\n * @throws Never - an absent Flutter SDK yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestPubVersions (cwd: string): Promise<LatestVersions> {\n const result = await runCaptureAsync('flutter', ['pub', 'outdated', '--json'], cwd)\n if (result.status !== 0) {\n return new Map()\n }\n\n try {\n const report = JSON.parse(result.stdout) as {\n packages?: Array<{ package?: string, latest?: { version?: string } }>\n }\n const latest: LatestVersions = new Map()\n const packages = report.packages ?? []\n for (const entry of packages) {\n if (entry.package && entry.latest?.version) {\n latest.set(entry.package, entry.latest.version)\n }\n }\n\n return latest\n } catch {\n return new Map()\n }\n}\n\n/**\n * Asks NuGet for the latest published version of each package.\n *\n * @remarks\n * `dotnet package search <name> --exact-match --format json` per package —\n * the same \"go through the ecosystem's own tool\" reasoning as `npm view`:\n * with no `--source` argument, it consults every source in the workspace's\n * resolved `nuget.config` (public nuget.org, and an Azure Artifacts feed\n * when one is configured), the same way `npm view` reads the resolved\n * `.npmrc`. Verified against Microsoft's own `dotnet package search` docs —\n * not assumed — including the exact JSON shape read below\n * (`searchResult[].packages[].latestVersion`) and that the command needs SDK\n * 8.0.2xx+, well under the SDK version mnci pins (`DOTNET_SDK_VERSION` in\n * `overlay.ts`).\n *\n * `--exact-match` still returns the package under `searchResult[].packages`,\n * so the result is matched by `id` (case-insensitively — NuGet package IDs\n * are case-preserving but not case-sensitive) rather than assumed to be the\n * only entry, in case a source ever returns more than one hit.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root, so `dotnet` reads the workspace `nuget.config`.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - a package no configured source knows is simply absent.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestNugetVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync(\n 'dotnet',\n ['package', 'search', name, '--exact-match', '--format', 'json'],\n cwd,\n )\n if (result.status !== 0) {\n return\n }\n try {\n const parsed = JSON.parse(result.stdout) as {\n searchResult?: Array<{ packages?: Array<{ id?: string, latestVersion?: string }> }>\n }\n const packages = parsed.searchResult?.flatMap(source => source.packages ?? []) ?? []\n const match =\n packages.find(entry => entry.id?.toLowerCase() === name.toLowerCase()) ?? packages[0]\n\n return match?.latestVersion ? ([name, match.latestVersion] as const) : undefined\n } catch {\n return\n }\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Resolves latest versions for one ecosystem.\n *\n * @remarks\n * The single entry point commands use, so the per-ecosystem asymmetry — two\n * ecosystems answer per package, two answer for the whole graph at once — stays\n * behind one signature instead of leaking into every caller.\n *\n * @param ecosystem - Which ecosystem to query.\n * @param names - The package names, used only by the per-package ecosystems.\n * @param cwd - The workspace root.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - an unavailable toolchain yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestVersions (\n ecosystem: Ecosystem,\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n switch (ecosystem) {\n case 'npm': {\n return await latestNpmVersions(names, cwd)\n }\n case 'pip': {\n return await latestPipVersions(names, cwd)\n }\n case 'nuget': {\n return await latestNugetVersions(names, cwd)\n }\n case 'go': {\n return await latestGoVersions(cwd)\n }\n // 'pub' explicitly, not a default: a future ecosystem added to the union\n // without a case here should fail to compile, not silently resolve as\n // Dart/Flutter's whole-graph query — exactly the trap this switch would\n // otherwise have laid for 'nuget' itself.\n case 'pub': {\n return await latestPubVersions(cwd)\n }\n }\n}\n","import { existsSync, globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport {\n ESLINT_MNCI_FILENAME,\n ESLINT_USER_FILENAME,\n ESLINT_VERSION,\n RETIRED_FORMATTER_FILES,\n type RegistryConfig,\n} from '../workspace-overlay'\nimport {\n canRepairRollupConfig,\n hasDeclarationSpecifierPlugin,\n hasDirectoryAwareDeclarationSpecifiers,\n hasRollupSourceMaps,\n resolveRollupConfigText,\n} from '../rollup-library'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * One check's outcome.\n *\n * @remarks\n * `remedy` is separate from `detail` on purpose: the detail says what is wrong in\n * this workspace, the remedy says what to type. A finding without a remedy is a\n * finding the user cannot act on, which is the main way a doctor command becomes\n * noise.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Finding {\n /** Short check name, shown as the line label. */\n check: string\n /** Whether the invariant holds. */\n ok: boolean\n /** What is wrong, when it is not ok. */\n detail?: string\n /** The command or edit that fixes it. */\n remedy?: string\n}\n\n/** The ESLint major this stack supports, derived from the version mnci pins. */\nconst SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\\D*/, '').split('.', 1)[0]\n\n/**\n * Normalises a `globSync` result to forward slashes.\n *\n * @remarks\n * Load-bearing on Windows, where `globSync` returns `packages\\name\\file`. Two\n * checks here split those results on `'/'`, and on a backslash path\n * `lastIndexOf('/')` is `-1`, so `slice(0, -1)` silently drops the last\n * character instead of the filename. The resulting path resolves to nothing,\n * which turned {@link checkVersionActions} — the highest-consequence check in\n * this file — into a check that reported a failure for EVERY publishable\n * Dart/Python package on Windows, including correctly configured ones, under a\n * mangled name. A doctor that cries wolf on a healthy workspace is worse than\n * one that stays quiet.\n *\n * @param path - A workspace-relative path from `globSync`.\n * @returns The same path with forward slashes.\n * @throws Never - performs a pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction toPosix (path: string): string {\n return path.replaceAll('\\\\', '/')\n}\n\n/**\n * Checks that the workspace has exactly one ESLint config, at the root.\n *\n * @remarks\n * The invariant that actually broke in practice, twice over: every `@nx/*`\n * generator writes a per-project config, and a workspace generated before mnci\n * owned linting has one in every project directory. Either way the root config\n * stops being the only opinion, silently, because each project lints against\n * whichever config sits nearest it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The findings for the root config and the per-project sweep.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintConfigs (workspaceRoot: string): Finding[] {\n const rootConfigs = globSync('eslint.config.{js,mjs,cjs,ts,mts,cts}', { cwd: workspaceRoot })\n const projectConfigs = globSync('{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}', {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return [\n {\n check: 'root ESLint config',\n ok: rootConfigs.length === 1,\n detail:\n rootConfigs.length === 0\n ? 'no eslint.config.* at the workspace root'\n : `${rootConfigs.length} root configs: ${rootConfigs.join(', ')}`,\n remedy: 'run `mnci upgrade` to rewrite the root config',\n },\n {\n check: 'no per-project ESLint configs',\n ok: projectConfigs.length === 0,\n detail: `found ${projectConfigs.length}: ${projectConfigs.join(', ')}`,\n remedy: 'run `mnci upgrade`, which sweeps {apps,libs,packages}/*/eslint.config.*',\n },\n ...checkEslintEntryPointReachesTheRules(workspaceRoot),\n ]\n}\n\n/**\n * Fails when `eslint.config.mjs` no longer reaches the rules mnci writes.\n *\n * @remarks\n * The two files are split so that `mnci upgrade` can rewrite the rules without\n * touching the workspace's own blocks — which is only true while the workspace's\n * file still imports the other one. A config that has stopped doing so lints\n * against whatever it does import and nothing else, and it does that quietly:\n * ESLint is perfectly happy with a config that carries no rules, so `lint`\n * passes and every file in the repository drifts.\n *\n * Only asked once `eslint.config.mnci.mjs` exists, so a workspace that has not\n * been upgraded yet is not nagged about a file it has never had.\n *\n * Matched on the filename rather than by parsing: a bare substring is enough to\n * tell an import of it from its absence, and running a workspace's config\n * through a parser inside a read-only diagnostic buys nothing.\n *\n * The remedy spells the CALL, `...mnci()`, not `...mnci`. The owned file\n * exports a function so that options still reach `@mnci/eslint-config`, and a\n * remedy a user follows literally has to produce something that works.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding, or none when the split has not been applied here.\n * @throws Never - an unreadable entry point reads as not reaching the rules.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintEntryPointReachesTheRules (workspaceRoot: string): Finding[] {\n const rules = join(workspaceRoot, ESLINT_MNCI_FILENAME)\n if (!existsSync(rules)) return []\n\n const entryPoint = join(workspaceRoot, ESLINT_USER_FILENAME)\n const source = existsSync(entryPoint) ? readFileSync(entryPoint, 'utf8') : ''\n\n return [\n {\n check: `${ESLINT_USER_FILENAME} imports the mnci rules`,\n ok: source.includes(ESLINT_MNCI_FILENAME),\n detail: source === ''\n ? `${ESLINT_USER_FILENAME} is missing, so nothing loads the rules`\n : `${ESLINT_USER_FILENAME} never mentions ${ESLINT_MNCI_FILENAME}`,\n remedy:\n `make its first import \\`import mnci from './${ESLINT_MNCI_FILENAME}'\\` and spread ` +\n '`...mnci()` into the exported array, keeping your own blocks after it — ' +\n 'mnci does not rewrite this file, so it cannot do this for you',\n },\n ]\n}\n\n/**\n * The root manifest's devDependencies, tolerantly.\n *\n * @remarks\n * `readJson` throws on a missing file, and doctor exists to diagnose exactly\n * the broken workspaces where one might be absent — so it must not die on the\n * thing it is inspecting.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The declared devDependencies, or an empty map.\n * @throws Never - a missing or malformed manifest reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction declaredDevDependencies (workspaceRoot: string): Record<string, string> {\n try {\n const manifest = readJson<{ devDependencies?: Record<string, string> }>(\n join(workspaceRoot, 'package.json'),\n )\n\n return manifest.devDependencies ?? {}\n } catch {\n return {}\n }\n}\n\n/**\n * Fails when a retired formatter's config or dependency is still present.\n *\n * @remarks\n * ESLint is the only linter and the only formatter. Two checks used to live\n * here — one for the eslint/oxlint mode split, one for \"exactly one formatter\n * declared\" — and both are meaningless now that there is nothing to choose.\n * This replaces them with the failure that survived the collapse.\n *\n * A leftover `.prettierrc.mjs`, `.oxfmtrc.json` or `oxlint.config.ts` is inert\n * from the command line, because nothing runs those binaries any more. That is\n * precisely what makes it worth a check: a globally installed\n * `esbenp.prettier-vscode` or `oxc.oxc-vscode` still resolves the config and\n * still reformats on save, so the editor quietly undoes Standard — semicolons\n * come back, `function f (a)` loses its space — while `npm run lint` reports\n * nothing, because the damage lands after the last check ran. A declared\n * `prettier` in `devDependencies` is the same trap by the other route, since\n * the extension resolves the formatter from the project.\n *\n * `mnci upgrade` removes all of them; this reports a workspace that has not\n * been upgraded yet, and names the command that fixes it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNoRetiredFormatter (workspaceRoot: string): Finding {\n const files = RETIRED_FORMATTER_FILES.filter(file => fileExists(join(workspaceRoot, file)))\n const declared = ['prettier', 'eslint-config-prettier', 'oxlint', 'oxfmt', '@mnci/oxlint-config']\n .filter(name => declaredDevDependencies(workspaceRoot)[name] !== undefined)\n const stale = [...files, ...declared]\n\n if (stale.length === 0) {\n return { check: 'ESLint is the only linter and formatter', ok: true }\n }\n\n return {\n check: 'no retired formatter is still configured',\n ok: false,\n detail:\n `Found: ${stale.join(', ')}. These no longer run, but an editor extension ` +\n 'still resolves them and will reformat on save against an opinion no gate ' +\n 'checks.',\n remedy: \"Run 'mnci upgrade' to remove them.\",\n }\n}\n\n/**\n * Checks that `@nx/eslint/plugin` is registered in `nx.json`.\n *\n * @remarks\n * This registration is what turns the single root config into a `lint` target on\n * every project. Without it, `npm run lint` still exits 0 while linting nothing —\n * a green check that proves nothing, which is worse than a red one.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns The finding.\n * @throws Never - pure inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintPlugin (nxJson: Record<string, unknown>): Finding {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n const registered = plugins.some(\n entry =>\n (typeof entry === 'string' ? entry : (entry as { plugin?: string }).plugin) ===\n '@nx/eslint/plugin',\n )\n\n return {\n check: '@nx/eslint/plugin registered',\n ok: registered,\n detail: 'not in nx.json plugins — every project silently loses its lint target',\n remedy: 'run `mnci upgrade`',\n }\n}\n\n/**\n * Checks that the ESLint actually installed is the major this stack supports.\n *\n * @remarks\n * The exact bug this command exists for. mnci pins ESLint 9 because\n * `eslint-plugin-react` has no ESLint 10 release, but a declared range and the\n * **resolved** version are different things: this repo carried four package\n * manifests declaring `^10` while the docs said 9, and the resolved binary was 10.\n * Nothing failed, because the repo has no `.tsx` of its own — the drift was only\n * visible by asking `node_modules` what actually got installed.\n *\n * Skipped rather than failed when there are no `node_modules`, since \"not\n * installed yet\" is not a drift.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding, or `undefined` when nothing is installed to inspect.\n * @throws Never - a malformed manifest yields no finding.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkResolvedEslint (workspaceRoot: string): Finding | undefined {\n const manifestPath = join(workspaceRoot, 'node_modules/eslint/package.json')\n if (!fileExists(manifestPath)) {\n return undefined\n }\n try {\n const { version } = readJson<{ version: string }>(manifestPath)\n const major = version.split('.', 1)[0]\n\n return {\n check: `resolved eslint is ${SUPPORTED_ESLINT_MAJOR}.x`,\n ok: major === SUPPORTED_ESLINT_MAJOR,\n detail: `node_modules/eslint is ${version}, but this stack supports ${SUPPORTED_ESLINT_MAJOR}.x (eslint-plugin-react has no release beyond it)`,\n remedy: `pin eslint to ${ESLINT_VERSION} in every package manifest, then reinstall`,\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * Checks that `.npmrc` matches the registry the workspace recorded.\n *\n * @remarks\n * Only meaningful now that publish auth is wired: the two registry kinds get\n * genuinely different files, so a workspace whose `.npmrc` predates its recorded\n * registry choice cannot publish. An `azure-artifacts` workspace additionally\n * needs its scope routed, which is what keeps a scoped package off npmjs.org.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param registry - The registry persisted in `nx.json`'s `mnci` block.\n * @param scope - The npm scope persisted alongside it.\n * @returns The finding, or `undefined` when nothing is persisted to compare against.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmrc (\n workspaceRoot: string,\n registry: RegistryConfig | undefined,\n scope: string | undefined,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n if (!registry || !fileExists(npmrcPath)) {\n return undefined\n }\n const npmrc = readFileSync(npmrcPath, 'utf8')\n if (registry.kind === 'npm') {\n return {\n check: '.npmrc authenticates the public registry',\n ok: npmrc.includes('//registry.npmjs.org/:_authToken='),\n detail: 'no npmjs.org token line — `npm publish` cannot authenticate',\n remedy: 'run `mnci upgrade`',\n }\n }\n\n return {\n check: '.npmrc routes the scope to the feed',\n ok: scope !== undefined && npmrc.includes(`${scope}:registry=`),\n detail: `no '${scope ?? '@scope'}:registry=' line — a scoped package could publish to npmjs.org instead of the feed`,\n remedy: 'run `mnci upgrade`',\n }\n}\n\n/**\n * Warns when this workspace's `.npmrc` would hand npm an empty credential.\n *\n * @remarks\n * A project `.npmrc` beats the user's one, and the generated file authenticates\n * through an environment variable that only CI exports. So with that variable\n * unset — which is every local shell — npm inside this workspace does not fall\n * back to whatever `npm login` wrote in `~/.npmrc`; it authenticates with an\n * empty string and every write is refused.\n *\n * That is a deliberate trade-off rather than a defect, and the generated\n * `.npmrc` says so in its own comments. This check exists because the comments\n * are in a file nobody reads at the moment they need them, and because **the\n * refusal is easy to misread**: for a write you are not allowed to make, the\n * registry answers\n *\n * ```text\n * npm error 404 Not Found - PUT https://registry.npmjs.org/<package>\n * ```\n *\n * with everything above it being npm listing what it *intended* to send. A long,\n * healthy-looking run changes nothing, and 404 reads as \"no such package\"\n * rather than \"you are not who you need to be\".\n *\n * WHAT THIS DOES NOT CHECK, AND WHY NOT\n *\n * Whether the credential is any good. That needs the registry, and `mnci doctor`\n * is a local, read-only, offline-capable check — a diagnostic that fails on a\n * train is a diagnostic people stop running. An expired token produces the\n * *same* misleading 404 as this does, so the remedy names `npm whoami`: one\n * command, and the only one that distinguishes the two.\n *\n * Generic in the variable name rather than looking for `NODE_AUTH_TOKEN`. The\n * public-registry file references that one and the Azure Artifacts file\n * references `${PAT}`, and a third could be added; reading the names out of the\n * file cannot go stale.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param environment - The variables to resolve against. Defaulted rather than\n * read inline so the intent is visible in the signature; every caller uses the\n * default.\n * @returns A finding when the file references a variable nothing has set, and\n * `undefined` when there is no `.npmrc`, it references no variables, or every\n * variable it references is set.\n * @throws Never - an unreadable `.npmrc` is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmrcCredentialResolves (\n workspaceRoot: string,\n environment: Record<string, string | undefined> = process.env,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n if (!fileExists(npmrcPath)) {\n return undefined\n }\n\n const npmrc = readFileSync(npmrcPath, 'utf8')\n // Credential lines only. A `${VAR}` inside a comment is documentation, and a\n // registry URL could contain one without being a secret.\n const credentials = npmrc\n .split('\\n')\n .filter(line => !line.trimStart().startsWith(';') && !line.trimStart().startsWith('#'))\n .filter(line => /(?:_authToken|_password|_auth)\\s*=/u.test(line))\n\n const referenced = [\n ...new Set(\n credentials.flatMap(line =>\n Array.from(line.matchAll(/\\$\\{(\\w+)\\}/gu), match => match[1]),\n ),\n ),\n ]\n if (referenced.length === 0) {\n return undefined\n }\n\n const unset = referenced.filter(name => (environment[name] ?? '') === '')\n if (unset.length === 0) {\n return npmjsCredentialShape(credentials, environment)\n }\n\n return {\n check: 'npm credentials in this workspace resolve to something',\n ok: false,\n detail:\n `.npmrc authenticates with \\${${unset.join('}, ${')}}, which ${unset.length > 1 ? 'are' : 'is'} ` +\n 'unset here — and a project .npmrc beats your user one, so npm will send an EMPTY token from ' +\n 'this directory rather than the one `npm login` wrote. Writes are then refused as a 404 on ' +\n 'the PUT, which reads like a missing package',\n remedy:\n 'nothing, for installing or building — this only affects authenticated commands. To publish ' +\n 'or deprecate by hand, run it from outside this workspace, or export a real token first. If ' +\n 'it still fails there, `npm whoami` — an expired token gives the same 404',\n }\n}\n\n/**\n * Checks that the two halves of build-identity npm auth agree with each other.\n *\n * @remarks\n * Build-identity auth is split across two files that cannot see each other: the\n * `.npmrc` carries no credentials, and `azure-pipelines.yml` runs the task that\n * injects them. Either half alone is broken, and neither breaks loudly:\n *\n * - **A credential-free `.npmrc` with no `npmAuthenticate@0` step** publishes\n * nothing. `npm ci` still works, since installing a public package never\n * authenticates, so the pipeline is green until the release step, where the feed\n * answers 401. This is precisely how a real workspace lost the task: a regenerated\n * pipeline dropped it and nothing said so until a publish.\n * - **A `.npmrc` that still holds a PAT block, with the step present,** makes the\n * task append a second credential to a file that already has one. Which of the\n * two npm ends up sending is not something to leave to chance.\n *\n * Only for an Azure Artifacts workspace with an Azure pipeline. A workspace with no\n * `azure-pipelines.yml` has nothing to disagree with, and public npm has no\n * build identity.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param registry - The registry persisted in `nx.json`'s `mnci` block.\n * @returns A finding when the halves disagree, `undefined` when there is nothing to\n * compare, and an `ok` finding when they agree.\n * @throws Never - an unreadable file is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmAuthMatchesPipeline (\n workspaceRoot: string,\n registry: RegistryConfig | undefined,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n const pipelinePath = join(workspaceRoot, 'azure-pipelines.yml')\n if (registry?.kind !== 'azure-artifacts' || !fileExists(npmrcPath) || !fileExists(pipelinePath)) {\n return undefined\n }\n\n const holdsCredentials = readFileSync(npmrcPath, 'utf8')\n .split('\\n')\n .filter(line => !line.trimStart().startsWith(';') && !line.trimStart().startsWith('#'))\n .some(line => /(?:_authToken|_password|_auth)\\s*=/u.test(line))\n const runsTask = readFileSync(pipelinePath, 'utf8').includes('npmAuthenticate@0')\n\n if (!holdsCredentials && !runsTask) {\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: false,\n detail:\n '.npmrc carries no credentials and azure-pipelines.yml does not run npmAuthenticate@0, so ' +\n 'nothing authenticates the feed — installs still pass, and the release step fails with a 401',\n remedy:\n 'run `mnci upgrade --npm-auth build-identity` to add the task, or `mnci upgrade --npm-auth pat` ' +\n 'to write the PAT block back',\n }\n }\n if (holdsCredentials && runsTask) {\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: false,\n detail:\n '.npmrc holds a credential block AND azure-pipelines.yml runs npmAuthenticate@0, which appends ' +\n 'a second credential to a file that already has one',\n remedy:\n 'run `mnci upgrade --npm-auth build-identity` to drop the PAT block, or `mnci upgrade ' +\n '--npm-auth pat` to drop the task',\n }\n }\n\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: true,\n detail: '',\n remedy: '',\n }\n}\n\n/**\n * Every current npm token starts with this. Granular and automation tokens have\n * since 2021; the legacy format was a UUID, matched separately below.\n */\nconst NPM_TOKEN_PREFIX = 'npm_'\n\n/** The legacy npm token format, still valid on very old accounts. */\nconst LEGACY_NPM_TOKEN = /^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/iu\n\n/**\n * Warns when the credential bound for npmjs.org is not shaped like an npm token.\n *\n * @remarks\n * A set variable is not a working one, and the failure looks identical. This\n * was found on a real machine: `NODE_AUTH_TOKEN` held an 84-character Azure\n * DevOps PAT, so every mnci workspace generated with `--registry npm` quietly\n * authenticated the **public** registry with an Azure credential and every\n * write came back as the same misleading 404.\n *\n * The name is the hazard. `NODE_AUTH_TOKEN` is what `actions/setup-node`\n * exports, which is why mnci writes it — but it is a generic name, so anything\n * else on the machine that sets it wins inside every generated workspace, and\n * nothing says so.\n *\n * A SHAPE CHECK, AND IT SAYS SO\n *\n * This cannot tell a valid token from a revoked one without the registry, and\n * `mnci doctor` stays offline. What it can do is notice that the value is not\n * an npm token at all — every current one begins `npm_`, and the legacy format\n * is a UUID. Anything else is either a credential for somewhere else or a typo,\n * and both are worth a line. The remedy names `npm whoami` because that is the\n * one command that settles what this cannot.\n *\n * The token's value never appears in the output. Its length does, which is\n * enough to recognise what you set without putting a secret on a terminal that\n * may be logged or shared.\n *\n * @param credentials - The `.npmrc` credential lines, comments already removed.\n * @param environment - The variables to resolve against.\n * @returns A finding when an npmjs.org credential is shaped wrongly, else\n * `undefined`.\n * @throws Never - pure string inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmjsCredentialShape (\n credentials: string[],\n environment: Record<string, string | undefined>,\n): Finding | undefined {\n const line = credentials.find(entry => entry.includes('//registry.npmjs.org/:_authToken='))\n const variable = line === undefined ? undefined : /\\$\\{(\\w+)\\}/u.exec(line)?.[1]\n if (variable === undefined) {\n return undefined\n }\n\n const value = environment[variable] ?? ''\n if (value.startsWith(NPM_TOKEN_PREFIX) || LEGACY_NPM_TOKEN.test(value)) {\n return undefined\n }\n\n return {\n check: 'the credential bound for npmjs.org looks like an npm token',\n ok: false,\n detail:\n `\\${${variable}} is set (${value.length} characters) but does not start with '${NPM_TOKEN_PREFIX}' ` +\n 'and is not a legacy UUID token, so it is probably a credential for somewhere else — ' +\n `'${variable}' is a generic name that other tooling also sets, and inside this workspace it ` +\n 'is sent to the PUBLIC registry. A write then fails as a 404 on the PUT, which reads like a ' +\n 'missing package',\n remedy:\n '`npm whoami` to confirm — it is the only thing that separates a wrong token from an ' +\n `expired one. Then unset \\`${variable}\\` for local work, or set it to a real npm token`,\n }\n}\n\n/**\n * Checks that every publishable non-npm package keeps its `versionActions` override.\n *\n * @remarks\n * The highest-consequence check here, because the blast radius is the whole\n * workspace rather than the offending project: Nx's default `versionActions` reads\n * a `package.json`, which a Dart, Python or C# package does not have, so it aborts\n * while building the release graph and `nx release` fails for **every** project.\n *\n * All three non-npm shapes are globbed, and C# is listed explicitly rather than\n * left implied: `csharp-lib` is the only C# kind that lands in `packages/`\n * (`csharp-app` and `csharp-function-app` go to `apps/`, `csharp-internal-lib`\n * to `libs/`), so every `.csproj` this glob finds is inside `release.projects`\n * and carries the same whole-workspace failure mode as a Dart or Python one —\n * it just resolves its override to a workspace-relative\n * `tools/csharp-version-actions.cjs` rather than to a plugin.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per publishable Dart/Python/C# package missing the override.\n * @throws Never - an unreadable `project.json` is reported as missing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkVersionActions (workspaceRoot: string): Finding[] {\n const candidates = [\n ...globSync('packages/*/pubspec.yaml', { cwd: workspaceRoot }),\n ...globSync('packages/*/*.csproj', { cwd: workspaceRoot }),\n ...globSync('python-packages/*/pyproject.toml', { cwd: workspaceRoot }),\n ].map(manifest => toPosix(manifest))\n\n return candidates.flatMap(manifest => {\n const projectRoot = manifest.slice(0, manifest.lastIndexOf('/'))\n const projectJsonPath = join(workspaceRoot, projectRoot, 'project.json')\n let hasOverride = false\n try {\n const projectJson = readJson<{\n release?: { version?: { versionActions?: string } }\n }>(projectJsonPath)\n hasOverride = Boolean(projectJson.release?.version?.versionActions)\n } catch {\n // No project.json at all, so no override — the initialiser already says so.\n }\n\n return [\n {\n check: `${projectRoot} keeps its versionActions override`,\n ok: hasOverride,\n detail: 'missing — nx release aborts for the ENTIRE workspace, not just this project',\n remedy: `add release.version.versionActions to ${projectRoot}/project.json`,\n },\n ]\n })\n}\n\n/**\n * The target options whose value is a single path to a file that must already\n * exist for the target to run.\n *\n * @remarks\n * Deliberately short. `outputPath` is an output, not an input, so it is absent\n * by design; `assets` takes globs and would need matching rather than an\n * existence test. These three are the ones whose absence produces a failure\n * that reads as a compiler problem rather than a config problem.\n */\nconst TARGET_FILE_OPTIONS = ['main', 'tsConfig', 'packageJson'] as const\n\n/**\n * Reads every Nx target a project declares, from either place it can declare them.\n *\n * @remarks\n * A project can carry targets in `project.json` **and** in `package.json`'s `nx`\n * block, and a real workspace was found doing both for one project with\n * different `main` values in each. Both are read here rather than whichever is\n * found first, so a stale definition cannot hide behind the live one.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - The project's workspace-relative directory.\n * @returns One entry per declaring file, with that file's targets.\n * @throws Never - an unreadable or absent manifest contributes no targets.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction declaredTargets (\n workspaceRoot: string,\n projectRoot: string,\n): { source: string; targets: Record<string, { options?: Record<string, unknown> }> }[] {\n const sources: {\n source: string\n targets: Record<string, { options?: Record<string, unknown> }>\n }[] = []\n for (const [file, extract] of [\n ['project.json', (json: Record<string, unknown>) => json['targets']],\n [\n 'package.json',\n (json: Record<string, unknown>) => (json['nx'] as Record<string, unknown>)?.['targets'],\n ],\n ] as const) {\n try {\n const targets = extract(\n readJson<Record<string, unknown>>(join(workspaceRoot, projectRoot, file)),\n )\n if (targets && typeof targets === 'object') {\n sources.push({\n source: `${projectRoot}/${file}`,\n targets: targets as Record<string, { options?: Record<string, unknown> }>,\n })\n }\n } catch {\n // No manifest, or unparseable — nothing to check, and the other checks\n // already report a workspace that broken.\n }\n }\n\n return sources\n}\n\n/**\n * Checks that every target option naming a file points at a file that exists.\n *\n * @remarks\n * Found in a real hand-built workspace, three times over in one repo: a build\n * target whose `main` was `src/index.ts` in `project.json` and `src/main.ts` in\n * the same project's `package.json`, with **neither** file present, and a\n * library whose `tsConfig` named a `tsconfig.lib.json` that had never been\n * written.\n *\n * Worth a check rather than left to the build because of how it surfaces. Nx\n * hands the path to the executor unvalidated, so the failure arrives as a\n * TypeScript or esbuild error about the compiler finding no inputs — which\n * reads as a broken tsconfig, and sends people to edit the one file that is\n * fine. Nothing anywhere names the missing file.\n *\n * Paths carrying an Nx token (`{workspaceRoot}`, `{projectRoot}`) are skipped:\n * they are resolved by Nx at run time, so testing them literally would report a\n * file that is never meant to exist under that name.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per target option pointing at a missing file.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkTargetFilesExist (workspaceRoot: string): Finding[] {\n const projectRoots = new Set(\n [\n ...globSync('{apps,libs,packages}/*/project.json', { cwd: workspaceRoot }),\n ...globSync('{apps,libs,packages}/*/package.json', { cwd: workspaceRoot }),\n ].map(manifest => toPosix(manifest).split('/').slice(0, 2).join('/')),\n )\n\n return [...projectRoots].flatMap(projectRoot =>\n declaredTargets(workspaceRoot, projectRoot).flatMap(({ source, targets }) =>\n Object.entries(targets).flatMap(([targetName, target]) =>\n checkOneTargetsFiles(workspaceRoot, source, targetName, target),\n ),\n ),\n )\n}\n\n/**\n * Checks one target's file-valued options.\n *\n * @remarks\n * Split out of {@link checkTargetFilesExist} so the sweep stays a flat\n * comprehension rather than four nested loops around a pair of `continue`s.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param source - The manifest that declared the target, for the message.\n * @param targetName - The target's name, for the message.\n * @param target - The target definition to inspect.\n * @returns One finding per option pointing at a missing file.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkOneTargetsFiles (\n workspaceRoot: string,\n source: string,\n targetName: string,\n target: { options?: Record<string, unknown> },\n): Finding[] {\n return TARGET_FILE_OPTIONS.flatMap(option => {\n const value = target?.options?.[option]\n // A token (`{workspaceRoot}`, `{projectRoot}`) is resolved by Nx at run\n // time, so a literal existence test would report a file that is never\n // meant to exist under that name.\n const checkable = typeof value === 'string' && !value.includes('{')\n if (!checkable || fileExists(join(workspaceRoot, value))) {\n return []\n }\n\n return [\n {\n check: `${source} → ${targetName}.${option} points at a real file`,\n ok: false,\n detail: `${value} does not exist — the target fails as if the compiler were misconfigured`,\n remedy: `create ${value}, or correct ${option} in ${source}`,\n },\n ]\n })\n}\n\n/**\n * Checks that the workspace's TypeScript project references are synced.\n *\n * @remarks\n * The one check that shells out, because only Nx can answer it. Reported as a\n * finding rather than left to CI so the fix happens before the push that fails.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding.\n * @throws Never - a non-zero exit is the finding, not an error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkSync (workspaceRoot: string): Finding {\n return {\n check: 'TypeScript project references synced',\n ok: runShell('npx', ['nx', 'sync:check'], workspaceRoot) === 0,\n detail: 'nx sync:check failed — a stale project reference was never committed',\n remedy: 'run `npx nx sync` and commit the result',\n }\n}\n\n/**\n * Checks that no runtime dependency is declared in the root manifest.\n *\n * @remarks\n * The root/project policy, and the one check that enforces it: **shared\n * development and tool packages belong at the root, runtime dependencies belong\n * to the package that imports them.**\n *\n * Two independent reasons, and the second is the one that bites. The root\n * manifest is `private` and never published, so a runtime dependency declared\n * there reaches no consumer of any package — an installed `@scope/lib` simply\n * fails to resolve it. And `@nx/rollup` externalises exactly what a project's\n * OWN manifest declares, so hoisting a dependency to the root does not make it\n * shared, it makes rollup **inline a private copy of it** into the bundle.\n * Measured on a real generated workspace: moving `axios` out of one package's\n * manifest took its published bundle from 14.5 KB to 832 KB, silently.\n *\n * `@nx/dependency-checks` catches the second half from the other direction — it\n * fails the project whose import is now undeclared — so this check and that\n * lint rule are complementary rather than redundant: the rule names the project\n * that lost the dependency, this names the root that took it.\n *\n * `devDependencies` are deliberately not checked. Sharing the toolchain is the\n * whole point of the root manifest, and npm's `overrides` only work there.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding, or `undefined` when there is no root manifest to read.\n * @throws Never - an unreadable manifest is treated as nothing to check.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNoRootRuntimeDependencies (workspaceRoot: string): Finding | undefined {\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!fileExists(manifestPath)) {\n return undefined\n }\n let declared: string[]\n try {\n declared = Object.keys(\n readJson<{ dependencies?: Record<string, string> }>(manifestPath).dependencies ?? {},\n )\n } catch {\n return undefined\n }\n\n return {\n check: 'no runtime dependencies in the root manifest',\n ok: declared.length === 0,\n detail: `the root package.json declares ${declared.join(', ')} — the root is private and never published, and @nx/rollup externalises only what a project's own manifest declares, so a package importing one of these ships an inlined private copy instead`,\n remedy: `move ${declared.join(', ')} into the dependencies of each package that imports it (devDependencies at the root are fine — that is what the root is for)`,\n }\n}\n\n/**\n * Checks that every rollup-built project can actually be debugged.\n *\n * @remarks\n * The invariant: a publishable library's `rollup.config.cjs` must switch\n * `sourceMap` on. Without it the build emits no `.js.map`, so VS Code cannot\n * map the running JavaScript back to the TypeScript and **every breakpoint in\n * a `.ts` file stays grey and unbound** — with no error, anywhere, to say why.\n * Found exactly that way: from the outside it looks like a broken debugger\n * rather than a build that omitted one flag.\n *\n * A config written before mnci wired this in will never fix itself, because a\n * rollup config is written once at `add` time — hence a check plus the\n * `mnci upgrade` sweep that repairs it.\n *\n * Reads through {@link resolveRollupConfigText} rather than the project's own\n * file text alone, for two reasons found in the same real workspace.\n * `@mnci/eslint-config`'s `@stylistic/key-spacing` (aligned on value) is\n * entitled to rewrite `sourceMap: true,` to `sourceMap: true,` to\n * line up with the object's longest key — `eslint --fix` is part of the\n * documented pre-commit routine, so a lint-clean config was failing this\n * check on formatting alone. And a workspace that hoists the shared\n * `withNx(...)` call into one root file and leaves each project as\n * `module.exports = require('../../rollup.base.cjs')()` has no\n * `sourceMap: true` text of its own — the flag lives one file away, so\n * reading only the project's file failed every project even when every one\n * genuinely had it on.\n *\n * The remedy is conditional on {@link canRepairRollupConfig} rather than\n * always naming `mnci upgrade`: that command edits the `},` / `{` boundary\n * between `withNx`'s two arguments, which a one-line delegation to a shared\n * base does not have. Recommending it there would send the user in a circle —\n * run the fix, watch it no-op, doctor reports the same failure again — so a\n * config in that shape gets told to edit the shared base by hand instead.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per rollup config missing the flag.\n * @throws Never - an unreadable config is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkRollupSourceMaps (workspaceRoot: string): Finding[] {\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return configs.flatMap(relativePath => {\n const configPath = join(workspaceRoot, relativePath)\n let config: string\n try {\n config = readFileSync(configPath, 'utf8')\n } catch {\n return []\n }\n\n return [\n {\n check: `source maps enabled in ${relativePath}`,\n ok: hasRollupSourceMaps(resolveRollupConfigText(configPath)),\n detail:\n 'rollup emits no .js.map without it, so a breakpoint in a .ts file can never bind',\n remedy: canRepairRollupConfig(config)\n ? 'run `mnci upgrade`, which adds it to every rollup config'\n : \"this config delegates via require() to a shared base mnci does not own — add `sourceMap: true` to withNx's first argument there by hand; `mnci upgrade` cannot repair a config in this shape\",\n },\n ]\n })\n}\n\n/**\n * Checks that every rollup-built project publishes resolvable declarations.\n *\n * @remarks\n * The invariant: a rollup config must carry the declaration-specifier plugin,\n * AND that plugin must be the version which resolves a bare relative specifier\n * against what the build actually emitted, rather than appending `.js` to all\n * of them.\n *\n * Worth a check for the same reason the source-map one is, only more so:\n * **there is no failure to notice.** The build succeeds, the package publishes,\n * runtime is unaffected (the bundle never goes through those specifiers), and\n * `skipLibCheck` — the default in most consumers — swallows the unresolved\n * import. All that happens is that every export behind a directory barrel\n * silently becomes `any` for every consumer. It was found by reading a real\n * published tarball, not by anything failing.\n *\n * Reported as two distinct findings because they are different states with\n * different histories, even though `mnci upgrade` fixes both: a config with no\n * plugin at all predates it (or was hand-edited), while a config with the\n * STALE plugin was generated by a version of mnci that had the plugin and got\n * the suffix wrong — and that second one is the dangerous one, because it\n * looks entirely healthy. In a real consuming workspace 9 of 11 published\n * packages sat in exactly that state, purely according to when each was\n * scaffolded.\n *\n * Read through {@link resolveRollupConfigText} rather than the project's own\n * file text, for the reason {@link checkRollupSourceMaps} documents: a\n * workspace that hoists `withNx(...)` into one shared base leaves each project\n * a one-line delegation, and the plugin then lives a file away.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per rollup config missing or carrying a stale plugin.\n * @throws Never - an unreadable config is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkDeclarationSpecifiers (workspaceRoot: string): Finding[] {\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return configs.flatMap(relativePath => {\n const configPath = join(workspaceRoot, relativePath)\n if (!fileExists(configPath)) {\n return []\n }\n const resolved = resolveRollupConfigText(configPath)\n if (!hasDeclarationSpecifierPlugin(resolved)) {\n return [\n {\n check: `${relativePath} normalises declaration specifiers`,\n ok: false,\n detail:\n 'no mnci-normalise-declaration-specifiers plugin — the emitted .d.ts files keep the extensionless imports tsconfig.lib.json allows, which a nodenext consumer cannot resolve',\n // Deliberately NOT `mnci upgrade`, which would send the user in a\n // circle: `upgradeDeclarationSpecifierPlugins` only rewrites a\n // plugin that is already there, and `repairDeclarationSpecifiers`\n // is anchored on the generator's own placeholder comment, which a\n // config in this state no longer has. Verified by running the\n // command against exactly this shape — it no-ops. Naming a command\n // that silently does nothing is the failure mode this file's own\n // source-map check already goes out of its way to avoid.\n remedy:\n \"add the plugin to withNx's second argument by hand (copy it from a freshly `mnci add npm-lib`'d project) — `mnci upgrade` only updates a plugin that is already present, it cannot restore a missing one\",\n },\n ]\n }\n\n return [\n {\n check: `${relativePath} resolves directory barrels in declarations`,\n ok: hasDirectoryAwareDeclarationSpecifiers(resolved),\n detail:\n 'the plugin appends .js to every bare specifier, including ones naming a directory barrel, which needs /index.js — the named file is never emitted, so every export behind it publishes as `any` with nothing reporting it',\n remedy: 'run `mnci upgrade`, which replaces the plugin body with the current one',\n },\n ]\n })\n}\n\n/**\n * Collects every doctor finding for a workspace.\n *\n * @remarks\n * Exported separately from {@link runDoctor} so the checks can be asserted\n * directly, without capturing console output or an exit code.\n *\n * Every check corresponds to an invariant that has actually been violated in\n * practice, in this repo or in a workspace it generated — none are hypothetical.\n * That is the bar for adding one: a check nobody has ever needed is noise that\n * trains people to ignore the output.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The findings, in report order.\n * @throws Error when `workspaceRoot` has no `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectFindings (workspaceRoot: string): Finding[] {\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n if (!fileExists(nxJsonPath)) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — run 'mnci doctor' from the workspace root.`,\n )\n }\n const nxJson = readJson<{\n plugins?: unknown[]\n mnci?: { registry?: RegistryConfig; scope?: string }\n }>(nxJsonPath)\n\n return [\n ...checkEslintConfigs(workspaceRoot),\n checkNoRetiredFormatter(workspaceRoot),\n checkEslintPlugin(nxJson),\n checkResolvedEslint(workspaceRoot),\n checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),\n checkNpmrcCredentialResolves(workspaceRoot),\n checkNpmAuthMatchesPipeline(workspaceRoot, nxJson.mnci?.registry),\n checkNoRootRuntimeDependencies(workspaceRoot),\n ...checkRollupSourceMaps(workspaceRoot),\n ...checkDeclarationSpecifiers(workspaceRoot),\n ...checkVersionActions(workspaceRoot),\n ...checkTargetFilesExist(workspaceRoot),\n checkSync(workspaceRoot),\n ].filter((finding): finding is Finding => finding !== undefined)\n}\n\n/**\n * Reports on the mnci invariants a workspace is supposed to uphold.\n *\n * @remarks\n * Read-only by design: it never edits the workspace, so it is safe to run\n * anywhere, and every failing finding names the command that fixes it (usually\n * `mnci upgrade`) rather than fixing it silently.\n *\n * Exits non-zero when anything failed, so it works as a CI step as well as a\n * local command. Sets `process.exitCode` rather than calling `process.exit`, so\n * output is never truncated.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an Nx workspace.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runDoctor (workspaceRoot: string): void {\n const findings = collectFindings(workspaceRoot)\n const failed = findings.filter(finding => !finding.ok)\n\n for (const finding of findings) {\n if (finding.ok) {\n logger.success(finding.check)\n } else {\n logger.error(`${finding.check} — ${finding.detail ?? 'failed'}`)\n if (finding.remedy) {\n logger.info(` fix: ${finding.remedy}`)\n }\n }\n }\n\n if (failed.length === 0) {\n logger.success(`All ${findings.length} checks passed.`)\n\n return\n }\n logger.error(`${failed.length} of ${findings.length} checks failed.`)\n process.exitCode = 1\n}\n","import { cpSync, existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Moving a freshly generated workspace into a directory that already exists.\n *\n * @remarks\n * `create-nx-workspace <name>` refuses a directory that is already there\n * (`DIRECTORY_EXISTS`), which rules out the single most common way a repository\n * starts: the host creates it, you clone it, and the clone holds a `.git`\n * directory and possibly a README. Generating elsewhere and moving the result\n * in is the only way through, and doing it by hand is exactly the kind of step\n * that silently loses a file.\n *\n * So the move is the thing that gets written down and tested, rather than left\n * to the reader. The whole risk is in the collisions, so every one of them is\n * decided in advance and reported, and nothing is copied until the target is\n * known to be safe.\n */\n\n/**\n * Entries that never travel: the target's own `.git` is the point of the\n * exercise, and `node_modules` is reinstalled from scratch straight after (the\n * lockfile and the tree are both discarded so npm re-resolves with the\n * overlay's `overrides` in place).\n */\nconst NEVER_COPIED: ReadonlySet<string> = new Set(['.git', 'node_modules'])\n\n/**\n * Entries the generator writes only for the overlay to strip or replace, so an\n * existing copy can always win.\n *\n * @remarks\n * `create-nx-workspace` (Nx 23.2) writes its AI-agent rules and skills into\n * every agent's conventional location: `CLAUDE.md` and `AGENTS.md` (inside a\n * marked block), `.claude/settings.json`, `.agents`, `.codex`, `.cursor`,\n * `.gemini`, `.opencode`, `opencode.json`, plus `.github/agents`,\n * `.github/prompts`, `.github/skills` and a `.github/workflows/ci.yml`. The\n * overlay that runs straight\n * after adoption removes or replaces every one of them\n * (`NX_SCAFFOLDING_TO_REMOVE`, `removeNxAuthoredAgentFiles`, and the CI file\n * it writes itself), so the generated copy never survives anyway.\n *\n * Treating them as collisions therefore rejected the case `--into` exists\n * for while protecting nothing: a repository with its own `CLAUDE.md` could\n * not be adopted at all. Found adopting Lore Master (russoedu/MarkDoc),\n * whose planning `CLAUDE.md` predates the workspace. The same holds for a\n * repository's own `.github` (issue templates, workflows): mnci's CI file is\n * written by the overlay, not copied from the generator, so keeping the\n * existing directory loses only Nx's agent skills.\n *\n */\nconst AGENT_AND_CI_ENTRIES: readonly string[] = [\n 'AGENTS.md',\n 'CLAUDE.md',\n '.claude',\n '.agents',\n '.codex',\n '.cursor',\n '.gemini',\n '.opencode',\n 'opencode.json',\n '.github',\n]\n\n/**\n * Files where an existing copy WINS, because they belong to whoever created\n * the repository rather than to the generator, or because the generator's\n * copy is stripped by the overlay anyway ({@link AGENT_AND_CI_ENTRIES}).\n *\n * @remarks\n * A host-created repository routinely arrives with a README and a licence\n * chosen by a human. `create-nx-workspace` writes a README of its own, and\n * treating that as a collision would reject the very case this exists for,\n * while overwriting it would throw away the only hand-written file in the\n * repository. Neither is right, so the existing file stays and the result says\n * so.\n */\nconst EXISTING_WINS: ReadonlySet<string> = new Set([\n 'README.md',\n 'LICENSE',\n 'LICENSE.md',\n 'LICENSE.txt',\n ...AGENT_AND_CI_ENTRIES,\n])\n\n/**\n * Entries whose presence is fatal, checked BEFORE generating anything.\n *\n * @remarks\n * Generating a workspace takes minutes, so the hopeless cases are worth\n * catching first. Any of these means the directory is already a project of some\n * kind, and adopting it would overwrite real work.\n */\nconst PREFLIGHT_BLOCKERS: readonly string[] = [\n 'package.json',\n 'nx.json',\n 'tsconfig.base.json',\n 'node_modules',\n 'apps',\n 'libs',\n 'packages',\n]\n\n/**\n * What {@link adoptGeneratedWorkspace} did, for reporting.\n *\n * @remarks\n * Every entry the adoption touched is accounted for in exactly one of these\n * lists, so the caller can print what happened to a directory the user already\n * owned rather than asking them to diff it afterwards.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface AdoptionResult {\n /** Top-level entries copied in from the generated workspace. */\n copied: string[]\n /** Top-level entries left exactly as they were found (see {@link EXISTING_WINS}). */\n kept: string[]\n /** `true` when the generated `.gitignore` contributed lines to an existing one. */\n mergedGitignore: boolean\n}\n\n/**\n * Rejects a target directory that cannot be adopted, before anything is\n * generated.\n *\n * @remarks\n * Deliberately cheap and deliberately incomplete: it catches a directory that\n * is already a project, so the caller does not spend minutes generating a\n * workspace it is about to throw away. The full collision check happens in\n * {@link adoptGeneratedWorkspace}, where the generated tree is known, and that\n * one still runs before a single file is written.\n *\n * @param target - Absolute path to the directory to adopt.\n * @throws Error when the path is missing, is not a directory, or already holds\n * a project.\n * @returns Nothing; it either passes or throws.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function assertAdoptableDirectory (target: string): void {\n if (!existsSync(target)) {\n throw new Error(`--into '${target}' does not exist. Create or clone it first.`)\n }\n if (!statSync(target).isDirectory()) {\n throw new Error(`--into '${target}' is not a directory.`)\n }\n\n const blockers = PREFLIGHT_BLOCKERS.filter(entry => existsSync(join(target, entry)))\n if (blockers.length > 0) {\n throw new Error(\n `--into '${target}' already holds ${blockers.join(', ')}, so it is a project already. ` +\n 'Use `mnci upgrade` to re-apply the overlay to an existing workspace, or point --into at an empty checkout.',\n )\n }\n}\n\n/**\n * Appends whatever the generated `.gitignore` adds to an existing one.\n *\n * @remarks\n * A merge rather than a choice, because both halves matter and neither is\n * optional. The repository's own file may ignore things the generator knows\n * nothing about; the generated one ignores `.nx/cache`, `dist` and\n * `out-tsc`, and a workspace that commits those is a workspace whose every\n * build shows up as a diff. Lines already present are not repeated, so running\n * it twice changes nothing.\n *\n * @param generatedFile - The generated `.gitignore`.\n * @param targetFile - The existing `.gitignore`, which is rewritten in place.\n * @returns `true` when at least one line was added.\n * @throws Propagates filesystem errors.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction mergeGitignore (generatedFile: string, targetFile: string): boolean {\n const existing = readFileSync(targetFile, 'utf8')\n const present = new Set(existing.split('\\n').map(line => line.trim()))\n const additions = readFileSync(generatedFile, 'utf8')\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => {\n const trimmed = line.trim()\n\n return trimmed !== '' && !trimmed.startsWith('#') && !present.has(trimmed)\n })\n\n if (additions.length === 0) return false\n\n const separator = existing.endsWith('\\n') ? '' : '\\n'\n writeFileSync(\n targetFile,\n `${existing}${separator}\\n# Added by mnci (Nx workspace)\\n${additions.join('\\n')}\\n`,\n )\n\n return true\n}\n\n/**\n * Moves a generated workspace into an existing directory, keeping its `.git`.\n *\n * @remarks\n * Every collision is resolved before anything is written, so a refusal leaves\n * the target byte-identical to how it was found. The generated tree is\n * discarded by the caller either way.\n *\n * @param generatedRoot - The freshly generated workspace (a staging directory).\n * @param target - The existing directory to adopt.\n * @returns What was copied, kept, and merged.\n * @throws Error naming every colliding entry, when any exists that is not\n * resolved by {@link EXISTING_WINS} or the `.gitignore` merge.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function adoptGeneratedWorkspace (generatedRoot: string, target: string): AdoptionResult {\n const entries = readdirSync(generatedRoot).filter(entry => !NEVER_COPIED.has(entry))\n\n const collisions = entries.filter(\n entry =>\n existsSync(join(target, entry)) && !EXISTING_WINS.has(entry) && entry !== '.gitignore',\n )\n if (collisions.length > 0) {\n throw new Error(\n `--into '${target}' already contains ${collisions.join(', ')}, which the new workspace also writes. ` +\n 'Move those aside and run it again; nothing has been changed.',\n )\n }\n\n const result: AdoptionResult = { copied: [], kept: [], mergedGitignore: false }\n\n for (const entry of entries) {\n const from = join(generatedRoot, entry)\n const to = join(target, entry)\n\n if (entry === '.gitignore' && existsSync(to)) {\n result.mergedGitignore = mergeGitignore(from, to)\n result.kept.push(entry)\n continue\n }\n if (existsSync(to)) {\n result.kept.push(entry)\n continue\n }\n\n cpSync(from, to, { recursive: true })\n result.copied.push(entry)\n }\n\n // The staging copy has served its purpose, and it carries a `.git` of its\n // own (`create-nx-workspace` initialises one), which is worth not leaving\n // behind in a temp directory.\n rmSync(generatedRoot, { recursive: true, force: true })\n\n return result\n}\n","import { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve } from 'node:path'\nimport { runFormatter, runNpx, runShell } from '../nx-workspace'\nimport {\n applyOverlay,\n DEFAULT_STACK,\n resolveNpmAuth,\n type CiProvider,\n type RegistryConfig,\n type StackConfig,\n} from '../workspace-overlay'\nimport { promptCi, promptNxCloud, promptRegistry, promptStack, promptText } from '../terminal'\nimport { logger } from '../terminal'\nimport { assertValidProjectName } from '../project-name'\nimport { adoptGeneratedWorkspace, assertAdoptableDirectory } from './adopt-directory.use-case'\n\n/**\n * Options accepted by {@link runNew}.\n *\n * @remarks\n * Mirrors the CLI's flags; every field left `undefined` is prompted for\n * interactively (unless `yes` short-circuits to defaults).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface NewOptions {\n /** Skip prompts, accepting defaults for everything not passed as a flag. */\n yes?: boolean\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope?: string\n /** Registry kind: `azure-artifacts` or `npm`. */\n registry?: RegistryConfig['kind']\n /** Azure DevOps organization (azure-artifacts only). */\n organization?: string\n /** Azure DevOps project (azure-artifacts only). */\n project?: string\n /** Azure Artifacts feed name (azure-artifacts only). */\n artifactsFeed?: string\n /** CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent?: string\n /** Library variable group holding the base64 npm `PAT`. */\n variableGroup?: string\n /** How npm authenticates to an Azure Artifacts feed: `pat` | `build-identity`. */\n npmAuth?: string\n /** CI provider: `azure` | `github` | `both`. */\n ci?: CiProvider\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n /** Opt in to Nx Cloud (remote caching + CI insights). Default: not connected. */\n nxCloud?: boolean\n /**\n * Bootstrap into an existing directory (typically a fresh clone holding\n * nothing but `.git`) instead of creating a new one.\n */\n into?: string\n}\n\n/**\n * Maps the chosen CI provider to the `create-nx-workspace --nxCloud` value\n * used when opting in.\n *\n * @remarks\n * Deliberately never `--nxCloud=yes` (the provider-agnostic value): verified\n * empirically that it prompts \"Will you be using GitHub as your git hosting\n * provider?\" even with `--useGitHub` and `--no-interactive` both set, and\n * exits without creating the workspace at all when stdin is not a TTY — a\n * real upstream inconsistency in `create-nx-workspace`, not something this\n * CLI can configure around. A named provider (`github`/`azure`/…) sidesteps\n * that prompt entirely and completes non-interactively every time.\n *\n * The provider value only controls the throwaway CI workflow file\n * `create-nx-workspace` writes as a side effect of Cloud setup — this CLI's\n * own {@link applyOverlay} unconditionally overwrites whatever lands at that\n * same path right after, so the specific value has no visible effect beyond\n * avoiding the hang. `both` has no Nx equivalent, so it maps to `github`\n * (arbitrary but harmless, for the same reason).\n *\n * @param ci - The chosen CI provider.\n * @returns The `--nxCloud` value to pass when Nx Cloud is opted into.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxCloudProviderValue (ci: CiProvider): 'github' | 'azure' {\n return ci === 'azure' ? 'azure' : 'github'\n}\n\n/**\n * Resolves the stack from flags, prompts, or `--yes` defaults.\n *\n * @remarks\n * The testRunner flag is taken as-is if passed; otherwise the interactive\n * {@link promptStack} runs and fills the gap, or `--yes` uses the default.\n *\n * @param options - The CLI flags.\n * @returns The resolved stack configuration.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function resolveStack (options: NewOptions): Promise<StackConfig> {\n // The flag skips the prompt entirely: the stack is one choice now that the\n // linter is no longer part of it.\n if (options.testRunner || options.yes) {\n return { testRunner: options.testRunner ?? DEFAULT_STACK.testRunner }\n }\n const prompted = await promptStack()\n\n return { testRunner: prompted.testRunner }\n}\n\n/**\n * Resolves the CI provider from a flag, `--yes`'s default, or a prompt.\n *\n * @remarks\n * `azure` stays the `--yes`/flagless default — the long-standing behaviour —\n * so an existing flagless `mnci new` keeps writing exactly the same file it\n * always has. An explicit flag value is trusted as-is with no validation\n * (matching `resolveRegistry`'s equally loose `--registry` handling): a\n * typo just falls through to the flagless default instead of prompting.\n *\n * @param options - The CLI flags.\n * @returns The resolved CI provider.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nconst CI_PROVIDERS: ReadonlySet<CiProvider> = new Set(['azure', 'github', 'both'])\n\nasync function resolveCi (options: NewOptions): Promise<CiProvider> {\n if (options.ci && CI_PROVIDERS.has(options.ci)) {\n return options.ci\n }\n if (options.yes) {\n return 'azure'\n }\n\n return await promptCi()\n}\n\n/**\n * Resolves the registry configuration from flags, prompts or defaults.\n *\n * @param options - The CLI flags.\n * @returns The resolved registry configuration.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function resolveRegistry (options: NewOptions): Promise<RegistryConfig> {\n if (options.registry === 'azure-artifacts' || (options.organization && options.artifactsFeed)) {\n return {\n kind: 'azure-artifacts',\n organization: options.organization ?? (await promptText('Azure DevOps organization')),\n project: options.project ?? (await promptText('Azure DevOps project')),\n artifactsFeed: options.artifactsFeed ?? (await promptText('Artifacts feed name')),\n }\n }\n if (options.registry === 'npm' || options.yes) {\n return { kind: 'npm' }\n }\n\n return await promptRegistry()\n}\n\n/**\n * Creates a brand-new monorepo: Nx's own TS preset plus this CLI's overlay.\n *\n * @remarks\n * The heavy lifting is `create-nx-workspace --preset=ts` (npm workspaces +\n * TypeScript project references, no per-project `project.json`). This then\n * applies its one layer of opinion ({@link applyOverlay}) and installs the\n * conventional-commit toolchain (`husky` + `@commitlint/*`) for real, so the\n * versions resolve at generation time.\n *\n * @param name - The workspace (and directory) name.\n * @param options - The CLI flags.\n * @returns A promise that resolves when the workspace is ready.\n * @throws Error when any underlying command exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runNew (name: string | undefined, options: NewOptions): Promise<void> {\n // `--into` doubles as the name when none is given, because the directory is\n // already named: a cloned repository's folder IS the workspace name, and\n // retyping it is a way to get the two out of step.\n const adoptTarget = options.into === undefined ? undefined : resolve(process.cwd(), options.into)\n // Checked before a single prompt, let alone the minutes `create-nx-workspace`\n // takes. Finding out that the target is unusable after generating into a\n // temp directory is finding out too late.\n if (adoptTarget !== undefined) assertAdoptableDirectory(adoptTarget)\n\n const workspaceName =\n name ?? (adoptTarget === undefined ? await promptText('Workspace name') : basename(adoptTarget))\n // Fails fast, before any further prompt or side effect: the name becomes a\n // directory, a `create-nx-workspace` argument and (derived) an npm scope, so\n // a bad one should never get this far — and an explicitly empty `name`\n // argument bypasses promptText's own non-empty check, which only fires on\n // the prompted path.\n assertValidProjectName(workspaceName, 'Workspace name')\n const scope =\n options.scope ??\n (options.yes\n ? `@${workspaceName}`\n : await promptText('npm scope for publishable packages', `@${workspaceName}`))\n const registry = await resolveRegistry(options)\n const ci = await resolveCi(options)\n const agent =\n options.agent ??\n (options.yes\n ? 'ubuntu-latest'\n : await promptText(\n 'CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)',\n 'ubuntu-latest',\n ))\n // The variable group is an Azure Pipelines concept (GitHub reads a plain\n // `PAT` repository secret instead, no CLI-collected name needed) — skipped\n // when Azure is not one of the chosen providers.\n const variableGroup =\n ci === 'github'\n ? (options.variableGroup ?? 'Build')\n : (options.variableGroup ??\n (options.yes\n ? 'Build'\n : await promptText('Azure DevOps variable group holding the npm PAT', 'Build')))\n // Validated before anything is created: a refused combination must not leave a\n // half-generated workspace behind.\n const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci)\n const stack = await resolveStack(options)\n const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud())\n\n // `create-nx-workspace <name>` insists on creating the directory itself and\n // exits with DIRECTORY_EXISTS otherwise, so adopting one means generating\n // somewhere else and moving the result in. A temp directory rather than a\n // sibling of the target, so a failed run leaves nothing next to the\n // repository to clean up by hand.\n const stagingParent =\n adoptTarget === undefined ? process.cwd() : mkdtempSync(join(tmpdir(), 'mnci-new-'))\n\n let workspaceRoot = join(stagingParent, workspaceName)\n try {\n logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`)\n runNpx(\n [\n '--yes',\n 'create-nx-workspace@latest',\n workspaceName,\n '--preset=ts',\n '--pm=npm',\n nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : '--nxCloud=skip',\n '--no-interactive',\n ],\n stagingParent,\n )\n\n if (adoptTarget !== undefined) {\n logger.step(`Adopting ${adoptTarget} (keeping its .git)`)\n // Throws on any collision it cannot resolve, having written nothing.\n const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget)\n for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`)\n if (adoption.mergedGitignore) logger.detail('merged the generated .gitignore into the existing one')\n workspaceRoot = adoptTarget\n }\n } finally {\n // The staging tree goes on EVERY path. An earlier comment here claimed the\n // failure path cleaned up \"by the process exiting\", which a temp directory\n // never does: a refused adoption left a full workspace, node_modules\n // included, in the OS temp dir on every attempt. Only the temp tree is\n // removed - without `--into`, `stagingParent` is the user's cwd.\n if (adoptTarget !== undefined) rmSync(stagingParent, { recursive: true, force: true })\n }\n\n logger.step(\n 'Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)',\n )\n applyOverlay(\n workspaceRoot,\n { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...(npmAuth && { npmAuth }) },\n logger.detail,\n )\n\n // npm honours `overrides` only when it RESOLVES a tree, and by this point\n // `create-nx-workspace` has already installed one and written its lockfile —\n // both without the overrides `applyOverlay` just added. npm then reuses that\n // tree wholesale on the next install, so the overrides are silently ignored.\n //\n // This is not theoretical, and it is npm-version dependent, which is what\n // made it survive three nightlies. Reproduced on nx 23.1.1, which nests a\n // vulnerable `brace-expansion@5.0.8` under `node_modules/nx`:\n //\n // npm 10.9.7 create → overlay → install → 0 advisories\n // npm 11.19.0 create → overlay → install → 6 advisories\n //\n // So the generated workspace shipped six high advisories while this repo —\n // whose own lockfile was resolved WITH the overrides present — read 0, and\n // every local check agreed with the wrong one.\n //\n // Both artifacts have to go. Removing only the lockfile still reports 6 under\n // npm 11: an existing `node_modules` is by itself enough for npm to keep the\n // stale tree. Measured both ways rather than assumed.\n rmSync(join(workspaceRoot, 'node_modules'), { recursive: true, force: true })\n rmSync(join(workspaceRoot, 'package-lock.json'), { force: true })\n\n // One install. It also pulls in everything `applyOverlay` just added to the\n // manifest — Prettier, ESLint and `@mnci/eslint-config` — which is why the\n // formatting pass below can run immediately after. It resolves from scratch,\n // by construction of the two removals above.\n logger.step('Installing the toolchain (commitlint + husky + linting/formatting)')\n const installStatus = runShell(\n 'npm',\n ['install', '--save-dev', 'husky', '@commitlint/cli', '@commitlint/config-conventional'],\n workspaceRoot,\n )\n if (installStatus !== 0) {\n throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`)\n }\n // The overlay already stamped `prepare: husky` into the root scripts.\n runShell('npx', ['husky'], workspaceRoot)\n\n // `create-nx-workspace` wrote its scaffold in its own style, which is not\n // mnci's. Normalise it now so the workspace passes its own `lint` from the\n // very first commit.\n logger.step('Formatting the workspace (eslint --fix, JavaScript Standard Style)')\n runFormatter(workspaceRoot)\n\n logger.success('Done. Next steps:')\n if (adoptTarget === undefined) logger.info(` cd ${workspaceName}`)\n logger.info(' mnci add react-app web # or: react-lib, react-internal-lib, node-app,')\n logger.info(' # node-function-app, npm-lib, internal-lib,')\n logger.info(\n ' # python-app, python-function-app, python-lib, python-internal-lib,',\n )\n logger.info(\n ' # go-app, go-function-app, go-lib, go-internal-lib,',\n )\n logger.info(\n ' # flutter-app, flutter-lib, flutter-internal-lib,',\n )\n logger.info(' # vscode-extension')\n logger.info(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../file-system'\nimport { runAdd } from '../project-scaffolding'\nimport { runNew } from './create-workspace.use-case'\nimport { runUpgrade } from '../workspace-upgrade'\n\n/** The three things the wizard can start: scaffold, add to, or upgrade a workspace. */\ntype InteractiveAction = 'new' | 'add' | 'upgrade'\n\n/**\n * Runs the guided wizard shown when `mnci` is invoked with no arguments.\n *\n * @remarks\n * A thin dispatcher: it picks between the three commands, then hands off to\n * the existing flows — {@link runNew}, {@link runAdd} and {@link runUpgrade} —\n * which already handle every field they need (name/scope/registry/agent/\n * variable group for `new`; kind/name, and the npm-lib scope on this bare\n * path, for `add`; whatever `mnci new`/a previous upgrade persisted, for\n * `upgrade`). Nothing about the prompting or resolution lives here, so the\n * wizard and the flag-driven commands can never drift.\n *\n * The menu is ordered by context: inside a workspace (an `nx.json` in the cwd)\n * `add` is offered first, then `upgrade`, then `new` — so the default\n * highlight matches what the user most likely wants. All three are always\n * shown; picking `add` or `upgrade` outside a workspace surfaces that flow's\n * own clear \"run from the workspace root\" error.\n *\n * @param None - this function takes no parameters.\n * @returns A promise that resolves when the chosen flow completes.\n * @throws Propagates prompt errors and any failure from the dispatched flow.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runInteractive (): Promise<void> {\n const inWorkspace = fileExists(join(process.cwd(), 'nx.json'))\n const choiceNew = { name: 'Create a new monorepo', value: 'new' as const }\n const choiceAdd = { name: 'Add a project to this workspace', value: 'add' as const }\n const choiceUpgrade = {\n name: 'Upgrade this workspace (re-apply the latest overlay)',\n value: 'upgrade' as const,\n }\n\n const action = await select<InteractiveAction>({\n message: 'What would you like to do?',\n choices: inWorkspace\n ? [choiceAdd, choiceUpgrade, choiceNew]\n : [choiceNew, choiceAdd, choiceUpgrade],\n })\n\n if (action === 'new') {\n await runNew(undefined, {})\n } else if (action === 'upgrade') {\n runUpgrade(process.cwd(), {})\n } else {\n await runAdd(undefined, undefined, {})\n }\n}\n","import { readdirSync, readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport { runFormatter } from '../nx-workspace'\nimport {\n addGoPlatformTargets,\n pinVscodeExtensionProjectNames,\n refreshVscodeExtensionScript,\n VSCODE_EXTENSION_SCRIPT_PATH,\n} from '../project-scaffolding'\nimport {\n repairPublishableManifests,\n repairRollupSourceMaps,\n upgradeDeclarationSpecifierPlugins,\n} from '../rollup-library'\nimport {\n applyOverlay,\n readMnciConfig,\n resolveNpmAuth,\n type CiProvider,\n type NpmAuthMode,\n type OverlayOptions,\n type RegistryConfig,\n type StackConfig,\n} from '../workspace-overlay'\nimport { fileExists } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * Options accepted by {@link runUpgrade}.\n *\n * @remarks\n * Mirrors `mnci new`'s flags exactly — an explicit flag overrides whatever\n * `mnci new`/a previous `upgrade` persisted in `nx.json`'s `mnci` block\n * ({@link readMnciConfig}); everything left `undefined` falls back to that\n * persisted value. Unlike `new`, there is no `--yes`/prompt fallback: a\n * workspace missing a persisted field (predates persistence, or had it\n * hand-edited away) needs that one flag passed explicitly, reported as a\n * clear error rather than guessed or prompted for.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface UpgradeOptions {\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope?: string\n /** Registry kind: `azure-artifacts` or `npm`. */\n registry?: RegistryConfig['kind']\n /** Azure DevOps organization (azure-artifacts only). */\n organization?: string\n /** Azure DevOps project (azure-artifacts only). */\n project?: string\n /** Azure Artifacts feed name (azure-artifacts only). */\n artifactsFeed?: string\n /** CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent?: string\n /** Library variable group holding the base64 npm `PAT`. */\n variableGroup?: string\n /** How npm authenticates to an Azure Artifacts feed: `pat` | `build-identity`. */\n npmAuth?: string\n /** CI provider: `azure` | `github` | `both`. */\n ci?: CiProvider\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n}\n\n/**\n * Resolves the registry from flags, falling back to whatever is persisted.\n *\n * @remarks\n * Mirrors `new.ts`'s `resolveRegistry`, minus the interactive-prompt\n * fallback: `upgrade` is as likely to run unattended (a script, a CI step)\n * as interactively, so a genuinely missing value is a clear thrown error,\n * not a prompt that would hang a non-interactive run.\n *\n * @param options - The CLI flags.\n * @param persisted - The registry persisted in `nx.json`'s `mnci` block, if any.\n * @returns The resolved registry configuration.\n * @throws Error when Azure Artifacts coordinates or a registry kind cannot\n * be resolved from either flags or the persisted config.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveRegistry (\n options: UpgradeOptions,\n persisted: RegistryConfig | undefined,\n): RegistryConfig {\n if (options.registry === 'azure-artifacts' || (options.organization && options.artifactsFeed)) {\n const persistedAzure = persisted?.kind === 'azure-artifacts' ? persisted : undefined\n const organization = options.organization ?? persistedAzure?.organization\n const project = options.project ?? persistedAzure?.project\n const artifactsFeed = options.artifactsFeed ?? persistedAzure?.artifactsFeed\n if (!organization || !project || !artifactsFeed) {\n throw new Error(\n \"Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either).\",\n )\n }\n\n return { kind: 'azure-artifacts', organization, project, artifactsFeed }\n }\n if (options.registry === 'npm') {\n return { kind: 'npm' }\n }\n if (persisted) {\n return persisted\n }\n throw new Error(\n \"No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed).\",\n )\n}\n\n/**\n * The workspace's own name, for the `<name>.code-workspace` file the overlay writes.\n *\n * @remarks\n * This exists because `resolveOverlayOptions` used to omit `workspaceName`\n * entirely, so `applyOverlay` interpolated `undefined` and every `mnci upgrade`\n * wrote a file literally named `undefined.code-workspace` — while the\n * workspace's real one was never rewritten, making the `.code-workspace` the one\n * mnci-owned file an upgrade could not actually carry a fix into.\n *\n * A fallback chain rather than a single source, because the authoritative one is\n * only available going forward:\n *\n * 1. **The persisted `mnci.workspaceName`** — written by `mnciConfig` as of this\n * fix, so correct for anything generated or upgraded from now on.\n * 2. **An existing `*.code-workspace` filename** — accurate for a workspace\n * generated before that field was persisted. `undefined.code-workspace` is\n * skipped explicitly: a workspace carrying junk from the old bug would\n * otherwise resolve its name as the string `undefined` and keep reproducing\n * it forever.\n * 3. **The directory basename** — what `create-nx-workspace` names the directory,\n * so it is right in the ordinary case and always available.\n *\n * The root `package.json` name is deliberately *not* in the chain: it is\n * `@<scope>/source`, which carries the scope rather than the workspace name, and\n * the two differ whenever `--scope` was passed explicitly.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param persisted - Whatever `mnci new`/a previous `upgrade` persisted.\n * @returns The workspace name.\n * @throws Never - an unreadable directory falls through to the basename.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveWorkspaceName (workspaceRoot: string, persisted: Partial<OverlayOptions>): string {\n if (persisted.workspaceName) {\n return persisted.workspaceName\n }\n try {\n const existing = readdirSync(workspaceRoot).find(\n file => file.endsWith('.code-workspace') && file !== 'undefined.code-workspace',\n )\n if (existing) {\n return existing.replace(/\\.code-workspace$/, '')\n }\n } catch {\n // Fall through to the basename.\n }\n\n return basename(workspaceRoot)\n}\n\n/**\n * Finds the npm auth mode a workspace already uses, for one that never recorded it.\n *\n * @remarks\n * `npmAuthenticate@0` in the existing pipeline is an unambiguous statement of\n * intent: nothing else writes it, and `mnci` did not generate it until this option\n * existed, so a workspace that has it added it by hand. Without this, an upgrade\n * would regenerate the pipeline without the task and the `.npmrc` with a PAT block -\n * silently reverting the one thing that makes a feed publish work, which is exactly\n * how a real workspace lost it. Consulted only when neither a flag nor a persisted\n * value says otherwise, so `--npm-auth pat` still wins.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns `build-identity` when the existing pipeline runs the task, else `undefined`.\n * @throws Never - an unreadable pipeline is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction detectNpmAuth (workspaceRoot: string): NpmAuthMode | undefined {\n try {\n return readFileSync(join(workspaceRoot, 'azure-pipelines.yml'), 'utf8').includes('npmAuthenticate@0')\n ? 'build-identity'\n : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Resolves the full overlay options a `mnci upgrade` run applies: an\n * explicit flag wins field-by-field, otherwise the persisted `mnci` block\n * (see {@link readMnciConfig}) is the default.\n *\n * @param workspaceRoot - Absolute path to the workspace, for\n * {@link resolveWorkspaceName}'s filename and basename fallbacks.\n * @param options - The CLI flags.\n * @param persisted - Whatever `mnci new`/a previous `upgrade` persisted.\n * @returns The fully resolved overlay options.\n * @throws Error when a required field is missing from both the flags and\n * the persisted config, naming the flag needed to supply it.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveOverlayOptions (\n workspaceRoot: string,\n options: UpgradeOptions,\n persisted: Partial<OverlayOptions>,\n): OverlayOptions {\n const scope = options.scope ?? persisted.scope\n if (!scope) {\n throw new Error(\"No npm scope found in nx.json's persisted config. Pass --scope explicitly.\")\n }\n const registry = resolveRegistry(options, persisted.registry)\n const ci = options.ci ?? persisted.ci\n if (!ci) {\n throw new Error(\n \"No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly.\",\n )\n }\n const agent = options.agent ?? persisted.agent\n if (!agent) {\n throw new Error(\n \"No CI build agent found in nx.json's persisted config. Pass --agent explicitly.\",\n )\n }\n // Azure-only concept; a github-only workspace never needed one, so a\n // missing persisted value is not an error the way scope/ci/agent are.\n const variableGroup = options.variableGroup ?? persisted.variableGroup ?? 'Build'\n // A flag wins, then what was persisted, then what an existing pipeline already\n // does. Detection only counts where the mode is deliverable: a workspace that also\n // carries a GitHub pipeline cannot be moved to build identity by inference.\n const detected =\n ci === 'azure' && persisted.npmAuth === undefined && options.npmAuth === undefined\n ? detectNpmAuth(workspaceRoot)\n : undefined\n if (detected) {\n logger.detail('keeping build-identity npm auth: azure-pipelines.yml already runs npmAuthenticate@0')\n }\n const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci, persisted.npmAuth ?? detected)\n // Defaults to `eslint` rather than erroring when absent, unlike testRunner\n const testRunner = options.testRunner ?? persisted.stack?.testRunner\n if (!testRunner) {\n throw new Error(\n \"No testRunner found in nx.json's persisted config. Pass --test-runner explicitly.\",\n )\n }\n\n return {\n workspaceName: resolveWorkspaceName(workspaceRoot, persisted),\n scope,\n registry,\n agent,\n variableGroup,\n ci,\n stack: { testRunner },\n ...(npmAuth && { npmAuth }),\n }\n}\n\n/**\n * Re-applies the latest MoNecromanCI overlay to an already-generated\n * workspace.\n *\n * @remarks\n * Every improvement to `overlay.ts` since a workspace's `mnci new` only ever\n * reached *future* workspaces until now — nothing let an existing one pick\n * up a later fix (the tag-push ordering fix, the global Python workspace\n * install, the Windows `python`/`python3` resolution, all landed this way).\n * `upgrade` closes that gap: it resolves the same {@link OverlayOptions}\n * `new` would have (explicit flags over whatever `mnci new` persisted — see\n * {@link resolveOverlayOptions}) and calls {@link applyOverlay} again, the\n * exact same pure, idempotent file-writer `new` itself calls. No diffing or\n * confirmation prompt: every file `applyOverlay` touches is `mnci`-owned\n * (`nx.json`'s `release`/`sync`/`generators`/`mnci` blocks, `.npmrc`,\n * `commitlint.config.mjs`, `.husky/commit-msg`, the CI pipeline file(s), and\n * the curated root `package.json` scripts), and virtually every generated\n * workspace is already a git repo — `git diff` is the review step, not a\n * bespoke one built here.\n *\n * @param workspaceRoot - Absolute path to the workspace to upgrade (the\n * current working directory).\n * @param options - The CLI flags.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an `mnci`-generated workspace,\n * or a required field is missing from both the flags and the persisted\n * config.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runUpgrade (workspaceRoot: string, options: UpgradeOptions): void {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`,\n )\n }\n const persisted = readMnciConfig(workspaceRoot)\n const resolved = resolveOverlayOptions(workspaceRoot, options, persisted)\n\n logger.step('Re-applying the MoNecromanCI overlay')\n applyOverlay(workspaceRoot, resolved, logger.detail)\n\n // A per-project sweep, unlike everything above it. A rollup config is written\n // once at `add` time, so a workspace generated before source maps were wired\n // in stays undebuggable forever otherwise — a breakpoint in a .ts file simply\n // never binds, and nothing says why. Idempotent, so a repeat upgrade is a\n // no-op, and it reports what it touched because this is the one step that\n // edits a file inside a project rather than an mnci-owned root file.\n const repaired = repairRollupSourceMaps(workspaceRoot)\n if (repaired.length > 0) {\n logger.step('Enabling source maps in rollup configs (breakpoints in .ts files)')\n for (const path of repaired) {\n logger.detail(`updated ${path}`)\n }\n }\n\n // Same reasoning as the source-map sweep above, for the two OTHER\n // publishable-project repairs that only used to run once, at `add` time:\n // the `types` path fix (a project `add`ed before it existed stayed\n // `any` for every TypeScript consumer forever) and the declaration\n // specifiers' `.js`-extension fix (stayed unresolvable for every\n // `nodenext` consumer forever). Both are idempotent for the same reason\n // the source-map sweep is.\n const repairedManifests = repairPublishableManifests(workspaceRoot)\n if (repairedManifests.length > 0) {\n logger.step('Repointing `types` at the real declaration file')\n for (const path of repairedManifests) {\n logger.detail(`updated ${path}`)\n }\n }\n // Go apps added before cross-compilation existed gain `build-all` and\n // `package-all`; nothing they already have is touched.\n const goApps = addGoPlatformTargets(workspaceRoot)\n if (goApps.length > 0) {\n logger.step('Adding the six-platform build to Go apps (build-all, package-all)')\n for (const path of goApps) {\n logger.detail(`updated ${path}`)\n }\n }\n // The packaging script is mnci's, like the overlay files: a fix to it reaches\n // every workspace with a VS Code extension, and only those.\n const pinnedExtensions = pinVscodeExtensionProjectNames(workspaceRoot)\n if (pinnedExtensions.length > 0) {\n logger.step('Pinning the Nx project name of VS Code extensions (nx.name)')\n for (const path of pinnedExtensions) {\n logger.detail(`updated ${path}`)\n }\n }\n if (refreshVscodeExtensionScript(workspaceRoot)) {\n logger.step('Updating the VS Code extension packaging script')\n logger.detail(`updated ${VSCODE_EXTENSION_SCRIPT_PATH}`)\n }\n const upgradedDtsPlugins = upgradeDeclarationSpecifierPlugins(workspaceRoot)\n if (upgradedDtsPlugins.length > 0) {\n logger.step('Upgrading declaration specifiers to resolve under nodenext')\n for (const path of upgradedDtsPlugins) {\n logger.detail(`updated ${path}`)\n }\n }\n\n // `new` and every `add` end this way; `upgrade` did not, so it left the\n // `nx.json` it had just rewritten mis-formatted — which now fails the\n // workspace's own `format:check` CI gate. Non-fatal for the same reason it is\n // there: the overlay is already applied by this point.\n //\n // Announced because it is by far the slowest part of an upgrade — `eslint --fix`\n // over the whole workspace — and it used to run in silence after the overlay\n // message, so a large workspace looked hung for a minute or more. `new` already\n // logged this; `upgrade` did not.\n logger.step(\n 'Formatting the workspace (eslint --fix) — the slowest step, minutes on a large workspace',\n )\n runFormatter(workspaceRoot)\n\n logger.success('Done. Review the changes with `git diff` before committing.')\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport spawn from 'cross-spawn'\nimport { logger } from '../terminal'\n\n/**\n * The published name of this CLI, queried against the npm registry.\n *\n * @remarks\n * Hardcoded rather than read back from the packaged `package.json`: the\n * update notice should point at the package users actually install, which\n * stays true even when the CLI is executed from a renamed local checkout.\n */\nconst PACKAGE_NAME = '@mnci/cli'\n\n/** How long to wait for the registry before giving up, in milliseconds. */\nconst REGISTRY_TIMEOUT_MS = 2000\n\n/**\n * Starts a background check for a newer published version of the CLI.\n *\n * @remarks\n * Deliberately synchronous and `void`-returning: the work is handed to\n * `setImmediate` and never awaited, so returning a promise would leave a\n * floating one at the call site — and a rejected floating promise takes the\n * whole process down with an unhandled rejection. Nothing here can reject,\n * and nothing here throws: a failed update check must never affect the\n * command the user actually ran.\n *\n * Two honest caveats about \"background\". The registry call is a *synchronous*\n * spawn, so once the deferred callback runs it blocks the event loop for as\n * long as the query takes (bounded by {@link REGISTRY_TIMEOUT_MS}); running\n * it in `setImmediate` keeps it off the startup path, not off the thread.\n * And there is no result cache — every eligible invocation queries afresh.\n *\n * Both costs are confined to interactive use: the check is skipped outright\n * unless stdout is a TTY, so CI runs and piped/scripted invocations pay\n * nothing and never have a notice injected into their output.\n *\n * @param currentVersion - The running CLI version (from package.json).\n * @returns Nothing — a newer version is reported on stdout when found.\n * @throws Never - every failure path is swallowed.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function checkForUpdate (currentVersion: string): void {\n try {\n if (!process.stdout.isTTY) {\n return\n }\n setImmediate(() => {\n try {\n reportIfOutdated(currentVersion)\n } catch {\n // A convenience feature must never break the invoked command.\n }\n })\n } catch {\n // Scheduling itself failing is pathological; ignore it like the rest.\n }\n}\n\n/**\n * Queries the registry and prints an update notice when one is warranted.\n *\n * @remarks\n * Uses `cross-spawn` with an argv array rather than `execSync` with a shell\n * string: it keeps the workspace's no-shell-injection invariant, and avoids\n * POSIX-only redirection that would fail outright on a Windows agent.\n *\n * @param currentVersion - The running CLI version.\n * @returns Nothing.\n * @throws Never - a failed or empty query is treated as \"no update known\".\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reportIfOutdated (currentVersion: string): void {\n const result = spawn.sync('npm', ['view', PACKAGE_NAME, 'version'], {\n encoding: 'utf8',\n timeout: REGISTRY_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n if (result.error || result.status !== 0) {\n return\n }\n\n const latestVersion = (result.stdout ?? '').trim()\n if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {\n return\n }\n\n logger.info('')\n logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`)\n logger.info(` Current version: ${currentVersion}`)\n logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`)\n logger.info('')\n}\n\n/**\n * Whether `candidate` is a strictly newer release than `current`.\n *\n * @remarks\n * Compares the numeric major/minor/patch triple left to right, ignoring any\n * prerelease suffix. That is intentionally narrower than full semver: the\n * only decision it drives is whether to print a notice, so treating\n * `1.2.3-beta.1` as `1.2.3` merely suppresses a notice rather than causing a\n * wrong action. A non-numeric segment makes the comparison bail out as \"not\n * newer\", so a malformed version is silent rather than noisy.\n *\n * @param candidate - The version offered by the registry.\n * @param current - The running version.\n * @returns `true` when `candidate` is strictly newer than `current`.\n * @throws Never - unparseable input yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isNewerVersion (candidate: string, current: string): boolean {\n const candidateParts = releaseTriple(candidate)\n const currentParts = releaseTriple(current)\n if (!candidateParts || !currentParts) {\n return false\n }\n\n for (const [index, candidatePart] of candidateParts.entries()) {\n if (candidatePart !== currentParts[index]) {\n return candidatePart > currentParts[index]\n }\n }\n\n return false\n}\n\n/**\n * Parses a version string into its numeric major/minor/patch triple.\n *\n * @param version - The version to parse, with any prerelease suffix ignored.\n * @returns The three numbers, or `undefined` when any segment is not numeric.\n * @throws Never - malformed input yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction releaseTriple (version: string): [number, number, number] | undefined {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version.trim())\n\n return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined\n}\n\n/**\n * Reads the CLI's own version from its packaged `package.json`.\n *\n * @remarks\n * Resolved relative to the compiled entry point (`__dirname`, i.e. `dist/`),\n * so the manifest sits one level up. Falls back to `0.0.0` rather than\n * throwing: an unreadable manifest should not stop the CLI from running.\n *\n * @param packageDirectory - Directory holding the compiled entry point.\n * @returns The version string, or `0.0.0` when it cannot be read.\n * @throws Never - any read or parse failure yields the fallback.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function readCliVersion (packageDirectory: string): string {\n try {\n const manifestPath = join(packageDirectory, '..', 'package.json')\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { version?: string }\n\n return manifest.version ?? '0.0.0'\n } catch {\n return '0.0.0'\n }\n}\n"],"mappings":"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","names":["import_node_path","import_prompts","value","spawn","resolve","import_node_fs","import_node_path","import_node_fs","import_node_path","pascalScope","pythonPublishUrl","nugetFeedUrl","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","module","import_node_path","ensurePlugin","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","bare","operator","import_node_path","import_prompts","import_node_fs","import_node_path","toPosix","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_path","import_prompts","import_node_fs","import_node_path","resolveRegistry","import_node_fs","import_node_path","import_cross_spawn","currentVersion","spawn"]}
1
+ {"version":3,"sources":["../src/main.ts","../src/project-scaffolding/add-project.use-case.ts","../src/nx-workspace/nx.client.ts","../src/terminal/logger.client.ts","../src/terminal/prompts.client.ts","../src/file-system/file-system.client.ts","../src/project-name/project-name.validator.ts","../src/project-scaffolding/csharp.use-case.ts","../src/workspace-overlay/overlay.use-case.ts","../src/project-scaffolding/post-generation.use-case.ts","../src/project-scaffolding/flutter.use-case.ts","../src/project-scaffolding/go.use-case.ts","../src/project-scaffolding/go-release.use-case.ts","../src/project-scaffolding/node.use-case.ts","../src/project-scaffolding/npm-lib.use-case.ts","../src/rollup-library/repair-publishable-manifest.use-case.ts","../src/rollup-library/repair-rollup-config.use-case.ts","../src/rollup-library/rollup-config.algorithm.ts","../src/rollup-library/rollup-config.repository.ts","../src/project-scaffolding/python.use-case.ts","../src/project-scaffolding/react-app.use-case.ts","../src/project-scaffolding/react-lib.use-case.ts","../src/project-scaffolding/vscode-extension.use-case.ts","../src/dependency-management/sync-dependencies.use-case.ts","../src/dependency-management/manifest.repository.ts","../src/dependency-management/semver.algorithm.ts","../src/dependency-management/update-dependencies.use-case.ts","../src/dependency-management/registry.client.ts","../src/workspace-diagnostics/check-invariants.use-case.ts","../src/workspace-creation/adopt-directory.use-case.ts","../src/workspace-creation/create-workspace.use-case.ts","../src/workspace-creation/interactive-wizard.use-case.ts","../src/workspace-upgrade/upgrade-workspace.use-case.ts","../src/cli-version/check-for-update.use-case.ts"],"sourcesContent":["import { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './project-scaffolding'\nimport { runDoctor } from './workspace-diagnostics'\nimport { runInteractive } from './workspace-creation'\nimport { runNew, type NewOptions } from './workspace-creation'\nimport { runSync, type SyncOptions } from './dependency-management'\nimport { runUp, type UpOptions } from './dependency-management'\nimport { runUpgrade, type UpgradeOptions } from './workspace-upgrade'\nimport { logger } from './terminal'\nimport { checkForUpdate, readCliVersion } from './cli-version'\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Six commands: `new`, `add`, `upgrade` (re-applies the overlay to an existing\n * workspace), `doctor` (read-only invariant check), `sync` (converge dependency\n * ranges and TypeScript project references) and `up` (report and apply\n * available upgrades). Everything else a generated repo needs day-to-day\n * (build/test/lint/release) is plain Nx, so the CLI deliberately has no wrapper\n * commands for those.\n *\n * `sync` and `up` exist because Nx covers neither: `nx sync` runs the\n * workspace's sync generators, and the only one registered is\n * `@nx/js:typescript-sync` — TypeScript project references, no opinion at all\n * about dependency versions. And npm has no `catalog:`, so one version per\n * workspace is a convention nothing enforces.\n *\n * @param cliVersion - The current CLI version (from package.json).\n * @returns The configured commander program.\n * @throws Never - wiring only; execution errors surface when commands run.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function buildProgram (cliVersion: string): Command {\n const program = new Command()\n\n program\n .name('mnci')\n .description('MoNecromanCI — a thin CLI over official Nx plugins')\n .version(cliVersion, '-v, --version', 'display the version')\n\n program\n .command('new')\n .argument('[name]', 'workspace name')\n .description('Create a new monorepo (Nx TS preset + release/pipeline overlay)')\n .option('-y, --yes', 'accept defaults for anything not passed as a flag')\n .option('--scope <scope>', 'npm scope for publishable packages (e.g. @demo)')\n .option('--registry <kind>', 'azure-artifacts | npm')\n .option('--organization <name>', 'Azure DevOps organization')\n .option('--project <name>', 'Azure DevOps project')\n .option('--artifacts-feed <name>', 'Azure Artifacts feed')\n .option(\n '--agent <pool>',\n 'CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name',\n )\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option(\n '--npm-auth <mode>',\n 'how npm authenticates to an Azure Artifacts feed: pat (default) | build-identity (npmAuthenticate@0, no PAT; --ci azure only)',\n )\n .option('--ci <provider>', 'CI provider: azure | github | both')\n .option('--test-runner <runner>', 'unit-test runner: jest | vitest')\n .option('--nx-cloud', 'connect the workspace to Nx Cloud (remote caching + CI insights)')\n .option(\n '--into <dir>',\n 'bootstrap into an existing directory (a fresh clone holding only .git); the directory name is the workspace name unless one is given',\n )\n .action(async (name: string | undefined, options: NewOptions) => {\n await runNew(name, options)\n })\n\n program\n .command('upgrade')\n .description(\n 'Re-apply the latest MoNecromanCI overlay to this workspace (release config, pipeline, npmrc, commitlint, husky hook, curated scripts)',\n )\n .option('--scope <scope>', 'npm scope for publishable packages (overrides the persisted value)')\n .option('--registry <kind>', 'azure-artifacts | npm (overrides the persisted value)')\n .option('--organization <name>', 'Azure DevOps organization')\n .option('--project <name>', 'Azure DevOps project')\n .option('--artifacts-feed <name>', 'Azure Artifacts feed')\n .option('--agent <pool>', 'CI build agent (overrides the persisted value)')\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option(\n '--npm-auth <mode>',\n 'how npm authenticates to an Azure Artifacts feed: pat (default) | build-identity (npmAuthenticate@0, no PAT; --ci azure only)',\n )\n .option('--ci <provider>', 'CI provider: azure | github | both (overrides the persisted value)')\n .option(\n '--test-runner <runner>',\n 'unit-test runner: jest | vitest (overrides the persisted value)',\n )\n .action((options: UpgradeOptions) => {\n runUpgrade(process.cwd(), options)\n })\n\n program\n .command('doctor')\n .description(\n 'Check this workspace against the invariants mnci maintains (one ESLint config, no stray .prettierrc, the eslint plugin registered, the resolved eslint major, .npmrc vs the recorded registry, versionActions overrides, nx sync) — read-only; exits non-zero if anything failed',\n )\n .action(() => {\n runDoctor(process.cwd())\n })\n\n program\n .command('sync')\n .description(\n \"Make every project agree: converge external dependency ranges declared at more than one version, then run 'nx sync' for TypeScript project references. Go is a no-op — one root go.mod means one version of every module\",\n )\n .option('--check', 'report drift and exit non-zero without writing anything')\n .option('--ecosystem <name>', 'restrict to one ecosystem: npm | pip | pub | go')\n .action((options: SyncOptions) => {\n runSync(process.cwd(), options)\n })\n\n program\n .command('up')\n .description(\n 'Show every dependency with a newer published release — grouped patch/minor/major/non-semver, with the projects declaring each one — and interactively update the ones you pick',\n )\n .option('--check', 'report only; never prompt and never write (the default when piped)')\n .option('-y, --yes', 'select every available update without prompting')\n .option('--ecosystem <name>', 'restrict to one ecosystem: npm | pip | pub | go')\n .option('--no-install', 'update the manifests but skip the reinstall step')\n .action(async (options: UpOptions) => {\n await runUp(process.cwd(), options)\n })\n\n program\n .command('add')\n // choices() rejects an unrecognized kind with a clean commander usage\n // error (and lists the valid ones in --help) before runAdd ever runs —\n // instead of a typo silently doing nothing but reporting \"success\".\n .addArgument(new Argument('[kind]', 'project kind').choices(PROJECT_KINDS))\n .argument('[name]', 'project name')\n .description('Add a project by delegating to the matching Nx plugin generator')\n .option('--scope <scope>', 'npm scope for a publishable lib (defaults to @<workspace name>)')\n .option(\n '--framework <framework>',\n 'node-app only: express | fastify | koa | nest | none (default: none)',\n )\n .option(\n '--lib <name>',\n 'python-vendor only: the internal Python library (libs/<name>) to vendor into <name>',\n )\n .option(\n '--publisher <id>',\n 'vscode-extension only: the Marketplace publisher id (defaults to the workspace scope without @)',\n )\n .option(\n '--sidecar <go-app>',\n 'vscode-extension only: a go-app whose six-platform build ships in the extension, one .vsix per platform',\n )\n .option(\n '--release',\n 'go-app only: release the app. Versioned from its git tag, its six-platform zips attached to its GitHub Release',\n )\n .action(\n async (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions) => {\n await runAdd(kind, name, options)\n },\n )\n\n // Bare `mnci` (no subcommand) launches the guided wizard; commander runs\n // this default action only when no subcommand is given (-v/--help still win).\n program.action(async () => {\n await runInteractive()\n })\n\n return program\n}\n\n/**\n * CLI entry point: parse arguments and surface failures as exit code 1.\n *\n * @remarks\n * Exported so tests can drive the program without spawning a process.\n * Runs a non-blocking version check in the background (fire-and-forget), and\n * prints a `mnci vX.Y.Z` banner for interactive use — gated on stdout being a\n * TTY (like {@link checkForUpdate}) so CI/piped invocations see nothing extra,\n * and skipped when `-v`/`--version` was passed explicitly since commander\n * already prints the bare version for that case; a banner above it would just\n * be a redundant duplicate.\n *\n * @param None - this function takes no parameters.\n * @returns A promise that resolves when the invoked command completes.\n * @throws Never - failures are logged and turned into a non-zero exit code.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function main (): Promise<void> {\n try {\n const cliVersion = readCliVersion(__dirname)\n const program = buildProgram(cliVersion)\n\n // Check for updates in the background (non-blocking).\n checkForUpdate(cliVersion)\n\n const arguments_ = new Set(process.argv.slice(2))\n const requestedVersionFlag = arguments_.has('-v') || arguments_.has('--version')\n if (!requestedVersionFlag && process.stdout.isTTY) {\n logger.info(`mnci v${cliVersion}`)\n }\n\n await program.parseAsync(process.argv)\n } catch (error) {\n logger.error(error instanceof Error ? error.message : String(error))\n process.exitCode = 1\n }\n}\n\n// Only self-execute when run as a binary, not when imported by tests.\n//\n// `void` rather than `await`/`.catch()`: `main` cannot reject — it wraps its whole\n// body in a try/catch that logs and sets a non-zero exit code — so there is no\n// rejection to handle, and there is nothing after this to sequence. The operator\n// states that deliberately instead of leaving a bare floating promise that reads\n// like an oversight.\nif (require.main === module) {\n void main()\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { runFormatter, runNx } from '../nx-workspace'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\nimport { assertValidProjectName } from '../project-name'\nimport { addCsharpApp, addCsharpFunctionApp, addCsharpInternalLib, addCsharpLib } from './csharp.use-case'\nimport { addFlutterApp, addFlutterInternalLib, addFlutterLib } from './flutter.use-case'\nimport { addGoApp, addGoFunctionApp, addGoInternalLib, addGoLib } from './go.use-case'\nimport { addNodeApp, addNodeFunctionApp } from './node.use-case'\nimport { addNpmLib } from './npm-lib.use-case'\nimport {\n addPythonApp,\n addPythonFunctionApp,\n addPythonInternalLib,\n addPythonLib,\n addPythonVendor,\n} from './python.use-case'\nimport { addReactApp } from './react-app.use-case'\nimport { addReactInternalLib, addReactLib } from './react-lib.use-case'\nimport { addVscodeExtension } from './vscode-extension.use-case'\nimport { syncProjectReferences } from '../dependency-management'\nimport {\n ensureNxPeerOverrides,\n markPrivate,\n renameScaffoldPlaceholder,\n registerProjectCommands,\n relocateRootRuntimeDependencies,\n removeGeneratedEslintConfig,\n rootRuntimeDependencies,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\nexport type { AddOptions } from './post-generation.use-case'\n\n/**\n * The project kinds this CLI can add — deliberately just twenty-three.\n *\n * @remarks\n * Each maps to an official (or established first-party) Nx plugin generator;\n * this CLI itself writes no project files (bar thin overlays). Layout convention\n * drives release scoping: `apps/` (never released, except a `vscode-extension`,\n * matched by its tag), `packages/` (publishable\n * npm, released by `nx release`), `libs/` (internal, never released),\n * `python-packages/` (publishable Python, published by `twine`).\n *\n * The TS/JS kinds use the official `@nx/*` generators only — `node-app` and\n * `node-function-app` are both the plain `@nx/node:application` (no\n * third-party Azure Functions plugin; `node-function-app` is that generator\n * plus a hand-written Azure Functions v4 file overlay). The Python kinds use\n * **`@mnci/nx-python-pip`** (`libs/nx-python-pip` in this same monorepo) — a\n * real Nx plugin this project built and maintains, since no maintained\n * Nx-23-compatible plugin supports pip (every one found ships uv/Poetry\n * only). Its generators write `pyproject.toml` + `project.json` + a sample\n * module/tests around **pip + Ruff + pytest + the standard PyPA\n * `build`/`twine`** — the industry-standard, uv-free Python toolchain — and\n * follow the identical app/function-app split. Every kind builds to its own\n * Nx-default output location — no post-generation build-output redirection.\n * Each kind's generation logic lives in its own module under `add/` — see\n * `add/reactApp.ts`, `add/reactLib.ts`, `add/node.ts`, `add/npmLib.ts` and\n * `add/python.ts` (internal-lib is small enough to stay inline below).\n *\n * The React family covers all three shapes: `react-app` (Vite SPA),\n * `react-lib` (publishable component library) and `react-internal-lib`\n * (private). The two library kinds were missing for a long time, which meant a\n * shared component library could not be built at all — `npm-lib` and\n * `internal-lib` are both `@nx/js:lib`, with no JSX support.\n *\n * The Go kinds use **`@nx-go/nx-go`** — an established third-party plugin,\n * validated empirically against a real Nx 23.1.0 workspace (it declares\n * `@nx/devkit \">= 20 < 23\"` as a plain dependency, so npm nests its own\n * devkit copy and everything still works). Go follows the same\n * root-manifest model as TS and Python: `add/go.ts` bootstraps a **single\n * root `go.mod`** on the first Go add (via the plugin's `init` +\n * `convert-to-one-mod`), so every Go project shares one module and a library\n * is imported as `<module>/libs/<name>` — no per-project manifests, no\n * `replace` directives. Because that single-module layout has no per-project\n * `go.mod`, the plugin's inference produces no targets, so mnci writes\n * build/test/lint explicitly (as it does for most kinds anyway). Lint is\n * `golangci-lint` — the executor's own default is plain `go fmt`, which only\n * reformats. Go needs no publish-time dependency injection at all: `go build`\n * links statically.\n *\n * The Flutter kinds use **`@mnci/nx-flutter`** (`packages/nx-flutter` in this\n * same monorepo) — the second real Nx plugin this project builds and\n * maintains, for the same reason as the Python one: no maintained,\n * Nx-23-compatible Flutter plugin exists (`@nxrocks/nx-flutter` cannot even\n * load on Nx 23 — it imports `@nx/workspace/src/utilities/fileutils`, removed\n * in 23). Its generators delegate scaffolding to the official Flutter CLI\n * (`flutter create`), so no template is hand-maintained against SDK releases.\n *\n * Flutter follows the same root-manifest model as the rest: a **Dart pub\n * workspace**, with one root `pubspec.yaml` listing every project and each\n * project carrying `resolution: workspace`, so a single `flutter pub get` at\n * the root resolves the whole graph into one `pubspec.lock`. The payoff is\n * that a project depending on an internal lib declares a **plain version\n * constraint with no `path:`** — pub resolves it locally because it is a\n * workspace member. That is also why Flutter needs no vendoring step (unlike\n * `python-vendor` below): there is nothing to weave in at build time. Apps\n * build for **web** only, which keeps the Android SDK off every build agent.\n *\n * `vscode-extension` is `@nx/node:application` turned into a Marketplace\n * extension: bundled with `vscode` external, packaged by `vsce` into one `.vsix`\n * (or one per platform with a `--sidecar` Go app's binaries in it), published by\n * `nx release` when `VSCE_PAT` is set. It lives in `apps/` and is released anyway,\n * through the `type:vscode-extension` tag in `release.projects`.\n *\n * `python-vendor` is the one kind that generates nothing: plain pip has no\n * bundled-local-dependency feature, so wiring an internal Python library\n * into a consumer's built wheel is a hand-edit of the consumer's\n * `pyproject.toml` (see `@mnci/nx-python-pip`'s README) — this kind\n * automates exactly that edit, idempotently, instead of delegating to a\n * generator. `name` is the consumer; the library is `--lib <name>`.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type ProjectKind =\n | 'react-app' |\n 'react-lib' |\n 'react-internal-lib' |\n 'node-app' |\n 'node-function-app' |\n 'npm-lib' |\n 'internal-lib' |\n 'python-app' |\n 'python-function-app' |\n 'python-lib' |\n 'python-internal-lib' |\n 'python-vendor' |\n 'go-app' |\n 'go-function-app' |\n 'go-lib' |\n 'go-internal-lib' |\n 'flutter-app' |\n 'flutter-lib' |\n 'flutter-internal-lib' |\n 'csharp-app' |\n 'csharp-function-app' |\n 'csharp-lib' |\n 'csharp-internal-lib' |\n 'vscode-extension'\n\n/**\n * Every kind {@link runAdd} accepts, in menu order.\n *\n * @remarks\n * Also drives the interactive kind picker shown when `add` is run bare. The\n * React family first, then the rest of the TS/JS kinds, then Python, Go,\n * Flutter and C#.\n */\nexport const PROJECT_KINDS: ProjectKind[] = [\n 'react-app',\n 'react-lib',\n 'react-internal-lib',\n 'node-app',\n 'node-function-app',\n 'npm-lib',\n 'internal-lib',\n 'python-app',\n 'python-function-app',\n 'python-lib',\n 'python-internal-lib',\n 'python-vendor',\n 'go-app',\n 'go-function-app',\n 'go-lib',\n 'go-internal-lib',\n 'flutter-app',\n 'flutter-lib',\n 'flutter-internal-lib',\n 'csharp-app',\n 'csharp-function-app',\n 'csharp-lib',\n 'csharp-internal-lib',\n 'vscode-extension',\n]\n\n/**\n * Adds a project to the workspace by delegating to the matching Nx generator.\n *\n * @remarks\n * A thin dispatcher — the actual generation logic for each kind lives in its\n * own module under `add/` (imported above), so this function only resolves\n * the shared inputs (kind, name, the workspace's stack) and routes to the\n * right one. Pure delegation throughout — no post-generation file rewriting\n * beyond each kind's own thin overlay. Known gap: a *publishable* lib\n * importing a *private internal* lib cannot be published as-is; internal\n * libs are for apps and other internal libs.\n *\n * @param kind - The project kind, prompted for when omitted.\n * @param name - The project name, prompted for when omitted.\n * @param options - The CLI flags.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when run outside a workspace root or a generator fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runAdd (\n kind: ProjectKind | undefined,\n name: string | undefined,\n options: AddOptions,\n): Promise<void> {\n const workspaceRoot = process.cwd()\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error('No nx.json found here. Run `add` from the workspace root.')\n }\n\n // The stack chosen at `mnci new` lives in nx.json; every generator (and the\n // hand-built function app) is wired to match it.\n const stack = readWorkspaceStack(workspaceRoot)\n\n // When the kind was not passed, the user is on the bare/interactive path, so\n // fill in every configuration — including the npm-lib scope (below) that the\n // flag path defaults silently.\n const kindProvided = kind !== undefined\n const resolvedKind =\n kind ??\n (await select<ProjectKind>({\n message: 'What kind of project?',\n choices: PROJECT_KINDS.map(value => ({ name: value, value })),\n }))\n if (resolvedKind !== 'go-app' && options.release === true) {\n // Not silently ignored like the other kind-specific options: a user who asked\n // for a release would otherwise believe the project is released.\n throw new Error(`--release applies to go-app only, not ${resolvedKind}.`)\n }\n const resolvedName = name ?? (await promptText('Project name'))\n // Fails fast, before any install or generator call: the name becomes a\n // directory, an argv token and (for Python kinds) a module identifier — and\n // an explicitly empty `name` argument bypasses promptText's own non-empty\n // check, which only fires on the prompted path.\n assertValidProjectName(resolvedName, 'Project name')\n\n // Snapshotted before any generator runs, so whatever appears in the root's\n // runtime dependencies afterwards can be attributed to this project and moved\n // into its own manifest. See relocateRootRuntimeDependencies for why the root\n // is the wrong place for one.\n const rootDependenciesBefore = rootRuntimeDependencies(workspaceRoot)\n\n // Before the switch, because every generator below installs as it scaffolds\n // and the override has to already be on disk when it does. See\n // ensureNxPeerOverrides.\n ensureNxPeerOverrides(workspaceRoot)\n\n switch (resolvedKind) {\n case 'react-app': {\n addReactApp(workspaceRoot, resolvedName, stack)\n break\n }\n case 'react-lib': {\n await addReactLib(workspaceRoot, resolvedName, options, kindProvided, stack)\n break\n }\n case 'react-internal-lib': {\n addReactInternalLib(workspaceRoot, resolvedName, stack)\n break\n }\n case 'node-app': {\n addNodeApp(workspaceRoot, resolvedName, stack, options.framework)\n break\n }\n case 'node-function-app': {\n addNodeFunctionApp(workspaceRoot, resolvedName, stack)\n break\n }\n case 'npm-lib': {\n await addNpmLib(workspaceRoot, resolvedName, options, kindProvided, stack)\n break\n }\n case 'internal-lib': {\n // tsc (not none): the default @nx/enforce-module-boundaries rule forbids\n // buildable libraries (every npm-lib) from importing non-buildable ones,\n // so internal libs must be buildable — just never published (private).\n runNx(\n [\n 'g',\n '@nx/js:lib',\n `libs/${resolvedName}`,\n '--bundler=tsc',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n markPrivate(join(workspaceRoot, 'libs', resolvedName, 'package.json'))\n // Same @nx/js:lib placeholder, same role-suffix rule — see\n // renameScaffoldPlaceholder.\n renameScaffoldPlaceholder(join(workspaceRoot, 'libs', resolvedName), resolvedName)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${resolvedName}`)\n registerProjectCommands(workspaceRoot, resolvedName, { build: true })\n break\n }\n case 'python-app': {\n addPythonApp(workspaceRoot, resolvedName)\n break\n }\n case 'python-function-app': {\n addPythonFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'python-lib': {\n addPythonLib(workspaceRoot, resolvedName)\n break\n }\n case 'python-internal-lib': {\n addPythonInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'go-app': {\n addGoApp(workspaceRoot, resolvedName, { release: options.release })\n break\n }\n case 'go-function-app': {\n addGoFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'go-lib': {\n addGoLib(workspaceRoot, resolvedName)\n break\n }\n case 'go-internal-lib': {\n addGoInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-app': {\n addFlutterApp(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-lib': {\n addFlutterLib(workspaceRoot, resolvedName)\n break\n }\n case 'flutter-internal-lib': {\n addFlutterInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-app': {\n addCsharpApp(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-function-app': {\n addCsharpFunctionApp(workspaceRoot, resolvedName)\n break\n }\n case 'csharp-lib': {\n await addCsharpLib(workspaceRoot, resolvedName, options, kindProvided)\n break\n }\n case 'csharp-internal-lib': {\n addCsharpInternalLib(workspaceRoot, resolvedName)\n break\n }\n case 'vscode-extension': {\n addVscodeExtension(workspaceRoot, resolvedName, stack, {\n publisher: options.publisher,\n sidecar: options.sidecar,\n })\n break\n }\n case 'python-vendor': {\n await addPythonVendor(workspaceRoot, resolvedName, options, kindProvided)\n // `resolvedName` is the consumer, not something newly created — the\n // generic \"Added ... 'name'\" success message below reads wrong for\n // this kind, so it returns early with its own message instead.\n syncProjectReferences(workspaceRoot)\n runFormatter(workspaceRoot)\n\n return\n }\n default: {\n // Unreachable while every ProjectKind has a case above: `exhaustive`\n // being `never` makes adding a new kind without a matching case a\n // *compile-time* error, not just a runtime gap. The CLI itself already\n // rejects an unrecognized value before this ever runs (cli.ts's\n // Argument#choices()); this is the last line of defense for any other\n // caller of runAdd (e.g. a future programmatic use).\n const exhaustive: never = resolvedKind\n throw new Error(\n `Unknown project kind '${exhaustive as string}'. Expected one of: ${PROJECT_KINDS.join(', ')}.`,\n )\n }\n }\n\n relocateRootRuntimeDependencies(workspaceRoot, resolvedName, rootDependenciesBefore)\n\n syncProjectReferences(workspaceRoot)\n\n // Nx's generators write in their own style (semicolons, double quotes), and\n // `nx sync` plus the root-manifest/`.code-workspace` edits above touch files\n // outside the new project — so this formats the workspace, not just\n // `<projectRoot>`. Keeps `npm run format:check` green after every add.\n runFormatter(workspaceRoot)\n\n logger.success(`Added ${resolvedKind} '${resolvedName}'.`)\n}\n\n/**\n * The workspace stack, read back from the `nx.json` `mnci.stack` block `new` wrote.\n *\n * @remarks\n * How a one-time `mnci new` choice reaches `add`: `mnci.stack` (written by\n * `mnciConfig` in `overlay.ts`) is the single source of truth — a dedicated\n * block, not inferred from one of Nx's own (three, always-identical)\n * generator-default blocks, so there's no \"stay in lockstep\" invariant to\n * silently drift. `add` passes the result back to the `@nx/*`\n * generators explicitly (predictable regardless of Nx's own default\n * resolution). The return shape is generator-facing: `linter` is `eslint`, or `none` when the\n * workspace chose oxlint (oxlint is not an Nx linter). Missing/blank (e.g. a\n * workspace generated before this field existed) falls back to the default\n * opinion (eslint + jest).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The linter and test runner to apply.\n * @throws Propagates any `fs`/JSON error reading `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction readWorkspaceStack (workspaceRoot: string): WorkspaceStack {\n const nxJson = readJson<{ mnci?: { stack?: { testRunner?: string; linter?: string } } }>(\n join(workspaceRoot, 'nx.json'),\n )\n const stack = nxJson.mnci?.stack\n\n return {\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest',\n }\n}\n","import spawn from 'cross-spawn'\nimport { logger } from '../terminal'\n\n/**\n * Runs a command synchronously, inheriting stdio so its output streams live.\n *\n * @remarks\n * Shells out for everything — `create-nx-workspace`, `nx add`, `nx g` — so\n * the CLI stays version-decoupled from whatever Nx is current, and the plugin\n * generators' own interactive output reaches the user unmodified.\n *\n * Uses `cross-spawn` (not `node:child_process`'s `spawnSync` with\n * `shell: true`) specifically so arguments are never interpreted by a shell:\n * `cross-spawn` is a drop-in `spawnSync` replacement that resolves Windows\n * `.cmd`/`.bat` shims itself, safely, from the `(command, args, options)`\n * array form — no string concatenation, so a value containing shell\n * metacharacters (semicolons, backticks, `$()`, …) is passed through as one\n * literal argv entry, never executed. Every argument we pass (workspace/\n * project name, scope, Azure coordinates, …) can originate from user input, so\n * this matters: the previous `[command, ...args].join(' ')` + `shell: true`\n * design let a crafted name run arbitrary shell commands.\n *\n * @param command - The executable to run (e.g. `npx`).\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns The child process exit status (`0` on success); `1` when the process\n * was terminated by a signal or never produced a status (e.g. spawn failure).\n * @throws Never - spawn failures surface through the returned status, not a throw.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runShell (command: string, arguments_: string[], cwd: string): number {\n const result = spawn.sync(command, arguments_, { stdio: 'inherit', cwd })\n\n return result.status ?? 1\n}\n\n/**\n * The captured outcome of a command run through {@link runCapture}.\n *\n * @remarks\n * `stdout` is always a string — never `null` — so callers can parse it without\n * a nullish guard at every site. A spawn that never started yields status `1`\n * and an empty string, which is indistinguishable from a command that failed\n * silently; that is deliberate, because every caller here treats both as \"no\n * answer available\" rather than branching on the difference.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface CaptureResult {\n /** The child process exit status; `1` when it never produced one. */\n status: number\n /** Everything the child wrote to stdout, decoded as UTF-8. */\n stdout: string\n}\n\n/**\n * How long a captured command may run before it is killed, in milliseconds.\n *\n * @remarks\n * Generous compared with the update checker's 2s registry budget, because the\n * commands routed through here are not all network calls: the Go and Dart\n * \"what is outdated\" queries each resolve a whole dependency graph, and a cold\n * `flutter pub` run on a large workspace genuinely takes tens of seconds. A\n * timeout tuned for a single registry lookup would abort those before they ever\n * answered.\n */\nconst CAPTURE_TIMEOUT_MS = 60_000\n\n/**\n * Runs a command and captures its stdout instead of streaming it.\n *\n * @remarks\n * The counterpart to {@link runShell}, which inherits stdio and therefore hands\n * the caller nothing but an exit code. Commands whose *output* is the answer —\n * `npm view`, `go list -m -u -json all`, `flutter pub outdated --json` — need\n * this one.\n *\n * Same `cross-spawn` argv-array contract as {@link runShell}, for the same\n * reason: no shell interprets the arguments, so a package name originating from\n * a manifest can never be executed. `execSync` with a shell string would\n * reintroduce exactly the injection hazard `runShell`'s docblock describes, and\n * its POSIX-only redirection would fail outright on a Windows agent.\n *\n * stderr is discarded (`'ignore'`) rather than piped: every caller parses\n * stdout, and npm in particular writes progress and audit noise to stderr that\n * would otherwise have to be filtered out at each site. A failing command is\n * detected by its status, not by what it printed.\n *\n * @param command - The executable to run (e.g. `npm`).\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns The exit status and the captured stdout.\n * @throws Never - a spawn failure or timeout surfaces as a non-zero status.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runCapture (command: string, arguments_: string[], cwd: string): CaptureResult {\n const result = spawn.sync(command, arguments_, {\n cwd,\n encoding: 'utf8',\n timeout: CAPTURE_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n // `result.error` covers the spawn that never started (an absent toolchain,\n // most often) — there `status` is null and stdout is null too.\n if (result.error) {\n return { status: 1, stdout: '' }\n }\n\n return { status: result.status ?? 1, stdout: result.stdout ?? '' }\n}\n\n/**\n * Runs a command asynchronously and captures its stdout.\n *\n * @remarks\n * The async twin of {@link runCapture}, and it exists for exactly one reason:\n * `mnci up` asks the registry about every declared package, and a synchronous\n * spawn per package makes that a serial wait of roughly half a second times the\n * dependency count — half a minute on a workspace of any size. Callers pool\n * these, so the same work overlaps.\n *\n * Same argv-array, no-shell contract as every other helper here. The promise\n * never rejects: a spawn error resolves as status `1` with empty output, so a\n * pool of these needs no per-item `catch` and one absent toolchain cannot take\n * the whole run down.\n *\n * @param command - The executable to run.\n * @param arguments_ - The arguments passed to the executable.\n * @param cwd - The working directory to run the command in.\n * @returns A promise of the exit status and captured stdout.\n * @throws Never - failures resolve as a non-zero status.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runCaptureAsync (\n command: string,\n arguments_: string[],\n cwd: string,\n): Promise<CaptureResult> {\n return await new Promise<CaptureResult>(resolve => {\n const child = spawn(command, arguments_, {\n cwd,\n timeout: CAPTURE_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n let stdout = ''\n child.stdout?.setEncoding('utf8')\n child.stdout?.on('data', (chunk: string) => {\n stdout += chunk\n })\n child.on('error', () => {\n resolve({ status: 1, stdout: '' })\n })\n child.on('close', code => {\n resolve({ status: code ?? 1, stdout })\n })\n })\n}\n\n/**\n * Runs an async job over every input, at most `limit` at a time.\n *\n * @remarks\n * A pool rather than `Promise.all` over the whole list: a workspace can declare\n * a hundred packages, and spawning a hundred `npm view` processes at once is\n * how a laptop runs out of file descriptors and a CI agent starts timing out.\n * Results come back in input order regardless of completion order, so a report\n * built from them is deterministic.\n *\n * @param items - The inputs to process.\n * @param limit - Maximum number of jobs in flight at once.\n * @param job - The work to run for one input.\n * @returns A promise of the results, in the same order as `items`.\n * @throws Propagates the first rejection from `job`.\n * @typeParam TIn - The input element type.\n * @typeParam TOut - The result element type.\n */\nexport async function pool<TIn, TOut> (\n items: readonly TIn[],\n limit: number,\n job: (item: TIn) => Promise<TOut>,\n): Promise<TOut[]> {\n const results: TOut[] = Array.from({ length: items.length })\n let next = 0\n\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next\n next += 1\n results[index] = await job(items[index])\n }\n }\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => await worker()))\n\n return results\n}\n\n/**\n * Runs `npx nx <args>` in the given workspace, failing loudly on error.\n *\n * @remarks\n * Every generator/plugin invocation funnels through here so tests can assert\n * the exact delegation and the error contract stays in one place.\n *\n * @param arguments_ - The Nx CLI arguments (e.g. `['g', '@nx/react:app', …]`).\n * @param cwd - The workspace root to run in.\n * @returns Nothing. Throws instead of returning a status.\n * @throws Error when the Nx process exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNx (arguments_: string[], cwd: string): void {\n const status = runShell('npx', ['nx', ...arguments_], cwd)\n if (status !== 0) {\n throw new Error(`nx ${arguments_.join(' ')} failed with exit code ${status}`)\n }\n}\n\n/**\n * Runs `npx <args>` in the given directory, failing loudly on error.\n *\n * @remarks\n * Used for the one non-Nx invocation: `create-nx-workspace` itself, which by\n * definition runs *outside* any workspace.\n *\n * @param arguments_ - The npx arguments (e.g. `['create-nx-workspace@latest', …]`).\n * @param cwd - The directory to run in.\n * @returns Nothing. Throws instead of returning a status.\n * @throws Error when the process exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNpx (arguments_: string[], cwd: string): void {\n const status = runShell('npx', arguments_, cwd)\n if (status !== 0) {\n throw new Error(`npx ${arguments_.join(' ')} failed with exit code ${status}`)\n }\n}\n\n/**\n * Runs the workspace's formatter over a freshly generated workspace or project.\n *\n * @remarks\n * Nx's own generators emit semicolons, double quotes and trailing commas —\n * the opposite of the JavaScript Standard Style mnci configures. Without this\n * pass a workspace fails its own `lint` the moment it is created, which is a\n * poor first impression and, worse, buries every real formatting change under a\n * wall of generator noise on the first commit. Running it here means what mnci\n * hands back is already normalised.\n *\n * **There is no formatter to choose any more, and that is what makes this\n * safe.** This function used to take the workspace's linter choice and pick a\n * binary from it, because getting that wrong was a real bug: running\n * `npx prettier --write .` in a workspace with no Prettier config does not\n * fail, it silently reformats everything against its own defaults — semicolons, double\n * quotes, trailing commas — over files mnci had just written correctly. Now\n * ESLint is the only tool, so the config that decides the style and the binary\n * that applies it cannot disagree: there is only one of each.\n *\n * The ignore list comes from `eslint.config.mjs` for the same reason, rather\n * than from a separate `.prettierignore` that a second tool had to be taught to\n * read.\n *\n * Deliberately non-fatal. The project has already been generated and wired by\n * the time this runs; aborting on a formatter hiccup would leave a usable\n * workspace behind an error message. A warning names the exact command to\n * re-run instead.\n *\n * @param cwd - The workspace root to run in.\n * @param target - What to format, relative to `cwd`. Defaults to the whole\n * workspace (`.`); `add` passes the new project's root to keep the pass\n * proportional to what actually changed.\n * @returns Nothing.\n * @throws Never - a non-zero exit is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runFormatter (cwd: string, target = '.'): void {\n // ESLint IS the formatter now, so formatting a generated project is\n // `eslint --fix` rather than a separate binary. Nx's generators emit\n // semicolons and double quotes, so without this pass a fresh workspace fails\n // its own `lint` before the user has written a line.\n //\n // No `--cache` here: this runs once on freshly written files, where a cache\n // can only cost a write. The lint TARGETS carry it, which is where repeat\n // runs actually happen.\n const status = runShell('npx', ['eslint', target, '--fix'], cwd)\n if (status !== 0) {\n logger.warn(\n `eslint could not format '${target}' (exit code ${status}). ` +\n \"The project was generated; run 'npm run format' to normalise it.\",\n )\n }\n}\n","/**\n * Minimal namespaced console logger used across the CLI.\n *\n * @remarks\n * Each method prefixes `message` with a small glyph to distinguish severity\n * when scanning terminal output.\n */\nexport const logger = {\n info (message: string): void {\n console.log(message)\n },\n step (message: string): void {\n console.log(`→ ${message}`)\n },\n /**\n * A sub-step under the most recent {@link logger.step}, indented to read as detail.\n *\n * `this: void` because this one is handed to `applyOverlay` as a progress callback,\n * detached from the object. It reads no instance state, and the annotation is what\n * says so — without it every such call is an unbound-method error.\n */\n detail (this: void, message: string): void {\n console.log(` · ${message}`)\n },\n success (message: string): void {\n console.log(`✓ ${message}`)\n },\n warn (message: string): void {\n console.warn(`! ${message}`)\n },\n error (message: string): void {\n console.error(`✗ ${message}`)\n },\n}\n","import { confirm, input, select } from '@inquirer/prompts'\nimport type { CiProvider, RegistryConfig, StackConfig } from '../workspace-overlay'\n\n/**\n * Prompts for a non-empty trimmed string with an optional default.\n *\n * @remarks\n * Wraps `@inquirer/prompts`'s `input`, enforcing a non-empty result.\n *\n * @param message - The prompt message to display.\n * @param fallback - Optional default value pre-filled in the prompt.\n * @returns The trimmed, non-empty string the user entered.\n * @throws Propagates any error `@inquirer/prompts`'s `input` raises (e.g. when\n * stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptText (message: string, fallback?: string): Promise<string> {\n const value = await input({\n message,\n default: fallback,\n validate: (value: string) => value.trim().length > 0 || 'A value is required',\n })\n\n return value.trim()\n}\n\n/**\n * Prompts for the publish registry (Azure Artifacts coordinates, or npm).\n *\n * @remarks\n * Azure Artifacts needs three coordinates (organization/project/feed) to build\n * the registry URL; public npm needs nothing further.\n *\n * @param fallbackOrganization - Default Azure DevOps organization, if any.\n * @returns The resolved registry configuration.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. when stdin is\n * not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptRegistry (fallbackOrganization?: string): Promise<RegistryConfig> {\n const kind = await select<RegistryConfig['kind']>({\n message: 'Package registry for publishable libraries',\n choices: [\n { name: 'Azure Artifacts', value: 'azure-artifacts' },\n { name: 'Public npm', value: 'npm' },\n ],\n })\n\n if (kind === 'npm') {\n return { kind }\n }\n\n return {\n kind,\n organization: await promptText('Azure DevOps organization', fallbackOrganization),\n project: await promptText('Azure DevOps project'),\n artifactsFeed: await promptText('Artifacts feed name'),\n }\n}\n\n/**\n * Prompts for which CI provider(s) to write a pipeline file for.\n *\n * @remarks\n * Azure Pipelines is listed first (and stays the `--yes` default) since it\n * is the long-standing default; GitHub Actions and \"both\" let a\n * GitHub-hosted workspace skip the unused Azure file, or carry both while\n * migrating between the two.\n *\n * @param None - this function takes no parameters.\n * @returns The chosen CI provider.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptCi (): Promise<CiProvider> {\n return await select<CiProvider>({\n message: 'CI provider',\n choices: [\n { name: 'Azure Pipelines', value: 'azure' },\n { name: 'GitHub Actions', value: 'github' },\n { name: 'Both', value: 'both' },\n ],\n })\n}\n\n/**\n * Prompts whether to connect the new workspace to Nx Cloud.\n *\n * @remarks\n * Defaults to `false` (declined) — an external service enrollment is not\n * something to opt a user into silently. Only reached on the interactive\n * path; `--yes`/an explicit `--nx-cloud` flag skip straight past this.\n *\n * @param None - this function takes no parameters.\n * @returns Whether to connect to Nx Cloud.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptNxCloud (): Promise<boolean> {\n return await confirm({\n message: 'Connect this workspace to Nx Cloud (remote caching + CI insights)?',\n default: false,\n })\n}\n\n/**\n * Prompts for the stack: unit-test runner and linter.\n *\n * @remarks\n * The two knobs asked up front at `mnci new`. TypeScript is fixed (the\n * `--preset=ts` premise, pinned to the TS 6 that Nx 23 supports).\n *\n * The linter choice carries the formatter with it, which is why it is one\n * question and not two: ESLint owns linting and formatting alike, and both\n * are the same seven JavaScript Standard Style options. Choosing is about speed\n * and toolchain, never about how the code ends up looking.\n *\n * The oxlint choice is deliberately labelled as a hybrid rather than as a\n * replacement, because that is what it is — see {@link StackConfig}. Presenting\n * it as \"oxlint instead of ESLint\" would be a false description of what the\n * workspace gets, and would make the ESLint config it still writes look like a\n * bug.\n *\n * @param None - this function takes no parameters.\n * @returns The resolved stack configuration.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function promptStack (): Promise<StackConfig> {\n const testRunner = await select<StackConfig['testRunner']>({\n message: 'Unit-test runner',\n choices: [\n { name: 'Jest', value: 'jest' },\n { name: 'Vitest', value: 'vitest' },\n ],\n })\n\n return { testRunner }\n}\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\n/**\n * Reads and parses a JSON file.\n *\n * @remarks\n * No safety net by design: a missing or malformed file is a real error the\n * caller should see, not silently default away.\n *\n * @param path - Absolute path to the JSON file.\n * @returns The parsed value, typed by the caller.\n * @throws Propagates any Node.js `fs` error (e.g. file not found) or JSON\n * parse error raised by the underlying read.\n * @typeParam T - The expected shape of the parsed JSON.\n */\nexport function readJson<T> (path: string): T {\n return JSON.parse(readFileSync(path, 'utf8')) as T\n}\n\n/**\n * Strips `//` and `/* *\\/` comments from a JSONC document, leaving every\n * string literal untouched.\n *\n * @remarks\n * A single-pass tokenizer rather than a regex, because a regex cannot tell a\n * `//` inside a string (a URL in a settings value, say) from a real comment\n * without tracking string state — and getting that wrong corrupts the value\n * instead of merely failing to strip a comment. String state itself has to\n * track escape sequences (`\\\"` does not end the string, `\\\\` does not escape\n * the character after it) or an escaped backslash immediately before a\n * closing quote is misread as escaping the quote.\n *\n * Comments are dropped rather than replaced with whitespace: the result is\n * only ever fed to `JSON.parse`, never written back verbatim, so preserving\n * line numbers or layout has no reader to matter to.\n *\n * @param text - The JSONC document's raw text.\n * @returns The same text with every comment removed.\n * @throws Never - performs a pure character scan.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction stripJsonComments (text: string): string {\n let result = ''\n let inString = false\n let inLineComment = false\n let inBlockComment = false\n\n for (let i = 0; i < text.length; i++) {\n const char = text[i]\n\n if (inLineComment) {\n if (char === '\\n') {\n inLineComment = false\n result += char\n }\n continue\n }\n\n const next = text[i + 1]\n\n if (inBlockComment) {\n if (char === '*' && next === '/') {\n inBlockComment = false\n i++\n }\n continue\n }\n if (inString) {\n result += char\n if (char === '\\\\') {\n result += next\n i++\n continue\n }\n if (char === '\"') {\n inString = false\n }\n continue\n }\n if (char === '\"') {\n inString = true\n result += char\n continue\n }\n if (char === '/' && next === '/') {\n inLineComment = true\n i++\n continue\n }\n if (char === '/' && next === '*') {\n inBlockComment = true\n i++\n continue\n }\n result += char\n }\n\n return result\n}\n\n/**\n * Reads a `.code-workspace` file, tolerating the JSONC dialect VS Code writes:\n * comments and trailing commas.\n *\n * @remarks\n * `.code-workspace` is VS Code's own JSONC dialect, not strict JSON — VS Code\n * itself writes trailing commas into it, and a hand-maintained file routinely\n * carries `//`/`/* *\\/` comments explaining a setting, since VS Code's own\n * editor accepts them there. `JSON.parse` rejects both outright, so reading\n * this file strictly means mnci breaks on exactly the workspace file a real\n * user has actually touched.\n *\n * Comments used to be left unstripped, on the reasoning that \"mnci never\n * writes one, and a file with hand-added comments predates this handling\n * regardless\" — true, and beside the point: predating this function's\n * handling does not stop a workspace from HAVING one. A single comment\n * anywhere in the file made `JSON.parse` throw, which every caller treats as\n * \"nothing to preserve\" and merges against `{}` — silently discarding\n * `folders`, `settings`, `extensions` and `launch` wholesale, and every task\n * `registerProjectCommands` had ever registered, the first time `mnci add` or\n * `mnci upgrade` touched a workspace whose `.code-workspace` had a comment in\n * it. Reproduced on a file with a single `// eslint settings` comment line.\n *\n * Comments are stripped before the trailing-comma pass, deliberately: a line\n * comment on its own line right before a closing brace leaves a trailing\n * comma behind once removed, which the second pass still needs to catch.\n *\n * Shared by the two layers that both have to read this file: `applyOverlay`\n * (which must preserve the `tasks` array when it rewrites the rest) and\n * `registerProjectCommands` (which appends to it). One implementation, because\n * two would drift on exactly the dialect quirk that motivated it.\n *\n * @param path - Absolute path to the `.code-workspace` file.\n * @returns The parsed value, or `undefined` when the file is missing or\n * unparseable — callers treat both as \"nothing to preserve\".\n * @throws Never - an unreadable or malformed file yields `undefined`.\n * @typeParam T - The expected shape of the parsed JSON.\n */\nexport function readCodeWorkspace<T> (path: string): T | undefined {\n try {\n const withoutComments = stripJsonComments(readFileSync(path, 'utf8'))\n\n return JSON.parse(withoutComments.replaceAll(/,(\\s*[}\\]])/g, '$1')) as T\n } catch {\n return undefined\n }\n}\n\n/**\n * Serialises a value as 2-space-indented JSON with a trailing newline.\n *\n * @remarks\n * Matches the formatting `create-nx-workspace` itself emits, so patched files\n * stay diff-friendly.\n *\n * @param value - The value to serialise.\n * @returns The JSON text.\n * @throws Propagates any `JSON.stringify` error (e.g. circular references).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function toJson (value: unknown): string {\n return `${JSON.stringify(value, undefined, 2)}\\n`\n}\n\n/**\n * Writes a file, creating any missing parent directories first.\n *\n * @remarks\n * Used for every overlay write, so callers never worry about directory\n * existence (e.g. `.husky/` on a fresh workspace).\n *\n * @param path - Absolute path to write.\n * @param content - The file content.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error (e.g. permission denied).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function writeFileEnsured (path: string, content: string): void {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n}\n\n/**\n * Marks a file as executable (0o755) — used for git hooks.\n *\n * @remarks\n * Git only runs hooks with the executable bit set; a plain write loses it.\n *\n * @param path - Absolute path of the file to mark.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error (e.g. file not found).\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markExecutable (path: string): void {\n chmodSync(path, 0o755)\n}\n\n/**\n * Whether a path exists on disk.\n *\n * @remarks\n * Thin wrapper kept for symmetry with the other helpers (and mockability).\n *\n * @param path - Absolute path to test.\n * @returns `true` when the path exists.\n * @throws Never - `existsSync` swallows errors.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function fileExists (path: string): boolean {\n return existsSync(path)\n}\n","/**\n * Validates a workspace or project name before it's used anywhere.\n *\n * @remarks\n * A name becomes, without any transformation, a filesystem path segment\n * (`apps/<name>`), an argv token passed straight to Nx generators, and — for\n * Python and Dart kinds, once hyphens and dots become underscores — a module or\n * package identifier. The charset is what keeps it safe in all of those roles at\n * once: lowercase letters, digits, hyphens and dots, starting with a letter.\n *\n * **Dots are permitted but constrained**, and the constraints are the load-bearing\n * part. A dot is safe in most roles a name plays — a path segment, an npm package\n * name (`socket.io` is precedent), an Nx project name, and the `<kind>-<name>.zip`\n * drop basename CI derives build tags from — but four forms are not, so each is\n * rejected here rather than left to fail later and further away:\n *\n * - a **leading** dot would make a hidden directory (and `.`/`..` are the\n * traversal segments themselves)\n * - a **trailing** dot cannot appear in a git ref, which `nx release`'s\n * `{projectName}@{version}` tag is\n * - **consecutive** dots are likewise forbidden in a git ref\n * - a name that is *only* dots has no identifier to derive at all\n *\n * The regex encodes this by requiring at least one `[a-z0-9-]` after every dot,\n * which rules out all four without a separate check per case.\n *\n * Note that the two kinds whose ecosystems reject dots outright do **not** get a\n * narrower charset here — they transform instead: `pythonModuleDirectory` and\n * `dartPackageName` map `.` to `_` exactly as they already do for `-`, since a\n * dot is Python's package separator and pub rejects it. Go needs nothing: Nx's\n * own `names()` helper already treats a dot as a word separator, so\n * `@nx-go/nx-go`'s `package` clause for `my.lib` is a valid `mylib` (verified\n * empirically against the installed plugin, not assumed).\n *\n * Applied uniformly regardless of where the name came from (a CLI flag/\n * positional argument, or an interactive prompt) — `promptText`'s own\n * non-empty check only runs on the *prompted* path, so an explicitly empty\n * flag (`mnci add react-app \"\"`) previously slipped straight through, since\n * `name ?? await promptText(...)` only substitutes on `null`/`undefined`, not\n * `''`. Calling this right after resolving the name closes that gap too — an\n * empty string fails the same charset check as any other invalid name.\n *\n * @param name - The candidate workspace or project name.\n * @param label - What the name is for, used in the error message (e.g.\n * `'Workspace name'`, `'Project name'`).\n * @returns Nothing.\n * @throws Error when the name is empty, contains a character outside\n * `[a-z0-9-.]`, doesn't start with a lowercase letter, or uses a dot in one of\n * the four rejected positions described above.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function assertValidProjectName (name: string, label: string): void {\n if (!/^[a-z][a-z0-9-]*(?:\\.[a-z0-9-]+)*$/.test(name)) {\n throw new Error(\n `${label} '${name}' is invalid — use lowercase letters, digits, hyphens and dots, starting with a letter, with no leading, trailing or repeated dot (e.g. 'my-project', 'my.service').`,\n )\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport { DOTNET_SDK_VERSION, NUGET_AZURE_SOURCE, nugetConfigContent, readMnciConfig } from '../workspace-overlay'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addProjectJsonTargets,\n defaultScope,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n type AddOptions,\n} from './post-generation.use-case'\n\n/**\n * The `dotnet new` template mnci passes straight through to the .NET CLI.\n *\n * @remarks\n * Mirrors `NodeFramework`: mnci has no opinion of its own here, it is a thin\n * pass-through to the underlying tool's own template names, the same way\n * `node.ts` passes `--framework` straight to `@nx/node:application` without\n * any framework-specific logic of its own. `console` is the default — the\n * least opinionated shape, matching `node-app`'s `none` default. `webapi` is\n * ASP.NET Core's REST API template; `worker` is the long-running\n * background-service template (the .NET analogue of a Node daemon).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type DotnetTemplate = 'console' | 'webapi' | 'worker'\n\n/**\n * Fails fast, with an install hint, when the .NET SDK is not on the PATH.\n *\n * @remarks\n * Mirrors `ensureGo` in `add/go.ts`: probed before any scaffold or plugin\n * install so a missing toolchain surfaces as one clear sentence rather than\n * an opaque `dotnet: command not found` from deep inside a shelled-out call.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `dotnet` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureDotnet (workspaceRoot: string): void {\n if (runShell('dotnet', ['--version'], workspaceRoot) !== 0) {\n throw new Error('.NET SDK not found. Install .NET 8+ first: https://dotnet.microsoft.com/download')\n }\n}\n\n/**\n * The target framework moniker `dotnet new` scaffolds every C# project\n * against, derived from {@link DOTNET_SDK_VERSION}.\n *\n * @remarks\n * Derived rather than hardcoded a second time, so bumping the SDK constant\n * reaches every newly scaffolded project for free — the same drift\n * `NODE_VERSION` already exists to prevent for Node, applied here before it\n * has the chance to happen twice.\n *\n * @returns A TFM string (e.g. `net10.0`).\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction targetFramework (): string {\n const [major, minor] = DOTNET_SDK_VERSION.split('.', 2)\n\n return `net${major}.${minor}`\n}\n\n/**\n * Converts mnci's kebab-case project name to the PascalCase .NET convention\n * for a project/namespace/assembly identity.\n *\n * @remarks\n * The directory stays kebab-case (`apps/<name>`, matching every other kind);\n * only the `.csproj`'s own name — and the C# namespace `dotnet new` derives\n * from it — changes shape. Mirrors `pythonModuleDirectory`'s kebab-to\n * -language-convention conversion in `python.ts` (there: kebab to\n * `snake_case`; here: kebab to `PascalCase`), each language kind converting\n * to whatever its own ecosystem actually expects rather than mnci imposing\n * one casing convention workspace-wide.\n *\n * @param name - The kebab-case project name (already validated).\n * @returns The PascalCase equivalent `dotnet new` writes as `-n`.\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pascalCase (name: string): string {\n return name\n .split('-')\n .map(part => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n}\n\n/**\n * Scaffolds a .NET project with the real `dotnet new` CLI.\n *\n * @remarks\n * `@nx/dotnet` ships no scaffolding generator at all — verified against the\n * real published package (23.2.0), not assumed from documentation: it is\n * purely a project-graph inference plugin, discovering targets from whatever\n * `.csproj` already exists on disk via a bundled MSBuild analyzer. Its own\n * README documents the workflow as `dotnet new <template> -n <Name>`, then\n * `nx build <project>` — there is no Nx generator standing in for the step\n * other kinds delegate to Nx entirely, so this shells out directly instead.\n * The same pattern `@mnci/nx-flutter`'s own generators already use\n * internally for `flutter create`, run from a `GeneratorCallback` because it\n * writes to the real filesystem rather than an Nx `Tree` — here there is no\n * Tree at all, since there is no generator wrapping the call.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - Workspace-relative directory (e.g. `apps/<name>`).\n * @param identity - The project/namespace/assembly identity `dotnet new`\n * writes as `-n` — PascalCase for an app, `<PascalScope>.<PascalName>` for a\n * publishable lib (see {@link addCsharpLib}).\n * @param template - The `dotnet new` template. A bare `string`, not\n * {@link DotnetTemplate}: that type is the user-facing app choice\n * (mirroring `NodeFramework`), while a lib's template (`classlib`) is fixed\n * and internal — never something `csharp-lib` exposes a flag for.\n * @returns Nothing.\n * @throws Error when the underlying `dotnet new` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction scaffoldDotnetProject (\n workspaceRoot: string,\n projectRoot: string,\n identity: string,\n template: string,\n): void {\n if (\n runShell(\n 'dotnet',\n ['new', template, '-n', identity, '-o', projectRoot, '--framework', targetFramework()],\n workspaceRoot,\n ) !== 0\n ) {\n throw new Error(`dotnet new ${template} failed for ${projectRoot}`)\n }\n}\n\n/**\n * The `package` target for a C# app: publish, then zip, into the drop.\n *\n * @remarks\n * Deliberately runs its own `dotnet publish` rather than depending on\n * `@nx/dotnet`'s inferred `publish` target and zipping wherever it happened\n * to write. That target's exact executor and output layout are decided\n * inside the plugin's bundled MSBuild analyzer — a compiled `.dll` this\n * package cannot introspect the way it inspects the plugin's own JS, and\n * this environment has no live .NET SDK to run it against and observe. An\n * explicit, mnci-controlled output path sidesteps that uncertainty entirely\n * — the same reasoning {@link goPackageTarget} already applies by zipping a\n * fixed `dist/apps/<name>` rather than trusting `@nx-go/nx-go`'s own build\n * target to land somewhere predictable.\n *\n * **Unverified without a real SDK — the first thing to confirm once the\n * gated e2e (which skips this section entirely where `dotnet` is absent) can\n * actually run it.** If `@nx/dotnet`'s inferred `publish` target turns out\n * reliable in practice, depending on it and dropping this project's own\n * `dotnet publish` call would be the simplification to make.\n *\n * @param tag - The drop basename prefix (`csharp-app` or `csharp-function-app`).\n * @param projectRoot - Workspace-relative project directory.\n * @param name - The C# app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpAppPackageTarget (\n tag: string,\n projectRoot: string,\n name: string,\n): Record<string, unknown> {\n const outDir = `dist/apps/${name}`\n const zip = `dist/drop/${tag}-${name}.zip`\n const command = `node -e \"const cp=require('node:child_process');const r=cp.spawnSync('dotnet',['publish','${projectRoot}','-c','Release','-o','${outDir}'],{stdio:'inherit',shell:true});if(r.status!==0)process.exit(r.status??1);const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('${outDir}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The `start` target for a C# app: `dotnet run`, locally.\n *\n * @remarks\n * `dotnet run` builds and runs from source in one step — unlike\n * {@link csharpAppPackageTarget}'s `publish`, no separate build/`dependsOn`\n * is needed. `continuous: true` marks it as a long-running dev task, the\n * same shape every other kind's custom `start` target uses (see\n * `goStartTarget`).\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpAppStartTarget (projectRoot: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'dotnet run', cwd: projectRoot },\n }\n}\n\n/**\n * Adds a C# app under `apps/`, scaffolded with the real `dotnet new` CLI.\n *\n * @remarks\n * Ensures the toolchain and the `@nx/dotnet` inference plugin are present,\n * scaffolds the project directly (see {@link scaffoldDotnetProject} for why\n * there is no Nx generator to delegate to), then layers mnci's own\n * `package`/`start` targets on top via a `project.json` this call creates —\n * `@nx/dotnet` writes none, since `build`/`test`/`restore`/etc. are all\n * inferred from the `.csproj` alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param template - The `dotnet new` template (defaults to a bare console app,\n * mirroring `node-app`'s `none`-by-default framework choice).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpApp (\n workspaceRoot: string,\n name: string,\n template: DotnetTemplate = 'console',\n): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n ensureAdmZip(workspaceRoot)\n\n const projectRoot = `apps/${name}`\n scaffoldDotnetProject(workspaceRoot, projectRoot, pascalCase(name), template)\n addProjectJsonTargets(join(workspaceRoot, projectRoot, 'project.json'), {\n package: csharpAppPackageTarget('csharp-app', projectRoot, name),\n start: csharpAppStartTarget(projectRoot),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Converts a workspace scope (`@demo`, `@my-org`) to the PascalCase prefix a\n * NuGet `PackageId` conventionally uses (`Demo`, `MyOrg`).\n *\n * @remarks\n * NuGet has no `@scope/name` convention the way npm does — package IDs are\n * flat, dot-separated strings (`Company.Product`), so the workspace scope is\n * folded into the project's own identity at `dotnet new` time\n * (`<PascalScope>.<PascalName>`) rather than written into any manifest field\n * afterwards. `dotnet new classlib -n Demo.Sdk` sets the assembly name, the\n * root namespace AND the default `PackageId` all in one step, which is also\n * why {@link addCsharpLib} needs no post-generation manifest repair the way\n * `npm-lib` does — there is no separate \"importPath\" field to get wrong.\n *\n * @param scope - The workspace scope, e.g. `@demo`.\n * @returns The PascalCase prefix, e.g. `Demo`.\n * @throws Never - pure string transformation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pascalScope (scope: string): string {\n return pascalCase(scope.replace(/^@/, ''))\n}\n\n/**\n * The workspace-relative path every publishable `csharp-lib`'s\n * `release.version.versionActions` points at.\n *\n * @remarks\n * Not a package specifier, deliberately: Nx resolves that config value in\n * two ways (`nx/dist/.../version-actions.js`, `resolveVersionActionsPath`,\n * read directly rather than assumed) — first as a module specifier via\n * `require.resolve(path)`, then, when that fails, as\n * `require.resolve(join(workspaceRoot, path))`. The second form is exactly\n * what {@link CSHARP_VERSION_ACTIONS} needs and nothing more: a shared,\n * hand-written file `mnci add csharp-lib` writes straight into the\n * generated workspace, with no sixth mnci package to scaffold, publish and\n * keep versioned just to carry one class — unlike `@mnci/nx-python-pip` and\n * `@mnci/nx-flutter`, which exist because a whole generator/executor surface\n * was missing, not one release hook.\n */\nconst CSHARP_VERSION_ACTIONS_PATH = 'tools/csharp-version-actions.cjs'\n\n/**\n * The `tools/csharp-version-actions.cjs` file every `csharp-lib` shares.\n *\n * @remarks\n * `@nx/dotnet` supplies build/test/pack inference but no `versionActions` at\n * all — verified against the real published package, the same way its\n * generator-less shape was verified (see {@link scaffoldDotnetProject}'s\n * remarks). Without this, `nx release` falls back to its npm default, which\n * looks for a `package.json` a `.csproj` project does not have, and aborts\n * while building the release graph — the same failure mode already fixed\n * for `go-lib` (see `RELEASE_CONFIG`'s `!tag:type:go-lib` exclusion in\n * `overlay.ts`). Exclusion is the wrong fix here, unlike Go: a `go-lib` has\n * no independent version because mnci puts every Go project in one shared\n * `go.mod`, but every `csharp-lib` is its own `.csproj` with its own\n * independently published NuGet identity — excluding it would silently stop\n * versioning packages the user explicitly asked to publish.\n *\n * **The `.csproj` filename varies per project** (the PascalCase scope+name\n * identity, not a fixed name the way `pubspec.yaml`/`package.json` are), so\n * this globs the project root for the first `*.csproj` rather than\n * `nx/release`'s built-in `validManifestFilenames` auto-discovery, which\n * only matches an exact filename — the reason `validate()` is overridden\n * too, rather than left to the base class's default (which would silently\n * find nothing to validate, since it drives off that same exact-match list).\n *\n * **`readCurrentVersionFromRegistry` is never actually called for these\n * projects** — verified by reading `nx`'s own release source\n * (`resolve-current-version.js`), not assumed: `RELEASE_CONFIG` sets\n * `version.conventionalCommits: true`, which makes Nx default\n * `currentVersionResolver` to `'git-tag'`, and that path reads the version\n * straight off the matching `{projectName}@{version}` tag — it only calls\n * into `readCurrentVersionFromSourceManifest` as the disk fallback when no\n * tag exists yet (the first release) or a clone has no tags fetched (see\n * `mnci-details.md`'s \"run `git fetch --tags` first\" troubleshooting entry).\n * So a real NuGet registry query here would be dead code today; overridden\n * anyway, with a null-and-explain return, purely to satisfy the base\n * class's contract — the exact shape `DartVersionActions` already uses for\n * the same reason (Dart's Flutter packages are git-tag-only for a different\n * cause, no pub.dev feed at all, but the resulting method body is\n * identical in spirit).\n *\n * Written as `.cjs`, not `.ts` or a bare `.js`: Nx `require()`s this file\n * directly (`requireWithTsconfigFallback`, not `loadTsFile` — that branch is\n * keyed on a `.ts`/`.cts`/`.mts` extension), and `.cjs` resolves as\n * CommonJS regardless of whichever `\"type\"` the generated root\n * `package.json` declares, so there is nothing here to get wrong by an\n * ESM/CJS mismatch two files can drift on independently.\n *\n * **Unverified without a real SDK/Nx release run** — the same caveat\n * {@link csharpAppPackageTarget} and `csharpFunctionAppCsproj` already carry.\n * `nx release --dry-run` against a real generated workspace with a real\n * `csharp-lib` is what the gated e2e (tracked separately) exists to confirm.\n */\nexport const CSHARP_VERSION_ACTIONS = `'use strict'\n\nconst { posix } = require('node:path')\nconst { VersionActions } = require('nx/release')\n\nconst VERSION_TAG = /<Version>([^<]*)<\\\\/Version>/\n\n/*\n * posix.join, never plain join. An Nx Tree path is workspace-relative and\n * forward-slashed on EVERY platform, while join emits a backslashed path on\n * Windows. This value is handed back to nx release for changelog and manifest\n * bookkeeping, and interpolated into the user-facing error below, so it was\n * wrong on every Windows machine. The same bug was already fixed in the\n * Python and Flutter plugins' release actions; this third copy was missed.\n */\nfunction findCsproj (tree, root) {\n const name = tree.children(root).find((entry) => entry.endsWith('.csproj'))\n\n return name ? posix.join(root, name) : null\n}\n\n/**\n * Hand-written Nx release VersionActions for a publishable C# library: reads\n * and writes the <Version> element of the project's own .csproj. Written by\n * \\`mnci add csharp-lib\\` — see that command's source for why this is a\n * workspace file rather than an npm package.\n */\nclass CsharpVersionActions extends VersionActions {\n validManifestFilenames = ['*.csproj']\n\n async readCurrentVersionFromSourceManifest (tree) {\n const manifestPath = findCsproj(tree, this.projectGraphNode.data.root)\n if (!manifestPath) {\n return null\n }\n const contents = tree.read(manifestPath, 'utf8') ?? ''\n const match = VERSION_TAG.exec(contents)\n if (!match || !match[1]) {\n throw new Error(\\`Could not find a <Version> element in \\${manifestPath}\\`)\n }\n\n return { currentVersion: match[1], manifestPath }\n }\n\n async readCurrentVersionFromRegistry (_tree, _currentVersionResolverMetadata) {\n return {\n currentVersion: null,\n logText: 'C# libraries resolve their current version from git tags, not a registry lookup',\n }\n }\n\n async readCurrentVersionOfDependency (_tree, _projectGraph, _dependencyProjectName) {\n return { currentVersion: null, dependencyCollection: null }\n }\n\n async updateProjectVersion (tree, newVersion) {\n const manifestPath = findCsproj(tree, this.projectGraphNode.data.root)\n if (!manifestPath) {\n throw new Error(\\`No .csproj found in \\${this.projectGraphNode.data.root}\\`)\n }\n const contents = tree.read(manifestPath, 'utf8') ?? ''\n if (!VERSION_TAG.test(contents)) {\n throw new Error(\\`Could not find a <Version> element in \\${manifestPath}\\`)\n }\n tree.write(manifestPath, contents.replace(VERSION_TAG, () => \\`<Version>\\${newVersion}</Version>\\`))\n\n return [\\`Updated \\${manifestPath} to version \\${newVersion}\\`]\n }\n\n async updateProjectDependencies (_tree, _projectGraph, _dependenciesToUpdate) {\n return []\n }\n\n async validate (tree) {\n if (!findCsproj(tree, this.projectGraphNode.data.root)) {\n throw new Error(\\`The project \"\\${this.projectGraphNode.name}\" does not have a .csproj file available in \\${this.projectGraphNode.data.root}\\`)\n }\n }\n}\n\nmodule.exports = CsharpVersionActions\n`\n\n/**\n * Writes the shared `tools/csharp-version-actions.cjs` file, idempotently.\n *\n * @remarks\n * Identical content on every call, so an unconditional overwrite on each\n * `mnci add csharp-lib` is safe — the same idempotency every other\n * mnci-owned file in a generated workspace already relies on.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction writeCsharpVersionActions (workspaceRoot: string): void {\n writeFileEnsured(join(workspaceRoot, CSHARP_VERSION_ACTIONS_PATH), CSHARP_VERSION_ACTIONS)\n}\n\n/**\n * Points a project's `project.json` at {@link CSHARP_VERSION_ACTIONS_PATH}\n * for `nx release`.\n *\n * @remarks\n * Mirrors `addProjectJsonTargets` in `shared.ts` — same tolerant-of-a-missing-file\n * read, same merge-not-replace shape — but merges into `release.version`\n * rather than `targets`, so it stays a small sibling function rather than a\n * parameter added to that one to cover a single caller.\n *\n * @param projectJsonPath - Absolute path to the project's `project.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addProjectJsonReleaseVersionActions (projectJsonPath: string): void {\n const project = fileExists(projectJsonPath)\n ? readJson<Record<string, unknown>>(projectJsonPath)\n : {}\n const release = (project.release as Record<string, unknown> | undefined) ?? {}\n const version = (release.version as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(\n projectJsonPath,\n toJson({\n ...project,\n release: { ...release, version: { ...version, versionActions: CSHARP_VERSION_ACTIONS_PATH } },\n }),\n )\n}\n\n/**\n * Adds an explicit `<Version>0.1.0</Version>` to a freshly scaffolded\n * `.csproj`, inside its first `<PropertyGroup>`.\n *\n * @remarks\n * `dotnet new classlib` writes no `<Version>` element at all — NuGet\n * defaults an absent one to `1.0.0` implicitly, which is a fine publish\n * default but a bad *first* one for {@link CSHARP_VERSION_ACTIONS} to read:\n * an explicit starting value here is what lets that class read/write a real\n * element from the very first `nx release`, rather than special-casing a\n * project whose manifest declares no version yet — the same reason `npm-lib`\n * and `python-lib` both start their manifests with an explicit version\n * rather than leaving it to a generator default.\n *\n * @param csprojPath - Absolute path to the freshly scaffolded `.csproj`.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addInitialVersion (csprojPath: string): void {\n const contents = readFileSync(csprojPath, 'utf8')\n writeFileEnsured(csprojPath, contents.replace('<PropertyGroup>', '<PropertyGroup>\\n <Version>0.1.0</Version>'))\n}\n\n/**\n * The `nx-release-publish` target for a `csharp-lib`: `dotnet pack` then\n * `dotnet nuget push` every produced `.nupkg`.\n *\n * @remarks\n * `nx release publish` requires the target literally named\n * `nx-release-publish` — confirmed by reading Nx's own release source\n * (`publish.js`), not assumed — and skips a project silently if it lacks\n * one, UNLESS every matched project lacks it, which throws for the whole\n * `nx release publish` run. So this target is always present, never\n * conditioned on the registry choice at generation time — the choice is\n * read at RUNTIME instead, from whether `NUGET_PAT` is set (only true when\n * {@link nugetPublishEnvFragment} in `overlay.ts` exported it, which only\n * happens for the Azure Artifacts registry). That keeps this target free of\n * any {@link RegistryConfig} of its own, the same way Python's publish\n * target carries no registry specifics — its `TWINE_*` env vars are\n * injected only at CI release time too.\n *\n * Runs its own `dotnet pack` into an mnci-controlled `dist/` directory\n * rather than depending on `@nx/dotnet`'s inferred `pack` target's output\n * location — the same reasoning {@link csharpAppPackageTarget} already\n * applies for `dotnet publish`, unverifiable here without a live SDK.\n *\n * `--source` names {@link NUGET_AZURE_SOURCE}, the fixed key\n * {@link nugetConfigContent} registers the feed under — `--api-key` is a\n * required-but-ignored placeholder for an Azure Artifacts feed (confirmed\n * from Microsoft's own \"Publish NuGet packages with dotnet CLI\" guide:\n * \"you can use any string as its value\"); the real auth is the\n * `packageSourceCredentials` entry keyed to that same source.\n *\n * **Unverified without a real SDK/Azure feed** — the same caveat every\n * other C# kind carries; the gated e2e (tracked separately) is what\n * confirms it.\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpLibPublishTarget (projectRoot: string): Record<string, unknown> {\n const outDir = `${projectRoot}/dist`\n const command = `node -e \"if(!process.env.NUGET_PAT){console.log('NuGet publish is not configured for this registry choice (no NUGET_PAT) - skipping. Regenerate with --registry azure-artifacts, or run dotnet nuget push manually.');process.exit(0)}const cp=require('node:child_process');const pack=cp.spawnSync('dotnet',['pack','${projectRoot}','-c','Release','-o','${outDir}'],{stdio:'inherit',shell:true});if(pack.status!==0)process.exit(pack.status??1);const fs=require('node:fs');for(const pkg of fs.globSync('${outDir}/*.nupkg')){const push=cp.spawnSync('dotnet',['nuget','push',pkg,'--source','${NUGET_AZURE_SOURCE}','--api-key','AZ'],{stdio:'inherit',shell:true});if(push.status!==0)process.exit(push.status??1)}\"`\n\n return { executor: 'nx:run-commands', options: { command } }\n}\n\n/**\n * Adds a publishable C# library under `packages/`: `dotnet new classlib`.\n *\n * @remarks\n * The scope is resolved exactly the way `addNpmLib` resolves it: an explicit\n * `--scope` wins; otherwise the flag path (`kindProvided`) defaults it\n * silently, while the interactive/bare path prompts for it (with the\n * workspace's own scope as the default) — one shared UX across every\n * publishable-lib kind, not a C#-specific decision.\n *\n * No `package`/zip target, unlike {@link addCsharpApp}: a publishable lib's\n * distribution path is `nx release` (NuGet publish), the same as\n * `npm-lib`/`python-lib`, never the `dist/drop` zip convention that exists\n * for apps. `@nx/dotnet` already infers a `pack` target from the `.csproj`\n * alone, so there is nothing extra to wire here.\n *\n * Wires `nx release` itself: an explicit `<Version>0.1.0</Version>` (see\n * {@link addInitialVersion}) plus a project-level `release.version.versionActions`\n * override pointing at the shared {@link CSHARP_VERSION_ACTIONS_PATH} (see\n * that constant's remarks for why a `.csproj` lib needs one at all, and why\n * it is a workspace file rather than a sixth mnci package), and an\n * `nx-release-publish` target (see {@link csharpLibPublishTarget}) so\n * `nx release` actually pushes the package, not just versions and tags it.\n *\n * Also (re)writes the shared root `nuget.config` from the workspace's\n * persisted registry choice ({@link readMnciConfig}) — the same \"written by\n * `mnci add` on the first project of that kind\" lifecycle\n * `requirements-dev.txt` already has for Python, since a JS-only or\n * Go/Flutter-only workspace has no reason to carry a NuGet config file at\n * all. Falls back to the public npm registry when an older, pre-registry\n * workspace's `nx.json` carries no persisted choice, rather than assuming\n * Azure Artifacts credentials that were never collected.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @returns A promise that resolves when the scaffold has finished.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addCsharpLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n): Promise<void> {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('NuGet package scope for the published library', defaultScope(workspaceRoot)))\n\n const projectRoot = `packages/${name}`\n const identity = `${pascalScope(scope)}.${pascalCase(name)}`\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'classlib')\n addInitialVersion(join(workspaceRoot, projectRoot, `${identity}.csproj`))\n writeCsharpVersionActions(workspaceRoot)\n addProjectJsonReleaseVersionActions(join(workspaceRoot, projectRoot, 'project.json'))\n addProjectJsonTargets(join(workspaceRoot, projectRoot, 'project.json'), {\n 'nx-release-publish': csharpLibPublishTarget(projectRoot),\n })\n\n const registry = readMnciConfig(workspaceRoot).registry ?? { kind: 'npm' }\n writeFileEnsured(join(workspaceRoot, 'nuget.config'), nugetConfigContent(registry, scope))\n\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds an internal (never-published) C# library under `libs/`.\n *\n * @remarks\n * No scope prefix, unlike {@link addCsharpLib}: a `PackageId` only means\n * anything for something that gets published, and this never does — the\n * same reasoning `internal-lib`'s plain `@nx/js:lib --bundler=tsc` needs no\n * `--importPath` either. `build: false` in the `registerProjectCommands`\n * call for the same reason: consistent with `go-internal-lib`, an\n * internal-only library gets no root `<name>:build` script of its own.\n *\n * **Consuming it is a manual step, deliberately not automated here.**\n * `mnci add`'s own signature has no \"consumer\" argument for this kind (that\n * is `python-vendor`'s narrower job, wiring one named consumer at a time) —\n * every other internal-lib kind resolves automatically once generated\n * (TS via `node_modules` symlinks, Go via one shared module, Dart via the\n * pub workspace's plain version constraint), but C#'s `<ProjectReference>`\n * has no such implicit resolution: it is an explicit edit to the consuming\n * `.csproj`, which `dotnet add <consumer> reference <lib>` makes in one\n * command. The step is named for the user rather than skipped silently, the\n * same courtesy {@link addGoInternalLib}'s `goModulePath` message already\n * extends for Go's import path.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin install/scaffold fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpInternalLib (workspaceRoot: string, name: string): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n\n const projectRoot = `libs/${name}`\n const identity = pascalCase(name)\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'classlib')\n registerProjectCommands(workspaceRoot, name, { build: false })\n logger.step(\n `Reference it from a consumer with: dotnet add <consumer>.csproj reference ${projectRoot}/${identity}.csproj`,\n )\n}\n\n/**\n * The `host.json` every generated Azure Functions app writes, C# included.\n *\n * @remarks\n * Identical to `NODE_FUNCTION_APP_HOST_JSON` — the extension bundle format is\n * the Functions runtime schema, not a language concern — duplicated rather\n * than imported across a `node.ts`/`csharp.ts` boundary that would otherwise\n * read as one language depending on another's internals for something\n * neither actually owns.\n */\nconst CSHARP_FUNCTION_APP_HOST_JSON = `{\n \"version\": \"2.0\",\n \"extensionBundle\": {\n \"id\": \"Microsoft.Azure.Functions.ExtensionBundle\",\n \"version\": \"[4.*, 5.0.0)\"\n }\n}\n`\n\n/**\n * The isolated-worker `.csproj`, overwriting whatever the base `console`\n * scaffold wrote.\n *\n * @remarks\n * `dotnet new` ships no Azure Functions template of its own — verified\n * against Microsoft's own current (2026) isolated-worker guide, not assumed:\n * a Functions app is an ordinary console app whose project file opts into\n * the `Azure.Functions.Sdk` MSBuild project SDK, the same \"scaffold the base\n * shape, then overlay the Functions-specific files\" split every other\n * `*-function-app` kind already uses (see `addNodeFunctionApp`).\n *\n * `Azure.Functions.Sdk` is the CURRENT recommended project shape (superseding\n * the older explicit `Microsoft.Azure.Functions.Worker.Sdk` package\n * reference plus hand-set `AzureFunctionsVersion`/`OutputType` properties):\n * it auto-configures both, is shorter, and is what Microsoft's own migration\n * guide moves existing projects TO — so new projects should start there\n * rather than at the thing that guide migrates away from.\n *\n * **The `FrameworkReference` is load-bearing, not a performance nicety —\n * found from a real `dotnet build` failure on the gated Windows e2e, then\n * confirmed by installing a real .NET 10 SDK and reproducing the exact\n * failure end to end (real `mnci add csharp-function-app` +\n * `nx run-many -t build`), not reasoned out from docs alone.** Without it,\n * `dotnet build` fails with a `CS0234` error naming the missing `AspNetCore`\n * namespace for every `using Microsoft.AspNetCore.Http;`/\n * `Microsoft.AspNetCore.Mvc;` line in {@link csharpFunctionAppHello} — with\n * both package references below already present. The reason: those types\n * live in the ASP.NET Core SHARED FRAMEWORK (`Microsoft.AspNetCore.App`),\n * not in any NuGet package. `Microsoft.NET.Sdk.Web`-based projects reference\n * that framework implicitly; `Azure.Functions.Sdk` does not, and neither\n * Functions package pulls it in as a transitive dependency — confirmed\n * against Microsoft's own docs, where the line only turns up in a separate\n * \"Performance optimizations\" section, framed as a cold-start improvement,\n * while the 4-step \"To enable ASP.NET Core integration for HTTP\" guide never\n * mentions it. It is both a perf knob and, for this SDK, a compile-time\n * requirement. Adding this line alone was not sufficient by itself — see\n * {@link addCsharpFunctionApp}'s remarks for the second, independent defect\n * (a stale restore) the same reproduction found.\n *\n * The package versions below are current as measured against Microsoft's\n * own docs and confirmed to exist via a direct NuGet API query\n * (`api.nuget.org/v3-flatcontainer/.../index.json`); the whole `.csproj`,\n * `Program.cs` and `Hello.cs` combination was then built end to end against\n * a real .NET 10.0.401 SDK, restore and all, not merely syntax-checked.\n *\n * @returns The `.csproj` XML content.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppCsproj (): string {\n return `<Project Sdk=\"Azure.Functions.Sdk/1.0.0\">\n <PropertyGroup>\n <TargetFramework>${targetFramework()}</TargetFramework>\n </PropertyGroup>\n\n <ItemGroup>\n <FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n <PackageReference Include=\"Microsoft.Azure.Functions.Worker\" Version=\"2.52.0\" />\n <PackageReference Include=\"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore\" Version=\"2.1.0\" />\n </ItemGroup>\n</Project>\n`\n}\n\n/**\n * The isolated-worker `Program.cs`, overwriting the base scaffold's `Main`.\n *\n * @remarks\n * `FunctionsApplication.CreateBuilder` + `ConfigureFunctionsWebApplication`\n * is the current `IHostApplicationBuilder` pattern (requires the 2.x worker\n * package, which {@link csharpFunctionAppCsproj} pins) — the ASP.NET Core\n * integration that lets a trigger use the ordinary `HttpRequest`/\n * `IActionResult` types in {@link csharpFunctionAppHello} rather than the\n * isolated-worker-specific `HttpRequestData`/`HttpResponseData` pair.\n */\nconst CSHARP_FUNCTION_APP_PROGRAM = `using Microsoft.Azure.Functions.Worker.Builder;\nusing Microsoft.Extensions.Hosting;\n\nvar builder = FunctionsApplication.CreateBuilder(args);\n\nbuilder.ConfigureFunctionsWebApplication();\n\nbuilder.Build().Run();\n`\n\n/**\n * The HTTP-triggered sample function written into a generated C# function app.\n *\n * @remarks\n * Mirrors `NODE_FUNCTION_APP_HELLO`'s role: a minimal, real handler so the\n * generated app is runnable rather than an empty shell, using the\n * ASP.NET-Core-integrated `HttpRequest`/`IActionResult` shape ASP.NET Core\n * integration.\n *\n * @param identity - The project's PascalCase identity, used as the namespace.\n * @returns The C# source for the sample function class.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppHello (identity: string): string {\n return `using Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.Mvc;\nusing Microsoft.Azure.Functions.Worker;\n\nnamespace ${identity};\n\npublic class Hello\n{\n [Function(\"Hello\")]\n public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, \"get\")] HttpRequest req)\n {\n return new OkObjectResult(\"Hello from mnci.\");\n }\n}\n`\n}\n\n/**\n * The `start` target for a C# function app: `dotnet run`, locally.\n *\n * @remarks\n * `Azure.Functions.Sdk` wires `dotnet run` to start the Functions host\n * directly when Azure Functions Core Tools (`func`) is installed — the same\n * assumption `nodeFunctionAppStartTarget`'s `func start` already makes, just\n * invoked through `dotnet` rather than `func` itself, since that is what\n * the project's own tooling now integrates with.\n *\n * @param projectRoot - Workspace-relative project directory.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction csharpFunctionAppStartTarget (projectRoot: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'dotnet run', cwd: projectRoot },\n }\n}\n\n/**\n * Adds a C# Azure Function app: an isolated-worker overlay on the base\n * console scaffold.\n *\n * @remarks\n * Structurally the same split as `addNodeFunctionApp`/`addPythonFunctionApp`:\n * scaffold the plain app, then overwrite/add exactly the files a Functions\n * app needs (`.csproj`, `Program.cs`, one sample HTTP trigger, `host.json`).\n * No scope/`PackageId` concept, unlike {@link addCsharpLib} — a function app\n * is never NuGet-published, matching every other `*-function-app` kind.\n *\n * **The `dotnet restore` after overwriting `.csproj` is load-bearing, found\n * from a real, gated-e2e `dotnet build` failure, not written defensively.**\n * {@link scaffoldDotnetProject} restores once, but against the ORIGINAL\n * `console` template `Microsoft.NET.Sdk` project — the restore that\n * succeeds is for a project shape this function immediately throws away.\n * Nothing restores again after the overwrite, and `@nx/dotnet`'s own\n * inferred `build` target runs `dotnet build --no-restore --no-dependencies`\n * (verified by reading its actual command from a real `nx build` run), on\n * the assumption restore already happened for the CURRENT project content.\n * It hadn't: `Microsoft.Azure.Functions.Worker`/`.Extensions.Http.AspNetCore`\n * were never fetched, so the first real build failed with `CS0246`s for\n * `Function`/`HttpTrigger`/`AuthorizationLevel` — reproduced against a real\n * SDK installed for this fix, not assumed from the CI log alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when the SDK is missing, or the plugin/scaffold/restore fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addCsharpFunctionApp (workspaceRoot: string, name: string): void {\n ensureDotnet(workspaceRoot)\n ensurePlugin(workspaceRoot, '@nx/dotnet')\n ensureAdmZip(workspaceRoot)\n\n const projectRoot = `apps/${name}`\n const identity = pascalCase(name)\n scaffoldDotnetProject(workspaceRoot, projectRoot, identity, 'console')\n\n const absoluteRoot = join(workspaceRoot, projectRoot)\n writeFileEnsured(join(absoluteRoot, `${identity}.csproj`), csharpFunctionAppCsproj())\n writeFileEnsured(join(absoluteRoot, 'Program.cs'), CSHARP_FUNCTION_APP_PROGRAM)\n writeFileEnsured(join(absoluteRoot, 'Hello.cs'), csharpFunctionAppHello(identity))\n writeFileEnsured(join(absoluteRoot, 'host.json'), CSHARP_FUNCTION_APP_HOST_JSON)\n\n if (runShell('dotnet', ['restore', projectRoot], workspaceRoot) !== 0) {\n throw new Error(`dotnet restore failed for ${projectRoot}`)\n }\n\n addProjectJsonTargets(join(absoluteRoot, 'project.json'), {\n package: csharpAppPackageTarget('csharp-function-app', projectRoot, name),\n start: csharpFunctionAppStartTarget(projectRoot),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n","import { existsSync, globSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport {\n fileExists,\n markExecutable,\n readCodeWorkspace,\n readJson,\n toJson,\n writeFileEnsured,\n} from '../file-system'\n\n/**\n * Where a generated monorepo publishes its npm packages.\n *\n * @remarks\n * Supports Azure Artifacts and the public npm registry. GitHub Packages is\n * out of scope for this cut.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type RegistryConfig =\n | { kind: 'azure-artifacts'; organization: string; project: string; artifactsFeed: string } |\n { kind: 'npm' }\n\n/**\n * Which CI provider(s) {@link applyOverlay} writes a pipeline file for.\n *\n * @remarks\n * `azure` (the default) writes only `azure-pipelines.yml`; `github` writes\n * only `.github/workflows/ci.yml`; `both` writes both — so a GitHub-hosted\n * repo can pick the provider it actually runs on instead of carrying an\n * unused Azure Pipelines file.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type CiProvider = 'azure' | 'github' | 'both'\n\n/**\n * The stack chosen at `mnci new` — asked up front, honoured by every `add`.\n *\n * @remarks\n * TypeScript is not a knob: every workspace runs the **dual compiler**\n * ({@link TS_COMPILER_DEPENDENCIES}) — TypeScript 6 for the programmatic API\n * (Nx's graph/plugins, Vite, typescript-eslint, the editor) and TypeScript 7's\n * native `tsc` for the `typecheck`/`build` tasks. The only stack knob is the\n * unit-test runner, persisted as Nx **generator defaults** in `nx.json`.\n * Linting AND formatting are ESLint (always) — there is no separate formatter.\n *\n * @typeParam None - this type has no generic type parameters.\n */\n/**\n * The parts of the toolchain a user chooses at `mnci new`.\n *\n * @remarks\n * Two knobs, deliberately. Everything else in an mnci workspace is fixed by the\n * `--preset=ts` premise, and each additional choice multiplies the matrix the\n * e2e has to cover — so a knob earns its place only when neither answer is\n * defensible for everyone. The test runner qualifies (Jest and Vitest are both\n * first-class in Nx) and so does the linter, now that the Rust toolchain is a\n * real alternative rather than an experiment.\n *\n * Persisted into `nx.json`'s `mnci` block, so `mnci upgrade` re-applies the\n * overlay for the stack the workspace actually chose instead of reverting it to\n * the defaults.\n */\nexport interface StackConfig {\n /** Unit-test runner (both Nx-native for the plugin kinds). */\n testRunner: 'jest' | 'vitest'\n}\n\n/**\n * The `--yes` / flagless defaults — the current opinionated stack.\n *\n * @remarks\n * Jest: the test runner existing generated repos (and the e2e suite)\n * already assume, so defaulting to it keeps behaviour unchanged when\n * the stack is not chosen explicitly. Linting and formatting are always ESLint.\n */\nexport const DEFAULT_STACK: StackConfig = { testRunner: 'jest' }\n\n/**\n * The dual TypeScript compiler stamped into every workspace's `devDependencies`.\n *\n * @remarks\n * TypeScript 7 is the native (Go) compiler: much faster, but it ships no\n * programmatic API yet, so tools that import `typescript` (Nx's\n * `@nx/js/typescript` plugin and project graph, Vite, typescript-eslint, the\n * editor language service) still need TypeScript 6. The\n * [Nx TS 7 guide](https://nx.dev/docs/technologies/typescript/guides/typescript-7)\n * solves this with two npm aliases: `typescript` resolves to a TS 6 package\n * (API intact, and its binary is `tsc6`, not `tsc`), while `@typescript/native`\n * provides the TS 7 `tsc`. The `@nx/js/typescript` plugin's inferred\n * `typecheck`/`build` tasks then run `tsc` = TS 7, while Nx analyses config\n * through the TS 6 API — automatically, with no target rewiring. Frozen per\n * repo by the committed lockfile, so `npm ci` reproduces it.\n */\nexport const TS_COMPILER_DEPENDENCIES: Record<string, string> = {\n '@typescript/native': 'npm:typescript@^7.0.2',\n 'typescript': 'npm:@typescript/typescript6@^6.0.2',\n}\n\n/**\n * How an Azure Artifacts workspace authenticates npm against its feed.\n *\n * @remarks\n * - `pat` — a base64 personal access token from the pipeline's variable group,\n * read by the root `.npmrc`'s `username`/`_password` block. The default, and the\n * only mode that works on every CI provider.\n * - `build-identity` — `npmAuthenticate@0` injects the build service identity's\n * token into `.npmrc` at build time, so the file carries **no credentials** and\n * there is no PAT to store, encode, rotate or let expire. Azure Pipelines only:\n * GitHub Actions has no equivalent task.\n *\n * The two are not interchangeable spellings of one thing. The feed's publish\n * endpoint advertises a Bearer scheme bound to `login.windows.net`, which wants an\n * **Entra ID access token**; a PAT is not one and is rejected as Bearer. A PAT\n * therefore has to go through Basic (`_password`), while the build identity's\n * token is Entra-issued and is what Bearer wants. See {@link npmrcContent}.\n */\nexport type NpmAuthMode = 'pat' | 'build-identity'\n\n/**\n * The auth modes, for validating a flag value.\n *\n * @remarks\n * One list, so the flag's error message and the validator cannot disagree about\n * what is accepted.\n */\nexport const NPM_AUTH_MODES: readonly NpmAuthMode[] = ['pat', 'build-identity']\n\n/**\n * Resolves the npm auth mode a workspace should be written with, or refuses one\n * its CI cannot deliver.\n *\n * @remarks\n * `build-identity` is `npmAuthenticate@0`, an Azure Pipelines task: GitHub Actions\n * has nothing equivalent, so a workspace whose CI includes GitHub would be written\n * a credential-free `.npmrc` that authenticates nowhere on that provider. That is\n * refused up front rather than generated, because the failure it would cause -\n * a publish rejected as an authentication error - names neither the mode nor the\n * flag. `--ci=both` has no single right answer, so it is refused too rather than\n * guessed at.\n *\n * It also only means anything for an Azure Artifacts feed: public npm has no\n * identity for the task to borrow.\n *\n * @param requested - The `--npm-auth` flag value, if one was passed.\n * @param registry - The workspace's registry.\n * @param ci - The workspace's CI provider(s).\n * @param fallback - What to use when no flag was passed: the persisted value, or\n * the mode detected in an existing pipeline.\n * @returns The mode to write, or `undefined` when none was requested anywhere -\n * which leaves the workspace on the default.\n * @throws Error when the flag is not a known mode, or when `build-identity` is\n * requested for a registry or CI provider that cannot use it.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveNpmAuth (\n requested: string | undefined,\n registry: RegistryConfig,\n ci: CiProvider,\n fallback?: NpmAuthMode,\n): NpmAuthMode | undefined {\n if (requested !== undefined && !NPM_AUTH_MODES.includes(requested as NpmAuthMode)) {\n throw new Error(`--npm-auth must be one of: ${NPM_AUTH_MODES.join(', ')} (got '${requested}').`)\n }\n const mode = (requested as NpmAuthMode | undefined) ?? fallback\n if (mode !== 'build-identity') {\n return mode\n }\n if (registry.kind !== 'azure-artifacts') {\n throw new Error(\n '--npm-auth build-identity needs an Azure Artifacts feed: public npm has no build identity to borrow. Use --registry azure-artifacts, or drop the flag.',\n )\n }\n if (ci !== 'azure') {\n throw new Error(\n `--npm-auth build-identity needs --ci azure (this workspace is '${ci}'): it is npmAuthenticate@0, an Azure Pipelines task, and GitHub Actions has no equivalent. Use --npm-auth pat, or drop the GitHub pipeline.`,\n )\n }\n\n return mode\n}\n\n/**\n * Returns the npm registry URL for a registry config.\n *\n * @remarks\n * Public npm needs no scoped registry, so it returns `undefined`.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The registry URL, or `undefined` for the public npm registry.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function registryUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/npm/registry/`\n }\n\n return undefined\n}\n\n/**\n * Builds the `.npmrc` body for a registry configuration.\n *\n * @remarks\n * The two registry kinds get deliberately **different** files, because the honest\n * answer differs. This replaced a comment-only placeholder that left\n * `npm publish` unable to authenticate in every generated workspace, while the CI\n * dutifully exported a token nothing consumed.\n *\n * **Azure Artifacts: scope routing plus auth.** `@<scope>:registry` sends both\n * resolution *and* `npm publish` of `@<scope>/*` to the feed, because npm prefers\n * a scope's registry over the global one when publishing a scoped package. That\n * makes it real protection — a package named `@<scope>/x` cannot reach npmjs.org\n * by accident. Verified against a real registry rather than read from docs: npm\n * reports `Publishing to <feed>` with only the scope line set.\n *\n * Only the scope is routed, and that is a choice. A global `registry=` would send\n * every install through the feed too, so `npm ci` would need feed auth just to\n * fetch public packages — verified: with only the scope routed, installing a\n * public dependency still succeeds with no token present at all.\n *\n * **Public npm: auth only, no scope routing.** npmjs.org is already the default\n * registry, so a `@<scope>:registry` line pointing there changes nothing — and\n * calling it protection against an accidental public publish would be false,\n * since the public registry *is* the intended target. That specific false claim\n * is why this function is worth reading twice: an earlier version of this file\n * asserted exactly that safety property in the README while emitting no routing\n * line at all, and `overlay.test.ts` asserted the line's absence. Do not\n * reintroduce a protection that the configuration cannot provide.\n *\n * **The one PAT, two encodings.** `_password` takes the base64 value Azure\n * Artifacts' own \"Connect to feed\" instructions hand you, so it is used as-is.\n * `twine` wants the *raw* token and the release guard decodes it there\n * ({@link releaseGuard}). Easy to get backwards; check before wiring a third\n * protocol.\n *\n * An unset `${PAT}`/`${NODE_AUTH_TOKEN}` does not break anything locally —\n * verified that `npm install` of a public dependency still succeeds with the\n * variable absent, so a developer needs no token to work in the workspace.\n *\n * **Build identity: routing only.** With {@link NpmAuthMode} `build-identity` the\n * Azure file carries the scope line and no credentials at all. `npmAuthenticate@0`\n * appends the build identity's Entra token to it in the pipeline (the generated\n * `azure-pipelines.yml` runs it before `npm ci`). The trade is stated in the file\n * rather than discovered: a developer no longer inherits a credential from it, so\n * a local authenticated command needs `npx vsts-npm-auth -config .npmrc` on\n * Windows or a hand-added entry. Public npm ignores the mode — there is nothing to\n * inject.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`), used for the routing line.\n * @param npmAuth - How npm authenticates against an Azure Artifacts feed.\n * @returns The full text of the generated `.npmrc`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function npmrcContent (\n registry: RegistryConfig,\n scope: string,\n npmAuth: NpmAuthMode = 'pat',\n): string {\n if (registry.kind === 'npm') {\n return `; Publish authentication for the public npm registry.\n;\n; NODE_AUTH_TOKEN is exported by the generated CI's release step. Nothing needs\n; it for day-to-day work here - installing, building and testing never\n; authenticate.\n;\n; It is NOT harmless the moment you DO want to authenticate from this directory,\n; which an earlier version of this comment claimed. A project .npmrc wins over\n; your user one, so with NODE_AUTH_TOKEN unset this line hands npm an empty\n; token and overrides 'npm login': inside this repo 'npm whoami' fails with a\n; 401 and any write is refused. The refusal is easy to misread, because for a\n; scoped package the registry answers a write you are not allowed to make with\n;\n; npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fthing\n;\n; and everything above it is npm listing what it intended to send, so a long\n; healthy-looking run still changes nothing.\n;\n; To publish or deprecate by hand, run it from outside this directory, or export\n; a real token first.\n//registry.npmjs.org/:_authToken=\\${NODE_AUTH_TOKEN}\n\n; There is deliberately NO '${scope}:registry' line here. npmjs.org is already\n; the default registry, so routing the scope to it would change nothing — and\n; presenting that as protection against an accidental public publish would be\n; false, because the public registry is the intended target. (An earlier version\n; of this file made exactly that claim while emitting no routing line at all.)\n; To keep a scoped package off npmjs.org, use a private feed: generate with\n; --registry azure-artifacts, which does route the scope.\n`\n }\n\n const feedUrl = registryUrl(registry) as string\n if (npmAuth === 'build-identity') {\n return `; Publish + resolution routing for this workspace's own scope.\n;\n; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the\n; feed: npm prefers a scope's registry over the global one when publishing a\n; scoped package, so a ${scope}/* package cannot reach npmjs.org by accident.\n;\n; Only the scope is routed, on purpose. A global 'registry=' would send every\n; install through the feed as well, so 'npm ci' would need feed auth just to fetch\n; public packages.\n${scope}:registry=${feedUrl}\n\n; NO credentials are written here. The 'npmAuthenticate@0' task in\n; azure-pipelines.yml injects them into this file at build time, using the build\n; service identity - so there is no PAT to store, rotate or encode.\n;\n; That is forced by what the feed accepts. An unauthenticated PUT to the publish\n; endpoint answers with:\n; www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,\n; Basic realm=\"...\", TFS-Federated\n; so its Bearer scheme wants an ENTRA ID access token, which is what the task\n; supplies. A PAT is not one: npm sends _authToken verbatim as a Bearer header, so\n; a PAT there is rejected with \"Unable to authenticate, your authentication token\n; seems to be invalid\". A PAT can only authenticate through the Basic scheme\n; (username + base64 _password), which is what 'mnci upgrade --npm-auth pat' writes.\n;\n; The cost is local: nothing in this file authenticates a developer's machine, so\n; publishing or installing a private package by hand needs\n; 'npx vsts-npm-auth -config .npmrc' (Windows) or a credential you add yourself.\n; Building and testing never authenticate.\n`\n }\n\n // npm keys per-registry credentials by the URL with the protocol stripped.\n const feedKey = feedUrl.replace(/^https:/, '')\n // npm matches credentials by URL prefix and walks only UP the path, so an entry\n // on '/npm/registry/' is never found for a request to '/npm/'. Both are keyed.\n const feedShortKey = feedKey.replace('npm/registry/', 'npm/')\n\n return `; Publish + resolution routing for this workspace's own scope.\n;\n; '${scope}:registry' sends BOTH resolution and 'npm publish' of ${scope}/* to the\n; feed: npm prefers a scope's registry over the global one when publishing a\n; scoped package, so a ${scope}/* package cannot reach npmjs.org by accident.\n;\n; Only the scope is routed, on purpose. A global 'registry=' would send every\n; install through the feed as well, so 'npm ci' would need feed auth just to fetch\n; public packages.\n${scope}:registry=${feedUrl}\n\n; Feed credentials. PAT is the BASE64 value Azure Artifacts' \"Connect to feed\"\n; instructions give you, which is exactly what _password expects, so it is used\n; as-is. (twine wants the RAW token; the CI release step decodes it there.)\n; Azure ignores the username, and npm requires an email it never uses.\n;\n; NOT _authToken, and that is measured rather than assumed. The feed answers an\n; unauthenticated PUT to the publish endpoint with:\n; www-authenticate: Bearer authorization_uri=https://login.windows.net/<tenant>,\n; Basic realm=\"...\", TFS-Federated\n; so its Bearer scheme wants an Entra ID access token, NOT a PAT. npm sends\n; _authToken verbatim as a Bearer header, so a PAT there is rejected with\n; \"Unable to authenticate, your authentication token seems to be invalid\". A PAT\n; authenticates through the Basic scheme, which is username/_password. Note the\n; Packaging REST API DOES accept a PAT as Bearer - do not generalise from it.\n;\n; Both path forms are keyed because npm walks only UP a URL when matching.\n${feedKey}:username=AzureArtifacts\n${feedKey}:_password=\\${PAT}\n${feedKey}:email=npm-requires-this-and-never-uses-it\n${feedShortKey}:username=AzureArtifacts\n${feedShortKey}:_password=\\${PAT}\n${feedShortKey}:email=npm-requires-this-and-never-uses-it\n`\n}\n\n/**\n * The Nx tag a `vscode-extension` project carries, and what `release.projects` matches it by.\n *\n * @remarks\n * An extension lives in `apps/`, which no release glob covers, and a path added to\n * `release.projects` by hand would be lost on the next `mnci upgrade`, which\n * rewrites the array (#229). A tag matcher needs no array merging: every upgrade\n * writes the same list, and it matches every extension there will ever be.\n */\nexport const VSCODE_EXTENSION_TAG = 'type:vscode-extension'\n\n/**\n * The Nx tag a releasable `go-app` carries, and what `release.projects` matches it by.\n *\n * @remarks\n * A separate tag from `type:go-app`, not a reuse of it: most Go apps are internal\n * tools and must stay unreleased, so releasing is an opt-in\n * (`mnci add go-app <name> --release`). Matched by tag for the reason\n * {@link VSCODE_EXTENSION_TAG} is: an app lives in `apps/`, which no release glob\n * covers, and the array is rewritten on every `mnci upgrade`.\n */\nexport const GO_RELEASE_TAG = 'release:go'\n\n/**\n * Builds the `release` block merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * The tag-only model: versions are computed from conventional commits since\n * each package's last release tag, the bump is\n * **never committed** (`git.commit: false`) — only the tag is created and\n * pushed — so a protected `main` never rejects a release, and future runs\n * resolve versions from tag names, not from a committed `package.json`.\n * `fallbackCurrentVersionResolver: 'disk'` keeps a brand-new package (no tag\n * yet) from hard-erroring.\n *\n * The git options live under a top-level `git` (not `version.git`): the\n * guarded CI release step and the generated `release:preview` script both run\n * the combined `nx release` command (never the bare `nx release version`\n * subcommand), and Nx hard-errors that combined command when git options are\n * granular (`version.git`/`changelog.git`) instead of top-level — the reverse\n * of the bare `version` subcommand's own requirement, which is why the two\n * forms aren't interchangeable (verified empirically).\n *\n * **GitHub Releases (`ci === 'github'` only).** Verified empirically against\n * the pinned Nx version (23.1.0, real `--dry-run` runs, and the installed\n * `release.js` source) that the combined `nx release` command now tags\n * *before* it pushes — an earlier version of this comment described a bug\n * where Nx's internal push fired before the tag existed, silently losing\n * every tag; that ordering bug no longer reproduces on this pinned version.\n * Nx itself also refuses to enable `createRelease` while `git.push: false`\n * (hard error: \"createRelease... cannot be enabled when git push is\n * explicitly disabled\"), so `push: true` here is required, not optional, once\n * `createRelease` is on. `changelog.projectChangelogs.file: false` sends the\n * generated changelog content straight into the GitHub Release body without\n * writing an unpushable `CHANGELOG.md` (`git.commit` stays `false`, so a\n * written file would just be silently discarded at the end of every CI run).\n * `workspaceChangelog` stays `false` for every provider: projects release\n * independently (see below), so only per-project changelogs/releases make\n * sense — a single workspace-wide changelog would conflate unrelated\n * packages' histories. `GITHUB_TOKEN` (GitHub Actions' own built-in token,\n * already sufficient under the `contents: write` permission the workflow\n * already grants) is exported in {@link githubActionsYaml}'s release step;\n * once it's pushing anyway, {@link githubActionsYaml} drops its separate\n * explicit `git push origin --tags` step as redundant.\n *\n * **Azure-only and `both`** deliberately keep today's `push: false` /\n * no-`createRelease` behaviour. GitHub Releases only make sense when the repo\n * is actually hosted on GitHub, which `ci` alone cannot confirm for `'azure'`\n * (Azure Pipelines can build a GitHub-hosted repo too) or safely guarantee\n * for `'both'` (both pipeline files exist; whichever one actually executes\n * might be the Azure one, which has no `GITHUB_TOKEN` to give Nx) — so this\n * scope is intentionally limited to the one case where a `GITHUB_TOKEN` is\n * guaranteed to exist: GitHub Actions as the *only* configured provider.\n * {@link azurePipelinesYaml} is unchanged and keeps its own explicit\n * `git push origin --tags` step for exactly this reason.\n *\n * Two directories are released: `packages/*` (publishable **npm** libraries)\n * and `python-packages/*` (publishable **Python** packages) — deliberately one\n * flat project list, not two named `release.groups`: Nx hard-errors\n * `nx release` entirely (every group, not just the empty one) when any\n * explicit group matches zero projects — a real failure mode for a workspace\n * that has added Python packages but no npm ones yet, or vice versa (verified\n * empirically). A flat list has no such all-or-nothing requirement: it stays\n * releasable as soon as *either* glob matches something. Each project's own\n * `versionActions` (npm's default, or `@mnci/nx-python-pip`'s\n * `PythonVersionActions` — stamped onto every publishable Python lib's own\n * `project.json` by that plugin's own `library` generator, not by anything\n * here) reads/writes the right manifest (`package.json` vs `pyproject.toml`)\n * — project-level config wins over the group's, so both kinds coexist in the\n * one group correctly. Publishable **Flutter/Dart** packages also live in\n * `packages/*` and are covered by the same flat list;\n * `@mnci/nx-flutter`'s own `library` generator stamps its\n * `DartVersionActions` on for `pubspec.yaml`.\n * Internal libraries live in `libs/` and apps in `apps/`, so release scoping\n * needs no tags for them.\n *\n * `!tag:type:go-lib` is the one exception, and it is a **bug fix**, not\n * fine-tuning. A `go-lib` also lands in `packages/`, but it has no\n * per-project manifest at all — mnci puts every Go project in one root\n * `go.mod` — so Nx falls back to its default `versionActions`, looks for a\n * `packages/<name>/package.json` that does not exist, and aborts. Because\n * that happens while building the release graph, it takes down the release of\n * **every** project in the workspace, not just the Go one: before this\n * exclusion, a single `mnci add go-lib` made `nx release` exit 1 for the\n * whole repo (verified empirically, then re-verified green with the\n * exclusion in place).\n *\n * Excluding rather than teaching Go a `versionActions` is the semantically\n * correct fix: in a single-module layout there is exactly one Go module, so\n * its packages have no independent versions to bump: a consumer running\n * `go get` on one of them resolves against the *module's* tag, so a\n * per-project version would be a fiction. Go's \"publishing\" is that\n * repo-level tag, which is not a per-project release concern.\n *\n * A `go-app` is the opposite case, and is released only when it opts in: an\n * executable has its own version, the one its users download. `tag:release:go`\n * selects it, and the app's own `project.json` carries the version config that\n * keeps Nx from looking for a `package.json` (see `GO_RELEASE_SCRIPT`), so it\n * does not reintroduce the abort `go-lib` hit. `!tag:type:go-lib` is unaffected: an\n * app is never tagged `type:go-lib`.\n *\n * @param ci - Which CI provider(s) the workspace generates a pipeline for —\n * only `'github'` (GitHub Actions and nothing else) turns on GitHub Release\n * creation; see the remarks above for why `'azure'` and `'both'` do not.\n * @returns The object to merge onto `nx.json`'s `release` key.\n * @throws Never - builds a plain object with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function releaseConfig (ci: CiProvider): Record<string, unknown> {\n const githubReleases = ci === 'github'\n\n return {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*', `tag:${VSCODE_EXTENSION_TAG}`, `tag:${GO_RELEASE_TAG}`, '!tag:type:go-lib'],\n releaseTag: { pattern: '{projectName}@{version}' },\n git: { commit: false, tag: true, push: githubReleases },\n version: {\n conventionalCommits: true,\n fallbackCurrentVersionResolver: 'disk',\n // Build only what is being released. Without this, @nx/js:lib's generator\n // defaults the pre-version command to building EVERY project, so a broken\n // (or merely slow) app build would block releasing unrelated packages.\n // Set here at `new` time it wins: the generator only fills this in when\n // absent (it spreads the existing release.version over its default). Both\n // globs are listed; `nx run-many` no-ops cleanly when one matches nothing.\n preVersionCommand: `npx nx run-many -t build --projects=packages/*,python-packages/*,tag:${VSCODE_EXTENSION_TAG}`,\n // The lock file resync is OFF, and the reason is that it cannot succeed\n // on the one release where it would matter.\n //\n // It is not `preVersionCommand` that does this, despite the name it gets\n // reported under: `@nx/js`'s `afterAllProjectsVersioned` hook shells\n // `npm install --package-lock-only` once every project has been versioned.\n // That command resolves the whole tree against the registry, so the first\n // release of two NEW interdependent packages 404s — package A declares a\n // dependency on package B at the version this very run is about to\n // publish, and B does not exist on the registry yet. Measured on a real\n // workspace releasing `@scope/cli` and `@scope/studio` together.\n //\n // Turning it off costs nothing here, because mnci releases are tag-only:\n // `git.commit` is `false`, so the refreshed lock file is written into an\n // ephemeral CI checkout and then thrown away without ever being committed.\n // It was never protecting anything. That it goes stale is not a new\n // condition either — every manifest on disk is already stale by design for\n // exactly the same reason, which is why `fallbackCurrentVersionResolver`\n // is `'disk'` and why a tagless clone makes a dry run silently wrong.\n //\n // A workspace that DOES want the lock file refreshed should commit the\n // bump too (`git.commit: true`), at which point this can be dropped.\n versionActionsOptions: { skipLockFileUpdate: true },\n },\n changelog: githubReleases\n ? {\n workspaceChangelog: false,\n projectChangelogs: { createRelease: 'github', file: false },\n // A GitHub Release needs a changelog, and a changelog needs a ref to start\n // from. On the first release of a repository there is no tag to start from,\n // and nx release dies after versioning (#243, measured). With this set it\n // starts from the first commit; once a tag exists it is never consulted.\n automaticFromRef: true,\n }\n : { workspaceChangelog: false },\n } as const\n}\n\n/**\n * The `sync` block merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * `--preset=ts` already registers `@nx/js:typescript-sync` on the `build` and\n * `typecheck` targets (via the `@nx/js/typescript` plugin), so Nx already\n * detects a stale TypeScript project reference — e.g. after hand-editing a\n * file to add a new cross-project import — on the next `build`/`typecheck`/\n * `affected` run. Without this block that detection only **prompts**\n * (\"Would you like to sync the identified changes?\"): easy to miss, and it\n * blocks any non-interactive run. `applyChanges: true` makes Nx fix it\n * silently instead, locally, every time — no more `nx sync` run by hand.\n *\n * This is deliberately safe in CI: per Nx's own docs, a non-interactive run\n * (CI) always runs sync generators in dry-run mode and **fails** instead of\n * applying, regardless of this setting — so a forgotten local sync still\n * surfaces as a clear CI failure ({@link azurePipelinesYaml}'s explicit\n * `nx sync:check` step gives that failure early and unambiguously) rather\n * than silently patching an ephemeral CI checkout that never gets committed.\n */\nexport const SYNC_CONFIG = { applyChanges: true } as const\n\n/**\n * The `@nx/eslint/plugin` registration merged into a generated workspace's `nx.json`.\n *\n * @remarks\n * This plugin is what gives every project its `lint` target: it maps ESLint\n * config *directories* onto the project roots beneath them, so the single root\n * config mnci writes covers the whole workspace and no project needs one of its\n * own.\n *\n * mnci registers it because mnci owns linting. Nx would otherwise add it as a\n * side effect of the first `nx g … --linter=eslint`, which is both invisible\n * and no longer true: the generators are invoked with `--linter=none` (see\n * `add/shared.ts`), precisely so they stop scaffolding a per-project config —\n * and, more pressingly, stop dragging in `eslint-plugin-import@2.31.0`, whose\n * peer range caps at ESLint 9 and made `mnci add react-app` fail outright on\n * the ESLint 10 toolchain this workspace installs.\n *\n * A consequence worth stating: `npm run lint` now works in a workspace with\n * zero projects, which it previously did only by accident.\n */\nexport const ESLINT_PLUGIN_CONFIG = {\n plugin: '@nx/eslint/plugin',\n options: { targetName: 'lint' },\n} as const\n\n/**\n * The plugin name of an `nx.json` `plugins` entry.\n *\n * @remarks\n * Nx accepts both the bare-string form (`\"@nx/eslint/plugin\"`) and the object\n * form (`{ plugin, options }`), and a real workspace can hold a mix.\n *\n * @param entry - One element of `nx.json`'s `plugins` array.\n * @returns The plugin name, or `undefined` for an entry in neither form.\n * @throws Never - pure property read.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pluginName (entry: unknown): string | undefined {\n return typeof entry === 'string' ? entry : (entry as { plugin?: string }).plugin\n}\n\n/**\n * Returns a copy of an `nx.json` object with `@nx/eslint/plugin` registered.\n *\n * @remarks\n * Idempotent, so `mnci upgrade` cannot accumulate duplicate entries — and a\n * workspace where Nx already added the plugin (generated before this existed)\n * keeps its own entry, options included, rather than having them overwritten.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns A new object whose `plugins` array contains the ESLint plugin.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withEslintPlugin (nxJson: Record<string, unknown>): Record<string, unknown> {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n const registered = plugins.some(entry => pluginName(entry) === ESLINT_PLUGIN_CONFIG.plugin)\n\n return registered\n ? { ...nxJson, plugins }\n : { ...nxJson, plugins: [...plugins, ESLINT_PLUGIN_CONFIG] }\n}\n\n/**\n * The workspace-wide files every project's verification depends on.\n *\n * @remarks\n * `nx affected` walks the project graph, and a root config file is in no\n * project — so without this, changing one marks only the root pseudo-project,\n * which has no `lint`/`typecheck`/`test`/`build` target. Measured on a real\n * workspace before the fix: a PR touching `tsconfig.base.json` alone verified\n * **nothing at all** and reported green.\n *\n * Each entry is a file that can change every project's result:\n * `eslint.config.mjs` is the whole linting opinion, `tsconfig.base.json` is\n * what every project's tsconfig extends, and the root `package.json` holds\n * every devDependency version and the curated scripts.\n *\n * BOTH ESLint files are listed. `eslint.config.mnci.mjs` carries every rule and\n * `eslint.config.mjs` is the thin file that imports it and holds the\n * workspace's own blocks — either can change every project's result, and\n * listing only one would make a change to the other invisible to\n * `nx affected`, which is the exact failure this input exists to prevent.\n *\n * No formatter config is listed because none exists: ESLint is the formatter,\n * and `eslint.config.mjs` is already the first entry. When Prettier owned\n * formatting, `.prettierrc.json` was excluded on the grounds that\n * `format:check` swept the whole tree on every run regardless, so listing it\n * would invalidate every project's cache and verify nothing new. Both the file\n * and that step are gone. `package-lock.json` is absent too — Nx already\n * marks projects affected from lockfile changes through its external-dependency\n * nodes (verified: a lockfile-only edit marks every project).\n */\nexport const SHARED_GLOBAL_INPUTS = [\n '{workspaceRoot}/eslint.config.mjs',\n '{workspaceRoot}/eslint.config.mnci.mjs',\n '{workspaceRoot}/tsconfig.base.json',\n '{workspaceRoot}/package.json',\n] as const\n\n/**\n * Returns a copy of an `nx.json` object whose `sharedGlobals` named input\n * covers the mnci-owned root config files.\n *\n * @remarks\n * Idempotent and additive: entries a workspace already has are kept in place\n * and never duplicated, so `mnci upgrade` can run repeatedly and a workspace\n * that added its own shared globals does not lose them. `sharedGlobals` is\n * referenced by the preset's `default` input, which `production` extends, so\n * one list reaches every target.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns A new object whose `namedInputs.sharedGlobals` includes every entry\n * of {@link SHARED_GLOBAL_INPUTS}.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withSharedGlobals (nxJson: Record<string, unknown>): Record<string, unknown> {\n const namedInputs = (nxJson.namedInputs as Record<string, unknown> | undefined) ?? {}\n const existing = (namedInputs.sharedGlobals as unknown[] | undefined) ?? []\n const missing = SHARED_GLOBAL_INPUTS.filter(entry => !existing.includes(entry))\n\n return {\n ...nxJson,\n namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] },\n }\n}\n\n/**\n * The `nx.json` `plugins` entry for `@mnci/nx-python-pip`'s dependency graph.\n *\n * @remarks\n * A bare string rather than the `{ plugin, options }` form: it takes no\n * options, and the string form is what Nx's own docs show for a plugin that\n * contributes only `createDependencies`.\n */\nexport const PYTHON_GRAPH_PLUGIN = '@mnci/nx-python-pip/graph'\n\n/**\n * Returns a copy of an `nx.json` object with the Python graph plugin\n * registered, when the workspace actually has the plugin.\n *\n * @remarks\n * The GATE is the point, and it is not a nicety. Nx resolves every entry in\n * `plugins` while constructing the project graph, and a name it cannot resolve\n * is a hard failure - so registering this unconditionally would break every\n * generated workspace that has no Python in it, which is most of them. The\n * plugin arrives only when `mnci add python-*` installs it, so that is the\n * signal this reads.\n *\n * Without the registration the plugin still supplies generators, executors and\n * release actions - those resolve by plain module lookup through\n * `generators.json`/`executors.json` - but contributes NO graph edges, because\n * `createDependencies` is only called for a registered plugin. That is the\n * difference between `nx affected` knowing a Python library's consumers and\n * silently testing none of them.\n *\n * Idempotent and additive, like {@link withEslintPlugin}: a workspace that\n * registered it by hand keeps its own entry, and `mnci upgrade` cannot\n * accumulate duplicates.\n *\n * @param nxJson - The parsed `nx.json`.\n * @param pluginInstalled - Whether the workspace declares\n * `@mnci/nx-python-pip`.\n * @returns A new object, with the plugin registered when it is installed.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withPythonGraphPlugin (\n nxJson: Record<string, unknown>,\n pluginInstalled: boolean,\n): Record<string, unknown> {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n if (!pluginInstalled || plugins.some(entry => pluginName(entry) === PYTHON_GRAPH_PLUGIN)) {\n return nxJson.plugins === undefined ? nxJson : { ...nxJson, plugins }\n }\n\n return { ...nxJson, plugins: [...plugins, PYTHON_GRAPH_PLUGIN] }\n}\n\n/**\n * Narrows to a mergeable object: not null, and not an array.\n *\n * @remarks\n * Arrays are deliberately NOT mergeable. `release.projects` is mnci's own\n * list, and element-wise merging it would leave a workspace unable to drop an\n * entry mnci once emitted.\n *\n * @param value - The value to test.\n * @returns Whether `value` is a plain object.\n * @throws Never - pure predicate.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isMergeableObject (value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Deep-merges mnci's owned config over whatever the workspace already had,\n * so keys mnci does not emit survive.\n *\n * @remarks\n * mnci wins for every key it emits — those encode the tag-only release model,\n * and a workspace silently flipping `git.commit` to `true` would be a worse\n * bug than the one this fixes. Everything else is the workspace's and is kept.\n *\n * The trade, stated: a key mnci USED to emit and no longer does now lingers\n * instead of being cleaned up. That is the right side to err on - a stale key\n * nx ignores costs nothing, and deleting a key the workspace meant to keep has\n * already cost a repo two days of silently failing releases.\n *\n * @param existing - The workspace's current value, of any shape.\n * @param owned - The config mnci generates.\n * @returns The merged object.\n * @throws Never - pure recursive merge.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction preservingUnknownKeys (\n existing: unknown,\n owned: Record<string, unknown>,\n): Record<string, unknown> {\n if (!isMergeableObject(existing)) {\n return owned\n }\n\n const merged: Record<string, unknown> = { ...existing }\n for (const [key, value] of Object.entries(owned)) {\n const previous = merged[key]\n merged[key] =\n isMergeableObject(value) && isMergeableObject(previous)\n ? preservingUnknownKeys(previous, value)\n : value\n }\n\n return merged\n}\n\n/**\n * Returns a copy of an `nx.json` object with the release block applied.\n *\n * @remarks\n * Pure read-modify-write on the object the Nx preset generated — this never\n * templates whole config files, it only patches in the one opinion Nx has no\n * default for.\n *\n * The release block is **merged, not replaced**, and that distinction is the\n * whole point. It used to be `release: releaseConfig(ci)`, which meant every\n * `mnci upgrade` deleted any key a workspace had added under `release` — and\n * `release` is the one block a workspace legitimately has to extend, because\n * nx exposes settings there that mnci has no opinion about and cannot\n * enumerate in advance.\n *\n * Observed, not hypothetical. A workspace had deliberately set\n * `version.preserveMatchingDependencyRanges: false` - without it nx REFUSES to\n * release when an internal dependency range cannot absorb the bump, which\n * under this tag-only model is always, since `git.commit` is `false` and the\n * on-disk manifests are permanently the scaffold version. An upgrade dropped\n * it, and every release from that day on failed in the version phase. Nothing\n * else noticed: lint, typecheck, test and build all stayed green, because the\n * only broken path was the release, and that runs on the default branch alone.\n *\n * `sharedGlobals` ({@link withSharedGlobals}) and `plugins`\n * ({@link withEslintPlugin}) were already additive, and `generators`, `sync`\n * and `mnci` are all spread-merged where `nx.json` is written. `release` was\n * the one wholesale replacement, and the only one whose value nests, so a\n * shallow spread would still have wiped `version.*`.\n *\n * @param nxJson - The parsed `nx.json` produced by `create-nx-workspace`.\n * @param ci - Which CI provider(s) the workspace generates a pipeline for —\n * forwarded to {@link releaseConfig} to decide whether GitHub Release\n * creation is turned on.\n * @returns A new object with `release` (and `defaultBase: 'main'`) set.\n * @throws Never - performs a pure object merge with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withReleaseConfig (\n nxJson: Record<string, unknown>,\n ci: CiProvider,\n): Record<string, unknown> {\n return {\n ...nxJson,\n defaultBase: 'main',\n release: preservingUnknownKeys(nxJson.release, releaseConfig(ci)),\n }\n}\n\n/**\n * The commitlint config written into generated workspaces.\n *\n * @remarks\n * Conventional commits are the release mechanism's input, so they are\n * enforced at commit time — the one piece Nx itself does not provide.\n */\nexport const COMMITLINT_CONFIG = `export default { extends: ['@commitlint/config-conventional'] }\n`\n\n/**\n * The husky `commit-msg` hook body that runs commitlint.\n *\n * @remarks\n * `--no` keeps npx from installing anything at commit time — commitlint is a\n * devDependency installed by `mnci new`.\n */\nexport const COMMIT_MSG_HOOK = `npx --no -- commitlint --edit \"$1\"\n`\n\n/**\n * The `@mnci/eslint-config` version generated workspaces depend on.\n *\n * @remarks\n * NOT a caret range, deliberately — `^` on a pre-1.0 package is minor-locked:\n * `^0.1.0` means `>=0.1.0 <0.2.0`, so `npm update` can never cross into 0.2.0\n * or later. That defeats the one reason this ships as a package rather than a\n * template string in this file: \"`npm update` carries lint-rule improvements\n * into existing workspaces without an `mnci upgrade`\" was never actually true\n * under `^0.1.0`. Found in a real generated workspace still declaring\n * `^0.1.0` after 20+ published releases — `mnci upgrade` was also stamping\n * this constant back over any manual bump on every run (see\n * {@link eslintToolchainDependencies}'s call site), so there was no way out\n * except editing the range by hand and never running `mnci upgrade` again.\n *\n * `>=0.1.0 <1.0.0` floats across every 0.x release without an upper bound\n * inside that line, while still requiring an explicit bump of this constant\n * (an intentional `mnci upgrade`-worthy decision) to cross into a stable 1.0.\n */\nexport const ESLINT_CONFIG_VERSION = '>=0.1.0 <1.0.0'\n\n/**\n * The `@mnci/eslint-config` spec to write into a generated workspace's manifest.\n *\n * @remarks\n * Reads `MNCI_ESLINT_CONFIG_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `packages/eslint-config`) instead of the published\n * registry package — without it, `npm install` in a freshly generated workspace\n * 404s until the package has been released at least once. Same escape hatch\n * `add/python.ts` and `add/flutter.ts` already use for their plugins\n * (`MNCI2_PYTHON_PIP_SPEC`, `MNCI_NX_FLUTTER_SPEC`).\n *\n * {@link ESLINT_CONFIG_VERSION} is the default and the only value a real\n * `mnci new` ever writes.\n *\n * @returns The dependency spec (a semver range, or a path/URL when overridden).\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function eslintConfigSpec (): string {\n return process.env.MNCI_ESLINT_CONFIG_SPEC ?? ESLINT_CONFIG_VERSION\n}\n\n/**\n * The `eslint` version generated workspaces depend on.\n *\n * @remarks\n * `@mnci/eslint-config` peers on `eslint`, so it never installs one itself.\n *\n * ESLint **10**. What held the stack at 9 was never ESLint — it was\n * `eslint-plugin-react`, which has no 10 release at all and has since been\n * replaced by `@eslint-react/eslint-plugin`. See\n * {@link ESLINT_PEER_OVERRIDES} for the one holdout that remains.\n */\nexport const ESLINT_VERSION = '^10.8.0'\n\n/**\n * The `.devcontainer/devcontainer.json` written into generated workspaces.\n *\n * @remarks\n * mnci's toolchain matrix is Node + Python + Go + Flutter, and until this existed\n * only **CI** had all four: the pipeline installs the Flutter SDK itself and\n * assumes CPython and Go are on the agent, while locally a contributor was on\n * their own. A devcontainer is what makes the local environment the same one CI\n * verifies, which is the whole \"just works\" promise applied to development rather\n * than to the build.\n *\n * Four decisions worth keeping:\n *\n * - **The Node major comes from {@link NODE_VERSION}**, the same constant the\n * workflow's `setup-node` step reads. Hardcoding it twice is exactly the drift\n * this file is supposed to remove.\n * - **`postCreateCommand` reuses the pipeline's own guards** rather than\n * reimplementing them — {@link PYTHON_INSTALL_GUARD} and friends via the\n * `python:install` root script, plus the same `golangci-lint` and Flutter SDK\n * one-liners the pipelines run. Each is already idempotent and already\n * no-ops when the workspace has no project of that kind (no `go.mod`, no\n * `pubspec.yaml`), so a JS-only workspace pays almost nothing and a polyglot\n * one gets exactly what CI gets. Reimplementing them would create a third\n * copy to keep in sync.\n * - **Go, Python and .NET arrive as devcontainer *features*, not as a custom\n * image.** A Dockerfile would be a second thing to maintain against\n * upstream, and features are the mechanism the ecosystem maintains for\n * precisely this. Unlike CI's install steps, a feature runs once at\n * container build time rather than on every job, so there is no reason to\n * gate it on the workspace actually having a project of that kind — Python\n * and Go have never been gated either.\n * - **`ghcr.io/devcontainers/features/dotnet:2`'s `version` option takes\n * `X.Y`/`X.Y.Z`, not {@link DOTNET_SDK_VERSION}'s `X.Y.x` verbatim** — that\n * suffix is specifically what `actions/setup-dotnet` and `UseDotNet@2`\n * expect (see its remarks), and the feature's own schema (checked directly\n * against `devcontainer-feature.json`, proposals `latest`/`lts`/`10.0`/…)\n * documents no `.x` form at all. The `.x` is stripped rather than a second\n * constant maintained in parallel, so there is still exactly one source of\n * truth to bump.\n * - **Flutter is NOT a feature**, because no maintained one exists — the same\n * reason `@mnci/nx-flutter` had to be written. The SDK guard clones a pinned\n * tag into the home directory, which is what CI does, so the version matches\n * by construction.\n *\n * @param workspaceName - The workspace name, used as the container's label.\n * @returns The JSON string for `.devcontainer/devcontainer.json`.\n * @throws Never - performs pure string formatting with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function devcontainerJson (workspaceName: string): string {\n return `${toJson({\n name: workspaceName,\n image: `mcr.microsoft.com/devcontainers/typescript-node:${NODE_VERSION}-bookworm`,\n features: {\n 'ghcr.io/devcontainers/features/python:1': { version: '3.12' },\n 'ghcr.io/devcontainers/features/go:1': { version: 'latest' },\n 'ghcr.io/devcontainers/features/dotnet:2': { version: DOTNET_SDK_VERSION.replace(/\\.x$/, '') },\n },\n // `npm ci` first: every guard after it runs through the workspace's own\n // scripts and Nx, which do not exist until the install completes.\n postCreateCommand: [\n // Same pin the workflow applies, for the same reason — a container whose\n // npm major differs from CI's resolves dependencies differently, which is\n // precisely the drift this file exists to remove.\n `npm install -g npm@${NPM_VERSION}`,\n 'npm ci',\n 'npm run python:install',\n GOLANGCI_LINT_INSTALL_GUARD,\n FLUTTER_SDK_INSTALL_GUARD,\n ].join(' && '),\n // The same recommendations the `.code-workspace` file carries, so opening\n // the folder in a container suggests the identical toolset.\n customizations: {\n vscode: { extensions: VSCODE_RECOMMENDED_EXTENSIONS },\n },\n })}\\n`\n}\n\n/**\n * The `lint` target mnci puts on a generated workspace's ROOT project.\n *\n * @remarks\n * Without it, root-level files are linted by **nothing**. `npm run lint` is\n * `nx run-many -t lint`, and every other `lint` target belongs to a project and\n * runs `eslint .` with that project as its cwd — so `.github/workflows/*.yml`,\n * `azure-pipelines.yml`, the root JSON and Markdown, `eslint.config.mjs` and\n * `commitlint.config.mjs` were covered by no target at all.\n *\n * **The ignore patterns are CLI flags on purpose, not config `ignores`.** In flat\n * config, `ignores` are relative to the config file, and every project's `lint`\n * resolves this same root `eslint.config.mjs` — so ignoring `packages/**` there\n * would switch linting off *inside* the packages too. A CLI flag applies to this\n * invocation alone. Each project already lints its own tree, so this target adds\n * coverage rather than duplicating it.\n *\n * `includedScripts: []` goes alongside it, and is load-bearing: the root manifest's\n * scripts are the `nx run-many` aggregators, so letting Nx infer targets from them\n * would make `lint` invoke `nx run-many -t lint` — itself.\n */\nexport const ROOT_LINT_TARGET = {\n executor: 'nx:run-commands',\n cache: true,\n options: {\n command: [\n // `--cache` is the one ESLint speed option that actually pays here:\n // measured best-of-3 on this repo, an unchanged re-run drops from 9,546ms\n // to 2,835ms (3.4x). `--concurrency=auto` was measured too and REJECTED —\n // it came out 8% SLOWER, because per-worker TypeScript type-service\n // startup costs more than the parallelism saves. The cache file is\n // per-machine and gitignored.\n 'eslint . --cache',\n '--ignore-pattern \"apps/**\"',\n '--ignore-pattern \"libs/**\"',\n '--ignore-pattern \"packages/**\"',\n '--ignore-pattern \"python-packages/**\"',\n '--ignore-pattern package-lock.json',\n ].join(' '),\n cwd: '.',\n },\n} as const\n\n/**\n * npm `overrides` for `@nx/*` peer ranges that are narrower than what they support.\n *\n * @remarks\n * `@nx/react@23.1.2` **added** `express: '^4.21.2'` as an optional peer in a\n * PATCH release — 23.1.0 and 23.1.1 declare no express peer at all. mnci's own\n * `node-app --framework express` installs express 5, so the two are\n * irreconcilable and `npm install` fails outright:\n *\n * ```\n * npm error Could not resolve dependency:\n * npm error peerOptional express@\"^4.21.2\" from @nx/react@23.2.1\n * npm error Conflicting peer dependency: express@4.22.3\n * ```\n *\n * `optional: true` is the trap: it means \"you needn't install it\", NOT \"any\n * version is fine if you do\". Once express is present the range is enforced.\n *\n * **The range is `@nx/node`'s own, not a number invented here.**\n * `@nx/node@23.2.1` declares `peerOptional express@'>=4.0.0 <6.0.0'`, and\n * `@nx/node` is the package that actually scaffolds the express app. So the\n * override says: resolve `@nx/react`'s express peer to the range the Nx\n * package that owns express already supports. Both majors satisfy it.\n *\n * **Unconditional, and that is the fix.** This used to be a function returning\n * `{ '@nx/react': { express: '$express' } }` only when the root manifest already\n * declared express, because `$express` is a dangling reference otherwise. Every\n * value was measured against a real workspace:\n *\n * | override value | express 5 | express 4 | react-only |\n * | -------------------- | --------- | --------- | ---------- |\n * | `'$express'` | works | works | **FAILS** — `Unable to resolve reference $express` |\n * | `'*'` | FAILS | works | works |\n * | `'^5.1.0'` | works | FAILS | works |\n * | `'>=4.0.0 <6.0.0'` | works | works | works |\n *\n * The last row is why this can be static, and being static is the whole point.\n * The conditional form could only be written **after** a generator had put\n * express in the manifest — but `nx g @nx/node:application --framework=express`\n * adds express and runs `npm install` in the SAME invocation, so the conflict\n * fired inside that generator, before any mnci post-generation step could write\n * the override. The old comment said \"the very next add would otherwise be the\n * one that fails\"; measured, it is THAT add that fails. A static entry written\n * at `mnci new` is already in place before any generator installs anything, and\n * it covers the reverse order too — a react-app added to a workspace that\n * already has express, where `nx add @nx/react` is the install that would die.\n *\n * Nothing in a generated workspace uses `@nx/react`'s express peer: react apps\n * build with Vite, and the peer exists for Nx's own module-federation/SSR\n * dev-server path, which mnci does not scaffold.\n */\nexport const NX_PEER_OVERRIDES = {\n '@nx/react': { express: '>=4.0.0 <6.0.0' },\n} as const\n\n/**\n * npm `overrides` a generated workspace needs for its ESLint toolchain to install.\n *\n * @remarks\n * `eslint-plugin-jsx-a11y@6.10.2` — the latest release — peers on\n * `eslint: ^3 … ^9`, so `npm install` fails outright with `ERESOLVE` on ESLint\n * 10. That cap is **stale rather than real**, and this was measured, not\n * assumed: with this override in place on `eslint@10.8.0`, the plugin installs\n * and its rules work — a missing `alt` reports `jsx-a11y/alt-text`, and\n * `alt=\"a picture\"` reports `img-redundant-alt`.\n *\n * `overrides` has to live in the **root** manifest; npm ignores it anywhere\n * else, which is why a config package cannot carry its own fix and mnci writes\n * this instead. `$eslint` resolves to the workspace's own `eslint` spec, so the\n * override never pins a version of its own.\n *\n * The trade, stated rather than glossed: mnci deleted `legacy-peer-deps` from\n * the generated `.npmrc` precisely for weakening dependency resolution. This is\n * far narrower — one named package, one peer, with evidence that the real\n * constraint is satisfied — but it is the same kind of decision, so it should\n * be removed the moment `jsx-a11y` ships a release declaring ESLint 10.\n *\n * **`nx` → `brace-expansion` is a security fix, not a resolution fix, and it is\n * here because a generated workspace shipped six HIGH advisories without it.**\n * Measured by the audit step running inside a freshly generated workspace for the\n * first time: `brace-expansion` carries a high advisory, and `nx`, `@nx/js`,\n * `@nx/eslint`, `@nx/eslint-plugin` and `@nx/workspace` all inherit it. npm\n * reports the fix as **semver-major**, meaning `npm audit fix` would try to bump\n * `nx` itself — unacceptable in a scaffold — while a targeted override on the one\n * vulnerable transitive is both sufficient and safe.\n *\n * This repo has carried exactly this override for its own tree the whole time,\n * which is why its audit reads 0 while generated workspaces read 6. Same\n * dogfooding drift as the missing audit step: fixed here, never shipped. That is\n * the argument for the audit step being blocking — it found this the first time\n * it ever ran somewhere that mattered.\n *\n * **`nx` → `smol-toml` is the same drift, recurring.** `smol-toml <=1.7.0`\n * (GHSA-7w5x-hrqm-74c2, a parser DoS on malformed TOML) is a second instance of\n * \"nx's own transitive dependency\", fixed on this repo's own tree by nesting\n * `smol-toml` under the SAME `nx` entry already here for `brace-expansion` — and,\n * exactly like that fix, never carried over to what `mnci new` ships. A generated\n * workspace's own audit step caught it the moment C#/.NET support shipped an e2e\n * failure into the `flutter` section too: the SDK-resolution crash the broken\n * `nuget.config` XML caused (fixed separately, see `nugetConfigContent`) corrupted\n * the whole run's project graph, but the npm audit block on a *fresh* `demo`\n * workspace — generated **before** any C# kind ever touches `nuget.config` — was\n * failing independently, for this reason alone. Unlike `brace-expansion`, ONE\n * entry nested under `nx` is enough: `smol-toml` is a dependency of `nx` itself,\n * not independently required by each `@nx/*` package the audit names, so there is\n * no sibling edge for a per-parent override to reach — verified by running the\n * real `NPM_AUDIT_STEP` against a workspace carrying only this one entry.\n *\n * **`nx` → `axios` is the third instance, on the same day mnci fixed it for\n * itself.** `nx@23.2.x` depends on `axios ^1.18.1`, and every `axios` below\n * `1.20.0` carries twelve high advisories (prototype-pollution gadgets, ReDoS,\n * proxy and redirect bypasses; the widest, GHSA-9fr6-4gfg-395g and\n * GHSA-j8rh-479h-cp32, cover `>=1.0.0 <1.20.0`). This repo's own lockfile moved\n * to `1.20.0` in a8a27bb; a workspace generated an hour later still resolved\n * `1.18.1` and failed its first CI run, with `nx` and four `@nx/*` packages\n * flagged only for inheriting it. `axios` is reached through `nx` alone (in a\n * fresh workspace, `npm ls axios` shows one path), so it nests under the `nx`\n * entry exactly as `smol-toml` does. Found bootstrapping Lore Master\n * (russoedu/MarkDoc). Check `npm ls axios` before assuming a second path ever\n * appears, and drop the entry once the `nx` mnci installs requires `^1.20.0`.\n */\nexport const ESLINT_PEER_OVERRIDES = {\n 'eslint-plugin-jsx-a11y': { eslint: '$eslint' },\n // Scoped to EACH named parent, not top-level and not `nx` alone.\n //\n // `nx` alone was the first attempt and it did not work: the e2e's audit step\n // still reported all six advisories in a freshly generated workspace, because\n // `@nx/js`, `@nx/eslint`, `@nx/eslint-plugin` and `@nx/workspace` are their own\n // dependents there, and an override scoped to `nx` never reaches a sibling's\n // dependencies. Those four are exactly the packages npm audit named.\n //\n // Top-level would be worse than wrong. A tree with `minimatch@3` legitimately\n // carries `brace-expansion@1.1.18` (and `@2.1.4` elsewhere) — this repo has\n // both — and forcing those to v5 breaks them. So the blast radius is one\n // dependency edge per named parent, and a test asserts there is no top-level\n // entry.\n 'nx': { 'brace-expansion': '^5.0.9', 'smol-toml': '^1.7.1', 'axios': '^1.20.0' },\n '@nx/js': { 'brace-expansion': '^5.0.9' },\n '@nx/eslint': { 'brace-expansion': '^5.0.9' },\n '@nx/eslint-plugin': { 'brace-expansion': '^5.0.9' },\n '@nx/workspace': { 'brace-expansion': '^5.0.9' },\n // `postcss` → `nanoid` reaches a generated workspace through `@nx/rollup`,\n // which every publishable `npm-lib` gets. GHSA-2v37-7h3g-55p8 is high, fixed in\n // 3.3.18, and the fix is a patch — so unlike the entry above this one is not\n // even a trade, just a version nobody had bumped yet.\n 'postcss': { nanoid: '^3.3.18' },\n} as const\n\n/**\n * Security overrides a generated workspace needs to pass its OWN npm audit gate.\n *\n * @remarks\n * Measured, not anticipated: a workspace straight out of `mnci new` plus one\n * `mnci add npm-lib` reported **8 high-severity advisories with zero lines of\n * user code**, and every one of them is actionable, so `NPM_AUDIT_STEP` exits 1\n * and CI is red on the first push. A scaffold that cannot pass the gate it\n * ships is the worst version of that gate — it teaches people to switch it off.\n *\n * All 8 are one advisory. `brace-expansion` GHSA-rgw5-rvv9-x895 (DoS via\n * unbounded intermediate arrays) covers `4.0.0 - 5.0.8`; `nx` and six `@nx/*`\n * packages are flagged only for depending on it. npm's own suggested remedy is\n * `nx@22.6.5` marked `isSemVerMajor` — a **downgrade of the build tool** by a\n * major, which nobody would take, and which is exactly why the audit step\n * reports `fixAvailable` rather than running `npm audit fix`.\n *\n * Two details make this override safe rather than a blunt pin:\n *\n * - It is keyed by the **vulnerable range**, not the bare package name. A plain\n * `\"brace-expansion\": \"^5.0.9\"` would drag the `1.1.18` and `2.1.4` copies\n * also present in the tree up to 5.x across a major API change, to fix an\n * advisory neither of them has. Keyed this way, `npm install` removed exactly\n * one package and left both others alone (verified).\n * - The fixed version was already resolved elsewhere in the same tree, so this\n * deduplicates onto something npm had installed anyway rather than\n * introducing a version nothing else uses.\n *\n * **Remove it when it stops being needed, and check rather than assume.** The\n * precedent is this repo's own `@verdaccio/config`/`js-yaml` pin, which read as\n * fixed while the advisory range had quietly been extended to include the\n * pinned version. The condition here is narrow: once the `nx` release this\n * scaffold installs no longer resolves a `brace-expansion` inside the\n * vulnerable range, this entry is dead weight. `npm audit` on a freshly\n * generated workspace is the check, and it is the only one that means anything.\n */\nexport const SECURITY_OVERRIDES = {\n 'brace-expansion@4.0.0 - 5.0.8': '^5.0.9',\n} as const\n\n/**\n * The ESLint toolchain a generated workspace needs as real devDependencies.\n *\n * @remarks\n * Declaring these is load-bearing, and the reason is easy to miss: `eslint`\n * ends up in `node_modules` anyway (hoisted via `@mnci/eslint-config`), but\n * Nx's generators resolve it from the workspace **manifest**, not from disk.\n * Without the declaration, `mnci add npm-lib` fails outright with \"Unable to\n * find `eslint`. Ensure a valid `eslint` version is installed\" — verified\n * against a real generated workspace.\n *\n * `create-nx-workspace --preset=ts` does not install any of these; they used\n * to arrive incidentally, whenever the first `nx add @nx/react`-style plugin\n * install happened to pull them in. Now that mnci owns the root ESLint config\n * it owns the toolchain that config needs, rather than relying on that\n * accident.\n *\n * `@nx/eslint` (the `lint` target's executor and the inference plugin) and\n * `@nx/eslint-plugin` (the `@nx/dependency-checks` rule) are pinned to the\n * workspace's own Nx version — a mismatched pair breaks target inference.\n *\n * **The formatter is deliberately not here.** Every entry above is shared by\n * both linter modes — the hybrid keeps ESLint for the file types oxlint cannot\n * parse — whereas the formatter is exactly what the choice swaps. It is picked\n * in one place, at the call site, next to {@link oxlintToolchainDependencies}\n * and {@link LINTER_ONLY_DEPENDENCIES}.\n *\n * @param nxVersion - The `nx` version already in the workspace manifest.\n * @returns The devDependency entries to merge in.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function eslintToolchainDependencies (nxVersion: string): Record<string, string> {\n return {\n 'eslint': ESLINT_VERSION,\n '@nx/eslint': nxVersion,\n '@nx/eslint-plugin': nxVersion,\n '@mnci/eslint-config': eslintConfigSpec(),\n }\n}\n\n/**\n * Config files a previous mnci version wrote for a second tool, now deleted.\n *\n * @remarks\n * mnci has shipped three formatter arrangements: Prettier (`.prettierrc.json`,\n * later `.prettierrc.mjs`), an oxlint/oxfmt mode (`oxlint.config.ts`,\n * `.oxfmtrc.json`), and now none at all — ESLint formats. `applyOverlay`\n * removes every one of these on each run, and that is not tidiness.\n *\n * A leftover config is INERT from the command line, because nothing invokes\n * those binaries any more, and that is exactly what makes it dangerous: a\n * globally installed `esbenp.prettier-vscode` or `oxc.oxc-vscode` still\n * resolves it and still reformats on save. The editor then quietly undoes\n * Standard — no semicolons become semicolons, `function f (a)` loses its space\n * — while `npm run lint` reports nothing, because the file was reformatted\n * after the last check. The `.prettierrc` precedence bug in its third costume.\n */\nexport const RETIRED_FORMATTER_FILES = [\n '.prettierrc',\n '.prettierrc.json',\n '.prettierrc.mjs',\n '.prettierignore',\n 'oxlint.config.ts',\n '.oxfmtrc.json',\n] as const\n\n/**\n * Drops the devDependencies of every formatter and linter mnci has retired.\n *\n * @remarks\n * The declaration is the point, not the install. `@mnci/eslint-config` no\n * longer depends on Prettier, so it leaves `node_modules` on its own — but a\n * workspace generated before the swap still DECLARES it, and the VS Code\n * extension resolves a formatter from the **project's** dependencies. Leaving\n * the declaration is what lets a globally installed extension find a real\n * `prettier` and reformat against an opinion nothing checks.\n *\n * Runs on `mnci upgrade` as well as `mnci new`, because a pre-swap workspace is\n * exactly the case that needs it — a fresh one has none of these to begin with.\n *\n * @param devDeps - The merged devDependencies for the root manifest.\n * @returns The same map without any retired tool.\n * @throws Never - pure object construction.\n */\nexport function withoutRetiredFormatterDependencies (\n devDeps: Record<string, string>,\n): Record<string, string> {\n const retired = new Set([\n 'prettier',\n 'eslint-config-prettier',\n 'oxlint',\n 'oxfmt',\n '@mnci/oxlint-config',\n ])\n\n return Object.fromEntries(Object.entries(devDeps).filter(([name]) => !retired.has(name)))\n}\n\n/**\n * The block-by-block inventory written into the generated `eslint.config.mjs`.\n *\n * @remarks\n * Moving every rule into `@mnci/eslint-config` bought a generated workspace one\n * root config and cost it discoverability: a three-line file gives no hint that\n * twenty tools are behind it, and someone looking at a rule they disagree with\n * has nothing to grep. This names each block, what supplies it, and what it\n * covers — keyed by the `name` every block carries, which is what\n * `eslint --inspect-config` reports and what an override targets.\n *\n * **It is not free text.** A test in `overlay.test.ts` resolves the real config\n * and fails if a name here is missing from it, or if a block in it is missing\n * here. A stale inventory is worse than none, since it sends the reader to a\n * block that no longer exists — and nothing about generating a workspace would\n * notice.\n *\n * A separate constant from {@link ESLINT_MNCI_CONFIG} so that test can pull the\n * `mnci/…` names out of it without also matching the override example further\n * down, which names a `local/…` block that deliberately does not exist.\n *\n * Some entries end in `*`, and that is deliberate rather than lazy:\n * `mnci/yaml/recommended` and `mnci/toml/base` are multi-block upstream presets,\n * and `mnci/typescript`/`mnci/type-aware` each carry a `/declarations` sibling.\n * How many blocks those split into is not a user-facing fact, so enumerating\n * them here would make the table fail on an upstream release that changes\n * nothing anyone cares about.\n */\nexport const ESLINT_BLOCK_INVENTORY = `// WHAT IS IN HERE. Each line is one config block, by the \\`name\\` it carries.\n//\n// mnci/ignores paths never linted (dist, coverage, .venv, …)\n// mnci/base JS/TS correctness — @eslint/js, eslint-plugin-unicorn,\n// -promise, -n, -unused-imports\n// typescript-eslint/* typescript-eslint's own recommended blocks\n// mnci/typescript* TS rules on top of them, no type information needed\n// mnci/type-aware* the rules that DO read types (no-floating-promises and\n// friends), scoped to {apps,libs,packages}/*/src\n// mnci/import-graph import cycles — eslint-plugin-import-x\n// mnci/react JSX/TSX — @eslint-react/eslint-plugin,\n// eslint-plugin-react-hooks, -react-refresh, -jsx-a11y\n// mnci/regexp* regex correctness — eslint-plugin-regexp\n// mnci/json mnci/jsonc mnci/json5\n// eslint-plugin-jsonc — comments are allowed in .jsonc\n// and tsconfig.json, forbidden in plain .json\n// mnci/yaml* eslint-plugin-yml — your CI pipeline files\n// mnci/toml/base* eslint-plugin-toml, PARSER ONLY: a malformed\n// pyproject.toml is a syntax error, nothing is styled\n// mnci/markdown @eslint/markdown\n// mnci/css @eslint/css\n// mnci/html @html-eslint/eslint-plugin\n// mnci/tests *.spec/*.test relaxations — eslint-plugin-jest\n// (Vitest's globals too; the two stacks share them)\n// mnci/tests/mock-aware specs in a project: eslint-plugin-jest's unbound-method,\n// which knows expect(mock.method) has no this to lose\n// mnci/nx-dependency-checks @nx/eslint-plugin, on publishable packages' manifests\n// mnci/standard JavaScript Standard Style as ESLint rules — the\n// whole formatting opinion, a faithful port of\n// neostandard\n// mnci/house-style the deliberate departures from Standard:\n// trailing commas, aligned object values,\n// consistent-as-needed quote-props, a blank line\n// before return. Composed LAST, on purpose:\n// nothing may follow that disables it.\n//\n// To list them as ESLint actually resolves them: npx eslint --inspect-config\n`\n\n/**\n * The mnci-owned half of the ESLint configuration.\n *\n * @remarks\n * Rewritten by every `mnci upgrade`, which is why it is not the file ESLint\n * loads: {@link ESLINT_USER_FILENAME} is, and it imports this one.\n */\nexport const ESLINT_MNCI_FILENAME = 'eslint.config.mnci.mjs'\n\n/**\n * The workspace's own half, and ESLint's actual entry point.\n *\n * @remarks\n * Written once and then never touched again, so a block appended to it\n * survives every upgrade. The name is ESLint's own default rather than a\n * choice — a flat config is found at `eslint.config.mjs`, so the file the tool\n * looks for has to be the one the workspace owns.\n */\nexport const ESLINT_USER_FILENAME = 'eslint.config.mjs'\n\n/**\n * The rules, in the file mnci owns.\n *\n * @remarks\n * This is the half that `mnci upgrade` rewrites, and it is deliberately NOT\n * the file ESLint loads. ESLint's entry point is `eslint.config.mjs`, which\n * imports this one and belongs to the workspace — see {@link ESLINT_USER_CONFIG}\n * for why the two are separate at all.\n *\n * Deliberately one import: every rule lives in `@mnci/eslint-config`, so the\n * twenty-odd plugins are that package's dependencies instead of twenty-odd\n * devDependencies in every generated workspace.\n *\n * `workspaceRoot` enables the `@nx/dependency-checks` block for `packages/*`\n * and `libs/*` — it has to scan for `private: true` manifests, which is why it\n * needs the path rather than deriving one. `import.meta.dirname` is correct\n * here because both ESLint files sit at the workspace root.\n *\n * It exports a FUNCTION rather than the resolved array, and that is the one\n * part of the split that is not obvious. `@mnci/eslint-config` takes options —\n * `verticalSlices` most notably — and a workspace that wants one has nowhere\n * to put it if this file hands back an array it may not edit. mnci's own\n * repository is the proof: it passes `verticalSlices` scoped to the CLI, and\n * the first draft of this split would have had nowhere for that to live.\n *\n * Everything else in the file is comment: {@link ESLINT_BLOCK_INVENTORY}, then\n * the rules about formatters. Both are there because the alternative to\n * documenting a three-line config is a user editing `node_modules`.\n */\nexport const ESLINT_MNCI_CONFIG = `// GENERATED BY mnci. Every \\`mnci upgrade\\` overwrites this file.\n//\n// Do not edit it: put your own blocks in eslint.config.mjs next to it, which\n// imports this one and which mnci never touches once it exists.\nimport mnci from '@mnci/eslint-config'\n\n${ESLINT_BLOCK_INVENTORY}//\n// FORMATTING IS LINTING HERE. There is no Prettier, no oxfmt and no\n// \\`format:check\\` — \\`npm run lint\\` reports indentation, quotes and spacing as\n// ordinary errors, and \\`npm run format\\` is \\`eslint . --fix\\`. So do not add a\n// formatter: whichever one you pick will disagree with the \\`mnci/standard\\`\n// block above, and because a formatter runs on save it wins silently, leaving\n// \\`lint\\` to fail on files you never edited by hand.\n//\n// That also applies to the editor. Installing a Prettier or oxfmt extension is\n// enough on its own — neither needs a config file, and with none present they\n// format against their own defaults (semicolons, double quotes), which is the\n// inverse of Standard.\n//\n// Exported as a FUNCTION, not as the resolved array. @mnci/eslint-config takes\n// options — \\`verticalSlices\\` is the one a workspace is most likely to want —\n// and an already-resolved array has nowhere to receive them. Calling it from\n// eslint.config.mjs is what keeps every option reachable from the file you own.\n//\n// \\`workspaceRoot\\` is resolved HERE because both files sit at the root, and it\n// is what enables the @nx/dependency-checks block.\n//\n// A named function declaration, exported inline. Both shapes matter and both\n// are enforced by this very config: \\`unicorn/no-anonymous-default-export\\`\n// rejects an anonymous arrow, and \\`unicorn/default-export-style\\` rejects\n// declaring one above the export and referring to it. Either mistake fails a\n// generated workspace's own lint on its first run.\nexport default function mnciConfig (options = {}) {\n return mnci({ workspaceRoot: import.meta.dirname, ...options })\n}\n`\n\n/**\n * The workspace's own ESLint config: the file ESLint loads, written ONCE.\n *\n * @remarks\n * The reason this file exists at all is that the previous layout lost work.\n * `eslint.config.mjs` was mnci-owned and rewritten wholesale on every\n * `mnci upgrade`, so any block a workspace had appended to it — the documented\n * way to override a rule, in a comment mnci itself wrote three lines above —\n * was deleted without a word. Worse, `upgrade` then tells the user to run\n * `npm run format`, so the first thing that happens after the rules change is\n * that every file in the repository is rewritten against them.\n *\n * So the file is split. `eslint.config.mnci.mjs` carries the rules and stays\n * mnci's; this one carries the workspace's and is written only when it does\n * not already exist. {@link isUnmodifiedMnciEslintConfig} is what lets an\n * existing workspace cross over safely: an `eslint.config.mjs` still on the old\n * single-file layout is replaced only when it provably holds nothing of the\n * user's, and is otherwise left exactly as it is for `mnci doctor` to report.\n *\n * The spread rather than a re-export is deliberate: `...mnci()` puts the blocks\n * in an array the reader can append to, and appending is the whole point.\n */\nexport const ESLINT_USER_CONFIG = `// This file is YOURS. mnci writes it once and never touches it again, so\n// anything you add here survives \\`mnci upgrade\\`.\n//\n// The rules live in ./eslint.config.mnci.mjs, which mnci DOES rewrite on every\n// upgrade — so put your changes here, not there.\nimport mnci from './${ESLINT_MNCI_FILENAME}'\n\n// TO CONFIGURE the shared rules, pass options to mnci() below — e.g.\n// \\`...mnci({ verticalSlices: ['packages/*/src/**/*.ts'] })\\`.\n//\n// TO OVERRIDE a rule, append a block AFTER the spread — later blocks win, so\n// one of your own beats anything above it. Give it a name, so\n// \\`npx eslint --inspect-config\\` shows where the change came from:\n//\n// {\n// name: 'local/legacy-app-allows-any',\n// files: ['apps/legacy/**/*.ts'],\n// rules: { '@typescript-eslint/no-explicit-any': 'off' }\n// }\n//\n// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it\n// is a dependency, so \\`npm update\\` brings rule fixes in the way it brings any\n// other. An override here survives that; an edit to the package does not.\nexport default [\n ...mnci(),\n]\n`\n\n/**\n * Whether an existing `eslint.config.mjs` is unmodified mnci output.\n *\n * @remarks\n * The question this answers is narrow and the answer has to be certain:\n * *may this file be replaced without losing anything a human wrote?* It is\n * asked once, when a workspace on the old single-file layout is upgraded.\n *\n * So it does not guess. Every comment and blank line is dropped, and what is\n * left has to be exactly the two statements mnci has always generated — the\n * import of `@mnci/eslint-config` and the default export of one call to it.\n * Anything else at all, including a block appended in the documented way,\n * makes this false and the file is left alone.\n *\n * Comments are recognised as lines whose first non-space characters are `//`,\n * which is the only comment form the generated file has ever used. A file\n * using another form simply fails to match, which is the safe direction: the\n * cost of a false negative is a `mnci doctor` finding, and the cost of a false\n * positive is deleted work.\n *\n * @param content - The file's current contents.\n * @returns Whether replacing it can lose nothing.\n * @throws Never - any content that does not match reads as modified.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isUnmodifiedMnciEslintConfig (content: string): boolean {\n const code = content\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line !== '' && !line.startsWith('//'))\n\n return (\n code.length === 2 &&\n code[0] === \"import mnci from '@mnci/eslint-config'\" &&\n code[1] === 'export default mnci({ workspaceRoot: import.meta.dirname })'\n )\n}\n\n/**\n * Writes `eslint.config.mjs` only when doing so cannot lose anything.\n *\n * @remarks\n * Three cases, and they are all the cases there are:\n *\n * - **Absent** — a new workspace, or one that has never had the split. Write\n * it.\n * - **Present and unmodified mnci output** — a workspace on the old\n * single-file layout that never overrode anything. Replacing it moves it\n * onto the split and loses only comments mnci wrote itself, so write it.\n * - **Present and modified** — either already on the split, or on the old\n * layout with the user's own blocks in it. Leave it. `mnci doctor` reports\n * the second of those, because a config that no longer reaches\n * `eslint.config.mnci.mjs` silently lints against nothing but its own blocks.\n *\n * @param workspaceRoot - Absolute path of the workspace root.\n * @param onProgress - Receives one line per file, for the CLI's output.\n * @returns Nothing.\n */\nfunction writeEslintEntryPoint (workspaceRoot: string, onProgress: (line: string) => void): void {\n const path = join(workspaceRoot, ESLINT_USER_FILENAME)\n if (existsSync(path) && !isUnmodifiedMnciEslintConfig(readFileSync(path, 'utf8'))) {\n onProgress(`${ESLINT_USER_FILENAME} — kept as it is, it is yours`)\n\n return\n }\n\n onProgress(`${ESLINT_USER_FILENAME} — the entry point, yours to edit from now on`)\n writeFileEnsured(path, ESLINT_USER_CONFIG)\n}\n\n/**\n * The editor extensions both the `.code-workspace` file and the devcontainer\n * recommend.\n *\n * @remarks\n * Shared so the two cannot drift: opening the workspace in a container should\n * suggest the same toolset as opening it directly.\n *\n * **`esbenp.prettier-vscode` is deliberately absent, and its absence is the\n * point.** mnci recommended it for as long as Prettier owned formatting, and\n * kept recommending it for a while after ESLint took over — which made mnci the\n * thing that installed its own hazard. The extension does not need a config\n * file to act: with none present it formats against Prettier's own defaults,\n * semicolons and double quotes, the exact inverse of Standard. It reformats on\n * save, so the damage lands *after* every gate has run and `lint` stays green\n * until the next time someone looks. {@link RETIRED_FORMATTER_FILES} exists to\n * clean up after precisely that, and recommending the extension alongside it\n * was the two halves of one decision disagreeing.\n */\nexport const VSCODE_RECOMMENDED_EXTENSIONS = [\n 'dbaeumer.vscode-eslint',\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner',\n] as const\n\n/**\n * Every language whose formatter mnci pins to the ESLint extension.\n *\n * @remarks\n * Not a convenience list — see {@link vscodeSettings} for why the general\n * `editor.defaultFormatter` cannot be relied on. Each entry is a language\n * `@mnci/eslint-config` actually has a parser and rules for, so that\n * format-on-save routes to a tool with an opinion about the file rather than to\n * whatever the user happens to have installed.\n *\n * `typescript` and `typescriptreact` are the two that matter most and were once\n * the two missing, which is the bug {@link vscodeSettings} documents.\n *\n * This list used to be justified as \"everything **both** formatters handle\",\n * measured against the real Prettier and oxfmt binaries. Both are retired, so\n * the membership test is now a single question — does the shared ESLint config\n * cover this language — and nothing has to be kept in agreement with a second\n * tool.\n */\nexport const FORMATTED_LANGUAGES = [\n 'javascript',\n 'javascriptreact',\n 'typescript',\n 'typescriptreact',\n 'json',\n 'jsonc',\n 'yaml',\n 'markdown',\n 'css',\n 'html',\n // TOML is here for PARSING, not formatting, and the distinction is measured\n // rather than assumed: `eslint --fix` leaves a badly-laid-out `.toml`\n // byte-identical (the TOML block is `flat/base`, parser only — `flat/standard`\n // reported six errors on the `pyproject.toml` mnci itself generates), but it\n // DOES report `Parsing error: ...` on a malformed one.\n //\n // So the entry buys two things. `eslint.validate` gains toml, so a broken\n // `pyproject.toml` is flagged in the editor rather than at the next CI run.\n // And pinning the formatter to ESLint means format-on-save does nothing to the\n // file instead of handing it to whichever TOML formatter the user happens to\n // have installed — the same reasoning as every other entry here: nothing\n // reformats against an opinion no gate checks.\n //\n // Impossible under the Prettier stack, which is why it is only here now:\n // `npx prettier` on a `.toml` exits with \"No parser could be inferred\".\n 'toml',\n] as const\n\n/**\n * The editor settings that depend on the linter choice.\n *\n * @remarks\n * Two things change together, and they have to: which extension formats, and\n * which languages ESLint is asked to validate.\n *\n * `eslint.validate` lists every language in {@link FORMATTED_LANGUAGES},\n * because ESLint owns all of them now. There is no second formatter to hand any\n * of them to, and so no narrowed variant of this list to keep in step.\n *\n * @returns The `settings` block for the `.code-workspace` file.\n * @throws Never - performs pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeSettings (): Record<string, unknown> {\n // ESLint is the formatter now, so `editor.defaultFormatter` points at the\n // ESLint extension and `source.fixAll.eslint` is what actually reformats on\n // save. There is no second formatter to disagree with it.\n const formatter = 'dbaeumer.vscode-eslint'\n const eslintLanguages = [...FORMATTED_LANGUAGES]\n\n return {\n 'eslint.validate': eslintLanguages,\n 'editor.codeActionsOnSave': {\n 'source.fixAll.eslint': 'explicit',\n },\n 'editor.defaultFormatter': formatter,\n 'editor.formatOnSave': true,\n // Every language spelled out, and the global default above is NOT enough on\n // its own — which is the bug this fixes rather than a belt-and-braces habit.\n //\n // VS Code resolves a language-specific setting ahead of a general one, and it\n // does that comparison BEFORE scope. So a `[typescript]` block in someone's\n // USER settings — left over from any other project — outranks this file's\n // workspace-level `editor.defaultFormatter`, and format-on-save quietly uses\n // that other formatter instead — and nothing reports it, because the gate\n // and the editor are looking at different tools. That is how it was found:\n // reported from a real workspace, not deduced from the docs.\n //\n // Reported from a real workspace where `.ts` files were not being formatted\n // while `.json`/`.jsonc`/`.yaml` were — exactly the three that had explicit\n // entries here and nothing else did.\n ...Object.fromEntries(\n FORMATTED_LANGUAGES.map(language => [\n `[${language}]`,\n { 'editor.defaultFormatter': formatter },\n ]),\n ),\n }\n}\n\n/**\n * Prefix marking a launch configuration as mnci-owned.\n *\n * @remarks\n * `vscodeWorkspace` replaces every configuration carrying this prefix and carries\n * every other one through untouched, so a hand-written debug config survives\n * `mnci upgrade`. Renaming it would orphan the previous generation's entries,\n * leaving a workspace with two of each.\n */\nexport const LAUNCH_CONFIG_PREFIX = 'mnci: '\n\n/**\n * The verify targets exposed in VS Code's **Run and Debug** panel.\n *\n * @remarks\n * Tasks alone were not enough. A `tasks` entry is only reachable through\n * *Terminal -\\> Run Task*; the Run and Debug dropdown reads `launch`, so a workspace\n * with tasks and no launch section offers nothing there at all. These four are the\n * same targets the root `affected` script runs.\n *\n * **They drive npm scripts, not the Nx binary.** Pointing `program` at\n * `node_modules/nx/bin/nx.js` would be wrong: Nx ships its bin at\n * `dist/bin/nx.js`, and that path is version-dependent. `npm run \\<script\\>` is stable,\n * needs no path into `node_modules`, and tracks whatever the root script does — so a\n * change to `ROOT_SCRIPTS` reaches these for free.\n *\n * **`node-terminal`, not `node`, and that is the load-bearing choice.** It runs the\n * command in VS Code's JS Debug Terminal, which instruments **child** processes as\n * they spawn. `nx run-many` executes every target in a child process, so a plain\n * `node` launch would attach to the Nx parent alone and a breakpoint inside a spec\n * would never bind. It also needs no `console` setting: a plain `node` launch\n * defaults to `internalConsole`, which renders none of Nx's progress output, so a\n * build there looks like a hang.\n */\nexport const LAUNCH_CONFIGURATIONS = ['build', 'test', 'lint', 'typecheck'] as const\n\n/**\n * Builds the `launch.configurations` array for a generated workspace.\n *\n * @remarks\n * One entry per {@link LAUNCH_CONFIGURATIONS} target, each named with\n * {@link LAUNCH_CONFIG_PREFIX} so an upgrade can tell its own entries from a\n * user's. Order matches the verify order the root `affected` script runs.\n *\n * @param workspaceName - The workspace name, used to scope `${workspaceFolder}`.\n * @returns One configuration per entry in {@link LAUNCH_CONFIGURATIONS}.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function launchConfigurations (workspaceName: string): Record<string, unknown>[] {\n return LAUNCH_CONFIGURATIONS.map((script, index) => ({\n type: 'node-terminal',\n request: 'launch',\n name: `${LAUNCH_CONFIG_PREFIX}${script}`,\n command: `npm run ${script}`,\n // Scoped by folder name rather than a bare ${workspaceFolder}: that variable is\n // ambiguous once a second folder is added to the workspace, and VS Code then\n // refuses to resolve it.\n cwd: `\\${workspaceFolder:${workspaceName}}`,\n // One shared group keeps the four together in the dropdown; a per-entry group\n // would make four groups of one. `order` holds them in verify order.\n presentation: { group: 'mnci', order: index + 1 },\n }))\n}\n\n/**\n * VS Code workspace file template for generated monorepos.\n *\n * @remarks\n * Creates a single .code-workspace file that configures the entire monorepo:\n * folder structure, recommended extensions (ESLint, Nx, Jest), workspace\n * settings, and a `tasks` array. The array starts empty; `add/*.ts`'s\n * `registerProjectCommands` (`commands/add/shared.ts`) appends a\n * `build`/`qa`/`start` task per project as it is added, so this template\n * itself carries no project-specific content — it must stay generic across\n * every `mnci new`-generated workspace, not just this repo's own dogfooded\n * root.\n * It also carries a `launch` section, because a `tasks` entry is reachable only\n * through *Terminal -\\> Run Task* while the **Run and Debug** panel reads `launch` —\n * a workspace with tasks alone offers nothing there. See\n * {@link LAUNCH_CONFIGURATIONS}. Unlike `tasks`, which is carried through wholesale,\n * the launch array is merged: mnci replaces only its own `mnci: *` entries.\n * Users open this file in VS Code (`File > Open Workspace from File`).\n *\n * **`existingTasks` is what keeps `mnci upgrade` non-destructive**, and the need\n * for it was masked by another bug. The overlay owns this file's folders,\n * settings and extensions, but the `tasks` array is per-project state written by\n * `mnci add`, not by the overlay — so regenerating the file wholesale destroys\n * every registered task. That never surfaced only because upgrade used to write\n * to `undefined.code-workspace` and leave the real file untouched; fixing the\n * filename exposed it immediately (verified: a workspace with three projects lost\n * all five of its tasks). Tasks are carried through verbatim rather than\n * regenerated, since the overlay has no idea which projects exist.\n *\n * @param workspaceName - The workspace name.\n * @param existingTasks - The `tasks` object read from a file already on disk, to\n * carry through unchanged. Omitted for a fresh `mnci new`, where there is none.\n * @returns The JSON string for a .code-workspace file.\n * @throws Never - performs pure string formatting with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeWorkspace (\n workspaceName: string,\n existingTasks?: { version?: string; tasks?: Record<string, unknown>[] },\n existingLaunch?: { version?: string; configurations?: Record<string, unknown>[] },\n existingSettings?: Record<string, unknown>,\n): string {\n // Additive, like nx.json's sharedGlobals: mnci replaces only the configurations it\n // owns (named `mnci: *`) and carries every other one through, so a hand-written\n // debug config survives `mnci upgrade`. Tasks are carried through wholesale\n // instead, because `mnci add` — not the overlay — is what writes them.\n const userConfigurations = (existingLaunch?.configurations ?? []).filter(\n (configuration) => !String(configuration.name ?? '').startsWith(LAUNCH_CONFIG_PREFIX),\n )\n // Settings are MERGED, with mnci winning on the keys it owns. Replacing them\n // wholesale destroyed every setting a workspace had added for itself — measured on\n // this repo's own file, where an upgrade would have deleted a 1,179-entry\n // `cSpell.words` dictionary that nothing else stores. mnci overwrites only the\n // keys it actually sets, so its opinion still lands on every upgrade while\n // anything it has no opinion about survives.\n const settings = { ...existingSettings, ...vscodeSettings() }\n\n // `toJson`, like every other JSON file mnci writes: it ends with a newline.\n // Bare JSON.stringify did not, while `mnci new`'s format pass adds one, so\n // every `mnci upgrade` showed the same one-character diff on this file.\n return toJson(\n {\n folders: [{ path: '.', name: workspaceName }],\n settings,\n extensions: { recommendations: VSCODE_RECOMMENDED_EXTENSIONS },\n tasks: {\n version: existingTasks?.version ?? '2.0.0',\n tasks: existingTasks?.tasks ?? [],\n },\n launch: {\n version: existingLaunch?.version ?? '0.2.0',\n configurations: [...launchConfigurations(workspaceName), ...userConfigurations],\n },\n },\n )\n}\n\n/**\n * The curated npm scripts stamped into a generated workspace's root manifest.\n *\n * @remarks\n * Each one is a single cross-platform Nx (or husky) invocation — the everyday\n * entry points, nothing more. `affected` compares against `main`\n * (`defaultBase` in `nx.json`); `release:preview` shows what `nx release`\n * would do on CI without touching anything.\n *\n * `typecheck` is its own script because nothing else type-checks. Under\n * `--preset=ts` the `@nx/js/typescript` plugin infers a `typecheck` target for\n * every project, but a bundler-built project's `build` does not type-check at\n * all — esbuild and swc strip types without reading them. So a workspace can\n * build, test and lint green while carrying real type errors, which is exactly\n * what happened in mnci's own repo: `mnci upgrade` shipped a bug that\n * TypeScript had already flagged, because CI never ran this.\n */\nexport const ROOT_SCRIPTS = {\n 'build': 'nx run-many -t build',\n 'lint': 'nx run-many -t lint',\n 'test': 'nx run-many -t test',\n 'typecheck': 'nx run-many -t typecheck',\n 'affected': 'nx affected -t lint,typecheck,test,build',\n 'graph': 'nx graph',\n 'release:preview': 'nx release --dry-run',\n 'prepare': 'husky',\n} as const\n\n/**\n * The curated scripts, with ESLint doing both the linting and the formatting.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`) covering code\n * quality, type-aware correctness *and* JavaScript Standard Style formatting.\n * `npm run format` is `eslint . --fix`; there is no `format:check`, because\n * `lint` already reports a formatting mistake as an ordinary error.\n *\n * Every stack also gets `python:install`, chaining the same two guards CI runs\n * ({@link PYTHON_INSTALL_GUARD} then {@link PYTHON_WORKSPACE_INSTALL_GUARD}) —\n * the fixed dev toolchain (ruff/pytest/build/twine) plus an editable install of\n * every workspace Python project, so a fresh clone's `pip install` step is one\n * command instead of \"read the CI pipeline to find the right invocation\". Both\n * guards already no-op cleanly on a workspace with no Python projects, so it is\n * safe to stamp unconditionally rather than gating on whether one exists yet.\n *\n * @returns The root scripts object to stamp into the manifest.\n * @throws Never - pure mapping.\n */\nexport function rootScripts (): Record<string, string> {\n return {\n ...ROOT_SCRIPTS,\n // ESLint is the formatter, so `format` is `--fix` and there is no separate\n // `format:check`: `lint` already reports formatting as ordinary errors.\n // Carrying a second script that ran the same tool twice would just make a\n // CI run slower for no extra coverage.\n 'format': 'eslint . --fix --cache',\n 'python:install': `${PYTHON_INSTALL_GUARD} && ${PYTHON_WORKSPACE_INSTALL_GUARD}`,\n }\n}\n\n/**\n * The Nx `generators` defaults patched into `nx.json` from the chosen stack.\n *\n * @remarks\n * Lets a user's own **direct** `nx g @nx/react:app ...` (outside `mnci add`)\n * pick up the workspace's chosen test runner automatically, and — via\n * `linter: 'none'` — keep the workspace's single root ESLint config intact\n * instead of scaffolding a competing per-project one.\n *\n * `mnci add` itself does **not** read this back — see {@link mnciConfig} for\n * the dedicated, single-source-of-truth block it reads instead. The two used\n * to be conflated (`add` inferred the stack from one of these three identical\n * blocks), an implicit \"all three stay in lockstep\" invariant nothing enforced.\n *\n * @param stack - The chosen stack.\n * @returns The `generators` object for `nx.json`.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function generatorDefaults (stack: StackConfig): Record<string, unknown> {\n const shared = {\n // `none`, not `eslint`, and the workspace is still fully linted. The root\n // config plus `@nx/eslint/plugin` ({@link ESLINT_PLUGIN_CONFIG}) give every\n // project its `lint` target; `eslint` here would only make the generator\n // scaffold a per-project config mnci deletes anyway — and drag in\n // `eslint-plugin-import@2.31.0`, which peer-caps at ESLint 9 and breaks the\n // install outright on this workspace's ESLint 10.\n linter: 'none',\n unitTestRunner: stack.testRunner,\n }\n\n return {\n '@nx/react:application': shared,\n '@nx/react:library': shared,\n '@nx/js:library': shared,\n }\n}\n\n/**\n * The `mnci` block patched into `nx.json` from the options a `new`/`upgrade`\n * call resolved.\n *\n * @remarks\n * Two independent readers trust this one block: `mnci add`'s\n * `readWorkspaceStack` (`add.ts`) reads only `.stack`, and `mnci upgrade`\n * (`readMnciConfig`, below) reads the whole thing back as the defaults for\n * everything an explicit flag does not override — the only reason `scope`/\n * `registry`/`agent`/`variableGroup`/`ci` are persisted at all, since nothing\n * else in a generated workspace records them. Deliberately separate from\n * {@link generatorDefaults}, which serves Nx's own generator-default\n * mechanism instead (a real, independent feature: it makes a user's own\n * direct `nx g` pick up the right defaults too).\n *\n * @param options - The resolved overlay options (a `new`/`upgrade` call).\n * @returns The `mnci` object for `nx.json`.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function mnciConfig (options: OverlayOptions): Record<string, unknown> {\n return {\n // Persisted so `mnci upgrade` can name the `<name>.code-workspace` file it\n // rewrites. Its absence is why upgrade used to write a file literally called\n // `undefined.code-workspace` and therefore never refreshed the real one —\n // see `resolveWorkspaceName` in `commands/upgrade.ts`, which still needs a\n // fallback chain for workspaces generated before this field existed.\n workspaceName: options.workspaceName,\n scope: options.scope,\n registry: options.registry,\n agent: options.agent,\n variableGroup: options.variableGroup,\n ci: options.ci,\n stack: { testRunner: options.stack.testRunner },\n // Only when chosen: a workspace on the default keeps the block it always had,\n // so an upgrade does not add a field nobody asked for.\n ...(options.npmAuth !== undefined && { npmAuth: options.npmAuth }),\n }\n}\n\n/**\n * Reads back whatever a previous `new`/`upgrade` call persisted via\n * {@link mnciConfig}.\n *\n * @remarks\n * The read-side counterpart `mnci upgrade` (`commands/upgrade.ts`) uses to\n * resolve options: an explicit flag wins, otherwise the persisted value here\n * is the default, so a plain `mnci upgrade` with no flags re-applies the\n * exact same overlay the workspace already has, just regenerated from\n * today's `overlay.ts`. A workspace generated before this was persisted (or\n * hand-edited to remove it) simply has fewer fields here — `upgrade` reports\n * exactly which ones are missing rather than guessing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Whatever subset of {@link OverlayOptions} is persisted in\n * `nx.json`'s `mnci` block (empty object when there is none).\n * @throws Propagates any Node.js `fs`/JSON error reading `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function readMnciConfig (workspaceRoot: string): Partial<OverlayOptions> {\n const nxJson = readJson<Record<string, unknown>>(join(workspaceRoot, 'nx.json'))\n\n return (nxJson.mnci as Partial<OverlayOptions> | undefined) ?? {}\n}\n\n/**\n * The Python package registry's `twine upload` URL for a registry config.\n *\n * @remarks\n * Azure Artifacts feeds are **multi-protocol**: the same org/project/feed that\n * serves npm also serves Python, so the pypi upload URL is derived from the same\n * {@link RegistryConfig} — no separate Python registry prompt at `new`. Public\n * npm has no Python analogue wired in this cut (publishing to public PyPI needs\n * a PyPI token, a separate mechanism), so it returns `undefined` and the CI\n * Python-publish step is omitted.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The pypi upload URL for Azure Artifacts, or `undefined` for npm.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pythonPublishUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/pypi/upload/`\n }\n\n return undefined\n}\n\n/**\n * The Azure Artifacts NuGet v3 feed URL for a registry config.\n *\n * @remarks\n * Same multi-protocol feed {@link pythonPublishUrl} already reads — one\n * org/project/feed serves npm, Python **and** NuGet — so this is the third\n * reader of the same {@link RegistryConfig}, not a separate prompt. Public\n * npm has no NuGet analogue wired in this cut, the same gap\n * {@link pythonPublishUrl}'s own remarks document for PyPI: publishing to\n * public nuget.org needs a nuget.org-issued API key, a credential mnci\n * collects nowhere, so this returns `undefined` and `csharp-lib` is\n * versioned + tagged but not auto-published — see {@link addCsharpLib}.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @returns The NuGet v3 service index URL for Azure Artifacts, or\n * `undefined` for npm.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function nugetFeedUrl (registry: RegistryConfig): string | undefined {\n if (registry.kind === 'azure-artifacts') {\n return `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}/nuget/v3/index.json`\n }\n\n return undefined\n}\n\n/**\n * The fixed `nuget.config` source key mnci registers the Azure Artifacts\n * NuGet feed under.\n *\n * @remarks\n * Deliberately a constant, not `registry.artifactsFeed` — the real feed\n * name. `packageSourceCredentials` attaches to a source by its registered\n * KEY, not its URL, so the publish target in `add/csharp.ts`\n * (`dotnet nuget push --source ...`) needs to name the SAME key\n * {@link nugetConfigContent} registers. Fixing it here means that target\n * needs no {@link RegistryConfig} of its own at generation time — it just\n * references this constant — which is what keeps `addCsharpLib` from\n * needing to re-derive the workspace's registry choice per `add`, the way\n * Python's `nx-release-publish` target also carries no registry specifics\n * of its own (its `TWINE_*` env vars are injected only at CI release time).\n */\nexport const NUGET_AZURE_SOURCE = 'AzureArtifacts'\n\n/**\n * Builds the `nuget.config` body for a registry configuration.\n *\n * @remarks\n * Mirrors {@link npmrcContent}'s split, but the underlying auth mechanics\n * genuinely differ — verified against NuGet's own docs\n * (`nuget.config` file reference, and Azure Artifacts' \"Publish NuGet\n * packages with dotnet CLI\" guide), not assumed from the `.npmrc` case:\n *\n * - **NuGet's env-var substitution is `%VAR%` on every platform, never\n * `${VAR}` or `$VAR`.** Confirmed from Microsoft's own compatibility\n * table: `$MY_VAR` resolves on none of `nuget.exe`/`dotnet.exe`, Windows or\n * Mac. Getting this backwards is the exact class of trap the `.npmrc`\n * Bearer-vs-Basic saga already cost this repo once — a config that\n * *parses* but silently never substitutes anything.\n * - **`packageSourceCredentials` attaches to a REGISTERED source by key**,\n * not to the URL passed to `--source` at push time — confirmed by Azure's\n * own guide, which registers the feed under `packageSources` even though\n * `dotnet nuget push --source <url>` alone would also resolve the URL.\n * Skipping registration would leave the credentials with nothing to\n * attach to. {@link NUGET_AZURE_SOURCE} is that key.\n * - **`packageSourceMapping` scopes the private feed to this workspace's own\n * `<PascalScope>.*` packages**, the direct analogue of `.npmrc`'s\n * scope-only routing and for the identical reason: an unscoped\n * `<packageSources>` entry would have every `dotnet restore` — including a\n * contributor's local one, with no PAT set — query the private feed for\n * packages that were never going to be found there, on every build.\n * - **Public nuget.org needs no credentials at all** — restore is anonymous,\n * and {@link nugetFeedUrl}'s remarks explain why publish is left\n * unconfigured for that choice rather than half-wired.\n *\n * The one PAT is the same **raw** value `twine` already uses (see\n * {@link pythonPublishEnvFragment}), not the base64 form `.npmrc`'s\n * `_password` takes — NuGet's Basic auth is handled by the HTTP client\n * itself from a plain `Username`/`ClearTextPassword` pair, so no manual\n * base64 step belongs here.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`); its PascalCase form is the\n * `packageSourceMapping` pattern, matching {@link addCsharpLib}'s own\n * `<PascalScope>.<PascalName>` NuGet identity.\n * @returns The full text of the generated `nuget.config`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function nugetConfigContent (registry: RegistryConfig, scope: string): string {\n if (registry.kind === 'npm') {\n return `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Publish authentication for NuGet is deliberately UNCONFIGURED for the\n public npm registry choice: publishing to public nuget.org needs a\n nuget.org-issued API key, a credential mnci collects nowhere (the same\n gap Python's PyPI publish has for this same registry choice). A\n csharp-lib is still versioned and tagged; run \"dotnet nuget push\"\n yourself with your own key, or regenerate choosing the azure-artifacts\n registry instead. -->\n<configuration>\n <packageSources>\n <clear />\n <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n </packageSources>\n</configuration>\n`\n }\n\n const pascalScope = scope\n .replace(/^@/, '')\n .split('-')\n .map(part => part.charAt(0).toUpperCase() + part.slice(1))\n .join('')\n const feedUrl = nugetFeedUrl(registry) as string\n\n return `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- NuGet's own environment-variable syntax is '%VAR%' on every platform —\n never '\\${VAR}'/'$VAR', which resolve on none of them. NUGET_PAT is the\n RAW PAT (not base64 — unlike .npmrc's _password, NuGet's HTTP client\n handles Basic auth itself from Username + ClearTextPassword), exported\n only by the CI release step (see nugetPublishEnvFragment in mnci); an\n ordinary build/test/lint step never needs it, since packageSourceMapping\n below scopes this feed to ${pascalScope}.* packages only. -->\n<configuration>\n <packageSources>\n <clear />\n <add key=\"nuget.org\" value=\"https://api.nuget.org/v3/index.json\" />\n <add key=\"${NUGET_AZURE_SOURCE}\" value=\"${feedUrl}\" />\n </packageSources>\n <packageSourceCredentials>\n <${NUGET_AZURE_SOURCE}>\n <add key=\"Username\" value=\"AzureArtifacts\" />\n <add key=\"ClearTextPassword\" value=\"%NUGET_PAT%\" />\n </${NUGET_AZURE_SOURCE}>\n </packageSourceCredentials>\n <packageSourceMapping>\n <clear />\n <packageSource key=\"${NUGET_AZURE_SOURCE}\">\n <package pattern=\"${pascalScope}.*\" />\n </packageSource>\n <packageSource key=\"nuget.org\">\n <package pattern=\"*\" />\n </packageSource>\n </packageSourceMapping>\n</configuration>\n`\n}\n\n/**\n * The portable `node -e` one-liner that installs the fixed Python toolchain\n * (`ruff`/`pytest`/`build`/`twine`) from `requirements-dev.txt`.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}\n * — one guard script, so the two providers can never drift on what \"install\n * Python deps\" means. Skips cleanly on a workspace with no Python projects\n * (no `requirements-dev.txt`, written by `add/python.ts` on the first\n * Python `add`).\n *\n * Resolves `python` vs `python3` at run time via `process.platform`, not a\n * hard-coded name: the standard python.org Windows installer registers only\n * `python.exe`, so a build agent on a `windows-latest` (or self-hosted\n * Windows) runner hard-fails immediately on a hard-coded `python3` with\n * \"'python3' is not recognized as an internal or external command\" — every\n * POSIX system (the assumed default) registers `python3`. Same resolution\n * {@link PYTHON_WORKSPACE_INSTALL_GUARD} and every `@mnci/nx-python-pip`\n * executor use (that package's own `pythonCommand` helper); this guard is a\n * plain generated string, not TypeScript, so it inlines the identical check\n * rather than importing it.\n */\nconst PYTHON_INSTALL_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'requirements-dev.txt\\')){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';process.exit(require(\\'node:child_process\\').spawnSync(py+\\' -m pip install -r requirements-dev.txt\\',{stdio:\\'inherit\\',shell:true}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that editable-installs every Python\n * project into one shared environment, so cross-project imports resolve at\n * lint/test/dev time — the pip-world counterpart of `npm install` hoisting\n * every workspace package into one root `node_modules`.\n *\n * @remarks\n * Pip has no native workspace protocol (no hoisting, no auto-symlinking of\n * sibling packages), so this is hand-built rather than something pip does on\n * its own: every project with a `pyproject.toml` (`apps/*`, `python-packages/*`,\n * `libs/*` — apps, publishable libs, and internal libs alike) is\n * `pip install -e`'d, and every Azure Function app (`requirements.txt`, no\n * `pyproject.toml` — the shape `@mnci/nx-python-pip`'s `function-application`\n * generator writes) gets `pip install -r`'d, all in **one** `pip install`\n * invocation (not one per project) so the resolver sees every requirement\n * together, same as one `npm install` at the root.\n *\n * This is deliberately broader than the `test` executor's own per-project\n * `pip install -e .` (`@mnci/nx-python-pip`'s `installEditable` option,\n * which only installs the project under test, not what it imports): an\n * internal lib is normally only woven into a consumer at **build** time (the\n * `build` executor's vendoring copy step — see `@mnci/nx-python-pip`'s\n * README), so without this step a project that imports an internal lib\n * cannot resolve that import at test/dev time, only at the final wheel. This\n * step editable-installs the internal lib too, so the import resolves\n * everywhere it is written, not just in the built artifact. It does not\n * change what a published wheel contains — vendoring at build time is\n * unaffected, since pip has no registry-time equivalent of installing an\n * unpublished workspace-only package.\n *\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Skips cleanly when the workspace has no Python projects. Runs after\n * {@link PYTHON_INSTALL_GUARD} (the fixed dev toolchain), before `sync:check`.\n * Resolves `python` vs `python3` at run time the same way\n * {@link PYTHON_INSTALL_GUARD} does — see its remarks.\n */\nconst PYTHON_WORKSPACE_INSTALL_GUARD = 'node -e \"const fs=require(\\'node:fs\\'),path=require(\\'node:path\\');const editableDirs=[...fs.globSync(\\'apps/*/pyproject.toml\\'),...fs.globSync(\\'python-packages/*/pyproject.toml\\'),...fs.globSync(\\'libs/*/pyproject.toml\\')].map((p)=>path.dirname(p));const requirementsFiles=fs.globSync(\\'apps/*/requirements.txt\\');if(editableDirs.length===0&&requirementsFiles.length===0){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const args=[\\'-m\\',\\'pip\\',\\'install\\',\\'--quiet\\',...editableDirs.flatMap((d)=>[\\'-e\\',d]),...requirementsFiles.flatMap((f)=>[\\'-r\\',f])];const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';process.exit(require(\\'node:child_process\\').spawnSync(py,args,{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that runs `pip-audit` against the shared\n * Python environment, non-blocking (warn-only).\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Runs after {@link PYTHON_WORKSPACE_INSTALL_GUARD}, so the environment it\n * scans already has every project's real dependencies installed (not just\n * the fixed toolchain) — a bare `pip-audit` with no arguments audits\n * whatever is currently installed, which by this point in the pipeline is\n * the workspace's actual dependency set. Skips cleanly when the workspace\n * has no Python projects (same `requirements-dev.txt` check every other\n * Python guard here uses).\n *\n * **Deliberately non-blocking**: `pip-audit`'s own exit code is discarded\n * (`process.exit(0)` always) rather than failing the build. An upstream-only\n * advisory with no user-actionable fix (a transitive dependency of a pinned\n * tool, not patchable by editing this workspace's own manifest) would\n * otherwise turn every build red for a problem nobody here can fix.\n *\n * **This no longer matches its npm sibling, and the asymmetry is a limitation\n * rather than a preference.** {@link NPM_AUDIT_STEP} now fails on an advisory\n * that has a published fix and passes the rest, because `npm audit --json`\n * reports `fixAvailable` per advisory. `pip-audit`'s output carries no\n * equivalent field, so the actionable/unactionable line cannot be drawn here\n * without resolving each advisory's fixed version by hand. Until that is\n * built, report-only is the honest choice: the alternative is a gate that goes\n * red on findings nobody can act on. Do not \"align\" the two by making this one\n * blocking — that trades a weak gate for a false one.\n */\nconst PIP_AUDIT_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'requirements-dev.txt\\')){console.log(\\'No Python projects - skipping.\\');process.exit(0)}const py=process.platform===\\'win32\\'?\\'python\\':\\'python3\\';require(\\'node:child_process\\').spawnSync(py,[\\'-m\\',\\'pip_audit\\'],{stdio:\\'inherit\\'});process.exit(0)\"'\n\n/**\n * The portable `node -e` one-liner that downloads the workspace's Go module\n * dependencies.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml},\n * and gated on the workspace-root `go.mod` that `add/go.ts` writes on the\n * first `mnci add go-*` — so a workspace with no Go projects skips cleanly,\n * exactly like the Python guards skip on a missing `requirements-dev.txt`.\n *\n * One root `go.mod` is the whole story here: mnci generates Go projects into\n * a single module (see `add/go.ts`), so there is no per-project manifest to\n * walk and no `go.work` to keep in step — a plain `go mod download` at the\n * root fetches everything every Go project needs.\n *\n * Strictly speaking this step is optional, since `go build` and `go test`\n * both fetch on demand. It is here so a network failure surfaces as an\n * obvious \"download dependencies\" failure rather than as a confusing error\n * inside the build, matching what the Python install steps do.\n */\nconst GO_MODULE_DOWNLOAD_GUARD = 'node -e \"if(!require(\\'node:fs\\').existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'go\\',[\\'mod\\',\\'download\\'],{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The golangci-lint version every generated workspace installs.\n *\n * @remarks\n * Pinned, like {@link FLUTTER_SDK_VERSION}, for two reasons measured on a real\n * generated workspace (russoedu/MoNecromanCi#239):\n *\n * - **Reproducibility.** The guard used to `go install …@latest`, so a new\n * golangci-lint release could add or tighten linters and turn every\n * workspace's CI red overnight with no change on its side, while a developer's\n * local copy disagreed with CI.\n * - **Speed.** A pinned version has a published prebuilt binary to download,\n * instead of a compile. See {@link GOLANGCI_LINT_INSTALL_GUARD}.\n *\n * Bump it deliberately: check the release notes for new default linters, then\n * run the e2e's Go section. Exported so tests and this repo's own workflow can\n * assert it.\n */\nexport const GOLANGCI_LINT_VERSION = '2.14.0'\n\n/**\n * The portable `node -e` one-liner that installs `golangci-lint` when the\n * workspace has Go projects and the agent does not already provide it.\n *\n * @remarks\n * Needed because mnci's generated Go `lint` target pins\n * `linter: golangci-lint` — `@nx-go/nx-go`'s own default is `go fmt`, which\n * only reformats and would make a green lint step meaningless. Hosted agents\n * ship Go but not golangci-lint, so CI has to supply it.\n *\n * **Downloads the project's prebuilt release instead of compiling it.** The\n * previous `go install …@latest` built golangci-lint from source on every run:\n * 68 s of a ~2 min job on Lore Master's first CI run, more than install, lint,\n * test and build together. The prebuilt archive (≈14 MB) downloads and extracts\n * in about a second. In order:\n *\n * 1. Map `process.platform`/`process.arch` to the release asset name.\n * 2. Fetch the release's `checksums.txt` and the archive, and refuse the archive\n * unless its SHA-256 matches, so a tampered or truncated download is never\n * executed.\n * 3. Extract it with `tar -xf`. On Windows that is the absolute\n * `%SystemRoot%\\System32\\tar.exe` (bsdtar, shipped with Windows 10 and\n * later), which reads the `.zip` the Windows builds come as. Not the bare\n * `tar`: on an agent with Git installed the one that wins on `PATH` can be\n * GNU tar, which cannot read zip files. Not PowerShell either, which the\n * pipeline avoids by design.\n * 4. Copy the binary into `GOPATH/bin`, the directory the \"Add Go tool bin to\n * PATH\" step already publishes, so nothing downstream changes.\n *\n * Any failure (an unmapped platform, a network error, a checksum mismatch, a\n * failed extract) falls back to `go install` **at the same pinned version**, so\n * an outage of GitHub's release CDN costs speed, never the build. The reason is\n * printed first.\n *\n * Skips when `golangci-lint` is already resolvable, so a self-hosted agent that\n * pre-installs it pays nothing.\n *\n * Constraints every guard here shares: one line, single quotes inside, and no\n * colon-space, no space-hash, no backtick, `$` or `%`, so the same text is a\n * valid YAML plain scalar and survives bash, PowerShell and `cmd.exe` quoting.\n */\nconst GOLANGCI_LINT_INSTALL_GUARD = 'node -e \"' +\n \"const fs=require('node:fs'),cp=require('node:child_process'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');\" +\n \"if(!fs.existsSync('go.mod')){console.log('No Go projects - skipping.');process.exit(0)}\" +\n \"if(cp.spawnSync('golangci-lint',['--version'],{stdio:'ignore'}).status===0){console.log('golangci-lint already installed - skipping.');process.exit(0)}\" +\n `const v='${GOLANGCI_LINT_VERSION}';` +\n \"const fallback=reason=>{console.log('Prebuilt golangci-lint '+v+' unavailable ('+reason+') - building it with go install instead.');process.exit(cp.spawnSync('go',['install','github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v'+v],{stdio:'inherit'}).status ?? 1)};\" +\n \"const plat={linux:'linux',darwin:'darwin',win32:'windows'}[process.platform],arch={x64:'amd64',arm64:'arm64'}[process.arch];\" +\n \"if(!plat||!arch)fallback('no prebuilt release for '+process.platform+'/'+process.arch);\" +\n \"const gopath=cp.spawnSync('go',['env','GOPATH'],{encoding:'utf8'});if(gopath.status!==0)fallback('go env GOPATH failed');\" +\n \"const name='golangci-lint-'+v+'-'+plat+'-'+arch,file=name+(plat==='windows'?'.zip':'.tar.gz'),base='https://github.com/golangci/golangci-lint/releases/download/v'+v+'/';\" +\n \"const get=async u=>{const r=await fetch(u,{signal:AbortSignal.timeout(60000)});if(!r.ok)throw new Error('HTTP '+r.status+' for '+u);return Buffer.from(await r.arrayBuffer())};\" +\n '(async()=>{' +\n String.raw`const sums=(await get(base+'golangci-lint-'+v+'-checksums.txt')).toString('utf8').split('\\n').map(l=>l.trim().split(' ').filter(Boolean));` +\n \"const entry=sums.find(p=>p[1]===file);if(!entry)throw new Error(file+' is not in the release checksums');\" +\n 'const archive=await get(base+file);' +\n \"if(crypto.createHash('sha256').update(archive).digest('hex')!==entry[0])throw new Error('checksum mismatch for '+file);\" +\n \"const tmp=fs.mkdtempSync(path.join(os.tmpdir(),'golangci-lint-')),archivePath=path.join(tmp,file);fs.writeFileSync(archivePath,archive);\" +\n \"const tar=plat==='windows'?path.join(process.env.SystemRoot||'','System32','tar.exe'):'tar';\" +\n \"const x=cp.spawnSync(tar,['-xf',archivePath,'-C',tmp],{stdio:'inherit'});\" +\n \"if(x.status!==0)throw new Error('could not extract '+file);\" +\n \"const exe='golangci-lint'+(plat==='windows'?'.exe':''),bin=path.join(gopath.stdout.trim(),'bin');fs.mkdirSync(bin,{recursive:true});\" +\n 'fs.copyFileSync(path.join(tmp,name,exe),path.join(bin,exe));fs.chmodSync(path.join(bin,exe),0o755);fs.rmSync(tmp,{recursive:true,force:true});' +\n \"console.log('golangci-lint '+v+' installed from its checksum-verified prebuilt release into '+bin)\" +\n '})().catch(error=>fallback(error.message))\"'\n\n/**\n * The shared prelude that resolves `GOPATH/bin` — where\n * {@link GOLANGCI_LINT_INSTALL_GUARD} puts the linter (whether downloaded or built).\n *\n * @remarks\n * Not a step on its own: {@link GO_TOOL_PATH_AZURE} and\n * {@link GO_TOOL_PATH_GITHUB} each append the one line that publishes the\n * directory to the rest of the job, because the two providers use entirely\n * different mechanisms for that (a logging command vs a file). Everything up\n * to that point — the skip-when-no-Go check and reading `go env GOPATH` —\n * is identical, so it lives here once.\n */\nconst GO_TOOL_PATH_PRELUDE = 'const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(!fs.existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}const r=cp.spawnSync(\\'go\\',[\\'env\\',\\'GOPATH\\'],{encoding:\\'utf8\\'});if(r.status!==0){console.log(\\'Could not resolve GOPATH - skipping.\\');process.exit(0)}const bin=require(\\'node:path\\').join(r.stdout.trim(),\\'bin\\');'\n\n/**\n * Azure Pipelines: publishes `GOPATH/bin` to later steps in the job.\n *\n * @remarks\n * Uses the `task.prependpath` logging command — Azure's supported way for a\n * step to alter `PATH` for the steps that follow it. Skips cleanly when the\n * workspace has no Go projects, so this is inert in a JS-only repo.\n */\nconst GO_TOOL_PATH_AZURE = `node -e \"${GO_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)\"`\n\n/**\n * GitHub Actions: publishes `GOPATH/bin` to later steps in the job.\n *\n * @remarks\n * Appends to the file named by `GITHUB_PATH`, the documented equivalent of\n * Azure's `prependpath` logging command. Skips cleanly both when the\n * workspace has no Go projects and when `GITHUB_PATH` is unset (i.e. when\n * the same script is run outside Actions).\n */\nconst GO_TOOL_PATH_GITHUB = `node -e \"${GO_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\\n'`})\"`\n\n/**\n * The Flutter SDK version the generated pipeline installs.\n *\n * @remarks\n * Pinned, like {@link GOLANGCI_LINT_VERSION}, because the Flutter version\n * *determines the Dart version*, and Dart is what has the hard floor here:\n * pub workspaces — the whole basis of mnci's central-dependency model for\n * Dart — need Dart 3.6+. A floating `stable` could in principle move the\n * toolchain under a workspace without warning, so the version is explicit and\n * bumped deliberately.\n *\n * `3.44.8` ships Dart 3.12.2. Exported so tests can assert it and so bumping\n * it is a one-line change.\n */\nexport const FLUTTER_SDK_VERSION = '3.44.8'\n\n/**\n * The Node major a generated workspace is built and tested against.\n *\n * @remarks\n * Read by both the GitHub workflow's `setup-node` step and the devcontainer's\n * base image, so the environment a contributor develops in cannot silently\n * diverge from the one CI verifies. That drift is the whole reason\n * {@link devcontainerJson} exists, so it must not be reintroduced by hardcoding\n * the number twice.\n *\n * Azure deliberately does not pin it: its pipeline uses whatever Node the agent\n * image ships, which is the existing behaviour and outside this change's scope.\n */\nexport const NODE_VERSION = '24'\n\n/**\n * The major of each GitHub Action the generated workflow pins.\n *\n * @remarks\n * Pinned here, in one place, because Dependabot cannot see them anywhere else.\n * It keeps THIS repo's `.github/workflows/ci.yml` current — that file is real\n * YAML it can parse — but these are string literals inside a TypeScript\n * generator, invisible to it. So every bump it lands here has to be carried\n * across by hand, and three of the four had not been: this repo ran\n * `checkout@v7`, `setup-node@v7` and `upload-artifact@v7` while every workspace\n * mnci generated still got `@v4`. Only `setup-dotnet` matched, because someone\n * noticed once and ported it (PR #189).\n *\n * The v7 majors are not a guess: they are exactly what this repo's own CI runs\n * green today, on a pipeline whose `run:` steps are asserted to be a superset of\n * the generated one's. `pipeline-drift.integration.spec.ts` now fails when this\n * map falls behind that workflow, so the carry-across is enforced rather than\n * remembered.\n */\nexport const ACTION_VERSIONS = {\n 'actions/checkout': 'v7',\n 'actions/setup-node': 'v7',\n 'actions/setup-dotnet': 'v6',\n 'actions/upload-artifact': 'v7',\n 'azure/login': 'v3',\n} as const\n\n/**\n * The npm major a generated workspace is built and tested against.\n *\n * @remarks\n * Pinned because npm's own behaviour is part of the contract, not an\n * implementation detail beneath it. npm 11 changed when `overrides` are\n * applied: given a tree that is already installed, it reuses it rather than\n * re-resolving, so an `overrides` entry added after the first install does\n * nothing. Measured on nx 23.1.1 with the same manifest and the same sequence:\n * npm 10.9.7 reported **0** advisories and npm 11.19.0 reported **6**.\n *\n * That divergence is what made the bug expensive. `setup-node` installs\n * whatever npm the Node image happens to bundle, so CI and a contributor's\n * machine could — and did — resolve differently, which meant a local check\n * could pass while the thing it was checking was broken for every user.\n *\n * **The MAJOR is pinned, not an exact version.** The risk this guards is a\n * major behavioural change, which is what actually happened; an exact pin would\n * add a version nothing bumps, and a pin that reads as current while being\n * stale is a failure mode this repo has already paid for once (the\n * `@verdaccio/config` → `js-yaml` entry that read as fixed and was not).\n */\nexport const NPM_VERSION = '11'\n\n/**\n * The .NET SDK version a generated workspace's C# projects are built and\n * tested against.\n *\n * @remarks\n * `@nx/dotnet` (the official Nx plugin mnci delegates to for C#) requires SDK\n * 8.0+; `10.0.x` is pinned rather than 8.0 because .NET's even-numbered\n * majors are the LTS line (8.0 released Nov 2023, 10.0 Nov 2025) and 10.0 is\n * the current one — the same reasoning `NODE_VERSION` already applies to\n * Node. **Verify the current LTS against the real .NET release notes before\n * this ships**, the way `FLUTTER_SDK_VERSION` was pinned against a real\n * `flutter --version` rather than assumed.\n *\n * The `.x` keeps the range open to patch releases the way `actions/setup-dotnet`\n * and `UseDotNet@2` both expect it (`dotnet-version: '10.0.x'` /\n * `version: '10.0.x'`) — unlike {@link NODE_VERSION}, which is a bare major\n * because `setup-node` resolves majors on its own; .NET's own tooling wants\n * the `.x` suffix explicit.\n *\n * Exported so tests can assert it and both CI providers' install steps and\n * the devcontainer's `dotnet` feature read the same value — the exact drift\n * {@link NODE_VERSION} already exists to prevent for Node.\n */\nexport const DOTNET_SDK_VERSION = '10.0.x'\n\n/**\n * Detects whether the workspace has any C# project — `apps/*\\/*.csproj`,\n * `packages/*\\/*.csproj`, `libs/*\\/*.csproj`, the same three roots\n * {@link PACK_APPS_GUARD} and `add/csharp.ts` already scan — and publishes\n * the result as an Azure Pipelines variable\n * (`##vso[task.setvariable variable=hasDotnetProjects]`).\n *\n * @remarks\n * Unlike Python, Go and Flutter's provisioning, .NET SDK install is not a\n * portable `node -e` one-liner: `UseDotNet@2` / `actions/setup-dotnet` are\n * each provider's own maintained, cached installer, and hand-rolling a\n * cross-platform equivalent of either would be strictly worse than using\n * them — the same trade already made for Node/npm setup itself, which is\n * fully provider-specific (`actions/setup-node` + `cache: npm` vs. Azure's\n * pre-installed image Node plus a separate `Cache@2` task). So this constant\n * exists only to give the Azure *task* something to gate on: unlike a\n * `script:` step, `UseDotNet@2` cannot self-gate by exiting early, and\n * Azure's `condition:` expression language has no glob-matching function —\n * where GitHub Actions' `hashFiles()` covers the same case inline (see\n * {@link githubActionsYaml}), Azure needs this detection step to run first\n * and hand the answer to `condition:` through a variable.\n *\n * Both the pattern list and the `##vso` syntax are copied from\n * {@link PACK_APPS_GUARD}'s sibling `build.addbuildtag` usage further down\n * this file — the same \"publish a fact for a later step\" mechanism, just\n * read by `condition:` instead of by a human.\n */\nconst DOTNET_DETECT_AZURE = 'node -e \"const fs=require(\\'node:fs\\');const has=[...fs.globSync(\\'apps/*/*.csproj\\'),...fs.globSync(\\'packages/*/*.csproj\\'),...fs.globSync(\\'libs/*/*.csproj\\')].length>0;console.log(\\'##vso[task.setvariable variable=hasDotnetProjects]\\'+has)\"'\n\n/**\n * The shared expression that resolves where the Flutter SDK is installed.\n *\n * @remarks\n * Deliberately **outside** the workspace, under the agent's home directory —\n * not in a workspace-local `.flutter-sdk`. Two reasons, and the second is the\n * serious one:\n *\n * 1. Nothing needs adding to `.gitignore` for it. The overlay still owns no\n * `.gitignore` of its own — `create-nx-workspace` does — but it does now\n * append the one entry a decision made here needs (see\n * {@link ensureEslintCacheIgnored}); this SDK path was chosen specifically\n * so it would never need a second one.\n * 2. The Flutter SDK ships **its own `pubspec.yaml` files** — dozens of them,\n * across `packages/flutter`, `packages/flutter_test` and the rest. Cloning\n * it inside the workspace would drop those into the pub workspace's own\n * tree, where `pub` treats a stray nested pubspec as an error to resolve\n * around, and would give Nx thousands of extra files to glob for its\n * project graph.\n *\n * Keyed by version, so bumping {@link FLUTTER_SDK_VERSION} provisions a fresh\n * clone instead of leaving a stale checkout on a cached agent. Computed\n * identically by the install guard and the PATH guards so they always agree.\n */\nconst FLUTTER_SDK_DIRECTORY_EXPRESSION = `require('node:path').join(require('node:os').homedir(),'.mnci-flutter-${FLUTTER_SDK_VERSION}')`\n\n/**\n * The portable `node -e` one-liner that installs the Flutter SDK.\n *\n * @remarks\n * Flutter is the one toolchain the pipeline genuinely has to install: Python\n * and Go both ship on every hosted agent image, Flutter does not. So unlike\n * the Python and Go guards — which only fetch *dependencies* — this one\n * fetches the SDK itself.\n *\n * Installed by shallow `git clone` at a pinned tag, which is Flutter's own\n * documented install method and the only one that is genuinely uniform across\n * agents: the release archives differ by platform (`.tar.xz` on Linux,\n * `.zip` on macOS/Windows), which would force a platform switch and an\n * extractor into what is meant to be one portable line. `git` is already a\n * hard requirement of this pipeline, so nothing new is assumed.\n * `--depth 1` keeps it to a single revision.\n *\n * Two levels of skip, mirroring {@link GOLANGCI_LINT_INSTALL_GUARD}: nothing\n * happens without a workspace-root `pubspec.yaml` (so a JS-only repo pays\n * nothing), and nothing happens when `flutter` already resolves — so a\n * self-hosted agent with a preinstalled SDK, or a second run on a warm\n * workspace, skips the download entirely.\n */\nconst FLUTTER_SDK_INSTALL_GUARD = `node -e \"const fs=require('node:fs'),cp=require('node:child_process');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}if(cp.spawnSync('flutter',['--version'],{stdio:'ignore',shell:process.platform==='win32'}).status===0){console.log('Flutter SDK already on PATH - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(fs.existsSync(sdk)){console.log('Flutter SDK already installed at '+sdk+' - skipping.');process.exit(0)}process.exit(cp.spawnSync('git',['clone','--depth','1','--branch','${FLUTTER_SDK_VERSION}','https://github.com/flutter/flutter.git',sdk],{stdio:'inherit'}).status ?? 1)\"`\n\n/**\n * The shared prelude that resolves the Flutter SDK's `bin` directory.\n *\n * @remarks\n * Not a step on its own — {@link FLUTTER_TOOL_PATH_AZURE} and\n * {@link FLUTTER_TOOL_PATH_GITHUB} each append the one line that publishes the\n * directory, exactly as the Go pair does. Skips when there are no Flutter\n * projects, and also when the SDK was never cloned because one was already on\n * `PATH` (in which case there is nothing to publish).\n */\nconst FLUTTER_TOOL_PATH_PRELUDE = `const fs=require('node:fs'),path=require('node:path');if(!fs.existsSync('pubspec.yaml')){console.log('No Flutter projects - skipping.');process.exit(0)}const sdk=${FLUTTER_SDK_DIRECTORY_EXPRESSION};if(!fs.existsSync(sdk)){console.log('Flutter SDK was not installed by mnci (already on PATH) - skipping.');process.exit(0)}const bin=path.join(sdk,'bin');`\n\n/**\n * Azure Pipelines: publishes the Flutter SDK's `bin` to later steps.\n *\n * @remarks\n * Uses the `task.prependpath` logging command, the same mechanism\n * {@link GO_TOOL_PATH_AZURE} uses.\n */\nconst FLUTTER_TOOL_PATH_AZURE = `node -e \"${FLUTTER_TOOL_PATH_PRELUDE}console.log('##vso[task.prependpath]'+bin)\"`\n\n/**\n * GitHub Actions: publishes the Flutter SDK's `bin` to later steps.\n *\n * @remarks\n * Appends to the file named by `GITHUB_PATH`, mirroring\n * {@link GO_TOOL_PATH_GITHUB}, and skips cleanly when run outside Actions.\n */\nconst FLUTTER_TOOL_PATH_GITHUB = `node -e \"${FLUTTER_TOOL_PATH_PRELUDE}if(!process.env.GITHUB_PATH){console.log('Not running in GitHub Actions - skipping.');process.exit(0)}fs.appendFileSync(process.env.GITHUB_PATH,bin+${String.raw`'\\n'`})\"`\n\n/**\n * The portable `node -e` one-liner that resolves every Dart dependency.\n *\n * @remarks\n * **This is the dependency-injection step for Flutter**, and it is a single\n * command for the whole workspace by design. Because every project is a\n * member of the root pub workspace (`workspace:` in the root `pubspec.yaml`,\n * `resolution: workspace` in each member), one `flutter pub get` at the root\n * resolves **internal and external dependencies together** into one\n * `pubspec.lock` and one `.dart_tool/package_config.json` — pub even deletes\n * any stale per-package copies.\n *\n * That is why there is no second, workspace-wide step here of the kind Python\n * needs ({@link PYTHON_WORKSPACE_INSTALL_GUARD} exists only because pip has no\n * workspace protocol and every project must be editable-installed by hand).\n * Dart has a real workspace protocol, so this one line is the whole story —\n * and a project importing an internal lib resolves it with a plain version\n * constraint, no `path:` and no vendoring.\n *\n * Runs with `shell: true` on Windows only, where `flutter` is a `.bat` shim\n * that `spawnSync` cannot execute directly.\n */\nconst FLUTTER_PUB_GET_GUARD = 'node -e \"const fs=require(\\'node:fs\\');if(!fs.existsSync(\\'pubspec.yaml\\')){console.log(\\'No Flutter projects - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'flutter\\',[\\'pub\\',\\'get\\'],{stdio:\\'inherit\\',shell:process.platform===\\'win32\\'}).status ?? 1)\"'\n\n/**\n * The `npm audit` step: blocks on an **actionable** advisory, reports the rest.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n *\n * **This replaced a warn-only step, and the reason is that the step's own\n * justification had stopped being true.** It used to be\n * `npm audit --audit-level=high || echo ...`, documented as non-blocking\n * because \"every flagged vulnerability traced back to `nx`'s and `verdaccio`'s\n * own bundled transitive dependencies ... nothing an edit to *this* workspace's\n * manifest could fix\". That was measured, and it has since inverted: a real\n * audit of this monorepo reported **9 advisories, 9 of them with\n * `fixAvailable`**, and every one was fixed by a targeted `overrides` entry —\n * exactly the edit the old note said was impossible. Same shape as the stale\n * `js-yaml` pin that audit run uncovered: a decision resting on a measurement\n * nothing re-checks.\n *\n * So the split is **actionable vs not**, which is a property `npm audit --json`\n * reports per advisory (`fixAvailable`) rather than a severity guess:\n *\n * - A published, **applyable** fix exists, at `moderate` or above → **exit 1**.\n * The remedy is a reviewed `overrides` entry, and leaving it to a human who\n * never sees a red build is how a fix sits unapplied for weeks.\n * - No fix exists upstream → printed, exit 0. This preserves the whole of the\n * original concern: going red for something nobody in this workspace can fix\n * is a gate that only teaches people to ignore it.\n * - **A fix npm marks `isSemVerMajor` → printed, exit 0.** `npm audit` sets\n * `fixAvailable` to a `{name,version,isSemVerMajor:true}` object even when the\n * only \"fix\" is a semver-**major** change to a *parent* that drops the\n * dependency path — frequently a downgrade, not a patch to the vulnerable\n * package at all (measured: `braces`/`http-cache-semantics` advisories whose\n * leaf was already at the newest published version, \"fixed\" by downgrading\n * `verdaccio` and `@swc/cli`). Blocking on that taught the gate to fire on\n * advisories nobody can actually clear with an override. A semver-major upgrade\n * is a deliberate, reviewed bump, never an automatic CI gate; it is reported so\n * it is not forgotten, but it does not go red.\n *\n * Three deliberate choices worth not undoing:\n *\n * - **Dev dependencies are included.** Measured: `npm audit --omit=dev` on the\n * tree that had those 9 advisories reports **0**. Every one arrived through a\n * devDependency (verdaccio, ts-jest's istanbul chain, eslint-plugin-tsdoc,\n * commitlint, Vite). Omitting them would have reported nothing and verified\n * nothing.\n * - **The threshold is `moderate`, not `high`.** The old `--audit-level=high`\n * would have missed the `postcss` advisory outright, which was moderate and\n * had a fix.\n * - **A broken audit does not fail the build.** No parseable JSON (registry\n * outage, npm change) exits 0 with the reason printed. A gate that cannot read\n * its input should say so, not guess.\n *\n * Verified by execution against two real dependency trees, not by reading it:\n * this monorepo's fixed tree exits 0, and the pre-fix tree exits 1 listing all\n * nine. The unit tests drive the remaining branches with a stub `npm` on PATH.\n */\nconst NPM_AUDIT_STEP = 'node -e \"const cp=require(\\'node:child_process\\');const r=cp.spawnSync(\\'npm\\',[\\'audit\\',\\'--json\\'],{encoding:\\'utf8\\',shell:process.platform===\\'win32\\',maxBuffer:33554432});let d;try{d=JSON.parse(r.stdout)}catch{console.log(\\'npm audit produced no JSON (exit \\'+r.status+\\') - not blocking on a broken audit.\\');process.exit(0)}const all=Object.values(d.vulnerabilities||{});const BLOCK=[\\'critical\\',\\'high\\',\\'moderate\\'];const major=v=>v.fixAvailable&&typeof v.fixAvailable===\\'object\\'&&v.fixAvailable.isSemVerMajor;const act=all.filter(v=>v.fixAvailable&&BLOCK.includes(v.severity)&&!major(v));const rest=all.filter(v=>!act.includes(v));for(const v of rest)console.log(\\' note [\\'+v.severity+\\'] \\'+v.name+(!v.fixAvailable?\\' - NO fix available upstream, nothing to do here\\':major(v)?\\' - only a semver-major change would remove it (often a parent downgrade), not applied automatically\\':\\' - fix available, below the blocking threshold\\'));if(act.length===0){console.log(\\'npm audit - \\'+all.length+\\' advisory(ies), none actionable at moderate or above.\\');process.exit(0)}for(const v of act)console.log(\\' BLOCKING [\\'+v.severity+\\'] \\'+v.name+\\' - fix available\\');console.log(\\'Each has a published fix. Add a targeted overrides entry in package.json rather than npm audit fix --force.\\');process.exit(1)\"'\n\n/**\n * The Nx targets every CI run verifies.\n *\n * @remarks\n * `typecheck` is not covered by `build`: a bundler-built project (esbuild, swc)\n * strips types without reading them, so a workspace can be green on\n * lint+test+build while carrying real type errors.\n */\nconst VERIFY_TARGETS = 'lint,typecheck,test,build'\n\n/**\n * The portable `node -e` one-liner that verifies the workspace: only the\n * **affected** projects on a pull request, **every** project otherwise.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml},\n * which matters more here than for the other guards: the two providers detect a\n * pull request through different environment variables, and a mechanism that\n * drifted would change *what CI verifies* rather than merely how it is spelled.\n *\n * **Every failure path falls back to `run-many`, never to nothing.** That is the\n * whole safety design, and it is deliberate rather than defensive habit. Getting\n * the base *too wide* costs a few minutes; getting it *too narrow* means CI runs\n * almost nothing, passes green, and has verified nothing — a silently weakened\n * gate, which is far worse than a slow one. So a missing target ref, an\n * unresolvable merge-base (a shallow clone, a missing remote branch) and a\n * non-PR run all take the full path.\n *\n * `main` needs no special case for the same reason: neither provider sets a\n * pull-request target branch on a push, so a release run always verifies\n * everything before publishing. That falls out of the fallback rather than being\n * a second condition to keep in step.\n *\n * The base is a real `git merge-base`, not the provider's \"base SHA\" field. Both\n * providers expose something base-ish, but those drift once the target branch\n * moves ahead of where the PR started, and a merge-base is correct in both by\n * construction — one mechanism instead of two.\n *\n * It resolves the merge-base against `origin/<target>` first and, only if that\n * ref is absent, **fetches the target branch once** and retries against\n * `FETCH_HEAD`. Both providers are configured for a full-depth clone, so\n * `origin/<target>` is normally there — but if it ever is not, the bare fallback\n * would make every run take the full path while still reporting success, so this\n * step would look like it worked and verify nothing selectively, forever. One\n * fetch is a cheap way to not depend on that.\n *\n * Uses a plain string `replace` rather than a regex for the `refs/heads/` prefix\n * (Azure sends the full ref, GitHub the bare branch name): this whole command\n * has to survive quoting under both `cmd.exe` and POSIX `sh`, and a regex\n * literal would drag backslashes into that.\n */\nconst AFFECTED_OR_ALL_GUARD = `node -e \"const cp=require('node:child_process');const T='${VERIFY_TARGETS}';const all=()=>process.exit(cp.spawnSync('npx nx run-many -t '+T,{stdio:'inherit',shell:true}).status ?? 1);const ref=process.env.GITHUB_BASE_REF||process.env.SYSTEM_PULLREQUEST_TARGETBRANCH||'';if(!ref){console.log('Not a pull request - verifying EVERY project.');all()}const target=ref.replace('refs/heads/','');const mergeBase=r=>{const o=cp.spawnSync('git',['merge-base',r,'HEAD'],{encoding:'utf8'});return o.status===0?o.stdout.trim():''};let base=mergeBase('origin/'+target);if(!base){console.log('No origin/'+target+' ref - fetching it to resolve a merge-base.');cp.spawnSync('git',['fetch','--no-tags','origin',target],{stdio:'inherit'});base=mergeBase('FETCH_HEAD')}if(!base){console.log('Could not resolve a merge-base with '+target+' - verifying EVERY project.');all()}console.log('Pull request against '+target+' - verifying projects affected since '+base);process.exit(cp.spawnSync('npx nx affected -t '+T+' --base='+base,{stdio:'inherit',shell:true}).status ?? 1)\"`\n\n/**\n * The portable `node -e` one-liner that packs every app into\n * `dist/drop/<type>-<name>.zip` via each app's own `package` target.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}.\n * Skips cleanly when the workspace has no apps yet.\n *\n * **Detects a project the way Nx itself does — `apps/*\\/project.json`, an\n * `apps/*\\/package.json` carrying an `nx` key, OR an `apps/*\\/*.csproj` — not\n * `project.json` alone.** `@nx/node:application` and `@nx/react:application`\n * write no `project.json` at all: their targets are inferred, and\n * `add/*.ts`'s `addNxTargets` layers the `package` target on through the\n * manifest's own `nx.targets` field (see its doc comment) specifically to\n * avoid the project-name clash a second `project.json` would risk in a\n * TS-solution workspace. `@nx/dotnet` (C#) is inference-only for the same\n * reason but has no manifest at all to carry an `nx` key on — verified\n * against the real published package, whose own `createNodes` glob is keyed\n * on `.csproj` directly — so `csharp.ts` creates a minimal `project.json`\n * itself purely to carry mnci's own `package` target, which the plain\n * `hasProjectJson` check already covers once that file exists. The\n * `*.csproj` branch below exists for the case a user later drops that\n * `project.json`, or for a `.csproj` this CLI never generated at all: a\n * `project.json`-only check would go back to never seeing that project. A\n * `project.json`-only check historically never saw a `node-app` or\n * `react-app` project at all either: the step silently logs \"No apps to pack\n * - skipping\", `dist/drop` stays empty, and every step downstream\n * (`PublishBuildArtifacts`, the per-app build tag) has nothing to work with —\n * a green run with no artifact. Go, Python and Flutter apps all get a real\n * generator-written `project.json`, so they were never affected.\n */\nconst PACK_APPS_GUARD = 'node -e \"const fs=require(\\'node:fs\\');fs.mkdirSync(\\'dist/drop\\',{recursive:true});const hasProjectJson=fs.globSync(\\'apps/*/project.json\\').length>0;const hasInlineNx=fs.globSync(\\'apps/*/package.json\\').some((f)=>{try{return Boolean(JSON.parse(fs.readFileSync(f,\\'utf8\\')).nx)}catch{return false}});const hasCsproj=fs.globSync(\\'apps/*/*.csproj\\').length>0;if(!hasProjectJson&&!hasInlineNx&&!hasCsproj){console.log(\\'No apps to pack - skipping.\\');process.exit(0)}process.exit(require(\\'node:child_process\\').spawnSync(\\'npx nx run-many -t package\\',{stdio:\\'inherit\\',shell:true}).status ?? 1)\"'\n\n/**\n * The portable `node -e` one-liner that fails the run when the checkout is a\n * shallow clone, before `nx release` ever gets to run.\n *\n * @remarks\n * `release.version.fallbackCurrentVersionResolver: 'disk'` ({@link releaseConfig})\n * exists so a brand-new, never-tagged package's first release does not hard-error\n * the whole release graph — nx's own recommended mechanism for that case\n * (`--first-release`) is a one-shot CLI flag, not something a fixed, non-interactive\n * CI command can apply selectively to only the projects that actually need it, so\n * the config-level fallback is the only option that keeps `mnci add npm-lib` (or\n * `python-lib`, `flutter-lib`, `csharp-lib`) followed by an ordinary release working\n * out of the box.\n *\n * That fallback is dangerous for every OTHER project, the ones already published:\n * with `git.commit: false`, a manifest's on-disk version is permanently whatever the\n * generator scaffolded (`0.0.1`/`1.0.0`), so if nx cannot resolve a project's tag for\n * ANY reason it silently falls back to that stale disk value and proposes a version\n * relative to it — which reads as a normal release and can propose (and, if nothing\n * stops it, publish) a downgrade. Reproduced against a real multi-package workspace:\n * a dry run with the release tags unreachable from the current branch proposed\n * `0.2.0` against a published `0.7.0`.\n *\n * The generated CI is safe today ONLY because both providers unconditionally fetch\n * full history and tags before releasing, and release only ever runs from `main` —\n * but nothing in `nx.json` expresses that this is load-bearing, so a well-meaning\n * future edit to the checkout step (shallower fetch, a cache shortcut) would\n * silently reintroduce the downgrade path with no error, anywhere. This guard turns\n * that implicit dependency into an explicit, checked precondition: `nx release`\n * NEVER runs against a shallow checkout, full stop — a hard, loud, immediate failure\n * naming the fix, instead of a quiet wrong version discovered after the fact.\n *\n * @returns The full `node -e` shallow-clone guard one-liner.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\n/**\n * Proves npm will accept this workspace's token BEFORE `nx release` does\n * anything irreversible.\n *\n * @remarks\n * `nx release` versions, tags, pushes the tag, and only THEN publishes. So a\n * missing or rejected token does not simply fail the run - it leaves a version\n * tagged with nothing on the registry, and because the next release resolves\n * the current version from the newest tag, that version number is skipped\n * FOREVER. A real workspace lost `0.0.2` exactly that way, which is where this\n * guard comes from.\n *\n * `npm whoami` is the cheapest thing that exercises the whole auth path at\n * once: the secret reaching the step, `.npmrc`'s `${NODE_AUTH_TOKEN}`\n * expanding, and the registry accepting the token. Checking the variable alone\n * would pass for an expired or under-scoped token, which fails at exactly the\n * same late moment.\n *\n * The empty-token branch is separate from the rejected-token branch on\n * purpose: an unset secret renders as blank in the step's env log rather than\n * as `***`, so naming that case explicitly turns the most common setup mistake\n * into one sentence instead of a registry error to interpret.\n *\n * PUBLIC NPM ONLY. An Azure Artifacts feed answers reads anonymously, so\n * `whoami` there proves nothing about whether a WRITE would be accepted - and\n * its auth story is Basic-with-a-PAT, which this repo has already been wrong\n * about once. A guard that passes without testing anything is worse than no\n * guard, so the Azure case deliberately gets none.\n */\nconst NPM_AUTH_PREFLIGHT = 'node -e \"const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(fs.globSync(\\'packages/*/package.json\\').length===0){console.log(\\'No npm packages to release - skipping.\\');process.exit(0)}if(!process.env.NODE_AUTH_TOKEN){console.error(\\'NPM_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly NPM_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret. Use an npm Automation token - a Publish token is refused by 2FA in CI.\\');process.exit(1)}const r=cp.spawnSync(\\'npm\\',[\\'whoami\\',\\'--registry=https://registry.npmjs.org/\\'],{encoding:\\'utf8\\',shell:process.platform===\\'win32\\'});if(r.status!==0){console.error(\\'NPM_TOKEN is set but the registry rejected it - \\'+((r.stderr||\\'\\')+(r.stdout||\\'\\')).trim()+\\'. Check that it has not expired and that it grants publish rights on this scope.\\');process.exit(1)}console.log(\\'npm auth OK as \\'+r.stdout.trim())\"'\n\n/**\n * The preflight step body for a registry kind, or `''` where none applies.\n *\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` command, or `''` for a non-npm registry.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmAuthPreflight (registryKind: RegistryConfig['kind']): string {\n return registryKind === 'npm' ? NPM_AUTH_PREFLIGHT : ''\n}\n\n/**\n * Names the PyPI projects a release would have to CREATE, before it tags.\n *\n * @remarks\n * **There is deliberately no token check here**, and that is the whole reason\n * this guard looks nothing like {@link NPM_AUTH_PREFLIGHT}. PyPI has no\n * `whoami`: the only endpoint that authenticates is the upload itself, and a\n * bare `POST` to it answers `405` whether the credentials are good, bad or\n * absent - measured against the live endpoint. Anything calling itself a PyPI\n * auth preflight would therefore pass without testing anything, which is the\n * same reason the Azure case already gets no npm preflight. What CAN be\n * checked without publishing is the token's SHAPE: every PyPI API token\n * begins with `pypi-`, so a password or a truncated paste is caught here\n * rather than at upload time, after nx release has tagged.\n *\n * What it does test is the failure that actually happens. PyPI rate limits\n * NEW PROJECT creation per account, and `nx release` tags first and publishes\n * last, so a `429` lands after the tags are pushed: those versions are then\n * tagged with nothing published, and skipped for ever, because the next run\n * resolves the current version from the newest tag. The public JSON API says\n * which projects do not exist yet, with no credentials at all, so the step\n * names them up front and says what to do when it happens.\n *\n * It never fails on that count - a first publish has to create the project,\n * and blocking it would block every new package for ever - nor when PyPI\n * cannot be reached, since a release must not hinge on this step's own\n * network.\n *\n * Written as a `String.raw` template rather than the single-quoted style its\n * npm counterpart uses. The body is one long `node -e` script full of single\n * quotes, and it splits on a newline escape - hand-escaping both kinds is how\n * a guard acquires a silent syntax error that only a real release would\n * reveal. A plain template literal would turn that newline escape into a real\n * newline in the middle of a string literal, which is a parse error in the\n * script node then runs. `String.raw` passes both through untouched, so what\n * is written here is exactly what runs.\n *\n * NO COLON-SPACE ANYWHERE IN THE MESSAGES. The command is emitted as an\n * unquoted YAML plain scalar after `run:`/`script:`, so a `: ` inside it reads\n * as a nested mapping and the whole pipeline file stops parsing. Every message\n * here uses ` - ` where prose wants a colon; `https://` is fine, since only\n * colon-SPACE ends a scalar. `pipeline-drift.integration.spec.ts` parses both\n * generated files, so this is caught, but it is caught as an unhelpful\n * `bad indentation of a mapping entry`.\n *\n * PUBLIC PYPI ONLY, mirroring {@link npmAuthPreflight}. An Azure Artifacts\n * feed takes a PAT rather than a `pypi-` token and answers reads\n * anonymously, so neither half would mean anything there.\n */\nconst PYPI_RELEASE_PREFLIGHT = String.raw`node -e \"const fs=require('node:fs');const files=fs.globSync('python-packages/*/pyproject.toml');if(files.length===0){console.log('No Python packages to release - skipping.');process.exit(0)}if(!process.env.PYPI_TOKEN){console.error('PYPI_TOKEN is empty or unset, so the publish would fail AFTER nx release has already tagged. Add it as a repository secret named exactly PYPI_TOKEN - under Actions, not the Dependabot or Codespaces tab, and not an Environment secret.');process.exit(1)}if(!process.env.PYPI_TOKEN.startsWith('pypi-')){console.error('PYPI_TOKEN does not look like a PyPI API token - every one of them begins with pypi-. A password or a truncated paste is rejected only at upload time, which is AFTER nx release has tagged.');process.exit(1)}const names=files.map(f=>{const t=fs.readFileSync(f,'utf8');const b=t.slice(t.indexOf('[project]'));const l=b.split('\\n').find(x=>x.trim().startsWith('name'));return l?l.split('=')[1].replace(/[^a-zA-Z0-9._-]/g,''):''}).filter(Boolean);(async()=>{const fresh=[];for(const n of names){try{const r=await fetch('https://pypi.org/pypi/'+n.toLowerCase().replace(/[-_.]+/g,'-')+'/json',{signal:AbortSignal.timeout(10000)});if(r.status===404)fresh.push(n)}catch{console.log('Could not reach PyPI to check '+n+' - continuing.')}}if(fresh.length===0){console.log('Every Python package already exists on PyPI - this release creates none.');return}console.log('NOTE - this release may CREATE '+fresh.length+' new PyPI project(s) - '+fresh.join(', ')+'. Project creation is rate limited per account, and a 429 there arrives AFTER nx release has tagged - leaving those versions tagged with nothing published, and skipped forever, because the next run resolves the current version from the newest tag. If that happens, delete the tags for the versions that did not publish before releasing again. PyPI offers no way to check the limit, or the token, in advance.')})()\"`\n\n/**\n * The PyPI preflight step body for a registry kind, or `''` where none applies.\n *\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` command, or `''` for a non-public registry.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pypiReleasePreflight (registryKind: RegistryConfig['kind']): string {\n return registryKind === 'npm' ? PYPI_RELEASE_PREFLIGHT : ''\n}\n\nconst SHALLOW_CLONE_GUARD = 'node -e \"const r=require(\\'node:child_process\\').spawnSync(\\'git\\',[\\'rev-parse\\',\\'--is-shallow-repository\\'],{encoding:\\'utf8\\'});if(r.status!==0){console.error(\\'Could not determine whether this checkout has full history (git rev-parse --is-shallow-repository failed) - refusing to release. \\'+(r.stderr||\\'\\').trim());process.exit(1)}if(r.stdout.trim()===\\'true\\'){console.error(\\'This checkout is a shallow clone. nx release resolves the current version of each package from its git tag, and silently falls back to the permanently-stale on-disk version when a tag cannot be found - which can propose, and publish, a version DOWNGRADE. Fetch full history before releasing - set fetchDepth (Azure) or fetch-depth (GitHub) to 0.\\');process.exit(1)}\"'\n\n/**\n * Builds the portable `node -e` one-liner that versions, tags and publishes\n * every releasable project — `packages/*` (npm, C#, Dart) and\n * `python-packages/*` (Python) — via `nx release`.\n *\n * @remarks\n * Shared bit-for-bit by {@link azurePipelinesYaml} and {@link githubActionsYaml}\n * — `pythonPublishEnv` is the only provider-specific fragment (both providers\n * decode the same base64 `PAT` env var, so the fragment itself is identical\n * too; only the caller decides whether to inject it). Skips cleanly when\n * there is nothing to release (`nx release` hard-errors on an empty scope).\n *\n * **Every manifest shape `release.projects` can match must be counted here**,\n * and a Dart one is the reason that is stated rather than assumed. The four\n * globs mirror `releaseConfig`'s `['packages/*', 'python-packages/*']`: npm\n * (`package.json`), C# (`*.csproj`), Python (`pyproject.toml`) and Dart\n * (`pubspec.yaml`). A publishable `flutter-lib` lands in `packages/` with a\n * `pubspec.yaml` and **no `package.json`** (see `@mnci/nx-flutter`'s library\n * generator), so while it was missing here a Flutter-only workspace logged\n * \"Nothing to release - skipping\" and exited 0 on every single release run —\n * green, and never releasing. Dart is also the one kind with no publish step\n * at all (Azure Artifacts has no pub feed, so publishing IS the git tag),\n * which is exactly why nothing downstream would ever have surfaced the miss.\n *\n * A `vscode-extension` is the one releasable project outside those folders: it\n * lives in `apps/` and is in scope through its tag (#229), so it is counted by the\n * same tag, read from its `package.json`. Without it an extension-only workspace\n * would log \"Nothing to release\" for ever, the Dart failure again.\n *\n * The same count also feeds the `RELEASE_SPECIFIER` keyword check below, so\n * under-counting weakened that guard too: one npm lib plus one flutter lib\n * counted as 1, and a bare keyword — the input that silently under-bumps\n * interdependent packages — was accepted for a workspace with 2.\n *\n * @param pythonPublishEnv - A `node -e`-fragment that exports `TWINE_*` when\n * there are Python packages and a configured feed, or `''` to export nothing.\n * @param nugetPublishEnv - A `node -e`-fragment that exports `NUGET_PAT` when\n * there are C# packages and a configured feed, or `''` to export nothing.\n * @returns The full `node -e` release one-liner.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction releaseGuard (pythonPublishEnv: string, nugetPublishEnv: string): string {\n return String.raw`node -e \"const fs=require('node:fs'),cp=require('node:child_process');const npmCount=fs.globSync('packages/*/package.json').length;const csharpCount=fs.globSync('packages/*/*.csproj').length;const pythonCount=fs.globSync('python-packages/*/pyproject.toml').length;const dartCount=fs.globSync('packages/*/pubspec.yaml').length;const vscodeCount=fs.globSync('apps/*/package.json').filter(p=>{try{return(JSON.parse(fs.readFileSync(p,'utf8')).nx?.tags||[]).includes('${VSCODE_EXTENSION_TAG}')}catch{return false}}).length;const goCount=fs.globSync('apps/*/project.json').filter(p=>{try{return(JSON.parse(fs.readFileSync(p,'utf8')).tags||[]).includes('${GO_RELEASE_TAG}')}catch{return false}}).length;const hasNpm=npmCount>0;const hasPython=pythonCount>0;const hasCsharp=csharpCount>0;const hasDart=dartCount>0;const hasVscode=vscodeCount>0;const hasGo=goCount>0;if(!hasNpm&&!hasPython&&!hasCsharp&&!hasDart&&!hasVscode&&!hasGo){console.log('Nothing to release - skipping.');process.exit(0)}const specifier=process.env.RELEASE_SPECIFIER||'';let releaseCmd='npx nx release --yes';if(specifier){if(!/^(major|minor|patch|\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?)$/.test(specifier)){console.error('RELEASE_SPECIFIER value \\''+specifier+'\\' is invalid - use major, minor, patch, or an exact version like 1.2.3.');process.exit(1)}const releaseProjectCount=npmCount+csharpCount+pythonCount+dartCount+vscodeCount+goCount;if(/^(major|minor|patch)$/.test(specifier)&&releaseProjectCount>1){console.error('RELEASE_SPECIFIER is a keyword (\\''+specifier+'\\') but this workspace has '+releaseProjectCount+' releasable packages - a keyword under-bumps interdependent packages, because nx computes the dependency-bump pass from a stale cached version. Set RELEASE_SPECIFIER to an exact version instead, or clear it.');process.exit(1)}releaseCmd='npx nx release '+specifier+' --yes'}const env={...process.env};${pythonPublishEnv}${nugetPublishEnv}process.exit(cp.spawnSync(releaseCmd,{stdio:'inherit',shell:true,env}).status ?? 1)\"`\n}\n\n/**\n * The env var each registry kind carries a PyPI-side credential in.\n *\n * @remarks\n * Deliberately NOT the same variable as npm's. Azure Artifacts' feed is\n * multi-protocol, so one PAT authenticates both; public npm and public PyPI\n * are unrelated services with unrelated credentials, and conflating them is\n * how a workspace ends up sending its npm automation token to PyPI.\n */\nexport const PYPI_TOKEN_VARIABLE = 'PYPI_TOKEN'\n\n/**\n * The env var an Azure Artifacts workspace carries its base64-encoded PAT in.\n *\n * @remarks\n * Named once and shared by {@link npmAuthEnvVariable},\n * {@link pythonPublishEnvFragment} and {@link nugetPublishEnvFragment}, for the\n * same \"cannot drift between providers\" reason {@link PYPI_TOKEN_VARIABLE} is a\n * constant rather than three string literals.\n */\nexport const AZURE_PAT_VARIABLE = 'PAT'\n\n/**\n * The fail-fast an Azure Artifacts publish fragment opens with, so a missing\n * PAT stops the release instead of being decoded into an empty credential.\n *\n * @remarks\n * The same reasoning {@link pythonPublishEnvFragment} documents for public\n * PyPI, applied to the feed that had no check at all. Both Azure fragments used\n * to call `Buffer.from(process.env.PAT, 'base64')` unguarded, and an\n * unconfigured secret does not arrive as `undefined` - **both providers\n * substitute the empty string** - so the decode SUCCEEDED and produced `''`.\n *\n * For C# that was silent: `csharpLibPublishTarget()` self-gates on a falsy\n * `NUGET_PAT`, printing \"NuGet publish is not configured\" and exiting 0. Under\n * a tag-only release model that is the worst possible outcome - `nx release`\n * versions and TAGS the package, the publish step reports success, and nothing\n * reaches the feed. The next run resolves the current version from that tag and\n * bumps past it, so the skipped version is never retried and never exists.\n *\n * Checked here, before `nx release` runs, rather than inside the publish\n * target: once versioning has tagged, refusing to publish is already too late.\n *\n * @param countExpression - The in-fragment variable holding the package count.\n * @param subject - How the message names this ecosystem's packages.\n * @returns The `node -e` fragment fail-fast, for use inside an `if(has…){}`.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction azurePatPreflight (countExpression: string, subject: string): string {\n return `if(!process.env.${AZURE_PAT_VARIABLE}){console.error('This workspace has '+${countExpression}+' ${subject} package(s) to publish to Azure Artifacts but ${AZURE_PAT_VARIABLE} is empty. Add your base64-encoded Azure DevOps PAT as a secret named ${AZURE_PAT_VARIABLE}, or remove the ${subject} packages from release.projects.');process.exit(1)}`\n}\n\n/**\n * The extra `env:` line the release step needs to carry a PyPI token.\n *\n * @remarks\n * Only for the public-npm registry kind. An Azure Artifacts feed publishes\n * Python through the SAME multi-protocol feed and the same PAT the step\n * already exports, so adding a second variable there would suggest a second\n * credential exists when it does not.\n *\n * Rendered from one function for both providers, so the variable name cannot\n * drift between them - the same reason {@link npmAuthEnvVariable} exists.\n *\n * @param registryKind - The workspace's registry kind.\n * @param variableReference - Renders a named secret in the calling provider's\n * own syntax (Azure `$(NAME)`, GitHub `${{ secrets.NAME }}`).\n * @param indent - The indentation the provider's `env:` block uses.\n * @returns The rendered line, or `''` when no PyPI token applies.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pypiTokenEnvLine (\n registryKind: RegistryConfig['kind'],\n variableReference: (name: string) => string,\n indent: string,\n): string {\n return registryKind === 'npm'\n ? `\n${indent}${PYPI_TOKEN_VARIABLE}: ${variableReference(PYPI_TOKEN_VARIABLE)}`\n : ''\n}\n\n/**\n * Injected into {@link releaseGuard}: exports twine publish credentials when\n * the workspace has Python packages.\n *\n * @remarks\n * TWO genuinely different mechanisms, which is why this is not one template\n * with a swapped URL.\n *\n * Azure Artifacts is a multi-protocol feed: the same org/project/feed that\n * serves npm serves Python, so the upload URL is derived and the credential is\n * the same PAT, decoded from the base64 value both providers read.\n *\n * Public PyPI takes an API token under the literal username `__token__` - that\n * spelling is PyPI's own convention, not a placeholder - and needs NO\n * repository URL, since twine's default already points at\n * `upload.pypi.org/legacy/`. Writing one would be a chance to get it wrong for\n * no gain. `TWINE_NON_INTERACTIVE` is set because twine otherwise PROMPTS for\n * a missing password, and a prompt on a CI agent is a build that hangs until\n * it times out rather than one that fails.\n *\n * The empty-token check is a fail-fast rather than a credential probe. A HEAD\n * against the upload endpoint was considered and rejected: `twine check` only\n * validates the artefact, and an authenticated probe against the real registry\n * means a release step that can fail for reasons unrelated to this workspace.\n * The failure worth catching cheaply is the common one - the secret was never\n * configured - and that is knowable without a network call. A token that is\n * present but wrong still fails at upload, loudly, which is correct.\n *\n * @param pythonPublishUrl - The twine upload URL for an Azure Artifacts feed,\n * or `undefined` for any other registry.\n * @param registryKind - The workspace's registry kind.\n * @returns The `node -e` fragment for the workspace's registry kind.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonPublishEnvFragment (\n pythonPublishUrl: string | undefined,\n registryKind: RegistryConfig['kind'],\n): string {\n if (pythonPublishUrl !== undefined) {\n return `if(hasPython){${azurePatPreflight('pythonCount', 'Python')}env.TWINE_REPOSITORY_URL='${pythonPublishUrl}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}`\n }\n if (registryKind !== 'npm') {\n return ''\n }\n\n return `if(hasPython){if(!process.env.${PYPI_TOKEN_VARIABLE}){console.error('This workspace has '+pythonCount+' Python package(s) to publish but ${PYPI_TOKEN_VARIABLE} is empty. Add a PyPI API token as a secret named ${PYPI_TOKEN_VARIABLE}, or remove the Python packages from release.projects.');process.exit(1)}env.TWINE_USERNAME='__token__';env.TWINE_PASSWORD=process.env.${PYPI_TOKEN_VARIABLE};env.TWINE_NON_INTERACTIVE='1'}`\n}\n\n/**\n * Injected into {@link releaseGuard}: when there are C# packages and a\n * configured Azure feed, export the raw PAT `nuget.config`'s `%NUGET_PAT%`\n * placeholder reads (see {@link nugetConfigContent}'s remarks for why NuGet\n * takes the raw value, unlike `.npmrc`'s base64 `_password`).\n *\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @returns The `node -e` fragment, or `''` when there is no NuGet feed.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nugetPublishEnvFragment (nugetFeedUrl?: string): string {\n return nugetFeedUrl\n ? `if(hasCsharp){${azurePatPreflight('csharpCount', 'C#')}env.NUGET_PAT=Buffer.from(process.env.${AZURE_PAT_VARIABLE},'base64').toString()}`\n : ''\n}\n\n/**\n * The env var name + value pair that authenticates `npm ci`/`nx release publish`,\n * keyed by registry kind — two genuinely different secrets, never conflated.\n *\n * @remarks\n * Azure Artifacts' `.npmrc` (`npmrcContent`) reads a base64-encoded PAT via\n * `${PAT}`; public npm's reads a raw npm automation token via\n * `${NODE_AUTH_TOKEN}`. Before this, both {@link azurePipelinesYaml} and\n * {@link githubActionsYaml} always exported `PAT` regardless of registry —\n * harmless for Azure Artifacts, but silently non-functional for public npm\n * (nothing ever populated `NODE_AUTH_TOKEN`, so a public-npm workspace's CI\n * could build and version but never actually authenticate a publish).\n *\n * @param registryKind - The workspace's registry kind.\n * @param variableReference - Renders a named secret/variable in the calling\n * provider's own syntax (Azure `$(NAME)`, GitHub `${{ secrets.NAME }}`).\n * @returns A `[envVarName, value]` pair to render under the step's `env:` block.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmAuthEnvVariable (\n registryKind: RegistryConfig['kind'],\n variableReference: (name: string) => string,\n): [string, string] {\n return registryKind === 'npm'\n ? ['NODE_AUTH_TOKEN', variableReference('NPM_TOKEN')]\n : [AZURE_PAT_VARIABLE, variableReference(AZURE_PAT_VARIABLE)]\n}\n\n/**\n * Renders the `pool:` block body for a chosen build agent.\n *\n * @remarks\n * One CLI value drives it: Microsoft-hosted images start `ubuntu-`/`windows-`/\n * `macos-` (`ubuntu-latest`, `windows-2022`, `macos-13`, …) → `vmImage`;\n * anything else is treated as a self-hosted pool name → `name`. Either way the\n * pipeline's steps are OS-agnostic, so it runs unchanged on the chosen agent.\n *\n * @param agent - The vmImage or self-hosted pool name.\n * @returns The two-space-indented `pool:` child line.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function poolBlock (agent: string): string {\n return /^(?:ubuntu|windows|macos)-/i.test(agent) ? ` vmImage: ${agent}` : ` name: ${agent}`\n}\n\n/**\n * Builds the generated workspace's whole CI: one short Azure Pipelines file.\n *\n * @remarks\n * Runs unchanged on ANY agent OS (Linux, macOS, Windows): no bash, no\n * PowerShell — every step is a built-in task or a single-line\n * `git`/`npm`/`npx`/`node` command `cmd.exe` and `sh` execute identically.\n *\n * Every run first checks `nx sync:check` — a fast, explicit failure when\n * someone forgot to run `nx sync` (and commit the result) after adding a\n * cross-project import. `sync.applyChanges` in `nx.json` ({@link SYNC_CONFIG})\n * means that locally this almost never happens: Nx auto-applies the fix on the\n * next build/typecheck instead of just prompting.\n *\n * On `main` (non-PR) the pipeline: **packs every app** into `dist/drop/` as one\n * zip per app named `<type>-<name>.zip` (each app owns an `nx` `package`\n * target — {@link runAdd}), publishes `dist/drop` as the **`drop`** artifact,\n * emits one **build tag per app** (`##vso[build.addbuildtag]<type>-<name>`,\n * derived from the zip filenames so it is *exactly* the zip name — the classic\n * release/CD pipeline keys off it), then `nx release`s: **publish packages +\n * tag main** (versions from conventional commits, tag-only push).\n *\n * npm auth is one of two modes ({@link NpmAuthMode}). By default it is the base64\n * PAT from the `variableGroup` (default `Build`): the group exposes `$(PAT)`,\n * mapped as env on the npm steps and read by the root `.npmrc`'s `_password`\n * block, and `npmAuthenticate@0` is NOT used because it would overwrite that\n * password. With `build-identity` the file holds no password, so the task is added\n * before `npm ci` and injects the build service identity's token instead.\n *\n * Hard-won Azure lessons carried over:\n * - `checkout: self` detaches HEAD; re-attach with `git checkout -B` first or\n * `nx release` cannot push tags.\n * - Fetch all refs + tags up front (version resolution needs the tags).\n * - A git identity is required to create annotated tags on CI.\n * - One-time grants (project admin): *Project Collection Build Service* needs\n * *Contribute* on the repo (tag push); the PAT's owner needs feed *publish*.\n *\n * `nx release` versions BOTH `packages/*` (npm) and `python-packages/*`\n * (Python) from conventional commits and tags each — one unified release. When\n * `pythonPublishUrl` is set (Azure Artifacts — {@link pythonPublishUrl}), the\n * release step also exports `TWINE_*` so `nx release` publishes the Python\n * packages with `twine`, reusing the base64 `PAT` decoded to the raw token\n * twine/pypi basic-auth needs (no second secret; Azure accepts any username).\n * For the public-npm registry that env is omitted, so a Python package there\n * is still versioned + tagged but its publish needs user-provided `TWINE_*`.\n * Before any Python target runs, one guarded step installs the fixed toolchain\n * (`ruff`/`pytest`/`build`/`twine`) from the workspace's `requirements-dev.txt`\n * — written by `add/python.ts` on the first Python `add` — and a second\n * editable-installs every Python project into that same environment (the\n * pip-world counterpart of `npm install` hoisting every workspace package\n * into one root `node_modules`); both are skipped cleanly on a workspace with\n * no Python projects.\n *\n * @param agent - The build agent (vmImage or self-hosted pool name).\n * @param variableGroup - The Library variable group holding the base64 `PAT`.\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @param registryKind - The workspace's registry kind — selects `PAT` vs\n * `NPM_TOKEN` for the npm-authenticating steps.\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @param npmAuth - `build-identity` adds the `npmAuthenticate@0` step before\n * `npm ci` and drops the `PAT` mapping from that step; `pat` keeps both as they were.\n * @returns The full text of `azure-pipelines.yml`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function azurePipelinesYaml (\n agent: string,\n variableGroup: string,\n pythonPublishUrl?: string,\n registryKind: RegistryConfig['kind'] = 'azure-artifacts',\n nugetFeedUrl?: string,\n npmAuth: NpmAuthMode = 'pat',\n): string {\n // ENUMERATED CI reasons, never \"not a pull request\" — the Azure half of the\n // fix #22 made for GitHub, and the more exposed of the two.\n //\n // `ne(Build.Reason, 'PullRequest')` admits every reason that is not a PR, and\n // Azure documents EIGHT of those: Manual, Schedule, IndividualCI, BatchedCI,\n // BuildCompletion, ResourceTrigger, ValidateShelveset and CheckInShelveset. So\n // clicking *Run pipeline* on `main` — an ordinary Azure workflow, not an\n // exotic one — would publish packages and push release tags. GitHub's\n // equivalent needed someone to add a trigger first; this one never did.\n //\n // BOTH CI reasons are listed, and dropping either is a silent failure in the\n // opposite direction — releases would simply stop, with a green pipeline.\n // `BatchedCI` is the one that matters here: the trigger above sets\n // `batch: true`, and Azure documents BatchedCI as the reason for \"a Git push\n // ... and the Batch changes was selected\". `IndividualCI` stays because\n // batching only applies once a run is already in progress, so an unbatched\n // push is still the ordinary case.\n //\n // Verified against Microsoft's own docs rather than assumed: `in()` is\n // \"Evaluates True if left parameter is equal to any right parameter\", min 1 /\n // max N — and Azure defines a step's own `succeeded()` as\n // `in(variables['Agent.JobStatus'], ...)`, so it is valid in a step condition.\n const onMain = 'and(succeeded(), in(variables[\\'Build.Reason\\'], \\'IndividualCI\\', \\'BatchedCI\\'), eq(variables[\\'Build.SourceBranchName\\'], \\'main\\'))'\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, name => `$(${name})`)\n // Build-identity auth only exists for an Azure Artifacts feed; public npm has\n // nothing for the task to inject, so the mode is ignored there.\n const buildIdentity = npmAuth === 'build-identity' && registryKind === 'azure-artifacts'\n const npmAuthenticateStep = buildIdentity\n ? ` # Injects feed credentials into .npmrc using the BUILD SERVICE IDENTITY, so no\n # PAT is stored, rotated or encoded anywhere. This is what the feed accepts: its\n # publish endpoint advertises a Bearer scheme bound to login.windows.net, which\n # wants an Entra ID access token, and a PAT is not one. Must run before 'npm ci'.\n # Needs the build identity to be a Feed Publisher (Contributor) on the feed.\n - task: npmAuthenticate@0\n displayName: Authenticate npm against the feed (build identity, no PAT)\n inputs:\n workingFile: .npmrc\n\n`\n : ''\n const npmCiEnv = buildIdentity\n ? ''\n : `\n env:\n ${npmAuthName}: ${npmAuthValue}`\n\n return `name: monorepo-ci-$(Date:yyyyMMdd)$(Rev:.r)\n\n# Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'\n# versions from conventional commits and pushes ONLY a tag to main, and each\n# app is packed into dist/drop/<type>-<name>.zip by its own 'package' target.\n#\n# Cross-platform by construction: no bash, no PowerShell. Every step is a\n# built-in task or a single-line git/npm/npx/node command, so the pipeline\n# runs unchanged on Linux, macOS and Windows agents.\n\ntrigger:\n # While a run is in progress, queue further pushes and run them together rather\n # than concurrently. This is the closest thing Azure Pipelines YAML has to\n # GitHub's concurrency group, and it is load-bearing on main for the same\n # reason: two concurrent 'nx release' runs would race to create the same tag.\n batch: true\n branches:\n include:\n - main\n # EVERY other branch too, and that breadth is a correction rather than\n # generosity. On Azure Repos Git a YAML 'pr:' block does nothing at all\n # (see the note below), so a CI trigger is the only pre-merge\n # verification this file can switch on by itself. Listing main alone —\n # which is what this pipeline used to do, next to a 'pr:' block that\n # looked like it covered the rest — means a workspace verifies NOTHING\n # until a change has already reached the branch it was supposed to\n # protect.\n #\n # Release stays gated on main (the 'condition:' on every release step\n # below), so a topic-branch run installs, audits, syncs, formats,\n # verifies and packs, and publishes nothing.\n - '*'\n\n# PR validation is deliberately NOT configured here, because on Azure Repos Git\n# it CANNOT be: \"For an Azure Repos Git repo, you cannot configure a PR trigger\n# in the YAML file. You need to use branch policies.\" A 'pr:' block here is not\n# an error — it is silently ignored, which is worse, because the file then\n# documents a gate that does not exist. (The same block IS honoured for GitHub\n# and Bitbucket repos, which is why .github/workflows/ci.yml keeps its\n# 'pull_request:' trigger.)\n#\n# To get PR validation, configure it once in the UI: Project Settings ->\n# Repositories -> <repo> -> Policies -> <branch> -> Build Validation, pointing\n# at this pipeline. That is also the only thing that sets\n# System.PullRequest.TargetBranch, which the verify step below reads to scope\n# itself to affected projects — without a policy that variable is never set and\n# the step verifies every project instead. Correct either way, just slower.\n#\n# Cancelling a superseded PR run lives in that same policy (\"automatically\n# cancel\"), and likewise has no YAML expression.\npr: none\n\npool:\n${poolBlock(agent)}\n\nvariables:\n # Holds the npm auth secret the root .npmrc reads: the base64-encoded \\`PAT\\`\n # for an Azure Artifacts feed, or a raw npm automation token as \\`NPM_TOKEN\\`\n # for public npm. Mark it secret in Library. Add app build vars here too if needed.\n - group: ${variableGroup}\n # Relocates npm's cache inside the pipeline workspace so Cache@2 can restore it\n # (the default ~/.npm is outside the cacheable area on hosted agents). npm reads\n # this as an ordinary env var, so it needs no config file and works on every\n # agent OS.\n - name: npm_config_cache\n value: $(Pipeline.Workspace)/.npm\n # Overrides the bump nx would compute from conventional commits for one\n # release run — 'major', 'minor', 'patch', or an exact version ('1.2.3').\n # Defined here (rather than left unset) so $(RELEASE_SPECIFIER) always\n # expands: an Azure Pipelines macro that names an undefined variable is\n # left as the literal, unexpanded text '$(RELEASE_SPECIFIER)' rather than\n # empty, which would otherwise reach the release guard below as a bogus\n # value. Override it per run in Pipelines -> Run pipeline -> Variables, or\n # set a repo-level default in Pipeline -> Edit -> Variables. See the\n # release step below for why a bare keyword is unreliable once more than\n # one package is releasable.\n - name: RELEASE_SPECIFIER\n value: ''\n\nsteps:\n - checkout: self\n # Lets later steps push release tags back to the repo. The Project\n # Collection Build Service account needs Contribute permission on this\n # repo (Project Settings -> Repositories -> Security) — one-time grant.\n persistCredentials: true\n fetchDepth: 0\n\n # checkout leaves a detached HEAD; nx release needs a real branch.\n - script: git checkout -B $(Build.SourceBranchName)\n displayName: Attach HEAD to the source branch\n\n # Version resolution needs the release tags.\n - script: git fetch --all --prune --tags\n displayName: Fetch branches and release tags\n\n - script: git config user.name \"Azure Pipelines\" && git config user.email \"pipeline@dev.azure.com\"\n displayName: Set the git identity used for release tags\n\n - task: UseNode@1\n inputs:\n version: 24.x\n\n # The Azure counterpart of \\`cache: npm\\` on actions/setup-node: restores npm's\n # download cache so \\`npm ci\\` does not re-fetch every tarball on every run.\n # Keyed on the lockfile, so a dependency change misses and anything else hits;\n # restoreKeys falls back to the newest cache for this OS on a miss, which still\n # avoids a fully cold install. Agent.OS is in the key because a cached native\n # module built for one OS is not reusable on another.\n - task: Cache@2\n displayName: Cache npm packages\n inputs:\n key: 'npm | \"$(Agent.OS)\" | package-lock.json'\n restoreKeys: |\n npm | \"$(Agent.OS)\"\n path: $(npm_config_cache)\n\n${npmAuthenticateStep} - script: npm ci\n displayName: Install dependencies${npmCiEnv}\n\n # Fails ONLY on an advisory that has a published fix, at moderate or above;\n # anything upstream has not fixed is printed and passes. See NPM_AUDIT_STEP's\n # remarks for why that split replaced a warn-only step.\n - script: ${NPM_AUDIT_STEP}\n displayName: npm audit (fails on an actionable advisory)\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine, pip-audit)\n # — written by 'mnci add' to requirements-dev.txt on the first Python\n # project. Plain pip, no uv/Poetry: portable guard skips cleanly on a\n # workspace with none.\n - script: ${PYTHON_INSTALL_GUARD}\n displayName: Install Python dependencies (ruff, pytest, build, twine, pip-audit)\n\n # Editable-installs every Python project into one shared environment, the\n # pip-world counterpart of 'npm install' hoisting every workspace package\n # into one root node_modules — so a project that imports an internal lib\n # (normally vendored only at build time) can resolve that import at\n # lint/test time too. Portable guard skips cleanly on a workspace with none.\n - script: ${PYTHON_WORKSPACE_INSTALL_GUARD}\n displayName: Install Python project dependencies (editable, workspace-wide)\n\n # Non-blocking, same reasoning as the npm audit step above. Runs after the\n # workspace-wide install so it scans the workspace's actual dependency set,\n # not just the fixed toolchain.\n - script: ${PIP_AUDIT_GUARD}\n displayName: pip-audit (non-blocking)\n\n # Go, if the workspace has any: hosted agents ship the toolchain, so only\n # the module cache and the linter need seeding. Both guards skip cleanly on\n # a workspace with no root go.mod.\n - script: ${GO_MODULE_DOWNLOAD_GUARD}\n displayName: Download Go module dependencies\n\n # golangci-lint is what the generated Go lint target actually runs (the\n # plugin's own default is 'go fmt', which only reformats). The pinned,\n # checksum-verified prebuilt release lands in GOPATH/bin (go install at the\n # same version is the fallback), which is not on PATH by default on a hosted\n # agent — so prepend it for every later step in the job.\n - script: ${GOLANGCI_LINT_INSTALL_GUARD}\n displayName: Install golangci-lint\n\n - script: ${GO_TOOL_PATH_AZURE}\n displayName: Add Go tool bin to PATH\n\n # Flutter, if the workspace has any. Unlike Python and Go, hosted agents do\n # NOT ship the Flutter SDK, so this installs it (shallow git clone at a\n # pinned tag) and puts it on PATH. All three guards skip cleanly on a\n # workspace with no root pubspec.yaml, and the install also skips when an\n # SDK is already on PATH.\n - script: ${FLUTTER_SDK_INSTALL_GUARD}\n displayName: Install the Flutter SDK (${FLUTTER_SDK_VERSION})\n\n - script: ${FLUTTER_TOOL_PATH_AZURE}\n displayName: Add the Flutter SDK to PATH\n\n # One command resolves EVERY Dart dependency, internal and external, for the\n # whole workspace: the projects are pub workspace members, so this writes a\n # single root pubspec.lock they all share.\n - script: ${FLUTTER_PUB_GET_GUARD}\n displayName: Resolve Dart dependencies (one pub get for the whole workspace)\n\n # .NET, if the workspace has any. Azure's 'condition:' expression language\n # has no glob function, so this script step detects C# projects first and\n # hands the answer to the install task below through a pipeline variable —\n # see DOTNET_DETECT_AZURE's remarks for why this differs from every other\n # toolchain's self-gating 'node -e' guard.\n - script: ${DOTNET_DETECT_AZURE}\n displayName: Detect .NET projects\n\n - task: UseDotNet@2\n displayName: Install the .NET SDK (${DOTNET_SDK_VERSION})\n condition: eq(variables['hasDotnetProjects'], 'true')\n inputs:\n version: ${DOTNET_SDK_VERSION}\n\n # Fails fast, with an unambiguous message, when a stale TypeScript project\n # reference (or another sync generator's drift) was never synced+committed\n # locally — sync.applyChanges (nx.json) only auto-applies interactively, so\n # CI still needs its own explicit, early check rather than surfacing this as\n # a confusing failure buried inside the build step below.\n - script: npx nx sync:check\n displayName: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)\n\n # The one verify step, and deliberately the only one: affected projects on a\n # pull request, EVERY project on anything else (a push to main included, so a\n # release is always verified in full). Every fallback — no target branch, an\n # unresolvable merge-base — takes the full path, because a run that verifies\n # too little still reports green.\n #\n # 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets\n # below, so adding it back as its own step would only duplicate work — and on\n # a pull request it would re-lint every project, discarding the point of this.\n - script: ${AFFECTED_OR_ALL_GUARD}\n displayName: Verify (affected on a PR, every project on main)\n\n # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'\n # target. Portable guard: skip cleanly when the workspace has no apps yet.\n - script: ${PACK_APPS_GUARD}\n displayName: Pack all apps (one zip per app -> dist/drop)\n condition: ${onMain}\n\n - task: PublishBuildArtifacts@1\n displayName: Publish the drop (one zip per app)\n condition: ${onMain}\n inputs:\n PathtoPublish: $(Build.SourcesDirectory)/dist/drop\n ArtifactName: drop\n\n # One build tag per packed app, EXACTLY the zip name (type-name), so the\n # classic release pipeline knows which app to run for. Derived from the zip\n # filenames so the tag can never drift from the artifact.\n - script: node -e \"const fs=require('node:fs');const path=require('node:path');for(const f of fs.globSync('dist/drop/*.zip')){console.log('##vso[build.addbuildtag]'+path.basename(f,'.zip'))}\"\n displayName: Tag the run per app (type-name)\n condition: ${onMain}\n\n # nx release resolves each package's current version from its git tag, and\n # falls back to the (permanently stale, since git.commit is false) on-disk\n # version when a tag cannot be resolved — which happens whenever this\n # checkout lacks full history. This is currently guaranteed by fetchDepth 0\n # above plus the explicit fetch below, but neither is expressed as a\n # dependency anywhere else, so this makes it a hard, loud precondition\n # instead of a silently wrong version discovered after release.\n - script: ${SHALLOW_CLONE_GUARD}\n displayName: Verify this is a full checkout (nx release needs the real tag history)\n condition: ${onMain}\n${\n npmAuthPreflight(registryKind)\n ? `\n # Prove npm will accept us BEFORE anything irreversible happens. nx release\n # tags first and publishes last, so a rejected token leaves a version tagged\n # with nothing on the registry - and that number is then skipped forever,\n # because the next run resolves the current version from the newest tag.\n - script: ${npmAuthPreflight(registryKind)}\n displayName: Preflight — npm must accept the token before anything is tagged\n condition: ${onMain}\n env:\n NODE_AUTH_TOKEN: $(NPM_TOKEN)\n`\n : ''\n}${\n pypiReleasePreflight(registryKind)\n ? `\n # PyPI cannot be asked whether a token is good - the only endpoint that\n # authenticates is the upload itself - so this checks the token's SHAPE and\n # names the projects this release would have to CREATE. Project creation is\n # rate limited per account, and a 429 arrives AFTER nx release has tagged.\n - script: ${pypiReleasePreflight(registryKind)}\n displayName: Preflight — name the PyPI projects this release would create\n condition: ${onMain}\n env:\n PYPI_TOKEN: $(PYPI_TOKEN)\n`\n : ''\n}\n # Version + tag + publish, in one release, for npm (packages/*), Python\n # (python-packages/*) AND C# (packages/*/*.csproj) — conventional commits,\n # tag-only push. Portable guard: nx release errors on an empty scope, so\n # skip cleanly when there is nothing to release. When there are Python or\n # C# packages and an Azure feed, twine/NuGet publish credentials are\n # exported (raw PAT, decoded from the base64 variable).\n #\n # RELEASE_SPECIFIER (the pipeline variable above) overrides the bump nx\n # would compute from conventional commits for THIS run. In a workspace with\n # more than one releasable package, a bare keyword ('major'/'minor'/'patch')\n # is unreliable — nx versions interdependent packages in two passes (a\n # dependency-bump pass, then the specifier pass), and the second pass\n # computes from a version cached before the first pass ran, silently\n # landing back on the same patch bump either way. An exact version ('1.2.3')\n # does not have this problem, since both passes apply it verbatim — prefer\n # it whenever more than one package is releasable. The guard below fails\n # the run rather than under-bumping silently: clear the variable back to\n # '' once the override is no longer needed.\n - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl, registryKind), nugetPublishEnvFragment(nugetFeedUrl))}\n displayName: Release — version, tag and publish (npm + Python + C# + VS Code)\n condition: ${onMain}\n env:\n ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, name => `$(${name})`, ' '.repeat(6))}\n RELEASE_SPECIFIER: $(RELEASE_SPECIFIER)\n # A VS Code extension publishes with it; tools/vscode-extension.cjs skips the\n # Marketplace when it is unset, including when Azure leaves it as '$(VSCE_PAT)'.\n VSCE_PAT: $(VSCE_PAT)\n\n # nx release's own git push (release.git.push) is deliberately left off: it\n # only runs when a remote GitHub/GitLab Release is configured, which this\n # pipeline never does, so it would never push the tag the step above just\n # created. Pushed explicitly, unconditionally (a no-op when nothing released)\n # once tagging is guaranteed to have already happened.\n - script: git push origin --tags\n displayName: Push release tags (nx release's own push never runs without a remote Release configured)\n condition: ${onMain}\n`\n}\n\n/**\n * Builds the generated workspace's whole CI as a GitHub Actions workflow —\n * the GitHub-hosted equivalent of {@link azurePipelinesYaml}.\n *\n * @remarks\n * Same pipeline, same shared guard scripts ({@link PYTHON_INSTALL_GUARD},\n * {@link PYTHON_WORKSPACE_INSTALL_GUARD}, {@link PIP_AUDIT_GUARD},\n * {@link NPM_AUDIT_STEP}, {@link PACK_APPS_GUARD}, {@link releaseGuard}) —\n * only the provider syntax differs, so the two YAML files can never drift on\n * what CI actually does.\n * Two steps from the Azure version are dropped, both for reasons already\n * documented there:\n * - **Attach HEAD to a branch**: `actions/checkout` (unlike Azure's\n * `checkout: self`) already leaves a push-triggered run on the real branch,\n * not a detached HEAD, so there is nothing to re-attach.\n * - **Tag the run per app**: `##vso[build.addbuildtag]` is an Azure classic\n * Release-pipeline mechanism with no GitHub Actions equivalent; the `drop`\n * artifact (one zip per app inside it) is the portable substitute.\n *\n * Auth is a single repository (or environment) secret — `PAT` for an Azure\n * Artifacts feed, or `NPM_TOKEN` (a raw npm automation token) for public npm\n * — GitHub has no \"variable group\" concept, so unlike the Azure version this\n * needs no CLI-collected name, just a secret the user creates once in the\n * repo settings, read here as `secrets.PAT`/`secrets.NPM_TOKEN`.\n * `permissions: contents: write` is what lets the checkout's own token push\n * the release tag back (no `persistCredentials` step to opt into — GitHub's\n * checkout wires this up from the job's `permissions` automatically).\n *\n * @param agent - The build agent — reused as-is for `runs-on:` (GitHub's\n * hosted runner labels, e.g. `ubuntu-latest`, already match the common Azure\n * vmImage names; anything else is passed through as a self-hosted runner\n * label).\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @param registryKind - The workspace's registry kind — selects `PAT` vs\n * `NPM_TOKEN` for the npm-authenticating steps.\n * @param ci - Which CI provider(s) the workspace generates a pipeline for.\n * @param nugetFeedUrl - The NuGet v3 feed URL for C# packages, or\n * `undefined` to leave NuGet publishing unconfigured (public npm).\n * @returns The full text of `.github/workflows/ci.yml`.\n * @throws Never - performs a pure mapping with no I/O.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function githubActionsYaml (\n agent: string,\n pythonPublishUrl?: string,\n registryKind: RegistryConfig['kind'] = 'azure-artifacts',\n ci: CiProvider = 'github',\n nugetFeedUrl?: string,\n): string {\n // `== 'push'`, not `!= 'pull_request'`. Identical today — the generated workflow\n // has exactly two triggers, `push` and `pull_request` — but the negative form\n // says \"anything that is not a PR\", which quietly means \"and any trigger anyone\n // adds later\". Add a `workflow_dispatch` or a `schedule` to a workflow written\n // the negative way and clicking *Run workflow* starts publishing packages and\n // pushing release tags, with nothing in the file hinting that it would.\n //\n // Not hypothetical: mnci's own workflow hand-added `workflow_dispatch` for its\n // Windows e2e job and inherited exactly that hazard. The positive form states\n // the actual intent — release on a push to main — and cannot be widened by\n // accident.\n const onMain = 'github.event_name == \\'push\\' && github.ref_name == \\'main\\''\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(\n registryKind,\n name => `\\${{ secrets.${name} }}`,\n )\n // Matches releaseConfig(ci)'s own condition exactly — GitHub Release\n // creation (and therefore Nx's own tag push) is only ever on when GitHub\n // Actions is the *only* configured provider; see releaseConfig's remarks.\n const githubReleases = ci === 'github'\n\n return `name: CI\n\n# Generated by MoNecromanCI. Deliberately thin: Nx builds, 'nx release'\n# versions from conventional commits and pushes ONLY a tag to main, and each\n# app is packed into dist/drop/<type>-<name>.zip by its own 'package' target.\n# The GitHub Actions equivalent of azure-pipelines.yml — see there for the\n# fuller rationale; both stay hand-kept in lockstep, there is no shared template.\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n # One run at a time per ref, but \\`cancel-in-progress\\` is an **expression** on\n # purpose rather than a flat \\`true\\`.\n #\n # A superseded PR run is pure waste, so cancel it. A run on \\`main\\` must never\n # be cancelled: it publishes packages and pushes release tags, and killing it\n # part-way can leave a tag pushed with the publish only half done — a state no\n # rerun repairs cleanly, because the version is then already tagged. Those runs\n # queue instead, which also stops two \\`nx release\\` invocations racing to create\n # the same tag when two commits land close together.\n group: \\${{ github.workflow }}-\\${{ github.ref }}\n cancel-in-progress: \\${{ github.event_name == 'pull_request' }}\n\npermissions:\n # Lets the release step push the version tag nx release creates back to main,\n # and (github-only provider) create the GitHub Release itself.\n contents: write\n # Lets azure/login exchange this job's OIDC token for a Microsoft Entra ID one,\n # which is how a VS Code extension reaches the Marketplace with no stored secret.\n # A token minted on a pull request cannot use it: the federated credential is\n # pinned to refs/heads/main.\n id-token: write\n\njobs:\n ci:\n runs-on: ${agent}\n steps:\n - uses: actions/checkout@${ACTION_VERSIONS['actions/checkout']}\n with:\n fetch-depth: 0\n\n # Version resolution needs the release tags.\n - run: git fetch --all --prune --tags\n name: Fetch branches and release tags\n\n - run: git config user.name \"github-actions[bot]\" && git config user.email \"github-actions[bot]@users.noreply.github.com\"\n name: Set the git identity used for release tags\n\n - uses: actions/setup-node@${ACTION_VERSIONS['actions/setup-node']}\n with:\n node-version: ${NODE_VERSION}\n # Caches ~/.npm keyed on package-lock.json, so \\`npm ci\\` restores from\n # the local cache instead of re-downloading every tarball on every run.\n # Nothing to invalidate by hand: the action keys on the lockfile, so a\n # dependency change misses the cache and a no-op change hits it.\n cache: npm\n\n # setup-node installs whatever npm the Node image bundles, and npm's major\n # is part of the contract: npm 11 reuses an already-installed tree instead\n # of re-resolving, so it applies \\`overrides\\` differently from npm 10. See\n # NPM_VERSION's remarks for the measurement.\n - run: npm install -g npm@${NPM_VERSION}\n name: Pin npm to the major mnci verifies against\n\n - run: npm ci\n name: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Fails ONLY on an advisory that has a published fix, at moderate or\n # above; anything upstream has not fixed is printed and passes. See\n # NPM_AUDIT_STEP's remarks for why that split replaced a warn-only step.\n - run: ${NPM_AUDIT_STEP}\n name: npm audit (fails on an actionable advisory)\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine,\n # pip-audit) — written by 'mnci add' to requirements-dev.txt on the\n # first Python project. Plain pip, no uv/Poetry: portable guard skips\n # cleanly on a workspace with none.\n - run: ${PYTHON_INSTALL_GUARD}\n name: Install Python dependencies (ruff, pytest, build, twine, pip-audit)\n\n # Editable-installs every Python project into one shared environment, the\n # pip-world counterpart of 'npm install' hoisting every workspace package\n # into one root node_modules — so a project that imports an internal lib\n # (normally vendored only at build time) can resolve that import at\n # lint/test time too. Portable guard skips cleanly on a workspace with none.\n - run: ${PYTHON_WORKSPACE_INSTALL_GUARD}\n name: Install Python project dependencies (editable, workspace-wide)\n\n # Non-blocking, same reasoning as the npm audit step above. Runs after\n # the workspace-wide install so it scans the workspace's actual\n # dependency set, not just the fixed toolchain.\n - run: ${PIP_AUDIT_GUARD}\n name: pip-audit (non-blocking)\n\n # Go, if the workspace has any: hosted runners ship the toolchain, so\n # only the module cache and the linter need seeding. Both guards skip\n # cleanly on a workspace with no root go.mod.\n - run: ${GO_MODULE_DOWNLOAD_GUARD}\n name: Download Go module dependencies\n\n # golangci-lint is what the generated Go lint target actually runs (the\n # plugin's own default is 'go fmt', which only reformats). The pinned,\n # checksum-verified prebuilt release lands in GOPATH/bin (go install at the\n # same version is the fallback), which is not on PATH by default on a\n # hosted runner — so publish it for every later step in the job.\n - run: ${GOLANGCI_LINT_INSTALL_GUARD}\n name: Install golangci-lint\n\n - run: ${GO_TOOL_PATH_GITHUB}\n name: Add Go tool bin to PATH\n\n # Flutter, if the workspace has any. Unlike Python and Go, hosted runners\n # do NOT ship the Flutter SDK, so this installs it (shallow git clone at\n # a pinned tag) and puts it on PATH. All three guards skip cleanly on a\n # workspace with no root pubspec.yaml, and the install also skips when an\n # SDK is already on PATH.\n - run: ${FLUTTER_SDK_INSTALL_GUARD}\n name: Install the Flutter SDK (${FLUTTER_SDK_VERSION})\n\n - run: ${FLUTTER_TOOL_PATH_GITHUB}\n name: Add the Flutter SDK to PATH\n\n # One command resolves EVERY Dart dependency, internal and external, for\n # the whole workspace: the projects are pub workspace members, so this\n # writes a single root pubspec.lock they all share.\n - run: ${FLUTTER_PUB_GET_GUARD}\n name: Resolve Dart dependencies (one pub get for the whole workspace)\n\n # .NET, if the workspace has any. actions/setup-dotnet is the\n # maintained, cached installer GitHub itself ships, so this uses it\n # directly rather than a hand-rolled 'node -e' guard — see\n # DOTNET_DETECT_AZURE's remarks for the full reasoning. hashFiles()\n # gates it inline, so a JS-only workspace pays nothing.\n - uses: actions/setup-dotnet@${ACTION_VERSIONS['actions/setup-dotnet']}\n if: \\${{ hashFiles('apps/*/*.csproj', 'packages/*/*.csproj', 'libs/*/*.csproj') != '' }}\n with:\n dotnet-version: ${DOTNET_SDK_VERSION}\n\n # Fails fast, with an unambiguous message, when a stale TypeScript project\n # reference (or another sync generator's drift) was never synced+committed\n # locally — sync.applyChanges (nx.json) only auto-applies interactively, so\n # CI still needs its own explicit, early check rather than surfacing this as\n # a confusing failure buried inside the build step below.\n - run: npx nx sync:check\n name: Verify the workspace is synced (run 'npx nx sync' locally and commit if this fails)\n\n # The one verify step, and deliberately the only one: affected projects on\n # a pull request, EVERY project on anything else (a push to main included,\n # so a release is always verified in full). Every fallback — no target\n # branch, an unresolvable merge-base — takes the full path, because a run\n # that verifies too little still reports green.\n #\n # 'npm run lint' is 'nx run-many -t lint', a strict subset of the targets\n # below, so adding it back as its own step would only duplicate work — and\n # on a pull request it would re-lint every project, discarding the point\n # of this.\n - run: ${AFFECTED_OR_ALL_GUARD}\n name: Verify (affected on a PR, every project on main)\n\n # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'\n # target. Portable guard: skip cleanly when the workspace has no apps yet.\n - run: ${PACK_APPS_GUARD}\n name: Pack all apps (one zip per app -> dist/drop)\n if: \\${{ ${onMain} }}\n\n - uses: actions/upload-artifact@${ACTION_VERSIONS['actions/upload-artifact']}\n if: \\${{ ${onMain} }}\n with:\n name: drop\n path: dist/drop\n if-no-files-found: ignore\n\n # nx release resolves each package's current version from its git tag,\n # and falls back to the (permanently stale, since git.commit is false)\n # on-disk version when a tag cannot be resolved — which happens\n # whenever this checkout lacks full history. This is currently\n # guaranteed by fetch-depth 0 above plus the explicit fetch below, but\n # neither is expressed as a dependency anywhere else, so this makes it\n # a hard, loud precondition instead of a silently wrong version\n # discovered after release.\n - run: ${SHALLOW_CLONE_GUARD}\n name: Verify this is a full checkout (nx release needs the real tag history)\n if: \\${{ ${onMain} }}\n${\n npmAuthPreflight(registryKind)\n ? `\n # Prove npm will accept us BEFORE anything irreversible happens. nx\n # release tags first and publishes last, so a rejected token leaves a\n # version tagged with nothing on the registry - and that number is then\n # skipped forever, because the next run resolves the current version\n # from the newest tag.\n - run: ${npmAuthPreflight(registryKind)}\n name: Preflight — npm must accept the token before anything is tagged\n if: \\${{ ${onMain} }}\n env:\n NODE_AUTH_TOKEN: \\${{ secrets.NPM_TOKEN }}\n`\n : ''\n}${\n pypiReleasePreflight(registryKind)\n ? `\n # PyPI cannot be asked whether a token is good - the only endpoint that\n # authenticates is the upload itself - so this checks the token's SHAPE\n # and names the projects this release would have to CREATE. Project\n # creation is rate limited per account, and a 429 arrives AFTER nx\n # release has tagged.\n - run: ${pypiReleasePreflight(registryKind)}\n name: Preflight — name the PyPI projects this release would create\n if: \\${{ ${onMain} }}\n env:\n PYPI_TOKEN: \\${{ secrets.PYPI_TOKEN }}\n`\n : ''\n}\n # Version + tag + publish, in one release, for npm (packages/*), Python\n # (python-packages/*) AND C# (packages/*/*.csproj) — conventional\n # commits, tag-only push. Portable guard: nx release errors on an empty\n # scope, so skip cleanly when there is nothing to release. When there\n # are Python or C# packages and an Azure feed, twine/NuGet publish\n # credentials are exported (raw PAT, decoded from the base64 secret).\n #\n # RELEASE_SPECIFIER (repository variable, Settings -> Secrets and\n # variables -> Actions -> Variables) overrides the bump nx would compute\n # from conventional commits for THIS run: 'major', 'minor', 'patch', or\n # an exact version ('1.2.3'). In a workspace with more than one\n # releasable package, a bare keyword is unreliable — nx versions\n # interdependent packages in two passes (a dependency-bump pass, then\n # the specifier pass), and the second pass computes from a version\n # cached before the first pass ran, silently landing back on the same\n # patch bump either way. An exact version does not have this problem,\n # since both passes apply it verbatim — prefer it whenever more than one\n # package is releasable. The guard below fails the run rather than\n # under-bumping silently: unset or clear the variable once the override\n # is no longer needed.${\n githubReleases\n ? `\n # This provider also creates a per-project GitHub Release (changelog\n # generated from conventional commits since that project's last tag),\n # which is why 'nx release' pushes the tag itself here — see\n # releaseConfig's remarks for why that is safe on this Nx version and\n # why every other provider combination keeps the explicit push step below.`\n : ''\n }\n # Signs in to Microsoft Entra ID as the Marketplace publishing identity (an\n # app registration with a federated credential for this repository's main\n # branch), so the release step publishes VS Code extensions with\n # 'vsce publish --azure-credential' and no token. Runs only when the\n # AZURE_CLIENT_ID and AZURE_TENANT_ID repository variables exist and the\n # workspace has an extension (every one ships a .vscodeignore). No Azure\n # subscription is needed. Setup: README, 'VS Code extensions'.\n - uses: azure/login@${ACTION_VERSIONS['azure/login']}\n name: Sign in to Microsoft Entra ID (VS Code Marketplace)\n if: \\${{ ${onMain} && vars.AZURE_CLIENT_ID != '' && hashFiles('apps/*/.vscodeignore') != '' }}\n with:\n client-id: \\${{ vars.AZURE_CLIENT_ID }}\n tenant-id: \\${{ vars.AZURE_TENANT_ID }}\n allow-no-subscriptions: true\n\n - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl, registryKind), nugetPublishEnvFragment(nugetFeedUrl))}\n name: Release — version, tag${githubReleases ? ', publish and GitHub Release' : ' and publish'} (npm + Python + C# + VS Code)\n if: \\${{ ${onMain} }}\n env:\n ${npmAuthName}: ${npmAuthValue}${pypiTokenEnvLine(registryKind, name => `\\${{ secrets.${name} }}`, ' '.repeat(10))}\n RELEASE_SPECIFIER: \\${{ vars.RELEASE_SPECIFIER }}\n # A VS Code extension publishes with Entra ID when the sign-in step\n # above ran (VSCE_AUTH=entra), else with the VSCE_PAT token;\n # tools/vscode-extension.cjs skips the Marketplace with neither.\n VSCE_AUTH: \\${{ vars.AZURE_CLIENT_ID != '' && 'entra' || '' }}\n VSCE_PAT: \\${{ secrets.VSCE_PAT }}${\n githubReleases\n ? `\n GITHUB_TOKEN: \\${{ secrets.GITHUB_TOKEN }}`\n : ''\n }\n${\n githubReleases\n ? `\n # A releasable Go app (mnci add go-app --release) is versioned by its git\n # tag, and its per-platform zips are attached to the GitHub Release the step\n # above just created. Built here, after tagging, with VERSION set to the tag's\n # version so the binary reports it. A run that released no Go app uploads\n # nothing, and a workspace without one skips the step.\n - run: node tools/go-app-release.cjs assets\n name: Attach the per-platform zips to the GitHub Release (releasable Go apps)\n if: \\${{ ${onMain} && hashFiles('tools/go-app-release.cjs') != '' }}\n env:\n GH_TOKEN: \\${{ secrets.GITHUB_TOKEN }}\n`\n : `\n # nx release's own git push (release.git.push) is deliberately left off: it\n # only runs when a remote GitHub/GitLab Release is configured, which this\n # workflow never does, so it would never push the tag the step above just\n # created. Pushed explicitly, unconditionally (a no-op when nothing released)\n # once tagging is guaranteed to have already happened.\n - run: git push origin --tags\n name: Push release tags (nx release's own push never runs without a remote Release configured)\n if: \\${{ ${onMain} }}\n`\n}`\n}\n\n/** Where a `pip` project's manifest can live, relative to the workspace root. */\nconst PIP_PROJECT_GLOBS = ['apps', 'python-packages', 'libs'] as const\n/** Where a `pub` project's manifest can live, relative to the workspace root. */\nconst PUB_PROJECT_GLOBS = ['apps', 'packages', 'libs'] as const\n/** Manifest filenames that mark a directory as belonging to each ecosystem. */\nconst ECOSYSTEM_MANIFESTS = {\n pip: ['pyproject.toml', 'requirements.txt'],\n pub: ['pubspec.yaml'],\n} as const\n\n/**\n * Whether the workspace has at least one project of the given ecosystem.\n *\n * @remarks\n * Detection rather than assumption, and that distinction is the whole point of\n * {@link dependabotConfig} — see there.\n *\n * @param workspaceRoot - The workspace to scan.\n * @param roots - The directories whose immediate children are projects.\n * @param manifests - Filenames that mark a project of this ecosystem.\n * @returns `true` when any project directory holds one of the manifests.\n * @throws Never - a missing directory reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction hasEcosystemProject (\n workspaceRoot: string,\n roots: readonly string[],\n manifests: readonly string[],\n): boolean {\n return roots.some(root => {\n const base = join(workspaceRoot, root)\n if (!existsSync(base)) {\n return false\n }\n\n return readdirSync(base, { withFileTypes: true }).some(\n entry =>\n entry.isDirectory() &&\n manifests.some(manifest => existsSync(join(base, entry.name, manifest))),\n )\n })\n}\n\n/**\n * One `updates:` entry, covering a whole ecosystem by glob.\n *\n * @param ecosystem - The `package-ecosystem` value.\n * @param roots - Directories whose immediate children hold the manifests.\n * @returns The YAML block, with a leading blank line.\n * @throws Never - string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction dependabotBlock (ecosystem: string, roots: readonly string[]): string {\n return (\n `\\n - package-ecosystem: ${ecosystem}\\n directories:\\n` +\n roots.map(root => ` - \"/${root}/*\"\\n`).join('') +\n ' schedule:\\n interval: weekly\\n'\n )\n}\n\n/**\n * The `.github/dependabot.yml` written for GitHub-hosted workspaces.\n *\n * @remarks\n * Cheap, high-signal hygiene every generated workspace gets automatically —\n * this very repo shipped without one and GitHub had to flag vulnerabilities\n * after the fact on every push instead of proposing update PRs proactively.\n * Dependabot is a GitHub-native feature (no app install, unlike Renovate), so\n * it is written only for `github`/`both` workspaces.\n *\n * `npm` and `github-actions` are unconditional, both at the workspace root:\n * `npm ci` installs every `packages/*` project from the one root lockfile, and\n * the actions entry patches the workflow {@link githubActionsYaml} writes.\n *\n * **`pip` and `pub` are emitted only when such a project actually exists, and\n * that is a bug fix rather than a refinement.** These blocks used to be written\n * unconditionally, on the stated reasoning that \"a glob matching nothing yet is\n * not an error — Dependabot simply finds no manifest there yet\". That claim was\n * false, and nothing ever re-checked it. Measured against this repo's own\n * Dependabot Updates history: the `pip` job failed on EVERY weekly run for over\n * a month, and `pub` on every run since it was added, while `npm_and_yarn` and\n * `github_actions` succeeded alongside them. An ecosystem entry whose globs\n * match no manifest is a hard failure, so every generated workspace without a\n * Python or Dart project produced two red Dependabot runs a week, for the whole\n * of its life, over projects it does not have.\n *\n * The globs are kept for the ecosystems that ARE emitted, so a second Python\n * project needs no rewrite. What changed is only whether the block appears at\n * all. `registerProjectCommands` re-runs this after every `mnci add`, so the\n * block appears the moment the first such project lands, and `mnci upgrade`\n * back-fills an existing workspace.\n *\n * @param workspaceRoot - The workspace to scan for Python and Dart projects.\n * @returns The YAML to write to `.github/dependabot.yml`.\n * @throws Never - a missing directory reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function dependabotConfig (workspaceRoot: string): string {\n let config = `# Generated by MoNecromanCI. Weekly dependency-update PRs so vulnerable or\n# stale dependencies surface as a reviewable PR instead of only a push-time\n# warning.\n#\n# pip and pub appear here only once the workspace HAS a Python or Dart project.\n# An ecosystem entry whose directories match no manifest is a hard Dependabot\n# failure, not a no-op, so emitting them unconditionally cost every workspace\n# two red runs a week over projects it does not have.\nversion: 2\nupdates:\n - package-ecosystem: npm\n directory: \"/\"\n schedule:\n interval: weekly\n\n - package-ecosystem: github-actions\n directory: \"/\"\n schedule:\n interval: weekly\n`\n if (hasEcosystemProject(workspaceRoot, PIP_PROJECT_GLOBS, ECOSYSTEM_MANIFESTS.pip)) {\n config += dependabotBlock('pip', PIP_PROJECT_GLOBS)\n }\n // Dart projects are pub workspace members, so each declares its own\n // dependencies even though they all resolve through one root pubspec.lock —\n // hence per-project directories rather than just \"/\".\n if (hasEcosystemProject(workspaceRoot, PUB_PROJECT_GLOBS, ECOSYSTEM_MANIFESTS.pub)) {\n config += dependabotBlock('pub', PUB_PROJECT_GLOBS)\n }\n\n return config\n}\n\n/**\n * Options for {@link applyOverlay}.\n *\n * @remarks\n * Collected by `mnci new`'s flags or prompts.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface OverlayOptions {\n /** The monorepo workspace name. */\n workspaceName: string\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope: string\n /** Where publishable packages are released to. */\n registry: RegistryConfig\n /** The CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent: string\n /** The Library variable group holding the base64 npm `PAT` (e.g. `Build`). */\n variableGroup: string\n /** Which CI provider(s) to write a pipeline file for. */\n ci: CiProvider\n /** The stack (TS major, linter, test runner) chosen at `new`. */\n stack: StackConfig\n /**\n * How npm authenticates against an Azure Artifacts feed. Absent means `pat`, so\n * a workspace generated before this existed keeps the file it has.\n */\n npmAuth?: NpmAuthMode\n}\n\n/**\n * Files `create-nx-workspace` scaffolds that mnci deliberately replaces.\n *\n * @remarks\n * Each entry duplicates something mnci owns, and leaving it in place is not\n * merely untidy — in the `.prettierrc` case it silently WINS:\n *\n * - **`.prettierrc`** — Nx writes `{ \"singleQuote\": true }`. Prettier resolves\n * `.prettierrc` before `.prettierrc.json`, so every option in\n * mnci's own formatting opinion was being ignored in every generated workspace.\n * Deleting it is what makes mnci's formatting opinion take effect at all.\n * - **`.vscode/`** — `extensions.json` lists the same recommendations the\n * `<workspace>.code-workspace` file already carries, so VS Code shows the\n * prompt twice and the two drift apart. mnci owned this file once\n * (`.vscode/extensions.json`), then moved to the single-file workspace and\n * never cleaned up the old location.\n * - **the AI-agent scaffolding** — `create-nx-workspace` 23.x writes ten\n * entries of it, and three copies of its `monitor-ci` scripts fail\n * `@mnci/eslint-config` with 36 errors (`unicorn/prefer-number-coercion`,\n * `unicorn/prefer-simple-condition-first`). So `mnci new` ended with\n * \"eslint could not format '.' (exit code 1)\" and `npm run lint` was RED on\n * a pristine workspace. Measured on 23.2.1.\n *\n * On that last one, two things were tried first and are recorded so nobody\n * repeats them:\n *\n * 1. `--aiAgents=none`, which `create-nx-workspace --help` documents (\"Use\n * \\\"none\\\" to skip\"). It does nothing on 23.2.1: a workspace generated with\n * it holds byte-identical scaffolding to one generated without it, in both\n * the `=none` and the space-separated array form. Verified against the real\n * binary, three times.\n * 2. Ignoring the files in `mnci/ignores` instead. That leaves files in the\n * repository the workspace's own linter disowns, which is exactly the\n * fragmentation the single root config exists to end.\n *\n * The list has to be COMPLETE, and that is the subtle part. Nx prints \"Your AI\n * agent configuration is outdated\" after a task run when an agent has both an\n * MCP config and rules present but would change if regenerated. Delete some of\n * the set and that condition still holds, so every `nx` command nags forever;\n * delete all of it and nothing is detected at all. `.github/agents` and\n * `.github/prompts` are the two easiest to miss — verified by deleting the full\n * set from a real workspace and confirming `nx show projects` and\n * `nx run-many -t build` print nothing.\n *\n * A user who wants them back runs `nx configure-ai-agents`, which is the\n * supported way in and is what the nag itself recommends.\n *\n * Removal is idempotent and safe on a workspace where they are already gone,\n * which is what makes `mnci upgrade` able to repair an existing workspace.\n */\nconst NX_SCAFFOLDING_TO_REMOVE = [\n '.prettierrc',\n '.prettierrc.json',\n '.vscode',\n // See LOCAL_REGISTRY_SCAFFOLDING: the config half of a publishing mechanism\n // this workspace does not use.\n '.verdaccio',\n // The AI-agent set, in full. See the remarks above: a partial delete leaves\n // every `nx` command printing an \"outdated configuration\" nag.\n '.agents',\n '.codex',\n '.cursor',\n '.gemini',\n '.opencode',\n '.github/agents',\n '.github/prompts',\n '.github/skills',\n 'opencode.json',\n // `.claude`, `AGENTS.md` and `CLAUDE.md` are NOT here. See\n // {@link NX_AUTHORED_AGENT_FILES} and {@link removeNxAuthoredAgentFiles}:\n // all three are paths a user writes by hand, so they are removed by what\n // they CONTAIN rather than by where they are.\n] as const\n\n/**\n * The marker Nx wraps its own agent rules in, and the file it owns outright.\n *\n * @remarks\n * `nx configure-ai-agents` writes its rules into `AGENTS.md` and `CLAUDE.md`\n * between a start and an end comment, and tells the reader to leave them\n * alone so the block can be updated in place. That marker is the only honest\n * way to tell Nx's text from the user's: both files are, by convention, where\n * a person writes instructions for their own repository. This project's own\n * `CLAUDE.md` is five hundred lines of hand-written guide with no marker in\n * it, and the previous behaviour - deleting both files by path - would have\n * taken all of it.\n *\n * `.claude/settings.json` is different: Nx writes the whole file, to register\n * its plugin marketplace, so it is removed outright. The rest of `.claude` -\n * `agents/`, `commands/`, anything else a user puts there - is theirs.\n */\nconst NX_AGENT_RULES_MARKER = '<!-- nx configuration start-->'\n\n/** Where Nx's own agent rules live, alongside whatever the user wrote there. */\nconst NX_AUTHORED_AGENT_FILES = ['AGENTS.md', 'CLAUDE.md'] as const\n\n/** The one file under `.claude` that Nx writes in full. */\nconst NX_CLAUDE_SETTINGS = '.claude/settings.json'\n\n/**\n * One key removed from a record, without mutating the original.\n *\n * @remarks\n * Exists because the destructuring form of this needs a computed key, which\n * reads badly enough that the lint config rejects it.\n *\n * @param record - The record to copy.\n * @param key - The key to leave out.\n * @returns A new record without that key.\n * @throws Never - pure object construction.\n * @typeParam T - The record's value type.\n */\nfunction withoutKey<T> (record: Record<string, T>, key: string): Record<string, T> {\n return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key))\n}\n\n/**\n * The local-registry story `@nx/js:lib --publishable` scaffolds, in the three\n * places it puts itself.\n *\n * @remarks\n * Generating a publishable library drops a `.verdaccio/config.yml`, a\n * `verdaccio` devDependency at the root, and a root `local-registry` target\n * wired to both. It is Nx's own way of rehearsing a publish against a\n * throwaway registry.\n *\n * mnci's way is different and already written down: `nx release` is tag-only\n * and publishes from CI on a push to `main`, and `npm run release:preview`\n * rehearses it without a registry at all. So this is a second publishing\n * mechanism arriving unasked, that nothing in the workspace refers to, and\n * bringing a devDependency with its own transitive tree and its own advisories\n * for a feature nobody chose.\n *\n * Deleted rather than documented, for the same reason as `.vscode/` and the\n * retired formatter configs: it re-appears on every `mnci add npm-lib`, so\n * leaving it is not a one-time cost. Anyone who wants a local registry can add\n * one deliberately; what they cannot do is notice it arriving.\n */\nexport const LOCAL_REGISTRY_SCAFFOLDING = {\n /** The config directory, removed with the rest of {@link NX_SCAFFOLDING_TO_REMOVE}. */\n directory: '.verdaccio',\n /** The root devDependency the target runs. */\n devDependency: 'verdaccio',\n /** The root Nx target wired to both of the above. */\n rootTarget: 'local-registry',\n} as const\n\n/**\n * Deletes the local-registry scaffolding from a workspace that already has it.\n *\n * @remarks\n * `applyOverlay` handles `mnci new` and `mnci upgrade` on its own — it removes\n * the directory with the rest of the Nx scaffolding, and drops the\n * devDependency and the target while rewriting the root manifest. This is for\n * `mnci add`, which scaffolds a publishable library without going near the\n * overlay.\n *\n * Edits in place rather than rebuilding from a spread, so every untouched key\n * keeps its position: a rebuild moves `devDependencies` and `nx` to the end of\n * the file, which is a diff in every generated workspace for no reason.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeLocalRegistryScaffolding (workspaceRoot: string): void {\n rmSync(join(workspaceRoot, LOCAL_REGISTRY_SCAFFOLDING.directory), {\n recursive: true,\n force: true,\n })\n\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!fileExists(manifestPath)) return\n\n const manifest = readJson<{\n devDependencies?: Record<string, string>\n nx?: Record<string, unknown> & { targets?: Record<string, unknown> }\n } & Record<string, unknown>>(manifestPath)\n\n if (manifest.devDependencies?.[LOCAL_REGISTRY_SCAFFOLDING.devDependency] !== undefined) {\n delete manifest.devDependencies[LOCAL_REGISTRY_SCAFFOLDING.devDependency]\n if (Object.keys(manifest.devDependencies).length === 0) delete manifest.devDependencies\n }\n if (manifest.nx?.targets?.[LOCAL_REGISTRY_SCAFFOLDING.rootTarget] !== undefined) {\n delete manifest.nx.targets[LOCAL_REGISTRY_SCAFFOLDING.rootTarget]\n // Emptied containers go entirely: nothing else in a generated manifest\n // carries an empty object, so a stray one would read as deliberate.\n if (Object.keys(manifest.nx.targets).length === 0) delete manifest.nx.targets\n if (Object.keys(manifest.nx).length === 0) delete manifest.nx\n }\n\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * Deletes the `create-nx-workspace` scaffolding mnci replaces.\n *\n * @remarks\n * See {@link NX_SCAFFOLDING_TO_REMOVE} for why each entry has to go. This is\n * the first thing `applyOverlay` deletes rather than overwrites, so\n * `mnci upgrade`'s \"review with `git diff`\" advice now covers deletions too.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeNxScaffolding (workspaceRoot: string): void {\n for (const entry of NX_SCAFFOLDING_TO_REMOVE) {\n rmSync(join(workspaceRoot, entry), { recursive: true, force: true })\n }\n removeNxAuthoredAgentFiles(workspaceRoot)\n removeProjectEslintConfigs(workspaceRoot)\n}\n\n/**\n * Strips Nx's agent rules from `AGENTS.md` and `CLAUDE.md`, keeping the rest.\n *\n * @remarks\n * The rules sit between {@link NX_AGENT_RULES_MARKER} and its closing\n * comment, which Nx puts there precisely so the block can be replaced without\n * touching what surrounds it. Everything outside the block is the user's and\n * survives; the file is deleted only when the block WAS the whole file, which\n * is the case `create-nx-workspace` produces.\n *\n * A file with no marker is Nx-unaware and is left completely alone - this\n * project's own `CLAUDE.md` is exactly that, and the previous by-path deletion\n * would have removed it in full.\n *\n * Suppressing Nx's \"your AI agent configuration is outdated\" nag needs the\n * rules gone, not the file gone: the nag fires on rules that would change if\n * regenerated, and an excised block leaves none. Verified against a real\n * workspace with `nx show projects` and `nx run-many -t build`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction removeNxAuthoredAgentFiles (workspaceRoot: string): void {\n // Nx writes this one in full, to register its plugin marketplace. The rest\n // of `.claude` - agents, commands - is the user's and is left alone.\n rmSync(join(workspaceRoot, NX_CLAUDE_SETTINGS), { force: true })\n // ...but when settings.json was all there was, the directory goes too. An\n // empty `.claude/` is still AI-agent scaffolding to every tool that probes\n // for the directory, and the e2e's \"no AI-agent scaffolding: .claude\" check\n // failed on every fresh workspace because of exactly this leftover.\n const claudeDirectory = join(workspaceRoot, dirname(NX_CLAUDE_SETTINGS))\n if (existsSync(claudeDirectory) && readdirSync(claudeDirectory).length === 0) {\n rmSync(claudeDirectory, { recursive: true, force: true })\n }\n\n for (const name of NX_AUTHORED_AGENT_FILES) {\n const path = join(workspaceRoot, name)\n if (!existsSync(path)) continue\n\n const kept = withoutNxAgentRules(readFileSync(path, 'utf8'))\n if (kept === undefined) continue\n\n if (kept.trim() === '') rmSync(path, { force: true })\n else writeFileEnsured(path, kept)\n }\n}\n\n/**\n * One agent-rules file with Nx's marked block taken out.\n *\n * @remarks\n * Returns `undefined` when there is no block to remove, so the caller can\n * leave a hand-written file untouched rather than rewriting it byte-for-byte\n * and showing a spurious diff.\n *\n * The end marker is matched by its opening rather than spelled out in full:\n * Nx has changed the words inside it before, and a missed match would silently\n * mean \"no block\", which is the failure that loses nothing but fixes nothing.\n *\n * @param content - The file's current text.\n * @returns What should remain, or `undefined` when the file carries no block.\n * @throws Never - pure string manipulation.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction withoutNxAgentRules (content: string): string | undefined {\n const start = content.indexOf(NX_AGENT_RULES_MARKER)\n if (start === -1) return undefined\n\n const endMarker = '<!-- nx configuration end'\n const end = content.indexOf(endMarker, start)\n if (end === -1) return undefined\n\n const afterEnd = content.indexOf('>', end)\n const rest = afterEnd === -1 ? '' : content.slice(afterEnd + 1)\n\n // `[\\r\\n]`, not `\\n`: a repository checked out on Windows has CRLF line\n // endings, and trimming only the LF leaves the carriage returns behind as\n // blank lines at the top of the file - which is most of this project's own\n // users, since Windows is the platform its e2e runs on.\n return `${content.slice(0, start)}${rest}`.replace(/^[\\r\\n]+/, '')\n}\n\n/**\n * Deletes one path if it exists, quietly.\n *\n * @remarks\n * For the config files that belong to the linter mode the workspace did **not**\n * choose. Leaving them behind is not cosmetic: two formatter configs means the\n * editor and CI can disagree about which one applies, which is the same class of\n * silent failure as the `.prettierrc` precedence bug — a config that is present,\n * valid, and ignored.\n *\n * `force: true` makes a missing path a no-op, which is the common case: a fresh\n * `mnci new` has neither mode's files yet.\n *\n * @param path - Absolute path to remove.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeIfPresent (path: string): void {\n rmSync(path, { recursive: true, force: true })\n}\n\n/**\n * Adds `.eslintcache` to `.gitignore` if it is not already there.\n *\n * @remarks\n * `mnci` never writes `.gitignore` itself — `create-nx-workspace` owns that\n * file, and nothing here needed adding to it, historically. That stopped\n * being true the moment the root `format`/`lint` scripts adopted\n * `eslint --cache` (measured 3.4x faster on an unchanged re-run): the very\n * first `npm run format` a fresh workspace runs — during `mnci new` itself —\n * writes a root `.eslintcache` file that `create-nx-workspace`'s own\n * `.gitignore` template has no reason to know about. Confirmed end to end: a\n * freshly generated workspace's very first `git add -A` (the command\n * `mnci new`'s own \"Next steps\" output tells the user to run) stages it.\n *\n * A plain string check, not a `.gitignore` parser: `.eslintcache` is written\n * here as a single bare line, so a literal line match is exactly as correct\n * as parsing gitignore syntax would be, without pulling in a parser for one\n * pattern. Idempotent — `mnci upgrade` runs this on every existing workspace,\n * and a workspace that already has the line (added by hand, or by a previous\n * `mnci upgrade`) is left untouched.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureEslintCacheIgnored (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n const current = readFileSync(gitignorePath, 'utf8')\n if (current.split('\\n').some(line => line.trim() === '.eslintcache')) {\n return\n }\n const withoutTrailingBlankLines = current.replace(/\\n+$/, '')\n const separator = withoutTrailingBlankLines.length > 0 ? '\\n\\n' : ''\n writeFileEnsured(\n gitignorePath,\n `${withoutTrailingBlankLines}${separator}# Added by MoNecromanCI: \\`npm run format\\`/\\`lint\\` run \\`eslint --cache\\`.\\n.eslintcache\\n`,\n )\n}\n\n/**\n * Lines every Python project needs `.gitignore` to carry, and nothing more.\n *\n * @remarks\n * Deliberately just the bytecode. Running `lint`, `typecheck` and `test` on a\n * generated `python-lib` writes five directories - `__pycache__/` twice,\n * `.mypy_cache/`, `.pytest_cache/` and `.ruff_cache/` - and the last three each\n * write their own `.gitignore` containing `*`, so they need nothing from here.\n * CPython does not, which is why `__pycache__/` is the one that leaks.\n *\n * Measured on a real generated workspace rather than assumed: after\n * `nx run-many -t lint,typecheck,test`, `git status` named exactly the two\n * `__pycache__/` directories and nothing else.\n *\n * `.venv/` is NOT here. mnci invokes the Python toolchain as `python3 -m <tool>`\n * and never creates a virtualenv, so ignoring one would be a guess about how\n * someone works rather than a fact about what this generates.\n */\nconst PYTHON_ARTEFACT_IGNORES = ['__pycache__/', '*.py[cod]'] as const\n\n/**\n * Adds the Python bytecode ignores when the workspace has a Python project.\n *\n * @remarks\n * `create-nx-workspace`'s `.gitignore` is a JavaScript template: `dist`, `tmp`,\n * `out-tsc`, `node_modules`, and not one line about any other language. That was\n * fine while mnci only scaffolded JavaScript, and has been wrong since it\n * scaffolded Python - the first `git add -A` after running a Python project's\n * own targets commits its bytecode.\n *\n * Found from the outside: a repository built with this CLI needed a hand-written\n * commit to add these lines, and `.gitignore` is the one file mnci appends to\n * without owning (see {@link ensureEslintCacheIgnored}, and `react-app`'s\n * `.env` negation) - so appending here is the established shape rather than a\n * new one.\n *\n * Conditional on a Python project existing, unlike `.eslintcache`, which every\n * workspace produces. A pure-JavaScript workspace has no business carrying\n * ignores for a language it does not use.\n *\n * Idempotent: `mnci upgrade` runs this on every existing workspace, and lines\n * already present - added by hand or by a previous upgrade - are left alone.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensurePythonArtefactsIgnored (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n // The same three roots every other Python-aware guard here scans.\n const hasPython =\n globSync(\n ['python-packages/*/pyproject.toml', 'apps/*/pyproject.toml', 'libs/*/pyproject.toml'],\n { cwd: workspaceRoot },\n ).length > 0\n if (!hasPython) {\n return\n }\n\n const current = readFileSync(gitignorePath, 'utf8')\n const present = new Set(current.split('\\n').map(line => line.trim()))\n const missing = PYTHON_ARTEFACT_IGNORES.filter(line => !present.has(line))\n if (missing.length === 0) {\n return\n }\n\n const withoutTrailingBlankLines = current.replace(/\\n+$/u, '')\n const separator = withoutTrailingBlankLines.length > 0 ? '\\n\\n' : ''\n const heading = [\n '# Added by MoNecromanCI: Python bytecode. The tool caches (.mypy_cache,',\n '# .pytest_cache, .ruff_cache) write their own .gitignore; CPython does not.',\n ].join('\\n')\n writeFileEnsured(\n gitignorePath,\n `${withoutTrailingBlankLines}${separator}${heading}\\n${missing.join('\\n')}\\n`,\n )\n}\n\n/**\n * Deletes every per-project ESLint config in the workspace.\n *\n * @remarks\n * An mnci workspace has exactly ONE ESLint config, at the root. `mnci add`\n * already deletes the one its generator just wrote\n * (`removeGeneratedEslintConfig` in `add/shared.ts`), but that only helps\n * projects created from now on.\n *\n * This is the migration path for workspaces generated **before** mnci owned\n * linting, which is the case that matters: they carry a config in every\n * `apps/*`, `libs/*` and `packages/*` directory, and without this an\n * `mnci upgrade` would install the root config while leaving each project\n * still linting itself against its own stale rules — the exact fragmentation\n * the root config exists to end. Verified against a real workspace: an\n * upgrade fixed every root file and left `packages/sdk/eslint.config.mjs`\n * behind until this was added.\n *\n * Only the three conventional project directories are swept, never the whole\n * tree, so a config a user deliberately placed elsewhere is left alone. The\n * root config is never matched — these globs are all one level deep inside a\n * project directory.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeProjectEslintConfigs (workspaceRoot: string): void {\n const matches = globSync('{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}', {\n cwd: workspaceRoot,\n })\n for (const match of matches) {\n rmSync(join(workspaceRoot, match), { force: true })\n }\n}\n\n/**\n * Applies MoNecromanCI's opinions on top of a freshly generated workspace.\n *\n * @remarks\n * This is the ONLY file-writing this CLI does — everything else in the\n * workspace is the untouched output of Nx's own generators. Writes: the\n * `nx.json` release patch, `eslint.config.mjs`, `.npmrc`,\n * `commitlint.config.mjs`, the husky\n * `commit-msg` hook, the `<workspace>.code-workspace` file and\n * the chosen CI provider's pipeline file(s) — `azure-pipelines.yml` and/or\n * `.github/workflows/ci.yml`, per `options.ci`. It also DELETES the Nx\n * scaffolding it replaces ({@link removeNxScaffolding}). Dependency\n * installation (`husky`, `@commitlint/*`) is the caller's job — it shells out\n * to real `npm install` so versions resolve at generation time instead of\n * being pinned here.\n *\n * @param workspaceRoot - Absolute path to the generated workspace.\n * @param options - The scope, registry, CI agent/variable group and provider chosen.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error raised while reading or writing.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function applyOverlay (\n workspaceRoot: string,\n options: OverlayOptions,\n onProgress: (message: string) => void = () => {},\n): void {\n // Patch nx.json with the release opinion, the stack generator defaults, the\n // shared global inputs (so `nx affected` on a PR is not blind to the root\n // config files — see SHARED_GLOBAL_INPUTS) and sync.applyChanges (so a stale\n // TS project reference — e.g. from hand-adding a cross-project import — is\n // fixed automatically on the next build/typecheck, not just flagged with a\n // prompt). Both `nx release` and every later `nx g`/`mnci add` see the\n // generator defaults.\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const generators = {\n ...(nxJson.generators as Record<string, unknown> | undefined),\n ...generatorDefaults(options.stack),\n }\n const sync = { ...(nxJson.sync as Record<string, unknown> | undefined), ...SYNC_CONFIG }\n const mnci = { ...(nxJson.mnci as Record<string, unknown> | undefined), ...mnciConfig(options) }\n /*\n * Read from the root manifest rather than assumed: the Python plugin arrives\n * only when `mnci add python-*` installs it, and registering a plugin Nx\n * cannot resolve is a hard failure of the whole project graph.\n */\n const rootManifest = readJson<{\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }>(join(workspaceRoot, 'package.json'))\n const hasPythonPlugin =\n (rootManifest.devDependencies ?? {})['@mnci/nx-python-pip'] !== undefined ||\n (rootManifest.dependencies ?? {})['@mnci/nx-python-pip'] !== undefined\n const withRelease = withReleaseConfig(nxJson, options.ci)\n const patched = withPythonGraphPlugin(\n withSharedGlobals(withEslintPlugin(withRelease)),\n hasPythonPlugin,\n )\n onProgress('nx.json — release, sync, generators, shared inputs, mnci block')\n writeFileEnsured(nxJsonPath, toJson({ ...patched, generators, sync, mnci }))\n\n // The preset names the root package a placeholder ('@org/source'); stamp the\n // chosen scope so `add npm-lib` can derive the default import path from it,\n // the curated everyday scripts (each a single cross-platform command, with\n // `lint` bound to the chosen linter), and the dual TS compiler — the alias\n // for `typescript` replaces the plain TS 6 the preset installed, and\n // `@typescript/native` adds the TS 7 `tsc`. The caller's `npm install`\n // materialises them.\n onProgress('package.json — root scripts, TS compilers, ESLint toolchain, overrides')\n const manifestPath = join(workspaceRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const scripts = {\n ...(manifest.scripts as Record<string, string> | undefined),\n ...rootScripts(),\n }\n const existingDevDeps = manifest.devDependencies as Record<string, string> | undefined\n // Dropped here as well as in `removeLocalRegistryScaffolding`, because this\n // merge is what carries an existing workspace's devDependencies across an\n // upgrade - so without this `mnci upgrade` would delete `.verdaccio/` and\n // leave the dependency that populates it.\n const devDeps = withoutKey(\n withoutRetiredFormatterDependencies({\n ...existingDevDeps,\n ...TS_COMPILER_DEPENDENCIES,\n // The preset pins `nx` itself; the ESLint plugins must match it exactly.\n ...eslintToolchainDependencies(existingDevDeps?.nx ?? 'latest'),\n }),\n LOCAL_REGISTRY_SCAFFOLDING.devDependency,\n )\n // Merged, never replaced: a workspace's own overrides must survive an upgrade.\n const overrides = {\n ...(manifest.overrides as Record<string, unknown> | undefined),\n ...ESLINT_PEER_OVERRIDES,\n ...SECURITY_OVERRIDES,\n // Static, so it is present before any generator can run an install — see\n // NX_PEER_OVERRIDES for why the conditional form could never be.\n ...NX_PEER_OVERRIDES,\n }\n // The root project's own Nx config. Merged the same way, so a workspace that\n // added root targets of its own keeps them — see ROOT_LINT_TARGET for why\n // `includedScripts` must stay empty.\n const existingNx = manifest.nx as Record<string, unknown> | undefined\n // Same reason as the devDependency above: the merge is what would otherwise\n // carry the target across an upgrade, pointing at a config file this run has\n // just deleted.\n const existingTargets = withoutKey(\n (existingNx?.targets as Record<string, unknown> | undefined) ?? {},\n LOCAL_REGISTRY_SCAFFOLDING.rootTarget,\n )\n const nx = {\n ...existingNx,\n includedScripts: (existingNx?.includedScripts as unknown[] | undefined) ?? [],\n targets: {\n ...existingTargets,\n lint: ROOT_LINT_TARGET,\n },\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n name: `${options.scope}/source`,\n scripts,\n devDependencies: devDeps,\n overrides,\n nx,\n }),\n )\n\n onProgress(\n `.npmrc — ${\n options.registry.kind === 'azure-artifacts'\n ? 'Azure Artifacts feed routing and credentials'\n : 'public npm registry auth'\n }`,\n )\n writeFileEnsured(\n join(workspaceRoot, '.npmrc'),\n npmrcContent(options.registry, options.scope, options.npmAuth),\n )\n onProgress('commitlint.config.mjs and .husky/commit-msg — conventional commit enforcement')\n writeFileEnsured(join(workspaceRoot, 'commitlint.config.mjs'), COMMITLINT_CONFIG)\n const hookPath = join(workspaceRoot, '.husky/commit-msg')\n writeFileEnsured(hookPath, COMMIT_MSG_HOOK)\n markExecutable(hookPath)\n // ESLint handles code quality, type-aware correctness AND formatting\n // (JavaScript Standard Style) — one tool, at the root. `add` deletes the\n // per-project configs Nx generators write.\n //\n // TWO files, and only the first is ours. `eslint.config.mnci.mjs` carries\n // every rule and is rewritten here on every upgrade; `eslint.config.mjs` is\n // what ESLint actually loads, imports that one, and holds the workspace's\n // own blocks — so it is written once and then left alone forever. Rewriting\n // it is what used to delete a user's overrides on upgrade, silently, having\n // told them in a comment that appending was how overriding worked.\n onProgress(`${ESLINT_MNCI_FILENAME} — the shared lint AND formatting opinion`)\n writeFileEnsured(join(workspaceRoot, ESLINT_MNCI_FILENAME), ESLINT_MNCI_CONFIG)\n writeEslintEntryPoint(workspaceRoot, onProgress)\n // `format`/`lint`'s `--cache` flag writes a root .eslintcache that\n // create-nx-workspace's own .gitignore template has no reason to know\n // about — see ensureEslintCacheIgnored.\n ensureEslintCacheIgnored(workspaceRoot)\n // Same file, same reason, different language: create-nx-workspace's template\n // says nothing about Python because it is a JavaScript template.\n ensurePythonArtefactsIgnored(workspaceRoot)\n // Every config a previous mnci version could have written for a second tool\n // has to be REMOVED, not merely left unwritten. A stale `.prettierrc.mjs` or\n // `.oxfmtrc.json` does nothing on its own now that neither binary runs, but a\n // globally installed `esbenp.prettier-vscode` or `oxc.oxc-vscode` resolves it\n // and reformats on save against an opinion no gate checks — silently undoing\n // Standard in the editor while CI stays green. `mnci upgrade` runs this same\n // path, so migrating an existing workspace is one command.\n for (const retired of RETIRED_FORMATTER_FILES) {\n removeIfPresent(join(workspaceRoot, retired))\n }\n // Makes a local environment match the one CI verifies — see devcontainerJson.\n onProgress('.devcontainer/devcontainer.json — a local toolchain matching CI')\n writeFileEnsured(\n join(workspaceRoot, '.devcontainer/devcontainer.json'),\n devcontainerJson(options.workspaceName),\n )\n removeNxScaffolding(workspaceRoot)\n // VS Code workspace file with folder structure, extensions, and settings. The\n // `tasks` array is read back first and carried through: it is per-project state\n // written by `mnci add`, not overlay-owned, so regenerating it wholesale would\n // wipe every registered build/qa/start task on `mnci upgrade`.\n onProgress(\n `${options.workspaceName}.code-workspace — settings, extensions, launch configs`,\n )\n const codeWorkspacePath = join(workspaceRoot, `${options.workspaceName}.code-workspace`)\n const existing = readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n launch?: { version?: string; configurations?: Record<string, unknown>[] }\n settings?: Record<string, unknown>\n }>(codeWorkspacePath)\n writeFileEnsured(\n codeWorkspacePath,\n vscodeWorkspace(\n options.workspaceName,\n existing?.tasks,\n existing?.launch,\n existing?.settings,\n ),\n )\n // Repairs mnci's own past bug rather than tidying: `mnci upgrade` used to pass\n // no `workspaceName` at all, so this write landed on the literal filename\n // `undefined.code-workspace` and the workspace's real one was never refreshed.\n // Any workspace upgraded before that fix still carries the junk file, and only\n // an upgrade can clear it. Guarded on the name so a workspace genuinely called\n // `undefined` does not delete its own file.\n if (options.workspaceName !== 'undefined') {\n rmSync(join(workspaceRoot, 'undefined.code-workspace'), { force: true })\n }\n // Either or both, per the chosen provider — a GitHub-hosted repo can skip\n // the unused Azure file entirely instead of carrying dead CI config.\n const publishUrl = pythonPublishUrl(options.registry)\n const nugetUrl = nugetFeedUrl(options.registry)\n if (options.ci === 'azure' || options.ci === 'both') {\n onProgress('azure-pipelines.yml — build, verify, pack and release')\n writeFileEnsured(\n join(workspaceRoot, 'azure-pipelines.yml'),\n azurePipelinesYaml(\n options.agent,\n options.variableGroup,\n publishUrl,\n options.registry.kind,\n nugetUrl,\n options.npmAuth,\n ),\n )\n }\n if (options.ci === 'github' || options.ci === 'both') {\n onProgress('.github/workflows/ci.yml and dependabot.yml')\n writeFileEnsured(\n join(workspaceRoot, '.github/workflows/ci.yml'),\n githubActionsYaml(options.agent, publishUrl, options.registry.kind, options.ci, nugetUrl),\n )\n writeFileEnsured(join(workspaceRoot, '.github/dependabot.yml'), dependabotConfig(workspaceRoot))\n }\n}\n","import { existsSync, readFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { NX_PEER_OVERRIDES, dependabotConfig, ensurePythonArtefactsIgnored } from '../workspace-overlay'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * The HTTP frameworks `@nx/node:application` can scaffold a `node-app` with.\n *\n * @remarks\n * The generator's own `--framework` choices (verified empirically against a\n * real Nx 23.1.0 workspace: `nx g @nx/node:application --help`), passed\n * straight through — `node.ts` adds no framework-specific logic of its own.\n * `none` (the default) is a bare Node app with no HTTP framework opinion.\n * `node-function-app` never accepts this: the Azure Functions v4 programming\n * model (`app.http(...)` registration) runs its own request lifecycle, so a\n * full HTTP server framework doesn't apply there.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type NodeFramework = 'express' | 'fastify' | 'koa' | 'nest' | 'none'\n\n/**\n * Options accepted by `runAdd`.\n *\n * @remarks\n * Mirrors the CLI's flags. Defined here (not in `add.ts`) because every\n * per-kind module (`react-app.ts`, `function-app.ts`, `npm-lib.ts`) needs it;\n * `add.ts` re-exports it so its existing public import path\n * (`import { type AddOptions } from './commands/add'`, used by `cli.ts`)\n * keeps working unchanged.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface AddOptions {\n /** npm scope for a publishable lib's import path (defaults to `@<workspace name>`). */\n scope?: string\n /** `node-app` only: the HTTP framework `@nx/node:application` scaffolds (defaults to `none`, a bare Node app). */\n framework?: NodeFramework\n /** `python-vendor` only: the internal Python library (under `libs/`) to vendor into `name`. */\n lib?: string\n /** `vscode-extension` only: the Marketplace publisher id (defaults to the workspace scope without `@`). */\n publisher?: string\n /** `vscode-extension` only: a `go-app` whose six-platform build ships inside the extension. */\n sidecar?: string\n /** `go-app` only: release the app (versioned from its git tag, per-platform zips on its GitHub Release). */\n release?: boolean\n}\n\n/**\n * The workspace stack, generator-facing shape (what `readWorkspaceStack` in\n * `add.ts` resolves and every plugin-generated kind consumes).\n *\n * @remarks\n * Only testRunner is configurable; linting and formatting are always ESLint.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface WorkspaceStack {\n testRunner: 'jest' | 'vitest'\n}\n\n/**\n * Whether a plugin package is already declared in the workspace's manifest.\n *\n * @remarks\n * Keeps repeat `add` calls fast by skipping the install step.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/react`).\n * @returns `true` when the package is a dependency or devDependency.\n * @throws Propagates any `fs`/JSON error reading the root manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasPlugin (workspaceRoot: string, packageName: string): boolean {\n const manifest = readJson<{\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }>(join(workspaceRoot, 'package.json'))\n const installed = { ...manifest.dependencies, ...manifest.devDependencies }\n\n return Object.hasOwn(installed, packageName)\n}\n\n/**\n * Ensures an Nx plugin is installed in the workspace, installing it on first use.\n *\n * @remarks\n * `nx add` installs the package and runs its init generator — the Nx-native way\n * to bring a plugin into an existing workspace. Shared by every kind whose\n * generator lives in a plugin the workspace may not have yet (`react-app`,\n * `react-lib`, `react-internal-lib`).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/react`).\n * @returns Nothing.\n * @throws Error when the underlying `nx add` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensurePlugin (workspaceRoot: string, packageName: string): void {\n if (hasPlugin(workspaceRoot, packageName)) {\n return\n }\n logger.step(`Installing Nx plugin ${packageName}`)\n runNx(['add', packageName], workspaceRoot)\n}\n\n/**\n * Runs an Nx generator, then applies mnci's own post-generation repairs even\n * when the generator's underlying install step failed.\n *\n * @remarks\n * `@nx/js:lib --bundler=rollup`/`@nx/react:library` both fetch and install a\n * plugin package (`@nx/rollup`, `@nx/vitest`, …) as part of the generator run\n * the FIRST time a workspace uses that bundler or test runner, via Nx's own\n * `installPackagesTask`. That install can fail for reasons that have nothing\n * to do with the generator's own correctness — reproduced end to end with a\n * real npm 10.9.7 arborist bug on this exact dependency tree\n * (`Cannot read properties of null (reading 'edgesOut')`, gone on npm 11) —\n * and when it does, `runNx` throws AFTER the generator has already written\n * every scaffold file to disk. Without this wrapper, every caller's own\n * post-generation repairs (`repairPublishableManifest`,\n * `repairDeclarationSpecifiers`, `registerProjectCommands`, …) never run,\n * silently, because the exception unwinds straight out of `addNpmLib`/\n * `addReactLib` — leaving a project with the broken `types` path and the\n * unrepaired source-map/declaration-extension defects those repairs exist to\n * fix, and `mnci doctor` had nothing that could point at it: the target\n * files exist, they are just wrong. Reproduced and fixed together.\n *\n * The distinguishing signal is `markerPath`: a file the generator itself\n * writes (its manifest) as one of its very first acts, well before the\n * install step runs. If it is missing, the generator failed before writing\n * anything repairable, and this rethrows the original error unchanged rather\n * than running repairs against a directory that does not exist. If it is\n * present, the scaffold is real regardless of whether install succeeded, so\n * `repair` runs unconditionally — and if install did fail, the original\n * error is rethrown afterwards, with a clearer, actionable message, so the\n * command still exits non-zero rather than reporting a false success.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param nxArguments - The Nx CLI arguments, exactly as `runNx` would take them.\n * @param markerPath - Absolute path to a file the generator writes early,\n * used to tell \"failed before writing anything\" apart from \"failed after\".\n * @param repair - Every post-generation repair this kind normally runs on\n * success; now run whenever the scaffold exists, success or not.\n * @returns Nothing.\n * @throws The original `runNx` error, unchanged, when `markerPath` was never\n * written. A new, clearer error wrapping it, after `repair` has still run,\n * when `markerPath` exists but the generator's install step failed anyway.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runGeneratorAndRepair (\n workspaceRoot: string,\n nxArguments: string[],\n markerPath: string,\n repair: () => void,\n): void {\n let installFailure: Error | undefined\n try {\n runNx(nxArguments, workspaceRoot)\n } catch (error) {\n installFailure = error instanceof Error ? error : new Error(String(error))\n }\n\n if (!fileExists(markerPath)) {\n throw installFailure ?? new Error(`unreachable: runNx did not throw and ${markerPath} is missing`)\n }\n\n repair()\n\n if (installFailure) {\n throw new Error(\n 'The project was generated and mnci\\'s own repairs were applied, but the ' +\n `underlying Nx generator's install step still failed (${installFailure.message}). ` +\n \"Fix the install error above (commonly resolved by 'npm install' with a newer \" +\n \"npm major), then verify with 'mnci doctor'.\",\n )\n }\n}\n\n/**\n * Sets `\"private\": true` in a package manifest.\n *\n * @remarks\n * One of the deliberate post-generation touches: it makes an internal library\n * structurally unpublishable, no matter what future config drifts. Shared by\n * every private-lib kind (`internal-lib`, `react-internal-lib`).\n *\n * @param manifestPath - Absolute path to the lib's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markPrivate (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }))\n}\n\n/**\n * Sets `publishConfig.access: \"public\"` in a package manifest.\n *\n * @remarks\n * npm treats every scoped package (`@scope/name` — what every publishable lib's\n * `importPath` always is) as private by default: an unmodified first publish\n * fails with `402 Payment Required — You must sign up for private packages`\n * (verified empirically against the real registry), not with anything a dry-run\n * surfaces, since dry-runs never call the registry. This is the one\n * post-generation touch that makes a freshly added publishable lib publishable\n * as-is. Shared by `npm-lib` and `react-lib`.\n *\n * @param manifestPath - Absolute path to the lib's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function markPublic (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: 'public' } }))\n}\n\n/**\n * Replaces the stock README the Nx generators write.\n *\n * @remarks\n * Theirs credits Nx, which did not generate this project - mnci did, delegating one\n * step to an Nx generator. It also names the project by its directory rather than by\n * the package name the workspace actually publishes.\n *\n * The directory form is NOT broken: Nx resolves `nx build secrets` to `@auto/secrets`\n * happily. Checked, because the opposite was assumed first. Naming the package is\n * simply the less ambiguous of two working forms, and the one that matches what\n * `nx show projects` prints.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @param projectName - The Nx project name, which is the package name.\n * @param testRunner - The workspace test runner, named in the test command.\n * @returns Nothing.\n * @throws Propagates any `fs` error raised while writing.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function writeProjectReadme (\n projectRoot: string,\n projectName: string,\n testRunner: WorkspaceStack['testRunner'],\n): void {\n const runner = testRunner === 'vitest' ? '[Vitest](https://vitest.dev)' : '[Jest](https://jestjs.io)'\n writeFileEnsured(\n join(projectRoot, 'README.md'),\n [\n `# ${projectName}`,\n '',\n 'Generated by [MoNecromanCI](https://github.com/russoedu/MoNecromanCi).',\n '',\n '## Building',\n '',\n `Run \\`nx build ${projectName}\\` to build this project.`,\n '',\n '## Running unit tests',\n '',\n `Run \\`nx test ${projectName}\\` to execute the unit tests via ${runner}.`,\n '',\n ].join('\\n'),\n )\n}\n\n/**\n * Removes the `.gitkeep` a directory no longer needs.\n *\n * @remarks\n * `create-nx-workspace` drops one into `packages/` and `libs/` so git tracks them\n * while empty. Once a real project lands there the file is not just redundant, it is\n * misleading - it says \"this directory is empty\" in a directory that is not.\n *\n * Swept across every scaffold directory rather than the one an `add` just wrote to,\n * so a workspace that predates this picks up the tidy on its next add regardless of\n * which kind was added.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a missing file is the expected case on every later add.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeStaleGitkeeps (workspaceRoot: string): void {\n for (const scaffold of ['apps', 'libs', 'packages']) {\n const directory = join(workspaceRoot, scaffold)\n try {\n const holdsAProject = readdirSync(directory, { withFileTypes: true }).some((entry) =>\n entry.isDirectory(),\n )\n if (holdsAProject) {\n rmSync(join(directory, '.gitkeep'), { force: true })\n }\n } catch {\n // The scaffold directory does not exist in this workspace; nothing to sweep.\n }\n }\n}\n\n/**\n * Ensures the `adm-zip` packager is a workspace devDependency.\n *\n * @remarks\n * Each app's `package` target zips its build output with `adm-zip` (pure JS,\n * cross-platform, no native build) so CI can pack apps on any agent OS. Shared\n * by every app kind that packages its own output (react-app, the Python\n * kinds); the function-app path folds the same install into its larger one.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureAdmZip (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, 'adm-zip')) {\n return\n }\n logger.step('Installing the app packager (adm-zip)')\n if (\n runShell(\n 'npm',\n ['install', '--save-dev', 'adm-zip', '--no-audit', '--no-fund'],\n workspaceRoot,\n ) !== 0\n ) {\n throw new Error('npm install of adm-zip failed')\n }\n}\n\n/**\n * Derives the default npm scope from the workspace's root package name.\n *\n * @remarks\n * Shared by the function-app and npm-lib kinds — both fall back to the\n * workspace's own scope when no `--scope` is given.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The scope, e.g. `@demo` for a workspace named `demo` (or\n * `@demo/source`-style names produced by some presets).\n * @throws Propagates any `fs`/JSON error reading the root manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function defaultScope (workspaceRoot: string): string {\n const { name } = readJson<{ name: string }>(join(workspaceRoot, 'package.json'))\n const base = (name.startsWith('@') ? name.slice(1) : name).split('/', 1)[0]\n\n return `@${base}`\n}\n\n/**\n * Merges extra Nx targets into an inference-only app via its manifest `nx` field.\n *\n * @remarks\n * Apps generated by `@nx/react:app`/`@nx/node:application` have no\n * `project.json` (targets are inferred), so extra targets (e.g. per-environment\n * builds, a `package` target) are attached through the package.json `nx`\n * field — merged with the inferred targets, and free of the project-name\n * clash a second `project.json` would risk in a TS-solution workspace. Shared\n * by every inference-only app kind that adds targets this way (react-app,\n * node-app, node-function-app).\n *\n * @param manifestPath - Absolute path to the app's `package.json`.\n * @param newTargets - The targets to merge in.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNxTargets (manifestPath: string, newTargets: Record<string, unknown>): void {\n // The generator always writes this manifest first (runAdd throws otherwise);\n // defaulting to {} only guards the pathological missing-file case.\n const manifest = fileExists(manifestPath) ? readJson<Record<string, unknown>>(manifestPath) : {}\n const nx = (manifest.nx as Record<string, unknown> | undefined) ?? {}\n const targets = (nx.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(\n manifestPath,\n toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } }),\n )\n}\n\n/**\n * Merges extra targets into a project's `project.json`, creating it if the\n * plugin that generated the project wrote none.\n *\n * @remarks\n * Was identical, hand-duplicated code in `go.ts`, `python.ts` and\n * `flutter.ts` — each of those plugins writes a real `project.json` (`go`'s\n * with an empty `targets` map, since `@nx-go/nx-go`'s own inference needs a\n * per-project `go.mod` mnci's single-root-module layout does not have; Python\n * and Flutter's carry their own lint/test/build already). Extracted once a\n * fourth caller needed it: `@nx/dotnet` is inference-only and writes no\n * `project.json` at all, so `csharp.ts` needs this to also CREATE one — the\n * one behavioural difference from the three hand-duplicated originals, which\n * could assume the file already existed. Tolerating a missing file is a\n * strict widening: the three existing callers are unaffected, since their\n * generators always write the file first.\n *\n * @param projectJsonPath - Absolute path to the project's `project.json`.\n * @param newTargets - The targets to merge in.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addProjectJsonTargets (\n projectJsonPath: string,\n newTargets: Record<string, unknown>,\n): void {\n const project = fileExists(projectJsonPath)\n ? readJson<Record<string, unknown>>(projectJsonPath)\n : {}\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\n}\n\n/**\n * Every ESLint flat-config filename an `@nx/*` generator might write.\n *\n * @remarks\n * Nx picks the extension from the project's module type, so all of these are\n * reachable across the kinds mnci generates.\n */\nconst ESLINT_CONFIG_FILENAMES = [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.cts',\n] as const\n\n/**\n * Deletes the per-project ESLint config an `@nx/*` generator just wrote, and\n * any `.vscode/` directory it re-created.\n *\n * @remarks\n * An mnci workspace has exactly ONE ESLint config, at the root\n * (`@mnci/eslint-config`). Every `@nx/*` generator nevertheless drops an\n * `eslint.config.mjs` into the project it creates, which re-fragments the\n * config the moment a project is added.\n *\n * Deleting them is safe, and that was verified rather than assumed: with no\n * per-project config a project still gets its inferred `lint` target from\n * `@nx/eslint/plugin` (which maps config directories to the project roots\n * beneath them), `nx lint <project>` still runs, and it still reports real\n * violations from the root config. The e2e asserts both halves permanently,\n * because a future Nx change to that inference is the one thing that would\n * silently turn linting off across a whole workspace.\n *\n * `.vscode/` is handled here too: `@nx/node` re-creates `launch.json` on every\n * `add`, so removing it once at `mnci new` would not be enough.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - The new project's path, relative to the workspace root\n * (e.g. `apps/web`).\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error other than a missing path.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function removeGeneratedEslintConfig (workspaceRoot: string, projectRoot: string): void {\n for (const filename of ESLINT_CONFIG_FILENAMES) {\n rmSync(join(workspaceRoot, projectRoot, filename), { force: true })\n }\n rmSync(join(workspaceRoot, '.vscode'), { recursive: true, force: true })\n}\n\n/**\n * The local-dev commands a newly added project actually has, for {@link registerProjectCommands}.\n *\n * @remarks\n * `qa` (lint then test) is unconditional — every kind has both — so it is not\n * a field here. `build` and `start` vary by kind: several (`go-lib`,\n * `python-internal-lib`, `flutter-lib`, ...) have no build target at all, and\n * only kinds with a genuine local dev-server story get `start` — never a\n * library, and not `go-function-app` (no Azure Functions custom-handler\n * wiring exists for Go yet, so a `func start` script would just fail).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface ProjectCommands {\n /** Whether this kind has a `build` Nx target — adds `<name>:build` when true. */\n build: boolean\n /**\n * The exact command for `<name>:start` (e.g. `nx run <name>:serve`,\n * `nx run <name>:start`) — omitted entirely when the kind has no local\n * dev-server story.\n */\n start?: string\n}\n\n/**\n * Finds the workspace's single `<name>.code-workspace` file.\n *\n * @remarks\n * Its filename is the workspace name, which `add/*.ts` call sites don't\n * otherwise track past `mnci new` — cheaper to look it up by extension than\n * to thread the name through every kind module.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The absolute path, or `undefined` when none exists (a workspace\n * predating this file, or a test fixture that never wrote one).\n * @throws Never - a missing/unreadable directory yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findCodeWorkspaceFile (workspaceRoot: string): string | undefined {\n try {\n const entry = readdirSync(workspaceRoot).find(file => file.endsWith('.code-workspace'))\n\n return entry ? join(workspaceRoot, entry) : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * One VS Code task for a project's script, matching its root `package.json` entry.\n *\n * @remarks\n * `start` tasks run a dev server that never exits on its own, so they are\n * marked `isBackground` (VS Code won't wait for them to finish) rather than\n * given a `group` — `build`/`qa` do exit, so they get the matching group\n * VS Code's Command Palette/Tasks menu groups them under.\n *\n * @param name - The project name.\n * @param kind - Which of the three commands this task runs.\n * @returns The VS Code task object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction projectTask (name: string, kind: 'build' | 'qa' | 'start'): Record<string, unknown> {\n const script = `${name}:${kind}`\n const base = { label: `${name}: ${kind}`, type: 'npm', script, problemMatcher: [] }\n\n return kind === 'start' ? { ...base, isBackground: true } : { ...base, group: kind }\n}\n\n/**\n * Renames `@nx/js:lib`'s placeholder to a name mnci's own lint rule accepts.\n *\n * @remarks\n * `@nx/js:lib` scaffolds `src/lib/<name>.ts` — a bare name with no role suffix —\n * and `vertical-slices/file-role`, which ships in `@mnci/eslint-config`, rejects\n * it on the first lint:\n *\n * ```\n * A production file ends in its role - .handler .use-case .algorithm ... -\n * so \"studio.ts\" says what it is vertical-slices/file-role\n * ```\n *\n * So mnci's own opinionated config failed mnci's own generator output, every\n * time, for any workspace that turned the slice rules on. Reproduced on a\n * freshly generated workspace: of the 14 problems `eslint` reports on a new\n * `npm-lib`, 13 are stylistic and `--fix` away — this one cannot, because the\n * only fix is a rename.\n *\n * `use-case` is the role: it is the generic one for a library's public\n * behaviour, and it is what the placeholder is standing in for.\n *\n * **Unconditional, not gated on the rule being active.** Detecting that would\n * mean parsing the workspace's own `eslint.config.mjs`, which is user-owned\n * arbitrary JavaScript, and mnci has twice shipped a correct conditional that\n * could never fire — see `NX_PEER_OVERRIDES` and `ensurePythonArtefactsIgnored`.\n * A role-suffixed placeholder costs nothing when the rule is off, and the\n * scaffold then demonstrates by example the convention the config enforces.\n *\n * Both the spec and the barrel's re-export are repointed, so the scaffold still\n * builds and its sample test still runs. A no-op when the placeholder is absent\n * or has already been renamed.\n *\n * **Both specifier forms are repointed, and the `.js` suffix is kept.** The\n * rollup `npm-lib` scaffold writes `'./lib/<name>'`, but the `tsc` `internal-lib`\n * scaffold writes ESM specifiers, `'./lib/<name>.js'`. Matching only the first\n * moved the file and left the barrel and the spec pointing at nothing, so a\n * freshly generated internal-lib failed its own `build` and `test`. The unit\n * test had faked the barrel in the bare form, and the e2e rewrote the missing file\n * itself, so neither noticed.\n *\n * `react-lib` is deliberately NOT covered: `@nx/react:library` scaffolds a\n * component, and `component` is not one of the shipped roles — renaming it to\n * `use-case` would make the name lie. A workspace linting React with these\n * rules passes `roles: ['component']` itself.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @param name - The project name the generator used for the placeholder.\n * @returns Nothing.\n * @throws Propagates any `fs` error renaming or rewriting the files.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function renameScaffoldPlaceholder (projectRoot: string, name: string): void {\n const placeholder = join(projectRoot, 'src', 'lib', `${name}.ts`)\n if (!existsSync(placeholder)) {\n return\n }\n renameSync(placeholder, join(projectRoot, 'src', 'lib', `${name}.use-case.ts`))\n\n const spec = join(projectRoot, 'src', 'lib', `${name}.spec.ts`)\n if (existsSync(spec)) {\n const renamed = join(projectRoot, 'src', 'lib', `${name}.use-case.spec.ts`)\n renameSync(spec, renamed)\n writeFileEnsured(\n renamed,\n repointSpecifier(readFileSync(renamed, 'utf8'), `./${name}`, `./${name}.use-case`),\n )\n }\n\n // The barrel re-exports `./lib/<name>`; without this the package has no\n // entry point at all, which the build would catch but only after the fact.\n const barrel = join(projectRoot, 'src', 'index.ts')\n if (existsSync(barrel)) {\n writeFileEnsured(\n barrel,\n repointSpecifier(readFileSync(barrel, 'utf8'), `./lib/${name}`, `./lib/${name}.use-case`),\n )\n }\n}\n\n/**\n * Repoints a module specifier in generated source, in both forms a scaffold uses.\n *\n * @remarks\n * `'<from>'` (rollup scaffolds) and `'<from>.js'` (the `tsc` scaffold's ESM\n * specifiers) both move to `<to>`, and the `.js` suffix is kept when it was\n * there, because it is what ESM resolution needs. The closing quote is part of\n * the match, so a sibling such as `'<from>-extra'` is left alone, and a specifier\n * already repointed does not match again.\n *\n * @param source - The generated file's text.\n * @param from - The specifier to replace, without quote or suffix (e.g. `./lib/utils`).\n * @param to - The specifier to use instead.\n * @returns The rewritten text.\n * @throws Never - pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repointSpecifier (source: string, from: string, to: string): string {\n return source\n .replaceAll(`${from}'`, () => `${to}'`)\n .replaceAll(`${from}.js'`, () => `${to}.js'`)\n}\n\n/**\n * Writes the `@nx/*` peer overrides into the root manifest before any generator runs.\n *\n * @remarks\n * Called at the top of every `mnci add`, before the switch that invokes a\n * generator, because the generators install as they scaffold: the express node\n * app's generator adds express to the root manifest and runs `npm install` in\n * one invocation, and adding the React plugin installs it into a workspace that\n * may already have express. Either one is the install that hits the stale\n * express peer, so the override has to be on disk first — writing it afterwards,\n * as mnci used to, is always too late.\n *\n * This is what makes `mnci add` work on a workspace generated before the fix,\n * without making the user run `mnci upgrade` first. Merged, never replaced, so a\n * workspace's own overrides survive. Idempotent: re-running writes the same\n * bytes, and the manifest is left untouched when nothing would change, so an\n * `add` that needs no override does not dirty the file.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function ensureNxPeerOverrides (workspaceRoot: string): void {\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!existsSync(manifestPath)) {\n return\n }\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const existing = (manifest.overrides as Record<string, unknown> | undefined) ?? {}\n const overrides = { ...existing, ...NX_PEER_OVERRIDES }\n if (JSON.stringify(existing) === JSON.stringify(overrides)) {\n return\n }\n writeFileEnsured(manifestPath, toJson({ ...manifest, overrides }))\n}\n\n/**\n * Registers a newly added project's local-dev commands: root `package.json`\n * scripts, and matching VS Code tasks in the workspace's `.code-workspace` file.\n *\n * @remarks\n * Every `add/*.ts` kind function calls this once it has finished generating\n * and target-wiring a project, so `npm run <name>:build`/`:qa`/`:start` (and\n * the equivalent VS Code Command Palette entries) work immediately — no\n * separate `mnci upgrade` needed, since these are per-project entries, not\n * one of the fixed files `applyOverlay()` regenerates.\n *\n * `<name>:qa` (`nx run <name>:lint && nx run <name>:test`) is unconditional.\n * `<name>:build`/`<name>:start` are added only when {@link ProjectCommands}\n * says the kind actually has them. Idempotent: repeat calls for the same\n * `name` (a second `add` of the same project) overwrite rather than\n * duplicate, in both the manifest scripts and the `.code-workspace` tasks\n * array. A workspace with no `.code-workspace` file (predates it, or a test\n * fixture) still gets the `package.json` scripts — only the VS Code half is\n * skipped.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The newly added project's name.\n * @param commands - Which commands this kind actually has.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing either file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function registerProjectCommands (\n workspaceRoot: string,\n name: string,\n commands: ProjectCommands,\n): void {\n // Every `add` kind ends here, which makes this the one place the scaffold\n // `.gitkeep` files can be swept without wiring 30 call sites.\n removeStaleGitkeeps(workspaceRoot)\n // Same reason, and it has to be here rather than only in `applyOverlay`:\n // `ensurePythonArtefactsIgnored` is conditional on a Python project existing,\n // and at `mnci new` — the only other time the overlay runs — none does yet.\n // So a workspace that gained its first Python project through `mnci add` never\n // got the bytecode ignores, and the first `git add -A` after running that\n // project's targets committed its `__pycache__/`. Re-evaluated after every add\n // because this add may be the one that created the first Python project.\n // Idempotent, and a no-op for a workspace that has none.\n ensurePythonArtefactsIgnored(workspaceRoot)\n const scripts: Record<string, string> = {\n [`${name}:qa`]: `nx run ${name}:lint && nx run ${name}:test`,\n }\n if (commands.build) {\n scripts[`${name}:build`] = `nx run ${name}:build`\n }\n if (commands.start) {\n scripts[`${name}:start`] = commands.start\n }\n\n // Re-derived after every add, because whether the pip/pub blocks belong\n // depends on what projects now EXIST, and this add may have created the\n // first one. Cheap (a directory scan) and idempotent. Skipped when the\n // workspace has no dependabot.yml, i.e. it was generated with --ci=azure.\n const dependabotPath = join(workspaceRoot, '.github/dependabot.yml')\n if (existsSync(dependabotPath)) {\n writeFileEnsured(dependabotPath, dependabotConfig(workspaceRoot))\n }\n\n const manifestPath = join(workspaceRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const existingScripts = (manifest.scripts as Record<string, string> | undefined) ?? {}\n // No override sync here any more: NX_PEER_OVERRIDES is static and\n // `ensureNxPeerOverrides` writes it BEFORE the generator runs, which is the\n // only moment early enough to matter. Spreading `manifest` carries whatever\n // overrides the workspace already has.\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n scripts: { ...existingScripts, ...scripts },\n }),\n )\n\n const codeWorkspacePath = findCodeWorkspaceFile(workspaceRoot)\n if (!codeWorkspacePath) {\n return\n }\n const workspaceFile =\n readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n }>(codeWorkspacePath) ?? {}\n const label = (task: Record<string, unknown>): string => (task.label as string | undefined) ?? ''\n const existingTasks = (workspaceFile.tasks?.tasks ?? []).filter(\n task => !label(task).startsWith(`${name}: `),\n )\n const newTasks = [\n projectTask(name, 'qa'),\n ...(commands.build ? [projectTask(name, 'build')] : []),\n ...(commands.start ? [projectTask(name, 'start')] : []),\n ]\n writeFileEnsured(\n codeWorkspacePath,\n toJson({\n ...workspaceFile,\n tasks: {\n version: workspaceFile.tasks?.version ?? '2.0.0',\n tasks: [...existingTasks, ...newTasks],\n },\n }),\n )\n}\n\n/** Where a generated project's own manifest can live, in resolution order. */\nconst PROJECT_MANIFEST_ROOTS = ['apps', 'packages', 'libs'] as const\n\n/**\n * Reads the root manifest's runtime dependencies.\n *\n * @remarks\n * Snapshotted before a generator runs so {@link relocateRootRuntimeDependencies}\n * can attribute what it added.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The `dependencies` block, or an empty object when unreadable.\n * @throws Never - an unreadable manifest reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rootRuntimeDependencies (workspaceRoot: string): Record<string, string> {\n try {\n return (\n readJson<{ dependencies?: Record<string, string> }>(join(workspaceRoot, 'package.json'))\n .dependencies ?? {}\n )\n } catch {\n return {}\n }\n}\n\n/**\n * Moves runtime dependencies a generator hoisted to the root into the project.\n *\n * @remarks\n * **The root/project policy, applied rather than only checked.** `mnci doctor`\n * gained a `no runtime dependencies in the root manifest` check, and a freshly\n * generated workspace failed it: `@nx/react:application` puts `react` and\n * `react-dom` in the ROOT manifest, `--framework=express` puts `express` there,\n * and `add node-function-app` installs `@azure/functions` the same way. So mnci\n * generated workspaces its own doctor rejected — the \"a gate that fails on day\n * one\" shape this repo has hit before.\n *\n * The reason the policy matters is `@nx/rollup`, which externalises exactly what\n * a project's OWN manifest declares: a dependency left at the root is not shared,\n * it is **inlined as a private copy** into that project's published bundle. The\n * root is also `private` and never published, so a consumer of `@scope/lib` can\n * never resolve it.\n *\n * **Attribution is by diff, not by guessing what a project imports.** Whatever\n * appeared in the root's `dependencies` while this generator ran belongs to the\n * project it just generated — accurate by construction, and it needs no import\n * analysis and no per-kind list to keep in sync as kinds are added.\n *\n * A value the project already declares **wins**: `add node-function-app` stamps\n * `@azure/functions` into the app manifest at the exact installed version, which\n * is more precise than the root's range.\n *\n * The lockfile is refreshed, because moving a dependency between manifests\n * leaves it stale. Measured: npm 10.9.7 runs `npm ci` against the stale lock\n * without complaint, which is leniency rather than correctness — the lock still\n * recorded the dependency against the root — and this repo has been bitten by\n * npm 10/11 divergence three times. A failure here warns rather than throws: the\n * project is already generated, and the remedy is one `npm install`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectName - The project just generated.\n * @param before - The root `dependencies` snapshot taken before the generator ran.\n * @returns Nothing.\n * @throws Never - an unreadable or absent project manifest leaves the root alone.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function relocateRootRuntimeDependencies (\n workspaceRoot: string,\n projectName: string,\n before: Record<string, string>,\n): void {\n const after = rootRuntimeDependencies(workspaceRoot)\n const added = Object.keys(after).filter(name => !Object.hasOwn(before, name))\n if (added.length === 0) {\n return\n }\n\n const manifestPath = PROJECT_MANIFEST_ROOTS.map(root =>\n join(workspaceRoot, root, projectName, 'package.json'),\n ).find(candidate => fileExists(candidate))\n if (manifestPath === undefined) {\n // Nothing to move them INTO — a Python, Go or Dart project has no npm\n // manifest. Leaving the root untouched is the honest outcome: dropping the\n // declaration would break resolution for whatever does need it.\n return\n }\n\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const owned = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n // `owned` is spread LAST so a value the project already declares wins, and\n // that ordering is the only mechanism enforcing it — filtering `moved` as\n // well would be a second, redundant guard that makes neither testable.\n const moved = Object.fromEntries(added.map(name => [name, after[name]]))\n writeFileEnsured(manifestPath, toJson({ ...manifest, dependencies: { ...moved, ...owned } }))\n\n const remaining = Object.fromEntries(\n Object.entries(after).filter(([name]) => !added.includes(name)),\n )\n const rootManifest = readJson<Record<string, unknown>>(join(workspaceRoot, 'package.json'))\n const { dependencies: _dropped, ...rest } = rootManifest\n writeFileEnsured(\n join(workspaceRoot, 'package.json'),\n toJson(Object.keys(remaining).length > 0 ? { ...rest, dependencies: remaining } : rest),\n )\n logger.step(`Moved ${added.join(', ')} into ${projectName}'s own manifest`)\n\n if (\n runShell(\n 'npm',\n ['install', '--package-lock-only', '--no-audit', '--no-fund'],\n workspaceRoot,\n ) !== 0\n ) {\n logger.warn(\n `Could not refresh package-lock.json after moving ${added.join(', ')}. Run 'npm install'.`,\n )\n }\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { logger } from '../terminal'\nimport { addProjectJsonTargets, ensureAdmZip, hasPlugin, registerProjectCommands } from './post-generation.use-case'\n\n/**\n * The `start` target for a Flutter app: `flutter run -d chrome`, locally.\n *\n * @remarks\n * Web is the only platform this plugin builds for ({@link addFlutterApp}), so\n * `-d chrome` matches — hot-reload against the same target the `build`\n * executor produces. `continuous: true` marks it as a long-running dev-server\n * task, the same shape every other kind's custom `start` target uses.\n *\n * @param name - The Flutter app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction flutterAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'flutter run -d chrome', cwd: `apps/${name}` },\n }\n}\n\n/**\n * Fails fast, with an install hint, when the Flutter SDK is not on the PATH.\n *\n * @remarks\n * Mirrors `ensureGo` in `add/go.ts` and `ensurePython` in `add/python.ts`:\n * probed before any install or generator call, so a missing toolchain\n * surfaces as one clear sentence rather than an opaque generator crash\n * halfway through scaffolding.\n *\n * This probe matters more here than for the other stacks: Python and Go both\n * ship on every hosted CI image, whereas Flutter does not — it is the one\n * toolchain the generated pipeline has to install itself.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `flutter` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureFlutter (workspaceRoot: string): void {\n if (runShell('flutter', ['--version'], workspaceRoot) !== 0) {\n throw new Error(\n 'Flutter not found. Install the Flutter SDK first (3.27+, for Dart 3.6+ pub workspaces): https://docs.flutter.dev/get-started/install',\n )\n }\n}\n\n/**\n * The `@mnci/nx-flutter` package spec to install.\n *\n * @remarks\n * Reads `MNCI_NX_FLUTTER_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `packages/nx-flutter` in this same monorepo)\n * instead of the published registry package — the published spec is the\n * default for every real `mnci add flutter-*` call. Same escape hatch\n * `add/python.ts` uses via `MNCI2_PYTHON_PIP_SPEC`.\n *\n * @returns The npm spec to install.\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxFlutterPluginSpec (): string {\n return process.env.MNCI_NX_FLUTTER_SPEC ?? '@mnci/nx-flutter'\n}\n\n/**\n * Ensures the `@mnci/nx-flutter` Nx plugin is installed.\n *\n * @remarks\n * Like `@mnci/nx-python-pip`, this plugin needs no `nx.json` `plugins`\n * registration: its generators and executors are explicit, resolved by plain\n * Node module lookup against its `generators.json`/`executors.json`.\n * Registration in `plugins` is only for inference plugins that scan the\n * filesystem, which this is not.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureNxFlutterPlugin (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@mnci/nx-flutter')) {\n return\n }\n const spec = nxFlutterPluginSpec()\n logger.step(`Installing the Flutter toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @mnci/nx-flutter failed')\n }\n}\n\n/** Shared preflight for every Flutter kind: toolchain, then plugin. */\nfunction prepareFlutter (workspaceRoot: string): void {\n ensureFlutter(workspaceRoot)\n ensureNxFlutterPlugin(workspaceRoot)\n}\n\n/**\n * Adds a Flutter web application under `apps/`.\n *\n * @remarks\n * Pure delegation to `@mnci/nx-flutter:application`, which scaffolds the app\n * via the official `flutter create`, registers it as a member of the root pub\n * workspace, and writes the lint/test/build/package targets. `adm-zip` is\n * installed here rather than by the plugin because the `package` (zip\n * into `dist/drop/`) convention is mnci's own CI contract, not something a\n * generic Nx plugin should assume.\n *\n * Web is the only platform scaffolded — see the plugin's `application`\n * generator for why (an Android build would drag the whole Android SDK onto\n * every build agent).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterApp (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:application', name, '--no-interactive'], workspaceRoot)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n start: flutterAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a publishable Flutter/Dart package under `packages/`.\n *\n * @remarks\n * \"Publishable\" means the same thing as it does for `go-lib`: there is **no\n * registry upload**. Azure Artifacts has no pub/Dart feed type, so publishing\n * is the git tag `nx release` creates and consumers depend on that tag.\n *\n * The plugin's generator stamps a project-level `versionActions` override so\n * `nx release` versions `pubspec.yaml`. That is not cosmetic: without it Nx\n * looks for a `package.json` a Dart package does not have and fails the\n * release for the *entire* workspace.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterLib (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:library', name, '--no-interactive'], workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n\n/**\n * Adds a private Flutter/Dart package under `libs/`.\n *\n * @remarks\n * Never released — `libs/` sits outside `release.projects`. Consumers depend\n * on it with a **plain version constraint and no `path:`**: being a member of\n * the root pub workspace is what makes pub resolve it locally. That is the\n * whole point of the pub-workspace model, and it means Flutter needs no\n * vendoring step at all (contrast `mnci add python-vendor`, which exists only\n * because pip cannot bundle an unpublished sibling into a wheel).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Flutter is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addFlutterInternalLib (workspaceRoot: string, name: string): void {\n prepareFlutter(workspaceRoot)\n runNx(['g', '@mnci/nx-flutter:internal-library', name, '--no-interactive'], workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n","import { readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport { makeGoAppReleasable } from './go-release.use-case'\nimport { addProjectJsonTargets, ensureAdmZip, hasPlugin, registerProjectCommands } from './post-generation.use-case'\n\n/**\n * Fails fast, with an install hint, when Go is not on the PATH.\n *\n * @remarks\n * Mirrors `ensurePython` in `add/python.ts`: probed before any install or\n * generator call so a missing toolchain surfaces as one clear sentence\n * rather than an opaque generator crash.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when `go` cannot be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureGo (workspaceRoot: string): void {\n if (runShell('go', ['version'], workspaceRoot) !== 0) {\n throw new Error('Go not found. Install Go 1.21+ first: https://go.dev/dl/')\n }\n}\n\n/**\n * Warns (without failing) when `golangci-lint` is not on the PATH.\n *\n * @remarks\n * Deliberately a warning, not an error: the generated `lint` target uses\n * `golangci-lint`, but a developer who only wants to build and test locally\n * should not be blocked at `add` time — CI installs it as its own step. Note\n * `@nx-go/nx-go`'s `lint` executor defaults to plain `go fmt` (formatting,\n * not linting), which is why mnci pins `golangci-lint` explicitly in the\n * target it writes.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Never - a missing linter only produces a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction warnIfNoGolangciLint (workspaceRoot: string): void {\n if (runShell('golangci-lint', ['--version'], workspaceRoot) !== 0) {\n logger.warn(\n 'golangci-lint not found — the generated lint target needs it. Install: https://golangci-lint.run/welcome/install/',\n )\n }\n}\n\n/**\n * The `@nx-go/nx-go` package spec to install.\n *\n * @remarks\n * Reads `MNCI_NX_GO_SPEC` so the e2e suite can pin or redirect the plugin\n * (e.g. to a local tarball) without touching this code; the published\n * package is the default for every real `mnci add go-*` call.\n *\n * Pinned to no particular version on purpose: `4.1.1` declares\n * `@nx/devkit \">= 20 < 23\"` while `4.1.0` declares `\">= 20 < 24\"`, but that\n * range is a plain dependency rather than a peer, so npm simply nests its\n * own devkit copy. Verified empirically against a real Nx 23.1.0 workspace:\n * generators, `build`, `test` and `lint` all work under 4.1.1.\n *\n * @returns The npm spec to install.\n * @throws Never - reads an environment variable.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxGoPluginSpec (): string {\n return process.env.MNCI_NX_GO_SPEC ?? '@nx-go/nx-go'\n}\n\n/**\n * Ensures the `@nx-go/nx-go` Nx plugin is installed.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureNxGoPlugin (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@nx-go/nx-go')) {\n return\n }\n const spec = nxGoPluginSpec()\n logger.step(`Installing the Go toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @nx-go/nx-go failed')\n }\n}\n\n/**\n * Idempotently bootstraps the workspace's single root `go.mod`.\n *\n * @remarks\n * This is the Go half of mnci's root-manifest model — the direct analogue of\n * the root `package.json` for TS and `requirements-dev.txt` for Python. Every\n * Go project in the workspace shares one module, so a library is imported as\n * `<module>/libs/<name>` with no per-project manifest and no replace\n * directives.\n *\n * The sequence is exact and order-sensitive, established empirically against\n * a real Nx 23.1.0 workspace:\n *\n * 1. `init` writes `go.work` and registers the plugin in `nx.json`.\n * 2. `convert-to-one-mod` deletes `go.work` and writes the root `go.mod`.\n *\n * Step 2 refuses to run once `go.work` contains any `use` line, so it must\n * happen before the first Go project exists — hence bootstrapping here, on\n * the first `mnci add go-*`, rather than lazily later. Skipped entirely once\n * `go.mod` exists, so repeat adds are cheap and a user's own edits (extra\n * `require` lines) survive.\n *\n * The multi-module `go.work` alternative was rejected deliberately: besides\n * splitting dependencies across per-project manifests, a single stale `use`\n * entry (a project directory removed by hand) makes `go list -m -json` fail,\n * which breaks the entire Nx project graph — not just the Go projects.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when either generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureGoModule (workspaceRoot: string): void {\n if (fileExists(join(workspaceRoot, 'go.mod'))) {\n return\n }\n logger.step('Bootstrapping the workspace Go module (single root go.mod)')\n runNx(['g', '@nx-go/nx-go:init', '--no-interactive'], workspaceRoot)\n runNx(['g', '@nx-go/nx-go:convert-to-one-mod', '--no-interactive'], workspaceRoot)\n}\n\n/**\n * The workspace's Go module path, read from the root `go.mod`.\n *\n * @remarks\n * Needed to tell a user the import path of a library that was just added.\n * `convert-to-one-mod` derives it from the root package.json name.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The module path, or `undefined` when `go.mod` is unreadable.\n * @throws Never - returns `undefined` rather than propagating a read error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goModulePath (workspaceRoot: string): string | undefined {\n try {\n // go.mod is not JSON, so it is read directly rather than via readJson.\n const contents = readFileSync(join(workspaceRoot, 'go.mod'), 'utf8')\n\n return /^module\\s+(\\S+)/m.exec(contents)?.[1]\n } catch {\n return undefined\n }\n}\n\n/**\n * The `test` target every Go project gets (`go test`).\n *\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goTestTarget (): Record<string, unknown> {\n return { executor: '@nx-go/nx-go:test' }\n}\n\n/**\n * The `lint` target every Go project gets (`golangci-lint run`).\n *\n * @remarks\n * The executor's own default is `go fmt`, which only reformats — pinning\n * `linter` to `golangci-lint` is what makes this an actual linter.\n *\n * **`parallelism: false` is not a performance knob, it is a correctness fix.**\n * `golangci-lint` takes a machine-global lock and refuses to run beside another\n * copy of itself, exiting non-zero with `parallel golangci-lint is running`. Nx\n * runs `lint` across projects concurrently by default, so a workspace with two\n * or more Go projects failed `nx run-many -t lint` at random — one project\n * reporting `0 issues` while a sibling died on the lock. Nothing to do with the\n * Go code, and the failure moves between projects from run to run, which is what\n * makes it so unpleasant to diagnose from a CI log.\n *\n * Found the first time the e2e ever ran this assertion: `golangci-lint` had\n * never been installed on the runner, so the whole check reported SKIPPED and\n * this shipped unnoticed. Go lint is now serialised across projects; the other\n * targets are untouched and still run in parallel.\n *\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goLintTarget (): Record<string, unknown> {\n return {\n executor: '@nx-go/nx-go:lint',\n parallelism: false,\n options: { linter: 'golangci-lint', args: ['run'] },\n }\n}\n\n/**\n * The `build` target for a Go executable (`go build`).\n *\n * @remarks\n * Builds to the workspace-root `dist/apps/<name>/` **directory**, with the\n * binary inside it, rather than to the executor's own default of a bare file\n * at `dist/apps/<name>`. That default cannot be declared as an Nx `outputs`\n * entry: Nx scans each declared output to cache it, and scanning a file\n * raises `ENOTDIR`. Building into a directory keeps the root-`dist`\n * convention, makes the output cacheable, and gives `package` a folder to\n * zip — verified end-to-end against a real workspace.\n *\n * `outputPath` is resolved relative to the executor's cwd (the project root),\n * hence the `../../` offset back to the workspace root.\n *\n * @param name - The project name, used for the project root in `outputs`.\n * @returns The nx target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goBuildTarget (name: string): Record<string, unknown> {\n return {\n executor: '@nx-go/nx-go:build',\n outputs: [`{workspaceRoot}/dist/apps/${name}`],\n options: { outputPath: `../../dist/apps/${name}/${name}` },\n }\n}\n\n/**\n * The `package` target for a Go app: zip its built binary into the drop.\n *\n * @remarks\n * Same cross-platform `adm-zip` one-liner used by every other packaged kind,\n * writing `dist/drop/<tag>-<name>.zip` — basename exactly `<tag>-<name>`,\n * the string CI turns into the per-app build tag. A Go binary is statically\n * linked, so unlike the Python kinds there is nothing else to inject.\n *\n * Zips the whole `dist/apps/<name>/` directory {@link goBuildTarget} writes,\n * so the same one-liner works whether the binary inside is `<name>` or\n * `<name>.exe` on a Windows agent.\n *\n * @param tag - The drop basename prefix (`go-app` or `go-function-app`).\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goPackageTarget (tag: string, name: string): Record<string, unknown> {\n const zip = `dist/drop/${tag}-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('dist/apps/${name}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The platforms a Go app is cross-compiled for by its `build-all` target.\n *\n * @remarks\n * Every desktop OS on both CPU families, the set an editor extension or a CLI that\n * ships its own binary has to cover. Go builds all of them from one machine without\n * cgo, which is why the targets below need no matrix of CI agents.\n */\nexport const GO_PLATFORMS = [\n 'windows/amd64', 'windows/arm64', 'linux/amd64', 'linux/arm64', 'darwin/amd64', 'darwin/arm64',\n] as const\n\n/**\n * The `build-all` target for a Go app: one static binary per {@link GO_PLATFORMS}.\n *\n * @remarks\n * Writes `dist/platforms/<name>/<goos>-<goarch>/<name>[.exe]` with `CGO_ENABLED=0`,\n * `-trimpath` and `-ldflags \"-s -w -X main.version=<VERSION>\"`, `VERSION` coming from\n * the environment (`dev` when unset), so a release stamps its tag without editing\n * anything.\n *\n * Deliberately NOT under `dist/apps/<name>/`, though that is where `build` writes: Nx\n * clears a target's declared outputs before restoring them from cache, so a cached\n * `build` would delete the cross-compiled binaries nested inside its directory.\n *\n * `VERSION` is an input, so a release never reuses a binary stamped `dev`; the root\n * `go.mod`/`go.sum` are inputs because a dependency bump changes every binary.\n *\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goBuildAllTarget (name: string): Record<string, unknown> {\n const platforms = JSON.stringify(GO_PLATFORMS).replaceAll('\"', \"'\")\n const command = `node -e \"const{spawnSync}=require('node:child_process');const v=process.env.VERSION||'dev';for(const p of ${platforms}){const[os,arch]=p.split('/');const out='../../dist/platforms/${name}/'+os+'-'+arch+'/${name}'+(os==='windows'?'.exe':'');const r=spawnSync('go',['build','-trimpath','-ldflags','-s -w -X main.version='+v,'-o',out,'.'],{cwd:'apps/${name}',stdio:'inherit',env:{...process.env,CGO_ENABLED:'0',GOOS:os,GOARCH:arch}});if(r.status!==0)process.exit(r.status??1)}\"`\n\n return {\n executor: 'nx:run-commands',\n inputs: ['default', '^default', '{workspaceRoot}/go.mod', '{workspaceRoot}/go.sum', { env: 'VERSION' }],\n outputs: [`{workspaceRoot}/dist/platforms/${name}`],\n options: { command },\n }\n}\n\n/**\n * The `package-all` target for a Go app: one zip per platform {@link goBuildAllTarget}\n * built.\n *\n * @remarks\n * `dist/drop/<tag>-<name>-<goos>-<goarch>.zip`, each holding that platform's binary,\n * the same `adm-zip` one-liner as {@link goPackageTarget}. The single-platform\n * `package` target keeps its name, so CI's drop handling is unchanged.\n *\n * @param tag - The drop basename prefix (`go-app` or `go-function-app`).\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goPackageAllTarget (tag: string, name: string): Record<string, unknown> {\n const command = `node -e \"const fs=require('node:fs');const A=require('adm-zip');fs.mkdirSync('dist/drop',{recursive:true});for(const d of fs.readdirSync('dist/platforms/${name}')){const z=new A();z.addLocalFolder('dist/platforms/${name}/'+d);z.writeZip('dist/drop/${tag}-${name}-'+d+'.zip')}\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build-all'],\n outputs: [`{workspaceRoot}/dist/drop/${tag}-${name}-*.zip`],\n options: { command },\n }\n}\n\n/**\n * Gives every Go app in the workspace its `build-all` and `package-all` targets when it\n * lacks them: the `mnci upgrade` path for apps added before the targets existed.\n *\n * @remarks\n * Only adds; a target the user already has, under either name, is never touched.\n * Idempotent, so a repeat upgrade is a no-op.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The `project.json` files it changed, workspace-relative.\n * @throws Error when a Go app's `project.json` is not valid JSON.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoPlatformTargets (workspaceRoot: string): string[] {\n const changed: string[] = []\n const apps = join(workspaceRoot, 'apps')\n if (!fileExists(apps)) {\n return changed\n }\n for (const name of readdirSync(apps)) {\n const projectJsonPath = join(apps, name, 'project.json')\n if (!fileExists(projectJsonPath)) {\n continue\n }\n const project = JSON.parse(readFileSync(projectJsonPath, 'utf8')) as { tags?: string[], targets?: Record<string, unknown> }\n const tag = (project.tags ?? []).find(each => each === 'type:go-app' || each === 'type:go-function-app')?.slice('type:'.length)\n if (tag === undefined) {\n continue\n }\n const missing: Record<string, unknown> = {}\n if (project.targets?.['build-all'] === undefined) {\n missing['build-all'] = goBuildAllTarget(name)\n }\n if (project.targets?.['package-all'] === undefined) {\n missing['package-all'] = goPackageAllTarget(tag, name)\n }\n if (Object.keys(missing).length > 0) {\n addProjectJsonTargets(projectJsonPath, missing)\n changed.push(`apps/${name}/project.json`)\n }\n }\n\n return changed\n}\n\n/**\n * The `start` target for a Go app: `go run .`, locally.\n *\n * @remarks\n * `go run` compiles and runs from source in one step — unlike\n * {@link goBuildTarget}, no separate build/`dependsOn` is needed.\n * `continuous: true` marks it as a long-running dev task, the same shape\n * every other kind's custom `start` target uses.\n *\n * @param name - The Go app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction goStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'go run .', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The Go identifiers `@nx-go/nx-go:library` derives from a project name.\n *\n * @remarks\n * Matches the plugin's own `normalizeOptions` for hyphenated names: the\n * package clause is `names(projectName).propertyName.toLowerCase()` and the\n * sample function is `names(projectName).className`. Re-derived here rather\n * than imported because `@nx/devkit` is not a runtime dependency of the CLI.\n * Project names are validated to lowercase letters, digits, `-` and `.`\n * (`project-name.validator.ts`), and splitting on every non-alphanumeric\n * character also covers the dotted names the plugin leaves as an invalid\n * package clause (`my.lib` → `mylib`, not `my.lib`).\n *\n * `fileStem` is the snake-case form used for the role-suffixed file names\n * (`markdown-workspace` → `markdown_workspace`), the Go spelling of the\n * `<kebab>.<role>.ts` convention.\n *\n * @param projectName - The validated project name.\n * @returns The package name, the exported function name and the file stem.\n * @throws Never - pure string computation.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function goLibraryIdentifiers (projectName: string): {\n packageName: string\n functionName: string\n fileStem: string\n} {\n const words = projectName.split(/[^a-z0-9]+/i).filter(word => word.length > 0)\n\n return {\n packageName: words.join('').toLowerCase(),\n functionName: words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''),\n fileStem: words.join('_').toLowerCase(),\n }\n}\n\n/**\n * Reshapes a freshly generated Go library into a capability with one slice.\n *\n * @remarks\n * `@nx-go/nx-go:library` writes `<projectName>.go` and `<projectName>_test.go`\n * at the project root, so the root package IS the library. That contradicts\n * the vertical-slice shape mnci follows everywhere else (capability → flat\n * slice → role-suffixed files, with only an entry point at the root), and a\n * library that grows from it grows as one flat package that never splits.\n * Found while bootstrapping Lore Master, whose three Go libraries are each\n * several slice packages (russoedu/MoNecromanCi#227).\n *\n * After this runs, the root holds only `doc.go` (the capability's package\n * comment) and the generator's sample code lives in one starter slice:\n *\n * ```\n * libs/markdown-workspace/\n * ├── doc.go package markdownworkspace\n * ├── project.json\n * └── markdownworkspace/\n * ├── doc.go\n * ├── markdown_workspace_use_case.go func MarkdownWorkspace(name string) string\n * └── markdown_workspace_use_case_test.go\n * ```\n *\n * `use_case` is the role for the same reason the TypeScript placeholder is\n * renamed to `.use-case.ts` (`renameScaffoldPlaceholder`): it is the generic\n * role for a library's public behaviour. Unconditional for the same reason\n * too — whether a workspace \"uses\" slices is not knowable from the files, and\n * the shape costs nothing when ignored.\n *\n * The project's `test` and `lint` targets need no change: the plugin's\n * executors run `go test ./...` and `<linter> run ./...` from the project\n * root, so the slice package below it is covered (russoedu/MoNecromanCi#233).\n *\n * Idempotent: the root placeholders are removed with `force`, and the slice\n * files are only written when absent, so a user's edits survive a re-run.\n *\n * @param projectRoot - Absolute path to the generated library.\n * @param projectName - The project name the generator used for its files.\n * @returns The import path suffix of the starter slice, relative to the module.\n * @throws Propagates any `fs` error writing the new files.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reshapeGoLibraryScaffold (projectRoot: string, projectName: string): string {\n const { packageName, functionName, fileStem } = goLibraryIdentifiers(projectName)\n\n rmSync(join(projectRoot, `${projectName}.go`), { force: true })\n rmSync(join(projectRoot, `${projectName}_test.go`), { force: true })\n\n const files: ReadonlyArray<readonly [string, string]> = [\n [\n 'doc.go',\n `// Package ${packageName} is the ${projectName} capability. Its code lives in the\\n` +\n '// slice packages below this directory, one package per outcome.\\n' +\n `package ${packageName}\\n`,\n ],\n [\n join(packageName, 'doc.go'),\n `// Package ${packageName} is the starter slice of ${projectName}: rename it after\\n` +\n '// the outcome it delivers, and add one package per further outcome.\\n' +\n `package ${packageName}\\n`,\n ],\n [\n join(packageName, `${fileStem}_use_case.go`),\n `package ${packageName}\\n\\n` +\n `// ${functionName} is the generator's sample behaviour, kept so the slice builds and tests.\\n` +\n `func ${functionName}(name string) string {\\n` +\n `\\treturn \"${functionName} \" + name\\n` +\n '}\\n',\n ],\n [\n join(packageName, `${fileStem}_use_case_test.go`),\n `package ${packageName}\\n\\n` +\n 'import \"testing\"\\n\\n' +\n `func Test${functionName}(t *testing.T) {\\n` +\n `\\tif got := ${functionName}(\"works\"); got != \"${functionName} works\" {\\n` +\n '\\t\\tt.Fatalf(\"got %q\", got)\\n' +\n '\\t}\\n' +\n '}\\n',\n ],\n ]\n for (const [relativePath, content] of files) {\n const path = join(projectRoot, relativePath)\n if (!fileExists(path)) {\n writeFileEnsured(path, content)\n }\n }\n\n return packageName\n}\n\n/** Shared preflight for every Go kind: toolchain, plugin and root module. */\nfunction prepareGo (workspaceRoot: string): void {\n ensureGo(workspaceRoot)\n warnIfNoGolangciLint(workspaceRoot)\n ensureNxGoPlugin(workspaceRoot)\n ensureGoModule(workspaceRoot)\n}\n\n/**\n * Adds a Go executable app under `apps/`.\n *\n * @remarks\n * Delegates project generation to `@nx-go/nx-go:application`, then writes the\n * build/test/lint targets (nothing is inferred in single-module mode) plus\n * mnci's own `package` zip convention. With `release`, the app also joins\n * `nx release` (see {@link makeGoAppReleasable}); without it, it is never released.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - `release`: release the app, versioned from its git tag.\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoApp (workspaceRoot: string, name: string, options: { release?: boolean } = {}): void {\n prepareGo(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:application',\n `apps/${name}`,\n `--name=${name}`,\n '--tags=type:go-app',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n 'build': goBuildTarget(name),\n 'test': goTestTarget(),\n 'lint': goLintTarget(),\n 'package': goPackageTarget('go-app', name),\n 'build-all': goBuildAllTarget(name),\n 'package-all': goPackageAllTarget('go-app', name),\n 'start': goStartTarget(name),\n })\n if (options.release === true) {\n makeGoAppReleasable(workspaceRoot, name)\n }\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a Go serverless function app under `apps/`.\n *\n * @remarks\n * Structurally the same as {@link addGoApp} — a Go function app is an\n * ordinary executable whose `main` is the platform's handler entry point —\n * so this shares the generator and targets, differing only in its tag and\n * its drop basename (`go-function-app-<name>`). The handler body itself is\n * left to the user: AWS Lambda, Google Cloud Functions and Azure each want a\n * different signature, and mnci does not pick one for you.\n *\n * No `start` target, unlike `node-function-app`/`python-function-app`:\n * `func start` needs a `host.json` (and, for the custom-handler model Go\n * would use, a matching `customHandler` config), and this kind writes\n * neither — an honest known gap, not an oversight papered over with a\n * command that would just fail. `go-app`'s `go run .` doesn't apply either,\n * since there is no Functions host to dispatch triggers to it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoFunctionApp (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:application',\n `apps/${name}`,\n `--name=${name}`,\n '--tags=type:go-function-app',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n 'build': goBuildTarget(name),\n 'test': goTestTarget(),\n 'lint': goLintTarget(),\n 'package': goPackageTarget('go-function-app', name),\n 'build-all': goBuildAllTarget(name),\n 'package-all': goPackageAllTarget('go-function-app', name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds a publishable Go library under `packages/`.\n *\n * @remarks\n * \"Publishable\" means something different in Go than in npm or PyPI, and it\n * is worth being precise: there is no registry upload step. The whole\n * repository is one module, so consumers depend on this library by its\n * import path at a repo-level version tag —\n * `go get <module>/packages/<name>@v1.2.3`. Publishing is therefore the git\n * tag `nx release` already creates; no `nx-release-publish` target is\n * written, because there is nothing to push. The only real difference from\n * an internal library is intent, recorded in the `type:go-lib` tag and the\n * `packages/` location.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoLib (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:library',\n `packages/${name}`,\n `--name=${name}`,\n '--tags=type:go-lib',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'packages', name, 'project.json'), {\n test: goTestTarget(),\n lint: goLintTarget(),\n })\n const slice = reshapeGoLibraryScaffold(join(workspaceRoot, 'packages', name), name)\n registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import its starter slice as ${module}/packages/${name}/${slice}`)\n }\n}\n\n/**\n * Adds a private Go library under `libs/`.\n *\n * @remarks\n * Identical machinery to {@link addGoLib} minus the publishable intent: same\n * single root module, imported as `<module>/libs/<name>`. Test and lint\n * targets only — a Go package that is not `main` produces no binary, so\n * there is no build target to write.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Go is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addGoInternalLib (workspaceRoot: string, name: string): void {\n prepareGo(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@nx-go/nx-go:library',\n `libs/${name}`,\n `--name=${name}`,\n '--tags=type:go-internal-lib',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n addProjectJsonTargets(join(workspaceRoot, 'libs', name, 'project.json'), {\n test: goTestTarget(),\n lint: goLintTarget(),\n })\n const slice = reshapeGoLibraryScaffold(join(workspaceRoot, 'libs', name), name)\n registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import its starter slice as ${module}/libs/${name}/${slice}`)\n }\n}\n","import { readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { GO_RELEASE_TAG } from '../workspace-overlay'\n\n/**\n * Where the script lives in a workspace.\n *\n * @remarks\n * Also the value of a releasable app's `release.version.versionActions`, which Nx\n * resolves relative to the workspace root.\n */\nexport const GO_RELEASE_SCRIPT_PATH = 'tools/go-app-release.cjs'\n\n/**\n * The `tools/go-app-release.cjs` script: the version actions and the asset upload of a releasable Go app.\n *\n * @remarks\n * One file with two roles, because both exist for the same reason: a Go app has no\n * manifest, and `nx release` is built around one.\n *\n * - **As `release.version.versionActions`.** Nx's default implementation reads a\n * `package.json`, and a project without one aborts the release graph for every\n * project in the workspace (the `go-lib` failure, and the Dart one before it). This\n * class declares no manifest, so the version lives in the git tag alone, which is\n * all mnci releases ever write (`git.commit` is `false`). The project's\n * `currentVersionResolver` is `git-tag`; for the very first release, when no tag\n * exists, the disk fallback is answered with a `0.0.0` base, so the first release is\n * `0.0.1` (Nx's own rule for a `0.x` base) instead of a failure. To start elsewhere,\n * push a `<name>@<version>` tag before the first release.\n * - **As `node tools/go-app-release.cjs assets`.** After `nx release` has tagged, it\n * builds `package-all` with `VERSION` set to the tag's version, so the binary\n * reports it, and attaches the per-platform zips to that tag's GitHub Release.\n * Tags are read from `HEAD`, so a run that released nothing uploads nothing, and\n * `--clobber` makes a re-run harmless.\n *\n * `nx` is run through its own `package.json` `bin` entry with the current `node`,\n * not through a shell, so a workspace path with a space in it works on Windows.\n */\nexport const GO_RELEASE_SCRIPT = String.raw`#!/usr/bin/env node\n// Written by mnci: 'mnci add go-app --release' creates it and 'mnci upgrade'\n// rewrites it, so local edits do not survive an upgrade.\n//\n// A releasable Go app (tag release:go) is versioned from its git tag, with no\n// manifest, and its per-platform zips are attached to its GitHub Release:\n// release.version.versionActions this file, loaded by 'nx release'\n// node tools/go-app-release.cjs assets\n'use strict'\nconst { spawnSync } = require('node:child_process')\nconst { existsSync, readdirSync } = require('node:fs')\nconst { dirname, join } = require('node:path')\nconst { VersionActions } = require('nx/release')\n\nconst FIRST_RELEASE_BASE = '0.0.0'\n\nclass GoAppVersionActions extends VersionActions {\n validManifestFilenames = null\n\n async validate () {\n // Nothing on disk to check: there is no manifest.\n }\n\n async readCurrentVersionFromSourceManifest () {\n // Reached only as the fallback of the git-tag resolver, i.e. before the first tag.\n return { currentVersion: FIRST_RELEASE_BASE, manifestPath: 'none (a Go app is versioned by its git tag)' }\n }\n\n async readCurrentVersionFromRegistry () {\n return null\n }\n\n async readCurrentVersionOfDependency () {\n return { currentVersion: null, dependencyCollection: null }\n }\n\n async updateProjectVersion () {\n return []\n }\n\n async updateProjectDependencies () {\n return []\n }\n}\n\nmodule.exports = GoAppVersionActions\n\nfunction fail (message) {\n console.error(message)\n process.exit(1)\n}\n\nfunction run (command, args, env) {\n const result = spawnSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'], env: { ...process.env, ...env }, shell: false })\n if (result.status !== 0) fail(command + ' ' + args.join(' ') + ' failed (exit ' + result.status + ')')\n\n return result.stdout\n}\n\nfunction nx (args, env) {\n const manifest = require.resolve('nx/package.json')\n const bin = require(manifest).bin\n const entry = typeof bin === 'string' ? bin : bin.nx\n\n return run(process.execPath, [join(dirname(manifest), entry), ...args], env)\n}\n\nfunction releasableApps () {\n return JSON.parse(nx(['show', 'projects', '--projects', 'tag:release:go', '--json'])).filter(Boolean)\n}\n\nfunction attachAssets () {\n const apps = releasableApps()\n if (apps.length === 0) {\n console.log('No releasable Go app - nothing to attach.')\n\n return\n }\n const tags = run('git', ['tag', '--points-at', 'HEAD']).split(/\\r?\\n/).filter(Boolean)\n let attached = 0\n for (const app of apps) {\n const tag = tags.find(each => each.startsWith(app + '@'))\n if (!tag) {\n console.log(app + ': not released by this run - skipping.')\n continue\n }\n const version = tag.slice(app.length + 1)\n console.log(app + ': building the six platforms as ' + version)\n nx(['run', app + ':package-all'], { VERSION: version })\n const prefix = 'go-app-' + app + '-'\n const zips = existsSync('dist/drop') ? readdirSync('dist/drop').filter(each => each.startsWith(prefix) && each.endsWith('.zip')) : []\n if (zips.length === 0) fail(app + ': package-all produced no ' + prefix + '*.zip in dist/drop.')\n const upload = spawnSync('gh', ['release', 'upload', tag, ...zips.map(each => join('dist/drop', each)), '--clobber'], { stdio: 'inherit', shell: process.platform === 'win32' })\n if (upload.status !== 0) fail(app + ': could not attach the zips to the ' + tag + ' release (exit ' + upload.status + ').')\n console.log(app + ': attached ' + zips.length + ' zips to ' + tag)\n attached += 1\n }\n console.log(attached + ' release(s) carry their platform zips.')\n}\n\nif (require.main === module) {\n if (process.argv[2] !== 'assets') fail('Usage: node tools/go-app-release.cjs assets')\n attachAssets()\n}\n`\n\n/** The slice of a `project.json` this module reads and writes. */\ninterface ReleasableProject {\n tags?: string[]\n targets?: Record<string, unknown>\n release?: { version?: Record<string, unknown> }\n}\n\n/**\n * The `nx-release-publish` target of a releasable Go app, which publishes nothing.\n *\n * @remarks\n * The target has to exist, not because anything is published: `nx release` tags first\n * and publishes last, and a project matched for publishing without the target aborts\n * the run with exit 1 after the tag exists (measured). The same reason the VS Code\n * extension and the C# library carry one. Publishing a Go app is its tag, and the\n * zips are attached to its GitHub Release by `tools/go-app-release.cjs assets`.\n */\nconst GO_RELEASE_PUBLISH_TARGET = {\n executor: 'nx:run-commands',\n options: {\n command: 'node -e \"console.log(\\'Published by its git tag; the platform zips are attached to the GitHub Release by tools/go-app-release.cjs assets.\\')\"',\n },\n} as const\n\n/**\n * Makes an existing Go app releasable: the tag, the project-level version config, and the script.\n *\n * @remarks\n * The tag is what puts the app in `nx release` scope (`release.projects` selects\n * `tag:release:go`, as it does for a VS Code extension), so an internal tool stays\n * unreleased unless it opts in. The project-level `release.version` is what keeps\n * Nx from looking for a `package.json`; it wins over the workspace default. The\n * no-op `nx-release-publish` target stops the publish phase aborting after the tag\n * exists. Safe to run twice, and a publish target the app already has is kept.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The Go app's project name.\n * @returns Nothing.\n * @throws Error when `apps/<name>/project.json` does not exist or is not valid JSON.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function makeGoAppReleasable (workspaceRoot: string, name: string): void {\n const projectJsonPath = join(workspaceRoot, 'apps', name, 'project.json')\n if (!fileExists(projectJsonPath)) {\n throw new Error(`No Go app at apps/${name}: cannot make it releasable.`)\n }\n const project = readJson<ReleasableProject>(projectJsonPath)\n writeFileEnsured(\n projectJsonPath,\n toJson({\n ...project,\n tags: [...new Set([...(project.tags ?? []), GO_RELEASE_TAG])],\n targets: { ...project.targets, 'nx-release-publish': project.targets?.['nx-release-publish'] ?? GO_RELEASE_PUBLISH_TARGET },\n release: {\n ...project.release,\n version: {\n ...project.release?.version,\n versionActions: GO_RELEASE_SCRIPT_PATH,\n currentVersionResolver: 'git-tag',\n },\n },\n }),\n )\n writeFileEnsured(join(workspaceRoot, GO_RELEASE_SCRIPT_PATH), GO_RELEASE_SCRIPT)\n}\n\n/**\n * Rewrites {@link GO_RELEASE_SCRIPT_PATH} when the workspace has a releasable Go app.\n *\n * @remarks\n * Called by `mnci upgrade`, so a fix to the script reaches every workspace that\n * uses it, and a workspace with no releasable Go app never gains the file.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Whether the file changed (an up-to-date script is left alone).\n * @throws Propagates any `fs` error writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function refreshGoReleaseScript (workspaceRoot: string): boolean {\n const apps = join(workspaceRoot, 'apps')\n let entries: string[]\n try {\n entries = readdirSync(apps)\n } catch {\n return false\n }\n const hasReleasableApp = entries.some((entry) => {\n const projectJsonPath = join(apps, entry, 'project.json')\n\n return fileExists(projectJsonPath) && (readJson<ReleasableProject>(projectJsonPath).tags ?? []).includes(GO_RELEASE_TAG)\n })\n const path = join(workspaceRoot, GO_RELEASE_SCRIPT_PATH)\n if (!hasReleasableApp || (fileExists(path) && readFileSync(path, 'utf8') === GO_RELEASE_SCRIPT)) {\n return false\n }\n writeFileEnsured(path, GO_RELEASE_SCRIPT)\n\n return true\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addNxTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type NodeFramework,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Ensures an Nx plugin is installed in the workspace, installing it on first use.\n *\n * @remarks\n * `nx add` installs the package and runs its init generator — the Nx-native\n * way to bring a plugin into an existing workspace. Same pattern as\n * `add/reactApp.ts`'s `ensurePlugin`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param packageName - The plugin package (e.g. `@nx/node`).\n * @returns Nothing.\n * @throws Error when the underlying `nx add` exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePlugin (workspaceRoot: string, packageName: string): void {\n if (hasPlugin(workspaceRoot, packageName)) {\n return\n }\n logger.step(`Installing Nx plugin ${packageName}`)\n runNx(['add', packageName], workspaceRoot)\n}\n\n/**\n * Generates a Node app with the official `@nx/node:application` generator.\n *\n * @remarks\n * Plain delegation — no framework-specific logic here at all, `framework` is\n * passed straight through to the generator. `--bundler=esbuild` is requested\n * either way, but verified empirically that `--framework=nest` silently\n * overrides it: NestJS needs its own webpack build (decorator/DI metadata\n * emission esbuild's transform-only approach can't produce), so a\n * `nest`-flavoured app builds to a single webpack-bundled `dist/main.js`\n * instead of the esbuild non-bundled mirrored-tree + shim the other\n * frameworks (and `none`) produce. {@link nodeAppPackageTarget} needs no\n * framework branch regardless — either shape's runnable entry is\n * `dist/main.js`, so zipping the whole `dist` folder works unchanged. Both\n * `node-app` and `node-function-app` generate identically (function apps\n * always pass `'none'` — see {@link addNodeFunctionApp}) — the function app\n * is this plus an Azure Functions v2 file overlay, the same split already\n * used for `python-app`/`python-function-app` (`add/python.ts`).\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @param framework - The HTTP framework to scaffold (`none` for a bare app).\n * @returns Nothing.\n * @throws Error when the generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runNodeApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n framework: NodeFramework,\n): void {\n ensurePlugin(workspaceRoot, '@nx/node')\n runNx(\n [\n 'g',\n '@nx/node:application',\n `apps/${name}`,\n '--bundler=esbuild',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--e2eTestRunner=none',\n `--framework=${framework}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n const appRoot = join(workspaceRoot, 'apps', name)\n separateDeclarationOutput(appRoot)\n repairAssetMapping(appRoot, name)\n}\n\n/**\n * Moves the app's declaration output out of the folder its own build empties.\n *\n * @remarks\n * `@nx/node:application --bundler=esbuild` writes `outDir: 'dist'` and\n * `tsBuildInfoFile: 'dist/...'` into `tsconfig.app.json`, while the build it\n * generates has `outputPath: 'apps/<name>/dist'` and empties that folder before\n * writing. Nx runs `typecheck` and `build` in parallel, so the build's clean\n * deletes the declarations `tsc --build` just wrote, and the spec project —\n * which references `tsconfig.app.json` — then fails with TS6305, reporting that\n * the output file in `dist` has not been built from its source file.\n *\n * Reproduced on a freshly generated workspace before changing anything: run the\n * app's typecheck, delete the `.d.ts` files the build's clean removes, run it\n * again, and TS6305 arrives every time. With the declarations in `out-tsc/app`\n * the same sequence passes.\n *\n * `out-tsc` is already in `tsconfig.app.json`'s `exclude` and in the generated\n * `.gitignore`, so nothing else has to move. The esbuild executor takes its\n * output from `outputPath`, never from the tsconfig, so `dist` is unchanged\n * apart from the tsbuildinfo no longer landing there.\n *\n * @param appRoot - Absolute path to the generated app's directory.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the tsconfig.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction separateDeclarationOutput (appRoot: string): void {\n const tsconfigPath = join(appRoot, 'tsconfig.app.json')\n if (!fileExists(tsconfigPath)) {\n return\n }\n const tsconfig = readJson<{ compilerOptions?: Record<string, unknown> }>(tsconfigPath)\n const compilerOptions = tsconfig.compilerOptions ?? {}\n if (compilerOptions.outDir !== 'dist') {\n return\n }\n writeFileEnsured(\n tsconfigPath,\n toJson({\n ...tsconfig,\n compilerOptions: {\n ...compilerOptions,\n outDir: 'out-tsc/app',\n tsBuildInfoFile: 'out-tsc/app/tsconfig.app.tsbuildinfo',\n },\n }),\n )\n}\n\n/**\n * Rewrites the generated asset mapping so assets land where the app reads them.\n *\n * @remarks\n * The generator writes the **string** form, `assets: ['apps/<name>/src/assets']`.\n * Under `@nx/esbuild` 23 that does not put the files at `dist/assets/`; measured\n * on a real build, a file placed in `src/assets` does not reach `dist` **at\n * all**. The generated `src/assets/.gitkeep` hides it until someone adds a real\n * asset, and then code reading `new URL('../assets/...', import.meta.url)` from\n * `dist/functions/*.js` simply finds nothing.\n *\n * The object form states input, glob and output explicitly, and with it the same\n * file lands at `dist/assets/<file>` — verified on a generated workspace.\n *\n * @param appRoot - Absolute path to the generated app's directory.\n * @param name - The app's project name, used to build the workspace-relative input.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repairAssetMapping (appRoot: string, name: string): void {\n const manifestPath = join(appRoot, 'package.json')\n if (!fileExists(manifestPath)) {\n return\n }\n const manifest = readJson<{\n nx?: { targets?: { build?: { options?: Record<string, unknown> } } }\n }>(manifestPath)\n const options = manifest.nx?.targets?.build?.options\n if (!options || !Array.isArray(options.assets)) {\n return\n }\n options.assets = options.assets.map(asset =>\n (typeof asset === 'string'\n ? { input: asset, glob: '**/*', output: asset.replace(`apps/${name}/src/`, '') }\n : asset),\n )\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * The `package` target for a plain Node app: zip its build output into the drop.\n *\n * @remarks\n * `@nx/esbuild:esbuild --bundle=false` mirrors the full workspace-relative\n * source tree into `apps/<name>/dist` (e.g. `dist/apps/<name>/src/main.js`)\n * plus a `dist/main.js` shim that `require`s it — so zipping the whole `dist`\n * folder is the complete, runnable output. Basename exactly `node-app-<name>`,\n * the string CI turns into the per-app build tag. Same cross-platform\n * `adm-zip` one-liner used throughout `add`.\n *\n * @param name - The Node app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/node-app-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('apps/${name}/dist');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Adds a plain Node app: `@nx/node:application` plus a packaging target.\n *\n * @remarks\n * Pure delegation to the official generator (esbuild, non-bundled unless\n * `framework` forces its own build — see {@link runNodeApp}) — no custom\n * build rewiring, no relocated output; every kind builds to its own\n * Nx-default location. {@link nodeAppPackageTarget} is the one thing the\n * generator doesn't do: zip the build into the CI drop.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @param framework - The HTTP framework to scaffold (defaults to `none`, a bare Node app).\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNodeApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n framework: NodeFramework = 'none',\n): void {\n runNodeApp(workspaceRoot, name, stack, framework)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(workspaceRoot, 'apps', name, 'package.json'), {\n package: nodeAppPackageTarget(name),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:serve` })\n}\n\n/**\n * Ensures `@azure/functions` is installed at the workspace root.\n *\n * @remarks\n * Unlike the removed `@nxazure/func` plugin (whose generators pulled this in\n * as a side effect), a plain `@nx/node:application` app has no Azure\n * Functions dependency by default, so `add node-function-app` installs it\n * for real — the version then gets read back and stamped into the app's own\n * manifest (see {@link repairNodeFunctionAppManifest}) for Azure's deploy-time\n * `npm install` (Oryx) to resolve.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureAzureFunctionsPackage (workspaceRoot: string): void {\n if (hasPlugin(workspaceRoot, '@azure/functions')) {\n return\n }\n logger.step('Installing @azure/functions')\n if (\n runShell('npm', ['install', '@azure/functions', '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @azure/functions failed')\n }\n}\n\n/**\n * The bundle entry point written into every generated Node function app.\n *\n * @remarks\n * `@nx/esbuild:esbuild` only includes what is reachable from `main.ts` — the\n * same convention the removed hand-rolled function app used — so add one\n * import per function file you create under `src/functions/`. The import\n * runs `app.http(...)` (or another trigger registration) as a side effect;\n * nothing needs to be re-exported.\n */\nexport const NODE_FUNCTION_APP_MAIN = `// esbuild only includes what is reachable from here, so add one import per\n// function file you create under src/functions/.\nimport './functions/hello'\n`\n\n/**\n * The `host.json` written into a generated Node function app.\n *\n * @remarks\n * Identical shape to the Python function app's `host.json` (written by\n * `@mnci/nx-python-pip:function-application`, the plugin `add/python.ts`\n * delegates to) — the schema is language-agnostic. The v4 extension bundle\n * is what the Functions host uses to resolve bindings; `version: 2.0` is the\n * runtime schema. Deliberately minimal.\n */\nexport const NODE_FUNCTION_APP_HOST_JSON = `{\n \"version\": \"2.0\",\n \"extensionBundle\": {\n \"id\": \"Microsoft.Azure.Functions.ExtensionBundle\",\n \"version\": \"[4.*, 5.0.0)\"\n }\n}\n`\n\n/**\n * The Azure Functions v4 HTTP trigger written into a generated Node function app.\n *\n * @remarks\n * The Node v4 programming model: `app.http(...)` registers a route at import\n * time. The handler is thin — the testable logic lives in\n * {@link NODE_FUNCTION_APP_GREETING} (imported here) — so the sample spec\n * needs no `@azure/functions` mocking. Anonymous auth keeps the sample\n * runnable locally with `func start`.\n */\nexport const NODE_FUNCTION_APP_HELLO = `import { app } from '@azure/functions'\nimport type { HttpRequest, HttpResponseInit } from '@azure/functions'\nimport { buildGreeting } from './greeting'\n\nasync function hello(request: HttpRequest): Promise<HttpResponseInit> {\n const name = request.query.get('name') ?? 'world'\n return { body: buildGreeting(name) }\n}\n\napp.http('hello', {\n methods: ['GET'],\n authLevel: 'anonymous',\n route: 'hello',\n handler: hello,\n})\n`\n\n/**\n * A sample pure helper written into every generated Node function app.\n *\n * @remarks\n * Gives the app a genuinely testable unit (the HTTP handler would need\n * `@azure/functions` mocking), so the generator's own jest/vitest wiring has\n * a real passing test out of the box. Delete it once you have your own.\n */\nexport const NODE_FUNCTION_APP_GREETING = `export function buildGreeting (name: string): string {\n return 'Hello, ' + name + '!'\n}\n`\n\n/**\n * The sample spec proving the function app's test target runs.\n *\n * @remarks\n * Deliberately dependency-free (no Azure SDK), so it passes on a bare\n * workspace under either jest or vitest — `@nx/node:application` wires the\n * chosen runner natively (unlike the removed hand-rolled function app, which\n * carried no test setup of its own).\n */\nexport const NODE_FUNCTION_APP_GREETING_SPEC = `import { buildGreeting } from './greeting'\n\ndescribe('buildGreeting', () => {\n it('greets a name', () => {\n expect(buildGreeting('world')).toBe('Hello, world!')\n })\n})\n`\n\n/**\n * Repairs a Node function app's manifest for an Azure deploy: `main` + the real dependency.\n *\n * @remarks\n * `@nx/esbuild:esbuild --bundle=false` writes a `dist/main.js` shim that\n * `require`s the compiled entry (verified empirically) — so `main` is\n * `dist/main.js`, resolved relative to wherever this manifest sits. That\n * makes the source layout (`host.json` + `package.json` + `dist/main.js`,\n * once built, all under `apps/<name>`) directly `func start`-able, which\n * matters: unlike `python-function-app`, there is no pre-build step Azure\n * Functions Core Tools does for you, so the local `start` target\n * ({@link nodeFunctionAppStartTarget}) depends on `build` and then runs\n * `func start` right here, no staging copy needed.\n * {@link nodeFunctionAppPackageTarget}\n * mirrors this same relative layout inside the deploy zip (nesting `dist/`\n * rather than flattening it), so one `main` value is correct for both local\n * dev and the Azure deploy — verified against a real generated app that\n * `main.js` sitting flat (the old shape) would have broken `func start`\n * entirely (`dist/main.js` never exists at `apps/<name>/main.js`).\n * `@azure/functions` is added for real (not just referenced): Azure's\n * deploy-time Oryx build reads this manifest's `dependencies` and runs\n * `npm install` there — verified empirically that a plain `npm install` in a\n * simulated deploy folder (no bundled `node_modules`) resolves and runs\n * correctly once the dependency is declared, mirroring exactly how\n * `python-function-app` already relies on Azure's Python Oryx build\n * installing `requirements.txt` at deploy time.\n *\n * @param nodeFunctionAppRoot - Absolute path to the function app's directory.\n * @param workspaceRoot - Absolute path to the workspace (to read the\n * installed `@azure/functions` version).\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction repairNodeFunctionAppManifest (nodeFunctionAppRoot: string, workspaceRoot: string): void {\n const manifestPath = join(nodeFunctionAppRoot, 'package.json')\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n const azureFunctionsVersion = readJson<{ version: string }>(\n join(workspaceRoot, 'node_modules/@azure/functions/package.json'),\n ).version\n const dependencies = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n main: 'dist/main.js',\n dependencies: { ...dependencies, '@azure/functions': `^${azureFunctionsVersion}` },\n }),\n )\n}\n\n/**\n * The `package` target for a Node Azure Function: zip the deployable folder.\n *\n * @remarks\n * Zips `apps/<name>/dist` (the esbuild output — the `main.js` shim plus the\n * mirrored source tree) **nested under `dist/`**, alongside `host.json` and\n * the repaired `package.json`, into `dist/drop/node-function-app-<name>.zip`\n * — the exact same relative layout as the source directory, so the deployed\n * `package.json`'s `main: 'dist/main.js'` resolves identically whether run\n * locally (`func start` in `apps/<name>`) or from the unzipped deploy\n * artifact (see {@link repairNodeFunctionAppManifest}). No `node_modules`\n * bundled: Azure's Oryx build installs real dependencies from the zipped\n * `package.json` at deploy time. Basename exactly `node-function-app-<name>`\n * (the CI build tag).\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/node-function-app-${name}.zip`\n const root = `apps/${name}`\n // The third argument to addLocalFolder is a filter; declarations are dropped\n // here rather than at build time. `declaration: false` on the esbuild target\n // is not available: it collides with the `composite`/`declarationMap` the\n // workspace's own `typecheck` needs, and the build then dies on TS5069\n // (measured, both alone and with declarationMap turned off beside it). An app\n // has no consumers for declarations, so the deploy artifact simply omits them.\n const command = String.raw`node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('${root}/dist','dist',(e)=>!/\\.d\\.ts(\\.map)?$/.test(e));z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Replaces Nx's no-op `prune` with one that carries the root `overrides` across.\n *\n * @remarks\n * `@nx/js:prune-lockfile` cuts `dist/package-lock.json` from the root lockfile,\n * so the pruned lockfile already has every `overrides` entry applied — while the\n * `dist/package.json` written beside it has no `overrides` key at all. When an\n * override changes the version of a package **inside the pruned tree**, the two\n * disagree and the container build's `npm ci --omit=dev` refuses:\n * it reports that the manifest and the lock file are not in sync, naming the\n * package whose version the override moved as missing from the lock file.\n *\n * Conditional, not universal: an override that misses the app's runtime tree\n * changes nothing, which is why a generated workspace's own SECURITY_OVERRIDES\n * (dev tooling — brace-expansion, smol-toml, nanoid) do not trigger it and a\n * plain `npm ci` in `dist` succeeds. It bites the moment a workspace pins\n * something the app actually ships.\n *\n * Copying every root entry rather than only the intersecting ones is deliberate:\n * npm accepts `overrides` naming packages absent from the tree, and computing\n * the intersection here would need the resolved tree this step does not have.\n *\n * Nx's own `prune` is `nx:noop` over `prune-lockfile` + `copy-workspace-modules`,\n * so the same `dependsOn` is kept and only the body changes.\n *\n * @param name - The app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppPruneTarget (name: string): Record<string, unknown> {\n const pruned = `apps/${name}/dist/package.json`\n const command = String.raw`node -e \"const fs=require('node:fs');const root=JSON.parse(fs.readFileSync('package.json','utf8'));const o=root.overrides;if(!o||Object.keys(o).length===0){console.log('No root overrides - nothing to carry into the pruned manifest.');process.exit(0)}const p='${pruned}';if(!fs.existsSync(p)){console.error('Pruned manifest not found at '+p+' - run the prune-lockfile target first.');process.exit(1)}const m=JSON.parse(fs.readFileSync(p,'utf8'));m.overrides={...o,...m.overrides};fs.writeFileSync(p,JSON.stringify(m,null,2)+'\\n');console.log('Carried '+Object.keys(o).length+' root override(s) into '+p)\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['prune-lockfile', 'copy-workspace-modules'],\n options: { command },\n }\n}\n\n/**\n * The `start` target for a Node Azure Function: `func start`, locally.\n *\n * @remarks\n * Depends on `build` so `dist/main.js` exists before Core Tools tries to load\n * it via the manifest's `main` field ({@link repairNodeFunctionAppManifest}).\n * `continuous: true` marks it as a long-running dev-server task, the same\n * shape `@nx/js:node`'s own inferred `serve` target uses — Nx does not try to\n * cache or wait out a process that never exits on its own. Requires Azure\n * Functions Core Tools locally (never a prerequisite for\n * `add node-function-app` itself — see `ensureAzureFunctionsPackage`'s docs).\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nodeFunctionAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n continuous: true,\n options: { command: 'func start', cwd: `apps/${name}` },\n }\n}\n\n/**\n * Adds a Node Azure Function: `@nx/node:application` plus the Azure Functions v4 shape.\n *\n * @remarks\n * Same generator as `node-app` — the difference is purely the overlay:\n * `@azure/functions` installed for real, an HTTP-trigger sample\n * ({@link NODE_FUNCTION_APP_HELLO}) + a pure, test-covered helper, `host.json`,\n * and the manifest repair the deploy needs. The `func` CLI is never invoked —\n * unlike the removed `@nxazure/func` plugin, nothing here shells out to it,\n * so it isn't a prerequisite for `add node-function-app` (only for local\n * `func start`, same as `python-function-app`). Always `--framework=none` —\n * unlike `node-app`, no framework choice: the Azure Functions v4 programming\n * model registers its own routes and runs its own request lifecycle\n * ({@link NODE_FUNCTION_APP_HELLO}'s `app.http(...)`), so a full HTTP server\n * framework (Express/Fastify/Koa/Nest) doesn't apply here.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addNodeFunctionApp (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n): void {\n runNodeApp(workspaceRoot, name, stack, 'none')\n ensureAzureFunctionsPackage(workspaceRoot)\n const nodeFunctionAppRoot = join(workspaceRoot, 'apps', name)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/main.ts'), NODE_FUNCTION_APP_MAIN)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/functions/hello.ts'), NODE_FUNCTION_APP_HELLO)\n writeFileEnsured(\n join(nodeFunctionAppRoot, 'src/functions/greeting.ts'),\n NODE_FUNCTION_APP_GREETING,\n )\n writeFileEnsured(\n join(nodeFunctionAppRoot, 'src/functions/greeting.spec.ts'),\n NODE_FUNCTION_APP_GREETING_SPEC,\n )\n writeFileEnsured(join(nodeFunctionAppRoot, 'host.json'), NODE_FUNCTION_APP_HOST_JSON)\n repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(nodeFunctionAppRoot, 'package.json'), {\n package: nodeFunctionAppPackageTarget(name),\n prune: nodeFunctionAppPruneTarget(name),\n start: nodeFunctionAppStartTarget(name),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n","import { join } from 'node:path'\nimport { promptText } from '../terminal'\nimport { repairDeclarationSpecifiers, repairPublishableManifest } from '../rollup-library'\nimport { removeLocalRegistryScaffolding } from '../workspace-overlay'\nimport {\n defaultScope,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n renameScaffoldPlaceholder,\n runGeneratorAndRepair,\n writeProjectReadme,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Adds a publishable npm library: `@nx/js:lib` as a rollup bundle.\n *\n * @remarks\n * rollup (not tsc): a bundler is what lets a published package depend on\n * private internal libs. `@nx/rollup`'s `withNx` externalizes exactly the\n * manifest's `dependencies`/`peerDependencies` — so imported internal libs\n * (never declared in the manifest, npm workspaces links them regardless) are\n * compiled INTO the bundle from source, and the private name never reaches\n * the published `package.json`.\n *\n * The scope is resolved here: an explicit `--scope` wins; otherwise the flag\n * path (`kindProvided`) defaults it silently, while the interactive/bare path\n * prompts for it (with the workspace's own scope as the default).\n *\n * Routed through {@link runGeneratorAndRepair} rather than calling `runNx`\n * directly: this generator's `@nx/rollup`/vitest plugin install can fail for\n * reasons unrelated to the generator itself (reproduced with a real npm 10\n * arborist bug on this exact dependency tree) after every file below has\n * already been written, and without the wrapper every repair — the `types`\n * fix, the declaration/source-map fixes, `registerProjectCommands` — would\n * silently never run, leaving a broken, half-configured project that\n * `mnci doctor` could not fully catch.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @param stack - The workspace's chosen test runner.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its install step\n * failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addNpmLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n stack: WorkspaceStack,\n): Promise<void> {\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n const projectRoot = join(workspaceRoot, 'packages', name)\n const manifestPath = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/js:lib',\n `packages/${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n '--bundler=rollup',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n manifestPath,\n () => {\n markPublic(manifestPath)\n // @nx/js:lib --bundler=rollup writes types: './dist/index.esm.d.ts', a file its\n // own build never emits, so every TypeScript consumer of the published package\n // would get `any`. See repairPublishableManifest.\n repairPublishableManifest(manifestPath)\n // The build writes dist/index.d.ts as a stub whose specifier @nx/rollup builds\n // with path.relative(), so on Windows it is not a valid module specifier at all.\n repairDeclarationSpecifiers(projectRoot)\n // The @nx/dependency-checks exclusions this kind needs now live in the ROOT\n // config (@mnci/eslint-config's dependencyChecks block), so the generator's\n // per-project config is deleted rather than overwritten.\n // Nx writes a README crediting itself; mnci generated this project.\n writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner)\n // @nx/js:lib's `src/lib/<name>.ts` has no role suffix, so mnci's own\n // vertical-slices/file-role rule rejects it on the first lint.\n renameScaffoldPlaceholder(projectRoot, name)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n // `--publishable` also scaffolds a whole local-registry story (verdaccio\n // config, devDependency, root target) that mnci's tag-only release model\n // has no use for.\n removeLocalRegistryScaffolding(workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n","/**\n * Repoints a generated publishable library's manifest at the declaration file its\n * build actually emits, and keeps declaration maps out of the tarball.\n *\n * @remarks\n * Applies to both `npm-lib` and `react-lib`, at generation time and again from\n * `mnci upgrade` for libraries generated before the repair existed.\n */\n\nimport { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { readJson, toJson, writeFileEnsured } from '../file-system'\n\n/**\n * The declaration path the rollup-bundled library generators write, which their\n * own build never produces.\n */\nconst WRONG_TYPES_PATH = './dist/index.esm.d.ts'\n\n/**\n * The real declaration file, pointed at directly rather than through the stub.\n *\n * @remarks\n * `@nx/rollup`'s `dts-bundle` plugin emits declarations at `dist/src/index.d.ts` and\n * then writes a stub `dist/index.d.ts` that re-exports from them. The obvious target\n * is that stub, and it is the wrong one: the plugin builds the specifier with\n * `path.relative()`, which returns an OS-NATIVE path, so on a Windows agent the stub\n * reads\n *\n * ```\n * export * from \"./src\\\\\\\\index\";\n * ```\n *\n * A module specifier is URL-style, not a filesystem path - `/` is correct on every\n * platform and `\\\\` is correct on none. It resolves on Windows only because the\n * resolver normalises separators there; on Linux and macOS a backslash is an ordinary\n * filename character, so the package is untyped for those consumers. Confirmed in a\n * real published tarball built on a Windows CI pool.\n *\n * Both library kinds share the layout, so this path is right for both:\n * `@nx/rollup`'s configuration generator writes `main: './src/index.ts'` for every\n * project it configures, and `@nx/js:lib` and `@nx/react:library` both route through\n * it.\n *\n * The stub still ships and is simply unused. Upstream fix tracked in ROADMAP 7c.\n */\nconst ACTUAL_TYPES_PATH = './dist/src/index.d.ts'\n\n/**\n * Keeps declaration source maps out of the published tarball.\n *\n * @remarks\n * `declarationMap` is on workspace-wide (`create-nx-workspace` writes it into\n * `tsconfig.base.json`) and is genuinely useful INSIDE the monorepo, where\n * go-to-definition across projects lands on the original `.ts`. It is dead weight\n * in a published package, because `files: [\"dist\"]` ships no sources for the maps\n * to point at: measured on a real published library, every `.d.ts.map` carried\n * `sources: [\"../src/<name>.ts\"]`, a path outside the tarball. They were also HALF\n * the package - 32 of its 67 files - so an editor following one lands on nothing\n * while every consumer downloads them.\n *\n * Excluded from `files` rather than by switching `declarationMap` off, so in-repo\n * navigation keeps working.\n */\nconst DECLARATION_MAP_EXCLUSION = '!**/*.d.ts.map'\n\n/**\n * Keeps JavaScript source maps out of the published tarball.\n *\n * @remarks\n * The counterweight to building them unconditionally. A `.js.map` carries the\n * whole of `sourcesContent` - every line of the package's TypeScript - so\n * publishing them would multiply the tarball for a benefit only this\n * workspace's own debugger collects. Same reasoning as\n * {@link DECLARATION_MAP_EXCLUSION}.\n */\nconst SOURCE_MAP_EXCLUSION = '!**/*.js.map'\n\n/**\n * Repairs the manifest of a generated publishable library: repoints its `types`\n * entries at the declaration file the build actually emits, and keeps declaration\n * maps out of the tarball.\n *\n * @remarks\n * Works around a real inconsistency in the `--bundler=rollup` library generators,\n * not a preference: they write `types: './dist/index.esm.d.ts'` (and the same path\n * under `exports['.']`), while the rollup build emits `dist/index.d.ts`. The\n * referenced file therefore never exists, so **every TypeScript consumer of the\n * published package gets `any`**, failing with\n * `TS7016: Could not find a declaration file for module '@scope/name'`.\n *\n * Applies to BOTH `@nx/js:lib` (`npm-lib`) and `@nx/react:library` (`react-lib`).\n * It lived in `reactLib.ts` alone for a while, which is how `npm-lib` shipped the\n * defect: the bug was found, diagnosed and fixed on one code path while the other\n * called the same generator family with the same flag. Measured on a real published\n * workspace — a consumer importing `@auto/env` failed `TS7016`, and repointing\n * `types` alone made it resolve, with a deliberate type error then reported\n * correctly.\n *\n * `main`/`module` are left alone: they correctly point at `index.esm.js`, which IS\n * emitted. Only the declaration paths are wrong.\n *\n * Guarded on the exact wrong value, so if Nx corrects this upstream the repair\n * quietly stops applying instead of overwriting a now-correct path.\n *\n * @param manifestPath - Absolute path to the library's `package.json`.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error reading or writing the manifest.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairPublishableManifest (manifestPath: string): void {\n const manifest = readJson<{\n types?: string\n files?: string[]\n exports?: Record<string, string | { types?: string }>\n }>(manifestPath)\n\n if (manifest.types === WRONG_TYPES_PATH) {\n manifest.types = ACTUAL_TYPES_PATH\n }\n const dot = manifest.exports?.['.']\n if (typeof dot === 'object' && dot.types === WRONG_TYPES_PATH) {\n dot.types = ACTUAL_TYPES_PATH\n }\n for (const exclusion of [DECLARATION_MAP_EXCLUSION, SOURCE_MAP_EXCLUSION]) {\n if (manifest.files && !manifest.files.includes(exclusion)) {\n manifest.files.push(exclusion)\n }\n }\n writeFileEnsured(manifestPath, toJson(manifest))\n}\n\n/**\n * Sweeps every project's manifest through {@link repairPublishableManifest}.\n *\n * @remarks\n * Called by `mnci upgrade`. `repairPublishableManifest` itself only runs at\n * `add` time (from `addNpmLib`/`addReactLib`/`addReactInternalLib`), so a\n * project `add`ed before this repair existed — or one whose manifest was\n * later hand-edited back to the wrong `types` path — never gets revisited.\n * `repairPublishableManifest` is already unconditional and idempotent (it\n * only changes a field that is actually wrong, and only adds a `files`\n * exclusion that is actually missing), so it is safe to call on every\n * project's manifest regardless of kind: a manifest with none of the wrong\n * values is read and re-serialised unchanged.\n *\n * Scoped to `packages/*` and `libs/*` for the same reason\n * {@link repairRollupSourceMaps} is: those are the only places a JS/TS\n * project's `package.json` lives.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairPublishableManifests (workspaceRoot: string): string[] {\n const changed: string[] = []\n const manifests = globSync(['packages/*/package.json', 'libs/*/package.json'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of manifests) {\n const manifestPath = join(workspaceRoot, relativePath)\n const before = readFileSync(manifestPath, 'utf8')\n repairPublishableManifest(manifestPath)\n const after = readFileSync(manifestPath, 'utf8')\n if (after !== before) {\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n","/**\n * Rewrites the rollup configs of already-generated libraries so they emit usable\n * source maps and declaration specifiers.\n *\n * @remarks\n * The same repairs `mnci add` applies at generation time, applied after the\n * fact: `mnci upgrade` runs them across an existing workspace, and a freshly\n * generated project gets the declaration fix on the spot.\n */\n\nimport { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport {\n ROLLUP_CONFIG_PLACEHOLDER,\n ROLLUP_CONFIG_WITH_DTS_FIX,\n withRollupSourceMaps,\n withUpgradedDeclarationSpecifierPlugin,\n} from './rollup-config.algorithm'\n\n/**\n * Sweeps every publishable project's rollup config, enabling source maps.\n *\n * @remarks\n * Called by `mnci upgrade`, so a workspace generated before this shipped stops\n * being undebuggable without anyone editing a config by hand. Scoped to\n * `packages/*` and `libs/*` because those are the only places mnci puts a\n * rollup-built project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairRollupSourceMaps (workspaceRoot: string): string[] {\n const changed: string[] = []\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of configs) {\n const configPath = join(workspaceRoot, relativePath)\n const before = readFileSync(configPath, 'utf8')\n const after = withRollupSourceMaps(before)\n if (after !== before) {\n writeFileEnsured(configPath, after)\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n\n/**\n * Sweeps every publishable project's rollup config, upgrading an existing\n * declaration-specifier plugin to the current version.\n *\n * @remarks\n * Called by `mnci upgrade`, the same way {@link repairRollupSourceMaps} is —\n * scoped to `packages/*` and `libs/*` for the same reason: those are the\n * only places mnci puts a rollup-built project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function upgradeDeclarationSpecifierPlugins (workspaceRoot: string): string[] {\n const changed: string[] = []\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n })\n\n for (const relativePath of configs) {\n const configPath = join(workspaceRoot, relativePath)\n const before = readFileSync(configPath, 'utf8')\n const after = withUpgradedDeclarationSpecifierPlugin(before)\n if (after !== before) {\n writeFileEnsured(configPath, after)\n changed.push(relativePath.replaceAll('\\\\', '/'))\n }\n }\n\n return changed\n}\n\n/**\n * Adds a rollup plugin that repairs the declaration stub the build emits.\n *\n * @remarks\n * `@nx/rollup` writes `dist/index.d.ts` as a stub re-exporting the real\n * declarations, building the specifier with `path.relative()` — an OS-native path.\n * On a Windows agent that yields `export * from \"./src\\\\index\"`, which is not a\n * valid module specifier on any platform: a specifier is URL-style, so `/` is right\n * everywhere and a backslash nowhere. It resolves on Windows only because the\n * resolver normalises separators there, so a package built there is untyped on Linux\n * and macOS. Confirmed in a real published tarball.\n *\n * mnci already points `types` past the stub ({@link repairPublishableManifest}), so\n * nothing depends on it — this makes the emitted artifact correct rather than merely\n * bypassed. The two repairs are deliberately independent: the manifest one keeps\n * consumers working even if this config is later hand-edited.\n *\n * The same plugin also appends `.js` to every bare relative specifier across\n * `dist/**\\/*.d.ts` — not just the stub. `tsconfig.lib.json` declares under\n * `moduleResolution: \"bundler\"`, where an extensionless relative import is\n * valid, so the REAL declarations (what `types` points at) carry the same bare\n * specifiers as the source. A consumer resolving under the workspace default,\n * `nodenext`, requires an explicit extension on every relative specifier;\n * without it `tsc` reports `TS2834`, and with `skipLibCheck` masking that, the\n * entry point's re-export fails to resolve at all and the package appears to\n * export nothing. Confirmed against a real packed tarball: zero exports\n * visible under `nodenext` before this, correct resolution after.\n *\n * Guarded on the exact placeholder the generators write, so a change to their\n * template makes this a no-op rather than corrupting the config.\n *\n * @param projectRoot - Absolute path to the generated project's directory.\n * @returns Nothing.\n * @throws Propagates any `fs` error raised while rewriting the config.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function repairDeclarationSpecifiers (projectRoot: string): void {\n const configPath = join(projectRoot, 'rollup.config.cjs')\n if (!fileExists(configPath)) {\n return\n }\n const config = readFileSync(configPath, 'utf8')\n if (!config.includes(ROLLUP_CONFIG_PLACEHOLDER)) {\n return\n }\n // Declaration stub FIRST, source maps second, and the order is load-bearing:\n // `withRollupSourceMaps` anchors on the `},` / `{` boundary between withNx's\n // two arguments and rewrites the `{` line, which is the same line the\n // placeholder starts with. Run the other way round it silently matches\n // nothing and the dts plugin is never written.\n const withDtsFix = config.split(ROLLUP_CONFIG_PLACEHOLDER).join(ROLLUP_CONFIG_WITH_DTS_FIX)\n writeFileEnsured(configPath, withRollupSourceMaps(withDtsFix))\n}\n","/**\n * The rollup config text a generated library carries: what it looks like when it\n * is correct, and what to change when it is not.\n *\n * @remarks\n * Every transform here is pure and deterministic - it takes config text and\n * returns config text, and touches no filesystem. The reads and writes live in\n * the sibling repository and use-case files.\n */\n\n/**\n * The empty second-argument slot `@nx/js:lib --bundler=rollup` writes verbatim.\n *\n * @remarks\n * Matched exactly, as the generator emits it, and used as the anchor every\n * declaration repair splices into. A config that no longer carries it has\n * either been repaired already or been edited by hand, and in both cases the\n * repair must leave it alone rather than guess at a new insertion point.\n */\nexport const ROLLUP_CONFIG_PLACEHOLDER = [\n ' {',\n ' // Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options',\n ' // e.g.',\n ' // output: { sourcemap: true },',\n ' }',\n].join('\\n')\n\n/**\n * The compiler `@nx/js:lib --bundler=rollup` hardcodes, and what mnci swaps it for.\n *\n * @remarks\n * **Without this swap the source maps are emitted but empty, so none of the\n * rest of this works.** `@nx/rollup`'s own configuration generator defaults\n * `compiler` to `babel`; `@nx/js:lib` passes `compiler: 'swc'` explicitly and\n * unconditionally, so every publishable library mnci generates is built with\n * swc. And `@nx/rollup`'s swc plugin calls swc's `transform()` **without**\n * `sourceMaps: true`, so it returns no map at all. A rollup transform hook that\n * returns no map breaks the chain: the output map comes out structurally valid\n * and semantically empty - `sources: []`, every mapping segment blank - which\n * is indistinguishable from a working build until a breakpoint refuses to bind.\n *\n * Measured on a real package in this repo: swc gave `sources: []`; the same\n * package on babel gave 9 sources, all resolving, with `sourcesContent`.\n *\n * Swapping the compiler is the fix rather than shipping a plugin that re-runs\n * swc with maps on, because two transform hooks would both compile the same\n * source and the second would see the first's output. Revert this the moment\n * `@nx/rollup` passes `sourceMaps` through - the upstream fix is one option in\n * `plugins/swc.js`.\n *\n * A regex, not a plain string, for the same reason {@link hasRollupSourceMaps}\n * is one: `@stylistic/key-spacing` (aligned on value) pads every property in\n * this object out to the longest key's column the moment `eslint --fix` runs\n * over it - which every mnci-generated file gets, and `outputPath`/`tsConfig`\n * both outrun `compiler`, so the padding fires on effectively every real\n * config. A literal `\" compiler: 'swc',\"` only matches the pristine,\n * never-formatted generator output; on a workspace where the repair is\n * reached late (a failed `add` finished by a later `mnci upgrade`, or an\n * older mnci version's already-formatted output) the line reads\n * `compiler: 'swc',` and the literal silently fails to match, leaving swc's\n * empty source maps in place while `mnci upgrade` reports success. Confirmed\n * end to end: a real crashed `add npm-lib` left `rollup.config.cjs`\n * unrepaired, one `npm run format` key-spacing-aligned it, and the literal\n * swap then no-op'd on the following `mnci upgrade`.\n */\nconst GENERATED_COMPILER_PATTERN = /^( {4})compiler\\s*:\\s*'swc',$/m\n\n/**\n * Builds the source-map-capable compiler line (plus its explaining comment)\n * at the given indent, matched from {@link GENERATED_COMPILER_PATTERN}'s\n * capture so the replacement lines up whatever the original indentation was.\n */\nfunction sourceMapCapableCompiler (indent: string): string {\n return [\n `${indent}// Swapped from swc by MoNecromanCI. @nx/rollup runs swc without`,\n `${indent}// sourceMaps, so it returns no map and the bundle's map comes out empty -`,\n `${indent}// valid-looking, and useless for debugging. See ROADMAP.`,\n `${indent}compiler: 'babel',`,\n ].join('\\n')\n}\n\n/**\n * The end of `withNx`'s FIRST argument, with source maps switched on.\n *\n * @remarks\n * `sourceMap` has to be set here and nowhere else. The obvious spot is\n * `output: { sourcemap: true }` in the second argument - the generator's own\n * placeholder comment even suggests it - and it silently does nothing:\n * `withNx` spreads the caller's `output` and *then* assigns\n * `sourcemap: options.sourceMap`, so its own (undefined) value always wins.\n *\n * Unconditional rather than gated behind a dev flag. Maps are what make a\n * breakpoint in a `.ts` file bind, so a build without them is undebuggable, and\n * every way of gating it costs something a generated workspace should not pay:\n * an env var is not portable across npm scripts without a fourth runtime\n * dependency, and a second build target is one more thing to remember at\n * exactly the moment you are already debugging. The maps are always built and\n * never *published* instead - {@link repairPublishableManifest} keeps them out\n * of `files`, the same trade this project already made for `.d.ts.map`.\n */\nconst ROLLUP_ARG_ONE_BOUNDARY = [' },', ' {', ''].join('\\n')\n\n/** The same boundary, with the source-map flag appended to argument one. */\nconst ROLLUP_ARG_ONE_WITH_SOURCE_MAPS = [\n ' // Added by MoNecromanCI: without this rollup emits no .js.map at all, so',\n ' // a breakpoint in a .ts file can never bind. Not published - see `files`.',\n ' sourceMap: true',\n ' },',\n ' {',\n '',\n].join('\\n')\n\n/**\n * Whether a rollup config already carries the source-map wiring.\n *\n * @remarks\n * The idempotence guard for both entry points, and what `mnci doctor` asks so\n * its finding and `mnci upgrade`'s edit can never disagree about what \"already\n * fixed\" means.\n *\n * A literal `config.includes('sourceMap: true')` broke on both halves of that\n * contract at once: `@mnci/eslint-config`'s `@stylistic/key-spacing` (aligned\n * on value) is entitled to rewrite `sourceMap: true,` to\n * `sourceMap: true,` to line up with whatever the object's\n * longest key is, and `eslint --fix` runs on every mnci-generated file. Tested\n * with a regex rather than a plain substring so any amount of horizontal\n * whitespace around the colon - however a formatter chooses to lay it out -\n * still reads as \"already on\".\n *\n * @param config - The config file's text.\n * @returns `true` when source maps are already switched on.\n * @throws Never - performs a regex test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasRollupSourceMaps (config: string): boolean {\n return /sourceMap\\s*:\\s*true\\b/.test(config)\n}\n\n/**\n * Whether `withRollupSourceMaps` can repair this config.\n *\n * @remarks\n * The write-time twin of {@link hasRollupSourceMaps}'s idempotence test:\n * `withRollupSourceMaps` anchors its edit on the `},` / `{` boundary between\n * `withNx`'s two arguments, so a config that never has that boundary in its\n * OWN text cannot be repaired no matter how many times `mnci upgrade` runs -\n * most commonly a one-line delegation to a shared base file, e.g.\n * `module.exports = require('../../rollup.base.cjs')()`, which mnci does not\n * own and has nothing to anchor on. `mnci doctor` uses this to decide whether\n * recommending `mnci upgrade` would actually fix anything, rather than\n * pointing the user at a command that silently no-ops.\n *\n * @param config - The config file's own text (not resolved through `require()`).\n * @returns `true` when `withRollupSourceMaps` has a boundary to anchor on.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function canRepairRollupConfig (config: string): boolean {\n return config.includes(ROLLUP_ARG_ONE_BOUNDARY)\n}\n\n/**\n * Switches source maps on in a rollup config, whatever shape it is in.\n *\n * @remarks\n * Deliberately not anchored on the generator's placeholder comment. That\n * comment survives only until mnci replaces it at `add` time, so a placeholder\n * anchor would work for a brand-new project and silently no-op for every\n * existing one - which is the whole population `mnci upgrade` exists to reach.\n * The `},` / `{` boundary between `withNx`'s two arguments is present in both\n * shapes, so it anchors both.\n *\n * Idempotent: a config that already has the flag is returned untouched, so\n * running `mnci upgrade` twice changes nothing the second time. That guard is\n * {@link hasRollupSourceMaps} rather than a second literal, deliberately -\n * two independent \"is it already on\" checks are two things that can disagree,\n * and disagreeing here means inserting a second `sourceMap: true` into a\n * config `eslint --fix` had only reformatted, not left off.\n *\n * @param config - The config file's text.\n * @returns The config with source maps enabled, or unchanged when already so.\n * @throws Never - an unrecognised config is returned unchanged.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withRollupSourceMaps (config: string): string {\n if (hasRollupSourceMaps(config) || !canRepairRollupConfig(config)) {\n return config\n }\n const withCompiler = config.replace(GENERATED_COMPILER_PATTERN, (_match, indent: string) =>\n sourceMapCapableCompiler(indent),\n )\n const withFlag = withCompiler.replace(\n ROLLUP_ARG_ONE_BOUNDARY,\n () => ROLLUP_ARG_ONE_WITH_SOURCE_MAPS,\n )\n\n return withFlag.includes('sourcemapPathTransform')\n ? withFlag\n : withFlag.replace(\n ROLLUP_ARG_ONE_WITH_SOURCE_MAPS,\n () => `${ROLLUP_ARG_ONE_WITH_SOURCE_MAPS}${SOURCEMAP_PATH_TRANSFORM}`,\n )\n}\n\n/** The `output` block that repairs rollup's wrong sourcemap source paths. */\nconst SOURCEMAP_PATH_TRANSFORM = [\n ' // Added by MoNecromanCI. rollup hands sourcemapPathTransform an OS-NATIVE',\n ' // path with one parent segment too many, so `sources` resolve to nothing',\n ' // and no breakpoint can bind. Separators are normalised too: a sources',\n ' // entry is URL-style, so a backslash is wrong on every platform.',\n ' output: {',\n ' sourcemapPathTransform: relativeSourcePath =>',\n ' relativeSourcePath',\n \" .replaceAll(String.fromCodePoint(92), '/')\",\n \" .replace(/^([.][.][/])+/, '../')\",\n ' },',\n '',\n].join('\\n')\n\n/**\n * A unique substring of {@link DECLARATION_SPECIFIER_PLUGIN}'s `name`,\n * present whenever the plugin exists in a rollup config at all — including a\n * version written before the `.js`-extension capability below existed.\n */\nconst DECLARATION_SPECIFIER_PLUGIN_MARKER = \"name: 'mnci-normalise-declaration-specifiers'\"\n\n/**\n * A unique identifier {@link DECLARATION_SPECIFIER_PLUGIN} only contains once\n * it also appends `.js` to bare relative specifiers — absent from the\n * earlier version that only normalised the stub's backslashes.\n */\nconst DECLARATION_SPECIFIER_EXTENSION_MARKER = 'bareRelativeSpecifier'\n\n/**\n * A unique identifier {@link DECLARATION_SPECIFIER_PLUGIN} only contains once\n * it resolves a bare specifier's suffix against what rollup actually emitted\n * — absent from the earlier version that appended `.js` unconditionally.\n *\n * @remarks\n * That earlier version could not tell a file specifier from a directory\n * barrel: `./scan-session` needs `/index.js`, not `.js` (a file that was\n * never emitted), and ESM resolution has no directory-index fallback — so an\n * affected import silently degraded the whole module to `any` under\n * `skipLibCheck`, the default in most consumers. Confirmed against a real\n * published tarball.\n */\nconst DECLARATION_SPECIFIER_DIRECTORY_MARKER = 'resolveSpecifierSuffix'\n\n/**\n * Whether a rollup config carries the declaration-specifier plugin at all.\n *\n * @remarks\n * Exported as a predicate rather than exporting the marker string itself, so\n * `mnci doctor` asks this module the question instead of re-implementing the\n * detection and drifting from it — the same contract\n * {@link hasRollupSourceMaps} already has with that caller.\n *\n * @param config - The config file's text, resolved through any shared base.\n * @returns `true` when the plugin is present, current or not.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasDeclarationSpecifierPlugin (config: string): boolean {\n return config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER)\n}\n\n/**\n * Whether that plugin resolves a bare specifier against what was emitted,\n * rather than appending `.js` unconditionally.\n *\n * @remarks\n * The distinction is the whole point of checking: a config carrying the\n * EARLIER plugin looks healthy by every other measure — the plugin is there,\n * the build succeeds, the package publishes — and still ships `any` for every\n * export behind a directory barrel, because `./scan-session` was rewritten to\n * `./scan-session.js`, a file rollup never emitted. Nothing reports it:\n * `skipLibCheck` (the default in most consumers) swallows the unresolved\n * import, and runtime is unaffected because the bundle never goes through\n * those specifiers. Measured in a real consuming workspace, 9 of 11 published\n * packages carried exactly that generation.\n *\n * Separate from {@link hasDeclarationSpecifierPlugin} so a missing plugin and\n * a stale one can be reported as the different findings they are, even though\n * `mnci upgrade` is the remedy for both.\n *\n * @param config - The config file's text, resolved through any shared base.\n * @returns `true` when the plugin is the current, directory-aware version.\n * @throws Never - performs a substring test.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasDirectoryAwareDeclarationSpecifiers (config: string): boolean {\n return config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER)\n}\n\n/**\n * The declaration-specifier plugin object, exactly as written into\n * `plugins: [ … ]`.\n *\n * @remarks\n * Its own text is the source of truth for both call sites that need it:\n * {@link ROLLUP_CONFIG_WITH_DTS_FIX} (a fresh `add`) and\n * {@link withUpgradedDeclarationSpecifierPlugin} (upgrading an existing one\n * in place), so the two can never drift into writing different plugin\n * bodies for the same generator version.\n */\nconst DECLARATION_SPECIFIER_PLUGIN = [\n ' {',\n \" name: 'mnci-normalise-declaration-specifiers',\",\n ' writeBundle (outputOptions) {',\n \" const { existsSync, readdirSync, readFileSync, writeFileSync } = require('node:fs')\",\n \" const { join } = require('node:path')\",\n \" const dir = outputOptions.dir ?? './dist'\",\n \" const stub = join(dir, 'index.d.ts')\",\n ' let source',\n ' try {',\n \" source = readFileSync(stub, 'utf8')\",\n ' } catch {',\n ' return',\n ' }',\n ' // The stub carries a TWO-character escape (JSON.stringify escaped one',\n ' // backslash), so this must not match a single one - that would turn',\n String.raw` // \"./src\\index\" into \"./src//index\". Built from char codes so there is`,\n ' // no escaping in this file to get wrong.',\n ' const separator = String.fromCodePoint(92, 92)',\n \" const normalised = source.replaceAll(separator, '/')\",\n ' if (normalised !== source) writeFileSync(stub, normalised)',\n '',\n String.raw` const ${DECLARATION_SPECIFIER_EXTENSION_MARKER} = /from(\\s+)(['\"])(\\.[^'\"]+?)\\2/g`,\n String.raw` const hasExtension = /\\.(?:mjs|cjs|jsx?|json)$/`,\n ' let entries',\n ' try {',\n ' entries = readdirSync(dir, { recursive: true, withFileTypes: true })',\n ' } catch {',\n ' return',\n ' }',\n ' for (const entry of entries) {',\n \" if (!entry.name.endsWith('.d.ts')) continue\",\n ' const from = entry.parentPath ?? entry.path',\n ' const filePath = join(from, entry.name)',\n ' let declaration',\n ' try {',\n \" declaration = readFileSync(filePath, 'utf8')\",\n ' } catch {',\n ' continue',\n ' }',\n ' // A bare specifier may name a FILE or a DIRECTORY BARREL - resolved',\n ' // against what rollup actually emitted next to this file, never',\n ' // guessed. A directory needs /index.js, not .js (a file that was',\n ' // never written); anything neither form matches is left alone rather',\n ' // than rewritten to a specifier that cannot resolve.',\n ` const ${DECLARATION_SPECIFIER_DIRECTORY_MARKER} = (specifier) => {`,\n ' if (hasExtension.test(specifier)) return null',\n \" if (existsSync(join(from, specifier + '.d.ts'))) return specifier + '.js'\",\n \" if (existsSync(join(from, specifier, 'index.d.ts'))) return specifier + '/index.js'\",\n ' return null',\n ' }',\n ' const withExtensions = declaration.replace(',\n ` ${DECLARATION_SPECIFIER_EXTENSION_MARKER},`,\n ' (match, space, quote, specifier) => {',\n ` const resolved = ${DECLARATION_SPECIFIER_DIRECTORY_MARKER}(specifier)`,\n ' return resolved === null ? match : `from${space}${quote}${resolved}${quote}`',\n ' },',\n ' )',\n ' if (withExtensions !== declaration) writeFileSync(filePath, withExtensions)',\n ' }',\n ' }',\n ' }',\n].join('\\n')\n\n/**\n * The same slot, carrying a plugin that repairs the declaration stub.\n *\n * @remarks\n * What {@link ROLLUP_CONFIG_PLACEHOLDER} is replaced with: the mnci-owned\n * rollup plugin, inline in the generated config rather than imported from a\n * package, so a generated workspace needs nothing published to build.\n */\nexport const ROLLUP_CONFIG_WITH_DTS_FIX = [\n ' {',\n \" // Added by MoNecromanCI. @nx/rollup's dts-bundle plugin writes dist/index.d.ts\",\n ' // as a stub re-exporting the real declarations, and builds that specifier with',\n ' // path.relative() - an OS-NATIVE path. On Windows it emits',\n String.raw` // export * from \"./src\\\\index\";`,\n ' // which is not a valid module specifier on ANY platform: a specifier is',\n ' // URL-style, so / is correct everywhere and a backslash nowhere. It resolves on',\n ' // Windows only because the resolver normalises separators there, leaving the',\n ' // package untyped on Linux and macOS.',\n ' //',\n ' // mnci also points `types` past this stub, so nothing depends on it being',\n ' // correct; this makes the emitted file correct too. Remove once Nx fixes the',\n ' // plugin - its own devkit already exports normalizePath for exactly this.',\n ' //',\n ' // The second half fixes a separate defect in the REAL declarations `types`',\n ' // points at: tsconfig.lib.json declares under moduleResolution \"bundler\",',\n ' // where a bare relative specifier (\"./lib/align\") is valid, so every emitted',\n ' // .d.ts keeps the source\\'s own extensionless imports verbatim. A consumer on',\n ' // \"moduleResolution\": \"nodenext\" - the workspace root default - requires an',\n ' // explicit extension on every relative specifier and gets TS2834 (or,',\n ' // combined with skipLibCheck, a silent zero-export module) instead. Declared',\n ' // extensions are left untouched; only a bare relative specifier gets .js',\n ' // appended, matching what tsc itself emits under node16/nodenext.',\n ' plugins: [',\n DECLARATION_SPECIFIER_PLUGIN,\n ' ]',\n ' }',\n].join('\\n')\n\n/**\n * Finds the `{ … }` span enclosing the first occurrence of `needle`, by\n * brace-counting rather than parsing.\n *\n * @remarks\n * Safe here specifically because {@link DECLARATION_SPECIFIER_PLUGIN} is\n * mnci's own generated content and contains no string or regex literal with\n * an unmatched `{`/`}` — checked by hand, and any future addition to that\n * plugin body must preserve it. `needle` is found first, then the span's\n * start is the nearest `{` before it (by construction, nothing but that\n * brace and whitespace can sit between them in a `{ name: … }` object\n * literal), and the end is wherever forward brace-counting from there first\n * returns to depth zero.\n *\n * @param text - The text to search.\n * @param needle - A substring known to appear inside the object to find.\n * @returns The `[start, end)` character span, `end` exclusive of nothing\n * (it points just past the closing `}`), or `undefined` when `needle` is\n * absent, has no preceding `{`, or the braces never balance.\n * @throws Never.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findEnclosingBraceSpan (text: string, needle: string): [number, number] | undefined {\n const needleIndex = text.indexOf(needle)\n if (needleIndex === -1) {\n return undefined\n }\n const start = text.lastIndexOf('{', needleIndex)\n if (start === -1) {\n return undefined\n }\n let depth = 0\n for (let index = start; index < text.length; index += 1) {\n if (text[index] === '{') {\n depth += 1\n } else if (text[index] === '}') {\n depth -= 1\n if (depth === 0) {\n return [start, index + 1]\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Upgrades an already-written declaration-specifier plugin in place, so a\n * project `add`ed before the `.js`-extension capability existed picks it up.\n *\n * @remarks\n * {@link repairDeclarationSpecifiers} writes this plugin exactly once,\n * anchored on the generator's own placeholder — so a project `add`ed before\n * a later capability shipped keeps running the OLD plugin body forever, and\n * `mnci upgrade` never revisited it (unlike the source-map flag, which\n * {@link withRollupSourceMaps} does sweep). This closes that gap.\n *\n * Deliberately NOT a literal full-body text match: the plugin's own body is\n * ordinary JS in a `.cjs` file, so `eslint --fix` is free to reformat it\n * (quotes, semicolons, spacing) between when `add` wrote it and when\n * `upgrade` next runs — matching an exact prior version's text is exactly\n * the class of bug the compiler-swap fix above exists to prevent. Instead\n * this locates the plugin object by brace-counting from its own unique\n * `name` ({@link findEnclosingBraceSpan}) and replaces the WHOLE object with\n * the current version whenever {@link DECLARATION_SPECIFIER_DIRECTORY_MARKER}\n * is missing from it — regardless of what the old body's text actually was.\n * Checking the directory-aware marker rather than\n * {@link DECLARATION_SPECIFIER_EXTENSION_MARKER} is load-bearing: a project\n * `add`ed after the `.js`-extension capability shipped but before the\n * directory-barrel fix already carries the extension marker, so checking\n * that one would leave it stuck silently publishing untyped packages forever.\n *\n * Idempotent: a plugin that already carries the marker is left untouched.\n *\n * @param config - The rollup config's text.\n * @returns The config with the plugin upgraded in place, or unchanged when\n * there is no plugin to upgrade, it is already current, or its span could\n * not be found.\n * @throws Never — an unrecognised shape is returned unchanged.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withUpgradedDeclarationSpecifierPlugin (config: string): string {\n if (\n !config.includes(DECLARATION_SPECIFIER_PLUGIN_MARKER) ||\n config.includes(DECLARATION_SPECIFIER_DIRECTORY_MARKER)\n ) {\n return config\n }\n const span = findEnclosingBraceSpan(config, DECLARATION_SPECIFIER_PLUGIN_MARKER)\n if (!span) {\n return config\n }\n const [start, end] = span\n\n return `${config.slice(0, start)}${DECLARATION_SPECIFIER_PLUGIN}${config.slice(end)}`\n}\n","/**\n * Reads the rollup config a generated library actually builds with, following the\n * `require()` chain into any shared base config it delegates to.\n *\n * @remarks\n * A project's own `rollup.config.cjs` is not always the whole story: the\n * generators emit configs that `require()` a workspace-level base, so a check\n * reading only the project file reports a repair as missing when it is simply\n * one file further out. This is the read side of the slice; the transforms it\n * feeds live in `rollup-config.algorithm.ts`.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileExists } from '../file-system'\n\n/**\n * Resolves a relative `require()` specifier to a real file.\n *\n * @remarks\n * Tries the specifier as given first, then the extensions a `.cjs` rollup\n * config (or a shared base it delegates to) is realistically written with -\n * `require()` itself resolves the same way, this just needs to agree with it\n * without actually loading the module.\n *\n * @param fromDir - The directory the `require()` call is made from.\n * @param specifier - The relative specifier passed to `require()`.\n * @returns The resolved absolute path, or `undefined` when none of the\n * candidates exist.\n * @throws Never - only checks the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveRequireTarget (fromDir: string, specifier: string): string | undefined {\n const base = join(fromDir, specifier)\n\n return [base, `${base}.cjs`, `${base}.js`].find(candidate => fileExists(candidate))\n}\n\n/**\n * Reads a rollup config's text, following local `require()` delegation so a\n * config hoisted into a shared base file is read as if it were inlined.\n *\n * @remarks\n * A workspace that pulls the `withNx(...)` call out into one root\n * `rollup.base.cjs` and leaves each project as\n * `module.exports = require('../../rollup.base.cjs')()` has no\n * `sourceMap: true` text of its own to find - {@link hasRollupSourceMaps}\n * reading only the project's own file would report every such project as\n * missing source maps even when every one genuinely has them, because the\n * flag lives one file away. Text-based rather than an actual `require()` of\n * the config, deliberately: `mnci doctor` is read-only, and evaluating a\n * user's build config as code to answer a yes/no question is a far larger\n * blast radius than reading more of its text.\n *\n * Only RELATIVE requires (`./…`, `../…`) are followed - an npm package's\n * installed source (e.g. `@nx/rollup/with-nx`) is never something this needs\n * to read, and grepping into node_modules would be both slow and pointless.\n * Each file is read at most once, so a require cycle terminates rather than\n * recursing forever.\n *\n * @param configPath - Absolute path to the rollup config being inspected.\n * @param visited - File paths already read, to stop a require cycle. Callers\n * should omit this; it is populated by recursive calls.\n * @returns The config's own text, followed by the text of every local file it\n * `require()`s, transitively.\n * @throws Never - an unreadable file or unresolvable require target is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveRollupConfigText (\n configPath: string,\n visited: Set<string> = new Set(),\n): string {\n if (visited.has(configPath)) {\n return ''\n }\n visited.add(configPath)\n\n let text: string\n try {\n text = readFileSync(configPath, 'utf8')\n } catch {\n return ''\n }\n\n const specifiers = Array.from(\n text.matchAll(/require\\((['\"])(\\.[^'\"]*)\\1\\)/g),\n match => match[2],\n )\n const requiredText = specifiers\n .map(specifier => resolveRequireTarget(dirname(configPath), specifier))\n .filter((target): target is string => target !== undefined)\n .map(target => resolveRollupConfigText(target, visited))\n\n return [text, ...requiredText].join('\\n')\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../nx-workspace'\nimport { PYTHON_GRAPH_PLUGIN, withPythonGraphPlugin } from '../workspace-overlay'\nimport { promptText } from '../terminal'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport {\n addProjectJsonTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n type AddOptions,\n} from './post-generation.use-case'\n\n/**\n * Fails fast, with an install hint, when Python is not on the PATH.\n *\n * @param workspaceRoot - Absolute path to the workspace (cwd for the probe).\n * @returns Nothing.\n * @throws Error when neither `python3` nor `python` can be run.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePython (workspaceRoot: string): void {\n const hasPython =\n runShell('python3', ['--version'], workspaceRoot) === 0 ||\n runShell('python', ['--version'], workspaceRoot) === 0\n if (!hasPython) {\n throw new Error(\n 'Python not found. Install Python 3.9+ first: https://www.python.org/downloads/',\n )\n }\n}\n\n/**\n * The `@mnci/nx-python-pip` package spec to install.\n *\n * @remarks\n * Reads `MNCI2_PYTHON_PIP_SPEC` so the e2e suite can point this at a local\n * tarball (`npm pack`'d from `libs/nx-python-pip` in this same monorepo)\n * instead of the published registry package — the real published spec is\n * the default for every real `mnci add python-*` call.\n */\nfunction pythonPipPluginSpec (): string {\n return process.env.MNCI2_PYTHON_PIP_SPEC ?? '@mnci/nx-python-pip'\n}\n\n/**\n * Ensures the `@mnci/nx-python-pip` Nx plugin is installed AND registered.\n *\n * @remarks\n * The generators and executors need no `nx.json` `plugins` registration —\n * they resolve by plain Node module lookup against the plugin's\n * `generators.json`/`executors.json`, so a plain `npm install` is the whole\n * story for them.\n *\n * Its DEPENDENCY GRAPH does need it. Nx calls `createDependencies` only for a\n * registered plugin, so without the entry a Python project has no edges: `nx\n * affected` cannot know that changing a Python library should retest the\n * projects that consume it, and an affected-scoped verify run passes having\n * checked none of them.\n *\n * Registered HERE rather than only in the overlay because this is where the\n * plugin arrives. `applyOverlay` registers it too, for a workspace that\n * already had Python before this existed, but it runs at `new`/`upgrade` time\n * — which is never during the `mnci add python-*` that first installs the\n * plugin.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensurePythonPipPlugin (workspaceRoot: string): void {\n if (!hasPlugin(workspaceRoot, '@mnci/nx-python-pip')) {\n const spec = pythonPipPluginSpec()\n logger.step(`Installing the Python toolchain plugin (${spec})`)\n if (\n runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0\n ) {\n throw new Error('npm install of @mnci/nx-python-pip failed')\n }\n }\n registerPythonGraphPlugin(workspaceRoot)\n}\n\n/**\n * Registers the Python dependency-graph plugin in `nx.json`.\n *\n * @remarks\n * Idempotent, and written only when it changes something — `mnci add` already\n * rewrites more of `nx.json` than a reviewer wants to read, and a no-op write\n * here would add another line of noise to every Python add after the first.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a missing or unreadable `nx.json` is left alone.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction registerPythonGraphPlugin (workspaceRoot: string): void {\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n if (!fileExists(nxJsonPath)) {\n return\n }\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const patched = withPythonGraphPlugin(nxJson, true)\n if ((patched.plugins as unknown[] | undefined)?.includes(PYTHON_GRAPH_PLUGIN) !== true) {\n return\n }\n if ((nxJson.plugins as unknown[] | undefined)?.includes(PYTHON_GRAPH_PLUGIN) === true) {\n return\n }\n logger.step(`Registering ${PYTHON_GRAPH_PLUGIN} so nx affected sees Python dependencies`)\n writeFileEnsured(nxJsonPath, toJson(patched))\n}\n\n/**\n * The fixed toolchain every Python project shares, written once to the\n * workspace root as `requirements-dev.txt`.\n *\n * @remarks\n * Ruff and pytest are the industry standard; `build` and `twine` are the\n * standard PyPA build/publish frontends that `@mnci/nx-python-pip`'s\n * executors shell out to; `pip-audit` is the PyPA vulnerability scanner the\n * generated CI's audit step (`overlay.ts`'s `PIP_AUDIT_GUARD`) runs against\n * the shared environment. No lock file — plain pip has none, matching the\n * company's standard toolchain (no uv, no Poetry). mnci writes this file\n * (not the plugin): the plugin is a generic Nx plugin with no opinion on how\n * its own runtime dependencies get onto a machine.\n */\nexport const PYTHON_REQUIREMENTS_DEV = `build\ntwine\nruff\nmypy\npytest\npip-audit\n`\n\n/**\n * Idempotently writes `requirements-dev.txt` to the workspace root.\n *\n * @remarks\n * Lazy, like {@link ensureAdmZip}: written on the first Python `add` of any\n * kind, not unconditionally by `mnci new` — a pure-JS/TS workspace never\n * gains this file. Only written when absent, so a user's own edits (extra\n * dev tools) survive repeat `add` calls.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any Node.js `fs` error raised while writing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureRequirementsDev (workspaceRoot: string): void {\n const requirementsDevPath = join(workspaceRoot, 'requirements-dev.txt')\n if (!fileExists(requirementsDevPath)) {\n writeFileEnsured(requirementsDevPath, PYTHON_REQUIREMENTS_DEV)\n }\n}\n\n/**\n * A Python project's module directory name, derived from its Nx project name.\n *\n * @remarks\n * Mirrors `@mnci/nx-python-pip`'s own derivation (Python identifiers cannot\n * contain hyphens) — needed here only to name the module folder inside the\n * function app's source zip.\n *\n * @param name - The project name.\n * @returns The module directory's basename (e.g. `my_svc`).\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonModuleDirectory (name: string): string {\n return name.replaceAll('-', '_')\n}\n\n/**\n * The runnable entry point written into every generated Python app.\n *\n * @remarks\n * `@mnci/nx-python-pip:application` scaffolds a module (`<name>/__init__.py`)\n * shaped like a library — no `if __name__ == '__main__':` block, since the\n * plugin has no opinion on what \"running\" an app means. This file is what\n * gives `mnci add python-app` a real local-dev entry point for\n * {@link pythonAppStartTarget} to run, independent of whatever the sample\n * module's own function happens to be named — so it survives that code being\n * deleted or rewritten.\n *\n * @param name - The project name (used only in the printed message).\n * @returns The `main.py` file contents.\n * @throws Never - pure string formatting.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pythonAppMain (name: string): string {\n return `def main() -> None:\n print('${name} is running')\n\n\nif __name__ == '__main__':\n main()\n`\n}\n\n/**\n * The `start` target for a Python app: `python3 main.py`, locally.\n *\n * @remarks\n * `continuous: true` marks it as a long-running dev task, matching the shape\n * every other kind's custom `start` target uses — Nx does not try to cache or\n * wait out a process that never exits on its own (a Python \"app\" here is\n * whatever `main.py` does; many will exit immediately, but the target shape\n * stays uniform rather than guessing).\n *\n * @param name - The Python app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'python3 main.py', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The `start` target for a Python Azure Function: `func start`, locally.\n *\n * @remarks\n * Unlike the Node function app, this needs no prior `build` — Python function\n * apps deploy as source ({@link pythonFunctionAppPackageTarget}), so\n * `function_app.py` + `host.json` already sit together in the project root\n * exactly as Core Tools expects. Requires Azure Functions Core Tools (and the\n * Python worker) locally — never a prerequisite for\n * `add python-function-app` itself.\n *\n * @param name - The function app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonFunctionAppStartTarget (name: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n continuous: true,\n options: { command: 'func start', cwd: `apps/${name}` },\n }\n}\n\n/**\n * The `package` target for a Python app: zip its built wheel into the drop.\n *\n * @remarks\n * Zips `apps/<name>/dist` (the plugin's `build` executor's wheel + sdist)\n * into `dist/drop/python-app-<name>.zip` — basename exactly\n * `python-app-<name>`, the string CI turns into the per-app build tag.\n * `dependsOn: build` guarantees the wheel exists; same cross-platform\n * `adm-zip` one-liner used throughout `add`.\n *\n * @param name - The Python app's project name.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonAppPackageTarget (name: string): Record<string, unknown> {\n const zip = `dist/drop/python-app-${name}.zip`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFolder('apps/${name}/dist');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * The `package` target for a Python Azure Function: zip the deploy folder.\n *\n * @remarks\n * An Azure Functions Python app is deployed as **source** (the host installs\n * `requirements.txt` and runs `function_app.py`), not as a wheel — so this\n * zips `function_app.py` + `host.json` + `requirements.txt` + the module\n * package (all written by the plugin's `function-application` generator)\n * into `dist/drop/python-function-app-<name>.zip`. Basename exactly\n * `python-function-app-<name>` (the CI build tag). No `build` dependency (the\n * artifact is source), same `adm-zip` one-liner.\n *\n * @param name - The function app's project name.\n * @param moduleDirectory - The app's Python module directory basename.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonFunctionAppPackageTarget (\n name: string,\n moduleDirectory: string,\n): Record<string, unknown> {\n const zip = `dist/drop/python-function-app-${name}.zip`\n const root = `apps/${name}`\n const command = `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');const z=new A();z.addLocalFile('${root}/function_app.py');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/requirements.txt');z.addLocalFolder('${root}/${moduleDirectory}','${moduleDirectory}');z.writeZip('${zip}')\"`\n\n return {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\n }\n}\n\n/**\n * Adds a Python app: `@mnci/nx-python-pip:application` plus mnci's own zip packaging.\n *\n * @remarks\n * Pure delegation to the plugin generator, same shape as every other kind\n * (`react-app`, `node-app`, `npm-lib`) — the plugin owns `pyproject.toml` +\n * `project.json` (lint/test/build); mnci only layers its own CI packaging\n * convention on top, via {@link pythonAppPackageTarget}.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonApp (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n ['g', '@mnci/nx-python-pip:application', name, `--directory=apps/${name}`, '--no-interactive'],\n workspaceRoot,\n )\n writeFileEnsured(join(workspaceRoot, 'apps', name, 'main.py'), pythonAppMain(name))\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n package: pythonAppPackageTarget(name),\n start: pythonAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a Python Azure Function: `@mnci/nx-python-pip:function-application` plus mnci's own zip packaging.\n *\n * @remarks\n * The plugin generator writes the Azure Functions v2 shape (`function_app.py`\n * + `host.json` + `requirements.txt` + a tested pure helper); mnci only adds\n * the `package` target zipping that source into the drop.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonFunctionApp (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n ensureAdmZip(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:function-application',\n name,\n `--directory=apps/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n const moduleDirectory = pythonModuleDirectory(name)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), {\n package: pythonFunctionAppPackageTarget(name, moduleDirectory),\n start: pythonFunctionAppStartTarget(name),\n })\n registerProjectCommands(workspaceRoot, name, { build: false, start: `nx run ${name}:start` })\n}\n\n/**\n * Adds a publishable Python library under `python-packages/`.\n *\n * @remarks\n * `@mnci/nx-python-pip:library` wires the whole publishable shape itself —\n * `nx-release-publish` (twine) and the project-level\n * `release.version.versionActions` override — so no post-generation merge is\n * needed here at all, unlike the app kinds.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonLib (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:library',\n name,\n `--directory=python-packages/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n\n/**\n * Adds a private Python library under `libs/`.\n *\n * @remarks\n * Not publishable — `@mnci/nx-python-pip:internal-library` writes lint/test\n * targets only, no build/publish. Consumers vendor its module directly into\n * their own wheel at build time by hand-adding a `vendor` entry (under\n * `[tool.mnci-python-pip]`) to their own `pyproject.toml`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @returns Nothing.\n * @throws Error when Python is missing, or the generator/install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addPythonInternalLib (workspaceRoot: string, name: string): void {\n ensurePython(workspaceRoot)\n ensurePythonPipPlugin(workspaceRoot)\n ensureRequirementsDev(workspaceRoot)\n\n runNx(\n [\n 'g',\n '@mnci/nx-python-pip:internal-library',\n name,\n `--directory=libs/${name}`,\n '--no-interactive',\n ],\n workspaceRoot,\n )\n registerProjectCommands(workspaceRoot, name, { build: false })\n}\n\n/**\n * The directories a Python project (app, publishable lib, internal lib) can\n * live under — mirrors the directory convention every Python `add*` function\n * above already writes to.\n */\nconst PYTHON_PROJECT_DIRS = ['apps', 'python-packages', 'libs'] as const\n\n/**\n * Finds which of the three Python project directories holds `name`'s\n * `pyproject.toml`.\n *\n * @remarks\n * A Python function app has no `pyproject.toml` at all (source deploy, no\n * wheel — see {@link addPythonFunctionApp}), so it is never found here; that\n * is intentional, not a gap — there is nothing to vendor into.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name to look up.\n * @returns The directory (`apps`, `python-packages`, or `libs`) the project\n * lives under, or `undefined` when no matching `pyproject.toml` exists.\n * @throws Never - pure filesystem probing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction findPythonProjectDirectory (\n workspaceRoot: string,\n name: string,\n): (typeof PYTHON_PROJECT_DIRS)[number] | undefined {\n return PYTHON_PROJECT_DIRS.find(directory =>\n fileExists(join(workspaceRoot, directory, name, 'pyproject.toml')),\n )\n}\n\n/**\n * Extracts the vendored internal-lib project names from a `pyproject.toml`'s\n * `[tool.mnci-python-pip] vendor = [...]` table.\n *\n * @remarks\n * Mirrors `@mnci/nx-python-pip`'s own `parseVendorEntries` (`internal/vendor.ts`,\n * not part of that package's public API) — kept as a small, independent\n * regex here rather than adding a runtime dependency on that package for one\n * function: this CLI already owns the exact shape it writes (via\n * {@link addPythonVendor}), the same reasoning that justifies the plugin's\n * own copy owning the shape it reads.\n *\n * @param pyprojectToml - The `pyproject.toml` file contents.\n * @returns The vendored project names, or `[]` when the project vendors nothing.\n * @throws Never - a missing/malformed table simply yields an empty array.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction parseVendorEntries (pyprojectToml: string): string[] {\n const match = /^\\s*vendor\\s*=\\s*\\[([^\\]]*)\\]/m.exec(pyprojectToml)\n if (!match) {\n return []\n }\n\n return match[1]\n .split(',')\n .map(entry => entry.trim().replaceAll(/^[\"']|[\"']$/g, ''))\n .filter(Boolean)\n}\n\n/**\n * Adds `lib` to a `pyproject.toml`'s `[tool.mnci-python-pip] vendor = [...]`\n * table, creating the table when absent.\n *\n * @remarks\n * Every Python project's `pyproject.toml` this CLI generates (via\n * `@mnci/nx-python-pip`'s `pythonPyprojectToml`) ends with a fixed\n * `[tool.pytest.ini_options]` table — a reliable, always-present anchor to\n * insert the new table right before, matching the exact shape a hand-edit\n * would produce (see `@mnci/nx-python-pip`'s README). Caller ensures `lib`\n * is not already present ({@link parseVendorEntries}) before calling this.\n *\n * @param pyprojectToml - The consumer's current `pyproject.toml` contents.\n * @param lib - The internal-lib project name to add.\n * @returns The patched `pyproject.toml` contents.\n * @throws Never - pure string manipulation; an existing table without the\n * `[tool.pytest.ini_options]` anchor is left with a new table appended at\n * the end instead.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addVendorEntry (pyprojectToml: string, lib: string): string {\n const existing = parseVendorEntries(pyprojectToml)\n if (existing.length > 0) {\n const merged = [...existing, lib].map(name => `\"${name}\"`).join(', ')\n\n return pyprojectToml.replace(\n /^(\\s*vendor\\s*=\\s*)\\[[^\\]]*\\]/m,\n (_match, prefix: string) => `${prefix}[${merged}]`,\n )\n }\n const table = `[tool.mnci-python-pip]\\nvendor = [\"${lib}\"]\\n\\n`\n\n return pyprojectToml.includes('[tool.pytest.ini_options]')\n ? pyprojectToml.replace('[tool.pytest.ini_options]', () => `${table}[tool.pytest.ini_options]`)\n : `${pyprojectToml}\\n${table}`\n}\n\n/**\n * Wires an internal Python library into a consumer's built wheel, by\n * hand-adding a `[tool.mnci-python-pip] vendor = [...]` entry to the\n * consumer's `pyproject.toml`.\n *\n * @remarks\n * Automates the one step `@mnci/nx-python-pip`'s README documents as always\n * hand-added (plain pip has no bundled-local-dependency feature — see that\n * package's \"Internal-lib vendoring\" section): this function does the exact\n * same text edit a developer would make by hand, just validated and\n * idempotent. Nothing is regenerated or reinstalled — the vendored module is\n * only copied into the consumer's wheel at the consumer's next `build` (the\n * plugin's `build` executor resolves `lib`'s root via the real Nx project\n * graph at that point, not anything this function writes).\n *\n * `lib` is resolved the same way `addNpmLib` resolves `--scope`: taken from\n * `options.lib` when given, prompted for on the bare/interactive path\n * (`kindProvided` false), and a clear error otherwise — there is no sensible\n * default library to fall back to silently.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param consumer - The project name to vendor into (an app, publishable lib,\n * or another internal lib — anything with a `pyproject.toml`).\n * @param options - The CLI flags (`--lib`).\n * @param kindProvided - Whether `python-vendor` was passed explicitly (flag\n * path) rather than picked interactively — gates the `--lib` prompt.\n * @returns A promise that resolves once the edit is written.\n * @throws Error when no library was given/prompted, `consumer` has no\n * `pyproject.toml`, the library is not an internal library under `libs/`, or\n * a project is asked to vendor itself.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addPythonVendor (\n workspaceRoot: string,\n consumer: string,\n options: AddOptions,\n kindProvided: boolean,\n): Promise<void> {\n const lib =\n options.lib ??\n (kindProvided\n ? undefined\n : await promptText('Internal Python library to vendor (an existing libs/<name>)'))\n if (!lib) {\n throw new Error(\n '`add python-vendor` needs the library to vendor: pass --lib <name> (an existing libs/<name> internal library).',\n )\n }\n\n const consumerDirectory = findPythonProjectDirectory(workspaceRoot, consumer)\n if (!consumerDirectory) {\n throw new Error(\n `No Python project named '${consumer}' with a pyproject.toml found (checked apps/, python-packages/, libs/). A Python function app has no pyproject.toml — there is nothing to vendor into.`,\n )\n }\n if (!fileExists(join(workspaceRoot, 'libs', lib, 'pyproject.toml'))) {\n throw new Error(\n `No internal Python library named '${lib}' found at libs/${lib}/pyproject.toml. Only an internal library (added via 'mnci add python-internal-lib') can be vendored.`,\n )\n }\n if (consumerDirectory === 'libs' && consumer === lib) {\n throw new Error(`'${consumer}' cannot vendor itself.`)\n }\n\n const pyprojectPath = join(workspaceRoot, consumerDirectory, consumer, 'pyproject.toml')\n const pyprojectToml = readFileSync(pyprojectPath, 'utf8')\n if (parseVendorEntries(pyprojectToml).includes(lib)) {\n logger.info(`'${consumer}' already vendors '${lib}' — nothing to do.`)\n\n return\n }\n\n writeFileEnsured(pyprojectPath, addVendorEntry(pyprojectToml, lib))\n logger.success(\n `'${consumer}' now vendors '${lib}' — its wheel will include '${lib}''s module on the next build.`,\n )\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx } from '../nx-workspace'\nimport { fileExists, writeFileEnsured } from '../file-system'\nimport {\n addNxTargets,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * The deploy environments each React app is built for.\n *\n * @remarks\n * A React SPA bakes its `VITE_*` config in at build time, so one build per\n * environment is needed. These are the three the generator scaffolds.\n */\nexport const REACT_ENVIRONMENTS = ['dev', 'uat', 'prod'] as const\n\n/**\n * The `.env.<environment>` file scaffolded into a React app.\n *\n * @remarks\n * `vite build --mode <environment>` loads this file and compiles its `VITE_`\n * vars into that environment's bundle. Because those values ship to the\n * browser they are inherently public, so committing them (rather than sourcing\n * secrets) is correct — real secrets never belong in a client bundle.\n *\n * @param environment - The environment name (`dev` | `uat` | `prod`).\n * @returns The `.env.<environment>` file contents.\n * @throws Never - pure string build.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactEnvironmentFile (environment: string): string {\n return `# Vite compiles VITE_-prefixed vars into the '${environment}' bundle at build time.\n# They ship to the browser, so keep only PUBLIC config here — never secrets.\nVITE_ENVIRONMENT=${environment}\nVITE_API_URL=https://api.${environment}.example.com\n`\n}\n\n/**\n * The per-environment build + packaging targets for a React app.\n *\n * @remarks\n * One `build-<env>` target per {@link REACT_ENVIRONMENTS} entry runs\n * `vite build --mode <env> --outDir dist-<env>`, so each environment gets its\n * own compiled-in `VITE_*` config. The `package` target depends on all three\n * and zips each `dist-<env>` into `dist/drop/react-app-<name>-<env>.zip` — one\n * artifact per environment. CI needs no change: the \"tag per app\" step derives\n * `##vso[build.addbuildtag]react-app-<name>-<env>` straight from the zip\n * filenames, so a classic release pipeline can deploy each environment from its\n * own artifact + tag. The inference-only `build` (default mode) stays for local\n * `nx build` and the CI verify step — every kind builds to its own Nx-default\n * location, no post-generation output-path redirection.\n *\n * @param name - The React app's project name.\n * @returns The Nx targets to merge into the app manifest's `nx` field.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactAppTargets (name: string): Record<string, unknown> {\n const targets: Record<string, unknown> = {}\n for (const environment of REACT_ENVIRONMENTS) {\n targets[`build-${environment}`] = {\n executor: 'nx:run-commands',\n outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],\n options: {\n command: `vite build --mode ${environment} --outDir dist-${environment}`,\n cwd: `apps/${name}`,\n },\n }\n }\n const zipStatements = REACT_ENVIRONMENTS.map(\n environment =>\n `z=new A();z.addLocalFolder('apps/${name}/dist-${environment}');z.writeZip('dist/drop/react-app-${name}-${environment}.zip')`,\n ).join(';')\n targets.package = {\n executor: 'nx:run-commands',\n dependsOn: REACT_ENVIRONMENTS.map(environment => `build-${environment}`),\n outputs: REACT_ENVIRONMENTS.map(\n environment => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`,\n ),\n options: {\n command: `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}\"`,\n },\n }\n\n return targets\n}\n\n/**\n * Ensures the committed per-environment `.env.<env>` files are not gitignored.\n *\n * @remarks\n * The `.env.<env>` files hold public config and are meant to be versioned, but\n * some presets ignore `.env*`. This appends an idempotent allow-block to the\n * root `.gitignore` so `apps/<app>/.env.<env>` stays tracked regardless (a\n * no-op when nothing ignored them). File-level negation works because the\n * parent `apps/<app>` directory is never ignored.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Propagates any `fs` error reading or writing `.gitignore`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction allowEnvFiles (workspaceRoot: string): void {\n const gitignorePath = join(workspaceRoot, '.gitignore')\n if (!fileExists(gitignorePath)) {\n return\n }\n const marker =\n '# MoNecromanCI: keep the committed per-environment Vite config (public VITE_ values)'\n const current = readFileSync(gitignorePath, 'utf8')\n if (current.includes(marker)) {\n return\n }\n const allow = REACT_ENVIRONMENTS.map(environment => `!apps/*/.env.${environment}`).join('\\n')\n writeFileEnsured(gitignorePath, `${current.replace(/\\n*$/, '\\n')}\\n${marker}\\n${allow}\\n`)\n}\n\n/**\n * Adds a React app: `@nx/react:app` (Vite) plus per-environment build/package targets.\n *\n * @remarks\n * Pure delegation to the generator, then the one thing it doesn't do: a React\n * SPA bakes its `VITE_` config in at build time, so it needs one build per\n * environment. Scaffolds a `.env.<env>` per {@link REACT_ENVIRONMENTS} and\n * attaches the per-env build + packaging targets (inference-only app, so via\n * the manifest's `nx` field). Each env produces its own dist/drop zip + tag.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen linter/test runner.\n * @returns Nothing.\n * @throws Error when the generator or a required install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addReactApp (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n ensurePlugin(workspaceRoot, '@nx/react')\n runNx(\n [\n 'g',\n '@nx/react:app',\n `apps/${name}`,\n '--bundler=vite',\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--style=css',\n '--e2eTestRunner=none',\n '--no-interactive',\n ],\n workspaceRoot,\n )\n ensureAdmZip(workspaceRoot)\n const reactAppRoot = join(workspaceRoot, 'apps', name)\n for (const environment of REACT_ENVIRONMENTS) {\n writeFileEnsured(join(reactAppRoot, `.env.${environment}`), reactEnvironmentFile(environment))\n }\n allowEnvFiles(workspaceRoot)\n addNxTargets(join(reactAppRoot, 'package.json'), reactAppTargets(name))\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n // Vite's inferred 'serve' target (the dev server) is what @nx/react:app\n // already wires — no per-env variant needed for local dev, unlike build.\n registerProjectCommands(workspaceRoot, name, { build: true, start: `nx run ${name}:serve` })\n}\n","import { join } from 'node:path'\nimport { promptText } from '../terminal'\nimport { repairDeclarationSpecifiers, repairPublishableManifest } from '../rollup-library'\nimport { removeLocalRegistryScaffolding } from '../workspace-overlay'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n runGeneratorAndRepair,\n writeProjectReadme,\n type AddOptions,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * The bundler both React library kinds are generated with.\n *\n * @remarks\n * `@nx/react:library` defaults to `none`, and that default is wrong for both\n * kinds here, for two different reasons:\n *\n * - For a **publishable** lib, a bundler is what lets a published package depend\n * on a private internal lib at all: `@nx/rollup`'s `withNx` externalizes\n * exactly the manifest's `dependencies`/`peerDependencies`, so an imported\n * internal lib (never declared, since npm workspaces links it regardless) is\n * compiled INTO the bundle and its private name never reaches the published\n * `package.json`. Same reasoning as `npm-lib`.\n * - For an **internal** lib, it has to be *buildable*: the default\n * `@nx/enforce-module-boundaries` rule forbids a buildable library (every\n * publishable lib) from importing a non-buildable one, so a non-buildable\n * React lib could not be consumed by a `react-lib` or `npm-lib` at all.\n *\n * `rollup` rather than `vite` because these are libraries, not apps — and it\n * keeps both React kinds on the same bundler as `npm-lib`, so there is one\n * externalization story to reason about rather than two. Note `tsc` is not an\n * option here: unlike `@nx/js:lib`, this generator's enum is\n * `none | vite | rollup` (verified against the real 23.1.0 schema).\n */\nconst REACT_LIB_BUNDLER = 'rollup'\n\n/**\n * Adds a publishable React component library under `packages/`.\n *\n * @remarks\n * The React counterpart of `npm-lib`, and the kind that was missing entirely —\n * before this, a shared component library could not be built at all, because\n * `npm-lib` and `internal-lib` both use `@nx/js:lib`, which has no JSX support.\n *\n * Pure delegation to the official `@nx/react:library`, plus the same two\n * post-generation touches `npm-lib` needs: {@link markPublic} (npm treats every\n * scoped package as private, so a first publish 402s without it) and deleting the\n * per-project ESLint config the generator writes.\n *\n * The scope is resolved the same way `npm-lib` resolves it: an explicit\n * `--scope` wins; otherwise the flag path defaults it silently while the\n * interactive path prompts, with the workspace's own scope as the default.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param options - The CLI flags.\n * @param kindProvided - Whether `kind` was passed as a flag (vs. prompted) —\n * gates whether the scope is prompted for or silently defaulted.\n * @param stack - The workspace's chosen test runner.\n * @returns A promise that resolves when the generator has finished.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its plugin install\n * step failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function addReactLib (\n workspaceRoot: string,\n name: string,\n options: AddOptions,\n kindProvided: boolean,\n stack: WorkspaceStack,\n): Promise<void> {\n ensurePlugin(workspaceRoot, '@nx/react')\n const scope =\n options.scope ??\n (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n const projectRoot = join(workspaceRoot, 'packages', name)\n const publishableManifest = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/react:library',\n `packages/${name}`,\n `--name=${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n `--bundler=${REACT_LIB_BUNDLER}`,\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n publishableManifest,\n () => {\n markPublic(publishableManifest)\n repairPublishableManifest(publishableManifest)\n repairDeclarationSpecifiers(projectRoot)\n writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n // `--publishable` also scaffolds a whole local-registry story (verdaccio\n // config, devDependency, root target) that mnci's tag-only release model\n // has no use for.\n removeLocalRegistryScaffolding(workspaceRoot)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n\n/**\n * Adds a private React component library under `libs/`.\n *\n * @remarks\n * Same generator as {@link addReactLib} minus the publishable intent: it lands in\n * `libs/` (never released — the directory *is* the release scoping) and is marked\n * `private` so it is structurally unpublishable no matter what config drifts\n * later. This is the common case for a shared component library consumed by apps\n * in the same monorepo.\n *\n * Still built with {@link REACT_LIB_BUNDLER} rather than left non-buildable —\n * see that constant for why `enforce-module-boundaries` makes that load-bearing.\n *\n * No `start` command: a library has no dev-server story, so\n * {@link registerProjectCommands} gets `build` and the unconditional `qa` only.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's chosen test runner.\n * @returns Nothing.\n * @throws Error when the generator's own scaffolding step fails, or a\n * clearer wrapped error when the scaffold was written but its plugin install\n * step failed anyway — see {@link runGeneratorAndRepair}.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addReactInternalLib (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n): void {\n ensurePlugin(workspaceRoot, '@nx/react')\n const projectRoot = join(workspaceRoot, 'libs', name)\n const privateManifest = join(projectRoot, 'package.json')\n runGeneratorAndRepair(\n workspaceRoot,\n [\n 'g',\n '@nx/react:library',\n `libs/${name}`,\n `--name=${name}`,\n `--bundler=${REACT_LIB_BUNDLER}`,\n `--unitTestRunner=${stack.testRunner}`,\n '--linter=none',\n '--no-interactive',\n ],\n privateManifest,\n () => {\n markPrivate(privateManifest)\n repairPublishableManifest(privateManifest)\n repairDeclarationSpecifiers(projectRoot)\n writeProjectReadme(projectRoot, name, stack.testRunner)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n },\n )\n}\n","import { readFileSync, readdirSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../file-system'\nimport { logger } from '../terminal'\nimport { VSCODE_EXTENSION_TAG } from '../workspace-overlay'\nimport { runNodeApp } from './node.use-case'\nimport {\n addNxTargets,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack,\n} from './post-generation.use-case'\n\n/**\n * Where the packaging and publishing script lives in a generated workspace.\n *\n * @remarks\n * A workspace-relative file rather than a seventh mnci package, the same call as\n * `tools/csharp-version-actions.cjs`: the targets need real logic (a loop over the\n * Marketplace platforms, a staging copy of the sidecar, a publish gate), the\n * workspace has no mnci runtime dependency to put it in, and a one-liner holding\n * all of it would be unreadable. mnci owns it: `mnci add vscode-extension` writes\n * it and `mnci upgrade` rewrites it whenever an extension exists.\n */\nexport const VSCODE_EXTENSION_SCRIPT_PATH = 'tools/vscode-extension.cjs'\n\n/**\n * The Marketplace targets a sidecar build packages, and the Go platform each takes its binary from.\n *\n * @remarks\n * The right-hand side is the directory a `go-app`'s `build-all` writes under\n * `dist/platforms/<app>/` (#226). Alpine reuses the Linux binaries: they are static\n * (`CGO_ENABLED=0`), so musl needs nothing of its own, and without an Alpine\n * package the Marketplace offers an Alpine user nothing at all, since a sidecar\n * build publishes no universal package to fall back on. `linux-armhf` and `web`\n * are left out: there is no Go build for the first, and a browser cannot run a\n * native binary.\n */\nexport const VSCODE_SIDECAR_TARGETS: Readonly<Record<string, string>> = {\n 'win32-x64': 'windows-amd64',\n 'win32-arm64': 'windows-arm64',\n 'linux-x64': 'linux-amd64',\n 'linux-arm64': 'linux-arm64',\n 'alpine-x64': 'linux-amd64',\n 'alpine-arm64': 'linux-arm64',\n 'darwin-x64': 'darwin-amd64',\n 'darwin-arm64': 'darwin-arm64',\n}\n\n/**\n * Renders a string map as an object literal in the house style.\n *\n * @remarks\n * The script is linted by the workspace it is written into (#249), and\n * `mnci upgrade` rewrites it without formatting, so it must be clean as written:\n * `JSON.stringify` would give double quotes and no spacing.\n *\n * @param map - The keys and values to render.\n * @returns A one-line object literal with single-quoted keys and values.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction scriptObjectLiteral (map: Readonly<Record<string, string>>): string {\n return `{ ${Object.entries(map).map(([key, value]) => `'${key}': '${value}'`).join(', ')} }`\n}\n\n/**\n * The `tools/vscode-extension.cjs` script: `package` and `publish` for every extension.\n *\n * @remarks\n * - **No shell, no path into `node_modules`.** `vsce` and `nx` are resolved through\n * their own `package.json` `bin` field and run with the current `node`, so a\n * workspace path with a space in it works on Windows and an upgrade of either\n * tool that moves its entry file changes nothing here.\n * - **`--no-dependencies`** is mandatory in npm workspaces: dependencies are hoisted\n * to the root, so `vsce`'s own `npm list` walk fails, and the build is bundled\n * anyway (`thirdParty: true`), so there is nothing to ship.\n * - **The sidecar is stamped with the extension's version.** `build-all` is run from\n * here with `VERSION` set to the manifest's version rather than declared as a\n * `dependsOn`, which cannot pass a different environment to one dependency. In a\n * release that is the version `nx release` just wrote, so the engine and the\n * extension it ships in always agree.\n * - **`publish` is gated on a credential**, the same pattern as the C# NuGet\n * target: the target must exist even when nothing is configured, because Nx\n * throws when no project in a release group carries `nx-release-publish`.\n * `VSCE_AUTH=entra` (set by the GitHub release step when the `AZURE_CLIENT_ID`\n * variable exists, after `azure/login`) publishes with `--azure-credential`:\n * Microsoft Entra ID through OIDC, no stored secret (#253). It wins over\n * `VSCE_PAT`, a Marketplace personal access token, which remains the fallback.\n * Azure Pipelines leaves an undefined `$(VSCE_PAT)` macro as that literal text,\n * so that reads as unset too. The signal is explicit rather than \"is\n * `AZURE_CLIENT_ID` set\": `azure/login` exports no variable, and exporting\n * `AZURE_CLIENT_ID` would steer `@azure/identity` towards a managed identity the\n * runner does not have.\n * - **A `#!/usr/bin/env node` line**, though it is always run as `node <file>`:\n * it is how `unicorn/no-process-exit` recognises a command-line program, the\n * one place an exit code is the interface.\n * - **A dry run never publishes.** `nx release --dry-run --yes` still runs every\n * `nx-release-publish` target, handing it `--dryRun=true` and `NX_DRY_RUN`\n * (measured); the script prints what it would publish and stops, even with a\n * token present.\n * - **`--skip-duplicate`** makes a re-run of a release that already reached the\n * Marketplace a no-op rather than a failure, the role `--skip-existing` plays for\n * twine.\n */\nexport const VSCODE_EXTENSION_SCRIPT = String.raw`#!/usr/bin/env node\n// Written by mnci: 'mnci add vscode-extension' creates it and 'mnci upgrade'\n// rewrites it, so local edits do not survive an upgrade.\n//\n// Packages and publishes a VS Code extension project. Its Nx targets call it:\n// node tools/vscode-extension.cjs package apps/<name> [--sidecar <go-app>]\n// node tools/vscode-extension.cjs publish apps/<name> [--sidecar <go-app>]\n// Without --sidecar: one universal dist/drop/<project>.vsix.\n// With --sidecar: one dist/drop/<project>-<target>.vsix per Marketplace target, each\n// carrying that platform's binary from the Go app's build-all in bin/.\n'use strict'\nconst { spawnSync } = require('node:child_process')\nconst { cpSync, existsSync, mkdirSync, readFileSync, rmSync } = require('node:fs')\nconst { basename, dirname, join, resolve } = require('node:path')\n\nconst TARGETS = ${scriptObjectLiteral(VSCODE_SIDECAR_TARGETS)}\nconst DROP = resolve('dist/drop')\nconst VSCE_FLAGS = ['--no-dependencies', '--skip-license', '--allow-missing-repository']\n\nfunction fail (message) {\n console.error(message)\n process.exit(1)\n}\n\nfunction bin (packageName, command) {\n const manifestPath = require.resolve(packageName + '/package.json', { paths: [process.cwd()] })\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))\n const entry = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin[command]\n\n return join(dirname(manifestPath), entry)\n}\n\nfunction run (packageName, command, args, options) {\n const result = spawnSync(process.execPath, [bin(packageName, command), ...args], { stdio: 'inherit', ...options })\n if (result.status !== 0) process.exit(result.status ?? 1)\n}\n\nfunction vsixFiles (name, sidecar) {\n return sidecar\n ? Object.keys(TARGETS).map(target => join(DROP, name + '-' + target + '.vsix'))\n : [join(DROP, name + '.vsix')]\n}\n\nfunction packageExtension (projectRoot, manifest, sidecar) {\n mkdirSync(DROP, { recursive: true })\n if (!sidecar) {\n run('@vscode/vsce', 'vsce', ['package', ...VSCE_FLAGS, '--out', vsixFiles(project)[0]], { cwd: projectRoot })\n\n return\n }\n run('nx', 'nx', ['run', sidecar + ':build-all'], { env: { ...process.env, VERSION: manifest.version } })\n const staging = join(projectRoot, 'bin')\n try {\n for (const [target, platform] of Object.entries(TARGETS)) {\n const source = join('dist/platforms', sidecar, platform)\n if (!existsSync(source)) fail('No ' + platform + ' build of ' + sidecar + ' at ' + source + ' - its build-all target did not write one.')\n rmSync(staging, { recursive: true, force: true })\n cpSync(source, staging, { recursive: true })\n run('@vscode/vsce', 'vsce', ['package', '--target', target, ...VSCE_FLAGS, '--out', join(DROP, project + '-' + target + '.vsix')], { cwd: projectRoot })\n }\n } finally {\n rmSync(staging, { recursive: true, force: true })\n }\n}\n\nfunction publishExtension (manifest, sidecar, dryRun) {\n if (dryRun) {\n console.log('Dry run - would publish ' + vsixFiles(project, sidecar).join(', ') + ' to the Marketplace.')\n\n return\n }\n const entra = process.env.VSCE_AUTH === 'entra'\n const token = process.env.VSCE_PAT ?? ''\n if (!entra && (token === '' || /^\\$\\(.*\\)$/.test(token))) {\n console.log('No Marketplace credential - skipping the Marketplace publish of ' + manifest.name + '. Set the AZURE_CLIENT_ID and AZURE_TENANT_ID variables (Microsoft Entra ID) or the VSCE_PAT secret to publish.')\n\n return\n }\n const files = vsixFiles(project, sidecar)\n const missing = files.filter(file => !existsSync(file))\n if (missing.length > 0) fail('Nothing to publish: ' + missing.join(', ') + ' not found - run the package target first.')\n run('@vscode/vsce', 'vsce', ['publish', ...(entra ? ['--azure-credential'] : []), '--packagePath', ...files, '--skip-duplicate'])\n}\n\nconst [command, projectRoot, ...rest] = process.argv.slice(2)\nconst sidecarIndex = rest.indexOf('--sidecar')\nconst sidecar = sidecarIndex === -1 ? undefined : rest[sidecarIndex + 1]\nif (!projectRoot || !existsSync(join(projectRoot, 'package.json'))) fail('Usage: node tools/vscode-extension.cjs package|publish <project root> [--sidecar <go-app>]')\nconst manifest = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'))\n// Packages are named after the project folder, never the manifest name, which is\n// free to change (the Marketplace id need not match the folder).\nconst project = basename(resolve(projectRoot))\n// nx release --dry-run hands the publish target --dryRun=true and sets NX_DRY_RUN\n// (measured on Nx 23), so a dry run never reaches the Marketplace, token or not.\nconst dryRun = rest.some(argument => /^--dry-?run(?:=true)?$/i.test(argument)) || process.env.NX_DRY_RUN === 'true'\nif (command === 'package') packageExtension(projectRoot, manifest, sidecar)\nelse if (command === 'publish') publishExtension(manifest, sidecar, dryRun)\nelse fail('Unknown command ' + command + ' - expected package or publish.')\n`\n\n/**\n * The `.vscodeignore` written into every extension.\n *\n * @remarks\n * The build is bundled into `dist/main.js`, so everything else in the project\n * folder is source or tooling. `**\\/*.d.ts` matters: declarations cannot be turned\n * off at build time (`declaration: false` collides with the workspace's\n * `composite`/`declarationMap` and dies on TS5069, measured), so they are dropped\n * here instead. `bin/` is deliberately NOT ignored: it is where a sidecar is staged.\n */\nexport const VSCODE_IGNORE = `src/**\ntest/**\nout-tsc/**\ntest-output/**\n**/*.map\n**/*.d.ts\n**/*.spec.*\ntsconfig*.json\njest.config.*\nvitest.config.*\n.spec.swcrc\nnode_modules/**\n`\n\n/**\n * The sample entry point: `activate` registers one command.\n *\n * @remarks\n * `src/main.ts`, not VS Code's customary `extension.ts`: the slice rules allow only\n * `index` and `main` at the root of `src`. `activationEvents` stays empty because VS\n * Code (since 1.74) activates on the contributed command by itself.\n *\n * @param name - The project name, which prefixes the command id.\n * @returns The file content.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionMain (name: string): string {\n return `import * as vscode from 'vscode'\n\nexport function activate (context: vscode.ExtensionContext): void {\n context.subscriptions.push(\n vscode.commands.registerCommand('${name}.hello', () => {\n void vscode.window.showInformationMessage('Hello from ${name}')\n }),\n )\n}\n\nexport function deactivate (): void {}\n`\n}\n\n/**\n * The sample spec, which runs `activate` against the `vscode` stub.\n *\n * @remarks\n * Written against the real `vscode` types and runner-neutral (`describe`/`it`/\n * `expect` are globals under both Jest and Vitest here), so the same file serves\n * either stack and typechecks without knowing the stub exists.\n *\n * @param name - The project name, which prefixes the command id.\n * @returns The file content.\n * @throws Never - pure string building.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionMainSpec (name: string): string {\n return `import * as vscode from 'vscode'\nimport { activate } from './main'\n\ndescribe('activate', () => {\n it('registers the ${name}.hello command', async () => {\n const context = { subscriptions: [] } as unknown as vscode.ExtensionContext\n\n activate(context)\n\n expect(context.subscriptions).toHaveLength(1)\n expect(await vscode.commands.getCommands()).toContain('${name}.hello')\n })\n})\n`\n}\n\n/**\n * Stands in for the `vscode` module in unit tests.\n *\n * @remarks\n * `vscode` exists only inside the extension host: it is injected at runtime and no\n * package by that name is ever installed (`@types/vscode` carries only types). So\n * a spec that imports anything importing it fails to resolve. The test runner maps\n * the specifier here instead (Jest `moduleNameMapper`, Vitest `alias`). It covers\n * what the sample uses and nothing more; it grows with the extension.\n */\nexport const VSCODE_STUB = `// Stands in for the 'vscode' module in unit tests: that module exists only\n// inside the extension host. Extend it as the extension reaches for more API.\nconst registered = new Map<string, (...arguments_: unknown[]) => unknown>()\n\nexport const commands = {\n registerCommand (id: string, handler: (...arguments_: unknown[]) => unknown): { dispose: () => void } {\n registered.set(id, handler)\n\n return { dispose: () => { registered.delete(id) } }\n },\n getCommands (): Promise<string[]> {\n return Promise.resolve(registered.keys().toArray())\n },\n executeCommand (id: string, ...arguments_: unknown[]): Promise<unknown> {\n return Promise.resolve(registered.get(id)?.(...arguments_))\n },\n}\n\nexport const window = {\n showInformationMessage (_message: string): Promise<undefined> {\n return Promise.resolve(undefined)\n },\n}\n`\n\n/** Where {@link VSCODE_STUB} is written, relative to the project root. */\nconst VSCODE_STUB_PATH = 'test/vscode.stub.ts'\n\n/**\n * Points the project's test runner at {@link VSCODE_STUB} for the `vscode` specifier.\n *\n * @remarks\n * Edits the config the generator just wrote at a line it always writes\n * (`testEnvironment: 'node',` for Jest, `environment: 'node',` for Vitest), and\n * throws if that anchor is gone rather than leaving a project whose first test of\n * real extension code fails to resolve `vscode` with no hint why.\n *\n * @param projectRoot - Absolute path to the extension project.\n * @param testRunner - The workspace's test runner.\n * @returns Nothing.\n * @throws Error when the generated config no longer has the expected anchor line.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction mapVscodeToStub (projectRoot: string, testRunner: WorkspaceStack['testRunner']): void {\n const [file, anchor, mapping] = testRunner === 'jest'\n ? ['jest.config.cts', \"testEnvironment: 'node',\", `moduleNameMapper: { '^vscode$': '<rootDir>/${VSCODE_STUB_PATH}' },`]\n : ['vitest.config.mts', \"environment: 'node',\", `alias: { vscode: \\`\\${import.meta.dirname}/${VSCODE_STUB_PATH}\\` },`]\n const path = join(projectRoot, file)\n const content = fileExists(path) ? readFileSync(path, 'utf8') : ''\n const line = content.split('\\n').find(candidate => candidate.trim() === anchor)\n if (line === undefined) {\n throw new Error(`${file} has no \"${anchor}\" line to map the vscode module after; map 'vscode' to ${VSCODE_STUB_PATH} by hand.`)\n }\n const indent = line.slice(0, line.indexOf(anchor))\n // A replacer function, not a string: the Jest mapping holds `'^vscode$'`, and in a\n // replacement STRING `$'` means \"the text after the match\", which spliced the rest\n // of the file into the middle of the line (caught by the spec).\n writeFileEnsured(path, content.replace(line, () => `${line}\\n${indent}${mapping}`))\n}\n\n/**\n * Installs the extension toolchain at the workspace root.\n *\n * @remarks\n * Root devDependencies, per the shared-dev-packages invariant: `@types/vscode` for\n * the API types and `@vscode/vsce` for packaging and publishing. Skipped when both\n * are already declared, so a second extension installs nothing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when the install exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction ensureVscodeToolchain (workspaceRoot: string): void {\n const missing = ['@types/vscode', '@vscode/vsce'].filter(name => !hasPlugin(workspaceRoot, name))\n if (missing.length === 0) {\n return\n }\n logger.step(`Installing the VS Code extension toolchain (${missing.join(', ')})`)\n if (runShell('npm', ['install', '--save-dev', ...missing, '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\n throw new Error(`npm install of ${missing.join(', ')} failed`)\n }\n}\n\n/**\n * The publisher a new extension gets when `--publisher` is not given.\n *\n * @remarks\n * The workspace's npm scope without the `@`, which is usually the organisation's\n * name and so a sensible first guess. It only has to be right before the first\n * publish: the Marketplace rejects a publisher id the token does not own.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns A publisher id.\n * @throws Never - an unreadable or unscoped root name falls back to the bare name.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction defaultPublisher (workspaceRoot: string): string {\n const { name } = readJson<{ name?: string }>(join(workspaceRoot, 'package.json'))\n\n return (name ?? 'publisher').replace(/^@/, '').split('/', 1)[0]\n}\n\n/**\n * Reshapes the generated manifest into an extension manifest and rewires its build.\n *\n * @remarks\n * - `name` loses its scope: `vsce` rejects scoped names. `nx.name` pins the Nx\n * project name to the folder, so `name` and `displayName` are free to change\n * afterwards (the Marketplace id need not match the folder, #247); without it Nx\n * takes the project name from `name`, and renaming the extension would orphan\n * every root script, task and launch entry that names the project.\n * - `engines.vscode` is pinned to the installed `@types/vscode` (`vsce` refuses a\n * package whose types are newer than its engine range).\n * - The build bundles (`bundle`, `thirdParty`): a `.vsix` ships no `node_modules`,\n * so the generator's un-bundled mirror of the source tree cannot run in the\n * extension host. `vscode` stays external, it is the host's.\n * `generatePackageJson: false`, because a `dist/package.json` confuses `vsce`.\n * - The generator's `serve` and prune targets go: an extension is not started with\n * `node`, and it is never deployed with an installed `node_modules`.\n *\n * @param projectRoot - Absolute path to the extension project.\n * @param name - The project name.\n * @param publisher - The Marketplace publisher id.\n * @param typesVersion - The installed `@types/vscode` version.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reshapeManifest (projectRoot: string, name: string, publisher: string, typesVersion: string): void {\n const manifestPath = join(projectRoot, 'package.json')\n const manifest = readJson<Record<string, unknown> & {\n nx?: { tags?: string[]; targets?: Record<string, { options?: Record<string, unknown> }> }\n }>(manifestPath)\n const { nx = {}, ...rest } = manifest\n const targets = { ...nx.targets }\n for (const target of ['serve', 'prune', 'prune-lockfile', 'copy-workspace-modules']) {\n delete targets[target]\n }\n const build = targets.build ?? {}\n targets.build = {\n ...build,\n options: {\n ...build.options,\n bundle: true,\n thirdParty: true,\n external: ['vscode'],\n generatePackageJson: false,\n },\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...rest,\n name,\n displayName: name,\n version: rest.version ?? '0.0.1',\n publisher,\n engines: { vscode: `^${typesVersion}` },\n categories: ['Other'],\n main: './dist/main.js',\n activationEvents: [],\n contributes: { commands: [{ command: `${name}.hello`, title: `${name}: Hello` }] },\n nx: { ...nx, name, tags: [...new Set([...(nx.tags ?? []), VSCODE_EXTENSION_TAG])], targets },\n }),\n )\n}\n\n/**\n * Adds the `vscode` types to a project tsconfig.\n *\n * @remarks\n * Both the app and the spec config need them: a `types` list stops TypeScript from\n * loading every `@types` package, and `vscode` is declared only by `@types/vscode`'s\n * ambient module, so without the entry `import * as vscode from 'vscode'` does not\n * resolve at all.\n *\n * @param tsconfigPath - Absolute path to the tsconfig.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addVscodeTypes (tsconfigPath: string): void {\n if (!fileExists(tsconfigPath)) {\n return\n }\n const tsconfig = readJson<{ compilerOptions?: { types?: string[] } }>(tsconfigPath)\n const compilerOptions = tsconfig.compilerOptions ?? {}\n const types = compilerOptions.types ?? []\n if (types.includes('vscode')) {\n return\n }\n writeFileEnsured(tsconfigPath, toJson({ ...tsconfig, compilerOptions: { ...compilerOptions, types: [...types, 'vscode'] } }))\n}\n\n/**\n * The `package` target: one universal `.vsix`, or one per Marketplace target with a sidecar.\n *\n * @remarks\n * Outputs are listed one per file rather than as a glob, so Nx caches and restores\n * exactly the packages this project writes and never another project's in the\n * shared `dist/drop`. The work is in {@link VSCODE_EXTENSION_SCRIPT}.\n *\n * @param name - The project name.\n * @param sidecar - The `go-app` whose binaries ship in `bin/`, when any.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionPackageTarget (name: string, sidecar?: string): Record<string, unknown> {\n const outputs = sidecar\n ? Object.keys(VSCODE_SIDECAR_TARGETS).map(target => `{workspaceRoot}/dist/drop/${name}-${target}.vsix`)\n : [`{workspaceRoot}/dist/drop/${name}.vsix`]\n\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs,\n options: { command: `node ${VSCODE_EXTENSION_SCRIPT_PATH} package apps/${name}${sidecar ? ` --sidecar ${sidecar}` : ''}` },\n }\n}\n\n/**\n * The `nx-release-publish` target: publish the packaged `.vsix` files to the Marketplace.\n *\n * @remarks\n * Depends on `package`, so it packages the version `nx release` has just written,\n * never a stale one. Gated on a Marketplace credential (Entra ID or `VSCE_PAT`) inside the script (see\n * {@link VSCODE_EXTENSION_SCRIPT}).\n *\n * @param name - The project name.\n * @param sidecar - The `go-app` whose binaries ship in `bin/`, when any.\n * @returns The nx:run-commands target object.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensionPublishTarget (name: string, sidecar?: string): Record<string, unknown> {\n return {\n executor: 'nx:run-commands',\n dependsOn: ['package'],\n options: { command: `node ${VSCODE_EXTENSION_SCRIPT_PATH} publish apps/${name}${sidecar ? ` --sidecar ${sidecar}` : ''}` },\n }\n}\n\n/**\n * Adds a `<name>: debug` launch configuration and the task it builds with.\n *\n * @remarks\n * `extensionHost` opens a second VS Code window with the extension loaded from\n * `apps/<name>`. The pre-launch task builds the `development` configuration, which\n * keeps source maps, so breakpoints in `src/` bind. Named `<name>: debug`, never\n * `mnci: …`: the overlay replaces every launch entry with that prefix on\n * `mnci upgrade`, and this one is per-project state, like the tasks.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name.\n * @returns Nothing.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction addDebugLaunch (workspaceRoot: string, name: string): void {\n const file = readdirSync(workspaceRoot).find(entry => entry.endsWith('.code-workspace'))\n if (file === undefined) {\n return\n }\n const path = join(workspaceRoot, file)\n const workspace = readCodeWorkspace<{\n folders?: { name?: string }[]\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n launch?: { version?: string; configurations?: Record<string, unknown>[] }\n }>(path) ?? {}\n const folder = `\\${workspaceFolder:${workspace.folders?.[0]?.name ?? file.replace(/\\.code-workspace$/, '')}}`\n const taskLabel = `${name}: build (development)`\n const launchName = `${name}: debug`\n const tasks = (workspace.tasks?.tasks ?? []).filter(task => task.label !== taskLabel)\n const configurations = (workspace.launch?.configurations ?? []).filter(configuration => configuration.name !== launchName)\n writeFileEnsured(\n path,\n toJson({\n ...workspace,\n tasks: {\n version: workspace.tasks?.version ?? '2.0.0',\n tasks: [...tasks, { label: taskLabel, type: 'shell', command: `npx nx run ${name}:build:development`, problemMatcher: [] }],\n },\n launch: {\n version: workspace.launch?.version ?? '0.2.0',\n configurations: [\n ...configurations,\n {\n name: launchName,\n type: 'extensionHost',\n request: 'launch',\n args: [`--extensionDevelopmentPath=${folder}/apps/${name}`],\n outFiles: [`${folder}/apps/${name}/dist/**/*.js`],\n preLaunchTask: taskLabel,\n },\n ],\n },\n }),\n )\n}\n\n/**\n * Rewrites {@link VSCODE_EXTENSION_SCRIPT_PATH} when the workspace has an extension.\n *\n * @remarks\n * Called by `mnci upgrade`, so a fix to the script reaches every workspace that\n * uses it, and a workspace with no extension never gains the file.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Whether the file changed (an up-to-date script is left alone).\n * @throws Propagates any `fs` error writing the file.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function refreshVscodeExtensionScript (workspaceRoot: string): boolean {\n const apps = join(workspaceRoot, 'apps')\n let entries: string[]\n try {\n entries = readdirSync(apps)\n } catch {\n return false\n }\n const hasExtension = entries.some((entry) => {\n const manifestPath = join(apps, entry, 'package.json')\n if (!fileExists(manifestPath)) {\n return false\n }\n const tags = readJson<{ nx?: { tags?: string[] } }>(manifestPath).nx?.tags ?? []\n\n return tags.includes(VSCODE_EXTENSION_TAG)\n })\n const path = join(workspaceRoot, VSCODE_EXTENSION_SCRIPT_PATH)\n if (!hasExtension || (fileExists(path) && readFileSync(path, 'utf8') === VSCODE_EXTENSION_SCRIPT)) {\n return false\n }\n writeFileEnsured(path, VSCODE_EXTENSION_SCRIPT)\n\n return true\n}\n\n/**\n * Pins `nx.name` on every extension that lacks it.\n *\n * @remarks\n * Extensions added by `@mnci/cli` 4.12.0 have no `nx.name`, so their Nx project\n * name still comes from the manifest `name` (#247). Pinning it to that same value\n * changes nothing today and makes a later rename of the extension safe.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The workspace-relative manifests that were updated.\n * @throws Propagates any `fs`/JSON error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pinVscodeExtensionProjectNames (workspaceRoot: string): string[] {\n const apps = join(workspaceRoot, 'apps')\n let entries: string[]\n try {\n entries = readdirSync(apps)\n } catch {\n return []\n }\n const updated: string[] = []\n for (const entry of entries) {\n const manifestPath = join(apps, entry, 'package.json')\n if (!fileExists(manifestPath)) {\n continue\n }\n const manifest = readJson<{ name?: string; nx?: { name?: string; tags?: string[] } }>(manifestPath)\n if (!manifest.nx?.tags?.includes(VSCODE_EXTENSION_TAG) || manifest.nx.name !== undefined || manifest.name === undefined) {\n continue\n }\n writeFileEnsured(manifestPath, toJson({ ...manifest, nx: { ...manifest.nx, name: manifest.name } }))\n updated.push(`apps/${entry}/package.json`)\n }\n\n return updated\n}\n\n/**\n * What `mnci add vscode-extension` takes beyond the name.\n *\n * @remarks\n * Both come from CLI flags (`--publisher`, `--sidecar`); neither is prompted for,\n * because both have a sensible absence: a default publisher and no sidecar.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface VscodeExtensionOptions {\n /** The Marketplace publisher id; defaults to the workspace scope without `@`. */\n publisher?: string\n /** A `go-app` whose six-platform build ships inside the extension, in `bin/`. */\n sidecar?: string\n}\n\n/**\n * Re-syncs `package-lock.json` with the reshaped manifest.\n *\n * @remarks\n * `apps/*` is an npm workspace, so the lock records each app under its manifest\n * `name`. The generator installed under its scoped name, and\n * {@link reshapeManifest} unscopes it, which `vsce` requires. Left alone, the lock\n * still names the old package, and CI's `npm ci` refuses it (\"Missing: <name> from\n * lock file\", #251, measured on a real workspace). A failure here only warns, as\n * the root-dependency relocation does: the project exists, and `npm install` fixes\n * the lock.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name, for the message.\n * @returns Nothing.\n * @throws Never - a failed refresh is reported, not thrown.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction refreshLockFile (workspaceRoot: string, name: string): void {\n if (runShell('npm', ['install', '--package-lock-only', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\n logger.warn(`Could not refresh package-lock.json after reshaping ${name}'s manifest. Run 'npm install' before committing.`)\n }\n}\n\n/**\n * Adds a VS Code extension: `@nx/node:application`, bundled, packaged with `vsce`.\n *\n * @remarks\n * The generator gives a TypeScript app with the workspace's test runner; this turns\n * it into an extension (see {@link reshapeManifest}) and adds what a Marketplace\n * extension needs: `package` (`.vsix` into `dist/drop`, per platform with a\n * `--sidecar`), `nx-release-publish`, a `vscode` stub for unit tests, a\n * `.vscodeignore`, and an Extension Development Host launch entry. Tagged\n * {@link VSCODE_EXTENSION_TAG}, which puts it in `nx release` scope.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The project name (already validated).\n * @param stack - The workspace's test runner.\n * @param options - Publisher and sidecar.\n * @returns Nothing.\n * @throws Error when the sidecar is not a Go app, or a generator or install fails.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function addVscodeExtension (\n workspaceRoot: string,\n name: string,\n stack: WorkspaceStack,\n options: VscodeExtensionOptions = {},\n): void {\n const { sidecar } = options\n if (sidecar !== undefined && !fileExists(join(workspaceRoot, 'apps', sidecar, 'project.json'))) {\n throw new Error(`--sidecar ${sidecar}: no Go app at apps/${sidecar}. Add it first with \\`mnci add go-app ${sidecar}\\`.`)\n }\n runNodeApp(workspaceRoot, name, stack, 'none')\n ensureVscodeToolchain(workspaceRoot)\n const projectRoot = join(workspaceRoot, 'apps', name)\n const typesVersion = readJson<{ version: string }>(join(workspaceRoot, 'node_modules/@types/vscode/package.json')).version\n reshapeManifest(projectRoot, name, options.publisher ?? defaultPublisher(workspaceRoot), typesVersion)\n addVscodeTypes(join(projectRoot, 'tsconfig.app.json'))\n addVscodeTypes(join(projectRoot, 'tsconfig.spec.json'))\n writeFileEnsured(join(projectRoot, 'src/main.ts'), vscodeExtensionMain(name))\n writeFileEnsured(join(projectRoot, 'src/main.spec.ts'), vscodeExtensionMainSpec(name))\n writeFileEnsured(join(projectRoot, VSCODE_STUB_PATH), VSCODE_STUB)\n mapVscodeToStub(projectRoot, stack.testRunner)\n writeFileEnsured(join(projectRoot, '.vscodeignore'), VSCODE_IGNORE)\n writeFileEnsured(join(workspaceRoot, VSCODE_EXTENSION_SCRIPT_PATH), VSCODE_EXTENSION_SCRIPT)\n addNxTargets(join(projectRoot, 'package.json'), {\n 'package': vscodeExtensionPackageTarget(name, sidecar),\n 'nx-release-publish': vscodeExtensionPublishTarget(name, sidecar),\n })\n removeGeneratedEslintConfig(workspaceRoot, `apps/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n addDebugLaunch(workspaceRoot, name)\n refreshLockFile(workspaceRoot, name)\n}\n","import { join } from 'node:path'\nimport {\n ECOSYSTEMS,\n PEER_SECTION,\n ROOT_LABEL,\n collectInventory,\n hasEcosystem,\n isWorkspaceProject,\n resolvedVersion,\n rewriteSpec,\n type DependencySite,\n type Ecosystem,\n} from './manifest.repository'\nimport { highestSpec, rangeOperator, specVersion } from './semver.algorithm'\nimport { runFormatter, runShell } from '../nx-workspace'\nimport { fileExists } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * Options accepted by `mnci sync`.\n *\n * @remarks\n * Mirrors `nx sync` / `nx sync:check`: the bare command writes, `--check`\n * reports and fails. A CI step wants the second, a developer the first.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface SyncOptions {\n /** Report drift and exit non-zero without writing anything. */\n check?: boolean\n /** Restrict the run to one ecosystem. */\n ecosystem?: string\n}\n\n/**\n * One package declared at two or more incompatible versions.\n *\n * @remarks\n * `fixable` and `blocked` are separate lists rather than one list with a flag\n * because they need different endings: the first is rewritten, the second is\n * reported as a warning naming the spec that could not be understood. Collapsing\n * them is how a `git:` dependency quietly gets a version number written over it.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Drift {\n /** The package name. */\n name: string\n /** Which ecosystem it belongs to. */\n ecosystem: Ecosystem\n /** The spec every site should converge on. */\n target: string\n /** Why that spec won, for the report. */\n reason: 'resolved' | 'highest'\n /** The sites that disagree with `target` and can be rewritten. */\n fixable: DependencySite[]\n /** Sites that disagree but whose spec shape cannot safely be edited. */\n blocked: DependencySite[]\n}\n\n/**\n * Resolves the `--ecosystem` flag to the list of ecosystems to process.\n *\n * @remarks\n * Throws on an unknown value rather than silently processing nothing: a typo in\n * `--ecosystem` would otherwise produce a confident \"everything agrees\" over a\n * workspace that was never inspected.\n *\n * Shared with `mnci up` so the two commands cannot drift on what they accept.\n *\n * @param requested - The raw flag value, if any.\n * @returns The ecosystems to process.\n * @throws Error when the flag names something that is not an ecosystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolveEcosystems (requested: string | undefined): readonly Ecosystem[] {\n if (!requested) {\n return ECOSYSTEMS\n }\n const match = ECOSYSTEMS.find(ecosystem => ecosystem === requested)\n if (!match) {\n throw new Error(\n `Unknown ecosystem '${requested}'. Expected one of: ${ECOSYSTEMS.join(', ')}.`,\n )\n }\n\n return [match]\n}\n\n/**\n * Finds every package declared at more than one version across the workspace.\n *\n * @remarks\n * The npm ecosystem has no `catalog:` — pnpm's one-version-per-workspace\n * mechanism — so keeping two projects on the same range is a convention nothing\n * enforces. This is the enforcement, and it deliberately extends to pip and pub\n * for the same reason.\n *\n * **Go is absent by construction, not by omission.** mnci's Go layout is a\n * single root `go.mod`, so one module cannot hold two versions of a dependency;\n * `runSync` says so explicitly rather than letting a silent zero-findings run\n * read as \"checked and fine\".\n *\n * **Peer ranges are excluded outright, not reported as blocked.** A peer range\n * says which versions a published package is compatible with, so two packages\n * declaring different ones are not in conflict — they are making different,\n * equally valid statements. Reporting them would be noise, and converging them\n * would narrow a published package's supported range. This exclusion exists\n * because the first run of this command against mnci's own repo reported six\n * findings, five of which were peers.\n *\n * The winning spec is the one matching what is actually **resolved** —\n * `node_modules` for npm, `pubspec.lock` for pub, the interpreter for pip.\n * That is the same source `@nx/dependency-checks` pins a drifted range to when\n * it auto-fixes, so this command and that lint rule converge on one answer\n * instead of overwriting each other. When nothing is installed, the highest\n * declared range wins instead — a fallback, and the report says which was used.\n *\n * Read-only: it never touches the workspace, so `runSync` can offer `--check`\n * by simply declining to call {@link applyDrift}.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to inspect.\n * @returns Every package whose declarations disagree.\n * @throws Never - unreadable manifests are skipped by the inventory.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectDrift (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[] = ECOSYSTEMS,\n): Drift[] {\n // Go has one manifest, so it can never disagree with itself.\n const reconcilable = ecosystems.filter(ecosystem => ecosystem !== 'go')\n const inventory = collectInventory(workspaceRoot, reconcilable)\n const drift: Drift[] = []\n\n for (const [name, allSites] of inventory) {\n const sites = allSites.filter(site => site.section !== PEER_SECTION)\n // A workspace-internal package is linked, not resolved from a registry;\n // its version is owned by `nx release`, not by this command.\n if (sites.length < 2 || isWorkspaceLocal(workspaceRoot, sites, name)) {\n continue\n }\n const distinct = new Set(sites.map(site => site.spec))\n if (distinct.size < 2) {\n continue\n }\n\n const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, name)\n const target = chooseTarget(sites, installed)\n if (!target) {\n continue\n }\n\n const disagreeing = sites.filter(site => site.spec !== target)\n drift.push({\n name,\n ecosystem: sites[0].ecosystem,\n target,\n reason: installed ? 'resolved' : 'highest',\n fixable: disagreeing.filter(site => site.rewritable),\n blocked: disagreeing.filter(site => !site.rewritable),\n })\n }\n\n return drift.toSorted((left, right) => left.name.localeCompare(right.name))\n}\n\n/**\n * Whether a package is one of this workspace's own projects.\n *\n * @remarks\n * A thin, ecosystem-guarded reading of `isWorkspaceProject`: only npm declares\n * an internal library by a name that maps to a directory, so pip and pub short-\n * circuit rather than testing a path that could accidentally match.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param sites - The declarations for one package name.\n * @param name - The package name.\n * @returns `true` when the package is a project in this workspace.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isWorkspaceLocal (\n workspaceRoot: string,\n sites: readonly DependencySite[],\n name: string,\n): boolean {\n // pip internal libs are vendored and pub ones are workspace members, so\n // neither is declared under a name this check could resolve.\n return sites[0].ecosystem === 'npm' && isWorkspaceProject(workspaceRoot, name)\n}\n\n/**\n * Picks the spec every site should converge on.\n *\n * @remarks\n * When something is installed, the target keeps the range operator the majority\n * of sites already use rather than imposing a caret — a workspace that pins\n * exactly stays pinned. Ties go to the caret, which is what every Nx generator\n * writes.\n *\n * @param sites - Every declaration of one package.\n * @param installed - The resolved version, when one exists.\n * @returns The spec to converge on, or `undefined` when none can be derived.\n * @throws Never - unparseable specs yield `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction chooseTarget (\n sites: readonly DependencySite[],\n installed: string | undefined,\n): string | undefined {\n if (!installed) {\n return highestSpec(sites.map(site => site.spec))\n }\n\n const counts = new Map<string, number>()\n for (const site of sites) {\n if (!specVersion(site.spec)) {\n continue\n }\n const operator = rangeOperator(site.spec)\n counts.set(operator, (counts.get(operator) ?? 0) + 1)\n }\n if (counts.size === 0) {\n return undefined\n }\n\n const [operator] = [...counts].toSorted(\n (left, right) => right[1] - left[1] || (left[0] === '^' ? -1 : 1),\n )[0]\n\n return `${operator}${installed}`\n}\n\n/**\n * Writes every fixable drift back to its manifest.\n *\n * @remarks\n * Deduplicates by manifest path, because one file commonly holds several of the\n * packages being converged and the caller reports what it changed.\n *\n * @param drift - The findings to apply.\n * @returns The manifest paths that actually changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function applyDrift (drift: readonly Drift[]): string[] {\n const changed = new Set<string>()\n for (const finding of drift) {\n for (const site of finding.fixable) {\n if (rewriteSpec(site, finding.target)) {\n changed.add(site.manifestPath)\n }\n }\n }\n\n return [...changed]\n}\n\n/**\n * Regenerates the workspace's TypeScript project references via `nx sync`.\n *\n * @remarks\n * The `--preset=ts` model resolves cross-project imports through TypeScript\n * project references, and those references are maintained by `nx sync` — not\n * by the generators. Without this, a freshly added project's references are\n * stale, so an editor (and a plain `tsc`) cannot resolve `@scope/lib` imports\n * between projects until the user runs `nx sync` by hand.\n *\n * Worth being precise about what this does and does not cover, because the\n * names invite the wrong assumption: `nx sync` runs the workspace's **sync\n * generators**, and the only one a generated workspace registers is\n * `@nx/js:typescript-sync`. It reconciles `tsconfig` project references and\n * nothing else — it has no opinion whatsoever about dependency versions. That\n * is what {@link collectDrift} is for, and why `mnci sync` does both.\n *\n * Non-fatal: `mnci add` calls this after a project is already generated, so a\n * sync failure only warns (with the manual command) rather than failing the run.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Never - a non-zero `nx sync` is reported as a warning, not thrown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function syncProjectReferences (workspaceRoot: string): void {\n logger.step('Syncing TypeScript project references (nx sync)')\n if (runShell('npx', ['nx', 'sync'], workspaceRoot) !== 0) {\n logger.warn(\n 'nx sync did not complete — run `npx nx sync` yourself so cross-project imports resolve in your editor.',\n )\n }\n}\n\n/**\n * Describes one drift finding as a single report line.\n *\n * @param finding - The finding to describe.\n * @returns The formatted line.\n * @throws Never - performs string formatting only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction describe (finding: Drift): string {\n const sites = [...finding.fixable, ...finding.blocked]\n .map(site => `${site.project === ROOT_LABEL ? ROOT_LABEL : site.project} ${site.spec}`)\n .join(', ')\n\n return `${finding.name} → ${finding.target} (${finding.reason}); disagreeing: ${sites}`\n}\n\n/**\n * Aligns every project's dependency ranges, then syncs TypeScript references.\n *\n * @remarks\n * Two jobs under one command because they are the two halves of \"the workspace\n * agrees with itself\", and a user should not have to know which mechanism owns\n * which half.\n *\n * The formatter pass at the end is not cosmetic: `eslint --fix` is what runs\n * `@nx/dependency-checks`' own auto-fix, so anything this command converged is\n * immediately re-checked by the rule that guards the same property — and any\n * range still drifted from what is installed gets pinned there rather than left\n * for CI to report.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param options - Parsed CLI options.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an Nx workspace, or the\n * `--ecosystem` flag names something unknown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runSync (workspaceRoot: string, options: SyncOptions): void {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — run 'mnci sync' from the workspace root.`,\n )\n }\n\n const ecosystems = resolveEcosystems(options.ecosystem)\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n logger.info(`⊘ SKIPPED ${ecosystem} — no ${ecosystem} project in this workspace.`)\n }\n }\n if (ecosystems.includes('go') && hasEcosystem(workspaceRoot, 'go')) {\n logger.success('go — nothing to sync; one root go.mod means one version of every module.')\n }\n\n const drift = collectDrift(workspaceRoot, ecosystems)\n\n if (drift.length === 0) {\n logger.success('Every project agrees on every shared dependency version.')\n } else {\n for (const finding of drift) {\n logger.info(` ${describe(finding)}`)\n }\n }\n\n if (options.check) {\n if (drift.length > 0) {\n logger.error(\n `${drift.length} package(s) declared at more than one version. Run 'mnci sync' to converge them.`,\n )\n process.exitCode = 1\n }\n\n // --check never runs `nx sync` either: `nx sync:check` is the read-only\n // command for that half, and mnci doctor already calls it.\n return\n }\n\n const changed = applyDrift(drift)\n for (const path of changed) {\n logger.detail(`updated ${path}`)\n }\n\n const blocked = drift.flatMap(finding => finding.blocked)\n for (const site of blocked) {\n logger.warn(\n `${site.name} in ${site.project} is declared as '${site.spec}', which is not a plain version range — left unchanged.`,\n )\n }\n\n syncProjectReferences(workspaceRoot)\n\n if (changed.length > 0) {\n logger.step('Formatting the changed manifests (eslint --fix)')\n runFormatter(workspaceRoot)\n }\n\n logger.success('Done. Review the changes with `git diff` before committing.')\n}\n","import { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runCapture } from '../nx-workspace'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../file-system'\n\n/**\n * The five dependency ecosystems a generated workspace can contain.\n *\n * @remarks\n * The set is closed on purpose — it mirrors the project kinds `mnci add`\n * generates, so a new ecosystem here is a deliberate decision rather than\n * something that appears because a stray manifest was found.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type Ecosystem = 'npm' | 'pip' | 'pub' | 'nuget' | 'go'\n\n/**\n * Every ecosystem, in the order commands report them.\n *\n * @remarks\n * npm first because it is the only one present in every workspace; Go last\n * because it is the one with nothing to reconcile — every other ecosystem,\n * NuGet included, has a real per-project manifest whose versions can drift.\n */\nexport const ECOSYSTEMS: readonly Ecosystem[] = ['npm', 'pip', 'pub', 'nuget', 'go']\n\n/**\n * The label shown for the workspace root, which is not a project.\n *\n * @remarks\n * Parenthesised so it can never collide with a real directory name in the\n * projects column — a workspace could legitimately contain `apps/root`.\n */\nexport const ROOT_LABEL = '(root)'\n\n/**\n * One place a package is declared.\n *\n * @remarks\n * A single package name typically has several of these — the same external\n * dependency declared by two projects, or by a project and the root. That list\n * is the whole point: it is what `mnci sync` reconciles and what `mnci up`\n * prints beside each row so the reader can see who is affected before choosing.\n *\n * `rewritable` is separate from the spec itself because \"understood well enough\n * to report\" and \"understood well enough to edit\" are different bars. A pub\n * dependency expressed as a `git:` map, or an npm one pointing at a tarball\n * URL, is worth showing and must never be rewritten.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface DependencySite {\n /** The package name, as the manifest spells it. */\n name: string\n /** Which ecosystem's manifest this came from. */\n ecosystem: Ecosystem\n /** Workspace-relative project directory, or {@link ROOT_LABEL}. */\n project: string\n /** Absolute path to the manifest declaring it. */\n manifestPath: string\n /** Short section label for display: `dep`, `devDep`, `peerDep`, … */\n section: string\n /** The declared spec, verbatim. */\n spec: string\n /** Whether {@link rewriteSpec} can safely edit this entry. */\n rewritable: boolean\n}\n\n/**\n * Every declaration in the workspace, keyed by package name.\n *\n * @remarks\n * Keyed by name rather than by project because both commands ask the same\n * cross-cutting question — \"who declares this, and do they agree?\" — which a\n * project-keyed structure answers only after a second pass.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type Inventory = Map<string, DependencySite[]>\n\n/** Where each ecosystem's per-project manifests live, relative to the root. */\nconst MANIFEST_GLOBS: Record<Ecosystem, string[]> = {\n // `libs/*` carries private internal libs; `packages/*` the publishable ones.\n npm: ['apps/*/package.json', 'libs/*/package.json', 'packages/*/package.json'],\n // Matches PYTHON_PROJECT_DIRS in commands/add/python.ts.\n pip: ['apps/*/pyproject.toml', 'python-packages/*/pyproject.toml', 'libs/*/pyproject.toml'],\n pub: ['apps/*/pubspec.yaml', 'packages/*/pubspec.yaml', 'libs/*/pubspec.yaml'],\n // Unlike every other ecosystem, the filename itself varies (the project's own\n // PascalCase identity, not a fixed name) — the same three roots\n // PACK_APPS_GUARD and add/csharp.ts already scan.\n nuget: ['apps/*/*.csproj', 'packages/*/*.csproj', 'libs/*/*.csproj'],\n // Single root module by design — there are no per-project Go manifests.\n go: [],\n}\n\n/**\n * The section label for a peer dependency.\n *\n * @remarks\n * Singled out because a peer range is a **compatibility declaration**, not a\n * version choice, and neither command may rewrite one. `@mnci/nx-python-pip`\n * peers `@nx/devkit` at `>=21.0.0` deliberately, so it loads on Nx 21, 22 and\n * 23 alike; converging that on the 23.x this repo resolves would silently drop\n * support for two majors of consumers. Found by running `mnci sync --check`\n * against this repo, where five of six findings were exactly that mistake.\n */\nexport const PEER_SECTION = 'peerDep'\n\n/** npm manifest blocks, mapped to the short label shown in a report. */\nconst NPM_SECTIONS: Record<string, string> = {\n dependencies: 'dep',\n devDependencies: 'devDep',\n peerDependencies: 'peerDep',\n optionalDependencies: 'optionalDep',\n}\n\n/**\n * Normalises a `globSync` result to forward slashes.\n *\n * @remarks\n * Load-bearing on Windows, where `globSync` returns `packages\\name\\file`. The\n * project label derived from these paths is both a display string and the key\n * a user matches against, so a backslash here would make the same project read\n * as two different ones between platforms. This mirrors `toPosix` in\n * `commands/doctor.ts`, whose docblock records the bug that motivated it.\n *\n * @param path - A workspace-relative path from `globSync`.\n * @returns The same path with forward slashes.\n * @throws Never - performs a pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function toPosix (path: string): string {\n return path.replaceAll('\\\\', '/')\n}\n\n/**\n * The project directory a manifest belongs to.\n *\n * @param manifestRelativePath - Workspace-relative path to the manifest.\n * @returns The directory holding it, forward-slashed.\n * @throws Never - performs string manipulation only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction projectOf (manifestRelativePath: string): string {\n const posix = toPosix(manifestRelativePath)\n const lastSlash = posix.lastIndexOf('/')\n\n return lastSlash === -1 ? ROOT_LABEL : posix.slice(0, lastSlash)\n}\n\n/**\n * Whether a workspace contains any project of the given ecosystem.\n *\n * @remarks\n * Every command here reports an absent ecosystem as a loud SKIPPED rather than\n * silently omitting it — the pattern the e2e already uses for Go and Flutter,\n * adopted for the same reason: an ecosystem that is quietly dropped stays\n * uncovered for months without anyone noticing.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem to test for.\n * @returns `true` when at least one manifest of that ecosystem exists.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function hasEcosystem (workspaceRoot: string, ecosystem: Ecosystem): boolean {\n // Go is the only one with no per-project manifest to fall back on.\n if (ecosystem === 'go') {\n return fileExists(join(workspaceRoot, 'go.mod'))\n }\n // NuGet has no root-level marker at all, unlike the other three: nuget.config\n // is publish auth, not a dependency declaration, and mnci writes it only once\n // a csharp-lib exists anyway. The per-project glob is the only signal.\n if (ecosystem === 'nuget') {\n return globSync(MANIFEST_GLOBS.nuget, { cwd: workspaceRoot }).length > 0\n }\n const rootMarker: Record<Exclude<Ecosystem, 'go' | 'nuget'>, string> = {\n npm: 'package.json',\n pip: 'requirements-dev.txt',\n pub: 'pubspec.yaml',\n }\n\n // Either marker counts. A root file alone is the usual case, but a workspace\n // mid-migration can have projects before it has the root file — reporting\n // nothing there would be the silent-skip failure this whole feature avoids.\n return (\n fileExists(join(workspaceRoot, rootMarker[ecosystem])) ||\n globSync(MANIFEST_GLOBS[ecosystem], { cwd: workspaceRoot }).length > 0\n )\n}\n\n/**\n * Reads every npm declaration in the workspace.\n *\n * @remarks\n * Includes the root manifest, because the root is exactly where a shared dev\n * tool legitimately lives and where a runtime dependency legitimately does not\n * — both facts a report is useless without.\n *\n * A spec that is not a plain version range (a `file:`/`git+`/`https:` target,\n * npm's `npm:pkg@range` alias, or a `workspace:` protocol) is recorded with\n * `rewritable: false`. The alias form matters here specifically: mnci's own\n * root manifest pins the dual TypeScript compiler that way\n * (`typescript: npm:@typescript/typescript6@^6.0.2`), and rewriting it as a\n * plain version would silently swap the compiler.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every npm declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectNpm (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n const relativePaths = ['package.json', ...globSync(MANIFEST_GLOBS.npm, { cwd: workspaceRoot })]\n\n for (const relativePath of relativePaths) {\n const manifestPath = join(workspaceRoot, relativePath)\n try {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n sites.push(...npmManifestSites(manifest, relativePath, manifestPath))\n } catch {\n // A malformed manifest is the workspace's problem to fix, not a reason\n // for the whole report to fail.\n }\n }\n\n return sites\n}\n\n/**\n * Reads every dependency block of one npm manifest.\n *\n * @remarks\n * Split out of {@link collectNpm} so the block loop is not nested inside the\n * manifest loop — three levels of iteration in one function is where a\n * `continue` stops obviously belonging to the loop the reader thinks it does.\n *\n * @param manifest - The parsed manifest.\n * @param relativePath - Its workspace-relative path, for the project label.\n * @param manifestPath - Its absolute path, for a later rewrite.\n * @returns Every declaration in the manifest.\n * @throws Never - a manifest with no dependency block yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmManifestSites (\n manifest: Record<string, unknown>,\n relativePath: string,\n manifestPath: string,\n): DependencySite[] {\n const sites: DependencySite[] = []\n const project = projectOf(relativePath)\n\n for (const [block, section] of Object.entries(NPM_SECTIONS)) {\n const declared = (manifest[block] ?? {}) as Record<string, string>\n for (const [name, spec] of Object.entries(declared)) {\n sites.push({\n name,\n ecosystem: 'npm',\n project,\n manifestPath,\n section,\n spec,\n // Two independent reasons to refuse a rewrite. A `file:`/`git+`/\n // `https:` target, a `workspace:` protocol or npm's `npm:pkg@range`\n // alias is a shape this cannot safely edit; a peer range is a shape it\n // could edit and must not (see PEER_SECTION).\n rewritable: section !== PEER_SECTION && /^[\\^~>=<]*\\d/.test(spec.trim()),\n })\n }\n }\n\n return sites\n}\n\n/**\n * Splits a PEP 508 requirement into its package name and version spec.\n *\n * @remarks\n * Regex rather than a TOML/PEP 508 parser, following the precedent set by\n * `parseVendorEntries` in `@mnci/nx-python-pip` — pip has no manifest parser in\n * the standard library reachable from Node, and the shapes mnci generates are\n * narrow. Extras (`requests[socks]>=2`) are stripped from the name and folded\n * into the spec, so an extras-carrying entry is reported but never rewritten.\n *\n * @param requirement - One requirement line or array entry.\n * @returns The name and spec, or `undefined` when the line declares nothing.\n * @throws Never - an unrecognised line yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function parseRequirement (\n requirement: string,\n): { name: string, spec: string, rewritable: boolean } | undefined {\n const comment = requirement.indexOf('#')\n const text = (comment === -1 ? requirement : requirement.slice(0, comment)).trim()\n if (!text || text.startsWith('-')) {\n return undefined\n }\n // Anchored, greedy, and followed by nothing — so it cannot backtrack. The\n // rest of the line is sliced off rather than matched, which is what keeps\n // this linear on a pathological input.\n const name = /^[a-z0-9][\\w.-]*/i.exec(text)?.[0]\n if (!name) {\n return undefined\n }\n const spec = text.slice(name.length).trim()\n\n return {\n name,\n spec,\n // Extras, environment markers and compound ranges are reportable only.\n rewritable: !spec.startsWith('[') && !spec.includes(';') && !spec.includes(','),\n }\n}\n\n/**\n * Reads every pip declaration in the workspace.\n *\n * @remarks\n * Three manifest shapes, and the split between them IS the root/project policy:\n * `requirements-dev.txt` at the root holds the shared toolchain and nothing\n * else, each `pyproject.toml` holds one project's runtime dependencies, and a\n * Python function app has no `pyproject.toml` at all — only its own\n * `requirements.txt`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every pip declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectPip (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const push = (relativePath: string, section: string, requirement: string): void => {\n const parsed = parseRequirement(requirement)\n if (!parsed) {\n return\n }\n sites.push({\n name: parsed.name,\n ecosystem: 'pip',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section,\n spec: parsed.spec,\n rewritable: parsed.rewritable && parsed.spec !== '',\n })\n }\n\n const pyprojectPaths = globSync(MANIFEST_GLOBS.pip, { cwd: workspaceRoot })\n for (const relativePath of pyprojectPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n const declared = pyprojectDependencies(content)\n for (const entry of declared) {\n push(relativePath, 'dep', entry)\n }\n }\n\n const requirementsPaths = [\n 'requirements-dev.txt',\n ...globSync('apps/*/requirements.txt', { cwd: workspaceRoot }),\n ]\n for (const relativePath of requirementsPaths) {\n if (!fileExists(join(workspaceRoot, relativePath))) {\n continue\n }\n // The root file is the shared toolchain; an app's own file is its runtime.\n const section = relativePath === 'requirements-dev.txt' ? 'devDep' : 'dep'\n const lines = readFileSync(join(workspaceRoot, relativePath), 'utf8').split(/\\r?\\n/)\n for (const line of lines) {\n push(relativePath, section, line)\n }\n }\n\n return sites\n}\n\n/**\n * Extracts the entries of a `pyproject.toml`'s `[project] dependencies` array.\n *\n * @remarks\n * Scoped to the `[project]` table deliberately: a `pyproject.toml` can carry a\n * `dependencies` key under other tables (build backends and tool sections both\n * do), and a whole-file regex would sweep those in as runtime requirements.\n *\n * @param content - The file's text.\n * @returns The raw string entries, unquoted.\n * @throws Never - a file with no such array yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pyprojectDependencies (content: string): string[] {\n const entries: string[] = []\n let inProjectTable = false\n let inArray = false\n\n for (const line of content.split(/\\r?\\n/)) {\n const text = line.trim()\n if (text.startsWith('[')) {\n // A new table ends both the scope and any array left open inside it.\n inProjectTable = text === '[project]'\n inArray = false\n continue\n }\n if (!inProjectTable) {\n continue\n }\n if (!inArray && /^dependencies\\s*=\\s*\\[/.test(text)) {\n inArray = true\n }\n if (!inArray) {\n continue\n }\n for (const quoted of text.matchAll(/\"([^\"]*)\"|'([^']*)'/g)) {\n entries.push(quoted[1] ?? quoted[2])\n }\n if (text.includes(']')) {\n inArray = false\n }\n }\n\n return entries\n}\n\n/**\n * Reads every pub declaration in the workspace.\n *\n * @remarks\n * The root `pubspec.yaml` is deliberately NOT read for dependencies: in a Dart\n * pub workspace it carries only the member list and an SDK floor, and mnci\n * writes it with no `dependencies:` block at all. Reading it would invent a\n * root/project conflict that cannot exist.\n *\n * A dependency whose value is a nested map — `git:`, `path:`, `hosted:`, or the\n * bare `flutter:` SDK reference every generated project carries — is recorded\n * with an empty spec and `rewritable: false`. Those are shown in a report and\n * never edited.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every pub declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectPub (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const pubspecPaths = globSync(MANIFEST_GLOBS.pub, { cwd: workspaceRoot })\n for (const relativePath of pubspecPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n for (const block of ['dependencies', 'dev_dependencies'] as const) {\n for (const entry of pubspecBlockEntries(content, block)) {\n sites.push({\n name: entry.name,\n ecosystem: 'pub',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section: block === 'dependencies' ? 'dep' : 'devDep',\n spec: entry.spec,\n rewritable: entry.spec !== '',\n })\n }\n }\n }\n\n return sites\n}\n\n/**\n * Reads one top-level block of a `pubspec.yaml` as name/spec pairs.\n *\n * @remarks\n * A line scanner rather than a YAML parser, the same technique\n * `@mnci/nx-flutter`'s `pubspec.ts` and `workspace.ts` already use to edit\n * these files. It reads only the block's direct children — a nested map\n * (`git:` with an indented `url:` under it) yields an empty spec, which is the\n * signal never to rewrite it.\n *\n * Handles CRLF explicitly. `flutter create` writes CRLF on Windows, and a\n * pattern that assumed LF is precisely how the Flutter internal-dependency\n * injection silently did nothing for months.\n *\n * @param content - The pubspec text.\n * @param block - The top-level key to read.\n * @returns The direct children of that block.\n * @throws Never - an absent block yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function pubspecBlockEntries (\n content: string,\n block: string,\n): Array<{ name: string, spec: string }> {\n const lines = content.split(/\\r?\\n/)\n const start = lines.findIndex(line => line.trimEnd() === `${block}:`)\n if (start === -1) {\n return []\n }\n\n const entries: Array<{ name: string, spec: string }> = []\n let indent: number | undefined\n\n const body = lines.slice(start + 1)\n for (const line of body) {\n if (line.trim() === '' || line.trimStart().startsWith('#')) {\n continue\n }\n const lineIndent = line.length - line.trimStart().length\n if (lineIndent === 0) {\n break\n }\n indent ??= lineIndent\n if (lineIndent > indent) {\n // A nested map's contents — belongs to the entry above, not a new one.\n continue\n }\n if (lineIndent < indent) {\n break\n }\n // Split on the first colon rather than matching the whole line: a value can\n // contain anything, and a pattern that tried to describe it is exactly the\n // shape that backtracks badly on a long line.\n const colon = line.indexOf(':')\n if (colon === -1) {\n continue\n }\n const key = line.slice(0, colon).trim()\n if (/^[a-z_][\\w-]*$/i.test(key)) {\n entries.push({ name: key, spec: stripQuotes(line.slice(colon + 1).trim()) })\n }\n }\n\n return entries\n}\n\n/**\n * Removes a single layer of surrounding quotes.\n *\n * @param value - The raw scalar.\n * @returns The value without its surrounding quotes.\n * @throws Never - performs string manipulation only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction stripQuotes (value: string): string {\n return /^(['\"]).*\\1$/.test(value) ? value.slice(1, -1) : value\n}\n\n/**\n * Reads every NuGet declaration in the workspace.\n *\n * @remarks\n * The `.csproj` itself is never read for the project's OWN identity or\n * version — same boundary `collectNpm`/`collectPub`/`collectPip` already keep\n * for `package.json`'s `name`/`version` fields — only `<PackageReference>`\n * entries are external dependencies.\n *\n * One section only (`dep`): SDK-style `.csproj` has no first-class\n * dependencies-vs-devDependencies split the way npm/pip do — a test-only\n * package is just a `PackageReference` in whichever project needs it (often\n * a dedicated `*.Tests` project), not a separate block on a shared manifest.\n * Reporting an invented split would be less honest than reporting none.\n *\n * An internal C# library is never expressed as a `PackageReference` at all —\n * it is a `<ProjectReference>`, a structurally different element this never\n * scans — so unlike npm there is no \"is this actually a workspace project\"\n * ambiguity to resolve for a name found here.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every NuGet declaration found.\n * @throws Never - an unreadable manifest is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectNuget (workspaceRoot: string): DependencySite[] {\n const sites: DependencySite[] = []\n\n const csprojPaths = globSync(MANIFEST_GLOBS.nuget, { cwd: workspaceRoot })\n for (const relativePath of csprojPaths) {\n let content: string\n try {\n content = readFileSync(join(workspaceRoot, relativePath), 'utf8')\n } catch {\n continue\n }\n for (const entry of csprojPackageReferences(content)) {\n sites.push({\n name: entry.name,\n ecosystem: 'nuget',\n project: projectOf(relativePath),\n manifestPath: join(workspaceRoot, relativePath),\n section: 'dep',\n spec: entry.spec,\n // A plain SemVer string is rewritable; an MSBuild property reference\n // ($(SharedVersion)) or an interval-notation range ([1.0,2.0)) is\n // reported only — neither is a version this can safely overwrite.\n rewritable: /^\\d/.test(entry.spec),\n })\n }\n }\n\n return sites\n}\n\n/**\n * Extracts every `<PackageReference Include=\"…\" Version=\"…\" />` from a\n * `.csproj`'s text.\n *\n * @remarks\n * Two passes, not one combined regex — the same discipline\n * `pubspecBlockEntries` already applies (find the block, then read its\n * children): the outer pattern finds a bounded `<PackageReference … />` tag\n * (anchored on `/>` so it cannot run past the tag it started in), and a\n * second pass reads `Include`/`Version` out of the captured attribute text in\n * either order — `dotnet add package` always writes `Include` first, but\n * nothing enforces that once a human edits the file by hand.\n *\n * Deliberately narrow: the nested-element form\n * (`<PackageReference Include=\"X\"><Version>1.0</Version></PackageReference>`)\n * is valid MSBuild but is not what `dotnet new`/`dotnet add package` emit —\n * verified against every `.csproj` this CLI itself generates — so it is left\n * unrecognised rather than guessed at.\n *\n * @param content - The `.csproj` text.\n * @returns Every package reference found, name and version spec.\n * @throws Never - a file with no matching tag yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function csprojPackageReferences (\n content: string,\n): Array<{ name: string, spec: string }> {\n const entries: Array<{ name: string, spec: string }> = []\n\n for (const tag of content.matchAll(/<PackageReference([^>]*)\\/>/g)) {\n const attributes = tag[1]\n const name = /\\bInclude\\s*=\\s*\"([^\"]*)\"/.exec(attributes)?.[1]\n const spec = /\\bVersion\\s*=\\s*\"([^\"]*)\"/.exec(attributes)?.[1]\n if (name && spec) {\n entries.push({ name, spec })\n }\n }\n\n return entries\n}\n\n/**\n * Reads the root `go.mod`'s require block.\n *\n * @remarks\n * Read-only, and there is exactly one manifest by design: mnci's Go layout is a\n * single root module, so a package cannot have two versions in the workspace\n * and there is nothing for `mnci sync` to reconcile. Collected anyway so\n * `mnci up` can report Go modules alongside everything else.\n *\n * Indirect requirements are labelled rather than dropped — `go mod tidy` owns\n * them, so offering one for a direct upgrade would be misleading.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Every module the root `go.mod` requires.\n * @throws Never - an absent or unreadable `go.mod` yields an empty list.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction collectGo (workspaceRoot: string): DependencySite[] {\n const manifestPath = join(workspaceRoot, 'go.mod')\n if (!fileExists(manifestPath)) {\n return []\n }\n let content: string\n try {\n content = readFileSync(manifestPath, 'utf8')\n } catch {\n return []\n }\n\n const sites: DependencySite[] = Array.from(content.matchAll(\n /^\\s*(?:require\\s+)?([\\w.~-]+(?:\\/[\\w.~-]+)+)\\s+(v\\S+)(\\s*\\/\\/\\s*indirect)?/gm,\n ), match => ({\n name: match[1],\n ecosystem: 'go',\n project: ROOT_LABEL,\n manifestPath,\n section: match[3] ? 'indirect' : 'module',\n spec: match[2],\n rewritable: false,\n }))\n\n return sites\n}\n\n/**\n * Builds the workspace-wide dependency inventory.\n *\n * @remarks\n * Pure with respect to the workspace — it only reads — so both `mnci sync` and\n * `mnci up` can build it, and tests can assert against it, without any command\n * having run.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to read. Defaults to all four.\n * @returns Every declaration, keyed by package name.\n * @throws Never - unreadable manifests are skipped individually.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectInventory (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[] = ECOSYSTEMS,\n): Inventory {\n const readers: Record<Ecosystem, (root: string) => DependencySite[]> = {\n npm: collectNpm,\n pip: collectPip,\n pub: collectPub,\n nuget: collectNuget,\n go: collectGo,\n }\n\n const inventory: Inventory = new Map()\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n continue\n }\n const sites = readers[ecosystem](workspaceRoot)\n for (const site of sites) {\n const existing = inventory.get(site.name)\n if (existing) {\n existing.push(site)\n } else {\n inventory.set(site.name, [site])\n }\n }\n }\n\n return inventory\n}\n\n/**\n * Rewrites one declaration's spec in place, leaving the rest of the file alone.\n *\n * @remarks\n * Minimal edits rather than parse-and-re-emit for the two text formats: a\n * `pyproject.toml` and a `pubspec.yaml` both carry comments mnci wrote and the\n * user may have added, and round-tripping either through a serialiser would\n * silently discard them. npm manifests are re-emitted through `toJson`, which\n * is what every other mnci write already does and is lossless because JSON has\n * no comments.\n *\n * A site with `rewritable: false` is refused rather than best-effort edited.\n *\n * @param site - The declaration to change.\n * @param spec - The new spec to write.\n * @returns `true` when the file was changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rewriteSpec (site: DependencySite, spec: string): boolean {\n if (!site.rewritable || spec === site.spec) {\n return false\n }\n const { name } = site\n\n if (site.ecosystem === 'npm') {\n const manifest = readJson<Record<string, Record<string, string>>>(site.manifestPath)\n const block = Object.entries(NPM_SECTIONS).find(([, label]) => label === site.section)?.[0]\n if (!block || manifest[block]?.[name] === undefined) {\n return false\n }\n manifest[block][name] = spec\n writeFileEnsured(site.manifestPath, toJson(manifest))\n\n return true\n }\n\n const before = readFileSync(site.manifestPath, 'utf8')\n const after = ((): string => {\n switch (site.ecosystem) {\n case 'pip': {\n return replacePipSpec(before, name, spec)\n }\n case 'pub': {\n return replacePubSpec(before, name, spec)\n }\n case 'nuget': {\n return replaceNugetSpec(before, name, spec)\n }\n // 'go' entries are never rewritable (see collectGo), so this is\n // unreachable in practice — returning the text unchanged is the safe\n // no-op if that invariant is ever broken, rather than guessing at a\n // rewrite for a format this branch was never meant to handle.\n default: {\n return before\n }\n }\n })()\n if (after === before) {\n return false\n }\n writeFileEnsured(site.manifestPath, after)\n\n return true\n}\n\n/**\n * Replaces a requirement's spec in a `pyproject.toml` or `requirements.txt`.\n *\n * @remarks\n * Anchored on the package name so only that entry moves. The name is escaped\n * before it reaches the pattern: a package name can legally contain `.` and\n * `-`, and an unescaped `.` would match any character, which is how a rewrite\n * of `foo.bar` could land on `fooxbar`.\n *\n * @param content - The file's current text.\n * @param name - The package name.\n * @param spec - The new spec.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replacePipSpec (content: string, name: string, spec: string): string {\n const escaped = escapeForRegex(name)\n\n return content.replaceAll(\n new RegExp(String.raw`(^|[\"'\\s])(${escaped})\\s*(?:[<>=!~^][^\"'\\n,]*)?(?=[\"'\\n]|$)`, 'gm'),\n (match, lead: string, matched: string) => `${lead}${matched}${spec}`,\n )\n}\n\n/**\n * Replaces a dependency's constraint in a `pubspec.yaml`.\n *\n * @remarks\n * Rewrites only a line whose value is a scalar — a line ending in a bare `:`\n * introduces a nested map and is left untouched, which is the write-side half\n * of the `rewritable: false` contract {@link pubspecBlockEntries} sets.\n *\n * @param content - The file's current text.\n * @param name - The dependency name.\n * @param spec - The new constraint.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replacePubSpec (content: string, name: string, spec: string): string {\n return content.replaceAll(\n new RegExp(String.raw`^(\\s+${escapeForRegex(name)}:[^\\S\\n]*)\\S[^\\n]*$`, 'gm'),\n (match, lead: string) => `${lead}${spec}`,\n )\n}\n\n/**\n * Replaces a `<PackageReference>`'s `Version` attribute in a `.csproj`.\n *\n * @remarks\n * Anchored on `Include=\"<name>\"` first, then rewrites only the `Version=`\n * attribute of that SAME tag — not the next occurrence of `Version=`\n * anywhere in the file, which a simpler name-then-version scan could land on\n * if two `PackageReference` tags for different packages sit close together.\n * The name is escaped for the same reason {@link replacePipSpec} escapes\n * one: a NuGet package ID can contain `.` (`Microsoft.Extensions.Http`), and\n * an unescaped `.` in a regex matches any character.\n *\n * @param content - The file's current text.\n * @param name - The package ID, exactly as `Include=` spells it.\n * @param spec - The new version.\n * @returns The updated text, unchanged when no entry matched.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function replaceNugetSpec (content: string, name: string, spec: string): string {\n const escaped = escapeForRegex(name)\n const includePattern = new RegExp(String.raw`\\bInclude\\s*=\\s*\"${escaped}\"`)\n\n // Same two-pass shape as csprojPackageReferences: find the bounded tag\n // first, then edit only inside it — so a Version= attribute belonging to a\n // DIFFERENT package's tag is never touched, and every replacement value is\n // a function's return, never a `$1`-style template a version string could\n // collide with.\n return content.replaceAll(/<PackageReference([^>]*)\\/>/g, (tag: string, attributes: string) => {\n if (!includePattern.test(attributes)) {\n return tag\n }\n\n return tag.replace(/\\bVersion\\s*=\\s*\"[^\"]*\"/, () => `Version=\"${spec}\"`)\n })\n}\n\n/**\n * Escapes a string for literal use inside a regular expression.\n *\n * @param value - The literal text.\n * @returns The escaped text.\n * @throws Never - performs string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction escapeForRegex (value: string): string {\n return value.replaceAll(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`)\n}\n\n/**\n * Whether a package name belongs to a project inside this workspace.\n *\n * @remarks\n * An internal `@scope/lib` is symlinked by npm and versioned by `nx release`;\n * its declared range is deliberately loose (often `*`) so both the workspace\n * link and the tagged version satisfy it. Neither command may touch one:\n * converging it would break on the next release, and offering to \"upgrade\" it\n * to whatever is published would replace the local link with a registry copy.\n *\n * npm only. A pip internal lib is vendored and a pub one is a workspace member,\n * so neither is declared under a name this could resolve to a directory.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The package name as declared.\n * @returns `true` when the package is a project in this workspace.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isWorkspaceProject (workspaceRoot: string, name: string): boolean {\n const short = name.includes('/') ? name.split('/').at(-1) : name\n\n return ['apps', 'libs', 'packages'].some(directory =>\n fileExists(join(workspaceRoot, directory, short ?? name, 'package.json')),\n )\n}\n\n/**\n * Whether a manifest key resolves to a differently-named package.\n *\n * @remarks\n * npm's `npm:pkg@range` alias lets a manifest install one package under another\n * name, and mnci's own root manifest does exactly that for the dual TypeScript\n * compiler: `typescript` resolves to `@typescript/typescript6`. Asking the\n * registry about the KEY then answers about a different package entirely — the\n * first `mnci up` run on this repo offered \"typescript 6.0.2 to 7.0.2\", which\n * is real TypeScript's version, not the aliased package's.\n *\n * Detected from the installed tree rather than by pattern-matching the spec,\n * because that is the fact that actually matters: whatever the spec says, the\n * name on disk is what a registry query would have to match.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param name - The package name as declared.\n * @returns `true` when the installed package reports a different name.\n * @throws Never - an uninstalled package reads as not aliased.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isAliasedInstall (workspaceRoot: string, name: string): boolean {\n try {\n const installed = readJson<{ name?: string }>(\n join(workspaceRoot, 'node_modules', name, 'package.json'),\n )\n\n return installed.name !== undefined && installed.name !== name\n } catch {\n return false\n }\n}\n\n/**\n * The version of a package actually resolved in the workspace.\n *\n * @remarks\n * This is what `mnci sync` reconciles towards, and the choice is deliberate:\n * `@nx/dependency-checks` pins a drifted range to the *installed* version when\n * it auto-fixes, so resolving against the same source is what makes the lint\n * rule and this command agree instead of fighting over the same manifest.\n *\n * npm reads `node_modules`, pub reads the single root `pubspec.lock`, and pip\n * has to ask the interpreter — plain pip has no lockfile, which is why its\n * answer is the weakest of the three and why an absent one falls back to the\n * highest declared range rather than failing.\n *\n * NuGet has no single answer to give: each `.csproj` restores into its OWN\n * `obj/project.assets.json`, so the same package could genuinely resolve to\n * different versions in different projects — unlike pub's one shared\n * workspace lockfile. Reporting one of those arbitrarily would be a wrong\n * answer dressed as a precise one, so this returns `undefined`, the same\n * \"nothing to resolve here\" contract Go already has for a different reason.\n * An absent resolved version falls back to the highest declared range, same\n * as pip.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - Which ecosystem the package belongs to.\n * @param name - The package name.\n * @returns The resolved version, or `undefined` when nothing is installed.\n * @throws Never - every lookup failure yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function resolvedVersion (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n name: string,\n): string | undefined {\n if (ecosystem === 'npm') {\n try {\n return readJson<{ version?: string }>(\n join(workspaceRoot, 'node_modules', name, 'package.json'),\n ).version\n } catch {\n return undefined\n }\n }\n\n if (ecosystem === 'pub') {\n const lockPath = join(workspaceRoot, 'pubspec.lock')\n if (!fileExists(lockPath)) {\n return undefined\n }\n try {\n const match = new RegExp(\n String.raw`^\\s{2}${escapeForRegex(name)}:[^]*?^\\s+version:\\s*[\"']?([^\"'\\s]+)`,\n 'm',\n ).exec(readFileSync(lockPath, 'utf8'))\n\n return match?.[1]\n } catch {\n return undefined\n }\n }\n\n if (ecosystem === 'pip') {\n const python = process.platform === 'win32' ? 'python' : 'python3'\n const result = runCapture(python, ['-m', 'pip', 'show', name], workspaceRoot)\n if (result.status !== 0) {\n return undefined\n }\n\n return /^Version:\\s*(\\S+)/m.exec(result.stdout)?.[1]\n }\n\n // nuget: no single workspace-wide resolved state to read (see the remarks\n // above). go: the root go.mod IS the resolved state — one module, one\n // version — so there is nothing further to resolve either.\n return undefined\n}\n","/**\n * How large an update is, in the buckets `npm-check` groups its output by.\n *\n * @remarks\n * `non-semver` is not \"unparseable\" — it is `npm-check`'s own fourth bucket,\n * *\"Versions less than 1.0.0, caution\"*. A `0.x` release makes no compatibility\n * promise, so a `0.1.15` to `0.3.4` bump is neither safely-minor nor\n * loudly-major; grouping it separately is what tells the reader to look. A\n * version this module cannot parse at all lands here too, for the same reason:\n * it deserves attention rather than a confident label.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type UpdateKind = 'patch' | 'minor' | 'major' | 'non-semver'\n\n/**\n * The order sections are printed in, matching `npm-check -u`.\n *\n * @remarks\n * Also the sort key for a report: rows are ordered by this array's index, so\n * the least risky upgrades appear first and the reader meets the safe bulk\n * before the majors.\n */\nexport const UPDATE_KINDS: readonly UpdateKind[] = ['patch', 'minor', 'major', 'non-semver']\n\n/**\n * The heading and blurb `npm-check` prints above each section.\n *\n * @remarks\n * Reproduced verbatim rather than reworded. `mnci up` is meant to be\n * recognisable to anyone who has run `npm-check -u`, and the wording is the\n * cheapest half of that.\n */\nexport const UPDATE_KIND_LABELS: Record<UpdateKind, { title: string, blurb: string }> = {\n 'patch': { title: 'Patch Update', blurb: 'Backwards-compatible bug fixes.' },\n 'minor': { title: 'Minor Update', blurb: 'New backwards-compatible features.' },\n 'major': { title: 'Major Update', blurb: 'Potentially breaking API changes. Use caution.' },\n 'non-semver': { title: 'Non-Semver', blurb: 'Versions less than 1.0.0, caution.' },\n}\n\n/**\n * A parsed release triple plus any prerelease suffix.\n *\n * @remarks\n * Build metadata is deliberately absent: nothing here compares it, and semver\n * says it must be ignored when ordering versions anyway.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface ParsedVersion {\n /** The major segment. */\n major: number\n /** The minor segment. */\n minor: number\n /** The patch segment. */\n patch: number\n /** The prerelease suffix without its leading dash, or `undefined`. */\n prerelease?: string\n}\n\n/**\n * The range operators this module understands, longest first.\n *\n * @remarks\n * Order matters: `>=` has to be tested before `>`, or every `>=1.2.3` would be\n * read as `>` with a version of `=1.2.3`. The empty string is not listed — it\n * is the fallback when nothing else matches, i.e. an exact pin.\n */\nconst RANGE_OPERATORS = ['>=', '<=', '^', '~', '>', '<', '='] as const\n\n/**\n * Parses a bare version string into its numeric triple.\n *\n * @remarks\n * Deliberately narrower than full semver: build metadata is dropped and a\n * prerelease is kept only as an opaque string, because the only decisions this\n * module drives are \"is it newer\" and \"which bucket\". Anything with fewer than\n * three numeric segments is rejected rather than defaulted, so a calendar\n * version or a git ref reports as unparseable instead of silently comparing as\n * zero.\n *\n * @param raw - The version text, e.g. `1.2.3` or `2.0.0-beta.1`.\n * @returns The parsed version, or `undefined` when it is not a numeric triple.\n * @throws Never - malformed input yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function parseVersion (raw: string): ParsedVersion | undefined {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.-]+))?/.exec(raw.trim())\n if (!match) {\n return undefined\n }\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n prerelease: match[4],\n }\n}\n\n/**\n * Reads the leading range operator off a dependency spec.\n *\n * @remarks\n * Returned so a rewrite can preserve whatever the project already chose: a\n * package pinned exactly stays pinned, a caret range stays a caret range. Every\n * write path in this feature routes through here rather than assuming a caret,\n * because silently widening an exact pin is a behaviour change the user never\n * asked for.\n *\n * @param spec - The declared dependency spec.\n * @returns The operator, or an empty string for an exact pin.\n * @throws Never - performs string matching only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rangeOperator (spec: string): string {\n const trimmed = spec.trim()\n\n return RANGE_OPERATORS.find(operator => trimmed.startsWith(operator)) ?? ''\n}\n\n/**\n * Strips a range operator off a dependency spec, leaving the bare version.\n *\n * @remarks\n * Handles the single-constraint specs that actually appear in a manifest mnci\n * generates (`^1.2.3`, `~1.2`, `2.0.0`). A compound range, a URL, a git ref, a\n * `workspace:` alias or npm's `npm:pkg@range` form returns `undefined`, which\n * every caller treats as \"leave this one alone\" — rewriting a spec whose shape\n * is not understood is how a manifest gets corrupted.\n *\n * @param spec - The declared dependency spec.\n * @returns The bare version text, or `undefined` when the spec is not a single\n * operator-plus-version constraint.\n * @throws Never - an unrecognised spec yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function specVersion (spec: string): string | undefined {\n const bare = spec.trim().slice(rangeOperator(spec).length).trim()\n\n return /^\\d+\\.\\d+(?:\\.\\d+)?(?:-[\\w.-]+)?$/.test(bare) ? bare : undefined\n}\n\n/**\n * Compares two version strings.\n *\n * @remarks\n * A release always sorts above its own prereleases (`1.2.3` beats\n * `1.2.3-beta.1`), which is semver's rule; two prereleases of the same triple\n * are compared as plain strings, which is narrower than semver's\n * segment-by-segment rule but only ever decides which of two prereleases is\n * offered, never whether a release is newer than one.\n *\n * @param left - The first version.\n * @param right - The second version.\n * @returns A negative number when `left` is older, positive when newer, `0`\n * when equal or when either side is unparseable.\n * @throws Never - unparseable input compares equal.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function compareVersions (left: string, right: string): number {\n const a = parseVersion(left)\n const b = parseVersion(right)\n if (!a || !b) {\n return 0\n }\n if (a.major !== b.major) {\n return a.major - b.major\n }\n if (a.minor !== b.minor) {\n return a.minor - b.minor\n }\n if (a.patch !== b.patch) {\n return a.patch - b.patch\n }\n if (a.prerelease === b.prerelease) {\n return 0\n }\n if (!a.prerelease) {\n return 1\n }\n if (!b.prerelease) {\n return -1\n }\n\n return a.prerelease < b.prerelease ? -1 : 1\n}\n\n/**\n * Whether `candidate` is strictly newer than `current`.\n *\n * @remarks\n * A thin reading of {@link compareVersions}, named so call sites read as the\n * question they are actually asking.\n *\n * @param candidate - The version offered.\n * @param current - The version in hand.\n * @returns `true` when `candidate` is strictly newer.\n * @throws Never - unparseable input yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function isNewer (candidate: string, current: string): boolean {\n return compareVersions(candidate, current) > 0\n}\n\n/**\n * Buckets an upgrade the way `npm-check` groups its report.\n *\n * @remarks\n * The `0.x` test is applied to the CURRENT version, not the latest: a package\n * the workspace is on `0.1.15` of is unpredictable to upgrade regardless of\n * where it is going, whereas one already past `1.0.0` has made a promise the\n * major/minor/patch split can be trusted to describe.\n *\n * @param current - The version in use.\n * @param latest - The version available.\n * @returns Which section the upgrade belongs in.\n * @throws Never - unparseable input yields `non-semver`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function classify (current: string, latest: string): UpdateKind {\n const from = parseVersion(current)\n const to = parseVersion(latest)\n if (!from || !to || from.major === 0) {\n return 'non-semver'\n }\n if (from.major !== to.major) {\n return 'major'\n }\n\n return from.minor === to.minor ? 'patch' : 'minor'\n}\n\n/**\n * Picks the highest of a set of declared specs.\n *\n * @remarks\n * The fallback `mnci sync` uses when nothing is installed to resolve against.\n * Specs whose shape {@link specVersion} does not understand\n * are skipped rather than treated as zero, so an unparseable entry can never\n * win and drag every other project down to it.\n *\n * @param specs - The declared specs to choose between.\n * @returns The highest spec, or `undefined` when none is parseable.\n * @throws Never - an all-unparseable set yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function highestSpec (specs: readonly string[]): string | undefined {\n let best: string | undefined\n let bestVersion: string | undefined\n for (const spec of specs) {\n const version = specVersion(spec)\n if (!version) {\n continue\n }\n if (!bestVersion || isNewer(version, bestVersion)) {\n best = spec\n bestVersion = version\n }\n }\n\n return best\n}\n","import { join } from 'node:path'\nimport { Separator, checkbox } from '@inquirer/prompts'\nimport {\n PEER_SECTION,\n collectInventory,\n hasEcosystem,\n isAliasedInstall,\n isWorkspaceProject,\n resolvedVersion,\n rewriteSpec,\n type DependencySite,\n type Ecosystem,\n} from './manifest.repository'\nimport { latestVersions } from './registry.client'\nimport {\n UPDATE_KINDS,\n UPDATE_KIND_LABELS,\n classify,\n highestSpec,\n isNewer,\n rangeOperator,\n specVersion,\n type UpdateKind,\n} from './semver.algorithm'\nimport { runFormatter, runShell } from '../nx-workspace'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\nimport { resolveEcosystems, syncProjectReferences } from './sync-dependencies.use-case'\n\n/**\n * Options accepted by `mnci up`.\n *\n * @remarks\n * `install` is inverted by commander: the flag is `--no-install`, so the field\n * is `true` unless the user asked to skip the reinstall. Compared against\n * `false` explicitly at the call site for that reason.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface UpOptions {\n /** Report only; never prompt and never write. */\n check?: boolean\n /** Select every available update without prompting. */\n yes?: boolean\n /** Restrict the run to one ecosystem. */\n ecosystem?: string\n /** Commander sets this to `false` for `--no-install`. */\n install?: boolean\n}\n\n/**\n * One package with a newer release available.\n *\n * @remarks\n * `current` is one version even though `sites` may declare several ranges: it\n * is what the workspace actually resolved, which is the only version an upgrade\n * is meaningfully measured from. A workspace whose sites genuinely disagree is\n * `mnci sync`'s problem, not this command's.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Outdated {\n /** The package name. */\n name: string\n /** Which ecosystem it belongs to. */\n ecosystem: Ecosystem\n /** The version currently in use. */\n current: string\n /** The newest published version. */\n latest: string\n /** Which `npm-check` section this upgrade belongs in. */\n kind: UpdateKind\n /** Every place it is declared — the column `npm-check` does not have. */\n sites: DependencySite[]\n}\n\n/**\n * The version of a package the workspace is effectively on.\n *\n * @remarks\n * Prefers what is resolved over what is declared, because a range says what\n * would be acceptable while the resolved version says what is actually running\n * — and an upgrade report about the former would be about nothing the user has.\n * Falls back to the highest declared range for a package nothing has installed.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param sites - Every declaration of one package.\n * @returns The current version, or `undefined` when it cannot be determined.\n * @throws Never - every lookup failure yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction currentVersion (\n workspaceRoot: string,\n sites: readonly DependencySite[],\n): string | undefined {\n const installed = resolvedVersion(workspaceRoot, sites[0].ecosystem, sites[0].name)\n if (installed) {\n return installed\n }\n if (sites[0].ecosystem === 'go') {\n // The root go.mod pins exactly and declares each module once, so its spec\n // IS the current version — but it carries a leading `v`, which every\n // version comparison here would otherwise reject as unparseable, silently\n // reporting the whole Go ecosystem as having no updates.\n return bare(sites[0].spec)\n }\n const highest = highestSpec(sites.map(site => site.spec))\n\n return highest ? specVersion(highest) : undefined\n}\n\n/**\n * Finds every package with a newer release, across every ecosystem present.\n *\n * @remarks\n * Indirect Go modules are excluded: `go mod tidy` owns them, so offering one\n * for a direct upgrade would be offering an edit the toolchain undoes.\n *\n * So is a package declared **only** as a peer dependency. A peer range states\n * which versions a published package supports, not which one this workspace\n * uses, so there is no upgrade to offer — and writing one would narrow the\n * package's compatibility. A package that is both a peer somewhere and a real\n * dependency elsewhere is still offered; `applyUpdate` then rewrites the real\n * declarations and leaves the peer range alone.\n *\n * An ecosystem whose toolchain is missing yields nothing rather than failing —\n * `latestVersions` resolves an empty map — and `runUp` reports that as a loud\n * SKIPPED so an absent Flutter SDK is never mistaken for \"everything is current\".\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - Which ecosystems to inspect.\n * @returns A promise of every outdated package, newest section first.\n * @throws Never - unreadable manifests and dead toolchains are skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function collectOutdated (\n workspaceRoot: string,\n ecosystems: readonly Ecosystem[],\n): Promise<Outdated[]> {\n const present = ecosystems.filter(ecosystem => hasEcosystem(workspaceRoot, ecosystem))\n const inventory = collectInventory(workspaceRoot, present)\n\n const outdated: Outdated[] = []\n for (const ecosystem of present) {\n const forEcosystem = [...inventory].filter(([name, sites]) =>\n offerable(workspaceRoot, ecosystem, name, sites),\n )\n const latest = await latestVersions(\n ecosystem,\n forEcosystem.map(([name]) => name),\n workspaceRoot,\n )\n outdated.push(...upgradable(workspaceRoot, ecosystem, forEcosystem, latest))\n }\n\n return outdated.toSorted(\n (left, right) =>\n UPDATE_KINDS.indexOf(left.kind) - UPDATE_KINDS.indexOf(right.kind) ||\n left.name.localeCompare(right.name),\n )\n}\n\n/**\n * Whether a package is one this command may offer an upgrade for.\n *\n * @remarks\n * Four exclusions, each one a wrong row the first real run produced or would\n * have produced:\n *\n * - **A different ecosystem's entry**, since the registry answer being matched\n * against was fetched for this one.\n * - **An indirect Go module**, which `go mod tidy` owns.\n * - **A package declared only as a peer**, whose range states which versions a\n * published package supports rather than which one this workspace uses.\n * - **A workspace project**, which `nx release` versions, and an **aliased\n * install**, whose manifest key names a different package than the one on\n * disk — the registry answer for that key is about something else entirely.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem currently being processed.\n * @param name - The package name.\n * @param sites - Every declaration of it.\n * @returns `true` when an upgrade may be offered.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction offerable (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n name: string,\n sites: readonly DependencySite[],\n): boolean {\n if (sites[0].ecosystem !== ecosystem || sites[0].section === 'indirect') {\n return false\n }\n if (sites.every(site => site.section === PEER_SECTION)) {\n return false\n }\n if (ecosystem !== 'npm') {\n return true\n }\n\n return !isWorkspaceProject(workspaceRoot, name) && !isAliasedInstall(workspaceRoot, name)\n}\n\n/**\n * Drops Go's leading `v` so a version can be compared like any other.\n *\n * @remarks\n * `v1.2.3` and `1.2.3` are the same release. Without this, every Go module\n * would read as unparseable and the whole ecosystem would silently report no\n * updates — the quiet-failure shape this codebase keeps running into.\n *\n * @param version - The version as its ecosystem spells it.\n * @returns The version without a leading `v`.\n * @throws Never - performs a string replacement only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction bare (version: string): string {\n return version.replace(/^v/, '')\n}\n\n/**\n * Selects the packages in one ecosystem that have a newer release.\n *\n * @remarks\n * Split out of {@link collectOutdated} so the per-package loop is not nested\n * inside the per-ecosystem one. Go's `v` prefix is stripped on both sides before\n * comparing: `v1.2.3` and `1.2.3` are the same release, and a comparison that\n * did not know that would report every Go module as unparseable.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystem - The ecosystem being processed.\n * @param declared - Its inventory entries, as name/sites pairs.\n * @param latest - The latest version per package name.\n * @returns The packages with an upgrade available.\n * @throws Never - a package with no known latest version is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction upgradable (\n workspaceRoot: string,\n ecosystem: Ecosystem,\n declared: ReadonlyArray<[string, DependencySite[]]>,\n latest: ReadonlyMap<string, string>,\n): Outdated[] {\n const outdated: Outdated[] = []\n\n for (const [name, sites] of declared) {\n const newest = latest.get(name)\n const current = currentVersion(workspaceRoot, sites)\n if (!newest || !current || !isNewer(bare(newest), bare(current))) {\n continue\n }\n outdated.push({\n name,\n ecosystem,\n current,\n latest: newest,\n kind: classify(bare(current), bare(newest)),\n sites,\n })\n }\n\n return outdated\n}\n\n/**\n * Renders one report row, column-aligned against its siblings.\n *\n * @remarks\n * Hand-rolled padding rather than a table dependency: the CLI ships three\n * runtime dependencies on purpose, and this is four `padEnd` calls.\n *\n * @param entry - The package to render.\n * @param widths - The column widths computed across the whole section.\n * @returns The formatted row.\n * @throws Never - performs string formatting only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction row (entry: Outdated, widths: { name: number, section: number, current: number }): string {\n const sections = [...new Set(entry.sites.map(site => site.section))].join('/')\n const projects = [...new Set(entry.sites.map(site => site.project))].join(', ')\n\n return [\n entry.name.padEnd(widths.name),\n sections.padEnd(widths.section),\n `${entry.current.padStart(widths.current)} › ${entry.latest}`,\n projects,\n ].join(' ')\n}\n\n/**\n * Computes the column widths for a set of rows.\n *\n * @param entries - The rows about to be rendered.\n * @returns The width of each padded column.\n * @throws Never - performs arithmetic only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction columnWidths (entries: readonly Outdated[]): {\n name: number\n section: number\n current: number\n} {\n return {\n name: widthOf(entries.map(entry => entry.name)),\n section: widthOf(\n entries.map(entry => [...new Set(entry.sites.map(site => site.section))].join('/')),\n ),\n current: widthOf(entries.map(entry => entry.current)),\n }\n}\n\n/**\n * Longest string in a set, or zero for an empty one.\n *\n * @param values - The strings to measure.\n * @returns The greatest length.\n * @throws Never - performs arithmetic only.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction widthOf (values: readonly string[]): number {\n return Math.max(0, ...values.map(value => value.length))\n}\n\n/**\n * Prints the grouped report `npm-check` prints, plus the projects column.\n *\n * @remarks\n * Also what a non-TTY invocation gets instead of a prompt, so a piped or\n * scripted run reports rather than hanging on a question nobody can answer.\n *\n * @param outdated - Everything with an update available.\n * @returns Nothing.\n * @throws Never - writes to stdout only.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reportOutdated (outdated: readonly Outdated[]): void {\n const widths = columnWidths(outdated)\n for (const kind of UPDATE_KINDS) {\n const section = outdated.filter(entry => entry.kind === kind)\n if (section.length === 0) {\n continue\n }\n const { title, blurb } = UPDATE_KIND_LABELS[kind]\n logger.info('')\n logger.info(`${title} ${blurb}`)\n for (const entry of section) {\n logger.info(` ${row(entry, widths)}`)\n }\n }\n}\n\n/**\n * Prompts for which updates to apply, grouped the way `npm-check -u` groups them.\n *\n * @param outdated - Everything with an update available.\n * @returns A promise of the selected packages.\n * @throws Propagates any error `@inquirer/prompts` raises (e.g. non-TTY stdin).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function promptForUpdates (outdated: readonly Outdated[]): Promise<Outdated[]> {\n const widths = columnWidths(outdated)\n const choices: Array<Separator | { name: string, value: Outdated }> = []\n\n for (const kind of UPDATE_KINDS) {\n const section = outdated.filter(entry => entry.kind === kind)\n if (section.length === 0) {\n continue\n }\n const { title, blurb } = UPDATE_KIND_LABELS[kind]\n choices.push(new Separator(`\\n${title} ${blurb}`))\n for (const entry of section) {\n choices.push({ name: row(entry, widths), value: entry })\n }\n }\n\n return await checkbox({\n message: 'Choose which packages to update.',\n choices,\n pageSize: 20,\n })\n}\n\n/**\n * Applies one selected update to every manifest that declares the package.\n *\n * @remarks\n * Every site, not just the first: the whole reason this command shows a\n * projects column is that a package usually has several declarations, and\n * updating one of them is how a workspace acquires the version drift\n * `mnci sync` then has to repair.\n *\n * Go is the exception and is handled by the caller — its version lives in the\n * root `go.mod`'s require block, which is the toolchain's file to write.\n *\n * @param entry - The selected package.\n * @returns The manifest paths that changed.\n * @throws Propagates any `fs` write error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction applyUpdate (entry: Outdated): string[] {\n const changed: string[] = []\n for (const site of entry.sites) {\n if (!site.rewritable) {\n continue\n }\n // Keep whatever operator this site already used, so an exact pin is never\n // silently widened into a range.\n const spec = `${rangeOperator(site.spec)}${entry.latest}`\n if (rewriteSpec(site, spec)) {\n changed.push(site.manifestPath)\n }\n }\n\n return changed\n}\n\n/**\n * Reinstalls each ecosystem whose manifests changed.\n *\n * @remarks\n * Editing a manifest without reinstalling leaves the workspace in the one state\n * this whole feature exists to prevent: declared and resolved disagreeing.\n *\n * The Python step goes through the workspace's own `python:install` root script\n * rather than a bare `pip install`, because that script is what installs every\n * project editable in one resolver pass — a per-project install would resolve\n * each project's requirements in isolation.\n *\n * The NuGet step is `nx run-many -t restore`, never a bare `dotnet restore`:\n * `@nx/dotnet`'s inference gives every `.csproj` its own `restore` target,\n * but there is no root `.sln`/`.csproj` for a bare `dotnet restore` at the\n * workspace root to act on — it would fail outright with \"the current\n * working directory does not contain a project or solution file\".\n * `nx run-many` is what reaches every project the way `flutter pub get`/\n * `go mod tidy` reach every dependency in one call for their own ecosystems.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param ecosystems - The ecosystems whose manifests were edited.\n * @returns Nothing.\n * @throws Never - a failed install is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reinstall (workspaceRoot: string, ecosystems: ReadonlySet<Ecosystem>): void {\n const commands: Array<[Ecosystem, string, string[]]> = [\n ['npm', 'npm', ['install']],\n ['pip', 'npm', ['run', 'python:install']],\n ['pub', 'flutter', ['pub', 'get']],\n ['nuget', 'npx', ['nx', 'run-many', '-t', 'restore']],\n ['go', 'go', ['mod', 'tidy']],\n ]\n\n for (const [ecosystem, command, arguments_] of commands) {\n if (!ecosystems.has(ecosystem)) {\n continue\n }\n if (ecosystem === 'pip' && !hasPythonInstallScript(workspaceRoot)) {\n logger.warn(\n \"This workspace has no 'python:install' script — run 'mnci upgrade' to add it, then reinstall by hand.\",\n )\n continue\n }\n logger.step(`Reinstalling ${ecosystem} dependencies (${command} ${arguments_.join(' ')})`)\n if (runShell(command, arguments_, workspaceRoot) !== 0) {\n logger.warn(\n `${command} ${arguments_.join(' ')} failed — the manifests were updated, so re-run it once the cause is fixed.`,\n )\n }\n }\n}\n\n/**\n * Whether the workspace's root manifest carries the `python:install` script.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns `true` when the script exists.\n * @throws Never - an unreadable manifest yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction hasPythonInstallScript (workspaceRoot: string): boolean {\n try {\n const manifest = readJson<{ scripts?: Record<string, string> }>(\n join(workspaceRoot, 'package.json'),\n )\n\n return manifest.scripts?.['python:install'] !== undefined\n } catch {\n return false\n }\n}\n\n/**\n * Upgrades Go modules through the toolchain rather than by editing `go.mod`.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param entries - The selected Go modules.\n * @returns Nothing.\n * @throws Never - a failed `go get` is reported as a warning.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction applyGoUpdates (workspaceRoot: string, entries: readonly Outdated[]): void {\n for (const entry of entries) {\n logger.step(`go get ${entry.name}@${entry.latest}`)\n if (runShell('go', ['get', `${entry.name}@${entry.latest}`], workspaceRoot) !== 0) {\n logger.warn(`go get ${entry.name}@${entry.latest} failed — left at ${entry.current}.`)\n }\n }\n}\n\n/**\n * Reports every outdated dependency across the workspace and offers to update.\n *\n * @remarks\n * Modelled on `npm-check -u`: the same four sections in the same order, the\n * same interactive multiselect. The addition is the last column — every project\n * that declares the package — which is the question `npm-check` cannot answer\n * in a monorepo and the one that actually decides whether an upgrade is safe.\n *\n * `--check` is not just a flag: it is also the automatic behaviour when stdout\n * is not a TTY, so a CI step or a piped invocation reports instead of hanging\n * on a prompt no one can answer.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param options - Parsed CLI options.\n * @returns A promise that resolves when the run completes.\n * @throws Error when `workspaceRoot` is not an Nx workspace, or the\n * `--ecosystem` flag names something unknown.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runUp (workspaceRoot: string, options: UpOptions): Promise<void> {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(`No nx.json found in ${workspaceRoot} — run 'mnci up' from the workspace root.`)\n }\n\n const ecosystems = resolveEcosystems(options.ecosystem)\n for (const ecosystem of ecosystems) {\n if (!hasEcosystem(workspaceRoot, ecosystem)) {\n logger.info(`⊘ SKIPPED ${ecosystem} — no ${ecosystem} project in this workspace.`)\n }\n }\n\n logger.step('Querying registries for the latest published versions')\n const outdated = await collectOutdated(workspaceRoot, ecosystems)\n\n if (outdated.length === 0) {\n logger.success('Every dependency is on its latest published version.')\n\n return\n }\n\n const readOnly = options.check ?? !process.stdout.isTTY\n if (readOnly) {\n reportOutdated(outdated)\n logger.info('')\n logger.info(`${outdated.length} package(s) have a newer release. Run 'mnci up' to update them.`)\n\n return\n }\n\n const selected = options.yes ? [...outdated] : await promptForUpdates(outdated)\n if (selected.length === 0) {\n logger.info('Nothing selected; no manifest was changed.')\n\n return\n }\n\n const goSelections = selected.filter(entry => entry.ecosystem === 'go')\n const changed = selected\n .filter(entry => entry.ecosystem !== 'go')\n .flatMap(entry => applyUpdate(entry))\n\n const changedManifests = new Set(changed)\n for (const path of changedManifests) {\n logger.detail(`updated ${path}`)\n }\n applyGoUpdates(workspaceRoot, goSelections)\n\n if (options.install !== false) {\n reinstall(workspaceRoot, new Set(selected.map(entry => entry.ecosystem)))\n }\n\n if (changed.length > 0) {\n syncProjectReferences(workspaceRoot)\n logger.step('Formatting the changed manifests (eslint --fix)')\n runFormatter(workspaceRoot)\n }\n\n logger.success(\n `Updated ${selected.length} package(s). Review the changes with \\`git diff\\` before committing.`,\n )\n}\n","import { pool, runCaptureAsync } from '../nx-workspace'\nimport type { Ecosystem } from './manifest.repository'\n\n/**\n * How many registry lookups run at once.\n *\n * @remarks\n * Each one is a process, not a socket, so the ceiling is about file descriptors\n * and CPU rather than politeness to the registry. Eight keeps a hundred-package\n * workspace under ten seconds without saturating a two-core CI agent.\n */\nconst LOOKUP_CONCURRENCY = 8\n\n/**\n * Latest published version per package name.\n *\n * @remarks\n * A package the registry does not know is simply absent from the map rather\n * than present with an empty value, so a caller reads \"no answer\" and \"no\n * update\" the same way — as nothing to offer.\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type LatestVersions = Map<string, string>\n\n/**\n * Asks npm for the latest published version of each package.\n *\n * @remarks\n * `npm view` per package rather than a raw HTTPS request to\n * `registry.npmjs.org`, and that is the load-bearing choice in this file.\n * mnci generates workspaces that publish to **Azure Artifacts**, where the feed\n * is reached through a scoped `@scope:registry` line and authenticated with\n * Basic credentials from the workspace `.npmrc`. `npm view` resolves both for\n * free. Hand-rolling that is how the `_authToken`-as-Bearer trap gets walked\n * into a second time: Azure Artifacts answers a PAT sent as a Bearer token with\n * a 401, and only Basic auth works — a distinction npm already knows about and\n * this command has no business re-implementing.\n *\n * `npm outdated --json` was rejected for a different reason: it is\n * workspace-shallow and omits any package whose declared range already covers\n * the latest release, so the patch/minor/major grouping built on it would be\n * silently incomplete.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root, so npm reads the workspace `.npmrc`.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - a package the registry does not know is simply absent.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestNpmVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync('npm', ['view', name, 'version', '--json'], cwd)\n if (result.status !== 0) {\n return\n }\n try {\n // `--json` yields a quoted string for a single field, and an array when\n // the spec matched several versions; the last is the newest.\n const parsed: unknown = JSON.parse(result.stdout)\n const version = Array.isArray(parsed) ? (parsed.at(-1) as unknown) : parsed\n\n return typeof version === 'string' ? ([name, version] as const) : undefined\n } catch {\n return\n }\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Asks PyPI (or whichever index pip is configured for) for latest versions.\n *\n * @remarks\n * `pip index versions` is the same call `@mnci/nx-python-pip`'s `VersionActions`\n * already makes to resolve a published version, reused here rather than hitting\n * `pypi.org/pypi/<name>/json` directly for the same reason npm goes through\n * `npm view`: a workspace may be pointed at a private index, and pip's own\n * configuration is the only thing that knows.\n *\n * The command is marked experimental by pip and prints a warning saying so —\n * on stderr, which {@link runCaptureAsync} discards.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - an unavailable index yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestPipVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const python = process.platform === 'win32' ? 'python' : 'python3'\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync(python, ['-m', 'pip', 'index', 'versions', name], cwd)\n if (result.status !== 0) {\n return\n }\n // First line is `<name> (<latest>)`; the second lists every version.\n const match = /\\(([^)]+)\\)/.exec(result.stdout)\n\n return match ? ([name, match[1]] as const) : undefined\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Asks the Go toolchain which required modules have a newer release.\n *\n * @remarks\n * One call for the whole module graph, because `go list` already reports the\n * available update per module in its `Update` field — there is no per-package\n * query to pool. `-json` emits a stream of concatenated objects rather than a\n * JSON array, so the output is split on the object boundary before parsing.\n *\n * @param cwd - The workspace root, which holds the single `go.mod`.\n * @returns A promise of the latest version per module with an update.\n * @throws Never - an absent Go toolchain yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestGoVersions (cwd: string): Promise<LatestVersions> {\n const result = await runCaptureAsync('go', ['list', '-m', '-u', '-json', 'all'], cwd)\n if (result.status !== 0) {\n return new Map()\n }\n\n const latest: LatestVersions = new Map()\n for (const chunk of result.stdout.split(/^\\}\\s*$/m)) {\n const text = `${chunk.trim()}${chunk.trim() ? '}' : ''}`\n if (!text.startsWith('{')) {\n continue\n }\n try {\n const module_ = JSON.parse(text) as { Path?: string, Update?: { Version?: string } }\n if (module_.Path && module_.Update?.Version) {\n latest.set(module_.Path, module_.Update.Version)\n }\n } catch {\n // A partial chunk at the tail of the stream is not an error worth raising.\n }\n }\n\n return latest\n}\n\n/**\n * Asks pub which workspace dependencies have a newer release.\n *\n * @remarks\n * One call, like Go: `flutter pub outdated --json` reports the whole pub\n * workspace in a single pass, which is what a shared `pubspec.lock` means.\n *\n * `latest` is read rather than `resolvable`: `resolvable` is capped by every\n * other constraint in the workspace, so reporting it would hide exactly the\n * upgrades a user runs this command to discover.\n *\n * @param cwd - The workspace root, which holds the root `pubspec.yaml`.\n * @returns A promise of the latest version per package.\n * @throws Never - an absent Flutter SDK yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestPubVersions (cwd: string): Promise<LatestVersions> {\n const result = await runCaptureAsync('flutter', ['pub', 'outdated', '--json'], cwd)\n if (result.status !== 0) {\n return new Map()\n }\n\n try {\n const report = JSON.parse(result.stdout) as {\n packages?: Array<{ package?: string, latest?: { version?: string } }>\n }\n const latest: LatestVersions = new Map()\n const packages = report.packages ?? []\n for (const entry of packages) {\n if (entry.package && entry.latest?.version) {\n latest.set(entry.package, entry.latest.version)\n }\n }\n\n return latest\n } catch {\n return new Map()\n }\n}\n\n/**\n * Asks NuGet for the latest published version of each package.\n *\n * @remarks\n * `dotnet package search <name> --exact-match --format json` per package —\n * the same \"go through the ecosystem's own tool\" reasoning as `npm view`:\n * with no `--source` argument, it consults every source in the workspace's\n * resolved `nuget.config` (public nuget.org, and an Azure Artifacts feed\n * when one is configured), the same way `npm view` reads the resolved\n * `.npmrc`. Verified against Microsoft's own `dotnet package search` docs —\n * not assumed — including the exact JSON shape read below\n * (`searchResult[].packages[].latestVersion`) and that the command needs SDK\n * 8.0.2xx+, well under the SDK version mnci pins (`DOTNET_SDK_VERSION` in\n * `overlay.ts`).\n *\n * `--exact-match` still returns the package under `searchResult[].packages`,\n * so the result is matched by `id` (case-insensitively — NuGet package IDs\n * are case-preserving but not case-sensitive) rather than assumed to be the\n * only entry, in case a source ever returns more than one hit.\n *\n * @param names - The package names to look up.\n * @param cwd - The workspace root, so `dotnet` reads the workspace `nuget.config`.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - a package no configured source knows is simply absent.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestNugetVersions (\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n const results = await pool(names, LOOKUP_CONCURRENCY, async name => {\n const result = await runCaptureAsync(\n 'dotnet',\n ['package', 'search', name, '--exact-match', '--format', 'json'],\n cwd,\n )\n if (result.status !== 0) {\n return\n }\n try {\n const parsed = JSON.parse(result.stdout) as {\n searchResult?: Array<{ packages?: Array<{ id?: string, latestVersion?: string }> }>\n }\n const packages = parsed.searchResult?.flatMap(source => source.packages ?? []) ?? []\n const match =\n packages.find(entry => entry.id?.toLowerCase() === name.toLowerCase()) ?? packages[0]\n\n return match?.latestVersion ? ([name, match.latestVersion] as const) : undefined\n } catch {\n return\n }\n })\n\n return new Map(results.filter(entry => entry !== undefined))\n}\n\n/**\n * Resolves latest versions for one ecosystem.\n *\n * @remarks\n * The single entry point commands use, so the per-ecosystem asymmetry — two\n * ecosystems answer per package, two answer for the whole graph at once — stays\n * behind one signature instead of leaking into every caller.\n *\n * @param ecosystem - Which ecosystem to query.\n * @param names - The package names, used only by the per-package ecosystems.\n * @param cwd - The workspace root.\n * @returns A promise of the latest version per package that answered.\n * @throws Never - an unavailable toolchain yields an empty map.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function latestVersions (\n ecosystem: Ecosystem,\n names: readonly string[],\n cwd: string,\n): Promise<LatestVersions> {\n switch (ecosystem) {\n case 'npm': {\n return await latestNpmVersions(names, cwd)\n }\n case 'pip': {\n return await latestPipVersions(names, cwd)\n }\n case 'nuget': {\n return await latestNugetVersions(names, cwd)\n }\n case 'go': {\n return await latestGoVersions(cwd)\n }\n // 'pub' explicitly, not a default: a future ecosystem added to the union\n // without a case here should fail to compile, not silently resolve as\n // Dart/Flutter's whole-graph query — exactly the trap this switch would\n // otherwise have laid for 'nuget' itself.\n case 'pub': {\n return await latestPubVersions(cwd)\n }\n }\n}\n","import { existsSync, globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx-workspace'\nimport {\n ESLINT_MNCI_FILENAME,\n ESLINT_USER_FILENAME,\n ESLINT_VERSION,\n RETIRED_FORMATTER_FILES,\n type RegistryConfig,\n} from '../workspace-overlay'\nimport {\n canRepairRollupConfig,\n hasDeclarationSpecifierPlugin,\n hasDirectoryAwareDeclarationSpecifiers,\n hasRollupSourceMaps,\n resolveRollupConfigText,\n} from '../rollup-library'\nimport { fileExists, readJson } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * One check's outcome.\n *\n * @remarks\n * `remedy` is separate from `detail` on purpose: the detail says what is wrong in\n * this workspace, the remedy says what to type. A finding without a remedy is a\n * finding the user cannot act on, which is the main way a doctor command becomes\n * noise.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface Finding {\n /** Short check name, shown as the line label. */\n check: string\n /** Whether the invariant holds. */\n ok: boolean\n /** What is wrong, when it is not ok. */\n detail?: string\n /** The command or edit that fixes it. */\n remedy?: string\n}\n\n/** The ESLint major this stack supports, derived from the version mnci pins. */\nconst SUPPORTED_ESLINT_MAJOR = ESLINT_VERSION.replace(/^\\D*/, '').split('.', 1)[0]\n\n/**\n * Normalises a `globSync` result to forward slashes.\n *\n * @remarks\n * Load-bearing on Windows, where `globSync` returns `packages\\name\\file`. Two\n * checks here split those results on `'/'`, and on a backslash path\n * `lastIndexOf('/')` is `-1`, so `slice(0, -1)` silently drops the last\n * character instead of the filename. The resulting path resolves to nothing,\n * which turned {@link checkVersionActions} — the highest-consequence check in\n * this file — into a check that reported a failure for EVERY publishable\n * Dart/Python package on Windows, including correctly configured ones, under a\n * mangled name. A doctor that cries wolf on a healthy workspace is worse than\n * one that stays quiet.\n *\n * @param path - A workspace-relative path from `globSync`.\n * @returns The same path with forward slashes.\n * @throws Never - performs a pure string replacement.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction toPosix (path: string): string {\n return path.replaceAll('\\\\', '/')\n}\n\n/**\n * Checks that the workspace has exactly one ESLint config, at the root.\n *\n * @remarks\n * The invariant that actually broke in practice, twice over: every `@nx/*`\n * generator writes a per-project config, and a workspace generated before mnci\n * owned linting has one in every project directory. Either way the root config\n * stops being the only opinion, silently, because each project lints against\n * whichever config sits nearest it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The findings for the root config and the per-project sweep.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintConfigs (workspaceRoot: string): Finding[] {\n const rootConfigs = globSync('eslint.config.{js,mjs,cjs,ts,mts,cts}', { cwd: workspaceRoot })\n const projectConfigs = globSync('{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}', {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return [\n {\n check: 'root ESLint config',\n ok: rootConfigs.length === 1,\n detail:\n rootConfigs.length === 0\n ? 'no eslint.config.* at the workspace root'\n : `${rootConfigs.length} root configs: ${rootConfigs.join(', ')}`,\n remedy: 'run `mnci upgrade` to rewrite the root config',\n },\n {\n check: 'no per-project ESLint configs',\n ok: projectConfigs.length === 0,\n detail: `found ${projectConfigs.length}: ${projectConfigs.join(', ')}`,\n remedy: 'run `mnci upgrade`, which sweeps {apps,libs,packages}/*/eslint.config.*',\n },\n ...checkEslintEntryPointReachesTheRules(workspaceRoot),\n ]\n}\n\n/**\n * Fails when `eslint.config.mjs` no longer reaches the rules mnci writes.\n *\n * @remarks\n * The two files are split so that `mnci upgrade` can rewrite the rules without\n * touching the workspace's own blocks — which is only true while the workspace's\n * file still imports the other one. A config that has stopped doing so lints\n * against whatever it does import and nothing else, and it does that quietly:\n * ESLint is perfectly happy with a config that carries no rules, so `lint`\n * passes and every file in the repository drifts.\n *\n * Only asked once `eslint.config.mnci.mjs` exists, so a workspace that has not\n * been upgraded yet is not nagged about a file it has never had.\n *\n * Matched on the filename rather than by parsing: a bare substring is enough to\n * tell an import of it from its absence, and running a workspace's config\n * through a parser inside a read-only diagnostic buys nothing.\n *\n * The remedy spells the CALL, `...mnci()`, not `...mnci`. The owned file\n * exports a function so that options still reach `@mnci/eslint-config`, and a\n * remedy a user follows literally has to produce something that works.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding, or none when the split has not been applied here.\n * @throws Never - an unreadable entry point reads as not reaching the rules.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintEntryPointReachesTheRules (workspaceRoot: string): Finding[] {\n const rules = join(workspaceRoot, ESLINT_MNCI_FILENAME)\n if (!existsSync(rules)) return []\n\n const entryPoint = join(workspaceRoot, ESLINT_USER_FILENAME)\n const source = existsSync(entryPoint) ? readFileSync(entryPoint, 'utf8') : ''\n\n return [\n {\n check: `${ESLINT_USER_FILENAME} imports the mnci rules`,\n ok: source.includes(ESLINT_MNCI_FILENAME),\n detail: source === ''\n ? `${ESLINT_USER_FILENAME} is missing, so nothing loads the rules`\n : `${ESLINT_USER_FILENAME} never mentions ${ESLINT_MNCI_FILENAME}`,\n remedy:\n `make its first import \\`import mnci from './${ESLINT_MNCI_FILENAME}'\\` and spread ` +\n '`...mnci()` into the exported array, keeping your own blocks after it — ' +\n 'mnci does not rewrite this file, so it cannot do this for you',\n },\n ]\n}\n\n/**\n * The root manifest's devDependencies, tolerantly.\n *\n * @remarks\n * `readJson` throws on a missing file, and doctor exists to diagnose exactly\n * the broken workspaces where one might be absent — so it must not die on the\n * thing it is inspecting.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The declared devDependencies, or an empty map.\n * @throws Never - a missing or malformed manifest reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction declaredDevDependencies (workspaceRoot: string): Record<string, string> {\n try {\n const manifest = readJson<{ devDependencies?: Record<string, string> }>(\n join(workspaceRoot, 'package.json'),\n )\n\n return manifest.devDependencies ?? {}\n } catch {\n return {}\n }\n}\n\n/**\n * Fails when a retired formatter's config or dependency is still present.\n *\n * @remarks\n * ESLint is the only linter and the only formatter. Two checks used to live\n * here — one for the eslint/oxlint mode split, one for \"exactly one formatter\n * declared\" — and both are meaningless now that there is nothing to choose.\n * This replaces them with the failure that survived the collapse.\n *\n * A leftover `.prettierrc.mjs`, `.oxfmtrc.json` or `oxlint.config.ts` is inert\n * from the command line, because nothing runs those binaries any more. That is\n * precisely what makes it worth a check: a globally installed\n * `esbenp.prettier-vscode` or `oxc.oxc-vscode` still resolves the config and\n * still reformats on save, so the editor quietly undoes Standard — semicolons\n * come back, `function f (a)` loses its space — while `npm run lint` reports\n * nothing, because the damage lands after the last check ran. A declared\n * `prettier` in `devDependencies` is the same trap by the other route, since\n * the extension resolves the formatter from the project.\n *\n * `mnci upgrade` removes all of them; this reports a workspace that has not\n * been upgraded yet, and names the command that fixes it.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNoRetiredFormatter (workspaceRoot: string): Finding {\n const files = RETIRED_FORMATTER_FILES.filter(file => fileExists(join(workspaceRoot, file)))\n const declared = ['prettier', 'eslint-config-prettier', 'oxlint', 'oxfmt', '@mnci/oxlint-config']\n .filter(name => declaredDevDependencies(workspaceRoot)[name] !== undefined)\n const stale = [...files, ...declared]\n\n if (stale.length === 0) {\n return { check: 'ESLint is the only linter and formatter', ok: true }\n }\n\n return {\n check: 'no retired formatter is still configured',\n ok: false,\n detail:\n `Found: ${stale.join(', ')}. These no longer run, but an editor extension ` +\n 'still resolves them and will reformat on save against an opinion no gate ' +\n 'checks.',\n remedy: \"Run 'mnci upgrade' to remove them.\",\n }\n}\n\n/**\n * Checks that `@nx/eslint/plugin` is registered in `nx.json`.\n *\n * @remarks\n * This registration is what turns the single root config into a `lint` target on\n * every project. Without it, `npm run lint` still exits 0 while linting nothing —\n * a green check that proves nothing, which is worse than a red one.\n *\n * @param nxJson - The parsed `nx.json`.\n * @returns The finding.\n * @throws Never - pure inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkEslintPlugin (nxJson: Record<string, unknown>): Finding {\n const plugins = (nxJson.plugins as unknown[] | undefined) ?? []\n const registered = plugins.some(\n entry =>\n (typeof entry === 'string' ? entry : (entry as { plugin?: string }).plugin) ===\n '@nx/eslint/plugin',\n )\n\n return {\n check: '@nx/eslint/plugin registered',\n ok: registered,\n detail: 'not in nx.json plugins — every project silently loses its lint target',\n remedy: 'run `mnci upgrade`',\n }\n}\n\n/**\n * Checks that the ESLint actually installed is the major this stack supports.\n *\n * @remarks\n * The exact bug this command exists for. mnci pins ESLint 9 because\n * `eslint-plugin-react` has no ESLint 10 release, but a declared range and the\n * **resolved** version are different things: this repo carried four package\n * manifests declaring `^10` while the docs said 9, and the resolved binary was 10.\n * Nothing failed, because the repo has no `.tsx` of its own — the drift was only\n * visible by asking `node_modules` what actually got installed.\n *\n * Skipped rather than failed when there are no `node_modules`, since \"not\n * installed yet\" is not a drift.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding, or `undefined` when nothing is installed to inspect.\n * @throws Never - a malformed manifest yields no finding.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkResolvedEslint (workspaceRoot: string): Finding | undefined {\n const manifestPath = join(workspaceRoot, 'node_modules/eslint/package.json')\n if (!fileExists(manifestPath)) {\n return undefined\n }\n try {\n const { version } = readJson<{ version: string }>(manifestPath)\n const major = version.split('.', 1)[0]\n\n return {\n check: `resolved eslint is ${SUPPORTED_ESLINT_MAJOR}.x`,\n ok: major === SUPPORTED_ESLINT_MAJOR,\n detail: `node_modules/eslint is ${version}, but this stack supports ${SUPPORTED_ESLINT_MAJOR}.x (eslint-plugin-react has no release beyond it)`,\n remedy: `pin eslint to ${ESLINT_VERSION} in every package manifest, then reinstall`,\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * Checks that `.npmrc` matches the registry the workspace recorded.\n *\n * @remarks\n * Only meaningful now that publish auth is wired: the two registry kinds get\n * genuinely different files, so a workspace whose `.npmrc` predates its recorded\n * registry choice cannot publish. An `azure-artifacts` workspace additionally\n * needs its scope routed, which is what keeps a scoped package off npmjs.org.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param registry - The registry persisted in `nx.json`'s `mnci` block.\n * @param scope - The npm scope persisted alongside it.\n * @returns The finding, or `undefined` when nothing is persisted to compare against.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmrc (\n workspaceRoot: string,\n registry: RegistryConfig | undefined,\n scope: string | undefined,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n if (!registry || !fileExists(npmrcPath)) {\n return undefined\n }\n const npmrc = readFileSync(npmrcPath, 'utf8')\n if (registry.kind === 'npm') {\n return {\n check: '.npmrc authenticates the public registry',\n ok: npmrc.includes('//registry.npmjs.org/:_authToken='),\n detail: 'no npmjs.org token line — `npm publish` cannot authenticate',\n remedy: 'run `mnci upgrade`',\n }\n }\n\n return {\n check: '.npmrc routes the scope to the feed',\n ok: scope !== undefined && npmrc.includes(`${scope}:registry=`),\n detail: `no '${scope ?? '@scope'}:registry=' line — a scoped package could publish to npmjs.org instead of the feed`,\n remedy: 'run `mnci upgrade`',\n }\n}\n\n/**\n * Warns when this workspace's `.npmrc` would hand npm an empty credential.\n *\n * @remarks\n * A project `.npmrc` beats the user's one, and the generated file authenticates\n * through an environment variable that only CI exports. So with that variable\n * unset — which is every local shell — npm inside this workspace does not fall\n * back to whatever `npm login` wrote in `~/.npmrc`; it authenticates with an\n * empty string and every write is refused.\n *\n * That is a deliberate trade-off rather than a defect, and the generated\n * `.npmrc` says so in its own comments. This check exists because the comments\n * are in a file nobody reads at the moment they need them, and because **the\n * refusal is easy to misread**: for a write you are not allowed to make, the\n * registry answers\n *\n * ```text\n * npm error 404 Not Found - PUT https://registry.npmjs.org/<package>\n * ```\n *\n * with everything above it being npm listing what it *intended* to send. A long,\n * healthy-looking run changes nothing, and 404 reads as \"no such package\"\n * rather than \"you are not who you need to be\".\n *\n * WHAT THIS DOES NOT CHECK, AND WHY NOT\n *\n * Whether the credential is any good. That needs the registry, and `mnci doctor`\n * is a local, read-only, offline-capable check — a diagnostic that fails on a\n * train is a diagnostic people stop running. An expired token produces the\n * *same* misleading 404 as this does, so the remedy names `npm whoami`: one\n * command, and the only one that distinguishes the two.\n *\n * Generic in the variable name rather than looking for `NODE_AUTH_TOKEN`. The\n * public-registry file references that one and the Azure Artifacts file\n * references `${PAT}`, and a third could be added; reading the names out of the\n * file cannot go stale.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param environment - The variables to resolve against. Defaulted rather than\n * read inline so the intent is visible in the signature; every caller uses the\n * default.\n * @returns A finding when the file references a variable nothing has set, and\n * `undefined` when there is no `.npmrc`, it references no variables, or every\n * variable it references is set.\n * @throws Never - an unreadable `.npmrc` is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmrcCredentialResolves (\n workspaceRoot: string,\n environment: Record<string, string | undefined> = process.env,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n if (!fileExists(npmrcPath)) {\n return undefined\n }\n\n const npmrc = readFileSync(npmrcPath, 'utf8')\n // Credential lines only. A `${VAR}` inside a comment is documentation, and a\n // registry URL could contain one without being a secret.\n const credentials = npmrc\n .split('\\n')\n .filter(line => !line.trimStart().startsWith(';') && !line.trimStart().startsWith('#'))\n .filter(line => /(?:_authToken|_password|_auth)\\s*=/u.test(line))\n\n const referenced = [\n ...new Set(\n credentials.flatMap(line =>\n Array.from(line.matchAll(/\\$\\{(\\w+)\\}/gu), match => match[1]),\n ),\n ),\n ]\n if (referenced.length === 0) {\n return undefined\n }\n\n const unset = referenced.filter(name => (environment[name] ?? '') === '')\n if (unset.length === 0) {\n return npmjsCredentialShape(credentials, environment)\n }\n\n return {\n check: 'npm credentials in this workspace resolve to something',\n ok: false,\n detail:\n `.npmrc authenticates with \\${${unset.join('}, ${')}}, which ${unset.length > 1 ? 'are' : 'is'} ` +\n 'unset here — and a project .npmrc beats your user one, so npm will send an EMPTY token from ' +\n 'this directory rather than the one `npm login` wrote. Writes are then refused as a 404 on ' +\n 'the PUT, which reads like a missing package',\n remedy:\n 'nothing, for installing or building — this only affects authenticated commands. To publish ' +\n 'or deprecate by hand, run it from outside this workspace, or export a real token first. If ' +\n 'it still fails there, `npm whoami` — an expired token gives the same 404',\n }\n}\n\n/**\n * Checks that the two halves of build-identity npm auth agree with each other.\n *\n * @remarks\n * Build-identity auth is split across two files that cannot see each other: the\n * `.npmrc` carries no credentials, and `azure-pipelines.yml` runs the task that\n * injects them. Either half alone is broken, and neither breaks loudly:\n *\n * - **A credential-free `.npmrc` with no `npmAuthenticate@0` step** publishes\n * nothing. `npm ci` still works, since installing a public package never\n * authenticates, so the pipeline is green until the release step, where the feed\n * answers 401. This is precisely how a real workspace lost the task: a regenerated\n * pipeline dropped it and nothing said so until a publish.\n * - **A `.npmrc` that still holds a PAT block, with the step present,** makes the\n * task append a second credential to a file that already has one. Which of the\n * two npm ends up sending is not something to leave to chance.\n *\n * Only for an Azure Artifacts workspace with an Azure pipeline. A workspace with no\n * `azure-pipelines.yml` has nothing to disagree with, and public npm has no\n * build identity.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param registry - The registry persisted in `nx.json`'s `mnci` block.\n * @returns A finding when the halves disagree, `undefined` when there is nothing to\n * compare, and an `ok` finding when they agree.\n * @throws Never - an unreadable file is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNpmAuthMatchesPipeline (\n workspaceRoot: string,\n registry: RegistryConfig | undefined,\n): Finding | undefined {\n const npmrcPath = join(workspaceRoot, '.npmrc')\n const pipelinePath = join(workspaceRoot, 'azure-pipelines.yml')\n if (registry?.kind !== 'azure-artifacts' || !fileExists(npmrcPath) || !fileExists(pipelinePath)) {\n return undefined\n }\n\n const holdsCredentials = readFileSync(npmrcPath, 'utf8')\n .split('\\n')\n .filter(line => !line.trimStart().startsWith(';') && !line.trimStart().startsWith('#'))\n .some(line => /(?:_authToken|_password|_auth)\\s*=/u.test(line))\n const runsTask = readFileSync(pipelinePath, 'utf8').includes('npmAuthenticate@0')\n\n if (!holdsCredentials && !runsTask) {\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: false,\n detail:\n '.npmrc carries no credentials and azure-pipelines.yml does not run npmAuthenticate@0, so ' +\n 'nothing authenticates the feed — installs still pass, and the release step fails with a 401',\n remedy:\n 'run `mnci upgrade --npm-auth build-identity` to add the task, or `mnci upgrade --npm-auth pat` ' +\n 'to write the PAT block back',\n }\n }\n if (holdsCredentials && runsTask) {\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: false,\n detail:\n '.npmrc holds a credential block AND azure-pipelines.yml runs npmAuthenticate@0, which appends ' +\n 'a second credential to a file that already has one',\n remedy:\n 'run `mnci upgrade --npm-auth build-identity` to drop the PAT block, or `mnci upgrade ' +\n '--npm-auth pat` to drop the task',\n }\n }\n\n return {\n check: 'npm auth: .npmrc and azure-pipelines.yml agree',\n ok: true,\n detail: '',\n remedy: '',\n }\n}\n\n/**\n * Every current npm token starts with this. Granular and automation tokens have\n * since 2021; the legacy format was a UUID, matched separately below.\n */\nconst NPM_TOKEN_PREFIX = 'npm_'\n\n/** The legacy npm token format, still valid on very old accounts. */\nconst LEGACY_NPM_TOKEN = /^[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}$/iu\n\n/**\n * Warns when the credential bound for npmjs.org is not shaped like an npm token.\n *\n * @remarks\n * A set variable is not a working one, and the failure looks identical. This\n * was found on a real machine: `NODE_AUTH_TOKEN` held an 84-character Azure\n * DevOps PAT, so every mnci workspace generated with `--registry npm` quietly\n * authenticated the **public** registry with an Azure credential and every\n * write came back as the same misleading 404.\n *\n * The name is the hazard. `NODE_AUTH_TOKEN` is what `actions/setup-node`\n * exports, which is why mnci writes it — but it is a generic name, so anything\n * else on the machine that sets it wins inside every generated workspace, and\n * nothing says so.\n *\n * A SHAPE CHECK, AND IT SAYS SO\n *\n * This cannot tell a valid token from a revoked one without the registry, and\n * `mnci doctor` stays offline. What it can do is notice that the value is not\n * an npm token at all — every current one begins `npm_`, and the legacy format\n * is a UUID. Anything else is either a credential for somewhere else or a typo,\n * and both are worth a line. The remedy names `npm whoami` because that is the\n * one command that settles what this cannot.\n *\n * The token's value never appears in the output. Its length does, which is\n * enough to recognise what you set without putting a secret on a terminal that\n * may be logged or shared.\n *\n * @param credentials - The `.npmrc` credential lines, comments already removed.\n * @param environment - The variables to resolve against.\n * @returns A finding when an npmjs.org credential is shaped wrongly, else\n * `undefined`.\n * @throws Never - pure string inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction npmjsCredentialShape (\n credentials: string[],\n environment: Record<string, string | undefined>,\n): Finding | undefined {\n const line = credentials.find(entry => entry.includes('//registry.npmjs.org/:_authToken='))\n const variable = line === undefined ? undefined : /\\$\\{(\\w+)\\}/u.exec(line)?.[1]\n if (variable === undefined) {\n return undefined\n }\n\n const value = environment[variable] ?? ''\n if (value.startsWith(NPM_TOKEN_PREFIX) || LEGACY_NPM_TOKEN.test(value)) {\n return undefined\n }\n\n return {\n check: 'the credential bound for npmjs.org looks like an npm token',\n ok: false,\n detail:\n `\\${${variable}} is set (${value.length} characters) but does not start with '${NPM_TOKEN_PREFIX}' ` +\n 'and is not a legacy UUID token, so it is probably a credential for somewhere else — ' +\n `'${variable}' is a generic name that other tooling also sets, and inside this workspace it ` +\n 'is sent to the PUBLIC registry. A write then fails as a 404 on the PUT, which reads like a ' +\n 'missing package',\n remedy:\n '`npm whoami` to confirm — it is the only thing that separates a wrong token from an ' +\n `expired one. Then unset \\`${variable}\\` for local work, or set it to a real npm token`,\n }\n}\n\n/**\n * Checks that every publishable non-npm package keeps its `versionActions` override.\n *\n * @remarks\n * The highest-consequence check here, because the blast radius is the whole\n * workspace rather than the offending project: Nx's default `versionActions` reads\n * a `package.json`, which a Dart, Python or C# package does not have, so it aborts\n * while building the release graph and `nx release` fails for **every** project.\n *\n * All three non-npm shapes are globbed, and C# is listed explicitly rather than\n * left implied: `csharp-lib` is the only C# kind that lands in `packages/`\n * (`csharp-app` and `csharp-function-app` go to `apps/`, `csharp-internal-lib`\n * to `libs/`), so every `.csproj` this glob finds is inside `release.projects`\n * and carries the same whole-workspace failure mode as a Dart or Python one —\n * it just resolves its override to a workspace-relative\n * `tools/csharp-version-actions.cjs` rather than to a plugin.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per publishable Dart/Python/C# package missing the override.\n * @throws Never - an unreadable `project.json` is reported as missing.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkVersionActions (workspaceRoot: string): Finding[] {\n const candidates = [\n ...globSync('packages/*/pubspec.yaml', { cwd: workspaceRoot }),\n ...globSync('packages/*/*.csproj', { cwd: workspaceRoot }),\n ...globSync('python-packages/*/pyproject.toml', { cwd: workspaceRoot }),\n ].map(manifest => toPosix(manifest))\n\n return candidates.flatMap(manifest => {\n const projectRoot = manifest.slice(0, manifest.lastIndexOf('/'))\n const projectJsonPath = join(workspaceRoot, projectRoot, 'project.json')\n let hasOverride = false\n try {\n const projectJson = readJson<{\n release?: { version?: { versionActions?: string } }\n }>(projectJsonPath)\n hasOverride = Boolean(projectJson.release?.version?.versionActions)\n } catch {\n // No project.json at all, so no override — the initialiser already says so.\n }\n\n return [\n {\n check: `${projectRoot} keeps its versionActions override`,\n ok: hasOverride,\n detail: 'missing — nx release aborts for the ENTIRE workspace, not just this project',\n remedy: `add release.version.versionActions to ${projectRoot}/project.json`,\n },\n ]\n })\n}\n\n/**\n * The target options whose value is a single path to a file that must already\n * exist for the target to run.\n *\n * @remarks\n * Deliberately short. `outputPath` is an output, not an input, so it is absent\n * by design; `assets` takes globs and would need matching rather than an\n * existence test. These three are the ones whose absence produces a failure\n * that reads as a compiler problem rather than a config problem.\n */\nconst TARGET_FILE_OPTIONS = ['main', 'tsConfig', 'packageJson'] as const\n\n/**\n * Reads every Nx target a project declares, from either place it can declare them.\n *\n * @remarks\n * A project can carry targets in `project.json` **and** in `package.json`'s `nx`\n * block, and a real workspace was found doing both for one project with\n * different `main` values in each. Both are read here rather than whichever is\n * found first, so a stale definition cannot hide behind the live one.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param projectRoot - The project's workspace-relative directory.\n * @returns One entry per declaring file, with that file's targets.\n * @throws Never - an unreadable or absent manifest contributes no targets.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction declaredTargets (\n workspaceRoot: string,\n projectRoot: string,\n): { source: string; targets: Record<string, { options?: Record<string, unknown> }> }[] {\n const sources: {\n source: string\n targets: Record<string, { options?: Record<string, unknown> }>\n }[] = []\n for (const [file, extract] of [\n ['project.json', (json: Record<string, unknown>) => json['targets']],\n [\n 'package.json',\n (json: Record<string, unknown>) => (json['nx'] as Record<string, unknown>)?.['targets'],\n ],\n ] as const) {\n try {\n const targets = extract(\n readJson<Record<string, unknown>>(join(workspaceRoot, projectRoot, file)),\n )\n if (targets && typeof targets === 'object') {\n sources.push({\n source: `${projectRoot}/${file}`,\n targets: targets as Record<string, { options?: Record<string, unknown> }>,\n })\n }\n } catch {\n // No manifest, or unparseable — nothing to check, and the other checks\n // already report a workspace that broken.\n }\n }\n\n return sources\n}\n\n/**\n * Checks that every target option naming a file points at a file that exists.\n *\n * @remarks\n * Found in a real hand-built workspace, three times over in one repo: a build\n * target whose `main` was `src/index.ts` in `project.json` and `src/main.ts` in\n * the same project's `package.json`, with **neither** file present, and a\n * library whose `tsConfig` named a `tsconfig.lib.json` that had never been\n * written.\n *\n * Worth a check rather than left to the build because of how it surfaces. Nx\n * hands the path to the executor unvalidated, so the failure arrives as a\n * TypeScript or esbuild error about the compiler finding no inputs — which\n * reads as a broken tsconfig, and sends people to edit the one file that is\n * fine. Nothing anywhere names the missing file.\n *\n * Paths carrying an Nx token (`{workspaceRoot}`, `{projectRoot}`) are skipped:\n * they are resolved by Nx at run time, so testing them literally would report a\n * file that is never meant to exist under that name.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per target option pointing at a missing file.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkTargetFilesExist (workspaceRoot: string): Finding[] {\n const projectRoots = new Set(\n [\n ...globSync('{apps,libs,packages}/*/project.json', { cwd: workspaceRoot }),\n ...globSync('{apps,libs,packages}/*/package.json', { cwd: workspaceRoot }),\n ].map(manifest => toPosix(manifest).split('/').slice(0, 2).join('/')),\n )\n\n return [...projectRoots].flatMap(projectRoot =>\n declaredTargets(workspaceRoot, projectRoot).flatMap(({ source, targets }) =>\n Object.entries(targets).flatMap(([targetName, target]) =>\n checkOneTargetsFiles(workspaceRoot, source, targetName, target),\n ),\n ),\n )\n}\n\n/**\n * Checks one target's file-valued options.\n *\n * @remarks\n * Split out of {@link checkTargetFilesExist} so the sweep stays a flat\n * comprehension rather than four nested loops around a pair of `continue`s.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param source - The manifest that declared the target, for the message.\n * @param targetName - The target's name, for the message.\n * @param target - The target definition to inspect.\n * @returns One finding per option pointing at a missing file.\n * @throws Never - only reads the filesystem.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkOneTargetsFiles (\n workspaceRoot: string,\n source: string,\n targetName: string,\n target: { options?: Record<string, unknown> },\n): Finding[] {\n return TARGET_FILE_OPTIONS.flatMap(option => {\n const value = target?.options?.[option]\n // A token (`{workspaceRoot}`, `{projectRoot}`) is resolved by Nx at run\n // time, so a literal existence test would report a file that is never\n // meant to exist under that name.\n const checkable = typeof value === 'string' && !value.includes('{')\n if (!checkable || fileExists(join(workspaceRoot, value))) {\n return []\n }\n\n return [\n {\n check: `${source} → ${targetName}.${option} points at a real file`,\n ok: false,\n detail: `${value} does not exist — the target fails as if the compiler were misconfigured`,\n remedy: `create ${value}, or correct ${option} in ${source}`,\n },\n ]\n })\n}\n\n/**\n * Checks that the workspace's TypeScript project references are synced.\n *\n * @remarks\n * The one check that shells out, because only Nx can answer it. Reported as a\n * finding rather than left to CI so the fix happens before the push that fails.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding.\n * @throws Never - a non-zero exit is the finding, not an error.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkSync (workspaceRoot: string): Finding {\n return {\n check: 'TypeScript project references synced',\n ok: runShell('npx', ['nx', 'sync:check'], workspaceRoot) === 0,\n detail: 'nx sync:check failed — a stale project reference was never committed',\n remedy: 'run `npx nx sync` and commit the result',\n }\n}\n\n/**\n * Checks that no runtime dependency is declared in the root manifest.\n *\n * @remarks\n * The root/project policy, and the one check that enforces it: **shared\n * development and tool packages belong at the root, runtime dependencies belong\n * to the package that imports them.**\n *\n * Two independent reasons, and the second is the one that bites. The root\n * manifest is `private` and never published, so a runtime dependency declared\n * there reaches no consumer of any package — an installed `@scope/lib` simply\n * fails to resolve it. And `@nx/rollup` externalises exactly what a project's\n * OWN manifest declares, so hoisting a dependency to the root does not make it\n * shared, it makes rollup **inline a private copy of it** into the bundle.\n * Measured on a real generated workspace: moving `axios` out of one package's\n * manifest took its published bundle from 14.5 KB to 832 KB, silently.\n *\n * `@nx/dependency-checks` catches the second half from the other direction — it\n * fails the project whose import is now undeclared — so this check and that\n * lint rule are complementary rather than redundant: the rule names the project\n * that lost the dependency, this names the root that took it.\n *\n * `devDependencies` are deliberately not checked. Sharing the toolchain is the\n * whole point of the root manifest, and npm's `overrides` only work there.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The finding, or `undefined` when there is no root manifest to read.\n * @throws Never - an unreadable manifest is treated as nothing to check.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkNoRootRuntimeDependencies (workspaceRoot: string): Finding | undefined {\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!fileExists(manifestPath)) {\n return undefined\n }\n let declared: string[]\n try {\n declared = Object.keys(\n readJson<{ dependencies?: Record<string, string> }>(manifestPath).dependencies ?? {},\n )\n } catch {\n return undefined\n }\n\n return {\n check: 'no runtime dependencies in the root manifest',\n ok: declared.length === 0,\n detail: `the root package.json declares ${declared.join(', ')} — the root is private and never published, and @nx/rollup externalises only what a project's own manifest declares, so a package importing one of these ships an inlined private copy instead`,\n remedy: `move ${declared.join(', ')} into the dependencies of each package that imports it (devDependencies at the root are fine — that is what the root is for)`,\n }\n}\n\n/**\n * Checks that every rollup-built project can actually be debugged.\n *\n * @remarks\n * The invariant: a publishable library's `rollup.config.cjs` must switch\n * `sourceMap` on. Without it the build emits no `.js.map`, so VS Code cannot\n * map the running JavaScript back to the TypeScript and **every breakpoint in\n * a `.ts` file stays grey and unbound** — with no error, anywhere, to say why.\n * Found exactly that way: from the outside it looks like a broken debugger\n * rather than a build that omitted one flag.\n *\n * A config written before mnci wired this in will never fix itself, because a\n * rollup config is written once at `add` time — hence a check plus the\n * `mnci upgrade` sweep that repairs it.\n *\n * Reads through {@link resolveRollupConfigText} rather than the project's own\n * file text alone, for two reasons found in the same real workspace.\n * `@mnci/eslint-config`'s `@stylistic/key-spacing` (aligned on value) is\n * entitled to rewrite `sourceMap: true,` to `sourceMap: true,` to\n * line up with the object's longest key — `eslint --fix` is part of the\n * documented pre-commit routine, so a lint-clean config was failing this\n * check on formatting alone. And a workspace that hoists the shared\n * `withNx(...)` call into one root file and leaves each project as\n * `module.exports = require('../../rollup.base.cjs')()` has no\n * `sourceMap: true` text of its own — the flag lives one file away, so\n * reading only the project's file failed every project even when every one\n * genuinely had it on.\n *\n * The remedy is conditional on {@link canRepairRollupConfig} rather than\n * always naming `mnci upgrade`: that command edits the `},` / `{` boundary\n * between `withNx`'s two arguments, which a one-line delegation to a shared\n * base does not have. Recommending it there would send the user in a circle —\n * run the fix, watch it no-op, doctor reports the same failure again — so a\n * config in that shape gets told to edit the shared base by hand instead.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per rollup config missing the flag.\n * @throws Never - an unreadable config is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkRollupSourceMaps (workspaceRoot: string): Finding[] {\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return configs.flatMap(relativePath => {\n const configPath = join(workspaceRoot, relativePath)\n let config: string\n try {\n config = readFileSync(configPath, 'utf8')\n } catch {\n return []\n }\n\n return [\n {\n check: `source maps enabled in ${relativePath}`,\n ok: hasRollupSourceMaps(resolveRollupConfigText(configPath)),\n detail:\n 'rollup emits no .js.map without it, so a breakpoint in a .ts file can never bind',\n remedy: canRepairRollupConfig(config)\n ? 'run `mnci upgrade`, which adds it to every rollup config'\n : \"this config delegates via require() to a shared base mnci does not own — add `sourceMap: true` to withNx's first argument there by hand; `mnci upgrade` cannot repair a config in this shape\",\n },\n ]\n })\n}\n\n/**\n * Checks that every rollup-built project publishes resolvable declarations.\n *\n * @remarks\n * The invariant: a rollup config must carry the declaration-specifier plugin,\n * AND that plugin must be the version which resolves a bare relative specifier\n * against what the build actually emitted, rather than appending `.js` to all\n * of them.\n *\n * Worth a check for the same reason the source-map one is, only more so:\n * **there is no failure to notice.** The build succeeds, the package publishes,\n * runtime is unaffected (the bundle never goes through those specifiers), and\n * `skipLibCheck` — the default in most consumers — swallows the unresolved\n * import. All that happens is that every export behind a directory barrel\n * silently becomes `any` for every consumer. It was found by reading a real\n * published tarball, not by anything failing.\n *\n * Reported as two distinct findings because they are different states with\n * different histories, even though `mnci upgrade` fixes both: a config with no\n * plugin at all predates it (or was hand-edited), while a config with the\n * STALE plugin was generated by a version of mnci that had the plugin and got\n * the suffix wrong — and that second one is the dangerous one, because it\n * looks entirely healthy. In a real consuming workspace 9 of 11 published\n * packages sat in exactly that state, purely according to when each was\n * scaffolded.\n *\n * Read through {@link resolveRollupConfigText} rather than the project's own\n * file text, for the reason {@link checkRollupSourceMaps} documents: a\n * workspace that hoists `withNx(...)` into one shared base leaves each project\n * a one-line delegation, and the plugin then lives a file away.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per rollup config missing or carrying a stale plugin.\n * @throws Never - an unreadable config is skipped.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction checkDeclarationSpecifiers (workspaceRoot: string): Finding[] {\n const configs = globSync(['packages/*/rollup.config.cjs', 'libs/*/rollup.config.cjs'], {\n cwd: workspaceRoot,\n }).map(config => toPosix(config))\n\n return configs.flatMap(relativePath => {\n const configPath = join(workspaceRoot, relativePath)\n if (!fileExists(configPath)) {\n return []\n }\n const resolved = resolveRollupConfigText(configPath)\n if (!hasDeclarationSpecifierPlugin(resolved)) {\n return [\n {\n check: `${relativePath} normalises declaration specifiers`,\n ok: false,\n detail:\n 'no mnci-normalise-declaration-specifiers plugin — the emitted .d.ts files keep the extensionless imports tsconfig.lib.json allows, which a nodenext consumer cannot resolve',\n // Deliberately NOT `mnci upgrade`, which would send the user in a\n // circle: `upgradeDeclarationSpecifierPlugins` only rewrites a\n // plugin that is already there, and `repairDeclarationSpecifiers`\n // is anchored on the generator's own placeholder comment, which a\n // config in this state no longer has. Verified by running the\n // command against exactly this shape — it no-ops. Naming a command\n // that silently does nothing is the failure mode this file's own\n // source-map check already goes out of its way to avoid.\n remedy:\n \"add the plugin to withNx's second argument by hand (copy it from a freshly `mnci add npm-lib`'d project) — `mnci upgrade` only updates a plugin that is already present, it cannot restore a missing one\",\n },\n ]\n }\n\n return [\n {\n check: `${relativePath} resolves directory barrels in declarations`,\n ok: hasDirectoryAwareDeclarationSpecifiers(resolved),\n detail:\n 'the plugin appends .js to every bare specifier, including ones naming a directory barrel, which needs /index.js — the named file is never emitted, so every export behind it publishes as `any` with nothing reporting it',\n remedy: 'run `mnci upgrade`, which replaces the plugin body with the current one',\n },\n ]\n })\n}\n\n/**\n * Collects every doctor finding for a workspace.\n *\n * @remarks\n * Exported separately from {@link runDoctor} so the checks can be asserted\n * directly, without capturing console output or an exit code.\n *\n * Every check corresponds to an invariant that has actually been violated in\n * practice, in this repo or in a workspace it generated — none are hypothetical.\n * That is the bar for adding one: a check nobody has ever needed is noise that\n * trains people to ignore the output.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The findings, in report order.\n * @throws Error when `workspaceRoot` has no `nx.json`.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function collectFindings (workspaceRoot: string): Finding[] {\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n if (!fileExists(nxJsonPath)) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — run 'mnci doctor' from the workspace root.`,\n )\n }\n const nxJson = readJson<{\n plugins?: unknown[]\n mnci?: { registry?: RegistryConfig; scope?: string }\n }>(nxJsonPath)\n\n return [\n ...checkEslintConfigs(workspaceRoot),\n checkNoRetiredFormatter(workspaceRoot),\n checkEslintPlugin(nxJson),\n checkResolvedEslint(workspaceRoot),\n checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),\n checkNpmrcCredentialResolves(workspaceRoot),\n checkNpmAuthMatchesPipeline(workspaceRoot, nxJson.mnci?.registry),\n checkNoRootRuntimeDependencies(workspaceRoot),\n ...checkRollupSourceMaps(workspaceRoot),\n ...checkDeclarationSpecifiers(workspaceRoot),\n ...checkVersionActions(workspaceRoot),\n ...checkTargetFilesExist(workspaceRoot),\n checkSync(workspaceRoot),\n ].filter((finding): finding is Finding => finding !== undefined)\n}\n\n/**\n * Reports on the mnci invariants a workspace is supposed to uphold.\n *\n * @remarks\n * Read-only by design: it never edits the workspace, so it is safe to run\n * anywhere, and every failing finding names the command that fixes it (usually\n * `mnci upgrade`) rather than fixing it silently.\n *\n * Exits non-zero when anything failed, so it works as a CI step as well as a\n * local command. Sets `process.exitCode` rather than calling `process.exit`, so\n * output is never truncated.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an Nx workspace.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runDoctor (workspaceRoot: string): void {\n const findings = collectFindings(workspaceRoot)\n const failed = findings.filter(finding => !finding.ok)\n\n for (const finding of findings) {\n if (finding.ok) {\n logger.success(finding.check)\n } else {\n logger.error(`${finding.check} — ${finding.detail ?? 'failed'}`)\n if (finding.remedy) {\n logger.info(` fix: ${finding.remedy}`)\n }\n }\n }\n\n if (failed.length === 0) {\n logger.success(`All ${findings.length} checks passed.`)\n\n return\n }\n logger.error(`${failed.length} of ${findings.length} checks failed.`)\n process.exitCode = 1\n}\n","import { cpSync, existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Moving a freshly generated workspace into a directory that already exists.\n *\n * @remarks\n * `create-nx-workspace <name>` refuses a directory that is already there\n * (`DIRECTORY_EXISTS`), which rules out the single most common way a repository\n * starts: the host creates it, you clone it, and the clone holds a `.git`\n * directory and possibly a README. Generating elsewhere and moving the result\n * in is the only way through, and doing it by hand is exactly the kind of step\n * that silently loses a file.\n *\n * So the move is the thing that gets written down and tested, rather than left\n * to the reader. The whole risk is in the collisions, so every one of them is\n * decided in advance and reported, and nothing is copied until the target is\n * known to be safe.\n */\n\n/**\n * Entries that never travel: the target's own `.git` is the point of the\n * exercise, and `node_modules` is reinstalled from scratch straight after (the\n * lockfile and the tree are both discarded so npm re-resolves with the\n * overlay's `overrides` in place).\n */\nconst NEVER_COPIED: ReadonlySet<string> = new Set(['.git', 'node_modules'])\n\n/**\n * Entries the generator writes only for the overlay to strip or replace, so an\n * existing copy can always win.\n *\n * @remarks\n * `create-nx-workspace` (Nx 23.2) writes its AI-agent rules and skills into\n * every agent's conventional location: `CLAUDE.md` and `AGENTS.md` (inside a\n * marked block), `.claude/settings.json`, `.agents`, `.codex`, `.cursor`,\n * `.gemini`, `.opencode`, `opencode.json`, plus `.github/agents`,\n * `.github/prompts`, `.github/skills` and a `.github/workflows/ci.yml`. The\n * overlay that runs straight\n * after adoption removes or replaces every one of them\n * (`NX_SCAFFOLDING_TO_REMOVE`, `removeNxAuthoredAgentFiles`, and the CI file\n * it writes itself), so the generated copy never survives anyway.\n *\n * Treating them as collisions therefore rejected the case `--into` exists\n * for while protecting nothing: a repository with its own `CLAUDE.md` could\n * not be adopted at all. Found adopting Lore Master (russoedu/MarkDoc),\n * whose planning `CLAUDE.md` predates the workspace. The same holds for a\n * repository's own `.github` (issue templates, workflows): mnci's CI file is\n * written by the overlay, not copied from the generator, so keeping the\n * existing directory loses only Nx's agent skills.\n *\n */\nconst AGENT_AND_CI_ENTRIES: readonly string[] = [\n 'AGENTS.md',\n 'CLAUDE.md',\n '.claude',\n '.agents',\n '.codex',\n '.cursor',\n '.gemini',\n '.opencode',\n 'opencode.json',\n '.github',\n]\n\n/**\n * Files where an existing copy WINS, because they belong to whoever created\n * the repository rather than to the generator, or because the generator's\n * copy is stripped by the overlay anyway ({@link AGENT_AND_CI_ENTRIES}).\n *\n * @remarks\n * A host-created repository routinely arrives with a README and a licence\n * chosen by a human. `create-nx-workspace` writes a README of its own, and\n * treating that as a collision would reject the very case this exists for,\n * while overwriting it would throw away the only hand-written file in the\n * repository. Neither is right, so the existing file stays and the result says\n * so.\n */\nconst EXISTING_WINS: ReadonlySet<string> = new Set([\n 'README.md',\n 'LICENSE',\n 'LICENSE.md',\n 'LICENSE.txt',\n ...AGENT_AND_CI_ENTRIES,\n])\n\n/**\n * Entries whose presence is fatal, checked BEFORE generating anything.\n *\n * @remarks\n * Generating a workspace takes minutes, so the hopeless cases are worth\n * catching first. Any of these means the directory is already a project of some\n * kind, and adopting it would overwrite real work.\n */\nconst PREFLIGHT_BLOCKERS: readonly string[] = [\n 'package.json',\n 'nx.json',\n 'tsconfig.base.json',\n 'node_modules',\n 'apps',\n 'libs',\n 'packages',\n]\n\n/**\n * What {@link adoptGeneratedWorkspace} did, for reporting.\n *\n * @remarks\n * Every entry the adoption touched is accounted for in exactly one of these\n * lists, so the caller can print what happened to a directory the user already\n * owned rather than asking them to diff it afterwards.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface AdoptionResult {\n /** Top-level entries copied in from the generated workspace. */\n copied: string[]\n /** Top-level entries left exactly as they were found (see {@link EXISTING_WINS}). */\n kept: string[]\n /** `true` when the generated `.gitignore` contributed lines to an existing one. */\n mergedGitignore: boolean\n}\n\n/**\n * Rejects a target directory that cannot be adopted, before anything is\n * generated.\n *\n * @remarks\n * Deliberately cheap and deliberately incomplete: it catches a directory that\n * is already a project, so the caller does not spend minutes generating a\n * workspace it is about to throw away. The full collision check happens in\n * {@link adoptGeneratedWorkspace}, where the generated tree is known, and that\n * one still runs before a single file is written.\n *\n * @param target - Absolute path to the directory to adopt.\n * @throws Error when the path is missing, is not a directory, or already holds\n * a project.\n * @returns Nothing; it either passes or throws.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function assertAdoptableDirectory (target: string): void {\n if (!existsSync(target)) {\n throw new Error(`--into '${target}' does not exist. Create or clone it first.`)\n }\n if (!statSync(target).isDirectory()) {\n throw new Error(`--into '${target}' is not a directory.`)\n }\n\n const blockers = PREFLIGHT_BLOCKERS.filter(entry => existsSync(join(target, entry)))\n if (blockers.length > 0) {\n throw new Error(\n `--into '${target}' already holds ${blockers.join(', ')}, so it is a project already. ` +\n 'Use `mnci upgrade` to re-apply the overlay to an existing workspace, or point --into at an empty checkout.',\n )\n }\n}\n\n/**\n * Appends whatever the generated `.gitignore` adds to an existing one.\n *\n * @remarks\n * A merge rather than a choice, because both halves matter and neither is\n * optional. The repository's own file may ignore things the generator knows\n * nothing about; the generated one ignores `.nx/cache`, `dist` and\n * `out-tsc`, and a workspace that commits those is a workspace whose every\n * build shows up as a diff. Lines already present are not repeated, so running\n * it twice changes nothing.\n *\n * @param generatedFile - The generated `.gitignore`.\n * @param targetFile - The existing `.gitignore`, which is rewritten in place.\n * @returns `true` when at least one line was added.\n * @throws Propagates filesystem errors.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction mergeGitignore (generatedFile: string, targetFile: string): boolean {\n const existing = readFileSync(targetFile, 'utf8')\n const present = new Set(existing.split('\\n').map(line => line.trim()))\n const additions = readFileSync(generatedFile, 'utf8')\n .split('\\n')\n .map(line => line.trimEnd())\n .filter(line => {\n const trimmed = line.trim()\n\n return trimmed !== '' && !trimmed.startsWith('#') && !present.has(trimmed)\n })\n\n if (additions.length === 0) return false\n\n const separator = existing.endsWith('\\n') ? '' : '\\n'\n writeFileSync(\n targetFile,\n `${existing}${separator}\\n# Added by mnci (Nx workspace)\\n${additions.join('\\n')}\\n`,\n )\n\n return true\n}\n\n/**\n * Moves a generated workspace into an existing directory, keeping its `.git`.\n *\n * @remarks\n * Every collision is resolved before anything is written, so a refusal leaves\n * the target byte-identical to how it was found. The generated tree is\n * discarded by the caller either way.\n *\n * @param generatedRoot - The freshly generated workspace (a staging directory).\n * @param target - The existing directory to adopt.\n * @returns What was copied, kept, and merged.\n * @throws Error naming every colliding entry, when any exists that is not\n * resolved by {@link EXISTING_WINS} or the `.gitignore` merge.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function adoptGeneratedWorkspace (generatedRoot: string, target: string): AdoptionResult {\n const entries = readdirSync(generatedRoot).filter(entry => !NEVER_COPIED.has(entry))\n\n const collisions = entries.filter(\n entry =>\n existsSync(join(target, entry)) && !EXISTING_WINS.has(entry) && entry !== '.gitignore',\n )\n if (collisions.length > 0) {\n throw new Error(\n `--into '${target}' already contains ${collisions.join(', ')}, which the new workspace also writes. ` +\n 'Move those aside and run it again; nothing has been changed.',\n )\n }\n\n const result: AdoptionResult = { copied: [], kept: [], mergedGitignore: false }\n\n for (const entry of entries) {\n const from = join(generatedRoot, entry)\n const to = join(target, entry)\n\n if (entry === '.gitignore' && existsSync(to)) {\n result.mergedGitignore = mergeGitignore(from, to)\n result.kept.push(entry)\n continue\n }\n if (existsSync(to)) {\n result.kept.push(entry)\n continue\n }\n\n cpSync(from, to, { recursive: true })\n result.copied.push(entry)\n }\n\n // The staging copy has served its purpose, and it carries a `.git` of its\n // own (`create-nx-workspace` initialises one), which is worth not leaving\n // behind in a temp directory.\n rmSync(generatedRoot, { recursive: true, force: true })\n\n return result\n}\n","import { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { basename, join, resolve } from 'node:path'\nimport { runFormatter, runNpx, runShell } from '../nx-workspace'\nimport {\n applyOverlay,\n DEFAULT_STACK,\n resolveNpmAuth,\n type CiProvider,\n type RegistryConfig,\n type StackConfig,\n} from '../workspace-overlay'\nimport { promptCi, promptNxCloud, promptRegistry, promptStack, promptText } from '../terminal'\nimport { logger } from '../terminal'\nimport { assertValidProjectName } from '../project-name'\nimport { adoptGeneratedWorkspace, assertAdoptableDirectory } from './adopt-directory.use-case'\n\n/**\n * Options accepted by {@link runNew}.\n *\n * @remarks\n * Mirrors the CLI's flags; every field left `undefined` is prompted for\n * interactively (unless `yes` short-circuits to defaults).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface NewOptions {\n /** Skip prompts, accepting defaults for everything not passed as a flag. */\n yes?: boolean\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope?: string\n /** Registry kind: `azure-artifacts` or `npm`. */\n registry?: RegistryConfig['kind']\n /** Azure DevOps organization (azure-artifacts only). */\n organization?: string\n /** Azure DevOps project (azure-artifacts only). */\n project?: string\n /** Azure Artifacts feed name (azure-artifacts only). */\n artifactsFeed?: string\n /** CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent?: string\n /** Library variable group holding the base64 npm `PAT`. */\n variableGroup?: string\n /** How npm authenticates to an Azure Artifacts feed: `pat` | `build-identity`. */\n npmAuth?: string\n /** CI provider: `azure` | `github` | `both`. */\n ci?: CiProvider\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n /** Opt in to Nx Cloud (remote caching + CI insights). Default: not connected. */\n nxCloud?: boolean\n /**\n * Bootstrap into an existing directory (typically a fresh clone holding\n * nothing but `.git`) instead of creating a new one.\n */\n into?: string\n}\n\n/**\n * Maps the chosen CI provider to the `create-nx-workspace --nxCloud` value\n * used when opting in.\n *\n * @remarks\n * Deliberately never `--nxCloud=yes` (the provider-agnostic value): verified\n * empirically that it prompts \"Will you be using GitHub as your git hosting\n * provider?\" even with `--useGitHub` and `--no-interactive` both set, and\n * exits without creating the workspace at all when stdin is not a TTY — a\n * real upstream inconsistency in `create-nx-workspace`, not something this\n * CLI can configure around. A named provider (`github`/`azure`/…) sidesteps\n * that prompt entirely and completes non-interactively every time.\n *\n * The provider value only controls the throwaway CI workflow file\n * `create-nx-workspace` writes as a side effect of Cloud setup — this CLI's\n * own {@link applyOverlay} unconditionally overwrites whatever lands at that\n * same path right after, so the specific value has no visible effect beyond\n * avoiding the hang. `both` has no Nx equivalent, so it maps to `github`\n * (arbitrary but harmless, for the same reason).\n *\n * @param ci - The chosen CI provider.\n * @returns The `--nxCloud` value to pass when Nx Cloud is opted into.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction nxCloudProviderValue (ci: CiProvider): 'github' | 'azure' {\n return ci === 'azure' ? 'azure' : 'github'\n}\n\n/**\n * Resolves the stack from flags, prompts, or `--yes` defaults.\n *\n * @remarks\n * The testRunner flag is taken as-is if passed; otherwise the interactive\n * {@link promptStack} runs and fills the gap, or `--yes` uses the default.\n *\n * @param options - The CLI flags.\n * @returns The resolved stack configuration.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function resolveStack (options: NewOptions): Promise<StackConfig> {\n // The flag skips the prompt entirely: the stack is one choice now that the\n // linter is no longer part of it.\n if (options.testRunner || options.yes) {\n return { testRunner: options.testRunner ?? DEFAULT_STACK.testRunner }\n }\n const prompted = await promptStack()\n\n return { testRunner: prompted.testRunner }\n}\n\n/**\n * Resolves the CI provider from a flag, `--yes`'s default, or a prompt.\n *\n * @remarks\n * `azure` stays the `--yes`/flagless default — the long-standing behaviour —\n * so an existing flagless `mnci new` keeps writing exactly the same file it\n * always has. An explicit flag value is trusted as-is with no validation\n * (matching `resolveRegistry`'s equally loose `--registry` handling): a\n * typo just falls through to the flagless default instead of prompting.\n *\n * @param options - The CLI flags.\n * @returns The resolved CI provider.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nconst CI_PROVIDERS: ReadonlySet<CiProvider> = new Set(['azure', 'github', 'both'])\n\nasync function resolveCi (options: NewOptions): Promise<CiProvider> {\n if (options.ci && CI_PROVIDERS.has(options.ci)) {\n return options.ci\n }\n if (options.yes) {\n return 'azure'\n }\n\n return await promptCi()\n}\n\n/**\n * Resolves the registry configuration from flags, prompts or defaults.\n *\n * @param options - The CLI flags.\n * @returns The resolved registry configuration.\n * @throws Propagates prompt errors (e.g. when stdin is not a TTY).\n * @typeParam None - this function has no generic type parameters.\n */\nasync function resolveRegistry (options: NewOptions): Promise<RegistryConfig> {\n if (options.registry === 'azure-artifacts' || (options.organization && options.artifactsFeed)) {\n return {\n kind: 'azure-artifacts',\n organization: options.organization ?? (await promptText('Azure DevOps organization')),\n project: options.project ?? (await promptText('Azure DevOps project')),\n artifactsFeed: options.artifactsFeed ?? (await promptText('Artifacts feed name')),\n }\n }\n if (options.registry === 'npm' || options.yes) {\n return { kind: 'npm' }\n }\n\n return await promptRegistry()\n}\n\n/**\n * Creates a brand-new monorepo: Nx's own TS preset plus this CLI's overlay.\n *\n * @remarks\n * The heavy lifting is `create-nx-workspace --preset=ts` (npm workspaces +\n * TypeScript project references, no per-project `project.json`). This then\n * applies its one layer of opinion ({@link applyOverlay}) and installs the\n * conventional-commit toolchain (`husky` + `@commitlint/*`) for real, so the\n * versions resolve at generation time.\n *\n * @param name - The workspace (and directory) name.\n * @param options - The CLI flags.\n * @returns A promise that resolves when the workspace is ready.\n * @throws Error when any underlying command exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runNew (name: string | undefined, options: NewOptions): Promise<void> {\n // `--into` doubles as the name when none is given, because the directory is\n // already named: a cloned repository's folder IS the workspace name, and\n // retyping it is a way to get the two out of step.\n const adoptTarget = options.into === undefined ? undefined : resolve(process.cwd(), options.into)\n // Checked before a single prompt, let alone the minutes `create-nx-workspace`\n // takes. Finding out that the target is unusable after generating into a\n // temp directory is finding out too late.\n if (adoptTarget !== undefined) assertAdoptableDirectory(adoptTarget)\n\n const workspaceName =\n name ?? (adoptTarget === undefined ? await promptText('Workspace name') : basename(adoptTarget))\n // Fails fast, before any further prompt or side effect: the name becomes a\n // directory, a `create-nx-workspace` argument and (derived) an npm scope, so\n // a bad one should never get this far — and an explicitly empty `name`\n // argument bypasses promptText's own non-empty check, which only fires on\n // the prompted path.\n assertValidProjectName(workspaceName, 'Workspace name')\n const scope =\n options.scope ??\n (options.yes\n ? `@${workspaceName}`\n : await promptText('npm scope for publishable packages', `@${workspaceName}`))\n const registry = await resolveRegistry(options)\n const ci = await resolveCi(options)\n const agent =\n options.agent ??\n (options.yes\n ? 'ubuntu-latest'\n : await promptText(\n 'CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)',\n 'ubuntu-latest',\n ))\n // The variable group is an Azure Pipelines concept (GitHub reads a plain\n // `PAT` repository secret instead, no CLI-collected name needed) — skipped\n // when Azure is not one of the chosen providers.\n const variableGroup =\n ci === 'github'\n ? (options.variableGroup ?? 'Build')\n : (options.variableGroup ??\n (options.yes\n ? 'Build'\n : await promptText('Azure DevOps variable group holding the npm PAT', 'Build')))\n // Validated before anything is created: a refused combination must not leave a\n // half-generated workspace behind.\n const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci)\n const stack = await resolveStack(options)\n const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud())\n\n // `create-nx-workspace <name>` insists on creating the directory itself and\n // exits with DIRECTORY_EXISTS otherwise, so adopting one means generating\n // somewhere else and moving the result in. A temp directory rather than a\n // sibling of the target, so a failed run leaves nothing next to the\n // repository to clean up by hand.\n const stagingParent =\n adoptTarget === undefined ? process.cwd() : mkdtempSync(join(tmpdir(), 'mnci-new-'))\n\n let workspaceRoot = join(stagingParent, workspaceName)\n try {\n logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`)\n runNpx(\n [\n '--yes',\n 'create-nx-workspace@latest',\n workspaceName,\n '--preset=ts',\n '--pm=npm',\n nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : '--nxCloud=skip',\n '--no-interactive',\n ],\n stagingParent,\n )\n\n if (adoptTarget !== undefined) {\n logger.step(`Adopting ${adoptTarget} (keeping its .git)`)\n // Throws on any collision it cannot resolve, having written nothing.\n const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget)\n for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`)\n if (adoption.mergedGitignore) logger.detail('merged the generated .gitignore into the existing one')\n workspaceRoot = adoptTarget\n }\n } finally {\n // The staging tree goes on EVERY path. An earlier comment here claimed the\n // failure path cleaned up \"by the process exiting\", which a temp directory\n // never does: a refused adoption left a full workspace, node_modules\n // included, in the OS temp dir on every attempt. Only the temp tree is\n // removed - without `--into`, `stagingParent` is the user's cwd.\n if (adoptTarget !== undefined) rmSync(stagingParent, { recursive: true, force: true })\n }\n\n logger.step(\n 'Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)',\n )\n applyOverlay(\n workspaceRoot,\n { workspaceName, scope, registry, agent, variableGroup, ci, stack, ...(npmAuth && { npmAuth }) },\n logger.detail,\n )\n\n // npm honours `overrides` only when it RESOLVES a tree, and by this point\n // `create-nx-workspace` has already installed one and written its lockfile —\n // both without the overrides `applyOverlay` just added. npm then reuses that\n // tree wholesale on the next install, so the overrides are silently ignored.\n //\n // This is not theoretical, and it is npm-version dependent, which is what\n // made it survive three nightlies. Reproduced on nx 23.1.1, which nests a\n // vulnerable `brace-expansion@5.0.8` under `node_modules/nx`:\n //\n // npm 10.9.7 create → overlay → install → 0 advisories\n // npm 11.19.0 create → overlay → install → 6 advisories\n //\n // So the generated workspace shipped six high advisories while this repo —\n // whose own lockfile was resolved WITH the overrides present — read 0, and\n // every local check agreed with the wrong one.\n //\n // Both artifacts have to go. Removing only the lockfile still reports 6 under\n // npm 11: an existing `node_modules` is by itself enough for npm to keep the\n // stale tree. Measured both ways rather than assumed.\n rmSync(join(workspaceRoot, 'node_modules'), { recursive: true, force: true })\n rmSync(join(workspaceRoot, 'package-lock.json'), { force: true })\n\n // One install. It also pulls in everything `applyOverlay` just added to the\n // manifest — Prettier, ESLint and `@mnci/eslint-config` — which is why the\n // formatting pass below can run immediately after. It resolves from scratch,\n // by construction of the two removals above.\n logger.step('Installing the toolchain (commitlint + husky + linting/formatting)')\n const installStatus = runShell(\n 'npm',\n ['install', '--save-dev', 'husky', '@commitlint/cli', '@commitlint/config-conventional'],\n workspaceRoot,\n )\n if (installStatus !== 0) {\n throw new Error(`npm install of the toolchain failed with exit code ${installStatus}`)\n }\n // The overlay already stamped `prepare: husky` into the root scripts.\n runShell('npx', ['husky'], workspaceRoot)\n\n // `create-nx-workspace` wrote its scaffold in its own style, which is not\n // mnci's. Normalise it now so the workspace passes its own `lint` from the\n // very first commit.\n logger.step('Formatting the workspace (eslint --fix, JavaScript Standard Style)')\n runFormatter(workspaceRoot)\n\n logger.success('Done. Next steps:')\n if (adoptTarget === undefined) logger.info(` cd ${workspaceName}`)\n logger.info(' mnci add react-app web # or: react-lib, react-internal-lib, node-app,')\n logger.info(' # node-function-app, npm-lib, internal-lib,')\n logger.info(\n ' # python-app, python-function-app, python-lib, python-internal-lib,',\n )\n logger.info(\n ' # go-app, go-function-app, go-lib, go-internal-lib,',\n )\n logger.info(\n ' # flutter-app, flutter-lib, flutter-internal-lib,',\n )\n logger.info(' # vscode-extension')\n logger.info(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../file-system'\nimport { runAdd } from '../project-scaffolding'\nimport { runNew } from './create-workspace.use-case'\nimport { runUpgrade } from '../workspace-upgrade'\n\n/** The three things the wizard can start: scaffold, add to, or upgrade a workspace. */\ntype InteractiveAction = 'new' | 'add' | 'upgrade'\n\n/**\n * Runs the guided wizard shown when `mnci` is invoked with no arguments.\n *\n * @remarks\n * A thin dispatcher: it picks between the three commands, then hands off to\n * the existing flows — {@link runNew}, {@link runAdd} and {@link runUpgrade} —\n * which already handle every field they need (name/scope/registry/agent/\n * variable group for `new`; kind/name, and the npm-lib scope on this bare\n * path, for `add`; whatever `mnci new`/a previous upgrade persisted, for\n * `upgrade`). Nothing about the prompting or resolution lives here, so the\n * wizard and the flag-driven commands can never drift.\n *\n * The menu is ordered by context: inside a workspace (an `nx.json` in the cwd)\n * `add` is offered first, then `upgrade`, then `new` — so the default\n * highlight matches what the user most likely wants. All three are always\n * shown; picking `add` or `upgrade` outside a workspace surfaces that flow's\n * own clear \"run from the workspace root\" error.\n *\n * @param None - this function takes no parameters.\n * @returns A promise that resolves when the chosen flow completes.\n * @throws Propagates prompt errors and any failure from the dispatched flow.\n * @typeParam None - this function has no generic type parameters.\n */\nexport async function runInteractive (): Promise<void> {\n const inWorkspace = fileExists(join(process.cwd(), 'nx.json'))\n const choiceNew = { name: 'Create a new monorepo', value: 'new' as const }\n const choiceAdd = { name: 'Add a project to this workspace', value: 'add' as const }\n const choiceUpgrade = {\n name: 'Upgrade this workspace (re-apply the latest overlay)',\n value: 'upgrade' as const,\n }\n\n const action = await select<InteractiveAction>({\n message: 'What would you like to do?',\n choices: inWorkspace\n ? [choiceAdd, choiceUpgrade, choiceNew]\n : [choiceNew, choiceAdd, choiceUpgrade],\n })\n\n if (action === 'new') {\n await runNew(undefined, {})\n } else if (action === 'upgrade') {\n runUpgrade(process.cwd(), {})\n } else {\n await runAdd(undefined, undefined, {})\n }\n}\n","import { readdirSync, readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport { runFormatter } from '../nx-workspace'\nimport {\n addGoPlatformTargets,\n GO_RELEASE_SCRIPT_PATH,\n pinVscodeExtensionProjectNames,\n refreshGoReleaseScript,\n refreshVscodeExtensionScript,\n VSCODE_EXTENSION_SCRIPT_PATH,\n} from '../project-scaffolding'\nimport {\n repairPublishableManifests,\n repairRollupSourceMaps,\n upgradeDeclarationSpecifierPlugins,\n} from '../rollup-library'\nimport {\n applyOverlay,\n readMnciConfig,\n resolveNpmAuth,\n type CiProvider,\n type NpmAuthMode,\n type OverlayOptions,\n type RegistryConfig,\n type StackConfig,\n} from '../workspace-overlay'\nimport { fileExists } from '../file-system'\nimport { logger } from '../terminal'\n\n/**\n * Options accepted by {@link runUpgrade}.\n *\n * @remarks\n * Mirrors `mnci new`'s flags exactly — an explicit flag overrides whatever\n * `mnci new`/a previous `upgrade` persisted in `nx.json`'s `mnci` block\n * ({@link readMnciConfig}); everything left `undefined` falls back to that\n * persisted value. Unlike `new`, there is no `--yes`/prompt fallback: a\n * workspace missing a persisted field (predates persistence, or had it\n * hand-edited away) needs that one flag passed explicitly, reported as a\n * clear error rather than guessed or prompted for.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface UpgradeOptions {\n /** The npm scope for publishable packages (e.g. `@demo`). */\n scope?: string\n /** Registry kind: `azure-artifacts` or `npm`. */\n registry?: RegistryConfig['kind']\n /** Azure DevOps organization (azure-artifacts only). */\n organization?: string\n /** Azure DevOps project (azure-artifacts only). */\n project?: string\n /** Azure Artifacts feed name (azure-artifacts only). */\n artifactsFeed?: string\n /** CI build agent — a Microsoft-hosted vmImage or a self-hosted pool name. */\n agent?: string\n /** Library variable group holding the base64 npm `PAT`. */\n variableGroup?: string\n /** How npm authenticates to an Azure Artifacts feed: `pat` | `build-identity`. */\n npmAuth?: string\n /** CI provider: `azure` | `github` | `both`. */\n ci?: CiProvider\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n}\n\n/**\n * Resolves the registry from flags, falling back to whatever is persisted.\n *\n * @remarks\n * Mirrors `new.ts`'s `resolveRegistry`, minus the interactive-prompt\n * fallback: `upgrade` is as likely to run unattended (a script, a CI step)\n * as interactively, so a genuinely missing value is a clear thrown error,\n * not a prompt that would hang a non-interactive run.\n *\n * @param options - The CLI flags.\n * @param persisted - The registry persisted in `nx.json`'s `mnci` block, if any.\n * @returns The resolved registry configuration.\n * @throws Error when Azure Artifacts coordinates or a registry kind cannot\n * be resolved from either flags or the persisted config.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveRegistry (\n options: UpgradeOptions,\n persisted: RegistryConfig | undefined,\n): RegistryConfig {\n if (options.registry === 'azure-artifacts' || (options.organization && options.artifactsFeed)) {\n const persistedAzure = persisted?.kind === 'azure-artifacts' ? persisted : undefined\n const organization = options.organization ?? persistedAzure?.organization\n const project = options.project ?? persistedAzure?.project\n const artifactsFeed = options.artifactsFeed ?? persistedAzure?.artifactsFeed\n if (!organization || !project || !artifactsFeed) {\n throw new Error(\n \"Azure Artifacts registry needs --organization, --project and --artifacts-feed (none found in nx.json's persisted config either).\",\n )\n }\n\n return { kind: 'azure-artifacts', organization, project, artifactsFeed }\n }\n if (options.registry === 'npm') {\n return { kind: 'npm' }\n }\n if (persisted) {\n return persisted\n }\n throw new Error(\n \"No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed).\",\n )\n}\n\n/**\n * The workspace's own name, for the `<name>.code-workspace` file the overlay writes.\n *\n * @remarks\n * This exists because `resolveOverlayOptions` used to omit `workspaceName`\n * entirely, so `applyOverlay` interpolated `undefined` and every `mnci upgrade`\n * wrote a file literally named `undefined.code-workspace` — while the\n * workspace's real one was never rewritten, making the `.code-workspace` the one\n * mnci-owned file an upgrade could not actually carry a fix into.\n *\n * A fallback chain rather than a single source, because the authoritative one is\n * only available going forward:\n *\n * 1. **The persisted `mnci.workspaceName`** — written by `mnciConfig` as of this\n * fix, so correct for anything generated or upgraded from now on.\n * 2. **An existing `*.code-workspace` filename** — accurate for a workspace\n * generated before that field was persisted. `undefined.code-workspace` is\n * skipped explicitly: a workspace carrying junk from the old bug would\n * otherwise resolve its name as the string `undefined` and keep reproducing\n * it forever.\n * 3. **The directory basename** — what `create-nx-workspace` names the directory,\n * so it is right in the ordinary case and always available.\n *\n * The root `package.json` name is deliberately *not* in the chain: it is\n * `@<scope>/source`, which carries the scope rather than the workspace name, and\n * the two differ whenever `--scope` was passed explicitly.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @param persisted - Whatever `mnci new`/a previous `upgrade` persisted.\n * @returns The workspace name.\n * @throws Never - an unreadable directory falls through to the basename.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveWorkspaceName (workspaceRoot: string, persisted: Partial<OverlayOptions>): string {\n if (persisted.workspaceName) {\n return persisted.workspaceName\n }\n try {\n const existing = readdirSync(workspaceRoot).find(\n file => file.endsWith('.code-workspace') && file !== 'undefined.code-workspace',\n )\n if (existing) {\n return existing.replace(/\\.code-workspace$/, '')\n }\n } catch {\n // Fall through to the basename.\n }\n\n return basename(workspaceRoot)\n}\n\n/**\n * Finds the npm auth mode a workspace already uses, for one that never recorded it.\n *\n * @remarks\n * `npmAuthenticate@0` in the existing pipeline is an unambiguous statement of\n * intent: nothing else writes it, and `mnci` did not generate it until this option\n * existed, so a workspace that has it added it by hand. Without this, an upgrade\n * would regenerate the pipeline without the task and the `.npmrc` with a PAT block -\n * silently reverting the one thing that makes a feed publish work, which is exactly\n * how a real workspace lost it. Consulted only when neither a flag nor a persisted\n * value says otherwise, so `--npm-auth pat` still wins.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns `build-identity` when the existing pipeline runs the task, else `undefined`.\n * @throws Never - an unreadable pipeline is treated as absent.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction detectNpmAuth (workspaceRoot: string): NpmAuthMode | undefined {\n try {\n return readFileSync(join(workspaceRoot, 'azure-pipelines.yml'), 'utf8').includes('npmAuthenticate@0')\n ? 'build-identity'\n : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Resolves the full overlay options a `mnci upgrade` run applies: an\n * explicit flag wins field-by-field, otherwise the persisted `mnci` block\n * (see {@link readMnciConfig}) is the default.\n *\n * @param workspaceRoot - Absolute path to the workspace, for\n * {@link resolveWorkspaceName}'s filename and basename fallbacks.\n * @param options - The CLI flags.\n * @param persisted - Whatever `mnci new`/a previous `upgrade` persisted.\n * @returns The fully resolved overlay options.\n * @throws Error when a required field is missing from both the flags and\n * the persisted config, naming the flag needed to supply it.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction resolveOverlayOptions (\n workspaceRoot: string,\n options: UpgradeOptions,\n persisted: Partial<OverlayOptions>,\n): OverlayOptions {\n const scope = options.scope ?? persisted.scope\n if (!scope) {\n throw new Error(\"No npm scope found in nx.json's persisted config. Pass --scope explicitly.\")\n }\n const registry = resolveRegistry(options, persisted.registry)\n const ci = options.ci ?? persisted.ci\n if (!ci) {\n throw new Error(\n \"No CI provider found in nx.json's persisted config. Pass --ci azure|github|both explicitly.\",\n )\n }\n const agent = options.agent ?? persisted.agent\n if (!agent) {\n throw new Error(\n \"No CI build agent found in nx.json's persisted config. Pass --agent explicitly.\",\n )\n }\n // Azure-only concept; a github-only workspace never needed one, so a\n // missing persisted value is not an error the way scope/ci/agent are.\n const variableGroup = options.variableGroup ?? persisted.variableGroup ?? 'Build'\n // A flag wins, then what was persisted, then what an existing pipeline already\n // does. Detection only counts where the mode is deliverable: a workspace that also\n // carries a GitHub pipeline cannot be moved to build identity by inference.\n const detected =\n ci === 'azure' && persisted.npmAuth === undefined && options.npmAuth === undefined\n ? detectNpmAuth(workspaceRoot)\n : undefined\n if (detected) {\n logger.detail('keeping build-identity npm auth: azure-pipelines.yml already runs npmAuthenticate@0')\n }\n const npmAuth = resolveNpmAuth(options.npmAuth, registry, ci, persisted.npmAuth ?? detected)\n // Defaults to `eslint` rather than erroring when absent, unlike testRunner\n const testRunner = options.testRunner ?? persisted.stack?.testRunner\n if (!testRunner) {\n throw new Error(\n \"No testRunner found in nx.json's persisted config. Pass --test-runner explicitly.\",\n )\n }\n\n return {\n workspaceName: resolveWorkspaceName(workspaceRoot, persisted),\n scope,\n registry,\n agent,\n variableGroup,\n ci,\n stack: { testRunner },\n ...(npmAuth && { npmAuth }),\n }\n}\n\n/**\n * Re-applies the latest MoNecromanCI overlay to an already-generated\n * workspace.\n *\n * @remarks\n * Every improvement to `overlay.ts` since a workspace's `mnci new` only ever\n * reached *future* workspaces until now — nothing let an existing one pick\n * up a later fix (the tag-push ordering fix, the global Python workspace\n * install, the Windows `python`/`python3` resolution, all landed this way).\n * `upgrade` closes that gap: it resolves the same {@link OverlayOptions}\n * `new` would have (explicit flags over whatever `mnci new` persisted — see\n * {@link resolveOverlayOptions}) and calls {@link applyOverlay} again, the\n * exact same pure, idempotent file-writer `new` itself calls. No diffing or\n * confirmation prompt: every file `applyOverlay` touches is `mnci`-owned\n * (`nx.json`'s `release`/`sync`/`generators`/`mnci` blocks, `.npmrc`,\n * `commitlint.config.mjs`, `.husky/commit-msg`, the CI pipeline file(s), and\n * the curated root `package.json` scripts), and virtually every generated\n * workspace is already a git repo — `git diff` is the review step, not a\n * bespoke one built here.\n *\n * @param workspaceRoot - Absolute path to the workspace to upgrade (the\n * current working directory).\n * @param options - The CLI flags.\n * @returns Nothing.\n * @throws Error when `workspaceRoot` is not an `mnci`-generated workspace,\n * or a required field is missing from both the flags and the persisted\n * config.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function runUpgrade (workspaceRoot: string, options: UpgradeOptions): void {\n if (!fileExists(join(workspaceRoot, 'nx.json'))) {\n throw new Error(\n `No nx.json found in ${workspaceRoot} — this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`,\n )\n }\n const persisted = readMnciConfig(workspaceRoot)\n const resolved = resolveOverlayOptions(workspaceRoot, options, persisted)\n\n logger.step('Re-applying the MoNecromanCI overlay')\n applyOverlay(workspaceRoot, resolved, logger.detail)\n\n // A per-project sweep, unlike everything above it. A rollup config is written\n // once at `add` time, so a workspace generated before source maps were wired\n // in stays undebuggable forever otherwise — a breakpoint in a .ts file simply\n // never binds, and nothing says why. Idempotent, so a repeat upgrade is a\n // no-op, and it reports what it touched because this is the one step that\n // edits a file inside a project rather than an mnci-owned root file.\n const repaired = repairRollupSourceMaps(workspaceRoot)\n if (repaired.length > 0) {\n logger.step('Enabling source maps in rollup configs (breakpoints in .ts files)')\n for (const path of repaired) {\n logger.detail(`updated ${path}`)\n }\n }\n\n // Same reasoning as the source-map sweep above, for the two OTHER\n // publishable-project repairs that only used to run once, at `add` time:\n // the `types` path fix (a project `add`ed before it existed stayed\n // `any` for every TypeScript consumer forever) and the declaration\n // specifiers' `.js`-extension fix (stayed unresolvable for every\n // `nodenext` consumer forever). Both are idempotent for the same reason\n // the source-map sweep is.\n const repairedManifests = repairPublishableManifests(workspaceRoot)\n if (repairedManifests.length > 0) {\n logger.step('Repointing `types` at the real declaration file')\n for (const path of repairedManifests) {\n logger.detail(`updated ${path}`)\n }\n }\n // Go apps added before cross-compilation existed gain `build-all` and\n // `package-all`; nothing they already have is touched.\n const goApps = addGoPlatformTargets(workspaceRoot)\n if (goApps.length > 0) {\n logger.step('Adding the six-platform build to Go apps (build-all, package-all)')\n for (const path of goApps) {\n logger.detail(`updated ${path}`)\n }\n }\n // The packaging script is mnci's, like the overlay files: a fix to it reaches\n // every workspace with a VS Code extension, and only those.\n const pinnedExtensions = pinVscodeExtensionProjectNames(workspaceRoot)\n if (pinnedExtensions.length > 0) {\n logger.step('Pinning the Nx project name of VS Code extensions (nx.name)')\n for (const path of pinnedExtensions) {\n logger.detail(`updated ${path}`)\n }\n }\n if (refreshVscodeExtensionScript(workspaceRoot)) {\n logger.step('Updating the VS Code extension packaging script')\n logger.detail(`updated ${VSCODE_EXTENSION_SCRIPT_PATH}`)\n }\n if (refreshGoReleaseScript(workspaceRoot)) {\n logger.step('Updating the releasable Go app script')\n logger.detail(`updated ${GO_RELEASE_SCRIPT_PATH}`)\n }\n const upgradedDtsPlugins = upgradeDeclarationSpecifierPlugins(workspaceRoot)\n if (upgradedDtsPlugins.length > 0) {\n logger.step('Upgrading declaration specifiers to resolve under nodenext')\n for (const path of upgradedDtsPlugins) {\n logger.detail(`updated ${path}`)\n }\n }\n\n // `new` and every `add` end this way; `upgrade` did not, so it left the\n // `nx.json` it had just rewritten mis-formatted — which now fails the\n // workspace's own `format:check` CI gate. Non-fatal for the same reason it is\n // there: the overlay is already applied by this point.\n //\n // Announced because it is by far the slowest part of an upgrade — `eslint --fix`\n // over the whole workspace — and it used to run in silence after the overlay\n // message, so a large workspace looked hung for a minute or more. `new` already\n // logged this; `upgrade` did not.\n logger.step(\n 'Formatting the workspace (eslint --fix) — the slowest step, minutes on a large workspace',\n )\n runFormatter(workspaceRoot)\n\n logger.success('Done. Review the changes with `git diff` before committing.')\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport spawn from 'cross-spawn'\nimport { logger } from '../terminal'\n\n/**\n * The published name of this CLI, queried against the npm registry.\n *\n * @remarks\n * Hardcoded rather than read back from the packaged `package.json`: the\n * update notice should point at the package users actually install, which\n * stays true even when the CLI is executed from a renamed local checkout.\n */\nconst PACKAGE_NAME = '@mnci/cli'\n\n/** How long to wait for the registry before giving up, in milliseconds. */\nconst REGISTRY_TIMEOUT_MS = 2000\n\n/**\n * Starts a background check for a newer published version of the CLI.\n *\n * @remarks\n * Deliberately synchronous and `void`-returning: the work is handed to\n * `setImmediate` and never awaited, so returning a promise would leave a\n * floating one at the call site — and a rejected floating promise takes the\n * whole process down with an unhandled rejection. Nothing here can reject,\n * and nothing here throws: a failed update check must never affect the\n * command the user actually ran.\n *\n * Two honest caveats about \"background\". The registry call is a *synchronous*\n * spawn, so once the deferred callback runs it blocks the event loop for as\n * long as the query takes (bounded by {@link REGISTRY_TIMEOUT_MS}); running\n * it in `setImmediate` keeps it off the startup path, not off the thread.\n * And there is no result cache — every eligible invocation queries afresh.\n *\n * Both costs are confined to interactive use: the check is skipped outright\n * unless stdout is a TTY, so CI runs and piped/scripted invocations pay\n * nothing and never have a notice injected into their output.\n *\n * @param currentVersion - The running CLI version (from package.json).\n * @returns Nothing — a newer version is reported on stdout when found.\n * @throws Never - every failure path is swallowed.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function checkForUpdate (currentVersion: string): void {\n try {\n if (!process.stdout.isTTY) {\n return\n }\n setImmediate(() => {\n try {\n reportIfOutdated(currentVersion)\n } catch {\n // A convenience feature must never break the invoked command.\n }\n })\n } catch {\n // Scheduling itself failing is pathological; ignore it like the rest.\n }\n}\n\n/**\n * Queries the registry and prints an update notice when one is warranted.\n *\n * @remarks\n * Uses `cross-spawn` with an argv array rather than `execSync` with a shell\n * string: it keeps the workspace's no-shell-injection invariant, and avoids\n * POSIX-only redirection that would fail outright on a Windows agent.\n *\n * @param currentVersion - The running CLI version.\n * @returns Nothing.\n * @throws Never - a failed or empty query is treated as \"no update known\".\n * @typeParam None - this function has no generic type parameters.\n */\nfunction reportIfOutdated (currentVersion: string): void {\n const result = spawn.sync('npm', ['view', PACKAGE_NAME, 'version'], {\n encoding: 'utf8',\n timeout: REGISTRY_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n if (result.error || result.status !== 0) {\n return\n }\n\n const latestVersion = (result.stdout ?? '').trim()\n if (!latestVersion || !isNewerVersion(latestVersion, currentVersion)) {\n return\n }\n\n logger.info('')\n logger.info(`A new version of ${PACKAGE_NAME} is available: ${latestVersion}`)\n logger.info(` Current version: ${currentVersion}`)\n logger.info(` Update with: npm install -g ${PACKAGE_NAME}@latest`)\n logger.info('')\n}\n\n/**\n * Whether `candidate` is a strictly newer release than `current`.\n *\n * @remarks\n * Compares the numeric major/minor/patch triple left to right, ignoring any\n * prerelease suffix. That is intentionally narrower than full semver: the\n * only decision it drives is whether to print a notice, so treating\n * `1.2.3-beta.1` as `1.2.3` merely suppresses a notice rather than causing a\n * wrong action. A non-numeric segment makes the comparison bail out as \"not\n * newer\", so a malformed version is silent rather than noisy.\n *\n * @param candidate - The version offered by the registry.\n * @param current - The running version.\n * @returns `true` when `candidate` is strictly newer than `current`.\n * @throws Never - unparseable input yields `false`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction isNewerVersion (candidate: string, current: string): boolean {\n const candidateParts = releaseTriple(candidate)\n const currentParts = releaseTriple(current)\n if (!candidateParts || !currentParts) {\n return false\n }\n\n for (const [index, candidatePart] of candidateParts.entries()) {\n if (candidatePart !== currentParts[index]) {\n return candidatePart > currentParts[index]\n }\n }\n\n return false\n}\n\n/**\n * Parses a version string into its numeric major/minor/patch triple.\n *\n * @param version - The version to parse, with any prerelease suffix ignored.\n * @returns The three numbers, or `undefined` when any segment is not numeric.\n * @throws Never - malformed input yields `undefined`.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction releaseTriple (version: string): [number, number, number] | undefined {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(version.trim())\n\n return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined\n}\n\n/**\n * Reads the CLI's own version from its packaged `package.json`.\n *\n * @remarks\n * Resolved relative to the compiled entry point (`__dirname`, i.e. `dist/`),\n * so the manifest sits one level up. Falls back to `0.0.0` rather than\n * throwing: an unreadable manifest should not stop the CLI from running.\n *\n * @param packageDirectory - Directory holding the compiled entry point.\n * @returns The version string, or `0.0.0` when it cannot be read.\n * @throws Never - any read or parse failure yields the fallback.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function readCliVersion (packageDirectory: string): string {\n try {\n const manifestPath = join(packageDirectory, '..', 'package.json')\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { version?: string }\n\n return manifest.version ?? '0.0.0'\n } catch {\n return '0.0.0'\n }\n}\n"],"mappings":"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","names":["import_node_path","import_prompts","value","spawn","resolve","import_node_fs","import_node_path","import_node_fs","import_node_path","pascalScope","pythonPublishUrl","nugetFeedUrl","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","module","import_node_path","ensurePlugin","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","bare","operator","import_node_path","import_prompts","import_node_fs","import_node_path","toPosix","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_path","import_prompts","import_node_fs","import_node_path","resolveRegistry","import_node_fs","import_node_path","import_cross_spawn","currentVersion","spawn"]}