@rness/cli 0.5.1 → 0.5.3

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
@@ -110,11 +110,20 @@ clean — and carries on: nothing to remember, nothing to run.
110
110
  container image, or a machine that cannot install. A workspace still on 0.5.0
111
111
  has no such catch-up: moving it to 0.5.1 is the last manual install.
112
112
 
113
+ The catch-up needs one of two things: a launcher — the `rness` you typed,
114
+ usually the global install — at 0.5.1 or later, or an installed copy at 0.5.3
115
+ or later, which checks for itself whatever launched it. With an older global
116
+ and a workspace below 0.5.3, a pin that moved is neither installed nor
117
+ announced: `npm i -g @rness/cli@latest` once.
118
+
113
119
  Usually nobody runs `upgrade` at all. A workspace ships
114
120
  `.github/dependabot.yml`, so each release opens a pull request on the
115
121
  organization's `.rness`: the pin and the lockfile in the diff, `validate`
116
122
  running on the new version, review, merge. `upgrade` writes that file into
117
- workspaces created before 0.5.1.
123
+ workspaces created before 0.5.1. The pull request arrives 3 to 10 days after
124
+ a release: the check is weekly, and Dependabot holds any new version back for
125
+ 3 days by default, a guard against a compromised release. The file's header
126
+ says how to lift that delay for `@rness/cli`.
118
127
 
119
128
  `upgrade` is never delegated to the pinned copy, which is what it replaces
120
129
  — so a workspace pinned below 0.5.0, whose copy has no such command, starts
@@ -127,6 +136,33 @@ its content does.
127
136
  Exit codes: 0 success, 1 failure, 2 usage — or a refusal without a TTY.
128
137
  `RNESS_DEBUG=1` adds stack traces; `RNESS_NO_DELEGATE=1` skips the delegation.
129
138
 
139
+ ## 0.5.3 — catching up no longer depends on the global install
140
+
141
+ - After a pull that moved the pin, the next `rness` command installs it. That
142
+ check ran in the launcher only, so a global `@rness/cli` below 0.5.1 — which
143
+ delegates to the installed copy without looking — left the old version in
144
+ place and said nothing: `sync --check` answered `unchanged` with 0.5.1
145
+ blocks under a 0.5.2 pin. The installed copy now checks as well.
146
+ - Reach: the check lives in the installed copy, so it works from the first pin
147
+ move after a workspace holds 0.5.3. The move to 0.5.3 itself still needs a
148
+ launcher at 0.5.1 or later.
149
+ - The scaffold's `dependabot.yml` says why the pin's pull request arrives 3 to
150
+ 10 days after a release (a weekly check, plus Dependabot's default 3-day
151
+ cooldown) and gives the three lines that lift it for `@rness/cli`. The
152
+ default stays: for an adopting organization the CLI is a third-party
153
+ package, and the cooldown guards against a compromised release.
154
+
155
+ ## 0.5.2 — the block points at a file that exists
156
+
157
+ - The generated block told agents to read `.rness/STATUS.md`, a file neither
158
+ the scaffold nor any command writes. It now sends them to
159
+ `.rness/AGENTS.md`, which every workspace has and which sets the reading
160
+ order.
161
+ - The intro is part of the hashed body: every block written by 0.5.1 or
162
+ earlier is out of date under 0.5.2. Run `rness sync` once after upgrading,
163
+ then commit the refreshed `AGENTS.md` in each repository; until then
164
+ `rness sync --check` exits 1.
165
+
130
166
  ## 0.5.1 — the version of a workspace moves by pull request
131
167
 
132
168
  - Joining an organization pinned to an older `@rness/cli` no longer mentions
package/dist/bin/rness.js CHANGED
@@ -1,18 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ OWN_COMMANDS,
3
4
  VERSION,
4
- driftWarning,
5
- ensureInstalled,
5
+ catchUp,
6
6
  findDelegate,
7
- findWorkspace,
8
- pinDrift,
9
- pinnedBinPath,
10
7
  reportError,
11
8
  run
12
- } from "../chunk-C6HBGV5V.js";
9
+ } from "../chunk-CQ6YD63L.js";
13
10
 
14
11
  // src/bin/rness.ts
15
- import { spawnSync } from "child_process";
16
12
  import { pathToFileURL } from "url";
17
13
 
18
14
  // src/core/node-version.ts
@@ -39,39 +35,9 @@ async function main() {
39
35
  return 1;
40
36
  }
41
37
  const argv = process.argv.slice(2);
42
- const own = [
43
- "create",
44
- "upgrade",
45
- "update",
46
- "login",
47
- "logout",
48
- "git-credential"
49
- ].includes(argv[0] ?? "");
50
- const noDelegate = process.env["RNESS_NO_DELEGATE"] === "1";
51
- const skipDelegation = own || noDelegate;
52
- const command = argv[0];
53
- if (command !== void 0 && !command.startsWith("-") && !skipDelegation) {
54
- const ws = await findWorkspace(process.cwd()).catch(() => null);
55
- const outcome = ws === null ? null : await ensureInstalled(ws.rnessDir, {
56
- announce: (pin) => process.stderr.write(`installing @rness/cli ${pin} in .rness\u2026
57
- `)
58
- });
59
- if (outcome !== null && !outcome.installed) {
60
- const warning = await driftWarning(process.cwd());
61
- if (warning !== null) process.stderr.write(`${warning}
62
- `);
63
- }
64
- if (ws !== null && outcome?.installed === true && await pinDrift(ws.rnessDir) === null) {
65
- const pinnedBin = await pinnedBinPath(ws.rnessDir);
66
- if (pinnedBin !== null) {
67
- const { status } = spawnSync(process.execPath, [pinnedBin, ...argv], {
68
- cwd: process.cwd(),
69
- stdio: "inherit"
70
- });
71
- return status ?? 1;
72
- }
73
- }
74
- }
38
+ const skipDelegation = OWN_COMMANDS.includes(argv[0] ?? "") || process.env["RNESS_NO_DELEGATE"] === "1";
39
+ const handedOff = await catchUp(argv);
40
+ if (handedOff !== null) return handedOff;
75
41
  const delegate = skipDelegation ? null : await findDelegate(process.cwd(), VERSION);
76
42
  if (delegate !== null) {
77
43
  const broken = (reason) => new Error(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/rness.ts","../../src/core/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawnSync } from 'node:child_process'\nimport { pathToFileURL } from 'node:url'\n\nimport { run } from '../cli.ts'\nimport { findDelegate, pinnedBinPath } from '../core/delegate.ts'\nimport { MIN_NODE_MAJOR, isSupportedNode } from '../core/node-version.ts'\nimport { driftWarning, ensureInstalled, pinDrift } from '../core/pinned.ts'\nimport { findWorkspace } from '../core/workspace.ts'\nimport { reportError } from '../report.ts'\nimport { VERSION } from '../version.ts'\n\n// `rness context | head`: once the reader has its lines it closes the pipe and\n// every further write emits EPIPE on stdout. Nothing is left to deliver, so end\n// quietly with success. This is the one process.exit() in the code base — it\n// runs only when there is no output left to drain (see main() below).\nprocess.stdout.on('error', (e: NodeJS.ErrnoException) => {\n if (e.code === 'EPIPE') process.exit(0)\n throw e\n})\n\n// Set process.exitCode rather than calling process.exit(): process.exit()\n// terminates before Node drains an async stdout pipe, truncating piped output\n// at the ~64 KiB pipe buffer (breaks `rness context --json | jq`, `$(...)`).\nasync function main(): Promise<number> {\n if (!isSupportedNode(process.versions.node)) {\n process.stderr.write(\n `rness needs Node ${MIN_NODE_MAJOR} or newer (running ${process.version})\\n`\n )\n return 1\n }\n const argv = process.argv.slice(2)\n // `create` makes a workspace and `upgrade` replaces the pinned copy: both\n // are served by the copy the user invoked (spec 0006 §3). The login concerns\n // the machine, not a workspace — and git runs `git-credential` from inside\n // a clone, on every fetch: no delegation, and no warning there either.\n const own = [\n 'create',\n 'upgrade',\n 'update',\n 'login',\n 'logout',\n 'git-credential',\n ].includes(argv[0] ?? '')\n const noDelegate = process.env['RNESS_NO_DELEGATE'] === '1'\n const skipDelegation = own || noDelegate\n const command = argv[0]\n if (command !== undefined && !command.startsWith('-') && !skipDelegation) {\n const ws = await findWorkspace(process.cwd()).catch(() => null)\n // The version was decided by the pull request that moved the pin, so\n // catching up asks nothing (spec 0008 §4).\n const outcome =\n ws === null\n ? null\n : await ensureInstalled(ws.rnessDir, {\n announce: (pin) =>\n process.stderr.write(`installing @rness/cli ${pin} in .rness…\\n`),\n })\n if (outcome !== null && !outcome.installed) {\n const warning = await driftWarning(process.cwd())\n if (warning !== null) process.stderr.write(`${warning}\\n`)\n }\n // The pinned tree was just rebuilt: importing out of it in this process is\n // fragile, so the freshly installed copy runs as its own process.\n // Only hand off when the install actually closed the drift: a child that\n // would install again on every run must never be spawned.\n if (\n ws !== null &&\n outcome?.installed === true &&\n (await pinDrift(ws.rnessDir)) === null\n ) {\n const pinnedBin = await pinnedBinPath(ws.rnessDir)\n if (pinnedBin !== null) {\n const { status } = spawnSync(process.execPath, [pinnedBin, ...argv], {\n cwd: process.cwd(),\n stdio: 'inherit',\n })\n return status ?? 1\n }\n }\n }\n const delegate = skipDelegation\n ? null\n : await findDelegate(process.cwd(), VERSION)\n if (delegate !== null) {\n const broken = (reason: string): Error =>\n new Error(\n `@rness/cli ${delegate.version} pinned in ${delegate.root}/.rness is not installed correctly (${reason}); reinstall in .rness/`\n )\n if (process.env['RNESS_DEBUG'] === '1') {\n process.stderr.write(\n `rness: delegating to @rness/cli ${delegate.version} (${delegate.entry})\\n`\n )\n }\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(delegate.entry).href)) as Record<\n string,\n unknown\n >\n } catch (e) {\n throw broken(e instanceof Error ? e.message : String(e))\n }\n if (typeof mod['run'] !== 'function')\n throw broken('dist/index.js does not export run')\n const delegateRun = mod['run'] as (argv: string[]) => Promise<unknown>\n const code = await delegateRun(argv)\n return typeof code === 'number' ? code : 1\n }\n return run(argv)\n}\n\nmain()\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n process.exitCode = reportError(e)\n })\n","export const MIN_NODE_MAJOR = 24\n\nexport function nodeMajor(version: string): number {\n const major = Number.parseInt(version.split('.')[0] ?? '', 10)\n return Number.isNaN(major) ? 0 : major\n}\n\nexport function isSupportedNode(version: string): boolean {\n return nodeMajor(version) >= MIN_NODE_MAJOR\n}\n"],"mappings":";;;;;;;;;;;;;;AACA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;;;ACFvB,IAAM,iBAAiB;AAEvB,SAAS,UAAU,SAAyB;AACjD,QAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE;AAC7D,SAAO,OAAO,MAAM,KAAK,IAAI,IAAI;AACnC;AAEO,SAAS,gBAAgB,SAA0B;AACxD,SAAO,UAAU,OAAO,KAAK;AAC/B;;;ADOA,QAAQ,OAAO,GAAG,SAAS,CAAC,MAA6B;AACvD,MAAI,EAAE,SAAS,QAAS,SAAQ,KAAK,CAAC;AACtC,QAAM;AACR,CAAC;AAKD,eAAe,OAAwB;AACrC,MAAI,CAAC,gBAAgB,QAAQ,SAAS,IAAI,GAAG;AAC3C,YAAQ,OAAO;AAAA,MACb,oBAAoB,cAAc,sBAAsB,QAAQ,OAAO;AAAA;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAKjC,QAAM,MAAM;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,SAAS,KAAK,CAAC,KAAK,EAAE;AACxB,QAAM,aAAa,QAAQ,IAAI,mBAAmB,MAAM;AACxD,QAAM,iBAAiB,OAAO;AAC9B,QAAM,UAAU,KAAK,CAAC;AACtB,MAAI,YAAY,UAAa,CAAC,QAAQ,WAAW,GAAG,KAAK,CAAC,gBAAgB;AACxE,UAAM,KAAK,MAAM,cAAc,QAAQ,IAAI,CAAC,EAAE,MAAM,MAAM,IAAI;AAG9D,UAAM,UACJ,OAAO,OACH,OACA,MAAM,gBAAgB,GAAG,UAAU;AAAA,MACjC,UAAU,CAAC,QACT,QAAQ,OAAO,MAAM,yBAAyB,GAAG;AAAA,CAAe;AAAA,IACpE,CAAC;AACP,QAAI,YAAY,QAAQ,CAAC,QAAQ,WAAW;AAC1C,YAAM,UAAU,MAAM,aAAa,QAAQ,IAAI,CAAC;AAChD,UAAI,YAAY,KAAM,SAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,IAC3D;AAKA,QACE,OAAO,QACP,SAAS,cAAc,QACtB,MAAM,SAAS,GAAG,QAAQ,MAAO,MAClC;AACA,YAAM,YAAY,MAAM,cAAc,GAAG,QAAQ;AACjD,UAAI,cAAc,MAAM;AACtB,cAAM,EAAE,OAAO,IAAI,UAAU,QAAQ,UAAU,CAAC,WAAW,GAAG,IAAI,GAAG;AAAA,UACnE,KAAK,QAAQ,IAAI;AAAA,UACjB,OAAO;AAAA,QACT,CAAC;AACD,eAAO,UAAU;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,QAAM,WAAW,iBACb,OACA,MAAM,aAAa,QAAQ,IAAI,GAAG,OAAO;AAC7C,MAAI,aAAa,MAAM;AACrB,UAAM,SAAS,CAAC,WACd,IAAI;AAAA,MACF,cAAc,SAAS,OAAO,cAAc,SAAS,IAAI,uCAAuC,MAAM;AAAA,IACxG;AACF,QAAI,QAAQ,IAAI,aAAa,MAAM,KAAK;AACtC,cAAQ,OAAO;AAAA,QACb,mCAAmC,SAAS,OAAO,KAAK,SAAS,KAAK;AAAA;AAAA,MACxE;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO,cAAc,SAAS,KAAK,EAAE;AAAA,IAIpD,SAAS,GAAG;AACV,YAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzD;AACA,QAAI,OAAO,IAAI,KAAK,MAAM;AACxB,YAAM,OAAO,mCAAmC;AAClD,UAAM,cAAc,IAAI,KAAK;AAC7B,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,WAAO,OAAO,SAAS,WAAW,OAAO;AAAA,EAC3C;AACA,SAAO,IAAI,IAAI;AACjB;AAEA,KAAK,EACF,KAAK,CAAC,SAAS;AACd,UAAQ,WAAW;AACrB,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAQ,WAAW,YAAY,CAAC;AAClC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/bin/rness.ts","../../src/core/node-version.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { pathToFileURL } from 'node:url'\n\nimport { run } from '../cli.ts'\nimport { OWN_COMMANDS, catchUp } from '../core/catch-up.ts'\nimport { findDelegate } from '../core/delegate.ts'\nimport { MIN_NODE_MAJOR, isSupportedNode } from '../core/node-version.ts'\nimport { reportError } from '../report.ts'\nimport { VERSION } from '../version.ts'\n\n// `rness context | head`: once the reader has its lines it closes the pipe and\n// every further write emits EPIPE on stdout. Nothing is left to deliver, so end\n// quietly with success. This is the one process.exit() in the code base — it\n// runs only when there is no output left to drain (see main() below).\nprocess.stdout.on('error', (e: NodeJS.ErrnoException) => {\n if (e.code === 'EPIPE') process.exit(0)\n throw e\n})\n\n// Set process.exitCode rather than calling process.exit(): process.exit()\n// terminates before Node drains an async stdout pipe, truncating piped output\n// at the ~64 KiB pipe buffer (breaks `rness context --json | jq`, `$(...)`).\nasync function main(): Promise<number> {\n if (!isSupportedNode(process.versions.node)) {\n process.stderr.write(\n `rness needs Node ${MIN_NODE_MAJOR} or newer (running ${process.version})\\n`\n )\n return 1\n }\n const argv = process.argv.slice(2)\n const skipDelegation =\n OWN_COMMANDS.includes(argv[0] ?? '') ||\n process.env['RNESS_NO_DELEGATE'] === '1'\n // A pin that moved is installed first, and the fresh copy runs the command\n // as its own process (spec 0008 §4).\n const handedOff = await catchUp(argv)\n if (handedOff !== null) return handedOff\n const delegate = skipDelegation\n ? null\n : await findDelegate(process.cwd(), VERSION)\n if (delegate !== null) {\n const broken = (reason: string): Error =>\n new Error(\n `@rness/cli ${delegate.version} pinned in ${delegate.root}/.rness is not installed correctly (${reason}); reinstall in .rness/`\n )\n if (process.env['RNESS_DEBUG'] === '1') {\n process.stderr.write(\n `rness: delegating to @rness/cli ${delegate.version} (${delegate.entry})\\n`\n )\n }\n let mod: Record<string, unknown>\n try {\n mod = (await import(pathToFileURL(delegate.entry).href)) as Record<\n string,\n unknown\n >\n } catch (e) {\n throw broken(e instanceof Error ? e.message : String(e))\n }\n if (typeof mod['run'] !== 'function')\n throw broken('dist/index.js does not export run')\n const delegateRun = mod['run'] as (argv: string[]) => Promise<unknown>\n const code = await delegateRun(argv)\n return typeof code === 'number' ? code : 1\n }\n return run(argv)\n}\n\nmain()\n .then((code) => {\n process.exitCode = code\n })\n .catch((e: unknown) => {\n process.exitCode = reportError(e)\n })\n","export const MIN_NODE_MAJOR = 24\n\nexport function nodeMajor(version: string): number {\n const major = Number.parseInt(version.split('.')[0] ?? '', 10)\n return Number.isNaN(major) ? 0 : major\n}\n\nexport function isSupportedNode(version: string): boolean {\n return nodeMajor(version) >= MIN_NODE_MAJOR\n}\n"],"mappings":";;;;;;;;;;;AACA,SAAS,qBAAqB;;;ACDvB,IAAM,iBAAiB;AAEvB,SAAS,UAAU,SAAyB;AACjD,QAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE;AAC7D,SAAO,OAAO,MAAM,KAAK,IAAI,IAAI;AACnC;AAEO,SAAS,gBAAgB,SAA0B;AACxD,SAAO,UAAU,OAAO,KAAK;AAC/B;;;ADKA,QAAQ,OAAO,GAAG,SAAS,CAAC,MAA6B;AACvD,MAAI,EAAE,SAAS,QAAS,SAAQ,KAAK,CAAC;AACtC,QAAM;AACR,CAAC;AAKD,eAAe,OAAwB;AACrC,MAAI,CAAC,gBAAgB,QAAQ,SAAS,IAAI,GAAG;AAC3C,YAAQ,OAAO;AAAA,MACb,oBAAoB,cAAc,sBAAsB,QAAQ,OAAO;AAAA;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,iBACJ,aAAa,SAAS,KAAK,CAAC,KAAK,EAAE,KACnC,QAAQ,IAAI,mBAAmB,MAAM;AAGvC,QAAM,YAAY,MAAM,QAAQ,IAAI;AACpC,MAAI,cAAc,KAAM,QAAO;AAC/B,QAAM,WAAW,iBACb,OACA,MAAM,aAAa,QAAQ,IAAI,GAAG,OAAO;AAC7C,MAAI,aAAa,MAAM;AACrB,UAAM,SAAS,CAAC,WACd,IAAI;AAAA,MACF,cAAc,SAAS,OAAO,cAAc,SAAS,IAAI,uCAAuC,MAAM;AAAA,IACxG;AACF,QAAI,QAAQ,IAAI,aAAa,MAAM,KAAK;AACtC,cAAQ,OAAO;AAAA,QACb,mCAAmC,SAAS,OAAO,KAAK,SAAS,KAAK;AAAA;AAAA,MACxE;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO,cAAc,SAAS,KAAK,EAAE;AAAA,IAIpD,SAAS,GAAG;AACV,YAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzD;AACA,QAAI,OAAO,IAAI,KAAK,MAAM;AACxB,YAAM,OAAO,mCAAmC;AAClD,UAAM,cAAc,IAAI,KAAK;AAC7B,UAAM,OAAO,MAAM,YAAY,IAAI;AACnC,WAAO,OAAO,SAAS,WAAW,OAAO;AAAA,EAC3C;AACA,SAAO,IAAI,IAAI;AACjB;AAEA,KAAK,EACF,KAAK,CAAC,SAAS;AACd,UAAQ,WAAW;AACrB,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,UAAQ,WAAW,YAAY,CAAC;AAClC,CAAC;","names":[]}