@jr2/cli 0.1.1 → 0.1.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/package.json +2 -2
- package/src/build.ts +50 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jr2/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The jr2 CLI: init, up, run — the interface to a jr2 Instance.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"types": "./src/index.ts",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"ts-blank-space": "^0.9.0",
|
|
40
|
-
"@jr2/orchestrator": "0.1.
|
|
40
|
+
"@jr2/orchestrator": "0.1.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^26.0.1",
|
package/src/build.ts
CHANGED
|
@@ -998,30 +998,47 @@ const execCommand: RunCommand = async (command, args, cwd) => {
|
|
|
998
998
|
|
|
999
999
|
/**
|
|
1000
1000
|
* Materialize the Instance into `outDir` (ADR-0043, as amended there). Two shapes, and the key is
|
|
1001
|
-
* the INSTANCE's own
|
|
1002
|
-
* {@link detectKitCheckout} — is what says whether `pnpm deploy` can run at all. A checkout CLI can
|
|
1003
|
-
* legitimately drive a standalone instance (the developer's `/tmp` folder), and keying on the CLI's
|
|
1004
|
-
* own provenance would send that instance down a path whose job — materializing workspace symlinks
|
|
1005
|
-
* — only exists in a workspace. The mirror holds too: an INSTALLED kit driving an instance nested
|
|
1006
|
-
* in the user's own pnpm monorepo takes `pnpm deploy`, because that instance carries no lockfile of
|
|
1007
|
-
* its own — the workspace root holds it.
|
|
1001
|
+
* the INSTANCE's own, asked in this order:
|
|
1008
1002
|
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1003
|
+
* 1. Its own lockfile. An instance that carries one is self-contained — the lockfile IS its
|
|
1004
|
+
* dependency graph, wherever the folder sits — so it is staged as a copy
|
|
1005
|
+
* ({@link BUNDLE_STAGE_EXCLUDE}) and installed frozen and production-only by the lockfile's own
|
|
1006
|
+
* package manager ({@link lockfileInstall}). This is how a standalone instance committed INSIDE
|
|
1007
|
+
* a pnpm monorepo bundles: the kit's own `docs/intro` sits under the checkout's
|
|
1008
|
+
* `pnpm-workspace.yaml` and is deliberately not one of its packages.
|
|
1009
|
+
* 2. A `pnpm-workspace.yaml` above it. A workspace member carries no lockfile of its own — the
|
|
1010
|
+
* workspace root holds it — so it is the ABSENCE of one that sends the instance to `pnpm deploy
|
|
1011
|
+
* --legacy`, unchanged. pnpm is a contributor prerequisite, like go for the operator, never a
|
|
1012
|
+
* product dependency.
|
|
1013
|
+
* 3. Neither: {@link lockfileInstall}'s named refusal.
|
|
1014
|
+
*
|
|
1015
|
+
* Neither key is {@link detectKitCheckout}: a checkout CLI legitimately drives a standalone
|
|
1016
|
+
* instance (the developer's `/tmp` folder), and an installed kit legitimately drives a member of
|
|
1017
|
+
* the user's own monorepo. Asking "is there a workspace file above me" FIRST — the previous rule —
|
|
1018
|
+
* answered a question about an ancestor, and sent shape (1) down `pnpm deploy`, which pnpm answers
|
|
1019
|
+
* for a non-member with "No projects matched the filters" and exit 0: no bundle, and the failure
|
|
1020
|
+
* surfaced later as an ENOENT from the seal. That silent success is why the deploy branch checks
|
|
1021
|
+
* that the bundle exists, and names the shape when it does not.
|
|
1014
1022
|
*/
|
|
1015
1023
|
export async function bundleInstance(
|
|
1016
1024
|
instanceDir: string,
|
|
1017
1025
|
outDir: string,
|
|
1018
1026
|
run: RunCommand = execCommand,
|
|
1019
1027
|
): Promise<void> {
|
|
1020
|
-
|
|
1028
|
+
const root = (await hasOwnLockfile(instanceDir)) ? undefined : await pnpmWorkspaceRoot(instanceDir);
|
|
1029
|
+
if (root) {
|
|
1021
1030
|
const pkg = JSON.parse(await readFile(join(instanceDir, "package.json"), "utf8")) as { name?: string };
|
|
1022
1031
|
if (!pkg.name) throw new Error(`${instanceDir}/package.json has no "name" — needed to bundle the instance`);
|
|
1023
1032
|
// --legacy: materialize (copy) workspace deps into the bundle rather than linking them.
|
|
1024
1033
|
await run("pnpm", ["--filter", pkg.name, "--prod", "deploy", "--legacy", outDir], instanceDir);
|
|
1034
|
+
// pnpm exits 0 — "No projects matched the filters" — for a `--filter` that names no member.
|
|
1035
|
+
if (!(await exists(outDir))) {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
`pnpm deploy wrote no bundle for ${instanceDir}: it sits under the pnpm workspace at ${root} but is not ` +
|
|
1038
|
+
`one of its packages, and it has no lockfile of its own — add it to the workspace's \`packages:\`, ` +
|
|
1039
|
+
`or install and commit a lockfile so it bundles as a standalone instance`,
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1025
1042
|
return;
|
|
1026
1043
|
}
|
|
1027
1044
|
// Asked before the copy: an instance with no lockfile must fail on the cheap half.
|
|
@@ -1033,9 +1050,26 @@ export async function bundleInstance(
|
|
|
1033
1050
|
await run(command, args, outDir);
|
|
1034
1051
|
}
|
|
1035
1052
|
|
|
1036
|
-
/**
|
|
1037
|
-
*
|
|
1038
|
-
|
|
1053
|
+
/** Whether the instance carries a lockfile of ANY manager — yarn included, so a yarn instance under
|
|
1054
|
+
* a workspace reaches {@link lockfileInstall}'s named refusal rather than `pnpm deploy`. */
|
|
1055
|
+
async function hasOwnLockfile(instanceDir: string): Promise<boolean> {
|
|
1056
|
+
const present = new Set(await readdir(instanceDir));
|
|
1057
|
+
return [...LOCKFILE_INSTALLS.flatMap((row) => row.lockfiles), YARN_LOCKFILE].some((f) => present.has(f));
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async function exists(path: string): Promise<boolean> {
|
|
1061
|
+
try {
|
|
1062
|
+
await stat(path);
|
|
1063
|
+
return true;
|
|
1064
|
+
} catch {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/** The nearest `pnpm-workspace.yaml` at or above `dir`. A file test, not a manifest parse:
|
|
1070
|
+
* membership proper is pnpm's to decide, and this is asked only for an instance with no lockfile of
|
|
1071
|
+
* its own ({@link bundleInstance}). A `packages:` glob that excludes the directory does NOT fail
|
|
1072
|
+
* `pnpm deploy --filter` — pnpm exits 0 having deployed nothing — which the caller checks. */
|
|
1039
1073
|
async function pnpmWorkspaceRoot(dir: string): Promise<string | undefined> {
|
|
1040
1074
|
let d = resolve(dir);
|
|
1041
1075
|
for (;;) {
|