@mnci/cli 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -320,10 +320,11 @@ than a surprise:
320
320
  `node_modules` beyond `npm install`): `mnci` neither creates nor activates
321
321
  one. CI installs `requirements-dev.txt`, then editable-installs every
322
322
  Python project workspace-wide (see "Workspace-wide install" above),
323
- straight into whatever `python3` resolves to on the agent; locally, create
324
- your own venv with `python3 -m venv` and reproduce the same two installs by
325
- hand (`pip install -r requirements-dev.txt`, then `pip install -e <dir>`
326
- for each Python project).
323
+ straight into whatever `<python>` resolves to on the agent (`python3` on
324
+ POSIX, `python` on Windows — see above); locally, create your own venv
325
+ (`python3 -m venv` / `python -m venv` on Windows) and reproduce the same
326
+ two installs by hand (`pip install -r requirements-dev.txt`, then
327
+ `pip install -e <dir>` for each Python project).
327
328
 
328
329
  ## How Node apps work (plain `@nx/node:application`, no Azure Functions plugin)
329
330
 
@@ -417,13 +418,18 @@ other npm devDependency (`npm install --save-dev @mnci/nx-python-pip` —
417
418
  no `nx.json` `plugins` registration needed, since its generators/executors
418
419
  are explicit, not inference-based) and writes exactly one file itself:
419
420
  `requirements-dev.txt` at the workspace root (the fixed `ruff`/`pytest`/
420
- `build`/`twine` toolchain — install with `python3 -m pip install -r
421
+ `build`/`twine` toolchain — install with `<python> -m pip install -r
421
422
  requirements-dev.txt`), since the plugin is a generic Nx plugin with no
422
423
  opinion on how its own runtime dependencies land on a machine. There is **no
423
424
  stack question** — Ruff (lint + format) and pytest are the standard, so they
424
- are always used, invoked as `python3 -m <tool>` everywhere (not a
425
+ are always used, invoked as `<python> -m <tool>` everywhere (not a
425
426
  hard-coded venv path), so the exact same command works whether or not a venv
426
- is activated.
427
+ is activated. `<python>` resolves to `python3` on POSIX or `python` on
428
+ Windows (the standard python.org Windows installer registers no
429
+ `python3.exe`) — every guard script in the generated pipeline and every
430
+ `@mnci/nx-python-pip` executor makes this same platform check, never a
431
+ hard-coded name, so a `windows-latest` (or self-hosted Windows) agent works
432
+ identically to a Linux/macOS one.
427
433
 
428
434
  | Kind | Location | Build / deploy |
429
435
  | ---- | -------- | -------------- |
package/dist/cli.js CHANGED
@@ -783,8 +783,8 @@ function pythonPublishUrl(registry) {
783
783
  }
784
784
  return void 0;
785
785
  }
786
- var PYTHON_INSTALL_GUARD = `node -e "if(!require('node:fs').existsSync('requirements-dev.txt')){console.log('No Python projects - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('python3 -m pip install -r requirements-dev.txt',{stdio:'inherit',shell:true}).status ?? 1)"`;
787
- var 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])];process.exit(require('node:child_process').spawnSync('python3',args,{stdio:'inherit'}).status ?? 1)"`;
786
+ var 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)"`;
787
+ var 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)"`;
788
788
  var 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)"`;
789
789
  function releaseGuard(pythonPublishEnv) {
790
790
  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)"`;
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/nx.ts","../src/prompts.ts","../src/util/fsx.ts","../src/util/logger.ts","../src/util/names.ts","../src/commands/add/node.ts","../src/commands/add/shared.ts","../src/commands/add/npmLib.ts","../src/commands/add/python.ts","../src/commands/add/reactApp.ts","../src/commands/interactive.ts","../src/commands/new.ts","../src/overlay.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './commands/add'\nimport { runInteractive } from './commands/interactive'\nimport { runNew, type NewOptions } from './commands/new'\nimport { logger } from './util/logger'\n\n/** Reads the CLI version from the packaged package.json (next to dist/). */\nfunction readVersion (): string {\n try {\n const package_ = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')) as { version?: string }\n return package_.version ?? '0.0.0'\n } catch {\n return '0.0.0'\n }\n}\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Two commands only — `new` and `add`. Everything a generated repo needs\n * day-to-day (build/test/lint/release) is plain Nx, so the CLI deliberately\n * has no wrapper commands for those.\n *\n * @param None - this function takes no parameters.\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 (): Command {\n const program = new Command()\n\n program\n .name('mnci')\n .description('MoNecromanCI — a thin CLI over official Nx plugins')\n .version(readVersion(), '-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('--agent <pool>', 'CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name')\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option('--ci <provider>', 'CI provider: azure | github | both')\n .option('--linter <linter>', 'linter: eslint | oxlint')\n .option('--test-runner <runner>', 'unit-test runner: jest | vitest')\n .action(async (name: string | undefined, options: NewOptions) => {\n await runNew(name, options)\n })\n\n program\n .command('add')\n // choices() rejects an unrecognized kind with a clean commander usage\n // error (and lists the valid ones in --help) before runAdd ever runs —\n // instead of a typo silently doing nothing but reporting \"success\".\n .addArgument(new Argument('[kind]', 'project kind').choices(PROJECT_KINDS))\n .argument('[name]', 'project name')\n .description('Add a project by delegating to the matching Nx plugin generator')\n .option('--scope <scope>', 'npm scope for a publishable lib (defaults to @<workspace name>)')\n .action(async (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions) => {\n await runAdd(kind, name, options)\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 *\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 await buildProgram().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.\nif (require.main === module) {\n main()\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { runNx, runShell } from '../nx'\nimport { promptText } from '../prompts'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../util/fsx'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\nimport { addNodeApp, addNodeFunctionApp } from './add/node'\nimport { addNpmLib } from './add/npmLib'\nimport { addPythonApp, addPythonFunctionApp, addPythonInternalLib, addPythonLib } from './add/python'\nimport { addReactApp } from './add/reactApp'\nimport type { AddOptions, WorkspaceStack } from './add/shared'\n\nexport type { AddOptions } from './add/shared'\n\n/**\n * The project kinds this CLI can add — deliberately just nine.\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/node.ts`, `add/npmLib.ts` and `add/python.ts`\n * (internal-lib is small enough to stay inline below).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type ProjectKind\n = | 'react-app' | 'node-app' | 'node-function-app' | 'npm-lib' | 'internal-lib'\n | 'python-app' | 'python-function-app' | 'python-lib' | 'python-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. TS/JS\n * kinds first, then the Python family.\n */\nexport const PROJECT_KINDS: ProjectKind[] = [\n 'react-app', 'node-app', 'node-function-app', 'npm-lib', 'internal-lib',\n 'python-app', 'python-function-app', 'python-lib', 'python-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 (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions): 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 = kind ?? 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 'node-app': {\n addNodeApp(workspaceRoot, resolvedName, stack)\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 'g', '@nx/js:lib', `libs/${resolvedName}`,\n '--bundler=tsc',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--no-interactive',\n ], workspaceRoot)\n markPrivate(join(workspaceRoot, 'libs', resolvedName, 'package.json'))\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 default: {\n // Unreachable while every ProjectKind has a case above: `exhaustive`\n // being `never` makes adding a 9th 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(`Unknown project kind '${exhaustive as string}'. Expected one of: ${PROJECT_KINDS.join(', ')}.`)\n }\n }\n\n syncProjectReferences(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('nx sync did not complete — run `npx nx sync` yourself so cross-project imports resolve in your editor.')\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?: { linter?: string, testRunner?: string } } }>(join(workspaceRoot, 'nx.json'))\n const stack = nxJson.mnci?.stack\n return {\n linter: stack?.linter === 'oxlint' ? 'none' : 'eslint',\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest',\n }\n}\n\n/**\n * Sets `\"private\": true` in a package manifest.\n *\n * @remarks\n * One of the deliberate post-generation touches: it makes internal\n * libraries structurally unpublishable, no matter what future config drifts.\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 */\nfunction markPrivate (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }))\n}\n","import spawn from 'cross-spawn'\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","import { 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 for the stack: linter and unit-test runner.\n *\n * @remarks\n * The knobs asked up front at `mnci new`. TypeScript is fixed (the\n * `--preset=ts` premise, pinned to the TS 6 that Nx 23 supports), so only the\n * linter and test runner are asked. Each is a binary choice — no \"none\" — with\n * the current opinionated default listed first.\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 linter = await select<StackConfig['linter']>({\n message: 'Linter',\n choices: [\n { name: 'ESLint', value: 'eslint' },\n { name: 'Oxlint', value: 'oxlint' },\n ],\n })\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 { linter, 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 * 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 * 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","/**\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 kinds, once hyphens become underscores — a Python module identifier.\n * A conservative charset makes it safe in all three roles at once: lowercase\n * letters, digits and hyphens, starting with a letter.\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 or contains a character outside\n * `[a-z0-9-]`, or doesn't start with a lowercase letter.\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-]*$/.test(name)) {\n throw new Error(`${label} '${name}' is invalid — use lowercase letters, digits and hyphens, starting with a letter (e.g. 'my-project').`)\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 { addNxTargets, ensureAdmZip, hasPlugin, type WorkspaceStack } 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 — `esbuild` (non-bundled: it transpiles each file and\n * mirrors the workspace tree into `dist`, rather than producing one bundled\n * file) is the generator's own default, and `--framework=none` keeps the\n * scaffold a bare Node app (no Express/Fastify/Koa/Nest opinion). Both\n * `node-app` and `node-function-app` generate identically — 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 * @returns Nothing.\n * @throws Error when the generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction runNodeApp (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n ensurePlugin(workspaceRoot, '@nx/node')\n runNx([\n 'g', '@nx/node:application', `apps/${name}`,\n '--bundler=esbuild',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--e2eTestRunner=none',\n '--framework=none',\n '--no-interactive',\n ], workspaceRoot)\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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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) — no\n * custom 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 * @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 (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n runNodeApp(workspaceRoot, name, stack)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(workspaceRoot, 'apps', name, 'package.json'), { package: nodeAppPackageTarget(name) })\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 (runShell('npm', ['install', '@azure/functions', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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, 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 the deployable\n * manifest's `main` is simply `main.js`, sitting beside that shim once the\n * `package` target zips `dist` + `host.json` + `package.json` together (see\n * {@link nodeFunctionAppPackageTarget}). `@azure/functions` is added for\n * real (not just referenced): Azure's deploy-time Oryx build reads this\n * manifest's `dependencies` and runs `npm install` there — verified\n * empirically that a plain `npm install` in a simulated deploy folder (no\n * bundled `node_modules`) resolves and runs correctly once the dependency is\n * declared, mirroring exactly how `python-function-app` already relies on\n * Azure's Python Oryx build 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 }>(join(workspaceRoot, 'node_modules/@azure/functions/package.json')).version\n const dependencies = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n writeFileEnsured(manifestPath, toJson({\n ...manifest,\n main: 'main.js',\n dependencies: { ...dependencies, '@azure/functions': `^${azureFunctionsVersion}` },\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) together with `host.json` and the repaired\n * `package.json` into `dist/drop/node-function-app-<name>.zip`. No\n * `node_modules` bundled: Azure's Oryx build installs real dependencies from\n * the zipped `package.json` at deploy time (see\n * {@link repairNodeFunctionAppManifest}). Basename exactly\n * `node-function-app-<name>` (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');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\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`).\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 (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n runNodeApp(workspaceRoot, name, stack)\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(join(nodeFunctionAppRoot, 'src/functions/greeting.ts'), NODE_FUNCTION_APP_GREETING)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/functions/greeting.spec.ts'), NODE_FUNCTION_APP_GREETING_SPEC)\n writeFileEnsured(join(nodeFunctionAppRoot, 'host.json'), NODE_FUNCTION_APP_HOST_JSON)\n repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(nodeFunctionAppRoot, 'package.json'), { package: nodeFunctionAppPackageTarget(name) })\n}\n","import { join } from 'node:path'\nimport { runShell } from '../../nx'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\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}\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 * `linter` is the value passed straight to `@nx/*` generators — `eslint`, or\n * `none` when the workspace chose oxlint (oxlint is not an Nx linter).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface WorkspaceStack {\n linter: 'eslint' | 'none'\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<{ dependencies?: Record<string, string>, devDependencies?: Record<string, string> }>(join(workspaceRoot, 'package.json'))\n const installed = { ...manifest.dependencies, ...manifest.devDependencies }\n return Object.hasOwn(installed, packageName)\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 (runShell('npm', ['install', '--save-dev', 'adm-zip', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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(manifestPath, toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } }))\n}\n","import { join } from 'node:path'\nimport { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { defaultScope, type AddOptions, type WorkspaceStack } from './shared'\n\n/**\n * The per-npm-lib ESLint config written over the generator's default.\n *\n * @remarks\n * Identical to what `@nx/js:lib --bundler=rollup` generates, plus ONE\n * addition: `@nx/dependency-checks` gets an `ignoredDependencies` list of\n * every `private: true` workspace package, computed at lint time. Private\n * libs are compiled INTO the rollup bundle and never declared in the\n * manifest (a consumer could not install them) — without this, the rule\n * flags every internal-lib import as a missing dependency. Because the list\n * is computed, adding a new internal lib never requires touching this file.\n */\nexport const NPM_LIB_ESLINT_CONFIG = `import { globSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport baseConfig from '../../eslint.config.mjs';\n\n// Private workspace libs are compiled INTO this package's rollup bundle and\n// never declared in the manifest (a consumer could not install them), so the\n// dependency check must ignore them. Computed at lint time: adding a new\n// internal lib never requires touching this file.\nconst workspaceRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');\nconst privateWorkspacePackages = globSync(['libs/*/package.json', 'packages/*/package.json'], { cwd: workspaceRoot })\n .map((manifestPath) => JSON.parse(readFileSync(join(workspaceRoot, manifestPath), 'utf8')))\n .filter((manifest) => manifest.private === true)\n .map((manifest) => manifest.name);\n\nexport default [\n ...baseConfig,\n {\n files: ['**/*.json'],\n rules: {\n '@nx/dependency-checks': [\n 'error',\n {\n ignoredDependencies: privateWorkspacePackages,\n ignoredFiles: [\n '{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',\n '{projectRoot}/rollup.config.{js,ts,mjs,mts,cjs,cts}',\n ],\n },\n ],\n },\n languageOptions: {\n parser: await import('jsonc-eslint-parser'),\n },\n },\n {\n ignores: ['**/out-tsc'],\n },\n];\n`\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 linter/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 (workspaceRoot: string, name: string, options: AddOptions, kindProvided: boolean, stack: WorkspaceStack): Promise<void> {\n const scope = options.scope ?? (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n runNx([\n 'g', '@nx/js:lib', `packages/${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n '--bundler=rollup',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--no-interactive',\n ], workspaceRoot)\n markPublic(join(workspaceRoot, 'packages', name, 'package.json'))\n // The dependency-check override is an ESLint config; oxlint has no such\n // rule, so it only applies when ESLint is the chosen linter.\n if (stack.linter === 'eslint') {\n writeFileEnsured(join(workspaceRoot, 'packages', name, 'eslint.config.mjs'), NPM_LIB_ESLINT_CONFIG)\n }\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 npm-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\n * dry-run surfaces, since dry-runs never call the registry. This is the one\n * post-generation touch that makes a freshly added npm-lib publishable\n * as-is.\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 */\nfunction markPublic (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: 'public' } }))\n}\n","import { 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 } 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 = runShell('python3', ['--version'], workspaceRoot) === 0 || runShell('python', ['--version'], workspaceRoot) === 0\n if (!hasPython) {\n throw new Error('Python not found. Install Python 3.9+ first: https://www.python.org/downloads/')\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 (runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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. 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\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 `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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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 (name: string, moduleDirectory: string): 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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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(['g', '@mnci/nx-python-pip:application', name, `--directory=apps/${name}`, '--no-interactive'], workspaceRoot)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), { package: pythonAppPackageTarget(name) })\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(['g', '@mnci/nx-python-pip:function-application', name, `--directory=apps/${name}`, '--no-interactive'], workspaceRoot)\n const moduleDirectory = pythonModuleDirectory(name)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), { package: pythonFunctionAppPackageTarget(name, moduleDirectory) })\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(['g', '@mnci/nx-python-pip:library', name, `--directory=python-packages/${name}`, '--no-interactive'], workspaceRoot)\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(['g', '@mnci/nx-python-pip:internal-library', name, `--directory=libs/${name}`, '--no-interactive'], workspaceRoot)\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx } from '../../nx'\nimport { fileExists, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { addNxTargets, ensureAdmZip, hasPlugin, type WorkspaceStack } 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.\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 */\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 * 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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\n outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],\n options: { command: `vite build --mode ${environment} --outDir dist-${environment}`, cwd: `apps/${name}` },\n }\n }\n const zipStatements = REACT_ENVIRONMENTS\n .map((environment) => `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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} tokens, not JS interpolation\n outputs: REACT_ENVIRONMENTS.map((environment) => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`),\n options: { command: `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}\"` },\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 = '# 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 'g', '@nx/react:app', `apps/${name}`,\n '--bundler=vite',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--style=css',\n '--e2eTestRunner=none',\n '--no-interactive',\n ], workspaceRoot)\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}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../util/fsx'\nimport { runAdd } from './add'\nimport { runNew } from './new'\n\n/** The two things the wizard can start: scaffold a workspace, or add to one. */\ntype InteractiveAction = 'new' | 'add'\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 two commands, then hands off to the\n * existing flows — {@link runNew} and {@link runAdd} — which already prompt for\n * every field they need (name/scope/registry/agent/variable group for `new`;\n * kind/name, and the npm-lib scope on this bare path, for `add`). Nothing about\n * the prompting lives here, so the wizard and the flag-driven commands can\n * never drift.\n *\n * The menu is ordered by context: inside a workspace (an `nx.json` in the cwd)\n * `add` is offered first, otherwise `new` — so the default highlight matches\n * what the user most likely wants. Both are always shown; picking `add`\n * outside a workspace surfaces {@link runAdd}'s clear \"run from the workspace\n * 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 newChoice = { name: 'Create a new monorepo', value: 'new' as const }\n const addChoice = { name: 'Add a project to this workspace', value: 'add' as const }\n\n const action = await select<InteractiveAction>({\n message: 'What would you like to do?',\n choices: inWorkspace ? [addChoice, newChoice] : [newChoice, addChoice],\n })\n\n if (action === 'new') {\n await runNew(undefined, {})\n } else {\n await runAdd(undefined, undefined, {})\n }\n}\n","import { join } from 'node:path'\nimport { runNpx, runShell } from '../nx'\nimport { applyOverlay, DEFAULT_STACK, type CiProvider, type RegistryConfig, type StackConfig } from '../overlay'\nimport { promptCi, 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 /** Linter (`eslint` or `oxlint`). */\n linter?: StackConfig['linter']\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n}\n\n/**\n * Resolves the stack from flags, prompts, or `--yes` defaults.\n *\n * @remarks\n * Any of the three knobs passed as a flag is taken as-is; if all three are\n * passed (or `--yes` is set) nothing is prompted. Otherwise the interactive\n * {@link promptStack} runs and its answers fill the gaps.\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 const fromFlags = { linter: options.linter, testRunner: options.testRunner }\n const complete = Boolean(fromFlags.linter && fromFlags.testRunner)\n if (complete || options.yes) {\n return {\n linter: fromFlags.linter ?? DEFAULT_STACK.linter,\n testRunner: fromFlags.testRunner ?? DEFAULT_STACK.testRunner,\n }\n }\n const prompted = await promptStack()\n return {\n linter: fromFlags.linter ?? prompted.linter,\n testRunner: fromFlags.testRunner ?? prompted.testRunner,\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 = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText('npm scope for publishable packages', `@${workspaceName}`))\n const registry = await resolveRegistry(options)\n const ci = await resolveCi(options)\n const agent = options.agent ?? (options.yes ? 'ubuntu-latest' : await promptText('CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)', 'ubuntu-latest'))\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 = ci === 'github'\n ? (options.variableGroup ?? 'Build')\n : options.variableGroup ?? (options.yes ? 'Build' : await promptText('Azure DevOps variable group holding the npm PAT', 'Build'))\n const stack = await resolveStack(options)\n\n logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`)\n runNpx([\n '--yes',\n 'create-nx-workspace@latest',\n workspaceName,\n '--preset=ts',\n '--pm=npm',\n '--nxCloud=skip',\n '--no-interactive',\n ], process.cwd())\n\n const workspaceRoot = join(process.cwd(), workspaceName)\n\n logger.step('Applying MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)')\n applyOverlay(workspaceRoot, { scope, registry, agent, variableGroup, ci, stack })\n\n // Install the commit toolchain (and, for the oxlint stack, oxc-standard —\n // which brings oxlint + oxfmt and the JavaScript Standard Style preset the\n // generated oxlint.config.mts / oxfmt.config.mts reference; ESLint is set up\n // by the Nx generators on first `add`). One install.\n const stackDependencies = stack.linter === 'oxlint' ? ['oxc-standard'] : []\n logger.step(`Installing the toolchain (${stack.linter === 'oxlint' ? 'oxc-standard' : 'eslint'}, commit toolchain)`)\n const installStatus = runShell('npm', ['install', '--save-dev', ...stackDependencies, 'husky', '@commitlint/cli', '@commitlint/config-conventional'], workspaceRoot)\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 logger.success('Done. Next steps:')\n logger.info(` cd ${workspaceName}`)\n logger.info(' mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,')\n logger.info(' # python-app, python-function-app, python-lib, python-internal-lib')\n logger.info(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { join } from 'node:path'\nimport { markExecutable, 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. So the two real knobs are the\n * linter and the unit-test runner, persisted as Nx **generator defaults** in\n * `nx.json` (the oxlint case also writes a workspace `oxlint.config.mts` and\n * points the root `lint` script at `oxlint`).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport interface StackConfig {\n /** Linter: Nx-native `eslint`, or workspace-wide `oxlint`. */\n linter: 'eslint' | 'oxlint'\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 * ESLint and Jest: the combination existing generated repos (and the e2e\n * suite) already assume, so defaulting to it keeps behaviour unchanged when\n * the stack is not chosen explicitly.\n */\nexport const DEFAULT_STACK: StackConfig = { linter: 'eslint', 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 * Scope-to-registry routing is what guarantees packages never land on the\n * public npm registry by accident: every publishable package is named under\n * `scope`, and the scoped registry line routes all of them to the configured\n * feed.\n *\n * Azure Artifacts is authenticated with the **base64-encoded PAT** the feed's\n * \"Connect to feed → npm\" instructions produce — the classic `_password`\n * block, one pair for the `/npm/registry/` path (install) and one for the feed\n * root (publish). The PAT *value* never lands in the file: `${PAT}` stays\n * literal on disk and is expanded at runtime — in CI from the `Build` variable\n * group's `PAT`, locally from an exported `PAT=<base64 token>`. (No\n * `npmAuthenticate@0` task: it would overwrite this hand-set password.) Public\n * npm keeps the raw `_authToken` an automation token uses.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`) the scoped registry applies to.\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 const scopeName = scope.replace(/^@/, '')\n const lines = [\n 'registry=https://registry.npmjs.org/',\n '; Community Nx plugins (e.g. the Azure Functions one) peer on the previous',\n '; Nx major for a while after each release; accept the resolved tree.',\n 'legacy-peer-deps=true',\n ]\n\n if (registry.kind === 'azure-artifacts') {\n const base = `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}`\n const registryHost = `${base}/npm/registry/`.replace(/^https:\\/\\//, '')\n const feedHost = `${base}/`.replace(/^https:\\/\\//, '')\n lines.push(\n `@${scopeName}:registry=${base}/npm/registry/`,\n '; Azure Artifacts auth — the base64 PAT is expanded at runtime from the',\n '; PAT env var (CI: your PAT variable group; locally: export PAT=<base64 token>).',\n `//${registryHost}:username=${registry.organization}`,\n `//${registryHost}:_password=\\${PAT}`,\n `//${registryHost}:email=npm@example.com`,\n `//${feedHost}:username=${registry.organization}`,\n `//${feedHost}:_password=\\${PAT}`,\n `//${feedHost}:email=npm@example.com`,\n )\n } else {\n lines.push(`//registry.npmjs.org/:_authToken=\\${NODE_AUTH_TOKEN}`)\n }\n\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * 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. Changelog files are disabled because they would be\n * unpushable under the tag-only model.\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 * `push: false` here is deliberate, not an oversight: the combined\n * `nx release` command's own final push (`release.js`, after tagging) only\n * runs when a remote GitHub/GitLab Release is being created\n * (`shouldCreateWorkspaceRemoteRelease`), which this config never enables —\n * so that push never fires. The *only* push Nx does run is an internal one\n * buried inside the version step, which happens **before** the tag exists\n * (tagging is deliberately suppressed there and done later, by `release.js`\n * itself) — so even with `pushArgs: '--tags'` that earlier push has no new\n * tag to push yet, and the tag is left local-only (verified empirically\n * against a real CI run: the release step and publish both report success,\n * \"Pushing to git remote\" logs *before* \"Tagging commit with git\", and the\n * new tag never reaches `origin`, even though the version it names does\n * reach npm — a silent, permanent mismatch that makes every subsequent\n * release recompute and re-skip the same already-published version forever).\n * Disabling Nx's own push entirely and pushing tags explicitly, once, after\n * tagging is guaranteed to have happened (the CI step below), sidesteps this\n * ordering bug rather than fighting it.\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.\n * Internal libraries live in `libs/` and apps in `apps/`, so release scoping\n * still needs no custom tags.\n */\nexport const RELEASE_CONFIG = {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*'],\n releaseTag: { pattern: '{projectName}@{version}' },\n git: { commit: false, tag: true, push: false },\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: { workspaceChangelog: false },\n} as const\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 * 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 * @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 (nxJson: Record<string, unknown>): Record<string, unknown> {\n return { ...nxJson, defaultBase: 'main', release: RELEASE_CONFIG }\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 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 */\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 affected: 'nx affected -t lint,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 `lint` bound to the chosen linter.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`); oxlint is a single\n * workspace-wide binary, so its `lint` is just `oxlint`. Everything downstream\n * (the CI pipeline) calls `npm run lint`, so it never needs to know which\n * linter was chosen.\n *\n * The oxlint stack also gets the oxc-standard formatter as `format` (write) and\n * `format:check` (CI-safe, no writes) — the JavaScript Standard Style *layout*\n * half that oxlint (a linter) does not enforce. The `-c oxfmt.config.mts` flag\n * is explicit because oxfmt (0.48) auto-discovers `.oxfmtrc.json` but not the\n * typed `.mts` config, and the whole stack is committed to typed configs.\n * ESLint workspaces keep Nx's own formatting story, so they get no `format`.\n *\n * @param stack - The chosen stack.\n * @returns The root scripts object to stamp into the manifest.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rootScripts (stack: StackConfig): Record<string, string> {\n if (stack.linter === 'oxlint') {\n return {\n ...ROOT_SCRIPTS,\n lint: 'oxlint',\n format: 'oxfmt -c oxfmt.config.mts .',\n 'format:check': 'oxfmt -c oxfmt.config.mts --check .',\n }\n }\n return { ...ROOT_SCRIPTS }\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 linter/runner automatically. oxlint is not an\n * Nx linter, so it maps to `linter: 'none'` — the workspace `oxlint.config.mts`\n * + the `oxlint` root script cover linting instead.\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 = { linter: stack.linter === 'oxlint' ? 'none' : 'eslint', unitTestRunner: stack.testRunner }\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 chosen stack.\n *\n * @remarks\n * The single source of truth `mnci add` reads back (`readWorkspaceStack` in\n * `add.ts`) — deliberately separate from {@link generatorDefaults}, which\n * serves Nx's own generator-default mechanism instead (a real, independent\n * feature: it makes a user's own direct `nx g` pick up the right defaults\n * too). One value, one place `add` trusts, with no invariant to keep in sync.\n *\n * @param stack - The chosen stack.\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 (stack: StackConfig): Record<string, unknown> {\n return { stack: { linter: stack.linter, testRunner: stack.testRunner } }\n}\n\n/**\n * The `oxlint.config.mts` written when oxlint is the chosen linter.\n *\n * @remarks\n * A typed config (`defineConfig` from `oxlint`, auto-detected — needs Node ≥22)\n * rather than JSON. Instead of hand-listing plugins and rules it **extends the\n * [oxc-standard](https://github.com/JohnDeved/ox-standard) preset** — the real\n * JavaScript Standard Style rule set for the oxc toolchain (unicorn + React +\n * react-perf + TypeScript + oxc, `correctness: error`, `suspicious: warn`,\n * `style: off`). A `.mts` config's `extends` must hold **config objects, not\n * paths** (unlike `.oxlintrc.json`, which cannot import from a package), so the\n * preset is imported as JSON and passed in — the pattern oxlint documents for\n * extending a shared package's config.\n *\n * The one local override keeps a freshly generated workspace green: Nx scaffolds\n * with the modern JSX transform, so React need not be in scope and the preset's\n * `react/react-in-jsx-scope` correctness rule is turned off. Add further project\n * overrides in the same `rules` block.\n *\n * Its formatting counterpart is {@link OXFMT_CONFIG} (Standard Style layout).\n */\nexport const OXLINT_CONFIG = `import { defineConfig } from 'oxlint'\nimport standard from 'oxc-standard/.oxlintrc.json' with { type: 'json' }\n\n// Generated by MoNecromanCI. JavaScript Standard Style linting via the\n// oxc-standard preset (unicorn + React + react-perf + TypeScript + oxc).\n// Add project-specific rule overrides in the \\`rules\\` block below.\nexport default defineConfig({\n extends: [standard],\n rules: {\n // The modern JSX transform (Nx's default) needs no React import in scope.\n 'react/react-in-jsx-scope': 'off',\n },\n})\n`\n\n/**\n * The `oxfmt.config.mts` written alongside {@link OXLINT_CONFIG} for oxlint.\n *\n * @remarks\n * oxfmt is the oxc toolchain's formatter — the *layout* half oxlint (a linter)\n * does not enforce. These options mirror oxc-standard's own `.oxfmtrc.json` so\n * formatting and linting agree on JavaScript Standard Style: no semicolons,\n * single quotes, 2-space indent, `es5` trailing commas, and parens omitted on\n * single-argument arrows. (oxfmt has no `extends`, so the preset's values are\n * inlined — exactly how oxc-standard itself scaffolds them.)\n *\n * `npm run format` writes; `npm run format:check` verifies without writing\n * ({@link rootScripts}) — both pass `-c oxfmt.config.mts` since oxfmt (0.48)\n * auto-discovers only `.oxfmtrc.json`, not the typed config. Nx generators emit\n * semicolon/double-quote code, so a new workspace is normalised on the first\n * `npm run format`.\n */\nexport const OXFMT_CONFIG = `import { defineConfig } from 'oxfmt'\n\n// Generated by MoNecromanCI. JavaScript Standard Style formatting, matching the\n// oxc-standard lint preset: no semicolons, single quotes, 2-space indent.\nexport default defineConfig({\n singleQuote: true,\n semi: false,\n printWidth: 100,\n tabWidth: 2,\n trailingComma: 'es5',\n arrowParens: 'avoid',\n})\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 */\nconst PYTHON_INSTALL_GUARD = `node -e \"if(!require('node:fs').existsSync('requirements-dev.txt')){console.log('No Python projects - skipping.');process.exit(0)}process.exit(require('node:child_process').spawnSync('python3 -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 */\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])];process.exit(require('node:child_process').spawnSync('python3',args,{stdio:'inherit'}).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 (registryKind: RegistryConfig['kind'], variableReference: (name: string) => string): [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 (agent: string, variableGroup: string, pythonPublishUrl?: string, registryKind: RegistryConfig['kind'] = 'azure-artifacts'): 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 branches:\n include: [main]\n\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\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 - script: npm ci\n displayName: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine) — written\n # by 'mnci add' to requirements-dev.txt on the first Python project. Plain\n # pip, no uv/Poetry: portable guard skips cleanly on a workspace with none.\n - script: ${PYTHON_INSTALL_GUARD}\n displayName: Install Python dependencies (ruff, pytest, build, twine)\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 # 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 # One verify for every run (PR and main). \\`npm run lint\\` abstracts the chosen\n # linter (eslint via nx, or oxlint), so the pipeline needs no linter branch;\n # Nx cache makes unchanged test/build projects instant.\n - script: npm run lint\n displayName: Lint\n # \\`lint\\` here also runs any Nx lint targets \\`npm run lint\\` does not cover —\n # notably Python's ruff (every Python project owns a hand-authored \\`lint\\`\n # target). For the eslint stack the JS lint runs twice, but Nx caches the\n # repeat instantly.\n - script: npx nx run-many -t lint,test,build\n displayName: Lint, test and build everything\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 PACK_APPS_GUARD},\n * {@link releaseGuard}) — only the provider syntax differs, so the two YAML\n * files can never drift on 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 (agent: string, pythonPublishUrl?: string, registryKind: RegistryConfig['kind'] = 'azure-artifacts'): string {\n const onMain = `github.event_name != 'pull_request' && github.ref_name == 'main'`\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, (name) => `\\${{ secrets.${name} }}`)\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\npermissions:\n # Lets the release step push the version tag nx release creates back to main.\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: 24\n\n - run: npm ci\n name: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine) — written\n # by 'mnci add' to requirements-dev.txt on the first Python project. Plain\n # pip, no uv/Poetry: portable guard skips cleanly on a workspace with none.\n - run: ${PYTHON_INSTALL_GUARD}\n name: Install Python dependencies (ruff, pytest, build, twine)\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 # 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 # One verify for every run (PR and main). \\`npm run lint\\` abstracts the chosen\n # linter (eslint via nx, or oxlint), so the workflow needs no linter branch;\n # Nx cache makes unchanged test/build projects instant.\n - run: npm run lint\n name: Lint\n # \\`lint\\` here also runs any Nx lint targets \\`npm run lint\\` does not cover —\n # notably Python's ruff (every Python project owns a hand-authored \\`lint\\`\n # target). For the eslint stack the JS lint runs twice, but Nx caches the\n # repeat instantly.\n - run: npx nx run-many -t lint,test,build\n name: Lint, test and build everything\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 - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n name: Release — version, tag and publish (npm + Python)\n if: \\${{ ${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 # 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 * 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 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 * 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, `.npmrc`, `commitlint.config.mjs`, the husky\n * `commit-msg` hook and\n * the chosen CI provider's pipeline file(s) — `azure-pipelines.yml` and/or\n * `.github/workflows/ci.yml`, per `options.ci`. Dependency installation\n * (`husky`, `@commitlint/*`) is the caller's job — it shells out to real\n * `npm install` so versions resolve at generation time instead of being\n * 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, and\n // sync.applyChanges (so a stale TS project reference — e.g. from hand-adding\n // a cross-project import — is fixed automatically on the next build/\n // typecheck, not just flagged with a prompt). Both `nx release` and every\n // later `nx g`/`mnci add` see the generator defaults.\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const generators = { ...(nxJson.generators as Record<string, unknown> | undefined), ...generatorDefaults(options.stack) }\n const sync = { ...(nxJson.sync as Record<string, unknown> | undefined), ...SYNC_CONFIG }\n const mnci = { ...(nxJson.mnci as Record<string, unknown> | undefined), ...mnciConfig(options.stack) }\n writeFileEnsured(nxJsonPath, toJson({ ...withReleaseConfig(nxJson), 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 = { ...(manifest.scripts as Record<string, string> | undefined), ...rootScripts(options.stack) }\n const devDependencies = { ...(manifest.devDependencies as Record<string, string> | undefined), ...TS_COMPILER_DEPENDENCIES }\n writeFileEnsured(manifestPath, toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies }))\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 // oxlint is workspace-wide: one config, no per-project Nx lint target. It\n // ships with oxfmt (both from the oxc-standard preset) for the Standard Style\n // formatting half — `npm run format` / `npm run format:check`.\n if (options.stack.linter === 'oxlint') {\n writeFileEnsured(join(workspaceRoot, 'oxlint.config.mts'), OXLINT_CONFIG)\n writeFileEnsured(join(workspaceRoot, 'oxfmt.config.mts'), OXFMT_CONFIG)\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(join(workspaceRoot, 'azure-pipelines.yml'), azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind))\n }\n if (options.ci === 'github' || options.ci === 'both') {\n writeFileEnsured(join(workspaceRoot, '.github/workflows/ci.yml'), githubActionsYaml(options.agent, publishUrl, options.registry.kind))\n }\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_node_path","import_prompts","spawn","value","import_node_path","import_node_path","import_node_path","import_node_path","import_node_fs","import_node_path","ensurePlugin","import_node_path","import_prompts","import_node_path","import_node_path","pythonPublishUrl"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/add.ts","../src/nx.ts","../src/prompts.ts","../src/util/fsx.ts","../src/util/logger.ts","../src/util/names.ts","../src/commands/add/node.ts","../src/commands/add/shared.ts","../src/commands/add/npmLib.ts","../src/commands/add/python.ts","../src/commands/add/reactApp.ts","../src/commands/interactive.ts","../src/commands/new.ts","../src/overlay.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { Argument, Command } from 'commander'\nimport { PROJECT_KINDS, runAdd, type AddOptions, type ProjectKind } from './commands/add'\nimport { runInteractive } from './commands/interactive'\nimport { runNew, type NewOptions } from './commands/new'\nimport { logger } from './util/logger'\n\n/** Reads the CLI version from the packaged package.json (next to dist/). */\nfunction readVersion (): string {\n try {\n const package_ = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')) as { version?: string }\n return package_.version ?? '0.0.0'\n } catch {\n return '0.0.0'\n }\n}\n\n/**\n * Builds the commander program for the CLI.\n *\n * @remarks\n * Two commands only — `new` and `add`. Everything a generated repo needs\n * day-to-day (build/test/lint/release) is plain Nx, so the CLI deliberately\n * has no wrapper commands for those.\n *\n * @param None - this function takes no parameters.\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 (): Command {\n const program = new Command()\n\n program\n .name('mnci')\n .description('MoNecromanCI — a thin CLI over official Nx plugins')\n .version(readVersion(), '-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('--agent <pool>', 'CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name')\n .option('--variable-group <name>', 'Azure DevOps variable group holding the npm PAT')\n .option('--ci <provider>', 'CI provider: azure | github | both')\n .option('--linter <linter>', 'linter: eslint | oxlint')\n .option('--test-runner <runner>', 'unit-test runner: jest | vitest')\n .action(async (name: string | undefined, options: NewOptions) => {\n await runNew(name, options)\n })\n\n program\n .command('add')\n // choices() rejects an unrecognized kind with a clean commander usage\n // error (and lists the valid ones in --help) before runAdd ever runs —\n // instead of a typo silently doing nothing but reporting \"success\".\n .addArgument(new Argument('[kind]', 'project kind').choices(PROJECT_KINDS))\n .argument('[name]', 'project name')\n .description('Add a project by delegating to the matching Nx plugin generator')\n .option('--scope <scope>', 'npm scope for a publishable lib (defaults to @<workspace name>)')\n .action(async (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions) => {\n await runAdd(kind, name, options)\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 *\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 await buildProgram().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.\nif (require.main === module) {\n main()\n}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { runNx, runShell } from '../nx'\nimport { promptText } from '../prompts'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../util/fsx'\nimport { logger } from '../util/logger'\nimport { assertValidProjectName } from '../util/names'\nimport { addNodeApp, addNodeFunctionApp } from './add/node'\nimport { addNpmLib } from './add/npmLib'\nimport { addPythonApp, addPythonFunctionApp, addPythonInternalLib, addPythonLib } from './add/python'\nimport { addReactApp } from './add/reactApp'\nimport type { AddOptions, WorkspaceStack } from './add/shared'\n\nexport type { AddOptions } from './add/shared'\n\n/**\n * The project kinds this CLI can add — deliberately just nine.\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/node.ts`, `add/npmLib.ts` and `add/python.ts`\n * (internal-lib is small enough to stay inline below).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport type ProjectKind\n = | 'react-app' | 'node-app' | 'node-function-app' | 'npm-lib' | 'internal-lib'\n | 'python-app' | 'python-function-app' | 'python-lib' | 'python-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. TS/JS\n * kinds first, then the Python family.\n */\nexport const PROJECT_KINDS: ProjectKind[] = [\n 'react-app', 'node-app', 'node-function-app', 'npm-lib', 'internal-lib',\n 'python-app', 'python-function-app', 'python-lib', 'python-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 (kind: ProjectKind | undefined, name: string | undefined, options: AddOptions): 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 = kind ?? 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 'node-app': {\n addNodeApp(workspaceRoot, resolvedName, stack)\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 'g', '@nx/js:lib', `libs/${resolvedName}`,\n '--bundler=tsc',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--no-interactive',\n ], workspaceRoot)\n markPrivate(join(workspaceRoot, 'libs', resolvedName, 'package.json'))\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 default: {\n // Unreachable while every ProjectKind has a case above: `exhaustive`\n // being `never` makes adding a 9th 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(`Unknown project kind '${exhaustive as string}'. Expected one of: ${PROJECT_KINDS.join(', ')}.`)\n }\n }\n\n syncProjectReferences(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('nx sync did not complete — run `npx nx sync` yourself so cross-project imports resolve in your editor.')\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?: { linter?: string, testRunner?: string } } }>(join(workspaceRoot, 'nx.json'))\n const stack = nxJson.mnci?.stack\n return {\n linter: stack?.linter === 'oxlint' ? 'none' : 'eslint',\n testRunner: stack?.testRunner === 'vitest' ? 'vitest' : 'jest',\n }\n}\n\n/**\n * Sets `\"private\": true` in a package manifest.\n *\n * @remarks\n * One of the deliberate post-generation touches: it makes internal\n * libraries structurally unpublishable, no matter what future config drifts.\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 */\nfunction markPrivate (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, private: true }))\n}\n","import spawn from 'cross-spawn'\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","import { 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 for the stack: linter and unit-test runner.\n *\n * @remarks\n * The knobs asked up front at `mnci new`. TypeScript is fixed (the\n * `--preset=ts` premise, pinned to the TS 6 that Nx 23 supports), so only the\n * linter and test runner are asked. Each is a binary choice — no \"none\" — with\n * the current opinionated default listed first.\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 linter = await select<StackConfig['linter']>({\n message: 'Linter',\n choices: [\n { name: 'ESLint', value: 'eslint' },\n { name: 'Oxlint', value: 'oxlint' },\n ],\n })\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 { linter, 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 * 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 * 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","/**\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 kinds, once hyphens become underscores — a Python module identifier.\n * A conservative charset makes it safe in all three roles at once: lowercase\n * letters, digits and hyphens, starting with a letter.\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 or contains a character outside\n * `[a-z0-9-]`, or doesn't start with a lowercase letter.\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-]*$/.test(name)) {\n throw new Error(`${label} '${name}' is invalid — use lowercase letters, digits and hyphens, starting with a letter (e.g. 'my-project').`)\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 { addNxTargets, ensureAdmZip, hasPlugin, type WorkspaceStack } 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 — `esbuild` (non-bundled: it transpiles each file and\n * mirrors the workspace tree into `dist`, rather than producing one bundled\n * file) is the generator's own default, and `--framework=none` keeps the\n * scaffold a bare Node app (no Express/Fastify/Koa/Nest opinion). Both\n * `node-app` and `node-function-app` generate identically — 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 * @returns Nothing.\n * @throws Error when the generator exits non-zero.\n * @typeParam None - this function has no generic type parameters.\n */\nfunction runNodeApp (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n ensurePlugin(workspaceRoot, '@nx/node')\n runNx([\n 'g', '@nx/node:application', `apps/${name}`,\n '--bundler=esbuild',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--e2eTestRunner=none',\n '--framework=none',\n '--no-interactive',\n ], workspaceRoot)\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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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) — no\n * custom 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 * @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 (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n runNodeApp(workspaceRoot, name, stack)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(workspaceRoot, 'apps', name, 'package.json'), { package: nodeAppPackageTarget(name) })\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 (runShell('npm', ['install', '@azure/functions', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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, 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 the deployable\n * manifest's `main` is simply `main.js`, sitting beside that shim once the\n * `package` target zips `dist` + `host.json` + `package.json` together (see\n * {@link nodeFunctionAppPackageTarget}). `@azure/functions` is added for\n * real (not just referenced): Azure's deploy-time Oryx build reads this\n * manifest's `dependencies` and runs `npm install` there — verified\n * empirically that a plain `npm install` in a simulated deploy folder (no\n * bundled `node_modules`) resolves and runs correctly once the dependency is\n * declared, mirroring exactly how `python-function-app` already relies on\n * Azure's Python Oryx build 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 }>(join(workspaceRoot, 'node_modules/@azure/functions/package.json')).version\n const dependencies = (manifest.dependencies as Record<string, string> | undefined) ?? {}\n writeFileEnsured(manifestPath, toJson({\n ...manifest,\n main: 'main.js',\n dependencies: { ...dependencies, '@azure/functions': `^${azureFunctionsVersion}` },\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) together with `host.json` and the repaired\n * `package.json` into `dist/drop/node-function-app-<name>.zip`. No\n * `node_modules` bundled: Azure's Oryx build installs real dependencies from\n * the zipped `package.json` at deploy time (see\n * {@link repairNodeFunctionAppManifest}). Basename exactly\n * `node-function-app-<name>` (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');z.addLocalFile('${root}/host.json');z.addLocalFile('${root}/package.json');z.writeZip('${zip}')\"`\n return {\n executor: 'nx:run-commands',\n dependsOn: ['build'],\n // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\n outputs: [`{workspaceRoot}/${zip}`],\n options: { command },\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`).\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 (workspaceRoot: string, name: string, stack: WorkspaceStack): void {\n runNodeApp(workspaceRoot, name, stack)\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(join(nodeFunctionAppRoot, 'src/functions/greeting.ts'), NODE_FUNCTION_APP_GREETING)\n writeFileEnsured(join(nodeFunctionAppRoot, 'src/functions/greeting.spec.ts'), NODE_FUNCTION_APP_GREETING_SPEC)\n writeFileEnsured(join(nodeFunctionAppRoot, 'host.json'), NODE_FUNCTION_APP_HOST_JSON)\n repairNodeFunctionAppManifest(nodeFunctionAppRoot, workspaceRoot)\n ensureAdmZip(workspaceRoot)\n addNxTargets(join(nodeFunctionAppRoot, 'package.json'), { package: nodeFunctionAppPackageTarget(name) })\n}\n","import { join } from 'node:path'\nimport { runShell } from '../../nx'\nimport { fileExists, readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\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}\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 * `linter` is the value passed straight to `@nx/*` generators — `eslint`, or\n * `none` when the workspace chose oxlint (oxlint is not an Nx linter).\n *\n * @typeParam None - this interface has no generic type parameters.\n */\nexport interface WorkspaceStack {\n linter: 'eslint' | 'none'\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<{ dependencies?: Record<string, string>, devDependencies?: Record<string, string> }>(join(workspaceRoot, 'package.json'))\n const installed = { ...manifest.dependencies, ...manifest.devDependencies }\n return Object.hasOwn(installed, packageName)\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 (runShell('npm', ['install', '--save-dev', 'adm-zip', '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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(manifestPath, toJson({ ...manifest, nx: { ...nx, targets: { ...targets, ...newTargets } } }))\n}\n","import { join } from 'node:path'\nimport { runNx } from '../../nx'\nimport { promptText } from '../../prompts'\nimport { readJson, toJson, writeFileEnsured } from '../../util/fsx'\nimport { defaultScope, type AddOptions, type WorkspaceStack } from './shared'\n\n/**\n * The per-npm-lib ESLint config written over the generator's default.\n *\n * @remarks\n * Identical to what `@nx/js:lib --bundler=rollup` generates, plus ONE\n * addition: `@nx/dependency-checks` gets an `ignoredDependencies` list of\n * every `private: true` workspace package, computed at lint time. Private\n * libs are compiled INTO the rollup bundle and never declared in the\n * manifest (a consumer could not install them) — without this, the rule\n * flags every internal-lib import as a missing dependency. Because the list\n * is computed, adding a new internal lib never requires touching this file.\n */\nexport const NPM_LIB_ESLINT_CONFIG = `import { globSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport baseConfig from '../../eslint.config.mjs';\n\n// Private workspace libs are compiled INTO this package's rollup bundle and\n// never declared in the manifest (a consumer could not install them), so the\n// dependency check must ignore them. Computed at lint time: adding a new\n// internal lib never requires touching this file.\nconst workspaceRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');\nconst privateWorkspacePackages = globSync(['libs/*/package.json', 'packages/*/package.json'], { cwd: workspaceRoot })\n .map((manifestPath) => JSON.parse(readFileSync(join(workspaceRoot, manifestPath), 'utf8')))\n .filter((manifest) => manifest.private === true)\n .map((manifest) => manifest.name);\n\nexport default [\n ...baseConfig,\n {\n files: ['**/*.json'],\n rules: {\n '@nx/dependency-checks': [\n 'error',\n {\n ignoredDependencies: privateWorkspacePackages,\n ignoredFiles: [\n '{projectRoot}/eslint.config.{js,cjs,mjs,ts,cts,mts}',\n '{projectRoot}/rollup.config.{js,ts,mjs,mts,cjs,cts}',\n ],\n },\n ],\n },\n languageOptions: {\n parser: await import('jsonc-eslint-parser'),\n },\n },\n {\n ignores: ['**/out-tsc'],\n },\n];\n`\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 linter/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 (workspaceRoot: string, name: string, options: AddOptions, kindProvided: boolean, stack: WorkspaceStack): Promise<void> {\n const scope = options.scope ?? (kindProvided\n ? defaultScope(workspaceRoot)\n : await promptText('npm scope for the published package', defaultScope(workspaceRoot)))\n runNx([\n 'g', '@nx/js:lib', `packages/${name}`,\n '--publishable',\n `--importPath=${scope}/${name}`,\n '--bundler=rollup',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--no-interactive',\n ], workspaceRoot)\n markPublic(join(workspaceRoot, 'packages', name, 'package.json'))\n // The dependency-check override is an ESLint config; oxlint has no such\n // rule, so it only applies when ESLint is the chosen linter.\n if (stack.linter === 'eslint') {\n writeFileEnsured(join(workspaceRoot, 'packages', name, 'eslint.config.mjs'), NPM_LIB_ESLINT_CONFIG)\n }\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 npm-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\n * dry-run surfaces, since dry-runs never call the registry. This is the one\n * post-generation touch that makes a freshly added npm-lib publishable\n * as-is.\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 */\nfunction markPublic (manifestPath: string): void {\n const manifest = readJson<Record<string, unknown>>(manifestPath)\n writeFileEnsured(manifestPath, toJson({ ...manifest, publishConfig: { access: 'public' } }))\n}\n","import { 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 } 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 = runShell('python3', ['--version'], workspaceRoot) === 0 || runShell('python', ['--version'], workspaceRoot) === 0\n if (!hasPython) {\n throw new Error('Python not found. Install Python 3.9+ first: https://www.python.org/downloads/')\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 (runShell('npm', ['install', '--save-dev', spec, '--no-audit', '--no-fund'], workspaceRoot) !== 0) {\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. 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\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 `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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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 (name: string, moduleDirectory: string): 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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\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(['g', '@mnci/nx-python-pip:application', name, `--directory=apps/${name}`, '--no-interactive'], workspaceRoot)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), { package: pythonAppPackageTarget(name) })\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(['g', '@mnci/nx-python-pip:function-application', name, `--directory=apps/${name}`, '--no-interactive'], workspaceRoot)\n const moduleDirectory = pythonModuleDirectory(name)\n addProjectJsonTargets(join(workspaceRoot, 'apps', name, 'project.json'), { package: pythonFunctionAppPackageTarget(name, moduleDirectory) })\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(['g', '@mnci/nx-python-pip:library', name, `--directory=python-packages/${name}`, '--no-interactive'], workspaceRoot)\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(['g', '@mnci/nx-python-pip:internal-library', name, `--directory=libs/${name}`, '--no-interactive'], workspaceRoot)\n}\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { runNx } from '../../nx'\nimport { fileExists, writeFileEnsured } from '../../util/fsx'\nimport { logger } from '../../util/logger'\nimport { addNxTargets, ensureAdmZip, hasPlugin, type WorkspaceStack } 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.\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 */\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 * 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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} is an Nx output token\n outputs: [`{workspaceRoot}/apps/${name}/dist-${environment}`],\n options: { command: `vite build --mode ${environment} --outDir dist-${environment}`, cwd: `apps/${name}` },\n }\n }\n const zipStatements = REACT_ENVIRONMENTS\n .map((environment) => `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 // eslint-disable-next-line unicorn/no-incorrect-template-string-interpolation -- {workspaceRoot} tokens, not JS interpolation\n outputs: REACT_ENVIRONMENTS.map((environment) => `{workspaceRoot}/dist/drop/react-app-${name}-${environment}.zip`),\n options: { command: `node -e \"const fs=require('node:fs');fs.mkdirSync('dist/drop',{recursive:true});const A=require('adm-zip');let z;${zipStatements}\"` },\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 = '# 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 'g', '@nx/react:app', `apps/${name}`,\n '--bundler=vite',\n `--unitTestRunner=${stack.testRunner}`,\n `--linter=${stack.linter}`,\n '--style=css',\n '--e2eTestRunner=none',\n '--no-interactive',\n ], workspaceRoot)\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}\n","import { join } from 'node:path'\nimport { select } from '@inquirer/prompts'\nimport { fileExists } from '../util/fsx'\nimport { runAdd } from './add'\nimport { runNew } from './new'\n\n/** The two things the wizard can start: scaffold a workspace, or add to one. */\ntype InteractiveAction = 'new' | 'add'\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 two commands, then hands off to the\n * existing flows — {@link runNew} and {@link runAdd} — which already prompt for\n * every field they need (name/scope/registry/agent/variable group for `new`;\n * kind/name, and the npm-lib scope on this bare path, for `add`). Nothing about\n * the prompting lives here, so the wizard and the flag-driven commands can\n * never drift.\n *\n * The menu is ordered by context: inside a workspace (an `nx.json` in the cwd)\n * `add` is offered first, otherwise `new` — so the default highlight matches\n * what the user most likely wants. Both are always shown; picking `add`\n * outside a workspace surfaces {@link runAdd}'s clear \"run from the workspace\n * 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 newChoice = { name: 'Create a new monorepo', value: 'new' as const }\n const addChoice = { name: 'Add a project to this workspace', value: 'add' as const }\n\n const action = await select<InteractiveAction>({\n message: 'What would you like to do?',\n choices: inWorkspace ? [addChoice, newChoice] : [newChoice, addChoice],\n })\n\n if (action === 'new') {\n await runNew(undefined, {})\n } else {\n await runAdd(undefined, undefined, {})\n }\n}\n","import { join } from 'node:path'\nimport { runNpx, runShell } from '../nx'\nimport { applyOverlay, DEFAULT_STACK, type CiProvider, type RegistryConfig, type StackConfig } from '../overlay'\nimport { promptCi, 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 /** Linter (`eslint` or `oxlint`). */\n linter?: StackConfig['linter']\n /** Unit-test runner (`jest` or `vitest`). */\n testRunner?: StackConfig['testRunner']\n}\n\n/**\n * Resolves the stack from flags, prompts, or `--yes` defaults.\n *\n * @remarks\n * Any of the three knobs passed as a flag is taken as-is; if all three are\n * passed (or `--yes` is set) nothing is prompted. Otherwise the interactive\n * {@link promptStack} runs and its answers fill the gaps.\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 const fromFlags = { linter: options.linter, testRunner: options.testRunner }\n const complete = Boolean(fromFlags.linter && fromFlags.testRunner)\n if (complete || options.yes) {\n return {\n linter: fromFlags.linter ?? DEFAULT_STACK.linter,\n testRunner: fromFlags.testRunner ?? DEFAULT_STACK.testRunner,\n }\n }\n const prompted = await promptStack()\n return {\n linter: fromFlags.linter ?? prompted.linter,\n testRunner: fromFlags.testRunner ?? prompted.testRunner,\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 = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText('npm scope for publishable packages', `@${workspaceName}`))\n const registry = await resolveRegistry(options)\n const ci = await resolveCi(options)\n const agent = options.agent ?? (options.yes ? 'ubuntu-latest' : await promptText('CI build agent/runner (vmImage, GitHub Actions runner label, or self-hosted pool name)', 'ubuntu-latest'))\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 = ci === 'github'\n ? (options.variableGroup ?? 'Build')\n : options.variableGroup ?? (options.yes ? 'Build' : await promptText('Azure DevOps variable group holding the npm PAT', 'Build'))\n const stack = await resolveStack(options)\n\n logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`)\n runNpx([\n '--yes',\n 'create-nx-workspace@latest',\n workspaceName,\n '--preset=ts',\n '--pm=npm',\n '--nxCloud=skip',\n '--no-interactive',\n ], process.cwd())\n\n const workspaceRoot = join(process.cwd(), workspaceName)\n\n logger.step('Applying MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)')\n applyOverlay(workspaceRoot, { scope, registry, agent, variableGroup, ci, stack })\n\n // Install the commit toolchain (and, for the oxlint stack, oxc-standard —\n // which brings oxlint + oxfmt and the JavaScript Standard Style preset the\n // generated oxlint.config.mts / oxfmt.config.mts reference; ESLint is set up\n // by the Nx generators on first `add`). One install.\n const stackDependencies = stack.linter === 'oxlint' ? ['oxc-standard'] : []\n logger.step(`Installing the toolchain (${stack.linter === 'oxlint' ? 'oxc-standard' : 'eslint'}, commit toolchain)`)\n const installStatus = runShell('npm', ['install', '--save-dev', ...stackDependencies, 'husky', '@commitlint/cli', '@commitlint/config-conventional'], workspaceRoot)\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 logger.success('Done. Next steps:')\n logger.info(` cd ${workspaceName}`)\n logger.info(' mnci add react-app web # or: node-app, node-function-app, npm-lib, internal-lib,')\n logger.info(' # python-app, python-function-app, python-lib, python-internal-lib')\n logger.info(' git add -A && git commit -m \"feat: initial workspace\"')\n}\n","import { join } from 'node:path'\nimport { markExecutable, 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. So the two real knobs are the\n * linter and the unit-test runner, persisted as Nx **generator defaults** in\n * `nx.json` (the oxlint case also writes a workspace `oxlint.config.mts` and\n * points the root `lint` script at `oxlint`).\n *\n * @typeParam None - this type has no generic type parameters.\n */\nexport interface StackConfig {\n /** Linter: Nx-native `eslint`, or workspace-wide `oxlint`. */\n linter: 'eslint' | 'oxlint'\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 * ESLint and Jest: the combination existing generated repos (and the e2e\n * suite) already assume, so defaulting to it keeps behaviour unchanged when\n * the stack is not chosen explicitly.\n */\nexport const DEFAULT_STACK: StackConfig = { linter: 'eslint', 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 * Scope-to-registry routing is what guarantees packages never land on the\n * public npm registry by accident: every publishable package is named under\n * `scope`, and the scoped registry line routes all of them to the configured\n * feed.\n *\n * Azure Artifacts is authenticated with the **base64-encoded PAT** the feed's\n * \"Connect to feed → npm\" instructions produce — the classic `_password`\n * block, one pair for the `/npm/registry/` path (install) and one for the feed\n * root (publish). The PAT *value* never lands in the file: `${PAT}` stays\n * literal on disk and is expanded at runtime — in CI from the `Build` variable\n * group's `PAT`, locally from an exported `PAT=<base64 token>`. (No\n * `npmAuthenticate@0` task: it would overwrite this hand-set password.) Public\n * npm keeps the raw `_authToken` an automation token uses.\n *\n * @param registry - The monorepo's resolved registry configuration.\n * @param scope - The npm scope (e.g. `@demo`) the scoped registry applies to.\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 const scopeName = scope.replace(/^@/, '')\n const lines = [\n 'registry=https://registry.npmjs.org/',\n '; Community Nx plugins (e.g. the Azure Functions one) peer on the previous',\n '; Nx major for a while after each release; accept the resolved tree.',\n 'legacy-peer-deps=true',\n ]\n\n if (registry.kind === 'azure-artifacts') {\n const base = `https://pkgs.dev.azure.com/${registry.organization}/${registry.project}/_packaging/${registry.artifactsFeed}`\n const registryHost = `${base}/npm/registry/`.replace(/^https:\\/\\//, '')\n const feedHost = `${base}/`.replace(/^https:\\/\\//, '')\n lines.push(\n `@${scopeName}:registry=${base}/npm/registry/`,\n '; Azure Artifacts auth — the base64 PAT is expanded at runtime from the',\n '; PAT env var (CI: your PAT variable group; locally: export PAT=<base64 token>).',\n `//${registryHost}:username=${registry.organization}`,\n `//${registryHost}:_password=\\${PAT}`,\n `//${registryHost}:email=npm@example.com`,\n `//${feedHost}:username=${registry.organization}`,\n `//${feedHost}:_password=\\${PAT}`,\n `//${feedHost}:email=npm@example.com`,\n )\n } else {\n lines.push(`//registry.npmjs.org/:_authToken=\\${NODE_AUTH_TOKEN}`)\n }\n\n lines.push('')\n return lines.join('\\n')\n}\n\n/**\n * 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. Changelog files are disabled because they would be\n * unpushable under the tag-only model.\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 * `push: false` here is deliberate, not an oversight: the combined\n * `nx release` command's own final push (`release.js`, after tagging) only\n * runs when a remote GitHub/GitLab Release is being created\n * (`shouldCreateWorkspaceRemoteRelease`), which this config never enables —\n * so that push never fires. The *only* push Nx does run is an internal one\n * buried inside the version step, which happens **before** the tag exists\n * (tagging is deliberately suppressed there and done later, by `release.js`\n * itself) — so even with `pushArgs: '--tags'` that earlier push has no new\n * tag to push yet, and the tag is left local-only (verified empirically\n * against a real CI run: the release step and publish both report success,\n * \"Pushing to git remote\" logs *before* \"Tagging commit with git\", and the\n * new tag never reaches `origin`, even though the version it names does\n * reach npm — a silent, permanent mismatch that makes every subsequent\n * release recompute and re-skip the same already-published version forever).\n * Disabling Nx's own push entirely and pushing tags explicitly, once, after\n * tagging is guaranteed to have happened (the CI step below), sidesteps this\n * ordering bug rather than fighting it.\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.\n * Internal libraries live in `libs/` and apps in `apps/`, so release scoping\n * still needs no custom tags.\n */\nexport const RELEASE_CONFIG = {\n projectsRelationship: 'independent',\n projects: ['packages/*', 'python-packages/*'],\n releaseTag: { pattern: '{projectName}@{version}' },\n git: { commit: false, tag: true, push: false },\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: { workspaceChangelog: false },\n} as const\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 * 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 * @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 (nxJson: Record<string, unknown>): Record<string, unknown> {\n return { ...nxJson, defaultBase: 'main', release: RELEASE_CONFIG }\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 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 */\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 affected: 'nx affected -t lint,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 `lint` bound to the chosen linter.\n *\n * @remarks\n * ESLint is a per-project Nx target (`nx run-many -t lint`); oxlint is a single\n * workspace-wide binary, so its `lint` is just `oxlint`. Everything downstream\n * (the CI pipeline) calls `npm run lint`, so it never needs to know which\n * linter was chosen.\n *\n * The oxlint stack also gets the oxc-standard formatter as `format` (write) and\n * `format:check` (CI-safe, no writes) — the JavaScript Standard Style *layout*\n * half that oxlint (a linter) does not enforce. The `-c oxfmt.config.mts` flag\n * is explicit because oxfmt (0.48) auto-discovers `.oxfmtrc.json` but not the\n * typed `.mts` config, and the whole stack is committed to typed configs.\n * ESLint workspaces keep Nx's own formatting story, so they get no `format`.\n *\n * @param stack - The chosen stack.\n * @returns The root scripts object to stamp into the manifest.\n * @throws Never - pure mapping.\n * @typeParam None - this function has no generic type parameters.\n */\nexport function rootScripts (stack: StackConfig): Record<string, string> {\n if (stack.linter === 'oxlint') {\n return {\n ...ROOT_SCRIPTS,\n lint: 'oxlint',\n format: 'oxfmt -c oxfmt.config.mts .',\n 'format:check': 'oxfmt -c oxfmt.config.mts --check .',\n }\n }\n return { ...ROOT_SCRIPTS }\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 linter/runner automatically. oxlint is not an\n * Nx linter, so it maps to `linter: 'none'` — the workspace `oxlint.config.mts`\n * + the `oxlint` root script cover linting instead.\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 = { linter: stack.linter === 'oxlint' ? 'none' : 'eslint', unitTestRunner: stack.testRunner }\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 chosen stack.\n *\n * @remarks\n * The single source of truth `mnci add` reads back (`readWorkspaceStack` in\n * `add.ts`) — deliberately separate from {@link generatorDefaults}, which\n * serves Nx's own generator-default mechanism instead (a real, independent\n * feature: it makes a user's own direct `nx g` pick up the right defaults\n * too). One value, one place `add` trusts, with no invariant to keep in sync.\n *\n * @param stack - The chosen stack.\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 (stack: StackConfig): Record<string, unknown> {\n return { stack: { linter: stack.linter, testRunner: stack.testRunner } }\n}\n\n/**\n * The `oxlint.config.mts` written when oxlint is the chosen linter.\n *\n * @remarks\n * A typed config (`defineConfig` from `oxlint`, auto-detected — needs Node ≥22)\n * rather than JSON. Instead of hand-listing plugins and rules it **extends the\n * [oxc-standard](https://github.com/JohnDeved/ox-standard) preset** — the real\n * JavaScript Standard Style rule set for the oxc toolchain (unicorn + React +\n * react-perf + TypeScript + oxc, `correctness: error`, `suspicious: warn`,\n * `style: off`). A `.mts` config's `extends` must hold **config objects, not\n * paths** (unlike `.oxlintrc.json`, which cannot import from a package), so the\n * preset is imported as JSON and passed in — the pattern oxlint documents for\n * extending a shared package's config.\n *\n * The one local override keeps a freshly generated workspace green: Nx scaffolds\n * with the modern JSX transform, so React need not be in scope and the preset's\n * `react/react-in-jsx-scope` correctness rule is turned off. Add further project\n * overrides in the same `rules` block.\n *\n * Its formatting counterpart is {@link OXFMT_CONFIG} (Standard Style layout).\n */\nexport const OXLINT_CONFIG = `import { defineConfig } from 'oxlint'\nimport standard from 'oxc-standard/.oxlintrc.json' with { type: 'json' }\n\n// Generated by MoNecromanCI. JavaScript Standard Style linting via the\n// oxc-standard preset (unicorn + React + react-perf + TypeScript + oxc).\n// Add project-specific rule overrides in the \\`rules\\` block below.\nexport default defineConfig({\n extends: [standard],\n rules: {\n // The modern JSX transform (Nx's default) needs no React import in scope.\n 'react/react-in-jsx-scope': 'off',\n },\n})\n`\n\n/**\n * The `oxfmt.config.mts` written alongside {@link OXLINT_CONFIG} for oxlint.\n *\n * @remarks\n * oxfmt is the oxc toolchain's formatter — the *layout* half oxlint (a linter)\n * does not enforce. These options mirror oxc-standard's own `.oxfmtrc.json` so\n * formatting and linting agree on JavaScript Standard Style: no semicolons,\n * single quotes, 2-space indent, `es5` trailing commas, and parens omitted on\n * single-argument arrows. (oxfmt has no `extends`, so the preset's values are\n * inlined — exactly how oxc-standard itself scaffolds them.)\n *\n * `npm run format` writes; `npm run format:check` verifies without writing\n * ({@link rootScripts}) — both pass `-c oxfmt.config.mts` since oxfmt (0.48)\n * auto-discovers only `.oxfmtrc.json`, not the typed config. Nx generators emit\n * semicolon/double-quote code, so a new workspace is normalised on the first\n * `npm run format`.\n */\nexport const OXFMT_CONFIG = `import { defineConfig } from 'oxfmt'\n\n// Generated by MoNecromanCI. JavaScript Standard Style formatting, matching the\n// oxc-standard lint preset: no semicolons, single quotes, 2-space indent.\nexport default defineConfig({\n singleQuote: true,\n semi: false,\n printWidth: 100,\n tabWidth: 2,\n trailingComma: 'es5',\n arrowParens: 'avoid',\n})\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 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 (registryKind: RegistryConfig['kind'], variableReference: (name: string) => string): [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 (agent: string, variableGroup: string, pythonPublishUrl?: string, registryKind: RegistryConfig['kind'] = 'azure-artifacts'): 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 branches:\n include: [main]\n\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\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 - script: npm ci\n displayName: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine) — written\n # by 'mnci add' to requirements-dev.txt on the first Python project. Plain\n # pip, no uv/Poetry: portable guard skips cleanly on a workspace with none.\n - script: ${PYTHON_INSTALL_GUARD}\n displayName: Install Python dependencies (ruff, pytest, build, twine)\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 # 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 # One verify for every run (PR and main). \\`npm run lint\\` abstracts the chosen\n # linter (eslint via nx, or oxlint), so the pipeline needs no linter branch;\n # Nx cache makes unchanged test/build projects instant.\n - script: npm run lint\n displayName: Lint\n # \\`lint\\` here also runs any Nx lint targets \\`npm run lint\\` does not cover —\n # notably Python's ruff (every Python project owns a hand-authored \\`lint\\`\n # target). For the eslint stack the JS lint runs twice, but Nx caches the\n # repeat instantly.\n - script: npx nx run-many -t lint,test,build\n displayName: Lint, test and build everything\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 PACK_APPS_GUARD},\n * {@link releaseGuard}) — only the provider syntax differs, so the two YAML\n * files can never drift on 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 (agent: string, pythonPublishUrl?: string, registryKind: RegistryConfig['kind'] = 'azure-artifacts'): string {\n const onMain = `github.event_name != 'pull_request' && github.ref_name == 'main'`\n const [npmAuthName, npmAuthValue] = npmAuthEnvVariable(registryKind, (name) => `\\${{ secrets.${name} }}`)\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\npermissions:\n # Lets the release step push the version tag nx release creates back to main.\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: 24\n\n - run: npm ci\n name: Install dependencies\n env:\n ${npmAuthName}: ${npmAuthValue}\n\n # Installs the fixed Python toolchain (ruff, pytest, build, twine) — written\n # by 'mnci add' to requirements-dev.txt on the first Python project. Plain\n # pip, no uv/Poetry: portable guard skips cleanly on a workspace with none.\n - run: ${PYTHON_INSTALL_GUARD}\n name: Install Python dependencies (ruff, pytest, build, twine)\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 # 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 # One verify for every run (PR and main). \\`npm run lint\\` abstracts the chosen\n # linter (eslint via nx, or oxlint), so the workflow needs no linter branch;\n # Nx cache makes unchanged test/build projects instant.\n - run: npm run lint\n name: Lint\n # \\`lint\\` here also runs any Nx lint targets \\`npm run lint\\` does not cover —\n # notably Python's ruff (every Python project owns a hand-authored \\`lint\\`\n # target). For the eslint stack the JS lint runs twice, but Nx caches the\n # repeat instantly.\n - run: npx nx run-many -t lint,test,build\n name: Lint, test and build everything\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 - run: ${releaseGuard(pythonPublishEnvFragment(pythonPublishUrl))}\n name: Release — version, tag and publish (npm + Python)\n if: \\${{ ${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 # 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 * 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 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 * 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, `.npmrc`, `commitlint.config.mjs`, the husky\n * `commit-msg` hook and\n * the chosen CI provider's pipeline file(s) — `azure-pipelines.yml` and/or\n * `.github/workflows/ci.yml`, per `options.ci`. Dependency installation\n * (`husky`, `@commitlint/*`) is the caller's job — it shells out to real\n * `npm install` so versions resolve at generation time instead of being\n * 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, and\n // sync.applyChanges (so a stale TS project reference — e.g. from hand-adding\n // a cross-project import — is fixed automatically on the next build/\n // typecheck, not just flagged with a prompt). Both `nx release` and every\n // later `nx g`/`mnci add` see the generator defaults.\n const nxJsonPath = join(workspaceRoot, 'nx.json')\n const nxJson = readJson<Record<string, unknown>>(nxJsonPath)\n const generators = { ...(nxJson.generators as Record<string, unknown> | undefined), ...generatorDefaults(options.stack) }\n const sync = { ...(nxJson.sync as Record<string, unknown> | undefined), ...SYNC_CONFIG }\n const mnci = { ...(nxJson.mnci as Record<string, unknown> | undefined), ...mnciConfig(options.stack) }\n writeFileEnsured(nxJsonPath, toJson({ ...withReleaseConfig(nxJson), 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 = { ...(manifest.scripts as Record<string, string> | undefined), ...rootScripts(options.stack) }\n const devDependencies = { ...(manifest.devDependencies as Record<string, string> | undefined), ...TS_COMPILER_DEPENDENCIES }\n writeFileEnsured(manifestPath, toJson({ ...manifest, name: `${options.scope}/source`, scripts, devDependencies }))\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 // oxlint is workspace-wide: one config, no per-project Nx lint target. It\n // ships with oxfmt (both from the oxc-standard preset) for the Standard Style\n // formatting half — `npm run format` / `npm run format:check`.\n if (options.stack.linter === 'oxlint') {\n writeFileEnsured(join(workspaceRoot, 'oxlint.config.mts'), OXLINT_CONFIG)\n writeFileEnsured(join(workspaceRoot, 'oxfmt.config.mts'), OXFMT_CONFIG)\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(join(workspaceRoot, 'azure-pipelines.yml'), azurePipelinesYaml(options.agent, options.variableGroup, publishUrl, options.registry.kind))\n }\n if (options.ci === 'github' || options.ci === 'both') {\n writeFileEnsured(join(workspaceRoot, '.github/workflows/ci.yml'), githubActionsYaml(options.agent, publishUrl, options.registry.kind))\n }\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_node_path","import_prompts","spawn","value","import_node_path","import_node_path","import_node_path","import_node_path","import_node_fs","import_node_path","ensurePlugin","import_node_path","import_prompts","import_node_path","import_node_path","pythonPublishUrl"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnci/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "MoNecromanCI CLI — a thin CLI over official and community Nx plugins: create an opinionated Nx monorepo (interactive wizard or flags; ESLint/oxlint+Prettier-equivalent oxfmt, Jest/Vitest) and add React apps, Node apps, Azure Function apps (Node or Python), npm/Python libraries and internal libraries, with automatic conventional-commit versioning (tag-only, nothing pushed to main) for both npm and Python packages.",
5
5
  "bin": {
6
6
  "mnci": "./dist/cli.js"