rigline 1.0.0-alpha.11 → 1.0.0-alpha.13

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
@@ -10,8 +10,9 @@ rather than a breakage to chase.
10
10
 
11
11
  **This modifies files belonging to Anthropic's extension, on your machine, and that is your call to
12
12
  make.** [Anthropic compliance](https://github.com/Rigline/Rigline/blob/main/docs/anthropic-compliance.md)
13
- is a straight account of what Rigline does and does not do — no credentials, no network calls, no
14
- rerouting of Claude usage, no redistribution — and how we read Anthropic's terms. `rigline restore`
13
+ is a straight account of what Rigline does to that extension and what it does not — no credentials,
14
+ no network calls from the panel, no rerouting of Claude usage, no redistribution — and how we read
15
+ Anthropic's terms. `rigline restore`
15
16
  puts every install back to Anthropic's own bytes and needs nothing but Node.
16
17
 
17
18
  ## Install
@@ -21,7 +22,8 @@ puts every install back to Anthropic's own bytes and needs nothing but Node.
21
22
 
22
23
  The first command you run fetches the engine, [`@rigline/core`](https://www.npmjs.com/package/@rigline/core),
23
24
  into `~/.rigline/engine` and runs it from there. It takes a few seconds and needs the registry;
24
- after that nothing reaches the network unless you ask it to. If that directory is ever in a bad
25
+ after that nothing reaches the network unless you ask it to, or install the companion below, which
26
+ asks the registry for a newer engine each time a window starts. If that directory is ever in a bad
25
27
  state, delete it and run any command again.
26
28
 
27
29
  `install` finds every installed version of the extension, harvests it, bakes the discovered plugins
@@ -39,12 +41,15 @@ directory until *Developer: Reload Window*.
39
41
  rigline vscode-setup
40
42
 
41
43
  If running `install` after every update is a chore, this installs a companion extension that does it
42
- for you — into every VS Code found on your `PATH`, Insiders, VSCodium, Cursor and Windsurf included,
43
- from a VSIX that ships inside the engine. Nothing is downloaded, and the companion moves when the
44
- engine does. It injects as it goes, so it is a step *instead of* `install` rather than after it.
44
+ for you — into VS Code, found on your `PATH`, from a VSIX that ships inside the engine. Nothing is
45
+ downloaded, and the companion moves when the engine does. It injects as it goes, so it is a step
46
+ *instead of* `install` rather than after it.
45
47
 
46
48
  From then on it watches for the extension update and re-injects behind it, so there is nothing to
47
- remember after one.
49
+ remember after one. It goes into every VS Code profile that has Claude Code, and into any profile
50
+ that gets Claude Code later; the `companion` settings in
51
+ [`~/.rigline/config.yaml`](https://github.com/Rigline/Rigline/blob/main/docs/config.md) keep it out
52
+ of the profiles you name.
48
53
 
49
54
  **Decline it and lose nothing.** `install` remains complete on its own, and is the right answer if
50
55
  you would rather not add an extension, cannot install one, or simply prefer running the thing
@@ -83,10 +88,11 @@ going back is one command.
83
88
 
84
89
  ## How it is put together
85
90
 
86
- `rigline` is a small retrieval layer. It fetches bytes — a plugin tarball, the engine — checks them,
87
- and hands them over; `@rigline/core` does everything else and answers every verb above. The split is
88
- not decoration: a process cannot replace the package it is running out of, so whatever performs an
89
- update has to sit above the thing being updated.
91
+ `rigline` is a small retrieval layer. It installs the engine and runs it, and `@rigline/core` answers
92
+ every verb above, fetching plugins included. The split is not decoration: a process cannot replace
93
+ the package it is running out of, so `update` moves the engine from here and then hands the rest to
94
+ it. Nothing updates `rigline` itself, so when the engine you have is newer, `rigline` says so and
95
+ names the command.
90
96
 
91
97
  **It belongs in no project's dependencies.** If you are writing plugins, your workspace declares
92
98
  `@rigline/core` and runs `rigline-engine`; `npm create rigline-plugin` scaffolds it that way. A
@@ -0,0 +1,61 @@
1
+ import { ENGINE_PACKAGE, engineDir, ensureEngine, formatEngineUpdate, handOffProblem, newerWrapper, readEngineState, riglineHome, updateEngine, wrapperVersion, } from "./engine.js";
2
+ import { UserError } from "./errors.js";
3
+ export function splitUpdateArgs(args) {
4
+ let tag;
5
+ let now = false;
6
+ const forward = [];
7
+ for (let i = 0; i < args.length; i++) {
8
+ const arg = args[i];
9
+ if (arg === "--") {
10
+ forward.push(...args.slice(i));
11
+ break;
12
+ }
13
+ if (arg === "--tag" || arg.startsWith("--tag=")) {
14
+ tag = arg === "--tag" ? args[++i] : arg.slice("--tag=".length);
15
+ if (tag === undefined || tag === "")
16
+ throw new UserError("--tag needs a dist-tag");
17
+ continue;
18
+ }
19
+ if (arg === "--now")
20
+ now = true;
21
+ forward.push(arg);
22
+ }
23
+ return { ...(tag === undefined ? {} : { tag }), now, forward };
24
+ }
25
+ export async function updateCommand(args, options = {}) {
26
+ const { tag, now, forward } = splitUpdateArgs(args);
27
+ const given = {
28
+ ...options,
29
+ registry: { ...options.registry, ignoreReleaseAge: now },
30
+ };
31
+ const moved = await updateEngine({ ...given, ...(tag === undefined ? {} : { tag }) });
32
+ console.log(formatEngineUpdate(moved, options.home ?? riglineHome()));
33
+ const engine = await ensureEngine(given);
34
+ const wrapper = options.version ?? wrapperVersion();
35
+ const problem = handOffProblem(wrapper, engine.version);
36
+ if (problem !== null)
37
+ throw new UserError(problem);
38
+ notice(engine.version, wrapper);
39
+ const code = await engine.run(["update", ...forward]);
40
+ return moved.outcome === "failed" ? 1 : code;
41
+ }
42
+ export function notice(engine, wrapper = wrapperVersion()) {
43
+ const line = newerWrapper(wrapper, engine);
44
+ if (line !== null)
45
+ console.error(line);
46
+ }
47
+ export function versionCommand() {
48
+ const prefix = engineDir();
49
+ const state = readEngineState(prefix);
50
+ const wrapper = wrapperVersion();
51
+ console.log(`rigline ${wrapper}`);
52
+ console.log(state.kind === "none"
53
+ ? `engine: none in ${prefix} — the next command installs one`
54
+ : `engine: ${ENGINE_PACKAGE} ${state.version} in ${prefix}` +
55
+ (state.kind === "unusable" ? ` (unusable: ${state.why})` : ""));
56
+ const newer = state.kind === "none" ? null : newerWrapper(wrapper, state.version);
57
+ if (newer !== null)
58
+ console.log(newer);
59
+ return 0;
60
+ }
61
+ //# sourceMappingURL=commands.js.map
package/dist/engine.js CHANGED
@@ -61,6 +61,61 @@ export function majorProblem(wrapper, engine) {
61
61
  return (`rigline ${ours}.x cannot run engine ${theirs}.x (${ENGINE_PACKAGE} ${engine}). ` +
62
62
  "Run npm i -g rigline@latest.");
63
63
  }
64
+ export function compareVersions(a, b) {
65
+ const x = parseVersion(a);
66
+ const y = parseVersion(b);
67
+ if (x === null || y === null)
68
+ return null;
69
+ for (let i = 0; i < 3; i++) {
70
+ const diff = x.release[i] - y.release[i];
71
+ if (diff !== 0)
72
+ return diff;
73
+ }
74
+ if (x.pre.length === 0 || y.pre.length === 0)
75
+ return y.pre.length - x.pre.length;
76
+ for (let i = 0; i < Math.max(x.pre.length, y.pre.length); i++) {
77
+ const p = x.pre[i];
78
+ const q = y.pre[i];
79
+ if (p === undefined)
80
+ return -1;
81
+ if (q === undefined)
82
+ return 1;
83
+ if (p === q)
84
+ continue;
85
+ const pNumeric = /^\d+$/.test(p);
86
+ const qNumeric = /^\d+$/.test(q);
87
+ if (pNumeric && qNumeric)
88
+ return Number(p) - Number(q);
89
+ if (pNumeric !== qNumeric)
90
+ return pNumeric ? -1 : 1;
91
+ return p < q ? -1 : 1;
92
+ }
93
+ return 0;
94
+ }
95
+ function parseVersion(version) {
96
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
97
+ if (match === null)
98
+ return null;
99
+ return {
100
+ release: [Number(match[1]), Number(match[2]), Number(match[3])],
101
+ pre: match[4] === undefined ? [] : match[4].split("."),
102
+ };
103
+ }
104
+ export function newerWrapper(wrapper, engine) {
105
+ const order = compareVersions(engine, wrapper);
106
+ if (order === null || order <= 0)
107
+ return null;
108
+ return `rigline ${engine} is out, and this is ${wrapper}: npm i -g rigline@${engine}`;
109
+ }
110
+ const LAST_WITHOUT_UPDATE = "1.0.0-alpha.12";
111
+ export function handOffProblem(wrapper, engine) {
112
+ const behind = compareVersions(engine, wrapper);
113
+ const old = compareVersions(engine, LAST_WITHOUT_UPDATE);
114
+ if (behind === null || old === null || behind >= 0 || old > 0)
115
+ return null;
116
+ return (`${ENGINE_PACKAGE} ${engine} does no update of its own, so no plugin was updated. ` +
117
+ `Run rigline update again once the engine has moved past ${LAST_WITHOUT_UPDATE}.`);
118
+ }
64
119
  export function findNpmCli(execPath = process.execPath, exists = existsSync) {
65
120
  const here = dirname(execPath);
66
121
  const candidates = [
@@ -88,7 +143,7 @@ export function engineInstallArgv(options) {
88
143
  ...options.specs,
89
144
  ];
90
145
  }
91
- const nodeSpawn = (command, args, options) => spawn(command, [...args], { stdio: [...options.stdio] });
146
+ const nodeSpawn = (command, args, options) => spawn(command, [...args], { stdio: [...options.stdio], windowsHide: true });
92
147
  export async function ensureEngine(options = {}) {
93
148
  const prefix = engineDir(options.home ?? riglineHome());
94
149
  const version = options.version ?? wrapperVersion();
@@ -108,7 +163,7 @@ export async function ensureEngine(options = {}) {
108
163
  const problem = majorProblem(version, resolved.version);
109
164
  if (problem !== null)
110
165
  throw new UserError(problem);
111
- console.log(`rigline: installing the engine, ${ENGINE_PACKAGE} ${resolved.version}`);
166
+ console.error(`rigline: installing the engine, ${ENGINE_PACKAGE} ${resolved.version}`);
112
167
  await installEngine(prefix, `${ENGINE_PACKAGE}@${resolved.version}`, options);
113
168
  const installed = readEngineState(prefix);
114
169
  if (installed.kind !== "ready") {
@@ -145,23 +200,11 @@ function engineAt(state, options) {
145
200
  child.on("error", fail);
146
201
  child.on("close", (code) => done(code ?? 1));
147
202
  }),
148
- json: async (argv) => {
149
- const run = await capture(node, [state.entry, ...argv], spawnImpl, "inherit");
150
- if (run.code !== 0) {
151
- throw new UserError(`the engine exited ${run.code} for \`${argv.join(" ")}\``);
152
- }
153
- try {
154
- return JSON.parse(run.output);
155
- }
156
- catch {
157
- throw new UserError(`the engine answered \`${argv.join(" ")}\` with something that is not JSON`);
158
- }
159
- },
160
203
  };
161
204
  }
162
- async function capture(command, argv, spawnImpl = nodeSpawn, stderr = "pipe") {
205
+ async function capture(command, argv, spawnImpl = nodeSpawn) {
163
206
  return await new Promise((done, fail) => {
164
- const child = spawnImpl(command, argv, { stdio: ["ignore", "pipe", stderr] });
207
+ const child = spawnImpl(command, argv, { stdio: ["ignore", "pipe", "pipe"] });
165
208
  let output = "";
166
209
  child.stdout?.setEncoding("utf8");
167
210
  child.stdout?.on("data", (chunk) => {
@@ -198,6 +241,7 @@ export async function updateEngine(options = {}) {
198
241
  return {
199
242
  outcome: "failed",
200
243
  ...(installed === null ? {} : { from: installed }),
244
+ to: resolved.version,
201
245
  reason: problem,
202
246
  };
203
247
  }
package/dist/index.js CHANGED
@@ -1,84 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs } from "node:util";
3
- import { ENGINE_PACKAGE, engineDir, ensureEngine, formatEngineUpdate, readEngineState, updateEngine, wrapperVersion, } from "./engine.js";
2
+ import { notice, updateCommand, versionCommand } from "./commands.js";
3
+ import { ensureEngine } from "./engine.js";
4
4
  import { UserError } from "./errors.js";
5
- import { addFromNpm, formatUpdates, updatePlugins } from "./remote.js";
6
- function isPathSpec(spec) {
7
- if (spec.startsWith("@"))
8
- return false;
9
- return spec.startsWith(".") || spec.includes("/") || spec.includes("\\");
10
- }
11
- async function addCommand(args) {
12
- const { values, positionals } = parseArgs({
13
- args,
14
- options: { now: { type: "boolean", default: false } },
15
- allowPositionals: true,
16
- });
17
- if (positionals.length !== 1) {
18
- throw new UserError("add needs exactly one plugin directory or npm package");
19
- }
20
- const spec = positionals[0];
21
- const engine = await ensureEngine();
22
- if (isPathSpec(spec))
23
- return await engine.run(["add", spec]);
24
- return await addFromNpm({
25
- spec,
26
- engine: engine.run,
27
- registry: { ignoreReleaseAge: values.now },
28
- });
29
- }
30
- async function listed(engine) {
31
- const answer = await engine.json(["list", "--json"]);
32
- if (!Array.isArray(answer))
33
- throw new UserError("the engine's plugin list was not a list");
34
- return answer;
35
- }
36
- async function updateCommand(args) {
37
- const { values, positionals } = parseArgs({
38
- args,
39
- options: { now: { type: "boolean", default: false }, tag: { type: "string" } },
40
- allowPositionals: true,
41
- });
42
- const registry = { ignoreReleaseAge: values.now };
43
- const engineUpdate = await updateEngine({ registry, ...(values.tag ? { tag: values.tag } : {}) });
44
- console.log(formatEngineUpdate(engineUpdate));
45
- const engine = await ensureEngine({ registry });
46
- const updates = await updatePlugins({
47
- listed: await listed(engine),
48
- engine: engine.run,
49
- ...(positionals.length > 0 ? { names: positionals } : {}),
50
- registry,
51
- });
52
- let injected = 0;
53
- if (engineUpdate.outcome === "moved" || engineUpdate.outcome === "installed") {
54
- injected = await engine.run(["install"]);
55
- }
56
- console.log(formatUpdates(updates));
57
- const failed = updates.some((u) => u.outcome === "failed") || engineUpdate.outcome === "failed";
58
- return failed || injected !== 0 ? 1 : 0;
59
- }
60
- function versionCommand() {
61
- const prefix = engineDir();
62
- const state = readEngineState(prefix);
63
- console.log(`rigline ${wrapperVersion()}`);
64
- console.log(state.kind === "none"
65
- ? `engine: none in ${prefix} — the next command installs one`
66
- : `engine: ${ENGINE_PACKAGE} ${state.version} in ${prefix}` +
67
- (state.kind === "unusable" ? ` (unusable: ${state.why})` : ""));
68
- return 0;
69
- }
70
5
  async function main(argv) {
71
6
  const [command, ...rest] = argv;
72
7
  switch (command) {
73
8
  case "--version":
74
9
  case "-v":
75
10
  return versionCommand();
76
- case "add":
77
- return await addCommand(rest);
78
11
  case "update":
79
12
  return await updateCommand(rest);
80
13
  default: {
81
14
  const engine = await ensureEngine();
15
+ notice(engine.version);
82
16
  return await engine.run(argv);
83
17
  }
84
18
  }
package/dist/lock.js CHANGED
@@ -1,4 +1,4 @@
1
- import { closeSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { UserError } from "./errors.js";
4
4
  const STALE_MS = 60_000;
@@ -31,12 +31,18 @@ function readHolder(path) {
31
31
  return null;
32
32
  }
33
33
  }
34
- export function isStale(holder, now, staleMs, isAlive) {
35
- if (holder === null)
36
- return true;
37
- const since = Date.parse(holder.since);
34
+ function writtenAt(path) {
35
+ try {
36
+ return statSync(path).mtimeMs;
37
+ }
38
+ catch {
39
+ return Number.NaN;
40
+ }
41
+ }
42
+ export function isStale(holder, fileTime, now, staleMs, isAlive) {
43
+ const since = holder === null ? fileTime : Date.parse(holder.since);
38
44
  const old = Number.isNaN(since) || now - since >= staleMs;
39
- return old && !isAlive(holder.pid);
45
+ return old && (holder === null || !isAlive(holder.pid));
40
46
  }
41
47
  export class HomeLockedError extends UserError {
42
48
  holder;
@@ -69,7 +75,7 @@ export async function withHomeLock(options, work) {
69
75
  if (error.code !== "EEXIST")
70
76
  throw error;
71
77
  const held = readHolder(path);
72
- if (isStale(held, now(), staleMs, isAlive)) {
78
+ if (isStale(held, writtenAt(path), now(), staleMs, isAlive)) {
73
79
  rmSync(path, { force: true });
74
80
  continue;
75
81
  }
package/dist/registry.js CHANGED
@@ -1,21 +1,6 @@
1
- import { createHash } from "node:crypto";
2
1
  import { UserError } from "./errors.js";
3
2
  export const DEFAULT_REGISTRY = "https://registry.npmjs.org";
4
3
  export const MINIMUM_RELEASE_AGE_MINUTES = 1440;
5
- export function parsePluginSpec(spec) {
6
- const at = spec.lastIndexOf("@");
7
- const scoped = spec.startsWith("@");
8
- if (at <= 0 || (scoped && at === 0)) {
9
- return { name: spec, version: null, tag: null };
10
- }
11
- const name = spec.slice(0, at);
12
- const rest = spec.slice(at + 1);
13
- if (rest.length === 0)
14
- return { name, version: null, tag: null };
15
- return /^\d+\.\d+\.\d+(?:[-+].*)?$/.test(rest)
16
- ? { name, version: rest, tag: null }
17
- : { name, version: null, tag: rest };
18
- }
19
4
  export async function resolveVersion(spec, options = {}) {
20
5
  const packument = await fetchPackument(spec.name, options);
21
6
  const tag = spec.version === null ? (spec.tag ?? "latest") : null;
@@ -41,33 +26,6 @@ export async function resolveVersion(spec, options = {}) {
41
26
  ageMinutes,
42
27
  };
43
28
  }
44
- export async function fetchTarball(resolved, options = {}) {
45
- const response = await request(resolved.tarball, options);
46
- const bytes = Buffer.from(await response.arrayBuffer());
47
- const problem = integrityProblem(bytes, resolved.integrity);
48
- if (problem !== null) {
49
- throw new UserError(`${resolved.name}@${resolved.version} from ${resolved.tarball} ${problem}. ` +
50
- "Nothing was written.");
51
- }
52
- return bytes;
53
- }
54
- export function integrityProblem(bytes, integrity) {
55
- const dash = integrity.indexOf("-");
56
- if (dash <= 0)
57
- return `has an integrity string this cannot read ("${integrity}")`;
58
- const algorithm = integrity.slice(0, dash);
59
- const expected = integrity.slice(dash + 1);
60
- let actual;
61
- try {
62
- actual = createHash(algorithm).update(bytes).digest("base64");
63
- }
64
- catch {
65
- return `names a digest this cannot compute ("${algorithm}")`;
66
- }
67
- return actual === expected
68
- ? null
69
- : `does not match its recorded integrity (${algorithm}-${actual} against ${integrity})`;
70
- }
71
29
  export function releaseAgeProblem(resolved, options = {}) {
72
30
  if (options.ignoreReleaseAge)
73
31
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rigline",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.13",
4
4
  "description": "Install, update and develop plugins for the Claude Code VS Code extension",
5
5
  "keywords": [
6
6
  "rigline",
package/dist/remote.js DELETED
@@ -1,132 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { dirname, join } from "node:path";
4
- import { UserError } from "./errors.js";
5
- import { fetchTarball, parsePluginSpec, releaseAgeProblem, resolveVersion, } from "./registry.js";
6
- import { readPackageTarball } from "./tarball.js";
7
- export async function addFromNpm(options) {
8
- const resolved = await resolveVersion(parsePluginSpec(options.spec), options.registry);
9
- const withheld = releaseAgeProblem(resolved, options.registry);
10
- if (withheld !== null)
11
- throw new UserError(withheld);
12
- return await stageAndAdd(resolved, options);
13
- }
14
- async function stageAndAdd(resolved, options) {
15
- const label = `${resolved.name}@${resolved.version}`;
16
- const files = readPackageTarball(await fetchTarball(resolved, options.registry), label);
17
- const staged = mkdtempSync(join(tmpdir(), "rigline-add-"));
18
- try {
19
- for (const file of files) {
20
- const target = join(staged, file.path);
21
- mkdirSync(dirname(target), { recursive: true });
22
- writeFileSync(target, file.bytes);
23
- }
24
- const source = {
25
- kind: "npm",
26
- name: resolved.name,
27
- version: resolved.version,
28
- tag: resolved.tag,
29
- integrity: resolved.integrity,
30
- addedAt: (options.now?.() ?? new Date()).toISOString(),
31
- };
32
- return await options.engine(["add", staged, "--source", JSON.stringify(source)]);
33
- }
34
- finally {
35
- rmSync(staged, { recursive: true, force: true });
36
- }
37
- }
38
- export async function updatePlugins(options) {
39
- const managed = options.listed.filter((plugin) => plugin.managed);
40
- const byName = new Map(managed.map((plugin) => [plugin.name, plugin]));
41
- const names = options.names ?? managed.map((plugin) => plugin.name);
42
- const updates = [];
43
- for (const name of names) {
44
- const plugin = byName.get(name);
45
- if (plugin === undefined) {
46
- updates.push({ name, outcome: "failed", reason: "not installed by rigline" });
47
- continue;
48
- }
49
- const source = plugin.source;
50
- if (source === null) {
51
- updates.push({ name, outcome: "unmanaged" });
52
- continue;
53
- }
54
- if (source.kind === "path") {
55
- updates.push({
56
- name,
57
- outcome: "local",
58
- reason: typeof source.from === "string" ? source.from : "a directory",
59
- });
60
- continue;
61
- }
62
- if (source.kind !== "npm") {
63
- updates.push({ name, outcome: "unmanaged", reason: `source kind "${source.kind}"` });
64
- continue;
65
- }
66
- if (source.name === undefined || source.version === undefined) {
67
- updates.push({ name, outcome: "failed", reason: "its npm source is incomplete" });
68
- continue;
69
- }
70
- if (source.tag === null || source.tag === undefined) {
71
- updates.push({ name, outcome: "pinned", from: source.version });
72
- continue;
73
- }
74
- updates.push(await updateOne(name, { name: source.name, version: source.version, tag: source.tag }, options));
75
- }
76
- return updates;
77
- }
78
- async function updateOne(name, source, options) {
79
- try {
80
- const resolved = await resolveVersion({ name: source.name, version: null, tag: source.tag }, options.registry);
81
- if (resolved.version === source.version) {
82
- return { name, outcome: "current", from: source.version };
83
- }
84
- const withheld = releaseAgeProblem(resolved, options.registry);
85
- if (withheld !== null) {
86
- return {
87
- name,
88
- outcome: "withheld",
89
- from: source.version,
90
- to: resolved.version,
91
- reason: withheld,
92
- };
93
- }
94
- const code = await stageAndAdd(resolved, options);
95
- if (code !== 0) {
96
- return { name, outcome: "failed", from: source.version, reason: `the engine exited ${code}` };
97
- }
98
- return { name, outcome: "updated", from: source.version, to: resolved.version };
99
- }
100
- catch (error) {
101
- return {
102
- name,
103
- outcome: "failed",
104
- from: source.version,
105
- reason: error instanceof Error ? error.message : String(error),
106
- };
107
- }
108
- }
109
- export function formatUpdates(updates) {
110
- if (updates.length === 0)
111
- return "no plugins to update — the bundled ones move with the engine";
112
- return updates.map(updateLine).join("\n");
113
- }
114
- function updateLine(update) {
115
- switch (update.outcome) {
116
- case "updated":
117
- return `${update.name}: ${update.from} -> ${update.to}`;
118
- case "current":
119
- return `${update.name}: ${update.from}, which is what its tag resolves to`;
120
- case "pinned":
121
- return `${update.name}: pinned to ${update.from}; add it again to move it`;
122
- case "local":
123
- return `${update.name}: added from ${update.reason}; run rigline add again to refresh it`;
124
- case "unmanaged":
125
- return `${update.name}: placed by hand, so there is nowhere to fetch a newer one from`;
126
- case "withheld":
127
- return `${update.name}: staying on ${update.from} — ${update.reason}`;
128
- default:
129
- return `${update.name}: FAILED — ${update.reason}`;
130
- }
131
- }
132
- //# sourceMappingURL=remote.js.map
package/dist/tarball.js DELETED
@@ -1,125 +0,0 @@
1
- import { gunzipSync } from "node:zlib";
2
- import { UserError } from "./errors.js";
3
- const BLOCK = 512;
4
- const TYPE_FILE = 0x30;
5
- const TYPE_FILE_LEGACY = 0x00;
6
- const TYPE_DIRECTORY = 0x35;
7
- const TYPE_PAX_ENTRY = 0x78;
8
- const TYPE_PAX_GLOBAL = 0x67;
9
- const MAX_ENTRIES = 2000;
10
- const MAX_FILE_BYTES = 16 * 1024 * 1024;
11
- const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
12
- export function readPackageTarball(gzipped, label) {
13
- let tar;
14
- try {
15
- tar = gunzipSync(gzipped);
16
- }
17
- catch (error) {
18
- throw new UserError(`${label} is not a gzipped tarball: ${error.message}`);
19
- }
20
- const members = [];
21
- let total = 0;
22
- let offset = 0;
23
- while (offset + BLOCK <= tar.length) {
24
- const header = tar.subarray(offset, offset + BLOCK);
25
- if (isEndOfArchive(header))
26
- break;
27
- offset += BLOCK;
28
- const name = readHeaderName(header, label);
29
- const size = readOctal(header, 124, 12, `${label}: entry "${name}" has an unreadable size`);
30
- const type = header[156] ?? 0;
31
- const body = tar.subarray(offset, offset + size);
32
- offset += Math.ceil(size / BLOCK) * BLOCK;
33
- if (type === TYPE_PAX_ENTRY || type === TYPE_PAX_GLOBAL)
34
- continue;
35
- if (type === TYPE_DIRECTORY)
36
- continue;
37
- if (type !== TYPE_FILE && type !== TYPE_FILE_LEGACY) {
38
- throw new UserError(`${label} carries "${name}", which is not a regular file (tar type ` +
39
- `"${String.fromCharCode(type)}"). A plugin is files; nothing here unpacks a link, a ` +
40
- "device or an archive member of any other kind.");
41
- }
42
- if (size > MAX_FILE_BYTES) {
43
- throw new UserError(`${label}: "${name}" is ${size} bytes, over the ${MAX_FILE_BYTES} cap`);
44
- }
45
- total += size;
46
- if (total > MAX_TOTAL_BYTES || members.length >= MAX_ENTRIES) {
47
- throw new UserError(`${label} unpacks to more than ${MAX_ENTRIES} files or ${MAX_TOTAL_BYTES} bytes`);
48
- }
49
- if (body.length < size) {
50
- throw new UserError(`${label} ends inside "${name}", so it is truncated`);
51
- }
52
- const path = safePath(name, label);
53
- if (path === null)
54
- continue;
55
- members.push({ path, bytes: Buffer.from(body) });
56
- }
57
- return strip(members, label);
58
- }
59
- function strip(members, label) {
60
- const first = members[0];
61
- if (first === undefined) {
62
- throw new UserError(`${label} has no files in it`);
63
- }
64
- const root = first.path.split("/")[0];
65
- const files = [];
66
- const seen = new Set();
67
- for (const member of members) {
68
- const segments = member.path.split("/");
69
- if (segments[0] !== root) {
70
- throw new UserError(`${label} carries "${member.path}", which is outside "${root}/"; every member of a ` +
71
- "published tarball sits under one directory, and this one has two");
72
- }
73
- const path = segments.slice(1).join("/");
74
- if (path.length === 0)
75
- continue;
76
- if (seen.has(path)) {
77
- throw new UserError(`${label} carries "${path}" twice`);
78
- }
79
- seen.add(path);
80
- files.push({ path, bytes: member.bytes });
81
- }
82
- if (files.length === 0) {
83
- throw new UserError(`${label} has nothing under "${root}/"`);
84
- }
85
- return files;
86
- }
87
- function isEndOfArchive(header) {
88
- for (const byte of header) {
89
- if (byte !== 0)
90
- return false;
91
- }
92
- return true;
93
- }
94
- function readHeaderName(header, label) {
95
- const name = readString(header, 0, 100);
96
- const prefix = readString(header, 345, 155);
97
- if (name.length === 0) {
98
- throw new UserError(`${label} carries an entry with no name`);
99
- }
100
- return prefix.length > 0 ? `${prefix}/${name}` : name;
101
- }
102
- function readString(header, at, length) {
103
- const field = header.subarray(at, at + length);
104
- const end = field.indexOf(0);
105
- return field.subarray(0, end === -1 ? field.length : end).toString("utf8");
106
- }
107
- function readOctal(header, at, length, problem) {
108
- const text = readString(header, at, length).trim();
109
- const value = text.length === 0 ? 0 : Number.parseInt(text, 8);
110
- if (!Number.isSafeInteger(value) || value < 0)
111
- throw new UserError(problem);
112
- return value;
113
- }
114
- function safePath(name, label) {
115
- const path = name.replace(/\\/g, "/").replace(/\/+$/, "");
116
- if (path.length === 0)
117
- return null;
118
- const segments = path.split("/");
119
- if (name.startsWith("/") ||
120
- segments.some((segment) => segment === "" || segment === "." || segment === ".." || /^[a-zA-Z]:/.test(segment))) {
121
- throw new UserError(`${label} carries "${name}", which does not stay inside the directory it unpacks into`);
122
- }
123
- return path;
124
- }
125
- //# sourceMappingURL=tarball.js.map