@mnci/cli 4.6.2 → 4.7.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 +59 -0
- package/dist/cli.js +214 -30
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ consumers point at it.
|
|
|
81
81
|
mnci new my-repo # create a monorepo (prompts scope + registry)
|
|
82
82
|
mnci new my-repo --yes --registry npm --scope @my
|
|
83
83
|
mnci new my-repo --yes --registry npm --scope @my --nx-cloud # opt in to Nx Cloud
|
|
84
|
+
mnci new --into . # ...or bootstrap into a clone that already exists
|
|
84
85
|
|
|
85
86
|
cd my-repo
|
|
86
87
|
mnci add react-app web # @nx/react (Vite + Jest)
|
|
@@ -613,6 +614,23 @@ the editor. It also makes `space-before-function-paren` enforceable for the
|
|
|
613
614
|
first time: every Prettier-compatible formatter, oxfmt included, rewrites
|
|
614
615
|
`function f (a)` back to `function f(a)`.
|
|
615
616
|
|
|
617
|
+
**`npm run lint` checks one thing `npm run format` does not.** `lint` is
|
|
618
|
+
`nx run-many -t lint`; `format` is a bare `eslint . --fix --cache`. The
|
|
619
|
+
`@nx/dependency-checks` rule needs the Nx project graph, and outside a target it
|
|
620
|
+
prints `No cached ProjectGraph is available. The rule will be skipped.` So a
|
|
621
|
+
dependency problem shows up in `lint` and never in `format`, your editor, or a
|
|
622
|
+
pre-commit hook.
|
|
623
|
+
|
|
624
|
+
That asymmetry is now a safety property rather than a hazard. The rule is
|
|
625
|
+
**fixable**, and `format` passes `--fix`; when the graph was warm — which it is
|
|
626
|
+
after any `nx` command in the same workspace — a `format` run could and did
|
|
627
|
+
rewrite `package.json`. `@mnci/eslint-config` turns off the two checks whose
|
|
628
|
+
fixers do that (`checkObsoleteDependencies`, `checkVersionMismatches`), so
|
|
629
|
+
neither path is destructive, and the skip means `format` cannot reach a manifest
|
|
630
|
+
at all. Warm the graph deliberately (`npx nx show projects`) if you want the
|
|
631
|
+
rule evaluated in `format` too; mnci does not, because it would make every
|
|
632
|
+
format run pay for a graph computation to enforce what `lint` already gates.
|
|
633
|
+
|
|
616
634
|
**Upgrading an older workspace.** `mnci upgrade` deletes every config a previous
|
|
617
635
|
version could have written for a second tool — `.prettierrc`, `.prettierrc.json`,
|
|
618
636
|
`.prettierrc.mjs`, `.prettierignore`, `.oxfmtrc.json`, `oxlint.config.ts` — and
|
|
@@ -623,6 +641,47 @@ formatter from the **project's** dependencies, so a stale declaration is enough
|
|
|
623
641
|
to reformat on save against an opinion nothing checks. `mnci doctor` reports a
|
|
624
642
|
workspace that has not been upgraded yet.
|
|
625
643
|
|
|
644
|
+
## `--into`: bootstrapping into a repository that already exists
|
|
645
|
+
|
|
646
|
+
`create-nx-workspace <name>` creates the directory itself and exits with
|
|
647
|
+
`DIRECTORY_EXISTS` when one is already there. That rules out the most common
|
|
648
|
+
way a repository actually starts: the host creates it, you clone it, and the
|
|
649
|
+
clone holds a `.git` directory, a README and a licence. Doing it by hand means
|
|
650
|
+
generating into a temp parent, copying everything except `.git` and
|
|
651
|
+
`node_modules` across, and reinstalling — four steps, each of which can quietly
|
|
652
|
+
lose a file.
|
|
653
|
+
|
|
654
|
+
`mnci new --into <dir>` is that, done once and tested:
|
|
655
|
+
|
|
656
|
+
```sh
|
|
657
|
+
git clone git@github.com:me/my-repo.git
|
|
658
|
+
cd my-repo
|
|
659
|
+
mnci new --into .
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
The workspace name defaults to the directory's name, because the directory is
|
|
663
|
+
already named and retyping it is a way to get the two out of step; pass a name
|
|
664
|
+
argument to override it.
|
|
665
|
+
|
|
666
|
+
What it does with the files that are already there is the whole of the risk, so
|
|
667
|
+
every case is decided in advance:
|
|
668
|
+
|
|
669
|
+
| Already in the directory | What happens |
|
|
670
|
+
| --- | --- |
|
|
671
|
+
| `.git` | Never touched. Keeping it is the point. |
|
|
672
|
+
| `README.md`, `LICENSE*` | **Kept.** The generator's README is boilerplate; yours is usually the only hand-written file in the repository. |
|
|
673
|
+
| `.gitignore` | **Merged.** Your lines stay, and the generated ones (`.nx/cache`, `dist`, `out-tsc`, …) are appended under a labelled heading. Lines already present are not repeated, so it is idempotent. |
|
|
674
|
+
| Anything else the new workspace also writes | **Refused**, naming every collision, before a single file is written. |
|
|
675
|
+
| Anything the new workspace does not write | Left alone. |
|
|
676
|
+
|
|
677
|
+
Two checks, not one. A directory holding `package.json`, `nx.json`,
|
|
678
|
+
`tsconfig.base.json`, `node_modules`, `apps/`, `libs/` or `packages/` is
|
|
679
|
+
rejected **before** anything is generated — it is a project already, and the
|
|
680
|
+
answer there is `mnci upgrade`, not `mnci new`. The full collision check needs
|
|
681
|
+
the generated tree, so it runs afterwards, but still before the first write:
|
|
682
|
+
a refusal leaves the target byte-identical to how it was found, and the staging
|
|
683
|
+
copy is discarded.
|
|
684
|
+
|
|
626
685
|
## Layout convention = release scoping
|
|
627
686
|
|
|
628
687
|
| Directory | Contents | Released? |
|
package/dist/cli.js
CHANGED
|
@@ -147,7 +147,7 @@ function runCapture(command, arguments_, cwd) {
|
|
|
147
147
|
return { status: result.status ?? 1, stdout: result.stdout ?? "" };
|
|
148
148
|
}
|
|
149
149
|
async function runCaptureAsync(command, arguments_, cwd) {
|
|
150
|
-
return await new Promise((
|
|
150
|
+
return await new Promise((resolve2) => {
|
|
151
151
|
const child = (0, import_cross_spawn.default)(command, arguments_, {
|
|
152
152
|
cwd,
|
|
153
153
|
timeout: CAPTURE_TIMEOUT_MS,
|
|
@@ -159,10 +159,10 @@ async function runCaptureAsync(command, arguments_, cwd) {
|
|
|
159
159
|
stdout += chunk;
|
|
160
160
|
});
|
|
161
161
|
child.on("error", () => {
|
|
162
|
-
|
|
162
|
+
resolve2({ status: 1, stdout: "" });
|
|
163
163
|
});
|
|
164
164
|
child.on("close", (code) => {
|
|
165
|
-
|
|
165
|
+
resolve2({ status: code ?? 1, stdout });
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
}
|
|
@@ -1558,7 +1558,58 @@ updates:
|
|
|
1558
1558
|
}
|
|
1559
1559
|
return config;
|
|
1560
1560
|
}
|
|
1561
|
-
var NX_SCAFFOLDING_TO_REMOVE = [
|
|
1561
|
+
var NX_SCAFFOLDING_TO_REMOVE = [
|
|
1562
|
+
".prettierrc",
|
|
1563
|
+
".prettierrc.json",
|
|
1564
|
+
".vscode",
|
|
1565
|
+
// See LOCAL_REGISTRY_SCAFFOLDING: the config half of a publishing mechanism
|
|
1566
|
+
// this workspace does not use.
|
|
1567
|
+
".verdaccio",
|
|
1568
|
+
// The AI-agent set, in full. See the remarks above: a partial delete leaves
|
|
1569
|
+
// every `nx` command printing an "outdated configuration" nag.
|
|
1570
|
+
".agents",
|
|
1571
|
+
".claude",
|
|
1572
|
+
".codex",
|
|
1573
|
+
".cursor",
|
|
1574
|
+
".gemini",
|
|
1575
|
+
".opencode",
|
|
1576
|
+
".github/agents",
|
|
1577
|
+
".github/prompts",
|
|
1578
|
+
".github/skills",
|
|
1579
|
+
"AGENTS.md",
|
|
1580
|
+
"CLAUDE.md",
|
|
1581
|
+
"opencode.json"
|
|
1582
|
+
];
|
|
1583
|
+
function withoutKey(record, key) {
|
|
1584
|
+
return Object.fromEntries(Object.entries(record).filter(([name]) => name !== key));
|
|
1585
|
+
}
|
|
1586
|
+
var LOCAL_REGISTRY_SCAFFOLDING = {
|
|
1587
|
+
/** The config directory, removed with the rest of {@link NX_SCAFFOLDING_TO_REMOVE}. */
|
|
1588
|
+
directory: ".verdaccio",
|
|
1589
|
+
/** The root devDependency the target runs. */
|
|
1590
|
+
devDependency: "verdaccio",
|
|
1591
|
+
/** The root Nx target wired to both of the above. */
|
|
1592
|
+
rootTarget: "local-registry"
|
|
1593
|
+
};
|
|
1594
|
+
function removeLocalRegistryScaffolding(workspaceRoot) {
|
|
1595
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, LOCAL_REGISTRY_SCAFFOLDING.directory), {
|
|
1596
|
+
recursive: true,
|
|
1597
|
+
force: true
|
|
1598
|
+
});
|
|
1599
|
+
const manifestPath = (0, import_node_path2.join)(workspaceRoot, "package.json");
|
|
1600
|
+
if (!fileExists(manifestPath)) return;
|
|
1601
|
+
const manifest = readJson(manifestPath);
|
|
1602
|
+
if (manifest.devDependencies?.[LOCAL_REGISTRY_SCAFFOLDING.devDependency] !== void 0) {
|
|
1603
|
+
delete manifest.devDependencies[LOCAL_REGISTRY_SCAFFOLDING.devDependency];
|
|
1604
|
+
if (Object.keys(manifest.devDependencies).length === 0) delete manifest.devDependencies;
|
|
1605
|
+
}
|
|
1606
|
+
if (manifest.nx?.targets?.[LOCAL_REGISTRY_SCAFFOLDING.rootTarget] !== void 0) {
|
|
1607
|
+
delete manifest.nx.targets[LOCAL_REGISTRY_SCAFFOLDING.rootTarget];
|
|
1608
|
+
if (Object.keys(manifest.nx.targets).length === 0) delete manifest.nx.targets;
|
|
1609
|
+
if (Object.keys(manifest.nx).length === 0) delete manifest.nx;
|
|
1610
|
+
}
|
|
1611
|
+
writeFileEnsured(manifestPath, toJson(manifest));
|
|
1612
|
+
}
|
|
1562
1613
|
function removeNxScaffolding(workspaceRoot) {
|
|
1563
1614
|
for (const entry of NX_SCAFFOLDING_TO_REMOVE) {
|
|
1564
1615
|
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(workspaceRoot, entry), { recursive: true, force: true });
|
|
@@ -1586,6 +1637,38 @@ function ensureEslintCacheIgnored(workspaceRoot) {
|
|
|
1586
1637
|
`
|
|
1587
1638
|
);
|
|
1588
1639
|
}
|
|
1640
|
+
var PYTHON_ARTEFACT_IGNORES = ["__pycache__/", "*.py[cod]"];
|
|
1641
|
+
function ensurePythonArtefactsIgnored(workspaceRoot) {
|
|
1642
|
+
const gitignorePath = (0, import_node_path2.join)(workspaceRoot, ".gitignore");
|
|
1643
|
+
if (!fileExists(gitignorePath)) {
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
const hasPython = (0, import_node_fs2.globSync)(
|
|
1647
|
+
["python-packages/*/pyproject.toml", "apps/*/pyproject.toml", "libs/*/pyproject.toml"],
|
|
1648
|
+
{ cwd: workspaceRoot }
|
|
1649
|
+
).length > 0;
|
|
1650
|
+
if (!hasPython) {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
const current = (0, import_node_fs2.readFileSync)(gitignorePath, "utf8");
|
|
1654
|
+
const present = new Set(current.split("\n").map((line) => line.trim()));
|
|
1655
|
+
const missing = PYTHON_ARTEFACT_IGNORES.filter((line) => !present.has(line));
|
|
1656
|
+
if (missing.length === 0) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const withoutTrailingBlankLines = current.replace(/\n+$/u, "");
|
|
1660
|
+
const separator = withoutTrailingBlankLines.length > 0 ? "\n\n" : "";
|
|
1661
|
+
const heading = [
|
|
1662
|
+
"# Added by MoNecromanCI: Python bytecode. The tool caches (.mypy_cache,",
|
|
1663
|
+
"# .pytest_cache, .ruff_cache) write their own .gitignore; CPython does not."
|
|
1664
|
+
].join("\n");
|
|
1665
|
+
writeFileEnsured(
|
|
1666
|
+
gitignorePath,
|
|
1667
|
+
`${withoutTrailingBlankLines}${separator}${heading}
|
|
1668
|
+
${missing.join("\n")}
|
|
1669
|
+
`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1589
1672
|
function removeProjectEslintConfigs(workspaceRoot) {
|
|
1590
1673
|
const matches = (0, import_node_fs2.globSync)("{apps,libs,packages}/*/eslint.config.{js,mjs,cjs,ts,mts,cts}", {
|
|
1591
1674
|
cwd: workspaceRoot
|
|
@@ -1621,12 +1704,15 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1621
1704
|
...rootScripts()
|
|
1622
1705
|
};
|
|
1623
1706
|
const existingDevDeps = manifest.devDependencies;
|
|
1624
|
-
const devDeps =
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1707
|
+
const devDeps = withoutKey(
|
|
1708
|
+
withoutRetiredFormatterDependencies({
|
|
1709
|
+
...existingDevDeps,
|
|
1710
|
+
...TS_COMPILER_DEPENDENCIES,
|
|
1711
|
+
// The preset pins `nx` itself; the ESLint plugins must match it exactly.
|
|
1712
|
+
...eslintToolchainDependencies(existingDevDeps?.nx ?? "latest")
|
|
1713
|
+
}),
|
|
1714
|
+
LOCAL_REGISTRY_SCAFFOLDING.devDependency
|
|
1715
|
+
);
|
|
1630
1716
|
const overrides = {
|
|
1631
1717
|
...manifest.overrides,
|
|
1632
1718
|
...ESLINT_PEER_OVERRIDES,
|
|
@@ -1638,11 +1724,15 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1638
1724
|
...reactExpressPeerOverride(manifest)
|
|
1639
1725
|
};
|
|
1640
1726
|
const existingNx = manifest.nx;
|
|
1727
|
+
const existingTargets = withoutKey(
|
|
1728
|
+
existingNx?.targets ?? {},
|
|
1729
|
+
LOCAL_REGISTRY_SCAFFOLDING.rootTarget
|
|
1730
|
+
);
|
|
1641
1731
|
const nx = {
|
|
1642
1732
|
...existingNx,
|
|
1643
1733
|
includedScripts: existingNx?.includedScripts ?? [],
|
|
1644
1734
|
targets: {
|
|
1645
|
-
...
|
|
1735
|
+
...existingTargets,
|
|
1646
1736
|
lint: ROOT_LINT_TARGET
|
|
1647
1737
|
}
|
|
1648
1738
|
};
|
|
@@ -1670,6 +1760,7 @@ function applyOverlay(workspaceRoot, options, onProgress = () => {
|
|
|
1670
1760
|
writeFileEnsured((0, import_node_path2.join)(workspaceRoot, ESLINT_MNCI_FILENAME), ESLINT_MNCI_CONFIG);
|
|
1671
1761
|
writeEslintEntryPoint(workspaceRoot, onProgress);
|
|
1672
1762
|
ensureEslintCacheIgnored(workspaceRoot);
|
|
1763
|
+
ensurePythonArtefactsIgnored(workspaceRoot);
|
|
1673
1764
|
for (const retired of RETIRED_FORMATTER_FILES) {
|
|
1674
1765
|
removeIfPresent((0, import_node_path2.join)(workspaceRoot, retired));
|
|
1675
1766
|
}
|
|
@@ -2937,6 +3028,7 @@ async function addNpmLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
|
2937
3028
|
repairDeclarationSpecifiers(projectRoot);
|
|
2938
3029
|
writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner);
|
|
2939
3030
|
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
3031
|
+
removeLocalRegistryScaffolding(workspaceRoot);
|
|
2940
3032
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
2941
3033
|
}
|
|
2942
3034
|
);
|
|
@@ -3283,6 +3375,7 @@ async function addReactLib(workspaceRoot, name, options, kindProvided, stack) {
|
|
|
3283
3375
|
repairDeclarationSpecifiers(projectRoot);
|
|
3284
3376
|
writeProjectReadme(projectRoot, `${scope}/${name}`, stack.testRunner);
|
|
3285
3377
|
removeGeneratedEslintConfig(workspaceRoot, `packages/${name}`);
|
|
3378
|
+
removeLocalRegistryScaffolding(workspaceRoot);
|
|
3286
3379
|
registerProjectCommands(workspaceRoot, name, { build: true });
|
|
3287
3380
|
}
|
|
3288
3381
|
);
|
|
@@ -4838,9 +4931,86 @@ function runDoctor(workspaceRoot) {
|
|
|
4838
4931
|
process.exitCode = 1;
|
|
4839
4932
|
}
|
|
4840
4933
|
|
|
4841
|
-
// src/workspace-creation/
|
|
4934
|
+
// src/workspace-creation/adopt-directory.use-case.ts
|
|
4842
4935
|
var import_node_fs13 = require("fs");
|
|
4843
4936
|
var import_node_path20 = require("path");
|
|
4937
|
+
var NEVER_COPIED = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
4938
|
+
var EXISTING_WINS = /* @__PURE__ */ new Set(["README.md", "LICENSE", "LICENSE.md", "LICENSE.txt"]);
|
|
4939
|
+
var PREFLIGHT_BLOCKERS = [
|
|
4940
|
+
"package.json",
|
|
4941
|
+
"nx.json",
|
|
4942
|
+
"tsconfig.base.json",
|
|
4943
|
+
"node_modules",
|
|
4944
|
+
"apps",
|
|
4945
|
+
"libs",
|
|
4946
|
+
"packages"
|
|
4947
|
+
];
|
|
4948
|
+
function assertAdoptableDirectory(target) {
|
|
4949
|
+
if (!(0, import_node_fs13.existsSync)(target)) {
|
|
4950
|
+
throw new Error(`--into '${target}' does not exist. Create or clone it first.`);
|
|
4951
|
+
}
|
|
4952
|
+
if (!(0, import_node_fs13.statSync)(target).isDirectory()) {
|
|
4953
|
+
throw new Error(`--into '${target}' is not a directory.`);
|
|
4954
|
+
}
|
|
4955
|
+
const blockers = PREFLIGHT_BLOCKERS.filter((entry) => (0, import_node_fs13.existsSync)((0, import_node_path20.join)(target, entry)));
|
|
4956
|
+
if (blockers.length > 0) {
|
|
4957
|
+
throw new Error(
|
|
4958
|
+
`--into '${target}' already holds ${blockers.join(", ")}, so it is a project already. Use \`mnci upgrade\` to re-apply the overlay to an existing workspace, or point --into at an empty checkout.`
|
|
4959
|
+
);
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
function mergeGitignore(generatedFile, targetFile) {
|
|
4963
|
+
const existing = (0, import_node_fs13.readFileSync)(targetFile, "utf8");
|
|
4964
|
+
const present = new Set(existing.split("\n").map((line) => line.trim()));
|
|
4965
|
+
const additions = (0, import_node_fs13.readFileSync)(generatedFile, "utf8").split("\n").map((line) => line.trimEnd()).filter((line) => {
|
|
4966
|
+
const trimmed = line.trim();
|
|
4967
|
+
return trimmed !== "" && !trimmed.startsWith("#") && !present.has(trimmed);
|
|
4968
|
+
});
|
|
4969
|
+
if (additions.length === 0) return false;
|
|
4970
|
+
const separator = existing.endsWith("\n") ? "" : "\n";
|
|
4971
|
+
(0, import_node_fs13.writeFileSync)(
|
|
4972
|
+
targetFile,
|
|
4973
|
+
`${existing}${separator}
|
|
4974
|
+
# Added by mnci (Nx workspace)
|
|
4975
|
+
${additions.join("\n")}
|
|
4976
|
+
`
|
|
4977
|
+
);
|
|
4978
|
+
return true;
|
|
4979
|
+
}
|
|
4980
|
+
function adoptGeneratedWorkspace(generatedRoot, target) {
|
|
4981
|
+
const entries = (0, import_node_fs13.readdirSync)(generatedRoot).filter((entry) => !NEVER_COPIED.has(entry));
|
|
4982
|
+
const collisions = entries.filter(
|
|
4983
|
+
(entry) => (0, import_node_fs13.existsSync)((0, import_node_path20.join)(target, entry)) && !EXISTING_WINS.has(entry) && entry !== ".gitignore"
|
|
4984
|
+
);
|
|
4985
|
+
if (collisions.length > 0) {
|
|
4986
|
+
throw new Error(
|
|
4987
|
+
`--into '${target}' already contains ${collisions.join(", ")}, which the new workspace also writes. Move those aside and run it again; nothing has been changed.`
|
|
4988
|
+
);
|
|
4989
|
+
}
|
|
4990
|
+
const result = { copied: [], kept: [], mergedGitignore: false };
|
|
4991
|
+
for (const entry of entries) {
|
|
4992
|
+
const from = (0, import_node_path20.join)(generatedRoot, entry);
|
|
4993
|
+
const to = (0, import_node_path20.join)(target, entry);
|
|
4994
|
+
if (entry === ".gitignore" && (0, import_node_fs13.existsSync)(to)) {
|
|
4995
|
+
result.mergedGitignore = mergeGitignore(from, to);
|
|
4996
|
+
result.kept.push(entry);
|
|
4997
|
+
continue;
|
|
4998
|
+
}
|
|
4999
|
+
if ((0, import_node_fs13.existsSync)(to)) {
|
|
5000
|
+
result.kept.push(entry);
|
|
5001
|
+
continue;
|
|
5002
|
+
}
|
|
5003
|
+
(0, import_node_fs13.cpSync)(from, to, { recursive: true });
|
|
5004
|
+
result.copied.push(entry);
|
|
5005
|
+
}
|
|
5006
|
+
(0, import_node_fs13.rmSync)(generatedRoot, { recursive: true, force: true });
|
|
5007
|
+
return result;
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
// src/workspace-creation/create-workspace.use-case.ts
|
|
5011
|
+
var import_node_fs14 = require("fs");
|
|
5012
|
+
var import_node_os = require("os");
|
|
5013
|
+
var import_node_path21 = require("path");
|
|
4844
5014
|
function nxCloudProviderValue(ci) {
|
|
4845
5015
|
return ci === "azure" ? "azure" : "github";
|
|
4846
5016
|
}
|
|
@@ -4876,7 +5046,9 @@ async function resolveRegistry(options) {
|
|
|
4876
5046
|
return await promptRegistry();
|
|
4877
5047
|
}
|
|
4878
5048
|
async function runNew(name, options) {
|
|
4879
|
-
const
|
|
5049
|
+
const adoptTarget = options.into === void 0 ? void 0 : (0, import_node_path21.resolve)(process.cwd(), options.into);
|
|
5050
|
+
if (adoptTarget !== void 0) assertAdoptableDirectory(adoptTarget);
|
|
5051
|
+
const workspaceName = name ?? (adoptTarget === void 0 ? await promptText("Workspace name") : (0, import_node_path21.basename)(adoptTarget));
|
|
4880
5052
|
assertValidProjectName(workspaceName, "Workspace name");
|
|
4881
5053
|
const scope = options.scope ?? (options.yes ? `@${workspaceName}` : await promptText("npm scope for publishable packages", `@${workspaceName}`));
|
|
4882
5054
|
const registry = await resolveRegistry(options);
|
|
@@ -4888,6 +5060,7 @@ async function runNew(name, options) {
|
|
|
4888
5060
|
const variableGroup = ci === "github" ? options.variableGroup ?? "Build" : options.variableGroup ?? (options.yes ? "Build" : await promptText("Azure DevOps variable group holding the npm PAT", "Build"));
|
|
4889
5061
|
const stack = await resolveStack(options);
|
|
4890
5062
|
const nxCloud = options.nxCloud ?? (options.yes ? false : await promptNxCloud());
|
|
5063
|
+
const stagingParent = adoptTarget === void 0 ? process.cwd() : (0, import_node_fs14.mkdtempSync)((0, import_node_path21.join)((0, import_node_os.tmpdir)(), "mnci-new-"));
|
|
4891
5064
|
logger.step(`Creating Nx workspace '${workspaceName}' (preset: ts)`);
|
|
4892
5065
|
runNpx(
|
|
4893
5066
|
[
|
|
@@ -4899,9 +5072,17 @@ async function runNew(name, options) {
|
|
|
4899
5072
|
nxCloud ? `--nxCloud=${nxCloudProviderValue(ci)}` : "--nxCloud=skip",
|
|
4900
5073
|
"--no-interactive"
|
|
4901
5074
|
],
|
|
4902
|
-
|
|
5075
|
+
stagingParent
|
|
4903
5076
|
);
|
|
4904
|
-
|
|
5077
|
+
let workspaceRoot = (0, import_node_path21.join)(stagingParent, workspaceName);
|
|
5078
|
+
if (adoptTarget !== void 0) {
|
|
5079
|
+
logger.step(`Adopting ${adoptTarget} (keeping its .git)`);
|
|
5080
|
+
const adoption = adoptGeneratedWorkspace(workspaceRoot, adoptTarget);
|
|
5081
|
+
for (const kept of adoption.kept) logger.detail(`kept the existing ${kept}`);
|
|
5082
|
+
if (adoption.mergedGitignore) logger.detail("merged the generated .gitignore into the existing one");
|
|
5083
|
+
(0, import_node_fs14.rmSync)(stagingParent, { recursive: true, force: true });
|
|
5084
|
+
workspaceRoot = adoptTarget;
|
|
5085
|
+
}
|
|
4905
5086
|
logger.step(
|
|
4906
5087
|
"Applying MoNecromanCI overlay (VS Code workspace, release config, .npmrc, commitlint, pipeline, stack)"
|
|
4907
5088
|
);
|
|
@@ -4910,8 +5091,8 @@ async function runNew(name, options) {
|
|
|
4910
5091
|
{ workspaceName, scope, registry, agent, variableGroup, ci, stack },
|
|
4911
5092
|
logger.detail
|
|
4912
5093
|
);
|
|
4913
|
-
(0,
|
|
4914
|
-
(0,
|
|
5094
|
+
(0, import_node_fs14.rmSync)((0, import_node_path21.join)(workspaceRoot, "node_modules"), { recursive: true, force: true });
|
|
5095
|
+
(0, import_node_fs14.rmSync)((0, import_node_path21.join)(workspaceRoot, "package-lock.json"), { force: true });
|
|
4915
5096
|
logger.step("Installing the toolchain (commitlint + husky + linting/formatting)");
|
|
4916
5097
|
const installStatus = runShell(
|
|
4917
5098
|
"npm",
|
|
@@ -4925,7 +5106,7 @@ async function runNew(name, options) {
|
|
|
4925
5106
|
logger.step("Formatting the workspace (eslint --fix, JavaScript Standard Style)");
|
|
4926
5107
|
runFormatter(workspaceRoot);
|
|
4927
5108
|
logger.success("Done. Next steps:");
|
|
4928
|
-
logger.info(` cd ${workspaceName}`);
|
|
5109
|
+
if (adoptTarget === void 0) logger.info(` cd ${workspaceName}`);
|
|
4929
5110
|
logger.info(" mnci add react-app web # or: react-lib, react-internal-lib, node-app,");
|
|
4930
5111
|
logger.info(" # node-function-app, npm-lib, internal-lib,");
|
|
4931
5112
|
logger.info(
|
|
@@ -4941,12 +5122,12 @@ async function runNew(name, options) {
|
|
|
4941
5122
|
}
|
|
4942
5123
|
|
|
4943
5124
|
// src/workspace-creation/interactive-wizard.use-case.ts
|
|
4944
|
-
var
|
|
5125
|
+
var import_node_path23 = require("path");
|
|
4945
5126
|
var import_prompts4 = require("@inquirer/prompts");
|
|
4946
5127
|
|
|
4947
5128
|
// src/workspace-upgrade/upgrade-workspace.use-case.ts
|
|
4948
|
-
var
|
|
4949
|
-
var
|
|
5129
|
+
var import_node_fs15 = require("fs");
|
|
5130
|
+
var import_node_path22 = require("path");
|
|
4950
5131
|
function resolveRegistry2(options, persisted) {
|
|
4951
5132
|
if (options.registry === "azure-artifacts" || options.organization && options.artifactsFeed) {
|
|
4952
5133
|
const persistedAzure = persisted?.kind === "azure-artifacts" ? persisted : void 0;
|
|
@@ -4975,7 +5156,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
4975
5156
|
return persisted.workspaceName;
|
|
4976
5157
|
}
|
|
4977
5158
|
try {
|
|
4978
|
-
const existing = (0,
|
|
5159
|
+
const existing = (0, import_node_fs15.readdirSync)(workspaceRoot).find(
|
|
4979
5160
|
(file) => file.endsWith(".code-workspace") && file !== "undefined.code-workspace"
|
|
4980
5161
|
);
|
|
4981
5162
|
if (existing) {
|
|
@@ -4983,7 +5164,7 @@ function resolveWorkspaceName(workspaceRoot, persisted) {
|
|
|
4983
5164
|
}
|
|
4984
5165
|
} catch {
|
|
4985
5166
|
}
|
|
4986
|
-
return (0,
|
|
5167
|
+
return (0, import_node_path22.basename)(workspaceRoot);
|
|
4987
5168
|
}
|
|
4988
5169
|
function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
4989
5170
|
const scope = options.scope ?? persisted.scope;
|
|
@@ -5021,7 +5202,7 @@ function resolveOverlayOptions(workspaceRoot, options, persisted) {
|
|
|
5021
5202
|
};
|
|
5022
5203
|
}
|
|
5023
5204
|
function runUpgrade(workspaceRoot, options) {
|
|
5024
|
-
if (!fileExists((0,
|
|
5205
|
+
if (!fileExists((0, import_node_path22.join)(workspaceRoot, "nx.json"))) {
|
|
5025
5206
|
throw new Error(
|
|
5026
5207
|
`No nx.json found in ${workspaceRoot} \u2014 this does not look like an Nx workspace. Run 'mnci upgrade' from the workspace root.`
|
|
5027
5208
|
);
|
|
@@ -5060,7 +5241,7 @@ function runUpgrade(workspaceRoot, options) {
|
|
|
5060
5241
|
|
|
5061
5242
|
// src/workspace-creation/interactive-wizard.use-case.ts
|
|
5062
5243
|
async function runInteractive() {
|
|
5063
|
-
const inWorkspace = fileExists((0,
|
|
5244
|
+
const inWorkspace = fileExists((0, import_node_path23.join)(process.cwd(), "nx.json"));
|
|
5064
5245
|
const choiceNew = { name: "Create a new monorepo", value: "new" };
|
|
5065
5246
|
const choiceAdd = { name: "Add a project to this workspace", value: "add" };
|
|
5066
5247
|
const choiceUpgrade = {
|
|
@@ -5081,8 +5262,8 @@ async function runInteractive() {
|
|
|
5081
5262
|
}
|
|
5082
5263
|
|
|
5083
5264
|
// src/cli-version/check-for-update.use-case.ts
|
|
5084
|
-
var
|
|
5085
|
-
var
|
|
5265
|
+
var import_node_fs16 = require("fs");
|
|
5266
|
+
var import_node_path24 = require("path");
|
|
5086
5267
|
var import_cross_spawn2 = __toESM(require("cross-spawn"));
|
|
5087
5268
|
var PACKAGE_NAME = "@mnci/cli";
|
|
5088
5269
|
var REGISTRY_TIMEOUT_MS = 2e3;
|
|
@@ -5138,8 +5319,8 @@ function releaseTriple(version) {
|
|
|
5138
5319
|
}
|
|
5139
5320
|
function readCliVersion(packageDirectory) {
|
|
5140
5321
|
try {
|
|
5141
|
-
const manifestPath = (0,
|
|
5142
|
-
const manifest = JSON.parse((0,
|
|
5322
|
+
const manifestPath = (0, import_node_path24.join)(packageDirectory, "..", "package.json");
|
|
5323
|
+
const manifest = JSON.parse((0, import_node_fs16.readFileSync)(manifestPath, "utf8"));
|
|
5143
5324
|
return manifest.version ?? "0.0.0";
|
|
5144
5325
|
} catch {
|
|
5145
5326
|
return "0.0.0";
|
|
@@ -5153,7 +5334,10 @@ function buildProgram(cliVersion) {
|
|
|
5153
5334
|
program.command("new").argument("[name]", "workspace name").description("Create a new monorepo (Nx TS preset + release/pipeline overlay)").option("-y, --yes", "accept defaults for anything not passed as a flag").option("--scope <scope>", "npm scope for publishable packages (e.g. @demo)").option("--registry <kind>", "azure-artifacts | npm").option("--organization <name>", "Azure DevOps organization").option("--project <name>", "Azure DevOps project").option("--artifacts-feed <name>", "Azure Artifacts feed").option(
|
|
5154
5335
|
"--agent <pool>",
|
|
5155
5336
|
"CI build agent: a vmImage (e.g. ubuntu-latest) or self-hosted pool name"
|
|
5156
|
-
).option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").
|
|
5337
|
+
).option("--variable-group <name>", "Azure DevOps variable group holding the npm PAT").option("--ci <provider>", "CI provider: azure | github | both").option("--test-runner <runner>", "unit-test runner: jest | vitest").option("--nx-cloud", "connect the workspace to Nx Cloud (remote caching + CI insights)").option(
|
|
5338
|
+
"--into <dir>",
|
|
5339
|
+
"bootstrap into an existing directory (a fresh clone holding only .git); the directory name is the workspace name unless one is given"
|
|
5340
|
+
).action(async (name, options) => {
|
|
5157
5341
|
await runNew(name, options);
|
|
5158
5342
|
});
|
|
5159
5343
|
program.command("upgrade").description(
|