@mnci/cli 4.0.6 → 4.0.7
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 +44 -25
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/nx.ts","../src/util/logger.ts","../src/prompts.ts","../src/util/fsx.ts","../src/util/names.ts","../src/commands/add/flutter.ts","../src/commands/add/shared.ts","../src/overlay.ts","../src/commands/add/go.ts","../src/commands/add/node.ts","../src/commands/add/npmLib.ts","../src/commands/add/python.ts","../src/commands/add/reactApp.ts","../src/commands/add/reactLib.ts","../src/commands/doctor.ts","../src/commands/interactive.ts","../src/commands/new.ts","../src/commands/upgrade.ts","../src/util/versionChecker.ts"],"sourcesContent":["import { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './commands/add'\nimport { runDoctor } from './commands/doctor'\nimport { runInteractive } from './commands/interactive'\nimport { runNew, type NewOptions } from './commands/new'\nimport { runUpgrade, type UpgradeOptions } from './commands/upgrade'\nimport { logger } from './util/logger'\nimport { checkForUpdate, readCliVersion } from './util/versionChecker'\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Four commands: `new`, `add`, `upgrade` (re-applies the overlay to an existing\n * workspace) and `doctor` (read-only invariant check). Everything a generated\n * repo needs day-to-day (build/test/lint/release) is plain Nx, so the CLI\n * deliberately has no wrapper commands for those.\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('--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 .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('--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('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 .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, runShell } from '../nx'\nimport { promptText } from '../prompts'\nimport { fileExists, readJson } from '../util/fsx'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\nimport { addFlutterApp, addFlutterInternalLib, addFlutterLib } from './add/flutter'\nimport { addGoApp, addGoFunctionApp, addGoInternalLib, addGoLib } from './add/go'\nimport { addNodeApp, addNodeFunctionApp } from './add/node'\nimport { addNpmLib } from './add/npmLib'\nimport {\n addPythonApp,\n addPythonFunctionApp,\n addPythonInternalLib,\n addPythonLib,\n addPythonVendor\n} from './add/python'\nimport { addReactApp } from './add/reactApp'\nimport { addReactInternalLib, addReactLib } from './add/reactLib'\nimport {\n markPrivate,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type AddOptions,\n type WorkspaceStack\n} from './add/shared'\n\nexport type { AddOptions } from './add/shared'\n\n/**\n * The project kinds this CLI can add — deliberately just nineteen.\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), `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 * `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\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 and\n * Flutter.\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]\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 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 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 '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 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 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 * 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: the missing step\n * that leaves VSCode unable to autocomplete across libraries. Run once after\n * every `add` so cross-project imports resolve immediately.\n *\n * Non-fatal: the project is already generated by the time this runs, so a sync\n * failure only warns (with the manual command) rather than failing the whole\n * `add` and leaving the user unsure whether their project was created.\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 */\nfunction 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 * 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 return {\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest'\n }\n}\n","import spawn from 'cross-spawn'\nimport { logger } from './util/logger'\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 return result.status ?? 1\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 './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 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 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 * Reads a `.code-workspace` file, tolerating the trailing commas VS Code allows.\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 so does anything that formats the\n * file as JSONC. `JSON.parse` rejects those outright, so reading this file\n * strictly means mnci breaks on a workspace the user's own editor last touched.\n * Tolerating the dialect the file is actually written in is the only correct\n * read here.\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 * Comments are not stripped: mnci never writes one, and a file with hand-added\n * comments predates this handling regardless.\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 return JSON.parse(readFileSync(path, 'utf8').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 { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands } from './shared'\n\n/**\n * Merges an extra target into a plugin-written `project.json`.\n *\n * @remarks\n * `@mnci/nx-flutter`'s generators write a real `project.json` (lint/test/\n * build/package), same as `@mnci/nx-python-pip` and `@nx-go/nx-go` — this is\n * the analogous post-generation merge those two plugins' `add/*.ts` modules\n * already have their own copy of (`addProjectJsonTargets`), used here only\n * for the `start` target ({@link flutterAppStartTarget}), which the plugin\n * itself has no opinion on.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\n}\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, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { reactExpressPeerOverride } from '../../overlay'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\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}\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 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 * 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 * 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 * 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 if (manifest.files && !manifest.files.includes(DECLARATION_MAP_EXCLUSION)) {\n manifest.files.push(DECLARATION_MAP_EXCLUSION)\n }\n writeFileEnsured(manifestPath, toJson(manifest))\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 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 * 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 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 return kind === 'start' ? { ...base, isBackground: true } : { ...base, group: kind }\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 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 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 // Synced on EVERY add, not only the express one, because the override depends\n // on the manifest's state rather than on which generator just ran — and this\n // runs after that generator has written its dependencies. `mnci add node-app\n // --framework express` is what introduces express, and the very next add would\n // otherwise be the one that fails. See reactExpressPeerOverride for why it is\n // conditional and why an unconditional form is worse than the bug.\n const overrides = {\n ...(manifest.overrides as Record<string, unknown> | undefined),\n ...reactExpressPeerOverride(manifest)\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n scripts: { ...existingScripts, ...scripts },\n ...((Object.keys(overrides).length > 0) && { overrides })\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","import { globSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { markExecutable, readCodeWorkspace, readJson, toJson, writeFileEnsured } from './util/fsx'\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 * 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 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 * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`), used for the routing line.\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 (registry: RegistryConfig, scope: string): 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; an unset value\n; is harmless locally, so no token is needed for day-to-day work.\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 // 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 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 * 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 return {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*', '!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/*'\n },\n changelog: githubReleases\n ? {\n workspaceChangelog: false,\n projectChangelogs: { createRelease: 'github', file: false }\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 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 * 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}/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 return {\n ...nxJson,\n namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] }\n }\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 * @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 { ...nxJson, defaultBase: 'main', release: releaseConfig(ci) }\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 * A caret range, so `npm update` carries lint-rule improvements into existing\n * workspaces without an `mnci upgrade` — the reason the config ships as a\n * package rather than as a template string in this file.\n */\nexport const ESLINT_CONFIG_VERSION = '^0.1.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 and Python arrive as devcontainer *features*, not as a custom image.**\n * A Dockerfile would be a second thing to maintain against upstream, and\n * features are the mechanism the ecosystem maintains for precisely this.\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 },\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 * The `@nx/react` express-peer override, written only when the workspace has express.\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.1.2\n * npm error Conflicting peer dependency: express@4.22.2\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 * **Conditional, and that is the whole design.** Every simpler form was measured\n * against a real generated workspace and each breaks a different shape:\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 *\n * So an unconditional `$express` would be worse than the bug it fixes: it turns\n * a conflict that only affects express+react workspaces into a hard failure in\n * EVERY react-only one. Written only when the root manifest declares express —\n * which is exactly when the conflict can occur, and exactly when `$express`\n * resolves.\n *\n * `$express` rather than a literal range because it means the correct thing:\n * resolve `@nx/react`'s express peer to whatever express THIS workspace uses.\n * A literal pins a major and breaks the other one.\n *\n * @param manifest - The workspace's root `package.json`, already parsed.\n * @returns The override entry, or an empty object when express is absent.\n * @throws Never - pure inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactExpressPeerOverride (\n manifest: Record<string, unknown>\n): Record<string, unknown> {\n const declared = {\n ...(manifest.dependencies as Record<string, string> | undefined),\n ...(manifest.devDependencies as Record<string, string> | undefined)\n }\n return declared.express === undefined ? {} : { '@nx/react': { express: '$express' } }\n}\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 */\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' },\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 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_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/nx-dependency-checks @nx/eslint-plugin, on publishable packages' manifests\n// mnci/standard JavaScript Standard Style as ESLint rules — the\n// whole formatting opinion. Composed LAST, on\n// purpose: 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 root `eslint.config.mjs` written into generated workspaces.\n *\n * @remarks\n * ESLint config is an mnci-owned file as of this change; it previously was not\n * owned at all, so workspaces silently kept `create-nx-workspace`'s bare\n * `@nx/eslint-plugin` default while the richer rules lived only in mnci's own\n * repo.\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.\n *\n * Everything else in the file is comment: {@link ESLINT_BLOCK_INVENTORY}, then\n * the override recipe. Both are there because the alternative to documenting a\n * three-line config is a user editing `node_modules`.\n */\nexport const ESLINT_CONFIG = `import mnci from '@mnci/eslint-config'\n\n${ESLINT_BLOCK_INVENTORY}//\n// TO OVERRIDE a rule, append a block AFTER the spread — later blocks win, so one\n// of your own beats anything above it. Give it a name, so the inspector shows\n// where the change came from:\n//\n// export default [\n// ...mnci({ workspaceRoot: import.meta.dirname }),\n// {\n// name: 'local/legacy-app-allows-any',\n// files: ['apps/legacy/**/*.ts'],\n// rules: { '@typescript-eslint/no-explicit-any': 'off' }\n// }\n// ]\n//\n// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it is\n// 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.\n//\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.\nexport default mnci({ workspaceRoot: import.meta.dirname })\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 return JSON.stringify(\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 null,\n 2\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 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 }\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 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 return undefined\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 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 * Installs via `go install`, which needs no package manager, no sudo and no\n * platform switch — the same binary lands on Linux, macOS and Windows\n * agents. `go install` places it in `GOBIN` (or `GOPATH/bin`), so that\n * directory is appended to `PATH` for subsequent steps through each\n * provider's own mechanism (see the call sites).\n *\n * Skips when `golangci-lint` is already resolvable, so a self-hosted agent\n * that pre-installs it pays nothing.\n */\nconst GOLANGCI_LINT_INSTALL_GUARD = 'node -e \"const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(!fs.existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}if(cp.spawnSync(\\'golangci-lint\\',[\\'--version\\'],{stdio:\\'ignore\\'}).status===0){console.log(\\'golangci-lint already installed - skipping.\\');process.exit(0)}process.exit(cp.spawnSync(\\'go\\',[\\'install\\',\\'github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest\\'],{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The shared prelude that resolves `GOPATH/bin` — where\n * {@link GOLANGCI_LINT_INSTALL_GUARD}'s `go install` puts the linter.\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, unlike `golangci-lint`'s `@latest`, 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 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 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` (the overlay writes no `.gitignore`\n * of its own; `create-nx-workspace` owns that file).\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 fix exists, at `moderate` or above → **exit 1**. The remedy is a\n * reviewed `overrides` entry, and leaving it to a human who never sees a red\n * 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 *\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 act=all.filter(v=>v.fixAvailable&&BLOCK.includes(v.severity));const rest=all.filter(v=>!act.includes(v));for(const v of rest)console.log(\\' note [\\'+v.severity+\\'] \\'+v.name+(v.fixAvailable?\\' - fix available, below the blocking threshold\\':\\' - NO fix available upstream, nothing to do here\\'));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\\'+(v.fixAvailable&&v.fixAvailable.isSemVerMajor?\\' (semver-major)\\':\\'\\'));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 */\nconst PACK_APPS_GUARD = 'node -e \"const fs=require(\\'node:fs\\');fs.mkdirSync(\\'dist/drop\\',{recursive:true});if(fs.globSync(\\'apps/*/project.json\\').length===0){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 * Builds the portable `node -e` one-liner that versions, tags and publishes\n * both `packages/*` (npm) and `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 * @param pythonPublishEnv - A `node -e`-fragment that exports `TWINE_*` when\n * there are Python 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): string {\n return `node -e \"const fs=require('node:fs'),cp=require('node:child_process');const hasNpm=fs.globSync('packages/*/package.json').length>0;const hasPython=fs.globSync('python-packages/*/pyproject.toml').length>0;if(!hasNpm&&!hasPython){console.log('Nothing to release - skipping.');process.exit(0)}const env={...process.env};${pythonPublishEnv}process.exit(cp.spawnSync('npx nx release --yes',{stdio:'inherit',shell:true,env}).status ?? 1)\"`\n}\n\n/**\n * Injected into {@link releaseGuard}: when there are Python packages and a\n * configured Azure feed, export twine publish credentials (the raw PAT,\n * decoded from the base64 value both providers read from a `PAT` env var).\n *\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @returns The `node -e` fragment, or `''` when there is no Python feed.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonPublishEnvFragment (pythonPublishUrl?: string): string {\n return pythonPublishUrl\n ? `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'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 : ['PAT', variableReference('PAT')]\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 the base64 PAT from the `variableGroup` (default `Build`): the\n * group exposes `$(PAT)`, mapped as env on the npm steps and read by the root\n * `.npmrc`'s `_password` block. No `npmAuthenticate@0` (it would overwrite\n * that password).\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 * @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): 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 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\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 - script: npm ci\n displayName: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\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). 'go install'\n # drops it in GOPATH/bin, 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 # 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 # Version + tag + publish, in one release, for npm (packages/*) AND Python\n # (python-packages/*) — conventional commits, tag-only push. Portable guard:\n # nx release errors on an empty scope, so skip cleanly when there is nothing\n # to release. When there are Python packages and an Azure feed, twine\n # publish credentials are exported (raw PAT, decoded from the base64 variable).\n - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n displayName: Release — version, tag and publish (npm + Python)\n condition: ${onMain}\n env:\n ${npmAuthName}: ${npmAuthValue}\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 * @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): 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 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\njobs:\n ci:\n runs-on: ${agent}\n steps:\n - uses: actions/checkout@v4\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@v4\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). 'go install'\n # drops it in GOPATH/bin, which is not on PATH by default on a hosted\n # 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 # 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@v4\n if: \\${{ ${onMain} }}\n with:\n name: drop\n path: dist/drop\n if-no-files-found: ignore\n\n # Version + tag + publish, in one release, for npm (packages/*) AND Python\n # (python-packages/*) — conventional commits, tag-only push. Portable guard:\n # nx release errors on an empty scope, so skip cleanly when there is nothing\n # to release. When there are Python packages and an Azure feed, twine\n # publish credentials are exported (raw PAT, decoded from the base64 secret).${\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 - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n name: Release — version, tag${githubReleases ? ', publish and GitHub Release' : ' and publish'} (npm + Python)\n if: \\${{ ${onMain} }}\n env:\n ${npmAuthName}: ${npmAuthValue}${\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/**\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 * (see the `fix(deps)` overrides commit). Dependabot is a GitHub-native\n * feature (no app/extension install, unlike Renovate), so it is written only\n * for `github`/`both` workspaces — the same conditional {@link applyOverlay}\n * already uses for `.github/workflows/ci.yml`.\n *\n * Three ecosystems, each on a weekly cadence (batches noise instead of a PR\n * per bump):\n * - `npm` at the workspace root — covers every `packages/*` project, since\n * `npm ci` installs from the one root lockfile.\n * - `github-actions` at the root — keeps `actions/checkout`, `setup-node`,\n * etc. patched too (the workflow {@link githubActionsYaml} writes).\n * - `pip`, via `directories` **globs** (`/apps/*`, `/python-packages/*`,\n * `/libs/*`) rather than one entry per project: Python projects do not\n * exist yet at `mnci new` time (`add python-*` writes them later), and a\n * glob that currently matches nothing is not an error — Dependabot simply\n * finds no manifest there yet. This is written unconditionally (no\n * Python-project detection here), so it starts covering Python\n * dependencies automatically the moment the first one is added, with no\n * `mnci upgrade` needed.\n */\nexport const DEPENDABOT_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.\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 # Globs, not one entry per project: Python projects are added later via\n # 'mnci add python-*', so this starts covering them automatically the\n # moment the first one exists — a glob matching nothing yet is not an error.\n - package-ecosystem: pip\n directories:\n - \"/apps/*\"\n - \"/python-packages/*\"\n - \"/libs/*\"\n schedule:\n interval: weekly\n\n # Same glob reasoning as pip above. Dart projects are pub workspace members,\n # so each one declares its own dependencies even though they all resolve\n # through the single root pubspec.lock — hence the per-project directories\n # rather than just \"/\".\n - package-ecosystem: pub\n directories:\n - \"/apps/*\"\n - \"/packages/*\"\n - \"/libs/*\"\n schedule:\n interval: weekly\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\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 *\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 = ['.prettierrc', '.prettierrc.json', '.vscode'] as const\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 removeProjectEslintConfigs(workspaceRoot)\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 * 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 const patched = withSharedGlobals(withEslintPlugin(withReleaseConfig(nxJson, options.ci)))\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 const devDeps = 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 // 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 // Last, and conditional — see reactExpressPeerOverride. At `mnci new` there\n // is no express yet, so this is a no-op; `mnci add node-app --framework\n // express` is what puts express in the root manifest, and syncs the override\n // itself. This line is what carries it across an `mnci upgrade`.\n ...reactExpressPeerOverride(manifest)\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 const nx = {\n ...existingNx,\n includedScripts: (existingNx?.includedScripts as unknown[] | undefined) ?? [],\n targets: {\n ...(existingNx?.targets as Record<string, unknown> | undefined),\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(join(workspaceRoot, '.npmrc'), npmrcContent(options.registry, options.scope))\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, so one config file, at the root.\n // `add` deletes the per-project ones Nx generators write.\n onProgress('eslint.config.mjs — the shared lint AND formatting opinion')\n writeFileEnsured(join(workspaceRoot, 'eslint.config.mjs'), ESLINT_CONFIG)\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 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(options.agent, options.variableGroup, publishUrl, options.registry.kind)\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)\n )\n writeFileEnsured(join(workspaceRoot, '.github/dependabot.yml'), DEPENDABOT_CONFIG)\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands } from './shared'\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 return /^module\\s+(\\S+)/m.exec(contents)?.[1]\n } catch {\n return undefined\n }\n}\n\n/**\n * Merges extra targets into a plugin-written `project.json`.\n *\n * @remarks\n * `@nx-go/nx-go`'s generators write a real `project.json`, but with an empty\n * `targets` map: the plugin supplies targets by inference, and inference is\n * keyed on a per-project `go.mod`. In mnci's single-root-module layout there\n * are no per-project manifests, so nothing is inferred and mnci writes the\n * targets explicitly — the same thing every other mnci kind does anyway.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\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 return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command }\n }\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/** 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 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 })\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 registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import it as ${module}/packages/${name}`)\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 registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import it as ${module}/libs/${name}`)\n }\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport {\n addNxTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type NodeFramework,\n type WorkspaceStack\n} from './shared'\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 */\nfunction 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}\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 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 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('${root}/dist','dist');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\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 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 { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport {\n defaultScope,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n repairPublishableManifest,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\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 * @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 exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const manifestPath = join(workspaceRoot, 'packages', name, 'package.json')\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 @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 removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { promptText } from '../../prompts'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands, type AddOptions } from './shared'\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.\n *\n * @remarks\n * Unlike `@nxlv/python`, this plugin needs no `nx.json` `plugins`\n * registration: its generators/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. A plain `npm install` is the whole story.\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 return\n }\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\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\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 * Merges extra targets into a plugin-written `project.json`.\n *\n * @remarks\n * `@mnci/nx-python-pip`'s generators write a real `project.json` (not an\n * inference-only manifest), so the `package` (zip) target — mnci's own CI\n * convention, not a generic plugin concern — is merged straight into it\n * after generation.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\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 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 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 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 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 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 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'\nimport { fileExists, writeFileEnsured } from '../../util/fsx'\nimport {\n addNxTargets,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack\n} from './shared'\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 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 { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n repairPublishableManifest,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\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 or the plugin install exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const publishableManifest = join(workspaceRoot, 'packages', name, 'package.json')\n markPublic(publishableManifest)\n repairPublishableManifest(publishableManifest)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\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 or the plugin install exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const privateManifest = join(workspaceRoot, 'libs', name, 'package.json')\n markPrivate(privateManifest)\n repairPublishableManifest(privateManifest)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n","import { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx'\nimport { ESLINT_VERSION, RETIRED_FORMATTER_FILES, type RegistryConfig } from '../overlay'\nimport { fileExists, readJson } from '../util/fsx'\nimport { logger } from '../util/logger'\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 ]\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 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 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 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 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 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 * 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 or Python package does not have, so it aborts\n * while building the release graph and `nx release` fails for **every** project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per publishable Dart/Python 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('python-packages/*/pyproject.toml', { cwd: workspaceRoot })\n ].map(manifest => toPosix(manifest))\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 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 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 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 * 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 ...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 return\n }\n logger.error(`${failed.length} of ${findings.length} checks failed.`)\n process.exitCode = 1\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../util/fsx'\nimport { runAdd } from './add'\nimport { runNew } from './new'\nimport { runUpgrade } from './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 { rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runFormatter, runNpx, runShell } from '../nx'\nimport {\n applyOverlay,\n DEFAULT_STACK,\n type CiProvider,\n type RegistryConfig,\n type StackConfig\n} from '../overlay'\nimport { promptCi, promptNxCloud, promptRegistry, promptStack, promptText } from '../prompts'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\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 /** 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\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 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 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 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 const workspaceName = name ?? (await promptText('Workspace name'))\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 const stack = await resolveStack(options)\n const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud())\n\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 process.cwd()\n )\n\n const workspaceRoot = join(process.cwd(), workspaceName)\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 },\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 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(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { readdirSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport { runFormatter } from '../nx'\nimport {\n applyOverlay,\n readMnciConfig,\n type CiProvider,\n type OverlayOptions,\n type RegistryConfig,\n type StackConfig\n} from '../overlay'\nimport { fileExists } from '../util/fsx'\nimport { logger } from '../util/logger'\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 /** 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 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 return basename(workspaceRoot)\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 // 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 }\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 // `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 './logger'\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 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 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 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","spawn","value","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","pythonPublishUrl","import_node_fs","import_node_path","addProjectJsonTargets","module","import_node_path","ensurePlugin","import_node_path","import_node_fs","import_node_path","addProjectJsonTargets","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","import_node_path","import_prompts","import_node_fs","import_node_path","import_node_fs","import_node_path","resolveRegistry","import_node_fs","import_node_path","import_cross_spawn","spawn"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/nx.ts","../src/util/logger.ts","../src/prompts.ts","../src/util/fsx.ts","../src/util/names.ts","../src/commands/add/flutter.ts","../src/commands/add/shared.ts","../src/overlay.ts","../src/commands/add/go.ts","../src/commands/add/node.ts","../src/commands/add/npmLib.ts","../src/commands/add/python.ts","../src/commands/add/reactApp.ts","../src/commands/add/reactLib.ts","../src/commands/doctor.ts","../src/commands/interactive.ts","../src/commands/new.ts","../src/commands/upgrade.ts","../src/util/versionChecker.ts"],"sourcesContent":["import { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './commands/add'\nimport { runDoctor } from './commands/doctor'\nimport { runInteractive } from './commands/interactive'\nimport { runNew, type NewOptions } from './commands/new'\nimport { runUpgrade, type UpgradeOptions } from './commands/upgrade'\nimport { logger } from './util/logger'\nimport { checkForUpdate, readCliVersion } from './util/versionChecker'\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Four commands: `new`, `add`, `upgrade` (re-applies the overlay to an existing\n * workspace) and `doctor` (read-only invariant check). Everything a generated\n * repo needs day-to-day (build/test/lint/release) is plain Nx, so the CLI\n * deliberately has no wrapper commands for those.\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('--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 .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('--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('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 .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, runShell } from '../nx'\nimport { promptText } from '../prompts'\nimport { fileExists, readJson } from '../util/fsx'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\nimport { addFlutterApp, addFlutterInternalLib, addFlutterLib } from './add/flutter'\nimport { addGoApp, addGoFunctionApp, addGoInternalLib, addGoLib } from './add/go'\nimport { addNodeApp, addNodeFunctionApp } from './add/node'\nimport { addNpmLib } from './add/npmLib'\nimport {\n addPythonApp,\n addPythonFunctionApp,\n addPythonInternalLib,\n addPythonLib,\n addPythonVendor\n} from './add/python'\nimport { addReactApp } from './add/reactApp'\nimport { addReactInternalLib, addReactLib } from './add/reactLib'\nimport {\n markPrivate,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type AddOptions,\n type WorkspaceStack\n} from './add/shared'\n\nexport type { AddOptions } from './add/shared'\n\n/**\n * The project kinds this CLI can add — deliberately just nineteen.\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), `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 * `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\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 and\n * Flutter.\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]\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 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 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 '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 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 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 * 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: the missing step\n * that leaves VSCode unable to autocomplete across libraries. Run once after\n * every `add` so cross-project imports resolve immediately.\n *\n * Non-fatal: the project is already generated by the time this runs, so a sync\n * failure only warns (with the manual command) rather than failing the whole\n * `add` and leaving the user unsure whether their project was created.\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 */\nfunction 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 * 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 return {\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest'\n }\n}\n","import spawn from 'cross-spawn'\nimport { logger } from './util/logger'\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 return result.status ?? 1\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 './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 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 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 * Reads a `.code-workspace` file, tolerating the trailing commas VS Code allows.\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 so does anything that formats the\n * file as JSONC. `JSON.parse` rejects those outright, so reading this file\n * strictly means mnci breaks on a workspace the user's own editor last touched.\n * Tolerating the dialect the file is actually written in is the only correct\n * read here.\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 * Comments are not stripped: mnci never writes one, and a file with hand-added\n * comments predates this handling regardless.\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 return JSON.parse(readFileSync(path, 'utf8').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 { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands } from './shared'\n\n/**\n * Merges an extra target into a plugin-written `project.json`.\n *\n * @remarks\n * `@mnci/nx-flutter`'s generators write a real `project.json` (lint/test/\n * build/package), same as `@mnci/nx-python-pip` and `@nx-go/nx-go` — this is\n * the analogous post-generation merge those two plugins' `add/*.ts` modules\n * already have their own copy of (`addProjectJsonTargets`), used here only\n * for the `start` target ({@link flutterAppStartTarget}), which the plugin\n * itself has no opinion on.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\n}\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 { existsSync, readdirSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { dependabotConfig, reactExpressPeerOverride } from '../../overlay'\nimport { fileExists, readCodeWorkspace, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\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}\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 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 * 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 * 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 * 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 if (manifest.files && !manifest.files.includes(DECLARATION_MAP_EXCLUSION)) {\n manifest.files.push(DECLARATION_MAP_EXCLUSION)\n }\n writeFileEnsured(manifestPath, toJson(manifest))\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 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 * 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 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 return kind === 'start' ? { ...base, isBackground: true } : { ...base, group: kind }\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 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 // Synced on EVERY add, not only the express one, because the override depends\n // on the manifest's state rather than on which generator just ran — and this\n // runs after that generator has written its dependencies. `mnci add node-app\n // --framework express` is what introduces express, and the very next add would\n // otherwise be the one that fails. See reactExpressPeerOverride for why it is\n // conditional and why an unconditional form is worse than the bug.\n const overrides = {\n ...(manifest.overrides as Record<string, unknown> | undefined),\n ...reactExpressPeerOverride(manifest)\n }\n writeFileEnsured(\n manifestPath,\n toJson({\n ...manifest,\n scripts: { ...existingScripts, ...scripts },\n ...((Object.keys(overrides).length > 0) && { overrides })\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","import { existsSync, globSync, readdirSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { markExecutable, readCodeWorkspace, readJson, toJson, writeFileEnsured } from './util/fsx'\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 * 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 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 * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`), used for the routing line.\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 (registry: RegistryConfig, scope: string): 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; an unset value\n; is harmless locally, so no token is needed for day-to-day work.\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 // 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 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 * 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 return {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*', '!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/*'\n },\n changelog: githubReleases\n ? {\n workspaceChangelog: false,\n projectChangelogs: { createRelease: 'github', file: false }\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 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 * 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}/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 return {\n ...nxJson,\n namedInputs: { ...namedInputs, sharedGlobals: [...existing, ...missing] }\n }\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 * @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 { ...nxJson, defaultBase: 'main', release: releaseConfig(ci) }\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 * A caret range, so `npm update` carries lint-rule improvements into existing\n * workspaces without an `mnci upgrade` — the reason the config ships as a\n * package rather than as a template string in this file.\n */\nexport const ESLINT_CONFIG_VERSION = '^0.1.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 and Python arrive as devcontainer *features*, not as a custom image.**\n * A Dockerfile would be a second thing to maintain against upstream, and\n * features are the mechanism the ecosystem maintains for precisely this.\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 },\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 * The `@nx/react` express-peer override, written only when the workspace has express.\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.1.2\n * npm error Conflicting peer dependency: express@4.22.2\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 * **Conditional, and that is the whole design.** Every simpler form was measured\n * against a real generated workspace and each breaks a different shape:\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 *\n * So an unconditional `$express` would be worse than the bug it fixes: it turns\n * a conflict that only affects express+react workspaces into a hard failure in\n * EVERY react-only one. Written only when the root manifest declares express —\n * which is exactly when the conflict can occur, and exactly when `$express`\n * resolves.\n *\n * `$express` rather than a literal range because it means the correct thing:\n * resolve `@nx/react`'s express peer to whatever express THIS workspace uses.\n * A literal pins a major and breaks the other one.\n *\n * @param manifest - The workspace's root `package.json`, already parsed.\n * @returns The override entry, or an empty object when express is absent.\n * @throws Never - pure inspection.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function reactExpressPeerOverride (\n manifest: Record<string, unknown>\n): Record<string, unknown> {\n const declared = {\n ...(manifest.dependencies as Record<string, string> | undefined),\n ...(manifest.devDependencies as Record<string, string> | undefined)\n }\n return declared.express === undefined ? {} : { '@nx/react': { express: '$express' } }\n}\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 */\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' },\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 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_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/nx-dependency-checks @nx/eslint-plugin, on publishable packages' manifests\n// mnci/standard JavaScript Standard Style as ESLint rules — the\n// whole formatting opinion. Composed LAST, on\n// purpose: 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 root `eslint.config.mjs` written into generated workspaces.\n *\n * @remarks\n * ESLint config is an mnci-owned file as of this change; it previously was not\n * owned at all, so workspaces silently kept `create-nx-workspace`'s bare\n * `@nx/eslint-plugin` default while the richer rules lived only in mnci's own\n * repo.\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.\n *\n * Everything else in the file is comment: {@link ESLINT_BLOCK_INVENTORY}, then\n * the override recipe. Both are there because the alternative to documenting a\n * three-line config is a user editing `node_modules`.\n */\nexport const ESLINT_CONFIG = `import mnci from '@mnci/eslint-config'\n\n${ESLINT_BLOCK_INVENTORY}//\n// TO OVERRIDE a rule, append a block AFTER the spread — later blocks win, so one\n// of your own beats anything above it. Give it a name, so the inspector shows\n// where the change came from:\n//\n// export default [\n// ...mnci({ workspaceRoot: import.meta.dirname }),\n// {\n// name: 'local/legacy-app-allows-any',\n// files: ['apps/legacy/**/*.ts'],\n// rules: { '@typescript-eslint/no-explicit-any': 'off' }\n// }\n// ]\n//\n// Do NOT edit @mnci/eslint-config inside node_modules, and do not fork it: it is\n// 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.\n//\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.\nexport default mnci({ workspaceRoot: import.meta.dirname })\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 return JSON.stringify(\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 null,\n 2\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 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 }\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 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 return undefined\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 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 * Installs via `go install`, which needs no package manager, no sudo and no\n * platform switch — the same binary lands on Linux, macOS and Windows\n * agents. `go install` places it in `GOBIN` (or `GOPATH/bin`), so that\n * directory is appended to `PATH` for subsequent steps through each\n * provider's own mechanism (see the call sites).\n *\n * Skips when `golangci-lint` is already resolvable, so a self-hosted agent\n * that pre-installs it pays nothing.\n */\nconst GOLANGCI_LINT_INSTALL_GUARD = 'node -e \"const fs=require(\\'node:fs\\'),cp=require(\\'node:child_process\\');if(!fs.existsSync(\\'go.mod\\')){console.log(\\'No Go projects - skipping.\\');process.exit(0)}if(cp.spawnSync(\\'golangci-lint\\',[\\'--version\\'],{stdio:\\'ignore\\'}).status===0){console.log(\\'golangci-lint already installed - skipping.\\');process.exit(0)}process.exit(cp.spawnSync(\\'go\\',[\\'install\\',\\'github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest\\'],{stdio:\\'inherit\\'}).status ?? 1)\"'\n\n/**\n * The shared prelude that resolves `GOPATH/bin` — where\n * {@link GOLANGCI_LINT_INSTALL_GUARD}'s `go install` puts the linter.\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, unlike `golangci-lint`'s `@latest`, 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 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 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` (the overlay writes no `.gitignore`\n * of its own; `create-nx-workspace` owns that file).\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 fix exists, at `moderate` or above → **exit 1**. The remedy is a\n * reviewed `overrides` entry, and leaving it to a human who never sees a red\n * 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 *\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 act=all.filter(v=>v.fixAvailable&&BLOCK.includes(v.severity));const rest=all.filter(v=>!act.includes(v));for(const v of rest)console.log(\\' note [\\'+v.severity+\\'] \\'+v.name+(v.fixAvailable?\\' - fix available, below the blocking threshold\\':\\' - NO fix available upstream, nothing to do here\\'));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\\'+(v.fixAvailable&&v.fixAvailable.isSemVerMajor?\\' (semver-major)\\':\\'\\'));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 */\nconst PACK_APPS_GUARD = 'node -e \"const fs=require(\\'node:fs\\');fs.mkdirSync(\\'dist/drop\\',{recursive:true});if(fs.globSync(\\'apps/*/project.json\\').length===0){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 * Builds the portable `node -e` one-liner that versions, tags and publishes\n * both `packages/*` (npm) and `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 * @param pythonPublishEnv - A `node -e`-fragment that exports `TWINE_*` when\n * there are Python 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): string {\n return `node -e \"const fs=require('node:fs'),cp=require('node:child_process');const hasNpm=fs.globSync('packages/*/package.json').length>0;const hasPython=fs.globSync('python-packages/*/pyproject.toml').length>0;if(!hasNpm&&!hasPython){console.log('Nothing to release - skipping.');process.exit(0)}const env={...process.env};${pythonPublishEnv}process.exit(cp.spawnSync('npx nx release --yes',{stdio:'inherit',shell:true,env}).status ?? 1)\"`\n}\n\n/**\n * Injected into {@link releaseGuard}: when there are Python packages and a\n * configured Azure feed, export twine publish credentials (the raw PAT,\n * decoded from the base64 value both providers read from a `PAT` env var).\n *\n * @param pythonPublishUrl - The twine upload URL for Python packages, or\n * `undefined` to leave Python publishing unconfigured (public npm).\n * @returns The `node -e` fragment, or `''` when there is no Python feed.\n * @throws Never - pure string mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction pythonPublishEnvFragment (pythonPublishUrl?: string): string {\n return pythonPublishUrl\n ? `if(hasPython){env.TWINE_REPOSITORY_URL='${pythonPublishUrl}';env.TWINE_USERNAME='AzureArtifacts';env.TWINE_PASSWORD=Buffer.from(process.env.PAT,'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 : ['PAT', variableReference('PAT')]\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 the base64 PAT from the `variableGroup` (default `Build`): the\n * group exposes `$(PAT)`, mapped as env on the npm steps and read by the root\n * `.npmrc`'s `_password` block. No `npmAuthenticate@0` (it would overwrite\n * that password).\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 * @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): 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 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\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 - script: npm ci\n displayName: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\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). 'go install'\n # drops it in GOPATH/bin, 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 # 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 # Version + tag + publish, in one release, for npm (packages/*) AND Python\n # (python-packages/*) — conventional commits, tag-only push. Portable guard:\n # nx release errors on an empty scope, so skip cleanly when there is nothing\n # to release. When there are Python packages and an Azure feed, twine\n # publish credentials are exported (raw PAT, decoded from the base64 variable).\n - script: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n displayName: Release — version, tag and publish (npm + Python)\n condition: ${onMain}\n env:\n ${npmAuthName}: ${npmAuthValue}\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 * @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): 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 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\njobs:\n ci:\n runs-on: ${agent}\n steps:\n - uses: actions/checkout@v4\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@v4\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). 'go install'\n # drops it in GOPATH/bin, which is not on PATH by default on a hosted\n # 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 # 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@v4\n if: \\${{ ${onMain} }}\n with:\n name: drop\n path: dist/drop\n if-no-files-found: ignore\n\n # Version + tag + publish, in one release, for npm (packages/*) AND Python\n # (python-packages/*) — conventional commits, tag-only push. Portable guard:\n # nx release errors on an empty scope, so skip cleanly when there is nothing\n # to release. When there are Python packages and an Azure feed, twine\n # publish credentials are exported (raw PAT, decoded from the base64 secret).${\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 - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n name: Release — version, tag${githubReleases ? ', publish and GitHub Release' : ' and publish'} (npm + Python)\n if: \\${{ ${onMain} }}\n env:\n ${npmAuthName}: ${npmAuthValue}${\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 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 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\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 *\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 = ['.prettierrc', '.prettierrc.json', '.vscode'] as const\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 removeProjectEslintConfigs(workspaceRoot)\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 * 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 const patched = withSharedGlobals(withEslintPlugin(withReleaseConfig(nxJson, options.ci)))\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 const devDeps = 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 // 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 // Last, and conditional — see reactExpressPeerOverride. At `mnci new` there\n // is no express yet, so this is a no-op; `mnci add node-app --framework\n // express` is what puts express in the root manifest, and syncs the override\n // itself. This line is what carries it across an `mnci upgrade`.\n ...reactExpressPeerOverride(manifest)\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 const nx = {\n ...existingNx,\n includedScripts: (existingNx?.includedScripts as unknown[] | undefined) ?? [],\n targets: {\n ...(existingNx?.targets as Record<string, unknown> | undefined),\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(join(workspaceRoot, '.npmrc'), npmrcContent(options.registry, options.scope))\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, so one config file, at the root.\n // `add` deletes the per-project ones Nx generators write.\n onProgress('eslint.config.mjs — the shared lint AND formatting opinion')\n writeFileEnsured(join(workspaceRoot, 'eslint.config.mjs'), ESLINT_CONFIG)\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 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(options.agent, options.variableGroup, publishUrl, options.registry.kind)\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)\n )\n writeFileEnsured(join(workspaceRoot, '.github/dependabot.yml'), dependabotConfig(workspaceRoot))\n }\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands } from './shared'\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 return /^module\\s+(\\S+)/m.exec(contents)?.[1]\n } catch {\n return undefined\n }\n}\n\n/**\n * Merges extra targets into a plugin-written `project.json`.\n *\n * @remarks\n * `@nx-go/nx-go`'s generators write a real `project.json`, but with an empty\n * `targets` map: the plugin supplies targets by inference, and inference is\n * keyed on a per-project `go.mod`. In mnci's single-root-module layout there\n * are no per-project manifests, so nothing is inferred and mnci writes the\n * targets explicitly — the same thing every other mnci kind does anyway.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\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 return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command }\n }\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/** 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 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 })\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 registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import it as ${module}/packages/${name}`)\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 registerProjectCommands(workspaceRoot, name, { build: false })\n\n const module = goModulePath(workspaceRoot)\n if (module) {\n logger.step(`Import it as ${module}/libs/${name}`)\n }\n}\n","import { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport {\n addNxTargets,\n ensureAdmZip,\n hasPlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type NodeFramework,\n type WorkspaceStack\n} from './shared'\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 */\nfunction 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}\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 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 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('${root}/dist','dist');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n outputs: [`{workspaceRoot}/${zip}`],\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 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 { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport {\n defaultScope,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n repairPublishableManifest,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\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 * @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 exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const manifestPath = join(workspaceRoot, 'packages', name, 'package.json')\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 @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 removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx, runShell } from '../../nx'\nimport { promptText } from '../../prompts'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { ensureAdmZip, hasPlugin, registerProjectCommands, type AddOptions } from './shared'\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.\n *\n * @remarks\n * Unlike `@nxlv/python`, this plugin needs no `nx.json` `plugins`\n * registration: its generators/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. A plain `npm install` is the whole story.\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 return\n }\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\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\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 * Merges extra targets into a plugin-written `project.json`.\n *\n * @remarks\n * `@mnci/nx-python-pip`'s generators write a real `project.json` (not an\n * inference-only manifest), so the `package` (zip) target — mnci's own CI\n * convention, not a generic plugin concern — is merged straight into it\n * after generation.\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 */\nfunction addProjectJsonTargets (projectJsonPath: string, newTargets: Record<string, unknown>): void {\n const project = readJson<Record<string, unknown>>(projectJsonPath)\n const targets = (project.targets as Record<string, unknown> | undefined) ?? {}\n writeFileEnsured(projectJsonPath, toJson({ ...project, targets: { ...targets, ...newTargets } }))\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 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 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 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 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 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 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'\nimport { fileExists, writeFileEnsured } from '../../util/fsx'\nimport {\n addNxTargets,\n ensureAdmZip,\n ensurePlugin,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type WorkspaceStack\n} from './shared'\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 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 { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n repairPublishableManifest,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\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 or the plugin install exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const publishableManifest = join(workspaceRoot, 'packages', name, 'package.json')\n markPublic(publishableManifest)\n repairPublishableManifest(publishableManifest)\n removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\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 or the plugin install exits non-zero.\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 runNx(\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 workspaceRoot\n )\n const privateManifest = join(workspaceRoot, 'libs', name, 'package.json')\n markPrivate(privateManifest)\n repairPublishableManifest(privateManifest)\n removeGeneratedEslintConfig(workspaceRoot, `libs/${name}`)\n registerProjectCommands(workspaceRoot, name, { build: true })\n}\n","import { globSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runShell } from '../nx'\nimport { ESLINT_VERSION, RETIRED_FORMATTER_FILES, type RegistryConfig } from '../overlay'\nimport { fileExists, readJson } from '../util/fsx'\nimport { logger } from '../util/logger'\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 ]\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 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 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 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 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 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 * 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 or Python package does not have, so it aborts\n * while building the release graph and `nx release` fails for **every** project.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns One finding per publishable Dart/Python 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('python-packages/*/pyproject.toml', { cwd: workspaceRoot })\n ].map(manifest => toPosix(manifest))\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 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 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 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 * 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 ...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 return\n }\n logger.error(`${failed.length} of ${findings.length} checks failed.`)\n process.exitCode = 1\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../util/fsx'\nimport { runAdd } from './add'\nimport { runNew } from './new'\nimport { runUpgrade } from './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 { rmSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runFormatter, runNpx, runShell } from '../nx'\nimport {\n applyOverlay,\n DEFAULT_STACK,\n type CiProvider,\n type RegistryConfig,\n type StackConfig\n} from '../overlay'\nimport { promptCi, promptNxCloud, promptRegistry, promptStack, promptText } from '../prompts'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\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 /** 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\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 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 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 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 const workspaceName = name ?? (await promptText('Workspace name'))\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 const stack = await resolveStack(options)\n const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud())\n\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 process.cwd()\n )\n\n const workspaceRoot = join(process.cwd(), workspaceName)\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 },\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 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(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { readdirSync } from 'node:fs'\nimport { basename, join } from 'node:path'\nimport { runFormatter } from '../nx'\nimport {\n applyOverlay,\n readMnciConfig,\n type CiProvider,\n type OverlayOptions,\n type RegistryConfig,\n type StackConfig\n} from '../overlay'\nimport { fileExists } from '../util/fsx'\nimport { logger } from '../util/logger'\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 /** 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 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 return basename(workspaceRoot)\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 // 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 }\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 // `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 './logger'\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 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 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 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","spawn","value","import_node_path","import_node_fs","import_node_path","import_node_fs","import_node_path","pythonPublishUrl","import_node_fs","import_node_path","addProjectJsonTargets","module","import_node_path","ensurePlugin","import_node_path","import_node_fs","import_node_path","addProjectJsonTargets","import_node_fs","import_node_path","import_node_path","import_node_fs","import_node_path","import_node_path","import_prompts","import_node_fs","import_node_path","import_node_fs","import_node_path","resolveRegistry","import_node_fs","import_node_path","import_cross_spawn","spawn"]}
|