@mnci/cli 1.12.0 → 1.12.2

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
@@ -15,14 +15,14 @@ config package, a custom CI engine, a dependency-injection step for published
15
15
  packages, a doctor/drift-sync system to keep it all consistent — already has a
16
16
  first-party (or established community) Nx equivalent:
17
17
 
18
- | Hand-rolled elsewhere | This CLI uses instead |
19
- | ------------------------------------------ | --------------------------------------------------------------------------- |
20
- | Template engine + per-project config files | `create-nx-workspace --preset=ts` + `nx g` plugin generators |
21
- | A shared toolchain package for configs | The configs the Nx generators emit (one root ESLint/tsconfig) |
22
- | A custom multi-step CI engine | `nx affected -t lint,test,build` + `nx release` (~60-line pipeline) |
23
- | A dist-package dependency injector | `nx release` updates dependent versions natively |
24
- | Hand-written Azure Function templates | `@nx/node:application` (plain Node app) + a thin Azure Functions v4 overlay |
25
- | doctor/drift sync of tool-owned files | Nothing to drift: this CLI owns 5 small files, Nx owns the rest |
18
+ | Hand-rolled elsewhere | This CLI uses instead |
19
+ | ------------------------------------------ | ----------------------------------------------------------------------------- |
20
+ | Template engine + per-project config files | `create-nx-workspace --preset=ts` + `nx g` plugin generators |
21
+ | A shared toolchain package for configs | The configs the Nx generators emit (one root ESLint/tsconfig) |
22
+ | A custom multi-step CI engine | `nx affected -t lint,typecheck,test,build` + `nx release` (~60-line pipeline) |
23
+ | A dist-package dependency injector | `nx release` updates dependent versions natively |
24
+ | Hand-written Azure Function templates | `@nx/node:application` (plain Node app) + a thin Azure Functions v4 overlay |
25
+ | doctor/drift sync of tool-owned files | Nothing to drift: this CLI owns 5 small files, Nx owns the rest |
26
26
 
27
27
  ## Commands (deliberately just three)
28
28
 
@@ -69,7 +69,8 @@ each a single cross-platform command:
69
69
  | `npm run build` | `nx run-many -t build` |
70
70
  | `npm run lint` | `nx run-many -t lint` |
71
71
  | `npm run test` | `nx run-many -t test` |
72
- | `npm run affected` | `nx affected -t lint,test,build` (vs `main`) |
72
+ | `npm run typecheck` | `nx run-many -t typecheck` — its own script because a bundler-built project's `build` strips types without reading them |
73
+ | `npm run affected` | `nx affected -t lint,typecheck,test,build` (vs `main`) |
73
74
  | `npm run graph` | `nx graph` |
74
75
  | `npm run release:preview` | `nx release --dry-run` |
75
76
  | `npm run python:install` | fixed Python toolchain (ruff/pytest/build/twine) + editable-install every Python project — the same two guards CI runs, for local dev |
@@ -146,9 +147,17 @@ same options `new` would have and calls the exact same `applyOverlay` `new`
146
147
  itself calls — the one function that does every bit of `mnci`-owned file
147
148
  writing (`nx.json`'s `release`/`sync`/`generators`/`mnci` blocks, `.npmrc`,
148
149
  `eslint.config.mjs`, `.prettierrc.json`, `commitlint.config.mjs`,
149
- `.husky/commit-msg`, the CI pipeline file(s), and the curated root
150
- `package.json` scripts). Nothing else in the workspace — app/lib source,
151
- `project.json` targets from `mnci add` — is ever touched.
150
+ `.husky/commit-msg`, the CI pipeline file(s), the
151
+ `<workspace-name>.code-workspace` file, and the curated root `package.json`
152
+ scripts). Nothing else in the workspace — app/lib source, `project.json` targets
153
+ from `mnci add` — is ever touched, and it finishes by running Prettier over the
154
+ result, the same way `new` and every `add` do.
155
+
156
+ The `.code-workspace` file is the one partial case, and deliberately so: its
157
+ folders, settings and extensions are regenerated, but the **`tasks` array is read
158
+ back and carried through unchanged**. Those tasks are per-project state written by
159
+ `mnci add`, not overlay-owned, so regenerating them wholesale would wipe every
160
+ project's build/qa/start entry on upgrade.
152
161
 
153
162
  `upgrade` also **deletes** things, which is stronger than the overwriting it
154
163
  has always done — one more reason to run `git diff` first, as the command's own
package/dist/cli.js CHANGED
@@ -150,6 +150,13 @@ var import_node_path = require("path");
150
150
  function readJson(path) {
151
151
  return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
152
152
  }
153
+ function readCodeWorkspace(path) {
154
+ try {
155
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8").replaceAll(/,(\s*[}\]])/g, "$1"));
156
+ } catch {
157
+ return void 0;
158
+ }
159
+ }
153
160
  function toJson(value) {
154
161
  return `${JSON.stringify(value, void 0, 2)}
155
162
  `;
@@ -234,10 +241,6 @@ function findCodeWorkspaceFile(workspaceRoot) {
234
241
  return void 0;
235
242
  }
236
243
  }
237
- function readCodeWorkspaceFile(path) {
238
- const contents = (0, import_node_fs2.readFileSync)(path, "utf8").replaceAll(/,(\s*[}\]])/g, "$1");
239
- return JSON.parse(contents);
240
- }
241
244
  function projectTask(name, kind) {
242
245
  const script = `${name}:${kind}`;
243
246
  const base = { label: `${name}: ${kind}`, type: "npm", script, problemMatcher: [] };
@@ -264,7 +267,7 @@ function registerProjectCommands(workspaceRoot, name, commands) {
264
267
  if (!codeWorkspacePath) {
265
268
  return;
266
269
  }
267
- const workspaceFile = readCodeWorkspaceFile(codeWorkspacePath);
270
+ const workspaceFile = readCodeWorkspace(codeWorkspacePath) ?? {};
268
271
  const label = (task) => task.label ?? "";
269
272
  const existingTasks = (workspaceFile.tasks?.tasks ?? []).filter(
270
273
  (task) => !label(task).startsWith(`${name}: `)
@@ -1276,7 +1279,7 @@ __pycache__
1276
1279
  .ruff_cache
1277
1280
  .dart_tool
1278
1281
  `;
1279
- function vscodeWorkspace(workspaceName) {
1282
+ function vscodeWorkspace(workspaceName, existingTasks) {
1280
1283
  return JSON.stringify(
1281
1284
  {
1282
1285
  folders: [{ path: ".", name: workspaceName }],
@@ -1314,7 +1317,10 @@ function vscodeWorkspace(workspaceName) {
1314
1317
  "firsttris.vscode-jest-runner"
1315
1318
  ]
1316
1319
  },
1317
- tasks: { version: "2.0.0", tasks: [] }
1320
+ tasks: {
1321
+ version: existingTasks?.version ?? "2.0.0",
1322
+ tasks: existingTasks?.tasks ?? []
1323
+ }
1318
1324
  },
1319
1325
  null,
1320
1326
  2
@@ -1324,7 +1330,8 @@ var ROOT_SCRIPTS = {
1324
1330
  build: "nx run-many -t build",
1325
1331
  lint: "nx run-many -t lint",
1326
1332
  test: "nx run-many -t test",
1327
- affected: "nx affected -t lint,test,build",
1333
+ typecheck: "nx run-many -t typecheck",
1334
+ affected: "nx affected -t lint,typecheck,test,build",
1328
1335
  graph: "nx graph",
1329
1336
  "release:preview": "nx release --dry-run",
1330
1337
  prepare: "husky"
@@ -1356,6 +1363,12 @@ function generatorDefaults(stack) {
1356
1363
  }
1357
1364
  function mnciConfig(options) {
1358
1365
  return {
1366
+ // Persisted so `mnci upgrade` can name the `<name>.code-workspace` file it
1367
+ // rewrites. Its absence is why upgrade used to write a file literally called
1368
+ // `undefined.code-workspace` and therefore never refreshed the real one —
1369
+ // see `resolveWorkspaceName` in `commands/upgrade.ts`, which still needs a
1370
+ // fallback chain for workspaces generated before this field existed.
1371
+ workspaceName: options.workspaceName,
1359
1372
  scope: options.scope,
1360
1373
  registry: options.registry,
1361
1374
  agent: options.agent,
@@ -1542,10 +1555,14 @@ steps:
1542
1555
  displayName: Check formatting (run 'npm run format' locally to fix)
1543
1556
  # \`lint\` here also runs any Nx lint targets \`npm run lint\` does not cover \u2014
1544
1557
  # notably Python's ruff (every Python project owns a hand-authored \`lint\`
1545
- # target). For the eslint stack the JS lint runs twice, but Nx caches the
1546
- # repeat instantly.
1547
- - script: npx nx run-many -t lint,test,build
1548
- displayName: Lint, test and build everything
1558
+ # target). The JS lint runs twice, but Nx caches the repeat instantly.
1559
+ #
1560
+ # \`typecheck\` is load-bearing and not covered by \`build\`: a bundler-built
1561
+ # project (esbuild, swc) strips types without reading them, so a workspace can
1562
+ # build, test and lint green while carrying real type errors. --preset=ts
1563
+ # infers a typecheck target for every project, so this costs one target name.
1564
+ - script: npx nx run-many -t lint,typecheck,test,build
1565
+ displayName: Lint, typecheck, test and build everything
1549
1566
 
1550
1567
  # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
1551
1568
  # target. Portable guard: skip cleanly when the workspace has no apps yet.
@@ -1720,10 +1737,15 @@ jobs:
1720
1737
  name: Check formatting (run 'npm run format' locally to fix)
1721
1738
  # \`lint\` here also runs any Nx lint targets \`npm run lint\` does not cover \u2014
1722
1739
  # notably Python's ruff (every Python project owns a hand-authored \`lint\`
1723
- # target). For the eslint stack the JS lint runs twice, but Nx caches the
1724
- # repeat instantly.
1725
- - run: npx nx run-many -t lint,test,build
1726
- name: Lint, test and build everything
1740
+ # target). The JS lint runs twice, but Nx caches the repeat instantly.
1741
+ #
1742
+ # \`typecheck\` is load-bearing and not covered by \`build\`: a bundler-built
1743
+ # project (esbuild, swc) strips types without reading them, so a workspace
1744
+ # can build, test and lint green while carrying real type errors.
1745
+ # --preset=ts infers a typecheck target for every project, so this costs
1746
+ # one target name.
1747
+ - run: npx nx run-many -t lint,typecheck,test,build
1748
+ name: Lint, typecheck, test and build everything
1727
1749
 
1728
1750
  # Pack every app into dist/drop/<type>-<name>.zip via each app's 'package'
1729
1751
  # target. Portable guard: skip cleanly when the workspace has no apps yet.
@@ -1856,10 +1878,12 @@ function applyOverlay(workspaceRoot, options) {
1856
1878
  writeFileEnsured((0, import_node_path10.join)(workspaceRoot, ".prettierrc.json"), PRETTIER_CONFIG);
1857
1879
  writeFileEnsured((0, import_node_path10.join)(workspaceRoot, ".prettierignore"), PRETTIER_IGNORE);
1858
1880
  removeNxScaffolding(workspaceRoot);
1859
- writeFileEnsured(
1860
- (0, import_node_path10.join)(workspaceRoot, `${options.workspaceName}.code-workspace`),
1861
- vscodeWorkspace(options.workspaceName)
1862
- );
1881
+ const codeWorkspacePath = (0, import_node_path10.join)(workspaceRoot, `${options.workspaceName}.code-workspace`);
1882
+ const existing = readCodeWorkspace(codeWorkspacePath);
1883
+ writeFileEnsured(codeWorkspacePath, vscodeWorkspace(options.workspaceName, existing?.tasks));
1884
+ if (options.workspaceName !== "undefined") {
1885
+ (0, import_node_fs6.rmSync)((0, import_node_path10.join)(workspaceRoot, "undefined.code-workspace"), { force: true });
1886
+ }
1863
1887
  const publishUrl = pythonPublishUrl(options.registry);
1864
1888
  if (options.ci === "azure" || options.ci === "both") {
1865
1889
  writeFileEnsured(
@@ -1976,6 +2000,7 @@ async function runNew(name, options) {
1976
2000
  }
1977
2001
 
1978
2002
  // src/commands/upgrade.ts
2003
+ var import_node_fs7 = require("fs");
1979
2004
  var import_node_path12 = require("path");
1980
2005
  function resolveRegistry2(options, persisted) {
1981
2006
  if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
@@ -2000,7 +2025,22 @@ function resolveRegistry2(options, persisted) {
2000
2025
  "No registry found in nx.json's persisted config. Pass --registry npm or --registry azure-artifacts (with --organization/--project/--artifacts-feed)."
2001
2026
  );
2002
2027
  }
2003
- function resolveOverlayOptions(options, persisted) {
2028
+ function resolveWorkspaceName(workspaceRoot, persisted) {
2029
+ if (persisted.workspaceName) {
2030
+ return persisted.workspaceName;
2031
+ }
2032
+ try {
2033
+ const existing = (0, import_node_fs7.readdirSync)(workspaceRoot).find(
2034
+ (file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
2035
+ );
2036
+ if (existing) {
2037
+ return existing.replace(/\.code-workspace$/, "");
2038
+ }
2039
+ } catch {
2040
+ }
2041
+ return (0, import_node_path12.basename)(workspaceRoot);
2042
+ }
2043
+ function resolveOverlayOptions(workspaceRoot, options, persisted) {
2004
2044
  const scope = options.scope ?? persisted.scope;
2005
2045
  if (!scope) {
2006
2046
  throw new Error("No npm scope found in nx.json's persisted config. Pass --scope explicitly.");
@@ -2025,7 +2065,15 @@ function resolveOverlayOptions(options, persisted) {
2025
2065
  "No testRunner found in nx.json's persisted config. Pass --test-runner explicitly."
2026
2066
  );
2027
2067
  }
2028
- return { scope, registry, agent, variableGroup, ci, stack: { testRunner } };
2068
+ return {
2069
+ workspaceName: resolveWorkspaceName(workspaceRoot, persisted),
2070
+ scope,
2071
+ registry,
2072
+ agent,
2073
+ variableGroup,
2074
+ ci,
2075
+ stack: { testRunner }
2076
+ };
2029
2077
  }
2030
2078
  function runUpgrade(workspaceRoot, options) {
2031
2079
  if (!fileExists((0, import_node_path12.join)(workspaceRoot, "nx.json"))) {
@@ -2034,11 +2082,12 @@ function runUpgrade(workspaceRoot, options) {
2034
2082
  );
2035
2083
  }
2036
2084
  const persisted = readMnciConfig(workspaceRoot);
2037
- const resolved = resolveOverlayOptions(options, persisted);
2085
+ const resolved = resolveOverlayOptions(workspaceRoot, options, persisted);
2038
2086
  logger.step(
2039
2087
  "Re-applying the MoNecromanCI overlay (release config, .npmrc, commitlint, pipeline, stack)"
2040
2088
  );
2041
2089
  applyOverlay(workspaceRoot, resolved);
2090
+ runPrettier(workspaceRoot);
2042
2091
  logger.success("Done. Review the changes with `git diff` before committing.");
2043
2092
  }
2044
2093
 
@@ -2065,7 +2114,7 @@ async function runInteractive() {
2065
2114
  }
2066
2115
 
2067
2116
  // src/util/versionChecker.ts
2068
- var import_node_fs7 = require("fs");
2117
+ var import_node_fs8 = require("fs");
2069
2118
  var import_node_path14 = require("path");
2070
2119
  var import_cross_spawn2 = __toESM(require("cross-spawn"));
2071
2120
  var PACKAGE_NAME = "@mnci/cli";
@@ -2123,7 +2172,7 @@ function releaseTriple(version) {
2123
2172
  function readCliVersion(packageDirectory) {
2124
2173
  try {
2125
2174
  const manifestPath = (0, import_node_path14.join)(packageDirectory, "..", "package.json");
2126
- const manifest = JSON.parse((0, import_node_fs7.readFileSync)(manifestPath, "utf8"));
2175
+ const manifest = JSON.parse((0, import_node_fs8.readFileSync)(manifestPath, "utf8"));
2127
2176
  return manifest.version ?? "0.0.0";
2128
2177
  } catch {
2129
2178
  return "0.0.0";