@mnci/cli 1.19.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/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/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/overlay.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(\n '--linter <linter>',\n 'linter + formatter: eslint (ESLint + Prettier) | oxlint (oxlint + oxfmt for JS/TS, ESLint for the rest)'\n )\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 .option(\n '--linter <linter>',\n 'linter + formatter: eslint | oxlint (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, stack.linter)\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, stack.linter)\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 // Defaults to `eslint` when absent, the same call `mnci upgrade` makes and\n // for the same reason: every workspace generated before the choice existed\n // has no persisted value and is an ESLint workspace. Reading it at all is\n // the fix — this function returned `testRunner` alone, so `add` finished by\n // formatting an oxlint workspace with Prettier, against Prettier's defaults,\n // since the overlay had deleted the config that would have corrected it.\n linter: stack?.linter === 'oxlint' ? 'oxlint' : 'eslint'\n }\n}\n","import spawn from 'cross-spawn'\nimport type { LinterChoice } from './overlay'\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 `npm run format:check` the moment it is\n * created, which is a poor first impression and, worse, buries every real\n * formatting change under a wall of generator noise on the first commit.\n * Running it here means what mnci hands back is already normalised.\n *\n * **Which formatter is not a detail, and hardcoding Prettier here was a real\n * bug.** An oxlint workspace has no `.prettierrc.mjs` — the overlay deletes it\n * — so `npx prettier --write .` there does not fail, it silently formats the\n * whole workspace against **Prettier's own defaults**: semicolons, double\n * quotes, trailing commas. The exact opposite of the shared opinion, applied to\n * files mnci itself had just written correctly. `oxfmt --check` then reported\n * 19 files unformatted in a freshly generated workspace, `eslint.config.mjs`\n * and `oxlint.config.ts` among them. Caught by the real e2e; no fixture could\n * have, since it needs a generated workspace with both configs in play.\n *\n * `.prettierignore` (written by the overlay) keeps this off `node_modules`,\n * build output and lockfiles, and oxfmt reads that same file by default — so\n * the pass stays cheap either way, with one ignore list rather than two.\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 linter - The workspace's linter choice, which picks the formatter:\n * `eslint` pairs with Prettier, `oxlint` with oxfmt.\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, linter: LinterChoice, target = '.'): void {\n // `--write` is oxfmt's default, but it is passed explicitly so the intent\n // survives a future change of that default — this call REWRITES a user's\n // files, which is not something to leave implicit.\n const [command, arguments_] =\n linter === 'oxlint'\n ? ['oxfmt', ['--write', target]]\n : ['prettier', ['--write', '--log-level', 'warn', target]]\n const status = runShell('npx', [command, ...arguments_], cwd)\n if (status !== 0) {\n logger.warn(\n `${command} 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 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 pairs with Prettier, oxlint with oxfmt, 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 const linter = await select<StackConfig['linter']>({\n message: 'Linter and formatter',\n choices: [\n {\n name: 'ESLint + Prettier — every file type, the widest rule coverage',\n value: 'eslint'\n },\n {\n name: 'oxlint + oxfmt for JS/TS, ESLint for the rest — much faster, same style',\n value: 'oxlint'\n }\n ]\n })\n return { testRunner, linter }\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. Prettier has\n * native `.code-workspace` support and reformats a single-entry array with a\n * trailing comma, which `JSON.parse` then rejects — and `npm run format` is part\n * of mnci's documented routine, so a strict parse breaks on the first `format`\n * run.\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 type { LinterChoice } 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 is always ESLint + Prettier.\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface WorkspaceStack {\n testRunner: 'jest' | 'vitest'\n /**\n * The workspace's linter choice, which is also what picks the formatter.\n *\n * @remarks\n * Not generator-facing — no `@nx/*` generator takes it, since `add` passes\n * `--linter=none` to all of them regardless. It is here because `add` ends by\n * formatting the workspace, and WHICH formatter is a per-workspace fact: an\n * oxlint workspace has no `.prettierrc.mjs`, so running Prettier there\n * silently applies Prettier's own defaults. The field was missing from this\n * shape entirely, so `add` had no way to know.\n */\n linter: LinterChoice\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 * 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 writeFileEnsured(\n manifestPath,\n toJson({ ...manifest, scripts: { ...existingScripts, ...scripts } })\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 { 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 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 markPublic(join(workspaceRoot, 'packages', name, 'package.json'))\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 { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\n\n/**\n * The declaration path `@nx/react:library --bundler=rollup` writes, which its own\n * build never produces.\n */\nconst WRONG_TYPES_PATH = './dist/index.esm.d.ts'\n\n/** The declaration path that rollup build actually emits. */\nconst ACTUAL_TYPES_PATH = './dist/index.d.ts'\n\n/**\n * Repoints a generated React library's `types` entries at the declaration file\n * its build actually emits.\n *\n * @remarks\n * Works around a real inconsistency in `@nx/react:library --bundler=rollup`, not\n * a preference: the generator writes `types: './dist/index.esm.d.ts'` (and the\n * same path under `exports['.']`), while its rollup build emits\n * `dist/index.d.ts`. The referenced file therefore never exists, and any\n * consumer importing the library fails with\n * `TS7016: Could not find a declaration file for module '@scope/name'` — so a\n * publishable React lib could not be consumed as a typed dependency at all.\n *\n * Verified empirically against a real generated pair: a `react-lib` importing a\n * `react-internal-lib` fails `typecheck` before this repair and passes after,\n * with `dist/index.d.ts` confirmed present in the build output. `main`/`module`\n * are left alone — they correctly point at `index.esm.js`, which *is* emitted;\n * only the declaration paths are wrong.\n *\n * The same class of bug as the `node-function-app` manifest `main` fix: a\n * generator-written path that is right for a layout other than the one actually\n * produced. Guarded on the exact wrong value, so if Nx corrects this upstream the\n * repair 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 */\nfunction repairTypesPath(manifestPath: string): void {\n const manifest = readJson<{\n types?: 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 writeFileEnsured(manifestPath, toJson(manifest))\n}\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 repairTypesPath(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 repairTypesPath(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, readMnciConfig, 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 * 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 })\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 * Checks that no `.prettierrc` outranks mnci's `.prettierrc.json`.\n *\n * @remarks\n * Worth a dedicated check because the failure is invisible: both files exist and\n * both look fine, but Prettier's config resolution puts `.prettierrc` **above**\n * `.prettierrc.json`, so the entire formatting opinion is silently discarded.\n * `create-nx-workspace` writes the winning filename.\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 checkPrettierConfig(workspaceRoot: string): Finding {\n const strayExists = fileExists(join(workspaceRoot, '.prettierrc'))\n return {\n check: 'Prettier config is mnci’s',\n ok: !strayExists,\n detail: '.prettierrc exists and outranks .prettierrc.json, so mnci’s config is ignored',\n remedy: 'delete .prettierrc (or run `mnci upgrade`, which deletes it)'\n }\n}\n\n/**\n * Checks that exactly one linter mode's config files are present.\n *\n * @remarks\n * The failure this catches is a workspace carrying **both** modes at once —\n * `.prettierrc.mjs` alongside `.oxfmtrc.json`, or an `oxlint.config.ts` in a\n * workspace whose persisted linter is `eslint`. `mnci upgrade` removes the\n * losing mode's files, so this only happens when someone adds one by hand or\n * copies config between repos.\n *\n * It is worth a check for the same reason the `.prettierrc` one is: two formatter\n * configs is not a visible error. Each file is valid on its own, the CLI picks\n * one, the editor extension may pick the other, and the two gates disagree\n * silently — a file formatted correctly by `npm run format` and reformatted on\n * every save.\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 checkLinterModeIsConsistent(workspaceRoot: string): Finding {\n const linter = readMnciConfig(workspaceRoot).stack?.linter ?? 'eslint'\n const oxlintFiles = ['oxlint.config.ts', '.oxfmtrc.json'].filter(file =>\n fileExists(join(workspaceRoot, file))\n )\n const hasPrettier = fileExists(join(workspaceRoot, '.prettierrc.mjs'))\n\n const stray = linter === 'oxlint' ? (hasPrettier ? ['.prettierrc.mjs'] : []) : oxlintFiles\n\n return {\n check: `linter is ${linter} and only ${linter}'s config files are present`,\n ok: stray.length === 0,\n detail: `${stray.join(', ')} belongs to the other linter mode, so two configs are in play at once`,\n remedy: 'run `mnci upgrade`, which removes the mode the workspace did not choose'\n }\n}\n\n/**\n * The workspace's declared devDependencies, or none when it has no manifest.\n *\n * @remarks\n * Tolerant on purpose. `readJson` throws on a missing file, and doctor's whole\n * value is reporting what is wrong rather than crashing on it — a workspace\n * broken enough to have lost its `package.json` is exactly one someone would run\n * this on. The `nx.json` check is the only one entitled to throw, because\n * without it there is no workspace to inspect at all.\n *\n * @param workspaceRoot - Absolute path to the workspace.\n * @returns The declared devDependencies, or an empty record.\n * @throws Never - a missing or unreadable manifest reads as empty.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction declaredDevDependencies(workspaceRoot: string): Record<string, string> {\n const manifestPath = join(workspaceRoot, 'package.json')\n if (!fileExists(manifestPath)) return {}\n const manifest = readJson<{ devDependencies?: Record<string, string> }>(manifestPath)\n return manifest.devDependencies ?? {}\n}\n\n/**\n * Checks that only the chosen mode's formatter is declared.\n *\n * @remarks\n * The dependency half of {@link checkLinterModeIsConsistent}, and it is a\n * separate finding because it fails for a different reason: the config-file\n * check catches a file somebody added, this catches a declaration `mnci upgrade`\n * itself used to leave behind when switching modes.\n *\n * Why the declaration matters at all, given that `@mnci/eslint-config` depends\n * on prettier outright and so puts it in `node_modules` regardless:\n * `esbenp.prettier-vscode` resolves the formatter from the **project's**\n * dependencies. A declared-but-unused prettier in an oxlint workspace is\n * therefore the thing that lets a globally installed prettier extension\n * reformat a file on save against the opinion oxfmt is not applying — with\n * `npm run format:check` (oxfmt) reporting the result as unformatted. Both\n * declared, both resolvable, neither one wrong on its own.\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 checkOneFormatterDeclared(workspaceRoot: string): Finding {\n const linter = readMnciConfig(workspaceRoot).stack?.linter ?? 'eslint'\n const stale = linter === 'oxlint' ? 'prettier' : 'oxfmt'\n const declared = declaredDevDependencies(workspaceRoot)[stale] !== undefined\n\n return {\n check: `only ${linter === 'oxlint' ? 'oxfmt' : 'prettier'} is declared as the formatter`,\n ok: !declared,\n detail: `${stale} is declared but nothing runs it — the editor may still resolve and apply it, disagreeing with \\`npm run format:check\\``,\n remedy: 'run `mnci upgrade`, which drops the formatter of the mode you left'\n }\n}\n\n/**\n * Checks that an oxlint workspace declares the binaries it needs.\n *\n * @remarks\n * `@mnci/oxlint-config` **peers** on `oxlint`, so the workspace has to declare\n * it — and `oxfmt` is what the `format` script invokes. Missing either turns\n * `npm run lint`/`npm run format` into a \"command not found\" at the worst moment\n * rather than at install time.\n *\n * Only meaningful for an oxlint workspace, so it reports `ok` for an ESLint one\n * rather than being skipped: a check that silently disappears is one nobody\n * notices has stopped running.\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 checkOxlintToolchainDeclared(workspaceRoot: string): Finding {\n const linter = readMnciConfig(workspaceRoot).stack?.linter ?? 'eslint'\n if (linter !== 'oxlint') {\n return {\n check: 'oxlint toolchain declared',\n ok: true,\n detail: 'not an oxlint workspace',\n remedy: ''\n }\n }\n const devDeps = declaredDevDependencies(workspaceRoot)\n const missing = ['oxlint', 'oxfmt', '@mnci/oxlint-config'].filter(\n dependency => devDeps[dependency] === undefined\n )\n\n return {\n check: 'oxlint toolchain declared in devDependencies',\n ok: missing.length === 0,\n detail: `missing: ${missing.join(', ')} — lint or format will fail with \"command not found\"`,\n remedy: 'run `mnci upgrade`, which writes the toolchain the chosen linter needs'\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 ]\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 * 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 checkPrettierConfig(workspaceRoot),\n checkLinterModeIsConsistent(workspaceRoot),\n checkOneFormatterDeclared(workspaceRoot),\n checkOxlintToolchainDeclared(workspaceRoot),\n checkEslintPlugin(nxJson),\n checkResolvedEslint(workspaceRoot),\n checkNpmrc(workspaceRoot, nxJson.mnci?.registry, nxJson.mnci?.scope),\n ...checkVersionActions(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 { 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 is ESLint + Prettier (always).\n *\n * @typeParam None - this type has no generic type parameters.\n */\n/**\n * Which linter (and therefore which formatter) a workspace uses.\n *\n * @remarks\n * `oxlint` is a **hybrid**, not a replacement. oxlint reads JS/TS/JSX/Vue and\n * nothing else, so an oxlint workspace still gets an ESLint config — built from\n * `@mnci/eslint-config`'s `nonJs()` — covering YAML, TOML, Markdown, CSS, HTML,\n * JSON and `@nx/dependency-checks`. Choosing oxlint buys speed on the JS/TS\n * half; it does not buy a workspace where a duplicate key in a CI pipeline goes\n * unreported.\n *\n * The formatter follows the linter rather than being a third choice, because the\n * two are one opinion: `eslint` pairs with Prettier via\n * `@mnci/eslint-config/prettier`, `oxlint` with oxfmt via\n * `@mnci/oxlint-config/oxfmt`. Both are the same seven Standard options, and a\n * test asserts they are equal, so the choice is about speed and toolchain, not\n * about style.\n */\nexport type LinterChoice = 'eslint' | 'oxlint'\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 /** Linter and paired formatter. See {@link LinterChoice}. */\n linter: LinterChoice\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 is always ESLint + Prettier.\n */\nexport const DEFAULT_STACK: StackConfig = { testRunner: 'jest', linter: 'eslint' }\n\n/**\n * Prettier version pinned into all workspaces.\n *\n * @remarks\n * Prettier handles code formatting following JavaScript Standard Style\n * in all generated workspaces.\n */\nexport const PRETTIER_VERSION = '^3.8.1'\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 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${feedKey}:username=AzureArtifacts\n${feedKey}:_password=\\${PAT}\n${feedKey}: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 * `.prettierrc.json` is deliberately absent: Prettier is not a project target,\n * so the pipeline's `format:check` step runs `prettier --check .` over the whole\n * tree on every run regardless. Listing it would invalidate every project's\n * cache and verify nothing new. `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 Prettier config written into generated workspaces.\n *\n * @remarks\n * The options themselves no longer live here — they live in\n * `@mnci/eslint-config/prettier`, and this file only re-exports them. Lint and\n * format are one opinion, so shipping them as one package means they cannot\n * drift into disagreeing, and a formatting fix reaches an existing workspace\n * through `npm update` rather than needing `mnci upgrade`. The file keeps\n * comments explaining the override path, which is why it is `.mjs` and not JSON.\n *\n * **Written as `.prettierrc.mjs`, and both other candidates are deleted.**\n * Prettier's precedence is `.prettierrc` → `.prettierrc.json` → … →\n * `.prettierrc.mjs`, so a leftover file of either earlier kind wins outright and\n * silently reinstates the old opinion. {@link removeNxScaffolding} therefore\n * removes `.prettierrc` (which `create-nx-workspace` writes as\n * `{ \"singleQuote\": true }`) **and** `.prettierrc.json` (which mnci itself wrote\n * before this change, so an `mnci upgrade` must clear it). Getting that wrong is\n * not hypothetical: it is exactly the bug that made every option in this file\n * silently ignored in every generated workspace until it was found with\n * `prettier.resolveConfig`.\n *\n * Prettier owns all formatting. The three JavaScript Standard rules Prettier\n * never touches (`spaced-comment`, `lines-between-class-members`,\n * `unicode-bom`) live in `@mnci/eslint-config`'s stylistic block instead.\n * `space-before-function-paren` is deliberately NOT among them: Prettier\n * actively reverses it, so enabling it would make `lint` and `format:check`\n * mutually unsatisfiable.\n */\nexport const PRETTIER_CONFIG = `// The formatting half of @mnci/eslint-config.\n//\n// Prettier's options live in the same package as the ESLint rules on purpose:\n// linting and formatting are one decision. \\`eslint-config-prettier\\` is composed\n// last inside that package so every formatting rule defers to these settings, and\n// splitting the two across packages means a version pair that can drift until\n// \\`npm run lint\\` and \\`npm run format:check\\` disagree with each other.\n//\n// Consuming it as a shareable config also means a formatting fix reaches this\n// workspace through \\`npm update\\`, the same way a rule change does, instead of\n// waiting for \\`mnci upgrade\\` to rewrite a file.\n//\n// TO OVERRIDE an option, spread it rather than re-exporting:\n//\n// import mnci from '@mnci/eslint-config/prettier'\n// export default { ...mnci, printWidth: 120 }\n//\n// TO SEE the resolved options for a file: npx prettier --find-config-path <file>\nexport { default } from '@mnci/eslint-config/prettier'\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, linter: LinterChoice): 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: vscodeExtensions(linter) }\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 'eslint .',\n '--ignore-pattern \"apps/**\"',\n '--ignore-pattern \"libs/**\"',\n '--ignore-pattern \"packages/**\"',\n '--ignore-pattern \"python-packages/**\"',\n '--ignore-pattern package-lock.json'\n ].join(' '),\n cwd: '.'\n }\n} as const\n\n/**\n * npm `overrides` 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 * 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 * The devDependencies that belong to exactly one linter mode.\n *\n * @remarks\n * Read as \"declared by this mode, and stale in the other one\". Everything\n * absent from both lists — `eslint`, `@nx/eslint`, `@nx/eslint-plugin`,\n * `@mnci/eslint-config` — is shared, because the hybrid runs ESLint in both\n * modes and pruning one of those would break the mode being switched *to*.\n *\n * `prettier` is on the `eslint` side even though `@mnci/eslint-config` depends\n * on it outright, so it stays in `node_modules` regardless. The declaration is\n * what matters: the VS Code extension resolves the formatter from the\n * **project's** dependencies, so declaring prettier in an oxlint workspace is\n * what lets a globally installed `esbenp.prettier-vscode` reformat a file on\n * save against the opinion oxfmt is not applying. Two formatters, both\n * resolvable, disagreeing silently — the `.prettierrc` precedence bug's exact\n * shape, which is why the fix is symmetric with deleting the other mode's\n * config file rather than merely not writing this one.\n */\nexport const LINTER_ONLY_DEPENDENCIES = {\n eslint: ['prettier'],\n oxlint: ['@mnci/oxlint-config', 'oxlint', 'oxfmt']\n} as const\n\n/**\n * Drops the devDependencies belonging to the linter mode the workspace left.\n *\n * @remarks\n * Only `mnci upgrade --linter=<other>` reaches this with anything to remove; on\n * `mnci new` the manifest has neither set yet. Without it a switched workspace\n * keeps declaring the formatter it no longer runs, since the merge starts from\n * the existing `devDependencies` and a spread can only add.\n *\n * @param devDeps - The merged devDependency set.\n * @param linter - The linter the workspace is being written for.\n * @returns The same set, minus the other mode's entries.\n * @throws Never - pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function withoutStaleLinterDependencies(\n devDeps: Record<string, string>,\n linter: LinterChoice\n): Record<string, string> {\n const stale = new Set<string>(LINTER_ONLY_DEPENDENCIES[linter === 'oxlint' ? 'eslint' : 'oxlint'])\n return Object.fromEntries(Object.entries(devDeps).filter(([name]) => !stale.has(name)))\n}\n\n/**\n * The root `lint` target for one linter choice.\n *\n * @remarks\n * **This is what actually runs oxlint.** Nothing else does, and that gap is worth\n * spelling out because it is invisible: an oxlint workspace can have a valid\n * `oxlint.config.ts`, a green `npm run lint`, and never invoke oxlint once —\n * every per-project `lint` target comes from `@nx/eslint/plugin` and runs ESLint\n * alone. A first pass shipped exactly that, and the e2e assertion \"lint runs\n * green\" passed while verifying nothing about the JS/TS half.\n *\n * So under oxlint the root target runs both, in the order they matter:\n *\n * 1. `oxlint .` over the **whole** workspace. Unlike ESLint's root invocation\n * this is deliberately not scoped away from `apps/`, `libs/` and `packages/`:\n * oxlint is designed to sweep a repo in one pass, and no per-project target\n * covers it, so scoping here would leave JS/TS linted by nothing.\n * 2. `eslint .` on root files only, exactly as the eslint path does — the\n * per-project ESLint targets already cover the project trees.\n *\n * `parallel: false` so a failure names which linter failed rather than\n * interleaving two outputs.\n *\n * @param linter - The workspace's linter choice.\n * @returns The `lint` target for the root project.\n * @throws Never - performs pure object construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rootLintTarget(linter: LinterChoice): Record<string, unknown> {\n if (linter !== 'oxlint') return ROOT_LINT_TARGET\n return {\n executor: 'nx:run-commands',\n cache: true,\n options: {\n commands: ['oxlint .', ROOT_LINT_TARGET.options.command],\n parallel: false,\n cwd: '.'\n }\n }\n}\n\n/**\n * The `oxlint` version generated workspaces declare.\n *\n * @remarks\n * Declared by the workspace because `@mnci/oxlint-config` peers on it, the same\n * arrangement `eslint` has — the config cannot pick the binary's version for you.\n */\nexport const OXLINT_VERSION = '^1.76.0'\n\n/**\n * The `oxfmt` version generated workspaces declare.\n *\n * @remarks\n * Pre-1.0, and pinned by caret deliberately: it is the one part of the oxlint\n * stack whose output could change between minors, and a test diffs it against\n * Prettier so a drift is reported rather than silently reformatting a repo.\n */\nexport const OXFMT_VERSION = '^0.61.0'\n\n/**\n * The extra dependencies an **oxlint** workspace needs on top of the ESLint set.\n *\n * @remarks\n * On top of, not instead of: the hybrid keeps ESLint for the file types oxlint\n * cannot parse, so `eslintToolchainDependencies` still applies in full.\n *\n * `oxlint` is a peer of `@mnci/oxlint-config` rather than a dependency, so the\n * workspace has to declare it — the same reason `eslint` is declared. `oxfmt` is\n * declared for the reason `prettier` is: the editor extension resolves the\n * formatter from the project, not from a transitive dependency.\n *\n * @returns The oxlint-specific devDependencies.\n * @throws Never - returns a literal.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function oxlintToolchainDependencies(): Record<string, string> {\n return {\n '@mnci/oxlint-config': oxlintConfigSpec(),\n oxlint: OXLINT_VERSION,\n oxfmt: OXFMT_VERSION\n }\n}\n\n/**\n * The `@mnci/oxlint-config` version generated workspaces depend on.\n *\n * @remarks\n * A caret range, so `npm update` carries rule fixes into existing workspaces\n * without an `mnci upgrade` — the same reason the ESLint config ships as a\n * package rather than as a template string.\n */\nexport const OXLINT_CONFIG_VERSION = '^0.1.0'\n\n/**\n * The `@mnci/oxlint-config` spec to write into a generated workspace's manifest.\n *\n * @remarks\n * Reads `MNCI_OXLINT_CONFIG_SPEC` so the e2e can point this at a locally packed\n * tarball instead of the registry, the same escape hatch\n * {@link eslintConfigSpec} uses — without it, `npm install` in a freshly\n * generated oxlint workspace 404s until the package has been published once.\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 oxlintConfigSpec(): string {\n return process.env.MNCI_OXLINT_CONFIG_SPEC ?? OXLINT_CONFIG_VERSION\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/prettier-compat eslint-config-prettier — switches off every rule\n// Prettier owns. Composed LAST, on purpose.\n// mnci/stylistic the 3 Standard rules Prettier does not touch\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// One override cannot work, and it is the one people reach for first:\n// \\`space-before-function-paren\\`. Prettier rewrites \\`f (a)\\` to \\`f(a)\\` on every\n// run, so switching that rule on makes \\`npm run lint\\` and \\`npm run format:check\\`\n// impossible to satisfy at the same time. Formatting lives in .prettierrc.mjs.\nexport default mnci({ workspaceRoot: import.meta.dirname })\n`\n\n/**\n * The root `eslint.config.mjs` for an **oxlint** workspace.\n *\n * @remarks\n * The hybrid's ESLint half. oxlint reads JS/TS/JSX/Vue and nothing else, so\n * without this a workspace that chose oxlint would lint its CI YAML, its\n * `pyproject.toml`, its Markdown, CSS, HTML and JSON with nothing at all — and\n * would lose `@nx/dependency-checks`, the only thing stopping a publishable\n * package's manifest from declaring the wrong dependencies.\n *\n * `nonJs()` composes exactly those blocks from the same modules the full config\n * uses, so a rule added to `configs/yaml.js` reaches both modes.\n */\nexport const ESLINT_CONFIG_NON_JS = `import { nonJs } from '@mnci/eslint-config'\n\n// This workspace lints JS/TS with oxlint (see oxlint.config.ts). ESLint keeps\n// the file types oxlint cannot parse at all — YAML, TOML, Markdown, CSS, HTML,\n// JSON — plus @nx/dependency-checks on publishable manifests.\n//\n// The JS/TS rule blocks are deliberately NOT composed here. Running both linters\n// over the same file would report one defect twice under two different names.\n//\n// TO OVERRIDE, append a named block after the spread, exactly as the full config\n// documents. To change a JS/TS rule, edit oxlint.config.ts instead — this file\n// does not lint JS/TS.\nexport default [...nonJs({ workspaceRoot: import.meta.dirname })]\n`\n\n/**\n * The root `oxlint.config.ts` written into oxlint workspaces.\n *\n * @remarks\n * `oxlint.config.ts`, not `.oxlintrc.json`, and that is forced rather than\n * preferred: `.oxlintrc`'s `extends` takes **paths** resolved relative to the\n * config file, so `extends: [\"@mnci/oxlint-config\"]` fails with\n * `No such file or directory`. `defineConfig` is the only shareable-config route\n * oxlint offers. Verified both ways.\n */\nexport const OXLINT_CONFIG = `import { defineConfig } from 'oxlint'\nimport mnci from '@mnci/oxlint-config'\n\n// oxlint owns JS/TS/JSX here; eslint.config.mjs keeps everything oxlint cannot\n// parse. The contract @mnci/oxlint-config holds itself to: anything\n// @mnci/eslint-config accepts must pass oxlint, so switching linters never turns\n// a green codebase red.\n//\n// oxlint.config.ts, not .oxlintrc.json, and that is not a preference: .oxlintrc's\n// \\`extends\\` takes PATHS relative to the config file, so\n// \\`extends: [\"@mnci/oxlint-config\"]\\` fails with \"No such file or directory\".\n// defineConfig is the only shareable-config route oxlint offers.\n//\n// Type-aware rules (no-floating-promises and friends) are opt-in — pass\n// { typeAware: true } AND run oxlint with --type-aware. They report findings the\n// ESLint stack accepts, so they are off by default to keep the contract above.\nexport default defineConfig({ extends: [mnci()] })\n`\n\n/**\n * The `.oxfmtrc.json` written into oxlint workspaces.\n *\n * @remarks\n * Inlined rather than re-exported from `@mnci/oxlint-config/oxfmt`, unlike the\n * Prettier config's `.prettierrc.mjs`. oxfmt's config is JSON only — it has no\n * `.mjs` form to import a package from — so the options are duplicated here and\n * `tests/oxfmt.spec.ts` asserts the package's option set equals the ESLint\n * package's, which is what keeps all three in step.\n *\n * The `$schema` entry is what gives an editor completion and validation on the\n * file, and costs nothing.\n */\nexport const OXFMT_CONFIG = `${JSON.stringify(\n {\n $schema: './node_modules/oxfmt/configuration_schema.json',\n semi: false,\n singleQuote: true,\n trailingComma: 'none',\n arrowParens: 'avoid',\n printWidth: 100,\n tabWidth: 2,\n useTabs: false\n },\n null,\n 2\n)}\\n`\n\n/**\n * The .prettierignore written into generated workspaces.\n *\n * @remarks\n * Patterns to exclude from Prettier formatting, matching root locations.\n *\n * The non-JS entries are not padding. This list used to cover only the\n * JavaScript toolchain, while mnci also generates Python and Dart projects whose\n * tool directories land inside the workspace — and `packages/cli/README.md`\n * explicitly tells users to create a venv there. So `npm run format:check`\n * failed on the `site-packages` tree under `.venv` in any workspace that\n * followed the documented Python setup, and `npm run format` would have\n * rewritten files inside installed third-party packages. Harmless while\n * formatting was unenforced; a hard failure now that it is a CI gate, which is\n * how it surfaced.\n */\nexport const PRETTIER_IGNORE = `node_modules\ndist\ndist-dev\ndist-uat\ndist-prod\ncoverage\n.next\nout\n.nx\ntmp\npackage-lock.json\n*.lock\n.venv\nvenv\n__pycache__\n.pytest_cache\n.ruff_cache\n.dart_tool\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 */\nexport const VSCODE_RECOMMENDED_EXTENSIONS = [\n 'dbaeumer.vscode-eslint',\n 'esbenp.prettier-vscode',\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner'\n] as const\n\n/**\n * The editor extensions that depend on which linter the workspace chose.\n *\n * @remarks\n * **`oxc.oxc-vscode` is one extension covering both halves of the Rust\n * toolchain** — it provides oxlint *and* oxfmt integration, verified on the\n * Marketplace rather than assumed, so an oxlint workspace needs no separate\n * formatter extension and `esbenp.prettier-vscode` is correctly absent.\n *\n * **`dbaeumer.vscode-eslint` stays in BOTH lists, and that is the hybrid\n * showing through.** Choosing oxlint does not remove ESLint from the workspace:\n * oxlint cannot parse YAML, TOML, Markdown, CSS, HTML or JSON, so\n * `@mnci/eslint-config`'s `nonJs()` keeps covering those (plus\n * `@nx/dependency-checks` on publishable manifests). Dropping the ESLint\n * extension would leave a contributor editing `azure-pipelines.yml` with no\n * in-editor feedback from a linter that *is* running in CI — the worst kind of\n * gap, because the file still fails the build.\n */\nexport const LINTER_EXTENSIONS = {\n eslint: ['dbaeumer.vscode-eslint', 'esbenp.prettier-vscode'],\n oxlint: ['oxc.oxc-vscode', 'dbaeumer.vscode-eslint']\n} as const\n\n/**\n * Extensions every workspace gets, whichever linter it chose.\n *\n * @remarks\n * Nx's console and a test-runner integration: neither depends on the lint\n * toolchain, so they are factored out rather than repeated in both lists, where\n * one of the two copies would eventually be the stale one.\n */\nexport const SHARED_VSCODE_EXTENSIONS = [\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner'\n] as const\n\n/**\n * Every language **both** formatters handle, and whose formatter mnci pins.\n *\n * @remarks\n * Not a convenience list — see {@link vscodeSettings} for why the general\n * `editor.defaultFormatter` cannot be relied on. The set is \"everything the\n * chosen formatter actually handles\", so it is measured against the real\n * binaries rather than assumed: each entry below is a file type Prettier 3.8\n * *and* oxfmt 0.61 both reformat.\n *\n * `typescript` and `typescriptreact` are the two that matter most and were the\n * two missing. `html` was missing too — both formatters have always handled it,\n * and the claim in this comment was simply not checked when it was written.\n *\n * See {@link OXFMT_ONLY_LANGUAGES} for the one file type where the two\n * formatters differ in what they can parse at all.\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] as const\n\n/**\n * Languages only oxfmt can format, pinned in oxlint workspaces alone.\n *\n * @remarks\n * TOML, and it is a capability difference rather than a preference: `prettier`\n * on a `.toml` exits with `No parser could be inferred for file`, while oxfmt\n * reformats it (`name = \"x\"` → `name = \"x\"`, `[ 1,2 ]` → `[1, 2]`). Measured\n * against both binaries.\n *\n * So an oxlint workspace gets something the default cannot have: a formatted\n * `pyproject.toml`. `configs/toml.js` in `@mnci/eslint-config` is parser-only\n * *because* Prettier has no TOML support — the one style rule tried there\n * reported six errors on the `pyproject.toml` `@mnci/nx-python-pip` itself\n * generates. Nothing here re-opens that: `nx-python-pip` writes the file, oxfmt\n * reformats it on save, and no lint rule has an opinion either way.\n *\n * Pinning it under `eslint` would be worse than useless — VS Code would route\n * `.toml` to a formatter that errors on it.\n */\nexport const OXFMT_ONLY_LANGUAGES = ['toml'] as const\n\n/**\n * The languages to pin a formatter for, for one linter choice.\n *\n * @remarks\n * {@link FORMATTED_LANGUAGES} plus {@link OXFMT_ONLY_LANGUAGES} under `oxlint`.\n * A function rather than two exported constants at the call site, so the one\n * place that needs the union cannot forget half of it.\n *\n * @param linter - The workspace's linter choice.\n * @returns The language identifiers, in a stable order.\n * @throws Never - pure array construction.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function formattedLanguages(linter: LinterChoice): string[] {\n return [...FORMATTED_LANGUAGES, ...(linter === 'oxlint' ? OXFMT_ONLY_LANGUAGES : [])]\n}\n\n/**\n * The extension list for one linter choice.\n *\n * @remarks\n * The single source both the `.code-workspace` file and the devcontainer read,\n * so opening the workspace in a container cannot suggest a different toolset\n * from opening it directly — the reason the list was shared in the first place,\n * now that there are two of them to keep straight.\n *\n * @param linter - The workspace's linter choice.\n * @returns The recommended extension IDs, linter-specific ones first.\n * @throws Never - performs a pure lookup.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function vscodeExtensions(linter: LinterChoice): string[] {\n return [...LINTER_EXTENSIONS[linter], ...SHARED_VSCODE_EXTENSIONS]\n}\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` is narrowed for oxlint to exactly the languages ESLint still\n * owns there. Leaving JS/TS in that list would ask the ESLint extension to\n * validate files whose config has no rules for them — reporting nothing, but\n * spending a language-server round trip per keystroke to do it, and telling the\n * reader that ESLint covers JS when it does not.\n *\n * @param linter - The workspace's linter choice.\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(linter: LinterChoice): Record<string, unknown> {\n const formatter = linter === 'oxlint' ? 'oxc.oxc-vscode' : 'esbenp.prettier-vscode'\n const eslintLanguages =\n linter === 'oxlint'\n ? ['json', 'jsonc', 'markdown', 'yaml', 'html', 'css']\n : [\n 'javascript',\n 'javascriptreact',\n 'typescript',\n 'typescriptreact',\n 'json',\n 'jsonc',\n 'markdown',\n 'yaml'\n ]\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. Nothing reports it: Prettier is installed,\n // the config resolves, `npm run format:check` still finds the 68 unformatted\n // files, and the editor simply never applies it.\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 formattedLanguages(linter).map(language => [\n `[${language}]`,\n { 'editor.defaultFormatter': formatter }\n ])\n )\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, Prettier), 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 * 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 linter: LinterChoice,\n existingTasks?: { version?: string; tasks?: Record<string, unknown>[] }\n): string {\n return JSON.stringify(\n {\n folders: [{ path: '.', name: workspaceName }],\n settings: vscodeSettings(linter),\n extensions: { recommendations: vscodeExtensions(linter) },\n tasks: {\n version: existingTasks?.version ?? '2.0.0',\n tasks: existingTasks?.tasks ?? []\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 formatting via Prettier and linting via ESLint.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`) following code-quality\n * and correctness rules. Prettier handles all formatting (`npm run format` writes;\n * `npm run format:check` verifies without writing), following JavaScript Standard Style.\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(linter: LinterChoice = 'eslint'): Record<string, string> {\n // oxfmt reads `.gitignore` and `.prettierignore` by default, so the same\n // `.prettierignore` mnci already writes governs both formatters and there is no\n // second ignore file to keep in sync. Verified against oxfmt's own CLI docs.\n const formatter = linter === 'oxlint' ? 'oxfmt' : 'prettier'\n return {\n ...ROOT_SCRIPTS,\n format: linter === 'oxlint' ? 'oxfmt .' : 'prettier --write .',\n 'format:check': `${formatter} --check .`,\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, linter: options.stack.linter }\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 const onMain = `and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), 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: [main]\n\n# Note: cancelling a superseded PR validation run is NOT a YAML setting on Azure\n# — it lives in the branch policy (\"Build validation -> automatically cancel\").\n# Nothing here can express it, so it is left to whoever configures the policy\n# rather than faked with a batch setting that would only delay PR runs.\npr:\n branches:\n include: [main]\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 # Prettier owns ALL formatting in this workspace, so it needs a gate of its\n # own: ESLint is configured for correctness only and deliberately reports\n # nothing about formatting. Without this step the entire formatting opinion is\n # advisory, and a workspace drifts out of compliance with no signal anywhere.\n #\n # Stays workspace-wide even on a PR, unlike the verify step below: it is one\n # Prettier invocation over the whole tree, not a per-project Nx target, so\n # there is nothing to scope down and formatting is never partially checked.\n - script: npm run format:check\n displayName: Check formatting (run 'npm run format' locally to fix)\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 # Prettier owns ALL formatting in this workspace, so it needs a gate of\n # its own: ESLint is configured for correctness only and deliberately\n # reports nothing about formatting. Without this step the entire\n # formatting opinion is advisory, and a workspace drifts out of\n # compliance with no signal anywhere.\n #\n # Stays workspace-wide even on a PR, unlike the verify step below: it is\n # one Prettier invocation over the whole tree, not a per-project Nx\n # target, so there is nothing to scope down and formatting is never\n # partially checked.\n - run: npm run format:check\n name: Check formatting (run 'npm run format' locally to fix)\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 * {@link PRETTIER_CONFIG} 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`, `.prettierrc.mjs`,\n * `.prettierignore`, `.npmrc`, `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(workspaceRoot: string, options: OverlayOptions): 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 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 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(options.stack.linter)\n }\n const existingDevDeps = manifest.devDependencies as Record<string, string> | undefined\n const devDeps = withoutStaleLinterDependencies(\n {\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 // Additive for the linter, exclusive for the formatter: the hybrid keeps\n // the whole ESLint toolchain for the file types oxlint cannot parse, but\n // exactly one of prettier/oxfmt formats the workspace. Declaring both is\n // how an editor ends up applying the one CI does not check.\n ...(options.stack.linter === 'oxlint'\n ? oxlintToolchainDependencies()\n : { prettier: PRETTIER_VERSION })\n },\n options.stack.linter\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 }\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: rootLintTarget(options.stack.linter)\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 writeFileEnsured(join(workspaceRoot, '.npmrc'), npmrcContent(options.registry, options.scope))\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 and correctness rules; Prettier handles all\n // formatting (JavaScript Standard Style). ONE ESLint config, at the root —\n // `add` deletes the per-project ones Nx generators write.\n // The linter choice decides which config files exist, and just as importantly\n // which do NOT: switching a workspace between modes has to remove the previous\n // mode's files, or both formatters end up configured and whichever the editor\n // picks wins. `mnci upgrade` runs this same code path, so a switch is a\n // one-command operation rather than a manual cleanup.\n if (options.stack.linter === 'oxlint') {\n writeFileEnsured(join(workspaceRoot, 'oxlint.config.ts'), OXLINT_CONFIG)\n writeFileEnsured(join(workspaceRoot, '.oxfmtrc.json'), OXFMT_CONFIG)\n // ESLint stays, trimmed to what oxlint cannot parse. See ESLINT_CONFIG_NON_JS.\n writeFileEnsured(join(workspaceRoot, 'eslint.config.mjs'), ESLINT_CONFIG_NON_JS)\n removeIfPresent(join(workspaceRoot, '.prettierrc.mjs'))\n } else {\n writeFileEnsured(join(workspaceRoot, 'eslint.config.mjs'), ESLINT_CONFIG)\n writeFileEnsured(join(workspaceRoot, '.prettierrc.mjs'), PRETTIER_CONFIG)\n removeIfPresent(join(workspaceRoot, 'oxlint.config.ts'))\n removeIfPresent(join(workspaceRoot, '.oxfmtrc.json'))\n }\n // Makes a local environment match the one CI verifies — see devcontainerJson.\n writeFileEnsured(\n join(workspaceRoot, '.devcontainer/devcontainer.json'),\n devcontainerJson(options.workspaceName, options.stack.linter)\n )\n writeFileEnsured(join(workspaceRoot, '.prettierignore'), PRETTIER_IGNORE)\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 const codeWorkspacePath = join(workspaceRoot, `${options.workspaceName}.code-workspace`)\n const existing = readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n }>(codeWorkspacePath)\n writeFileEnsured(\n codeWorkspacePath,\n vscodeWorkspace(options.workspaceName, options.stack.linter, existing?.tasks)\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 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 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 { 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 /** Linter + paired formatter. See {@link StackConfig}. */\n linter?: StackConfig['linter']\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 // Either flag skips the prompt, so a caller can pin one half of the stack and\n // take the default for the other without being asked about it.\n if (options.testRunner || options.linter || options.yes) {\n return {\n testRunner: options.testRunner ?? DEFAULT_STACK.testRunner,\n linter: options.linter ?? DEFAULT_STACK.linter\n }\n }\n const prompted = await promptStack()\n return {\n testRunner: prompted.testRunner,\n linter: prompted.linter\n }\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(workspaceRoot, { workspaceName, scope, registry, agent, variableGroup, ci, stack })\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 `format:check`\n // from the very first commit.\n logger.step(\n `Formatting the workspace (${stack.linter === 'oxlint' ? 'oxfmt' : 'Prettier'}, JavaScript Standard Style)`\n )\n runFormatter(workspaceRoot, stack.linter)\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 /** Override the persisted linter choice. See {@link StackConfig}. */\n linter?: StackConfig['linter']\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 // above. Every workspace generated before the linter choice existed has no\n // persisted value and is an ESLint workspace, so an upgrade of one must be a\n // no-op here — demanding a flag would break `mnci upgrade` for every existing\n // workspace, which is the one thing that command must never do.\n const linter = options.linter ?? persisted.stack?.linter ?? 'eslint'\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, linter }\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(\n 'Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)'\n )\n applyOverlay(workspaceRoot, resolved)\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 // The linter comes from `resolved`, which is what `applyOverlay` was just\n // given — so a `mnci upgrade --linter=oxlint` formats with the formatter it\n // has this moment switched TO, not the one the workspace had on the way in.\n runFormatter(workspaceRoot, resolved.stack.linter)\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAkC;;;ACAlC,IAAAA,qBAAqB;AACrB,IAAAC,kBAAuB;;;ACDvB,yBAAkB;;;ACOX,IAAM,SAAS;AAAA,EACpB,KAAK,SAAuB;AAC1B,YAAQ,IAAI,OAAO;AAAA,EACrB;AAAA,EACA,KAAK,SAAuB;AAC1B,YAAQ,IAAI,UAAK,OAAO,EAAE;AAAA,EAC5B;AAAA,EACA,QAAQ,SAAuB;AAC7B,YAAQ,IAAI,UAAK,OAAO,EAAE;AAAA,EAC5B;AAAA,EACA,KAAK,SAAuB;AAC1B,YAAQ,KAAK,KAAK,OAAO,EAAE;AAAA,EAC7B;AAAA,EACA,MAAM,SAAuB;AAC3B,YAAQ,MAAM,UAAK,OAAO,EAAE;AAAA,EAC9B;AACF;;;ADQO,SAAS,SAAS,SAAiB,YAAsB,KAAqB;AACnF,QAAM,SAAS,mBAAAC,QAAM,KAAK,SAAS,YAAY,EAAE,OAAO,WAAW,IAAI,CAAC;AACxE,SAAO,OAAO,UAAU;AAC1B;AAeO,SAAS,MAAM,YAAsB,KAAmB;AAC7D,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,GAAG,UAAU,GAAG,GAAG;AACzD,MAAI,WAAW,GAAG;AAChB,UAAM,IAAI,MAAM,MAAM,WAAW,KAAK,GAAG,CAAC,0BAA0B,MAAM,EAAE;AAAA,EAC9E;AACF;AAeO,SAAS,OAAO,YAAsB,KAAmB;AAC9D,QAAM,SAAS,SAAS,OAAO,YAAY,GAAG;AAC9C,MAAI,WAAW,GAAG;AAChB,UAAM,IAAI,MAAM,OAAO,WAAW,KAAK,GAAG,CAAC,0BAA0B,MAAM,EAAE;AAAA,EAC/E;AACF;AA0CO,SAAS,aAAa,KAAa,QAAsB,SAAS,KAAW;AAIlF,QAAM,CAAC,SAAS,UAAU,IACxB,WAAW,WACP,CAAC,SAAS,CAAC,WAAW,MAAM,CAAC,IAC7B,CAAC,YAAY,CAAC,WAAW,eAAe,QAAQ,MAAM,CAAC;AAC7D,QAAM,SAAS,SAAS,OAAO,CAAC,SAAS,GAAG,UAAU,GAAG,GAAG;AAC5D,MAAI,WAAW,GAAG;AAChB,WAAO;AAAA,MACL,GAAG,OAAO,sBAAsB,MAAM,gBAAgB,MAAM;AAAA,IAE9D;AAAA,EACF;AACF;;;AEnIA,qBAAuC;AAgBvC,eAAsB,WAAW,SAAiB,UAAoC;AACpF,QAAM,QAAQ,UAAM,sBAAM;AAAA,IACxB;AAAA,IACA,SAAS;AAAA,IACT,UAAU,CAACC,WAAkBA,OAAM,KAAK,EAAE,SAAS,KAAK;AAAA,EAC1D,CAAC;AACD,SAAO,MAAM,KAAK;AACpB;AAeA,eAAsB,eAAe,sBAAwD;AAC3F,QAAM,OAAO,UAAM,uBAA+B;AAAA,IAChD,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,mBAAmB,OAAO,kBAAkB;AAAA,MACpD,EAAE,MAAM,cAAc,OAAO,MAAM;AAAA,IACrC;AAAA,EACF,CAAC;AAED,MAAI,SAAS,OAAO;AAClB,WAAO,EAAE,KAAK;AAAA,EAChB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,cAAc,MAAM,WAAW,6BAA6B,oBAAoB;AAAA,IAChF,SAAS,MAAM,WAAW,sBAAsB;AAAA,IAChD,eAAe,MAAM,WAAW,qBAAqB;AAAA,EACvD;AACF;AAgBA,eAAsB,WAAgC;AACpD,SAAO,UAAM,uBAAmB;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,mBAAmB,OAAO,QAAQ;AAAA,MAC1C,EAAE,MAAM,kBAAkB,OAAO,SAAS;AAAA,MAC1C,EAAE,MAAM,QAAQ,OAAO,OAAO;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAeA,eAAsB,gBAAkC;AACtD,SAAO,UAAM,wBAAQ;AAAA,IACnB,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACH;AAyBA,eAAsB,cAAoC;AACxD,QAAM,aAAa,UAAM,uBAAkC;AAAA,IACzD,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,QAAQ,OAAO,OAAO;AAAA,MAC9B,EAAE,MAAM,UAAU,OAAO,SAAS;AAAA,IACpC;AAAA,EACF,CAAC;AACD,QAAM,SAAS,UAAM,uBAA8B;AAAA,IACjD,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO,EAAE,YAAY,OAAO;AAC9B;;;ACrJA,qBAA8E;AAC9E,uBAAwB;AAejB,SAAS,SAAY,MAAiB;AAC3C,SAAO,KAAK,UAAM,6BAAa,MAAM,MAAM,CAAC;AAC9C;AA0BO,SAAS,kBAAqB,MAA6B;AAChE,MAAI;AACF,WAAO,KAAK,UAAM,6BAAa,MAAM,MAAM,EAAE,WAAW,gBAAgB,IAAI,CAAC;AAAA,EAC/E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcO,SAAS,OAAO,OAAwB;AAC7C,SAAO,GAAG,KAAK,UAAU,OAAO,QAAW,CAAC,CAAC;AAAA;AAC/C;AAeO,SAAS,iBAAiB,MAAc,SAAuB;AACpE,oCAAU,0BAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,oCAAc,MAAM,OAAO;AAC7B;AAaO,SAAS,eAAe,MAAoB;AACjD,gCAAU,MAAM,GAAK;AACvB;AAaO,SAAS,WAAW,MAAuB;AAChD,aAAO,2BAAW,IAAI;AACxB;;;AC/DO,SAAS,uBAAuB,MAAc,OAAqB;AACxE,MAAI,CAAC,qCAAqC,KAAK,IAAI,GAAG;AACpD,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AACF;;;ACzDA,IAAAC,oBAAqB;;;ACArB,IAAAC,kBAAoC;AACpC,IAAAC,oBAAqB;AAgFd,SAAS,UAAU,eAAuB,aAA8B;AAC7E,QAAM,WAAW,aAGd,wBAAK,eAAe,cAAc,CAAC;AACtC,QAAM,YAAY,EAAE,GAAG,SAAS,cAAc,GAAG,SAAS,gBAAgB;AAC1E,SAAO,OAAO,OAAO,WAAW,WAAW;AAC7C;AAiBO,SAAS,aAAa,eAAuB,aAA2B;AAC7E,MAAI,UAAU,eAAe,WAAW,GAAG;AACzC;AAAA,EACF;AACA,SAAO,KAAK,wBAAwB,WAAW,EAAE;AACjD,QAAM,CAAC,OAAO,WAAW,GAAG,aAAa;AAC3C;AAeO,SAAS,YAAY,cAA4B;AACtD,QAAM,WAAW,SAAkC,YAAY;AAC/D,mBAAiB,cAAc,OAAO,EAAE,GAAG,UAAU,SAAS,KAAK,CAAC,CAAC;AACvE;AAmBO,SAAS,WAAW,cAA4B;AACrD,QAAM,WAAW,SAAkC,YAAY;AAC/D,mBAAiB,cAAc,OAAO,EAAE,GAAG,UAAU,eAAe,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC;AAC7F;AAgBO,SAAS,aAAa,eAA6B;AACxD,MAAI,UAAU,eAAe,SAAS,GAAG;AACvC;AAAA,EACF;AACA,SAAO,KAAK,uCAAuC;AACnD,MACE;AAAA,IACE;AAAA,IACA,CAAC,WAAW,cAAc,WAAW,cAAc,WAAW;AAAA,IAC9D;AAAA,EACF,MAAM,GACN;AACA,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACF;AAeO,SAAS,aAAa,eAA+B;AAC1D,QAAM,EAAE,KAAK,IAAI,aAA2B,wBAAK,eAAe,cAAc,CAAC;AAC/E,QAAM,QAAQ,KAAK,WAAW,GAAG,IAAI,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM,KAAK,CAAC,EAAE,CAAC;AAC1E,SAAO,IAAI,IAAI;AACjB;AAoBO,SAAS,aAAa,cAAsB,YAA2C;AAG5F,QAAM,WAAW,WAAW,YAAY,IAAI,SAAkC,YAAY,IAAI,CAAC;AAC/F,QAAM,KAAM,SAAS,MAA8C,CAAC;AACpE,QAAM,UAAW,GAAG,WAAmD,CAAC;AACxE;AAAA,IACE;AAAA,IACA,OAAO,EAAE,GAAG,UAAU,IAAI,EAAE,GAAG,IAAI,SAAS,EAAE,GAAG,SAAS,GAAG,WAAW,EAAE,EAAE,CAAC;AAAA,EAC/E;AACF;AASA,IAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8BO,SAAS,4BAA4B,eAAuB,aAA2B;AAC5F,aAAW,YAAY,yBAAyB;AAC9C,oCAAO,wBAAK,eAAe,aAAa,QAAQ,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACpE;AACA,kCAAO,wBAAK,eAAe,SAAS,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACzE;AAwCA,SAAS,sBAAsB,eAA2C;AACxE,MAAI;AACF,UAAM,YAAQ,6BAAY,aAAa,EAAE,KAAK,UAAQ,KAAK,SAAS,iBAAiB,CAAC;AACtF,WAAO,YAAQ,wBAAK,eAAe,KAAK,IAAI;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,SAAS,YAAY,MAAc,MAAyD;AAC1F,QAAM,SAAS,GAAG,IAAI,IAAI,IAAI;AAC9B,QAAM,OAAO,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,IAAI,MAAM,OAAO,QAAQ,gBAAgB,CAAC,EAAE;AAClF,SAAO,SAAS,UAAU,EAAE,GAAG,MAAM,cAAc,KAAK,IAAI,EAAE,GAAG,MAAM,OAAO,KAAK;AACrF;AA6BO,SAAS,wBACd,eACA,MACA,UACM;AACN,QAAM,UAAkC;AAAA,IACtC,CAAC,GAAG,IAAI,KAAK,GAAG,UAAU,IAAI,mBAAmB,IAAI;AAAA,EACvD;AACA,MAAI,SAAS,OAAO;AAClB,YAAQ,GAAG,IAAI,QAAQ,IAAI,UAAU,IAAI;AAAA,EAC3C;AACA,MAAI,SAAS,OAAO;AAClB,YAAQ,GAAG,IAAI,QAAQ,IAAI,SAAS;AAAA,EACtC;AAEA,QAAM,mBAAe,wBAAK,eAAe,cAAc;AACvD,QAAM,WAAW,SAAkC,YAAY;AAC/D,QAAM,kBAAmB,SAAS,WAAkD,CAAC;AACrF;AAAA,IACE;AAAA,IACA,OAAO,EAAE,GAAG,UAAU,SAAS,EAAE,GAAG,iBAAiB,GAAG,QAAQ,EAAE,CAAC;AAAA,EACrE;AAEA,QAAM,oBAAoB,sBAAsB,aAAa;AAC7D,MAAI,CAAC,mBAAmB;AACtB;AAAA,EACF;AACA,QAAM,gBACJ,kBAEG,iBAAiB,KAAK,CAAC;AAC5B,QAAM,QAAQ,CAAC,SAA2C,KAAK,SAAgC;AAC/F,QAAM,iBAAiB,cAAc,OAAO,SAAS,CAAC,GAAG;AAAA,IACvD,UAAQ,CAAC,MAAM,IAAI,EAAE,WAAW,GAAG,IAAI,IAAI;AAAA,EAC7C;AACA,QAAM,WAAW;AAAA,IACf,YAAY,MAAM,IAAI;AAAA,IACtB,GAAI,SAAS,QAAQ,CAAC,YAAY,MAAM,OAAO,CAAC,IAAI,CAAC;AAAA,IACrD,GAAI,SAAS,QAAQ,CAAC,YAAY,MAAM,OAAO,CAAC,IAAI,CAAC;AAAA,EACvD;AACA;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,GAAG;AAAA,MACH,OAAO;AAAA,QACL,SAAS,cAAc,OAAO,WAAW;AAAA,QACzC,OAAO,CAAC,GAAG,eAAe,GAAG,QAAQ;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ADrZA,SAAS,sBAAsB,iBAAyB,YAA2C;AACjG,QAAM,UAAU,SAAkC,eAAe;AACjE,QAAM,UAAW,QAAQ,WAAmD,CAAC;AAC7E,mBAAiB,iBAAiB,OAAO,EAAE,GAAG,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,CAAC;AAClG;AAgBA,SAAS,sBAAsB,MAAuC;AACpE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,SAAS,EAAE,SAAS,yBAAyB,KAAK,QAAQ,IAAI,GAAG;AAAA,EACnE;AACF;AAoBA,SAAS,cAAc,eAA6B;AAClD,MAAI,SAAS,WAAW,CAAC,WAAW,GAAG,aAAa,MAAM,GAAG;AAC3D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAgBA,SAAS,sBAA8B;AACrC,SAAO,QAAQ,IAAI,wBAAwB;AAC7C;AAiBA,SAAS,sBAAsB,eAA6B;AAC1D,MAAI,UAAU,eAAe,kBAAkB,GAAG;AAChD;AAAA,EACF;AACA,QAAM,OAAO,oBAAoB;AACjC,SAAO,KAAK,4CAA4C,IAAI,GAAG;AAC/D,MACE,SAAS,OAAO,CAAC,WAAW,cAAc,MAAM,cAAc,WAAW,GAAG,aAAa,MAAM,GAC/F;AACA,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACF;AAGA,SAAS,eAAe,eAA6B;AACnD,gBAAc,aAAa;AAC3B,wBAAsB,aAAa;AACrC;AAuBO,SAAS,cAAc,eAAuB,MAAoB;AACvE,iBAAe,aAAa;AAC5B,eAAa,aAAa;AAC1B,QAAM,CAAC,KAAK,gCAAgC,MAAM,kBAAkB,GAAG,aAAa;AACpF,4BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,OAAO,sBAAsB,IAAI;AAAA,EACnC,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;AAqBO,SAAS,cAAc,eAAuB,MAAoB;AACvE,iBAAe,aAAa;AAC5B,QAAM,CAAC,KAAK,4BAA4B,MAAM,kBAAkB,GAAG,aAAa;AAChF,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,CAAC;AAC/D;AAmBO,SAAS,sBAAsB,eAAuB,MAAoB;AAC/E,iBAAe,aAAa;AAC5B,QAAM,CAAC,KAAK,qCAAqC,MAAM,kBAAkB,GAAG,aAAa;AACzF,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,CAAC;AAC/D;;;AE9MA,IAAAC,kBAA6B;AAC7B,IAAAC,oBAAqB;AAmBrB,SAAS,SAAS,eAA6B;AAC7C,MAAI,SAAS,MAAM,CAAC,SAAS,GAAG,aAAa,MAAM,GAAG;AACpD,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACF;AAkBA,SAAS,qBAAqB,eAA6B;AACzD,MAAI,SAAS,iBAAiB,CAAC,WAAW,GAAG,aAAa,MAAM,GAAG;AACjE,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AAoBA,SAAS,iBAAyB;AAChC,SAAO,QAAQ,IAAI,mBAAmB;AACxC;AAUA,SAAS,iBAAiB,eAA6B;AACrD,MAAI,UAAU,eAAe,cAAc,GAAG;AAC5C;AAAA,EACF;AACA,QAAM,OAAO,eAAe;AAC5B,SAAO,KAAK,uCAAuC,IAAI,GAAG;AAC1D,MACE,SAAS,OAAO,CAAC,WAAW,cAAc,MAAM,cAAc,WAAW,GAAG,aAAa,MAAM,GAC/F;AACA,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACF;AAkCA,SAAS,eAAe,eAA6B;AACnD,MAAI,eAAW,wBAAK,eAAe,QAAQ,CAAC,GAAG;AAC7C;AAAA,EACF;AACA,SAAO,KAAK,4DAA4D;AACxE,QAAM,CAAC,KAAK,qBAAqB,kBAAkB,GAAG,aAAa;AACnE,QAAM,CAAC,KAAK,mCAAmC,kBAAkB,GAAG,aAAa;AACnF;AAcA,SAAS,aAAa,eAA2C;AAC/D,MAAI;AAEF,UAAM,eAAW,kCAAa,wBAAK,eAAe,QAAQ,GAAG,MAAM;AACnE,WAAO,mBAAmB,KAAK,QAAQ,IAAI,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkBA,SAASC,uBAAsB,iBAAyB,YAA2C;AACjG,QAAM,UAAU,SAAkC,eAAe;AACjE,QAAM,UAAW,QAAQ,WAAmD,CAAC;AAC7E,mBAAiB,iBAAiB,OAAO,EAAE,GAAG,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,CAAC;AAClG;AASA,SAAS,eAAwC;AAC/C,SAAO,EAAE,UAAU,oBAAoB;AACzC;AA2BA,SAAS,eAAwC;AAC/C,SAAO;AAAA,IACL,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS,EAAE,QAAQ,iBAAiB,MAAM,CAAC,KAAK,EAAE;AAAA,EACpD;AACF;AAsBA,SAAS,cAAc,MAAuC;AAC5D,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,CAAC,6BAA6B,IAAI,EAAE;AAAA,IAC7C,SAAS,EAAE,YAAY,mBAAmB,IAAI,IAAI,IAAI,GAAG;AAAA,EAC3D;AACF;AAqBA,SAAS,gBAAgB,KAAa,MAAuC;AAC3E,QAAM,MAAM,aAAa,GAAG,IAAI,IAAI;AACpC,QAAM,UAAU,0JAA0J,IAAI,kBAAkB,GAAG;AACnM,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW,CAAC,OAAO;AAAA,IACnB,SAAS,CAAC,mBAAmB,GAAG,EAAE;AAAA,IAClC,SAAS,EAAE,QAAQ;AAAA,EACrB;AACF;AAgBA,SAAS,cAAc,MAAuC;AAC5D,SAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,SAAS,EAAE,SAAS,YAAY,KAAK,QAAQ,IAAI,GAAG;AAAA,EACtD;AACF;AAGA,SAAS,UAAU,eAA6B;AAC9C,WAAS,aAAa;AACtB,uBAAqB,aAAa;AAClC,mBAAiB,aAAa;AAC9B,iBAAe,aAAa;AAC9B;AAgBO,SAAS,SAAS,eAAuB,MAAoB;AAClE,YAAU,aAAa;AACvB,eAAa,aAAa;AAE1B;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,EAAAA,2BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,OAAO,cAAc,IAAI;AAAA,IACzB,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,IACnB,SAAS,gBAAgB,UAAU,IAAI;AAAA,IACvC,OAAO,cAAc,IAAI;AAAA,EAC3B,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;AA0BO,SAAS,iBAAiB,eAAuB,MAAoB;AAC1E,YAAU,aAAa;AACvB,eAAa,aAAa;AAE1B;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,EAAAA,2BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,OAAO,cAAc,IAAI;AAAA,IACzB,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,IACnB,SAAS,gBAAgB,mBAAmB,IAAI;AAAA,EAClD,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9D;AAsBO,SAAS,SAAS,eAAuB,MAAoB;AAClE,YAAU,aAAa;AAEvB;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,EAAAA,2BAAsB,wBAAK,eAAe,YAAY,MAAM,cAAc,GAAG;AAAA,IAC3E,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,EACrB,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,CAAC;AAE7D,QAAMC,UAAS,aAAa,aAAa;AACzC,MAAIA,SAAQ;AACV,WAAO,KAAK,gBAAgBA,OAAM,aAAa,IAAI,EAAE;AAAA,EACvD;AACF;AAiBO,SAAS,iBAAiB,eAAuB,MAAoB;AAC1E,YAAU,aAAa;AAEvB;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,EAAAD,2BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,EACrB,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,CAAC;AAE7D,QAAMC,UAAS,aAAa,aAAa;AACzC,MAAIA,SAAQ;AACV,WAAO,KAAK,gBAAgBA,OAAM,SAAS,IAAI,EAAE;AAAA,EACnD;AACF;;;ACleA,IAAAC,oBAAqB;AA4BrB,SAASC,cAAa,eAAuB,aAA2B;AACtE,MAAI,UAAU,eAAe,WAAW,GAAG;AACzC;AAAA,EACF;AACA,SAAO,KAAK,wBAAwB,WAAW,EAAE;AACjD,QAAM,CAAC,OAAO,WAAW,GAAG,aAAa;AAC3C;AA6BA,SAAS,WACP,eACA,MACA,OACA,WACM;AACN,EAAAA,cAAa,eAAe,UAAU;AACtC;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,oBAAoB,MAAM,UAAU;AAAA,MACpC;AAAA,MACA;AAAA,MACA,eAAe,SAAS;AAAA,MACxB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AAkBA,SAAS,qBAAqB,MAAuC;AACnE,QAAM,MAAM,sBAAsB,IAAI;AACtC,QAAM,UAAU,qJAAqJ,IAAI,uBAAuB,GAAG;AACnM,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW,CAAC,OAAO;AAAA,IACnB,SAAS,CAAC,mBAAmB,GAAG,EAAE;AAAA,IAClC,SAAS,EAAE,QAAQ;AAAA,EACrB;AACF;AAoBO,SAAS,WACd,eACA,MACA,OACA,YAA2B,QACrB;AACN,aAAW,eAAe,MAAM,OAAO,SAAS;AAChD,eAAa,aAAa;AAC1B,mBAAa,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IAC9D,SAAS,qBAAqB,IAAI;AAAA,EACpC,CAAC;AACD,8BAA4B,eAAe,QAAQ,IAAI,EAAE;AACzD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;AAkBA,SAAS,4BAA4B,eAA6B;AAChE,MAAI,UAAU,eAAe,kBAAkB,GAAG;AAChD;AAAA,EACF;AACA,SAAO,KAAK,6BAA6B;AACzC,MACE,SAAS,OAAO,CAAC,WAAW,oBAAoB,cAAc,WAAW,GAAG,aAAa,MAAM,GAC/F;AACA,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACF;AAYO,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAe/B,IAAM,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBpC,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBhC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAcnC,IAAM,kCAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2C/C,SAAS,8BAA8B,qBAA6B,eAA6B;AAC/F,QAAM,mBAAe,wBAAK,qBAAqB,cAAc;AAC7D,QAAM,WAAW,SAAkC,YAAY;AAC/D,QAAM,wBAAwB;AAAA,QAC5B,wBAAK,eAAe,4CAA4C;AAAA,EAClE,EAAE;AACF,QAAM,eAAgB,SAAS,gBAAuD,CAAC;AACvF;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM;AAAA,MACN,cAAc,EAAE,GAAG,cAAc,oBAAoB,IAAI,qBAAqB,GAAG;AAAA,IACnF,CAAC;AAAA,EACH;AACF;AAsBA,SAAS,6BAA6B,MAAuC;AAC3E,QAAM,MAAM,+BAA+B,IAAI;AAC/C,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,UAAU,gJAAgJ,IAAI,kCAAkC,IAAI,gCAAgC,IAAI,+BAA+B,GAAG;AAChR,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW,CAAC,OAAO;AAAA,IACnB,SAAS,CAAC,mBAAmB,GAAG,EAAE;AAAA,IAClC,SAAS,EAAE,QAAQ;AAAA,EACrB;AACF;AAmBA,SAAS,2BAA2B,MAAuC;AACzE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW,CAAC,OAAO;AAAA,IACnB,YAAY;AAAA,IACZ,SAAS,EAAE,SAAS,cAAc,KAAK,QAAQ,IAAI,GAAG;AAAA,EACxD;AACF;AAyBO,SAAS,mBACd,eACA,MACA,OACM;AACN,aAAW,eAAe,MAAM,OAAO,MAAM;AAC7C,8BAA4B,aAAa;AACzC,QAAM,0BAAsB,wBAAK,eAAe,QAAQ,IAAI;AAC5D,uBAAiB,wBAAK,qBAAqB,aAAa,GAAG,sBAAsB;AACjF,uBAAiB,wBAAK,qBAAqB,wBAAwB,GAAG,uBAAuB;AAC7F;AAAA,QACE,wBAAK,qBAAqB,2BAA2B;AAAA,IACrD;AAAA,EACF;AACA;AAAA,QACE,wBAAK,qBAAqB,gCAAgC;AAAA,IAC1D;AAAA,EACF;AACA,uBAAiB,wBAAK,qBAAqB,WAAW,GAAG,2BAA2B;AACpF,gCAA8B,qBAAqB,aAAa;AAChE,eAAa,aAAa;AAC1B,mBAAa,wBAAK,qBAAqB,cAAc,GAAG;AAAA,IACtD,SAAS,6BAA6B,IAAI;AAAA,IAC1C,OAAO,2BAA2B,IAAI;AAAA,EACxC,CAAC;AACD,8BAA4B,eAAe,QAAQ,IAAI,EAAE;AACzD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;;;ACzaA,IAAAC,oBAAqB;AAqCrB,eAAsB,UACpB,eACA,MACA,SACA,cACA,OACe;AACf,QAAM,QACJ,QAAQ,UACP,eACG,aAAa,aAAa,IAC1B,MAAM,WAAW,uCAAuC,aAAa,aAAa,CAAC;AACzF;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,YAAY,IAAI;AAAA,MAChB;AAAA,MACA,gBAAgB,KAAK,IAAI,IAAI;AAAA,MAC7B;AAAA,MACA,oBAAoB,MAAM,UAAU;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,iBAAW,wBAAK,eAAe,YAAY,MAAM,cAAc,CAAC;AAIhE,8BAA4B,eAAe,YAAY,IAAI,EAAE;AAC7D,0BAAwB,eAAe,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9D;;;ACrEA,IAAAC,kBAA6B;AAC7B,IAAAC,oBAAqB;AAerB,SAAS,aAAa,eAA6B;AACjD,QAAM,YACJ,SAAS,WAAW,CAAC,WAAW,GAAG,aAAa,MAAM,KACtD,SAAS,UAAU,CAAC,WAAW,GAAG,aAAa,MAAM;AACvD,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAWA,SAAS,sBAA8B;AACrC,SAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAiBA,SAAS,sBAAsB,eAA6B;AAC1D,MAAI,UAAU,eAAe,qBAAqB,GAAG;AACnD;AAAA,EACF;AACA,QAAM,OAAO,oBAAoB;AACjC,SAAO,KAAK,2CAA2C,IAAI,GAAG;AAC9D,MACE,SAAS,OAAO,CAAC,WAAW,cAAc,MAAM,cAAc,WAAW,GAAG,aAAa,MAAM,GAC/F;AACA,UAAM,IAAI,MAAM,2CAA2C;AAAA,EAC7D;AACF;AAgBO,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBvC,SAAS,sBAAsB,eAA6B;AAC1D,QAAM,0BAAsB,wBAAK,eAAe,sBAAsB;AACtE,MAAI,CAAC,WAAW,mBAAmB,GAAG;AACpC,qBAAiB,qBAAqB,uBAAuB;AAAA,EAC/D;AACF;AAeA,SAAS,sBAAsB,MAAsB;AACnD,SAAO,KAAK,WAAW,KAAK,GAAG;AACjC;AAiBA,SAASC,uBAAsB,iBAAyB,YAA2C;AACjG,QAAM,UAAU,SAAkC,eAAe;AACjE,QAAM,UAAW,QAAQ,WAAmD,CAAC;AAC7E,mBAAiB,iBAAiB,OAAO,EAAE,GAAG,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,CAAC;AAClG;AAmBO,SAAS,cAAc,MAAsB;AAClD,SAAO;AAAA,aACI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAMjB;AAiBA,SAAS,qBAAqB,MAAuC;AACnE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,SAAS,EAAE,SAAS,mBAAmB,KAAK,QAAQ,IAAI,GAAG;AAAA,EAC7D;AACF;AAkBA,SAAS,6BAA6B,MAAuC;AAC3E,SAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,SAAS,EAAE,SAAS,cAAc,KAAK,QAAQ,IAAI,GAAG;AAAA,EACxD;AACF;AAiBA,SAAS,uBAAuB,MAAuC;AACrE,QAAM,MAAM,wBAAwB,IAAI;AACxC,QAAM,UAAU,qJAAqJ,IAAI,uBAAuB,GAAG;AACnM,SAAO;AAAA,IACL,UAAU;AAAA,IACV,WAAW,CAAC,OAAO;AAAA,IACnB,SAAS,CAAC,mBAAmB,GAAG,EAAE;AAAA,IAClC,SAAS,EAAE,QAAQ;AAAA,EACrB;AACF;AAoBA,SAAS,+BACP,MACA,iBACyB;AACzB,QAAM,MAAM,iCAAiC,IAAI;AACjD,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,UAAU,8IAA8I,IAAI,sCAAsC,IAAI,gCAAgC,IAAI,yCAAyC,IAAI,IAAI,eAAe,MAAM,eAAe,kBAAkB,GAAG;AAC1V,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,CAAC,mBAAmB,GAAG,EAAE;AAAA,IAClC,SAAS,EAAE,QAAQ;AAAA,EACrB;AACF;AAiBO,SAAS,aAAa,eAAuB,MAAoB;AACtE,eAAa,aAAa;AAC1B,wBAAsB,aAAa;AACnC,wBAAsB,aAAa;AACnC,eAAa,aAAa;AAE1B;AAAA,IACE,CAAC,KAAK,mCAAmC,MAAM,oBAAoB,IAAI,IAAI,kBAAkB;AAAA,IAC7F;AAAA,EACF;AACA,uBAAiB,wBAAK,eAAe,QAAQ,MAAM,SAAS,GAAG,cAAc,IAAI,CAAC;AAClF,EAAAA,2BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,SAAS,uBAAuB,IAAI;AAAA,IACpC,OAAO,qBAAqB,IAAI;AAAA,EAClC,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;AAgBO,SAAS,qBAAqB,eAAuB,MAAoB;AAC9E,eAAa,aAAa;AAC1B,wBAAsB,aAAa;AACnC,wBAAsB,aAAa;AACnC,eAAa,aAAa;AAE1B;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAoB,IAAI;AAAA,MACxB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,sBAAsB,IAAI;AAClD,EAAAA,2BAAsB,wBAAK,eAAe,QAAQ,MAAM,cAAc,GAAG;AAAA,IACvE,SAAS,+BAA+B,MAAM,eAAe;AAAA,IAC7D,OAAO,6BAA6B,IAAI;AAAA,EAC1C,CAAC;AACD,0BAAwB,eAAe,MAAM,EAAE,OAAO,OAAO,OAAO,UAAU,IAAI,SAAS,CAAC;AAC9F;AAiBO,SAAS,aAAa,eAAuB,MAAoB;AACtE,eAAa,aAAa;AAC1B,wBAAsB,aAAa;AACnC,wBAAsB,aAAa;AAEnC;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,+BAA+B,IAAI;AAAA,MACnC;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,0BAAwB,eAAe,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9D;AAiBO,SAAS,qBAAqB,eAAuB,MAAoB;AAC9E,eAAa,aAAa;AAC1B,wBAAsB,aAAa;AACnC,wBAAsB,aAAa;AAEnC;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAoB,IAAI;AAAA,MACxB;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,CAAC;AAC/D;AAOA,IAAM,sBAAsB,CAAC,QAAQ,mBAAmB,MAAM;AAkB9D,SAAS,2BACP,eACA,MACkD;AAClD,SAAO,oBAAoB;AAAA,IAAK,eAC9B,eAAW,wBAAK,eAAe,WAAW,MAAM,gBAAgB,CAAC;AAAA,EACnE;AACF;AAmBA,SAAS,mBAAmB,eAAiC;AAC3D,QAAM,QAAQ,iCAAiC,KAAK,aAAa;AACjE,MAAI,CAAC,OAAO;AACV,WAAO,CAAC;AAAA,EACV;AACA,SAAO,MAAM,CAAC,EACX,MAAM,GAAG,EACT,IAAI,WAAS,MAAM,KAAK,EAAE,WAAW,gBAAgB,EAAE,CAAC,EACxD,OAAO,OAAO;AACnB;AAsBA,SAAS,eAAe,eAAuB,KAAqB;AAClE,QAAM,WAAW,mBAAmB,aAAa;AACjD,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,SAAS,CAAC,GAAG,UAAU,GAAG,EAAE,IAAI,UAAQ,IAAI,IAAI,GAAG,EAAE,KAAK,IAAI;AACpE,WAAO,cAAc;AAAA,MACnB;AAAA,MACA,CAAC,QAAQ,WAAmB,GAAG,MAAM,IAAI,MAAM;AAAA,IACjD;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,aAAsC,GAAG;AAAA;AAAA;AACvD,SAAO,cAAc,SAAS,2BAA2B,IACrD,cAAc,QAAQ,6BAA6B,MAAM,GAAG,KAAK,2BAA2B,IAC5F,GAAG,aAAa;AAAA,EAAK,KAAK;AAChC;AAkCA,eAAsB,gBACpB,eACA,UACA,SACA,cACe;AACf,QAAM,MACJ,QAAQ,QACP,eACG,SACA,MAAM,WAAW,6DAA6D;AACpF,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,oBAAoB,2BAA2B,eAAe,QAAQ;AAC5E,MAAI,CAAC,mBAAmB;AACtB,UAAM,IAAI;AAAA,MACR,4BAA4B,QAAQ;AAAA,IACtC;AAAA,EACF;AACA,MAAI,CAAC,eAAW,wBAAK,eAAe,QAAQ,KAAK,gBAAgB,CAAC,GAAG;AACnE,UAAM,IAAI;AAAA,MACR,qCAAqC,GAAG,mBAAmB,GAAG;AAAA,IAChE;AAAA,EACF;AACA,MAAI,sBAAsB,UAAU,aAAa,KAAK;AACpD,UAAM,IAAI,MAAM,IAAI,QAAQ,yBAAyB;AAAA,EACvD;AAEA,QAAM,oBAAgB,wBAAK,eAAe,mBAAmB,UAAU,gBAAgB;AACvF,QAAM,oBAAgB,8BAAa,eAAe,MAAM;AACxD,MAAI,mBAAmB,aAAa,EAAE,SAAS,GAAG,GAAG;AACnD,WAAO,KAAK,IAAI,QAAQ,sBAAsB,GAAG,yBAAoB;AACrE;AAAA,EACF;AAEA,mBAAiB,eAAe,eAAe,eAAe,GAAG,CAAC;AAClE,SAAO;AAAA,IACL,IAAI,QAAQ,kBAAkB,GAAG,oCAA+B,GAAG;AAAA,EACrE;AACF;;;AC3kBA,IAAAC,kBAA6B;AAC7B,IAAAC,oBAAqB;AAmBd,IAAM,qBAAqB,CAAC,OAAO,OAAO,MAAM;AAgBhD,SAAS,qBAAqB,aAA6B;AAChE,SAAO,iDAAiD,WAAW;AAAA;AAAA,mBAElD,WAAW;AAAA,2BACH,WAAW;AAAA;AAEtC;AAsBO,SAAS,gBAAgB,MAAuC;AACrE,QAAM,UAAmC,CAAC;AAC1C,aAAW,eAAe,oBAAoB;AAC5C,YAAQ,SAAS,WAAW,EAAE,IAAI;AAAA,MAChC,UAAU;AAAA,MACV,SAAS,CAAC,wBAAwB,IAAI,SAAS,WAAW,EAAE;AAAA,MAC5D,SAAS;AAAA,QACP,SAAS,qBAAqB,WAAW,kBAAkB,WAAW;AAAA,QACtE,KAAK,QAAQ,IAAI;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,QAAM,gBAAgB,mBAAmB;AAAA,IACvC,iBACE,oCAAoC,IAAI,SAAS,WAAW,sCAAsC,IAAI,IAAI,WAAW;AAAA,EACzH,EAAE,KAAK,GAAG;AACV,UAAQ,UAAU;AAAA,IAChB,UAAU;AAAA,IACV,WAAW,mBAAmB,IAAI,iBAAe,SAAS,WAAW,EAAE;AAAA,IACvE,SAAS,mBAAmB;AAAA,MAC1B,iBAAe,uCAAuC,IAAI,IAAI,WAAW;AAAA,IAC3E;AAAA,IACA,SAAS;AAAA,MACP,SAAS,oHAAoH,aAAa;AAAA,IAC5I;AAAA,EACF;AACA,SAAO;AACT;AAiBA,SAAS,cAAc,eAA6B;AAClD,QAAM,oBAAgB,wBAAK,eAAe,YAAY;AACtD,MAAI,CAAC,WAAW,aAAa,GAAG;AAC9B;AAAA,EACF;AACA,QAAM,SACJ;AACF,QAAM,cAAU,8BAAa,eAAe,MAAM;AAClD,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B;AAAA,EACF;AACA,QAAM,QAAQ,mBAAmB,IAAI,iBAAe,gBAAgB,WAAW,EAAE,EAAE,KAAK,IAAI;AAC5F,mBAAiB,eAAe,GAAG,QAAQ,QAAQ,QAAQ,IAAI,CAAC;AAAA,EAAK,MAAM;AAAA,EAAK,KAAK;AAAA,CAAI;AAC3F;AAmBO,SAAS,YAAY,eAAuB,MAAc,OAA6B;AAC5F,eAAa,eAAe,WAAW;AACvC;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,oBAAoB,MAAM,UAAU;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,eAAa,aAAa;AAC1B,QAAM,mBAAe,wBAAK,eAAe,QAAQ,IAAI;AACrD,aAAW,eAAe,oBAAoB;AAC5C,yBAAiB,wBAAK,cAAc,QAAQ,WAAW,EAAE,GAAG,qBAAqB,WAAW,CAAC;AAAA,EAC/F;AACA,gBAAc,aAAa;AAC3B,mBAAa,wBAAK,cAAc,cAAc,GAAG,gBAAgB,IAAI,CAAC;AACtE,8BAA4B,eAAe,QAAQ,IAAI,EAAE;AAGzD,0BAAwB,eAAe,MAAM,EAAE,OAAO,MAAM,OAAO,UAAU,IAAI,SAAS,CAAC;AAC7F;;;ACvKA,IAAAC,oBAAqB;AAmBrB,IAAM,mBAAmB;AAGzB,IAAM,oBAAoB;AA+B1B,SAAS,gBAAgB,cAA4B;AACnD,QAAM,WAAW,SAGd,YAAY;AAEf,MAAI,SAAS,UAAU,kBAAkB;AACvC,aAAS,QAAQ;AAAA,EACnB;AACA,QAAM,MAAM,SAAS,UAAU,GAAG;AAClC,MAAI,OAAO,QAAQ,YAAY,IAAI,UAAU,kBAAkB;AAC7D,QAAI,QAAQ;AAAA,EACd;AACA,mBAAiB,cAAc,OAAO,QAAQ,CAAC;AACjD;AA0BA,IAAM,oBAAoB;AA6B1B,eAAsB,YACpB,eACA,MACA,SACA,cACA,OACe;AACf,eAAa,eAAe,WAAW;AACvC,QAAM,QACJ,QAAQ,UACP,eACG,aAAa,aAAa,IAC1B,MAAM,WAAW,uCAAuC,aAAa,aAAa,CAAC;AACzF;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI;AAAA,MACd;AAAA,MACA,gBAAgB,KAAK,IAAI,IAAI;AAAA,MAC7B,aAAa,iBAAiB;AAAA,MAC9B,oBAAoB,MAAM,UAAU;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,QAAM,0BAAsB,wBAAK,eAAe,YAAY,MAAM,cAAc;AAChF,aAAW,mBAAmB;AAC9B,kBAAgB,mBAAmB;AACnC,8BAA4B,eAAe,YAAY,IAAI,EAAE;AAC7D,0BAAwB,eAAe,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9D;AAyBO,SAAS,oBACd,eACA,MACA,OACM;AACN,eAAa,eAAe,WAAW;AACvC;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,UAAU,IAAI;AAAA,MACd,aAAa,iBAAiB;AAAA,MAC9B,oBAAoB,MAAM,UAAU;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,QAAM,sBAAkB,wBAAK,eAAe,QAAQ,MAAM,cAAc;AACxE,cAAY,eAAe;AAC3B,kBAAgB,eAAe;AAC/B,8BAA4B,eAAe,QAAQ,IAAI,EAAE;AACzD,0BAAwB,eAAe,MAAM,EAAE,OAAO,KAAK,CAAC;AAC9D;;;AbvEO,IAAM,gBAA+B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAqBA,eAAsB,OACpB,MACA,MACA,SACe;AACf,QAAM,gBAAgB,QAAQ,IAAI;AAClC,MAAI,CAAC,eAAW,yBAAK,eAAe,SAAS,CAAC,GAAG;AAC/C,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AAIA,QAAM,QAAQ,mBAAmB,aAAa;AAK9C,QAAM,eAAe,SAAS;AAC9B,QAAM,eACJ,QACC,UAAM,wBAAoB;AAAA,IACzB,SAAS;AAAA,IACT,SAAS,cAAc,IAAI,YAAU,EAAE,MAAM,OAAO,MAAM,EAAE;AAAA,EAC9D,CAAC;AACH,QAAM,eAAe,QAAS,MAAM,WAAW,cAAc;AAK7D,yBAAuB,cAAc,cAAc;AAEnD,UAAQ,cAAc;AAAA,IACpB,KAAK,aAAa;AAChB,kBAAY,eAAe,cAAc,KAAK;AAC9C;AAAA,IACF;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,YAAY,eAAe,cAAc,SAAS,cAAc,KAAK;AAC3E;AAAA,IACF;AAAA,IACA,KAAK,sBAAsB;AACzB,0BAAoB,eAAe,cAAc,KAAK;AACtD;AAAA,IACF;AAAA,IACA,KAAK,YAAY;AACf,iBAAW,eAAe,cAAc,OAAO,QAAQ,SAAS;AAChE;AAAA,IACF;AAAA,IACA,KAAK,qBAAqB;AACxB,yBAAmB,eAAe,cAAc,KAAK;AACrD;AAAA,IACF;AAAA,IACA,KAAK,WAAW;AACd,YAAM,UAAU,eAAe,cAAc,SAAS,cAAc,KAAK;AACzE;AAAA,IACF;AAAA,IACA,KAAK,gBAAgB;AAInB;AAAA,QACE;AAAA,UACE;AAAA,UACA;AAAA,UACA,QAAQ,YAAY;AAAA,UACpB;AAAA,UACA,oBAAoB,MAAM,UAAU;AAAA,UACpC;AAAA,UACA;AAAA,QACF;AAAA,QACA;AAAA,MACF;AACA,sBAAY,yBAAK,eAAe,QAAQ,cAAc,cAAc,CAAC;AACrE,kCAA4B,eAAe,QAAQ,YAAY,EAAE;AACjE,8BAAwB,eAAe,cAAc,EAAE,OAAO,KAAK,CAAC;AACpE;AAAA,IACF;AAAA,IACA,KAAK,cAAc;AACjB,mBAAa,eAAe,YAAY;AACxC;AAAA,IACF;AAAA,IACA,KAAK,uBAAuB;AAC1B,2BAAqB,eAAe,YAAY;AAChD;AAAA,IACF;AAAA,IACA,KAAK,cAAc;AACjB,mBAAa,eAAe,YAAY;AACxC;AAAA,IACF;AAAA,IACA,KAAK,uBAAuB;AAC1B,2BAAqB,eAAe,YAAY;AAChD;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,eAAS,eAAe,YAAY;AACpC;AAAA,IACF;AAAA,IACA,KAAK,mBAAmB;AACtB,uBAAiB,eAAe,YAAY;AAC5C;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,eAAS,eAAe,YAAY;AACpC;AAAA,IACF;AAAA,IACA,KAAK,mBAAmB;AACtB,uBAAiB,eAAe,YAAY;AAC5C;AAAA,IACF;AAAA,IACA,KAAK,eAAe;AAClB,oBAAc,eAAe,YAAY;AACzC;AAAA,IACF;AAAA,IACA,KAAK,eAAe;AAClB,oBAAc,eAAe,YAAY;AACzC;AAAA,IACF;AAAA,IACA,KAAK,wBAAwB;AAC3B,4BAAsB,eAAe,YAAY;AACjD;AAAA,IACF;AAAA,IACA,KAAK,iBAAiB;AACpB,YAAM,gBAAgB,eAAe,cAAc,SAAS,YAAY;AAIxE,4BAAsB,aAAa;AACnC,mBAAa,eAAe,MAAM,MAAM;AACxC;AAAA,IACF;AAAA,IACA,SAAS;AAOP,YAAM,aAAoB;AAC1B,YAAM,IAAI;AAAA,QACR,yBAAyB,UAAoB,uBAAuB,cAAc,KAAK,IAAI,CAAC;AAAA,MAC9F;AAAA,IACF;AAAA,EACF;AAEA,wBAAsB,aAAa;AAMnC,eAAa,eAAe,MAAM,MAAM;AAExC,SAAO,QAAQ,SAAS,YAAY,KAAK,YAAY,IAAI;AAC3D;AAuBA,SAAS,sBAAsB,eAA6B;AAC1D,SAAO,KAAK,iDAAiD;AAC7D,MAAI,SAAS,OAAO,CAAC,MAAM,MAAM,GAAG,aAAa,MAAM,GAAG;AACxD,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACF;AAsBA,SAAS,mBAAmB,eAAuC;AACjE,QAAM,SAAS;AAAA,QACb,yBAAK,eAAe,SAAS;AAAA,EAC/B;AACA,QAAM,QAAQ,OAAO,MAAM;AAC3B,SAAO;AAAA,IACL,YAAY,OAAO,eAAe,WAAW,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOxD,QAAQ,OAAO,WAAW,WAAW,WAAW;AAAA,EAClD;AACF;;;Ac1YA,IAAAC,kBAAuC;AACvC,IAAAC,qBAAqB;;;ACDrB,IAAAC,kBAAiC;AACjC,IAAAC,qBAAqB;AA4Fd,IAAM,gBAA6B,EAAE,YAAY,QAAQ,QAAQ,SAAS;AAS1E,IAAM,mBAAmB;AAkBzB,IAAM,2BAAmD;AAAA,EAC9D,sBAAsB;AAAA,EACtB,YAAY;AACd;AAaO,SAAS,YAAY,UAA8C;AACxE,MAAI,SAAS,SAAS,mBAAmB;AACvC,WAAO,8BAA8B,SAAS,YAAY,IAAI,SAAS,OAAO,eAAe,SAAS,aAAa;AAAA,EACrH;AACA,SAAO;AACT;AAgDO,SAAS,aAAa,UAA0B,OAAuB;AAC5E,MAAI,SAAS,SAAS,OAAO;AAC3B,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAMmB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjC;AAEA,QAAM,UAAU,YAAY,QAAQ;AAEpC,QAAM,UAAU,QAAQ,QAAQ,WAAW,EAAE;AAC7C,SAAO;AAAA;AAAA,KAEJ,KAAK,yDAAyD,KAAK;AAAA;AAAA,yBAE/C,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,EAK5B,KAAK,aAAa,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA;AAET;AAoGO,SAAS,cAAc,IAAyC;AACrE,QAAM,iBAAiB,OAAO;AAC9B,SAAO;AAAA,IACL,sBAAsB;AAAA,IACtB,UAAU,CAAC,cAAc,qBAAqB,kBAAkB;AAAA,IAChE,YAAY,EAAE,SAAS,0BAA0B;AAAA,IACjD,KAAK,EAAE,QAAQ,OAAO,KAAK,MAAM,MAAM,eAAe;AAAA,IACtD,SAAS;AAAA,MACP,qBAAqB;AAAA,MACrB,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOhC,mBAAmB;AAAA,IACrB;AAAA,IACA,WAAW,iBACP;AAAA,MACE,oBAAoB;AAAA,MACpB,mBAAmB,EAAE,eAAe,UAAU,MAAM,MAAM;AAAA,IAC5D,IACA,EAAE,oBAAoB,MAAM;AAAA,EAClC;AACF;AAsBO,IAAM,cAAc,EAAE,cAAc,KAAK;AAsBzC,IAAM,uBAAuB;AAAA,EAClC,QAAQ;AAAA,EACR,SAAS,EAAE,YAAY,OAAO;AAChC;AAcA,SAAS,WAAW,OAAoC;AACtD,SAAO,OAAO,UAAU,WAAW,QAAS,MAA8B;AAC5E;AAeO,SAAS,iBAAiB,QAA0D;AACzF,QAAM,UAAW,OAAO,WAAqC,CAAC;AAC9D,QAAM,aAAa,QAAQ,KAAK,WAAS,WAAW,KAAK,MAAM,qBAAqB,MAAM;AAC1F,SAAO,aACH,EAAE,GAAG,QAAQ,QAAQ,IACrB,EAAE,GAAG,QAAQ,SAAS,CAAC,GAAG,SAAS,oBAAoB,EAAE;AAC/D;AAwBO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAmBO,SAAS,kBAAkB,QAA0D;AAC1F,QAAM,cAAe,OAAO,eAAuD,CAAC;AACpF,QAAM,WAAY,YAAY,iBAA2C,CAAC;AAC1E,QAAM,UAAU,qBAAqB,OAAO,WAAS,CAAC,SAAS,SAAS,KAAK,CAAC;AAC9E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa,EAAE,GAAG,aAAa,eAAe,CAAC,GAAG,UAAU,GAAG,OAAO,EAAE;AAAA,EAC1E;AACF;AAkBO,SAAS,kBACd,QACA,IACyB;AACzB,SAAO,EAAE,GAAG,QAAQ,aAAa,QAAQ,SAAS,cAAc,EAAE,EAAE;AACtE;AASO,IAAM,oBAAoB;AAAA;AAU1B,IAAM,kBAAkB;AAAA;AAgCxB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BxB,IAAM,wBAAwB;AAoB9B,SAAS,mBAA2B;AACzC,SAAO,QAAQ,IAAI,2BAA2B;AAChD;AAaO,IAAM,iBAAiB;AAuCvB,SAAS,iBAAiB,eAAuB,QAA8B;AACpF,SAAO,GAAG,OAAO;AAAA,IACf,MAAM;AAAA,IACN,OAAO,mDAAmD,YAAY;AAAA,IACtE,UAAU;AAAA,MACR,2CAA2C,EAAE,SAAS,OAAO;AAAA,MAC7D,uCAAuC,EAAE,SAAS,SAAS;AAAA,IAC7D;AAAA;AAAA;AAAA,IAGA,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAIjB,sBAAsB,WAAW;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,MAAM;AAAA;AAAA;AAAA,IAGb,gBAAgB;AAAA,MACd,QAAQ,EAAE,YAAY,iBAAiB,MAAM,EAAE;AAAA,IACjD;AAAA,EACF,CAAC,CAAC;AAAA;AACJ;AAuBO,IAAM,mBAAmB;AAAA,EAC9B,UAAU;AAAA,EACV,OAAO;AAAA,EACP,SAAS;AAAA,IACP,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,GAAG;AAAA,IACV,KAAK;AAAA,EACP;AACF;AAuCO,IAAM,wBAAwB;AAAA,EACnC,0BAA0B,EAAE,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc9C,IAAI,EAAE,mBAAmB,SAAS;AAAA,EAClC,UAAU,EAAE,mBAAmB,SAAS;AAAA,EACxC,cAAc,EAAE,mBAAmB,SAAS;AAAA,EAC5C,qBAAqB,EAAE,mBAAmB,SAAS;AAAA,EACnD,iBAAiB,EAAE,mBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAK/C,SAAS,EAAE,QAAQ,UAAU;AAC/B;AAkCO,SAAS,4BAA4B,WAA2C;AACrF,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,qBAAqB;AAAA,IACrB,uBAAuB,iBAAiB;AAAA,EAC1C;AACF;AAqBO,IAAM,2BAA2B;AAAA,EACtC,QAAQ,CAAC,UAAU;AAAA,EACnB,QAAQ,CAAC,uBAAuB,UAAU,OAAO;AACnD;AAiBO,SAAS,+BACd,SACA,QACwB;AACxB,QAAM,QAAQ,IAAI,IAAY,yBAAyB,WAAW,WAAW,WAAW,QAAQ,CAAC;AACjG,SAAO,OAAO,YAAY,OAAO,QAAQ,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;AACxF;AA8BO,SAAS,eAAe,QAA+C;AAC5E,MAAI,WAAW,SAAU,QAAO;AAChC,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,MACP,UAAU,CAAC,YAAY,iBAAiB,QAAQ,OAAO;AAAA,MACvD,UAAU;AAAA,MACV,KAAK;AAAA,IACP;AAAA,EACF;AACF;AASO,IAAM,iBAAiB;AAUvB,IAAM,gBAAgB;AAkBtB,SAAS,8BAAsD;AACpE,SAAO;AAAA,IACL,uBAAuB,iBAAiB;AAAA,IACxC,QAAQ;AAAA,IACR,OAAO;AAAA,EACT;AACF;AAUO,IAAM,wBAAwB;AAe9B,SAAS,mBAA2B;AACzC,SAAO,QAAQ,IAAI,2BAA2B;AAChD;AA8BO,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqD/B,IAAM,gBAAgB;AAAA;AAAA,EAE3B,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsCjB,IAAM,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyB7B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCtB,IAAM,eAAe,GAAG,KAAK;AAAA,EAClC;AAAA,IACE,SAAS;AAAA,IACT,MAAM;AAAA,IACN,aAAa;AAAA,IACb,eAAe;AAAA,IACf,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAAA;AAkBM,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqDxB,IAAM,oBAAoB;AAAA,EAC/B,QAAQ,CAAC,0BAA0B,wBAAwB;AAAA,EAC3D,QAAQ,CAAC,kBAAkB,wBAAwB;AACrD;AAUO,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AACF;AAmBO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAqBO,IAAM,uBAAuB,CAAC,MAAM;AAepC,SAAS,mBAAmB,QAAgC;AACjE,SAAO,CAAC,GAAG,qBAAqB,GAAI,WAAW,WAAW,uBAAuB,CAAC,CAAE;AACtF;AAgBO,SAAS,iBAAiB,QAAgC;AAC/D,SAAO,CAAC,GAAG,kBAAkB,MAAM,GAAG,GAAG,wBAAwB;AACnE;AAoBO,SAAS,eAAe,QAA+C;AAC5E,QAAM,YAAY,WAAW,WAAW,mBAAmB;AAC3D,QAAM,kBACJ,WAAW,WACP,CAAC,QAAQ,SAAS,YAAY,QAAQ,QAAQ,KAAK,IACnD;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEN,SAAO;AAAA,IACL,mBAAmB;AAAA,IACnB,4BAA4B;AAAA,MAC1B,wBAAwB;AAAA,IAC1B;AAAA,IACA,2BAA2B;AAAA,IAC3B,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAevB,GAAG,OAAO;AAAA,MACR,mBAAmB,MAAM,EAAE,IAAI,cAAY;AAAA,QACzC,IAAI,QAAQ;AAAA,QACZ,EAAE,2BAA2B,UAAU;AAAA,MACzC,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAiCO,SAAS,gBACd,eACA,QACA,eACQ;AACR,SAAO,KAAK;AAAA,IACV;AAAA,MACE,SAAS,CAAC,EAAE,MAAM,KAAK,MAAM,cAAc,CAAC;AAAA,MAC5C,UAAU,eAAe,MAAM;AAAA,MAC/B,YAAY,EAAE,iBAAiB,iBAAiB,MAAM,EAAE;AAAA,MACxD,OAAO;AAAA,QACL,SAAS,eAAe,WAAW;AAAA,QACnC,OAAO,eAAe,SAAS,CAAC;AAAA,MAClC;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAmBO,IAAM,eAAe;AAAA,EAC1B,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AAAA,EACP,mBAAmB;AAAA,EACnB,SAAS;AACX;AAqBO,SAAS,YAAY,SAAuB,UAAkC;AAInF,QAAM,YAAY,WAAW,WAAW,UAAU;AAClD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,WAAW,WAAW,YAAY;AAAA,IAC1C,gBAAgB,GAAG,SAAS;AAAA,IAC5B,kBAAkB,GAAG,oBAAoB,OAAO,8BAA8B;AAAA,EAChF;AACF;AAqBO,SAAS,kBAAkB,OAA6C;AAC7E,QAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOb,QAAQ;AAAA,IACR,gBAAgB,MAAM;AAAA,EACxB;AACA,SAAO;AAAA,IACL,yBAAyB;AAAA,IACzB,qBAAqB;AAAA,IACrB,kBAAkB;AAAA,EACpB;AACF;AAsBO,SAAS,WAAW,SAAkD;AAC3E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAML,eAAe,QAAQ;AAAA,IACvB,OAAO,QAAQ;AAAA,IACf,UAAU,QAAQ;AAAA,IAClB,OAAO,QAAQ;AAAA,IACf,eAAe,QAAQ;AAAA,IACvB,IAAI,QAAQ;AAAA,IACZ,OAAO,EAAE,YAAY,QAAQ,MAAM,YAAY,QAAQ,QAAQ,MAAM,OAAO;AAAA,EAC9E;AACF;AAqBO,SAAS,eAAe,eAAgD;AAC7E,QAAM,SAAS,aAAkC,yBAAK,eAAe,SAAS,CAAC;AAC/E,SAAQ,OAAO,QAAgD,CAAC;AAClE;AAkBO,SAAS,iBAAiB,UAA8C;AAC7E,MAAI,SAAS,SAAS,mBAAmB;AACvC,WAAO,8BAA8B,SAAS,YAAY,IAAI,SAAS,OAAO,eAAe,SAAS,aAAa;AAAA,EACrH;AACA,SAAO;AACT;AAwBA,IAAM,uBAAuB;AAsC7B,IAAM,iCAAiC;AAgCvC,IAAM,kBAAkB;AAsBxB,IAAM,2BAA2B;AAqBjC,IAAM,8BAA8B;AAcpC,IAAM,uBAAuB;AAU7B,IAAM,qBAAqB,YAAY,oBAAoB;AAW3D,IAAM,sBAAsB,YAAY,oBAAoB,uJAAuJ,OAAO,SAAS;AAgB5N,IAAM,sBAAsB;AAe5B,IAAM,eAAe;AAwBrB,IAAM,cAAc;AAuB3B,IAAM,mCAAmC,yEAAyE,mBAAmB;AAyBrI,IAAM,4BAA4B,mWAAmW,gCAAgC,kLAAkL,mBAAmB;AAY1mB,IAAM,4BAA4B,qKAAqK,gCAAgC;AASvO,IAAM,0BAA0B,YAAY,yBAAyB;AASrE,IAAM,2BAA2B,YAAY,yBAAyB,uJAAuJ,OAAO,SAAS;AAwB7O,IAAM,wBAAwB;AAgD9B,IAAM,iBAAiB;AAUvB,IAAM,iBAAiB;AA2CvB,IAAM,wBAAwB,4DAA4D,cAAc;AAUxG,IAAM,kBAAkB;AAmBxB,SAAS,aAAa,kBAAkC;AACtD,SAAO,gUAAgU,gBAAgB;AACzV;AAaA,SAAS,yBAAyBC,mBAAmC;AACnE,SAAOA,oBACH,2CAA2CA,iBAAgB,+GAC3D;AACN;AAsBA,SAAS,mBACP,cACA,mBACkB;AAClB,SAAO,iBAAiB,QACpB,CAAC,mBAAmB,kBAAkB,WAAW,CAAC,IAClD,CAAC,OAAO,kBAAkB,KAAK,CAAC;AACtC;AAgBO,SAAS,UAAU,OAAuB;AAC/C,SAAO,8BAA8B,KAAK,KAAK,IAAI,cAAc,KAAK,KAAK,WAAW,KAAK;AAC7F;AA6DO,SAAS,mBACd,OACA,eACAA,mBACA,eAAuC,mBAC/B;AACR,QAAM,SAAS;AACf,QAAM,CAAC,aAAa,YAAY,IAAI,mBAAmB,cAAc,UAAQ,KAAK,IAAI,GAAG;AACzF,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BP,UAAU,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAML,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAgDlB,WAAW,KAAK,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,cAKtB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOd,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAQpB,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAM9B,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAMf,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOxB,2BAA2B;AAAA;AAAA;AAAA,cAG3B,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAQlB,yBAAyB;AAAA,4CACK,mBAAmB;AAAA;AAAA,cAEjD,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAMvB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cA+BrB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,cAKrB,eAAe;AAAA;AAAA,iBAEZ,MAAM;AAAA;AAAA;AAAA;AAAA,iBAIN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAUN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAOT,aAAa,yBAAyBA,iBAAgB,CAAC,CAAC;AAAA;AAAA,iBAErD,MAAM;AAAA;AAAA,QAEf,WAAW,KAAK,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBASnB,MAAM;AAAA;AAEvB;AA0CO,SAAS,kBACd,OACAA,mBACA,eAAuC,mBACvC,KAAiB,UACT;AAYR,QAAM,SAAS;AACf,QAAM,CAAC,aAAa,YAAY,IAAI;AAAA,IAClC;AAAA,IACA,UAAQ,gBAAgB,IAAI;AAAA,EAC9B;AAIA,QAAM,iBAAiB,OAAO;AAC9B,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAkCM,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAeM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAWJ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMjC,WAAW,KAAK,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,eAKzB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAOd,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAQpB,8BAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAM9B,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMf,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAOxB,2BAA2B;AAAA;AAAA;AAAA,eAG3B,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAQnB,yBAAyB;AAAA,yCACC,mBAAmB;AAAA;AAAA,eAE7C,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMxB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAkCrB,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,eAKrB,eAAe;AAAA;AAAA,mBAEX,MAAM;AAAA;AAAA;AAAA,mBAGN,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qFAWjB,iBACI;AAAA;AAAA;AAAA;AAAA;AAAA,oFAMA,EACN;AAAA,eACS,aAAa,yBAAyBA,iBAAgB,CAAC,CAAC;AAAA,2CACjC,iBAAiB,iCAAiC,cAAc;AAAA,mBACnF,MAAM;AAAA;AAAA,YAEb,WAAW,KAAK,YAAY,GAC5B,iBACI;AAAA,wDAEA,EACN;AAAA,EAER,iBACI,KACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAQa,MAAM;AAAA,CAEzB;AACA;AA6BO,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoFjC,IAAM,2BAA2B,CAAC,eAAe,oBAAoB,SAAS;AAevE,SAAS,oBAAoB,eAA6B;AAC/D,aAAW,SAAS,0BAA0B;AAC5C,oCAAO,yBAAK,eAAe,KAAK,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACrE;AACA,6BAA2B,aAAa;AAC1C;AAoBO,SAAS,gBAAgB,MAAoB;AAClD,8BAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC/C;AA8BO,SAAS,2BAA2B,eAA6B;AACtE,QAAM,cAAU,0BAAS,gEAAgE;AAAA,IACvF,KAAK;AAAA,EACP,CAAC;AACD,aAAW,SAAS,SAAS;AAC3B,oCAAO,yBAAK,eAAe,KAAK,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACpD;AACF;AAwBO,SAAS,aAAa,eAAuB,SAA+B;AAQjF,QAAM,iBAAa,yBAAK,eAAe,SAAS;AAChD,QAAM,SAAS,SAAkC,UAAU;AAC3D,QAAM,aAAa;AAAA,IACjB,GAAI,OAAO;AAAA,IACX,GAAG,kBAAkB,QAAQ,KAAK;AAAA,EACpC;AACA,QAAM,OAAO,EAAE,GAAI,OAAO,MAA8C,GAAG,YAAY;AACvF,QAAM,OAAO,EAAE,GAAI,OAAO,MAA8C,GAAG,WAAW,OAAO,EAAE;AAC/F,QAAM,UAAU,kBAAkB,iBAAiB,kBAAkB,QAAQ,QAAQ,EAAE,CAAC,CAAC;AACzF,mBAAiB,YAAY,OAAO,EAAE,GAAG,SAAS,YAAY,MAAM,KAAK,CAAC,CAAC;AAS3E,QAAM,mBAAe,yBAAK,eAAe,cAAc;AACvD,QAAM,WAAW,SAAkC,YAAY;AAC/D,QAAM,UAAU;AAAA,IACd,GAAI,SAAS;AAAA,IACb,GAAG,YAAY,QAAQ,MAAM,MAAM;AAAA,EACrC;AACA,QAAM,kBAAkB,SAAS;AACjC,QAAM,UAAU;AAAA,IACd;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA;AAAA,MAEH,GAAG,4BAA4B,iBAAiB,MAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAK9D,GAAI,QAAQ,MAAM,WAAW,WACzB,4BAA4B,IAC5B,EAAE,UAAU,iBAAiB;AAAA,IACnC;AAAA,IACA,QAAQ,MAAM;AAAA,EAChB;AAEA,QAAM,YAAY;AAAA,IAChB,GAAI,SAAS;AAAA,IACb,GAAG;AAAA,EACL;AAIA,QAAM,aAAa,SAAS;AAC5B,QAAM,KAAK;AAAA,IACT,GAAG;AAAA,IACH,iBAAkB,YAAY,mBAA6C,CAAC;AAAA,IAC5E,SAAS;AAAA,MACP,GAAI,YAAY;AAAA,MAChB,MAAM,eAAe,QAAQ,MAAM,MAAM;AAAA,IAC3C;AAAA,EACF;AACA;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,GAAG,QAAQ,KAAK;AAAA,MACtB;AAAA,MACA,iBAAiB;AAAA,MACjB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,uBAAiB,yBAAK,eAAe,QAAQ,GAAG,aAAa,QAAQ,UAAU,QAAQ,KAAK,CAAC;AAC7F,uBAAiB,yBAAK,eAAe,uBAAuB,GAAG,iBAAiB;AAChF,QAAM,eAAW,yBAAK,eAAe,mBAAmB;AACxD,mBAAiB,UAAU,eAAe;AAC1C,iBAAe,QAAQ;AASvB,MAAI,QAAQ,MAAM,WAAW,UAAU;AACrC,yBAAiB,yBAAK,eAAe,kBAAkB,GAAG,aAAa;AACvE,yBAAiB,yBAAK,eAAe,eAAe,GAAG,YAAY;AAEnE,yBAAiB,yBAAK,eAAe,mBAAmB,GAAG,oBAAoB;AAC/E,wBAAgB,yBAAK,eAAe,iBAAiB,CAAC;AAAA,EACxD,OAAO;AACL,yBAAiB,yBAAK,eAAe,mBAAmB,GAAG,aAAa;AACxE,yBAAiB,yBAAK,eAAe,iBAAiB,GAAG,eAAe;AACxE,wBAAgB,yBAAK,eAAe,kBAAkB,CAAC;AACvD,wBAAgB,yBAAK,eAAe,eAAe,CAAC;AAAA,EACtD;AAEA;AAAA,QACE,yBAAK,eAAe,iCAAiC;AAAA,IACrD,iBAAiB,QAAQ,eAAe,QAAQ,MAAM,MAAM;AAAA,EAC9D;AACA,uBAAiB,yBAAK,eAAe,iBAAiB,GAAG,eAAe;AACxE,sBAAoB,aAAa;AAKjC,QAAM,wBAAoB,yBAAK,eAAe,GAAG,QAAQ,aAAa,iBAAiB;AACvF,QAAM,WAAW,kBAEd,iBAAiB;AACpB;AAAA,IACE;AAAA,IACA,gBAAgB,QAAQ,eAAe,QAAQ,MAAM,QAAQ,UAAU,KAAK;AAAA,EAC9E;AAOA,MAAI,QAAQ,kBAAkB,aAAa;AACzC,oCAAO,yBAAK,eAAe,0BAA0B,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACzE;AAGA,QAAM,aAAa,iBAAiB,QAAQ,QAAQ;AACpD,MAAI,QAAQ,OAAO,WAAW,QAAQ,OAAO,QAAQ;AACnD;AAAA,UACE,yBAAK,eAAe,qBAAqB;AAAA,MACzC,mBAAmB,QAAQ,OAAO,QAAQ,eAAe,YAAY,QAAQ,SAAS,IAAI;AAAA,IAC5F;AAAA,EACF;AACA,MAAI,QAAQ,OAAO,YAAY,QAAQ,OAAO,QAAQ;AACpD;AAAA,UACE,yBAAK,eAAe,0BAA0B;AAAA,MAC9C,kBAAkB,QAAQ,OAAO,YAAY,QAAQ,SAAS,MAAM,QAAQ,EAAE;AAAA,IAChF;AACA,yBAAiB,yBAAK,eAAe,wBAAwB,GAAG,iBAAiB;AAAA,EACnF;AACF;;;ADxgGA,IAAM,yBAAyB,eAAe,QAAQ,QAAQ,EAAE,EAAE,MAAM,KAAK,CAAC,EAAE,CAAC;AAiBjF,SAAS,mBAAmB,eAAkC;AAC5D,QAAM,kBAAc,0BAAS,yCAAyC,EAAE,KAAK,cAAc,CAAC;AAC5F,QAAM,qBAAiB,0BAAS,gEAAgE;AAAA,IAC9F,KAAK;AAAA,EACP,CAAC;AAED,SAAO;AAAA,IACL;AAAA,MACE,OAAO;AAAA,MACP,IAAI,YAAY,WAAW;AAAA,MAC3B,QACE,YAAY,WAAW,IACnB,6CACA,GAAG,YAAY,MAAM,kBAAkB,YAAY,KAAK,IAAI,CAAC;AAAA,MACnE,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,IAAI,eAAe,WAAW;AAAA,MAC9B,QAAQ,SAAS,eAAe,MAAM,KAAK,eAAe,KAAK,IAAI,CAAC;AAAA,MACpE,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAgBA,SAAS,oBAAoB,eAAgC;AAC3D,QAAM,cAAc,eAAW,yBAAK,eAAe,aAAa,CAAC;AACjE,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI,CAAC;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAuBA,SAAS,4BAA4B,eAAgC;AACnE,QAAM,SAAS,eAAe,aAAa,EAAE,OAAO,UAAU;AAC9D,QAAM,cAAc,CAAC,oBAAoB,eAAe,EAAE;AAAA,IAAO,UAC/D,eAAW,yBAAK,eAAe,IAAI,CAAC;AAAA,EACtC;AACA,QAAM,cAAc,eAAW,yBAAK,eAAe,iBAAiB,CAAC;AAErE,QAAM,QAAQ,WAAW,WAAY,cAAc,CAAC,iBAAiB,IAAI,CAAC,IAAK;AAE/E,SAAO;AAAA,IACL,OAAO,aAAa,MAAM,aAAa,MAAM;AAAA,IAC7C,IAAI,MAAM,WAAW;AAAA,IACrB,QAAQ,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA,IAC3B,QAAQ;AAAA,EACV;AACF;AAiBA,SAAS,wBAAwB,eAA+C;AAC9E,QAAM,mBAAe,yBAAK,eAAe,cAAc;AACvD,MAAI,CAAC,WAAW,YAAY,EAAG,QAAO,CAAC;AACvC,QAAM,WAAW,SAAuD,YAAY;AACpF,SAAO,SAAS,mBAAmB,CAAC;AACtC;AAyBA,SAAS,0BAA0B,eAAgC;AACjE,QAAM,SAAS,eAAe,aAAa,EAAE,OAAO,UAAU;AAC9D,QAAM,QAAQ,WAAW,WAAW,aAAa;AACjD,QAAM,WAAW,wBAAwB,aAAa,EAAE,KAAK,MAAM;AAEnE,SAAO;AAAA,IACL,OAAO,QAAQ,WAAW,WAAW,UAAU,UAAU;AAAA,IACzD,IAAI,CAAC;AAAA,IACL,QAAQ,GAAG,KAAK;AAAA,IAChB,QAAQ;AAAA,EACV;AACF;AAoBA,SAAS,6BAA6B,eAAgC;AACpE,QAAM,SAAS,eAAe,aAAa,EAAE,OAAO,UAAU;AAC9D,MAAI,WAAW,UAAU;AACvB,WAAO;AAAA,MACL,OAAO;AAAA,MACP,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AACA,QAAM,UAAU,wBAAwB,aAAa;AACrD,QAAM,UAAU,CAAC,UAAU,SAAS,qBAAqB,EAAE;AAAA,IACzD,gBAAc,QAAQ,UAAU,MAAM;AAAA,EACxC;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI,QAAQ,WAAW;AAAA,IACvB,QAAQ,YAAY,QAAQ,KAAK,IAAI,CAAC;AAAA,IACtC,QAAQ;AAAA,EACV;AACF;AAeA,SAAS,kBAAkB,QAA0C;AACnE,QAAM,UAAW,OAAO,WAAqC,CAAC;AAC9D,QAAM,aAAa,QAAQ;AAAA,IACzB,YACG,OAAO,UAAU,WAAW,QAAS,MAA8B,YACpE;AAAA,EACJ;AACA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAqBA,SAAS,oBAAoB,eAA4C;AACvE,QAAM,mBAAe,yBAAK,eAAe,kCAAkC;AAC3E,MAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,EAAE,QAAQ,IAAI,SAA8B,YAAY;AAC9D,UAAM,QAAQ,QAAQ,MAAM,KAAK,CAAC,EAAE,CAAC;AACrC,WAAO;AAAA,MACL,OAAO,sBAAsB,sBAAsB;AAAA,MACnD,IAAI,UAAU;AAAA,MACd,QAAQ,0BAA0B,OAAO,6BAA6B,sBAAsB;AAAA,MAC5F,QAAQ,iBAAiB,cAAc;AAAA,IACzC;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkBA,SAAS,WACP,eACA,UACA,OACqB;AACrB,QAAM,gBAAY,yBAAK,eAAe,QAAQ;AAC9C,MAAI,CAAC,YAAY,CAAC,WAAW,SAAS,GAAG;AACvC,WAAO;AAAA,EACT;AACA,QAAM,YAAQ,8BAAa,WAAW,MAAM;AAC5C,MAAI,SAAS,SAAS,OAAO;AAC3B,WAAO;AAAA,MACL,OAAO;AAAA,MACP,IAAI,MAAM,SAAS,mCAAmC;AAAA,MACtD,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI,UAAU,UAAa,MAAM,SAAS,GAAG,KAAK,YAAY;AAAA,IAC9D,QAAQ,OAAO,SAAS,QAAQ;AAAA,IAChC,QAAQ;AAAA,EACV;AACF;AAgBA,SAAS,oBAAoB,eAAkC;AAC7D,QAAM,aAAa;AAAA,IACjB,OAAG,0BAAS,2BAA2B,EAAE,KAAK,cAAc,CAAC;AAAA,IAC7D,OAAG,0BAAS,oCAAoC,EAAE,KAAK,cAAc,CAAC;AAAA,EACxE;AACA,SAAO,WAAW,QAAQ,cAAY;AACpC,UAAM,cAAc,SAAS,MAAM,GAAG,SAAS,YAAY,GAAG,CAAC;AAC/D,UAAM,sBAAkB,yBAAK,eAAe,aAAa,cAAc;AACvE,QAAI,cAAc;AAClB,QAAI;AACF,YAAM,cAAc,SAEjB,eAAe;AAClB,oBAAc,QAAQ,YAAY,SAAS,SAAS,cAAc;AAAA,IACpE,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,MACL;AAAA,QACE,OAAO,GAAG,WAAW;AAAA,QACrB,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,QAAQ,yCAAyC,WAAW;AAAA,MAC9D;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAcA,SAAS,UAAU,eAAgC;AACjD,SAAO;AAAA,IACL,OAAO;AAAA,IACP,IAAI,SAAS,OAAO,CAAC,MAAM,YAAY,GAAG,aAAa,MAAM;AAAA,IAC7D,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;AAmBO,SAAS,gBAAgB,eAAkC;AAChE,QAAM,iBAAa,yBAAK,eAAe,SAAS;AAChD,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR,uBAAuB,aAAa;AAAA,IACtC;AAAA,EACF;AACA,QAAM,SAAS,SAGZ,UAAU;AAEb,SAAO;AAAA,IACL,GAAG,mBAAmB,aAAa;AAAA,IACnC,oBAAoB,aAAa;AAAA,IACjC,4BAA4B,aAAa;AAAA,IACzC,0BAA0B,aAAa;AAAA,IACvC,6BAA6B,aAAa;AAAA,IAC1C,kBAAkB,MAAM;AAAA,IACxB,oBAAoB,aAAa;AAAA,IACjC,WAAW,eAAe,OAAO,MAAM,UAAU,OAAO,MAAM,KAAK;AAAA,IACnE,GAAG,oBAAoB,aAAa;AAAA,IACpC,UAAU,aAAa;AAAA,EACzB,EAAE,OAAO,CAAC,YAAgC,YAAY,MAAS;AACjE;AAmBO,SAAS,UAAU,eAA6B;AACrD,QAAM,WAAW,gBAAgB,aAAa;AAC9C,QAAM,SAAS,SAAS,OAAO,aAAW,CAAC,QAAQ,EAAE;AAErD,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,IAAI;AACd,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B,OAAO;AACL,aAAO,MAAM,GAAG,QAAQ,KAAK,WAAM,QAAQ,UAAU,QAAQ,EAAE;AAC/D,UAAI,QAAQ,QAAQ;AAClB,eAAO,KAAK,YAAY,QAAQ,MAAM,EAAE;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,QAAQ,OAAO,SAAS,MAAM,iBAAiB;AACtD;AAAA,EACF;AACA,SAAO,MAAM,GAAG,OAAO,MAAM,OAAO,SAAS,MAAM,iBAAiB;AACpE,UAAQ,WAAW;AACrB;;;AEreA,IAAAC,qBAAqB;AACrB,IAAAC,kBAAuB;;;ACDvB,IAAAC,kBAAuB;AACvB,IAAAC,qBAAqB;AA0ErB,SAAS,qBAAqB,IAAoC;AAChE,SAAO,OAAO,UAAU,UAAU;AACpC;AAcA,eAAe,aAAa,SAA2C;AAGrE,MAAI,QAAQ,cAAc,QAAQ,UAAU,QAAQ,KAAK;AACvD,WAAO;AAAA,MACL,YAAY,QAAQ,cAAc,cAAc;AAAA,MAChD,QAAQ,QAAQ,UAAU,cAAc;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,WAAW,MAAM,YAAY;AACnC,SAAO;AAAA,IACL,YAAY,SAAS;AAAA,IACrB,QAAQ,SAAS;AAAA,EACnB;AACF;AAiBA,IAAM,eAAwC,oBAAI,IAAI,CAAC,SAAS,UAAU,MAAM,CAAC;AAEjF,eAAe,UAAU,SAA0C;AACjE,MAAI,QAAQ,MAAM,aAAa,IAAI,QAAQ,EAAE,GAAG;AAC9C,WAAO,QAAQ;AAAA,EACjB;AACA,MAAI,QAAQ,KAAK;AACf,WAAO;AAAA,EACT;AACA,SAAO,MAAM,SAAS;AACxB;AAUA,eAAe,gBAAgB,SAA8C;AAC3E,MAAI,QAAQ,aAAa,qBAAsB,QAAQ,gBAAgB,QAAQ,eAAgB;AAC7F,WAAO;AAAA,MACL,MAAM;AAAA,MACN,cAAc,QAAQ,gBAAiB,MAAM,WAAW,2BAA2B;AAAA,MACnF,SAAS,QAAQ,WAAY,MAAM,WAAW,sBAAsB;AAAA,MACpE,eAAe,QAAQ,iBAAkB,MAAM,WAAW,qBAAqB;AAAA,IACjF;AAAA,EACF;AACA,MAAI,QAAQ,aAAa,SAAS,QAAQ,KAAK;AAC7C,WAAO,EAAE,MAAM,MAAM;AAAA,EACvB;AACA,SAAO,MAAM,eAAe;AAC9B;AAkBA,eAAsB,OAAO,MAA0B,SAAoC;AACzF,QAAM,gBAAgB,QAAS,MAAM,WAAW,gBAAgB;AAMhE,yBAAuB,eAAe,gBAAgB;AACtD,QAAM,QACJ,QAAQ,UACP,QAAQ,MACL,IAAI,aAAa,KACjB,MAAM,WAAW,sCAAsC,IAAI,aAAa,EAAE;AAChF,QAAM,WAAW,MAAM,gBAAgB,OAAO;AAC9C,QAAM,KAAK,MAAM,UAAU,OAAO;AAClC,QAAM,QACJ,QAAQ,UACP,QAAQ,MACL,kBACA,MAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AAIN,QAAM,gBACJ,OAAO,WACF,QAAQ,iBAAiB,UACzB,QAAQ,kBACR,QAAQ,MACL,UACA,MAAM,WAAW,mDAAmD,OAAO;AACrF,QAAM,QAAQ,MAAM,aAAa,OAAO;AACxC,QAAM,UAAU,QAAQ,YAAY,QAAQ,MAAM,QAAQ,MAAM,cAAc;AAE9E,SAAO,KAAK,0BAA0B,aAAa,gBAAgB;AACnE;AAAA,IACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,aAAa,qBAAqB,EAAE,CAAC,KAAK;AAAA,MACpD;AAAA,IACF;AAAA,IACA,QAAQ,IAAI;AAAA,EACd;AAEA,QAAM,oBAAgB,yBAAK,QAAQ,IAAI,GAAG,aAAa;AAEvD,SAAO;AAAA,IACL;AAAA,EACF;AACA,eAAa,eAAe,EAAE,eAAe,OAAO,UAAU,OAAO,eAAe,IAAI,MAAM,CAAC;AAqB/F,kCAAO,yBAAK,eAAe,cAAc,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5E,kCAAO,yBAAK,eAAe,mBAAmB,GAAG,EAAE,OAAO,KAAK,CAAC;AAMhE,SAAO,KAAK,oEAAoE;AAChF,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA,CAAC,WAAW,cAAc,SAAS,mBAAmB,iCAAiC;AAAA,IACvF;AAAA,EACF;AACA,MAAI,kBAAkB,GAAG;AACvB,UAAM,IAAI,MAAM,sDAAsD,aAAa,EAAE;AAAA,EACvF;AAEA,WAAS,OAAO,CAAC,OAAO,GAAG,aAAa;AAKxC,SAAO;AAAA,IACL,6BAA6B,MAAM,WAAW,WAAW,UAAU,UAAU;AAAA,EAC/E;AACA,eAAa,eAAe,MAAM,MAAM;AAExC,SAAO,QAAQ,mBAAmB;AAClC,SAAO,KAAK,QAAQ,aAAa,EAAE;AACnC,SAAO,KAAK,gFAAgF;AAC5F,SAAO,KAAK,kFAAkF;AAC9F,SAAO;AAAA,IACL;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,EACF;AACA,SAAO,KAAK,yDAAyD;AACvE;;;AClSA,IAAAC,kBAA4B;AAC5B,IAAAC,qBAA+B;AAkE/B,SAASC,iBACP,SACA,WACgB;AAChB,MAAI,QAAQ,aAAa,qBAAsB,QAAQ,gBAAgB,QAAQ,eAAgB;AAC7F,UAAM,iBAAiB,WAAW,SAAS,oBAAoB,YAAY;AAC3E,UAAM,eAAe,QAAQ,gBAAgB,gBAAgB;AAC7D,UAAM,UAAU,QAAQ,WAAW,gBAAgB;AACnD,UAAM,gBAAgB,QAAQ,iBAAiB,gBAAgB;AAC/D,QAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,eAAe;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,MAAM,mBAAmB,cAAc,SAAS,cAAc;AAAA,EACzE;AACA,MAAI,QAAQ,aAAa,OAAO;AAC9B,WAAO,EAAE,MAAM,MAAM;AAAA,EACvB;AACA,MAAI,WAAW;AACb,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAmCA,SAAS,qBAAqB,eAAuB,WAA4C;AAC/F,MAAI,UAAU,eAAe;AAC3B,WAAO,UAAU;AAAA,EACnB;AACA,MAAI;AACF,UAAM,eAAW,6BAAY,aAAa,EAAE;AAAA,MAC1C,UAAQ,KAAK,SAAS,iBAAiB,KAAK,SAAS;AAAA,IACvD;AACA,QAAI,UAAU;AACZ,aAAO,SAAS,QAAQ,qBAAqB,EAAE;AAAA,IACjD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,aAAO,6BAAS,aAAa;AAC/B;AAgBA,SAAS,sBACP,eACA,SACA,WACgB;AAChB,QAAM,QAAQ,QAAQ,SAAS,UAAU;AACzC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC9F;AACA,QAAM,WAAWA,iBAAgB,SAAS,UAAU,QAAQ;AAC5D,QAAM,KAAK,QAAQ,MAAM,UAAU;AACnC,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAQ,QAAQ,SAAS,UAAU;AACzC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,gBAAgB,QAAQ,iBAAiB,UAAU,iBAAiB;AAM1E,QAAM,SAAS,QAAQ,UAAU,UAAU,OAAO,UAAU;AAC5D,QAAM,aAAa,QAAQ,cAAc,UAAU,OAAO;AAC1D,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,eAAe,qBAAqB,eAAe,SAAS;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,EAAE,YAAY,OAAO;AAAA,EAC9B;AACF;AA+BO,SAAS,WAAW,eAAuB,SAA+B;AAC/E,MAAI,CAAC,eAAW,yBAAK,eAAe,SAAS,CAAC,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR,uBAAuB,aAAa;AAAA,IACtC;AAAA,EACF;AACA,QAAM,YAAY,eAAe,aAAa;AAC9C,QAAM,WAAW,sBAAsB,eAAe,SAAS,SAAS;AAExE,SAAO;AAAA,IACL;AAAA,EACF;AACA,eAAa,eAAe,QAAQ;AAUpC,eAAa,eAAe,SAAS,MAAM,MAAM;AAEjD,SAAO,QAAQ,6DAA6D;AAC9E;;;AFpOA,eAAsB,iBAAgC;AACpD,QAAM,cAAc,eAAW,yBAAK,QAAQ,IAAI,GAAG,SAAS,CAAC;AAC7D,QAAM,YAAY,EAAE,MAAM,yBAAyB,OAAO,MAAe;AACzE,QAAM,YAAY,EAAE,MAAM,mCAAmC,OAAO,MAAe;AACnF,QAAM,gBAAgB;AAAA,IACpB,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAEA,QAAM,SAAS,UAAM,wBAA0B;AAAA,IAC7C,SAAS;AAAA,IACT,SAAS,cACL,CAAC,WAAW,eAAe,SAAS,IACpC,CAAC,WAAW,WAAW,aAAa;AAAA,EAC1C,CAAC;AAED,MAAI,WAAW,OAAO;AACpB,UAAM,OAAO,QAAW,CAAC,CAAC;AAAA,EAC5B,WAAW,WAAW,WAAW;AAC/B,eAAW,QAAQ,IAAI,GAAG,CAAC,CAAC;AAAA,EAC9B,OAAO;AACL,UAAM,OAAO,QAAW,QAAW,CAAC,CAAC;AAAA,EACvC;AACF;;;AGxDA,IAAAC,mBAA6B;AAC7B,IAAAC,qBAAqB;AACrB,IAAAC,sBAAkB;AAWlB,IAAM,eAAe;AAGrB,IAAM,sBAAsB;AA4BrB,SAAS,eAAe,gBAA8B;AAC3D,MAAI;AACF,QAAI,CAAC,QAAQ,OAAO,OAAO;AACzB;AAAA,IACF;AACA,iBAAa,MAAM;AACjB,UAAI;AACF,yBAAiB,cAAc;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;AAeA,SAAS,iBAAiB,gBAA8B;AACtD,QAAM,SAAS,oBAAAC,QAAM,KAAK,OAAO,CAAC,QAAQ,cAAc,SAAS,GAAG;AAAA,IAClE,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,EACpC,CAAC;AACD,MAAI,OAAO,SAAS,OAAO,WAAW,GAAG;AACvC;AAAA,EACF;AAEA,QAAM,iBAAiB,OAAO,UAAU,IAAI,KAAK;AACjD,MAAI,CAAC,iBAAiB,CAAC,eAAe,eAAe,cAAc,GAAG;AACpE;AAAA,EACF;AAEA,SAAO,KAAK,EAAE;AACd,SAAO,KAAK,oBAAoB,YAAY,kBAAkB,aAAa,EAAE;AAC7E,SAAO,KAAK,sBAAsB,cAAc,EAAE;AAClD,SAAO,KAAK,iCAAiC,YAAY,SAAS;AAClE,SAAO,KAAK,EAAE;AAChB;AAmBA,SAAS,eAAe,WAAmB,SAA0B;AACnE,QAAM,iBAAiB,cAAc,SAAS;AAC9C,QAAM,eAAe,cAAc,OAAO;AAC1C,MAAI,CAAC,kBAAkB,CAAC,cAAc;AACpC,WAAO;AAAA,EACT;AAEA,aAAW,CAAC,OAAO,aAAa,KAAK,eAAe,QAAQ,GAAG;AAC7D,QAAI,kBAAkB,aAAa,KAAK,GAAG;AACzC,aAAO,gBAAgB,aAAa,KAAK;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAUA,SAAS,cAAc,SAAuD;AAC5E,QAAM,QAAQ,uBAAuB,KAAK,QAAQ,KAAK,CAAC;AACxD,SAAO,QAAQ,CAAC,OAAO,MAAM,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI;AAC1E;AAeO,SAAS,eAAe,kBAAkC;AAC/D,MAAI;AACF,UAAM,mBAAe,yBAAK,kBAAkB,MAAM,cAAc;AAChE,UAAM,WAAW,KAAK,UAAM,+BAAa,cAAc,MAAM,CAAC;AAC9D,WAAO,SAAS,WAAW;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ApB3IO,SAAS,aAAa,YAA6B;AACxD,QAAM,UAAU,IAAI,yBAAQ;AAE5B,UACG,KAAK,MAAM,EACX,YAAY,yDAAoD,EAChE,QAAQ,YAAY,iBAAiB,qBAAqB;AAE7D,UACG,QAAQ,KAAK,EACb,SAAS,UAAU,gBAAgB,EACnC,YAAY,iEAAiE,EAC7E,OAAO,aAAa,mDAAmD,EACvE,OAAO,mBAAmB,iDAAiD,EAC3E,OAAO,qBAAqB,uBAAuB,EACnD,OAAO,yBAAyB,2BAA2B,EAC3D,OAAO,oBAAoB,sBAAsB,EACjD,OAAO,2BAA2B,sBAAsB,EACxD;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,2BAA2B,iDAAiD,EACnF,OAAO,mBAAmB,oCAAoC,EAC9D,OAAO,0BAA0B,iCAAiC,EAClE;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,cAAc,kEAAkE,EACvF,OAAO,OAAO,MAA0B,YAAwB;AAC/D,UAAM,OAAO,MAAM,OAAO;AAAA,EAC5B,CAAC;AAEH,UACG,QAAQ,SAAS,EACjB;AAAA,IACC;AAAA,EACF,EACC,OAAO,mBAAmB,oEAAoE,EAC9F,OAAO,qBAAqB,uDAAuD,EACnF,OAAO,yBAAyB,2BAA2B,EAC3D,OAAO,oBAAoB,sBAAsB,EACjD,OAAO,2BAA2B,sBAAsB,EACxD,OAAO,kBAAkB,gDAAgD,EACzE,OAAO,2BAA2B,iDAAiD,EACnF,OAAO,mBAAmB,oEAAoE,EAC9F;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,CAAC,YAA4B;AACnC,eAAW,QAAQ,IAAI,GAAG,OAAO;AAAA,EACnC,CAAC;AAEH,UACG,QAAQ,QAAQ,EAChB;AAAA,IACC;AAAA,EACF,EACC,OAAO,MAAM;AACZ,cAAU,QAAQ,IAAI,CAAC;AAAA,EACzB,CAAC;AAEH,UACG,QAAQ,KAAK,EAIb,YAAY,IAAI,0BAAS,UAAU,cAAc,EAAE,QAAQ,aAAa,CAAC,EACzE,SAAS,UAAU,cAAc,EACjC,YAAY,iEAAiE,EAC7E,OAAO,mBAAmB,iEAAiE,EAC3F;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC;AAAA,IACC,OAAO,MAA+B,MAA0B,YAAwB;AACtF,YAAM,OAAO,MAAM,MAAM,OAAO;AAAA,IAClC;AAAA,EACF;AAIF,UAAQ,OAAO,YAAY;AACzB,UAAM,eAAe;AAAA,EACvB,CAAC;AAED,SAAO;AACT;AAmBA,eAAsB,OAAsB;AAC1C,MAAI;AACF,UAAM,aAAa,eAAe,SAAS;AAC3C,UAAM,UAAU,aAAa,UAAU;AAGvC,mBAAe,UAAU;AAEzB,UAAM,aAAa,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC;AAChD,UAAM,uBAAuB,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,WAAW;AAC/E,QAAI,CAAC,wBAAwB,QAAQ,OAAO,OAAO;AACjD,aAAO,KAAK,SAAS,UAAU,EAAE;AAAA,IACnC;AAEA,UAAM,QAAQ,WAAW,QAAQ,IAAI;AAAA,EACvC,SAAS,OAAO;AACd,WAAO,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACnE,YAAQ,WAAW;AAAA,EACrB;AACF;AASA,IAAI,QAAQ,SAAS,QAAQ;AAC3B,OAAK,KAAK;AACZ;","names":["import_node_path","import_prompts","spawn","value","import_node_path","import_node_fs","import_node_path","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_fs","import_node_path","pythonPublishUrl","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/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/overlay.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 `npm run format:check` the moment it is\n * created, which is a poor first impression and, worse, buries every real\n * formatting change under a wall of generator noise on the first commit.\n * Running it here means what mnci hands back is already normalised.\n *\n * **Which formatter is not a detail, and hardcoding Prettier here was a real\n * bug.** An oxlint workspace has no `.prettierrc.mjs` — the overlay deletes it\n * — so `npx prettier --write .` there does not fail, it silently formats the\n * whole workspace against **Prettier's own defaults**: semicolons, double\n * quotes, trailing commas. The exact opposite of the shared opinion, applied to\n * files mnci itself had just written correctly. `oxfmt --check` then reported\n * 19 files unformatted in a freshly generated workspace, `eslint.config.mjs`\n * and `oxlint.config.ts` among them. Caught by the real e2e; no fixture could\n * have, since it needs a generated workspace with both configs in play.\n *\n * `.prettierignore` (written by the overlay) keeps this off `node_modules`,\n * build output and lockfiles, and oxfmt reads that same file by default — so\n * the pass stays cheap either way, with one ignore list rather than two.\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 linter - The workspace's linter choice, which picks the formatter:\n * `eslint` pairs with Prettier, `oxlint` with oxfmt.\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 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 pairs with Prettier, oxlint with oxfmt, 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. Prettier has\n * native `.code-workspace` support and reformats a single-entry array with a\n * trailing comma, which `JSON.parse` then rejects — and `npm run format` is part\n * of mnci's documented routine, so a strict parse breaks on the first `format`\n * run.\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 { 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 is always ESLint + Prettier.\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 * 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 writeFileEnsured(\n manifestPath,\n toJson({ ...manifest, scripts: { ...existingScripts, ...scripts } })\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 { 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 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 markPublic(join(workspaceRoot, 'packages', name, 'package.json'))\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 { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport {\n defaultScope,\n ensurePlugin,\n markPrivate,\n markPublic,\n registerProjectCommands,\n removeGeneratedEslintConfig,\n type AddOptions,\n type WorkspaceStack\n} from './shared'\n\n/**\n * The declaration path `@nx/react:library --bundler=rollup` writes, which its own\n * build never produces.\n */\nconst WRONG_TYPES_PATH = './dist/index.esm.d.ts'\n\n/** The declaration path that rollup build actually emits. */\nconst ACTUAL_TYPES_PATH = './dist/index.d.ts'\n\n/**\n * Repoints a generated React library's `types` entries at the declaration file\n * its build actually emits.\n *\n * @remarks\n * Works around a real inconsistency in `@nx/react:library --bundler=rollup`, not\n * a preference: the generator writes `types: './dist/index.esm.d.ts'` (and the\n * same path under `exports['.']`), while its rollup build emits\n * `dist/index.d.ts`. The referenced file therefore never exists, and any\n * consumer importing the library fails with\n * `TS7016: Could not find a declaration file for module '@scope/name'` — so a\n * publishable React lib could not be consumed as a typed dependency at all.\n *\n * Verified empirically against a real generated pair: a `react-lib` importing a\n * `react-internal-lib` fails `typecheck` before this repair and passes after,\n * with `dist/index.d.ts` confirmed present in the build output. `main`/`module`\n * are left alone — they correctly point at `index.esm.js`, which *is* emitted;\n * only the declaration paths are wrong.\n *\n * The same class of bug as the `node-function-app` manifest `main` fix: a\n * generator-written path that is right for a layout other than the one actually\n * produced. Guarded on the exact wrong value, so if Nx corrects this upstream the\n * repair 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 */\nfunction repairTypesPath (manifestPath: string): void {\n const manifest = readJson<{\n types?: 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 writeFileEnsured(manifestPath, toJson(manifest))\n}\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 repairTypesPath(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 repairTypesPath(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 * 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 })\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 ]\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 * 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 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 { 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 is ESLint + Prettier (always).\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 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${feedKey}:username=AzureArtifacts\n${feedKey}:_password=\\${PAT}\n${feedKey}: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 * `.prettierrc.json` is deliberately absent: Prettier is not a project target,\n * so the pipeline's `format:check` step runs `prettier --check .` over the whole\n * tree on every run regardless. Listing it would invalidate every project's\n * cache and verify nothing new. `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 * 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 * 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// One override cannot work, and it is the one people reach for first:\n// \\`space-before-function-paren\\`. Prettier rewrites \\`f (a)\\` to \\`f(a)\\` on every\n// run, so switching that rule on makes \\`npm run lint\\` and \\`npm run format:check\\`\n// impossible to satisfy at the same time. Formatting lives in .prettierrc.mjs.\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 */\nexport const VSCODE_RECOMMENDED_EXTENSIONS = [\n 'dbaeumer.vscode-eslint',\n 'esbenp.prettier-vscode',\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner'\n] as const\n\n/**\n * Extensions every workspace gets, whichever linter it chose.\n *\n * @remarks\n * Nx's console and a test-runner integration: neither depends on the lint\n * toolchain, so they are factored out rather than repeated in both lists, where\n * one of the two copies would eventually be the stale one.\n */\nexport const SHARED_VSCODE_EXTENSIONS = [\n 'nrwl.angular-console',\n 'firsttris.vscode-jest-runner'\n] as const\n\n/**\n * Every language **both** formatters handle, and whose formatter mnci pins.\n *\n * @remarks\n * Not a convenience list — see {@link vscodeSettings} for why the general\n * `editor.defaultFormatter` cannot be relied on. The set is \"everything the\n * chosen formatter actually handles\", so it is measured against the real\n * binaries rather than assumed: each entry below is a file type Prettier 3.8\n * *and* oxfmt 0.61 both reformat.\n *\n * `typescript` and `typescriptreact` are the two that matter most and were the\n * two missing. `html` was missing too — both formatters have always handled it,\n * and the claim in this comment was simply not checked when it was written.\n *\n * See {@link OXFMT_ONLY_LANGUAGES} for the one file type where the two\n * formatters differ in what they can parse at all.\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] 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` is narrowed for oxlint to exactly the languages ESLint still\n * owns there. Leaving JS/TS in that list would ask the ESLint extension to\n * validate files whose config has no rules for them — reporting nothing, but\n * spending a language-server round trip per keystroke to do it, and telling the\n * reader that ESLint covers JS when it does not.\n *\n * @param linter - The workspace's linter choice.\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. Nothing reports it: Prettier is installed,\n // the config resolves, `npm run format:check` still finds the 68 unformatted\n // files, and the editor simply never applies it.\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 * 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, Prettier), 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 * 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): string {\n return JSON.stringify(\n {\n folders: [{ path: '.', name: workspaceName }],\n settings: vscodeSettings(),\n extensions: { recommendations: VSCODE_RECOMMENDED_EXTENSIONS },\n tasks: {\n version: existingTasks?.version ?? '2.0.0',\n tasks: existingTasks?.tasks ?? []\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 formatting via Prettier and linting via ESLint.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`) following code-quality\n * and correctness rules. Prettier handles all formatting (`npm run format` writes;\n * `npm run format:check` verifies without writing), following JavaScript Standard Style.\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 const onMain = 'and(succeeded(), ne(variables[\\'Build.Reason\\'], \\'PullRequest\\'), 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: [main]\n\n# Note: cancelling a superseded PR validation run is NOT a YAML setting on Azure\n# — it lives in the branch policy (\"Build validation -> automatically cancel\").\n# Nothing here can express it, so it is left to whoever configures the policy\n# rather than faked with a batch setting that would only delay PR runs.\npr:\n branches:\n include: [main]\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 * {@link PRETTIER_CONFIG} 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`, `.prettierrc.mjs`,\n * `.prettierignore`, `.npmrc`, `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 (workspaceRoot: string, options: OverlayOptions): 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 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 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 }\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 writeFileEnsured(join(workspaceRoot, '.npmrc'), npmrcContent(options.registry, options.scope))\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 and correctness rules; Prettier handles all\n // formatting (JavaScript Standard Style). ONE ESLint config, at the root —\n // `add` deletes the per-project ones Nx generators write.\n // ONE config file, because there is one tool.\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 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 const codeWorkspacePath = join(workspaceRoot, `${options.workspaceName}.code-workspace`)\n const existing = readCodeWorkspace<{\n tasks?: { version?: string; tasks?: Record<string, unknown>[] }\n }>(codeWorkspacePath)\n writeFileEnsured(\n codeWorkspacePath,\n vscodeWorkspace(options.workspaceName, existing?.tasks)\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 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 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 { 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(workspaceRoot, { workspaceName, scope, registry, agent, variableGroup, ci, stack })\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(\n 'Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)'\n )\n applyOverlay(workspaceRoot, resolved)\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 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","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_fs","import_node_path","pythonPublishUrl","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"]}