rigline 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +30 -7
- package/dist/engine.js +238 -0
- package/dist/errors.js +4 -0
- package/dist/index.js +53 -464
- package/dist/registry.js +134 -0
- package/dist/remote.js +132 -0
- package/dist/tarball.js +125 -0
- package/package.json +2 -6
- package/dist/build.js +0 -25
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# rigline
|
|
2
2
|
|
|
3
|
-
The command
|
|
4
|
-
|
|
3
|
+
The command for [Rigline](https://github.com/Rigline/Rigline), a plugin layer for the Claude Code
|
|
4
|
+
VS Code extension.
|
|
5
5
|
|
|
6
6
|
Rigline injects a small loader into the installed extension's webview, gives plugins a
|
|
7
7
|
capability-scoped context to write against, and harvests the identifiers those plugins depend on
|
|
@@ -13,6 +13,11 @@ rather than a breakage to chase.
|
|
|
13
13
|
npm install -g rigline
|
|
14
14
|
rigline install
|
|
15
15
|
|
|
16
|
+
The first command you run fetches the engine, [`@rigline/core`](https://www.npmjs.com/package/@rigline/core),
|
|
17
|
+
into `~/.rigline/engine` and runs it from there. It takes a few seconds and needs the registry;
|
|
18
|
+
after that nothing reaches the network unless you ask it to. If that directory is ever in a bad
|
|
19
|
+
state, delete it and run any command again.
|
|
20
|
+
|
|
16
21
|
`install` finds every installed version of the extension, harvests it, bakes the discovered plugins
|
|
17
22
|
into a payload and injects the loader. It keeps a byte-faithful backup of each bundle it touches,
|
|
18
23
|
and `rigline restore` puts everything back using nothing but Node and the backup.
|
|
@@ -30,8 +35,10 @@ directory until *Developer: Reload Window*.
|
|
|
30
35
|
|
|
31
36
|
rigline add SPEC install a plugin from a directory or npm, and re-inject
|
|
32
37
|
rigline remove N delete a plugin rigline installed, and re-inject
|
|
33
|
-
rigline
|
|
34
|
-
rigline
|
|
38
|
+
rigline disable N switch a plugin off, and re-inject
|
|
39
|
+
rigline enable N switch it back on, and re-inject
|
|
40
|
+
rigline update move the engine and every npm plugin to what its tag resolves to
|
|
41
|
+
rigline list every plugin, in load order: version, origin, source, switch, what it can do
|
|
35
42
|
|
|
36
43
|
rigline build build a plugin to one browser ES module
|
|
37
44
|
rigline dev rebuild and re-inject as you edit
|
|
@@ -39,9 +46,25 @@ directory until *Developer: Reload Window*.
|
|
|
39
46
|
rigline diff A B identifier drift between two extension directories
|
|
40
47
|
rigline doctor install state per version, as a pasteable report
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
rigline --version this command's version and the installed engine's
|
|
50
|
+
rigline --help the whole surface, from the engine
|
|
51
|
+
|
|
52
|
+
`add` never runs a package manager for a plugin: a published plugin is one bundled ES module and a
|
|
53
|
+
manifest, so there is nothing to resolve. A version must reach a minimum age (a day, by default)
|
|
54
|
+
before `add` or `update` will take it, and a withheld version is named rather than skipped in
|
|
55
|
+
silence. `update` applies the same rule to the engine, and says which version it moved from so
|
|
56
|
+
going back is one command.
|
|
57
|
+
|
|
58
|
+
## How it is put together
|
|
59
|
+
|
|
60
|
+
`rigline` is a small retrieval layer. It fetches bytes — a plugin tarball, the engine — checks them,
|
|
61
|
+
and hands them over; `@rigline/core` does everything else and answers every verb above. The split is
|
|
62
|
+
not decoration: a process cannot replace the package it is running out of, so whatever performs an
|
|
63
|
+
update has to sit above the thing being updated.
|
|
64
|
+
|
|
65
|
+
**It belongs in no project's dependencies.** If you are writing plugins, your workspace declares
|
|
66
|
+
`@rigline/core` and runs `rigline-engine`; `npm create rigline-plugin` scaffolds it that way. A
|
|
67
|
+
project that can declare dependencies does not need a delivery mechanism.
|
|
45
68
|
|
|
46
69
|
## Writing a plugin
|
|
47
70
|
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { UserError } from "./errors.js";
|
|
7
|
+
import { releaseAgeProblem, resolveVersion } from "./registry.js";
|
|
8
|
+
export const ENGINE_PACKAGE = "@rigline/core";
|
|
9
|
+
export const ENGINE_BIN = "rigline-engine";
|
|
10
|
+
export function riglineHome(env = process.env) {
|
|
11
|
+
const override = env.RIGLINE_HOME;
|
|
12
|
+
return override && override.length > 0 ? override : join(homedir(), ".rigline");
|
|
13
|
+
}
|
|
14
|
+
export function engineDir(home = riglineHome()) {
|
|
15
|
+
return join(home, "engine");
|
|
16
|
+
}
|
|
17
|
+
export function wrapperVersion() {
|
|
18
|
+
const manifest = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
19
|
+
const parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
20
|
+
const version = parsed.version;
|
|
21
|
+
if (typeof version !== "string")
|
|
22
|
+
throw new Error(`${manifest} has no version`);
|
|
23
|
+
return version;
|
|
24
|
+
}
|
|
25
|
+
export function majorOf(version) {
|
|
26
|
+
const match = /^(\d+)\./.exec(version);
|
|
27
|
+
return match === null ? null : Number(match[1]);
|
|
28
|
+
}
|
|
29
|
+
export function readEngineState(prefix) {
|
|
30
|
+
const dir = join(prefix, "node_modules", ENGINE_PACKAGE);
|
|
31
|
+
const manifest = join(dir, "package.json");
|
|
32
|
+
if (!existsSync(manifest))
|
|
33
|
+
return { kind: "none" };
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(readFileSync(manifest, "utf8"));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { kind: "unusable", version: "unreadable", why: `${manifest} is not readable JSON` };
|
|
40
|
+
}
|
|
41
|
+
const version = typeof parsed.version === "string" ? parsed.version : "unreadable";
|
|
42
|
+
const bin = parsed.bin;
|
|
43
|
+
const named = typeof bin === "object" && bin !== null
|
|
44
|
+
? bin[ENGINE_BIN]
|
|
45
|
+
: undefined;
|
|
46
|
+
if (typeof named !== "string") {
|
|
47
|
+
return { kind: "unusable", version, why: `it has no ${ENGINE_BIN} command` };
|
|
48
|
+
}
|
|
49
|
+
const entry = resolve(dir, named);
|
|
50
|
+
if (!existsSync(entry)) {
|
|
51
|
+
return { kind: "unusable", version, why: `its ${ENGINE_BIN} entry ${entry} is missing` };
|
|
52
|
+
}
|
|
53
|
+
return { kind: "ready", version, entry };
|
|
54
|
+
}
|
|
55
|
+
export function majorProblem(wrapper, engine) {
|
|
56
|
+
const ours = majorOf(wrapper);
|
|
57
|
+
const theirs = majorOf(engine);
|
|
58
|
+
if (ours === null || theirs === null || ours === theirs)
|
|
59
|
+
return null;
|
|
60
|
+
return (`rigline ${ours}.x cannot run engine ${theirs}.x (${ENGINE_PACKAGE} ${engine}). ` +
|
|
61
|
+
"Run npm i -g rigline@latest.");
|
|
62
|
+
}
|
|
63
|
+
export function findNpmCli(execPath = process.execPath, exists = existsSync) {
|
|
64
|
+
const here = dirname(execPath);
|
|
65
|
+
const candidates = [
|
|
66
|
+
join(here, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
67
|
+
join(here, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
68
|
+
];
|
|
69
|
+
const found = candidates.find(exists);
|
|
70
|
+
if (found === undefined) {
|
|
71
|
+
throw new UserError(`no npm was found beside ${execPath}, so the engine cannot be installed. ` +
|
|
72
|
+
`Run: npm install --prefix ${engineDir()} --save-exact --ignore-scripts ${ENGINE_PACKAGE}@latest`);
|
|
73
|
+
}
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
export function engineInstallArgv(options) {
|
|
77
|
+
return [
|
|
78
|
+
options.npmCli,
|
|
79
|
+
"install",
|
|
80
|
+
"--prefix",
|
|
81
|
+
options.prefix,
|
|
82
|
+
"--save-exact",
|
|
83
|
+
"--ignore-scripts",
|
|
84
|
+
"--no-audit",
|
|
85
|
+
"--no-fund",
|
|
86
|
+
"--loglevel=error",
|
|
87
|
+
...options.specs,
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
const nodeSpawn = (command, args, options) => spawn(command, [...args], { stdio: [...options.stdio] });
|
|
91
|
+
export async function ensureEngine(options = {}) {
|
|
92
|
+
const prefix = engineDir(options.home ?? riglineHome());
|
|
93
|
+
const version = options.version ?? wrapperVersion();
|
|
94
|
+
const state = readEngineState(prefix);
|
|
95
|
+
if (state.kind === "ready") {
|
|
96
|
+
const problem = majorProblem(version, state.version);
|
|
97
|
+
if (problem !== null)
|
|
98
|
+
throw new UserError(problem);
|
|
99
|
+
return engineAt(state, options);
|
|
100
|
+
}
|
|
101
|
+
const resolved = await resolveEngine(options);
|
|
102
|
+
if (state.kind === "unusable" && resolved.version === state.version) {
|
|
103
|
+
throw new UserError(`the engine in ${prefix} is ${ENGINE_PACKAGE} ${state.version} and ${state.why}, ` +
|
|
104
|
+
`and that is what ${ENGINE_PACKAGE}@${options.tag ?? "latest"} resolves to. ` +
|
|
105
|
+
`Delete ${prefix} and try again, or install a version that has one.`);
|
|
106
|
+
}
|
|
107
|
+
const problem = majorProblem(version, resolved.version);
|
|
108
|
+
if (problem !== null)
|
|
109
|
+
throw new UserError(problem);
|
|
110
|
+
console.log(`rigline: installing the engine, ${ENGINE_PACKAGE} ${resolved.version}`);
|
|
111
|
+
await installEngine(prefix, `${ENGINE_PACKAGE}@${resolved.version}`, options);
|
|
112
|
+
const installed = readEngineState(prefix);
|
|
113
|
+
if (installed.kind !== "ready") {
|
|
114
|
+
throw new UserError(`${ENGINE_PACKAGE} ${resolved.version} was installed into ${prefix}, but ` +
|
|
115
|
+
(installed.kind === "unusable" ? installed.why : "nothing is there"));
|
|
116
|
+
}
|
|
117
|
+
return engineAt(installed, options);
|
|
118
|
+
}
|
|
119
|
+
async function resolveEngine(options) {
|
|
120
|
+
return await resolveVersion({ name: ENGINE_PACKAGE, version: null, tag: options.tag ?? "latest" }, options.registry);
|
|
121
|
+
}
|
|
122
|
+
async function installEngine(prefix, spec, options) {
|
|
123
|
+
mkdirSync(prefix, { recursive: true });
|
|
124
|
+
const argv = engineInstallArgv({ npmCli: findNpmCli(), prefix, specs: [spec] });
|
|
125
|
+
const run = await capture(process.execPath, argv, options.spawnImpl);
|
|
126
|
+
if (run.code !== 0) {
|
|
127
|
+
throw new UserError(`installing ${spec} into ${prefix} failed (npm exited ${run.code}).\n${run.output.trim()}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function engineAt(state, options) {
|
|
131
|
+
const spawnImpl = options.spawnImpl ?? nodeSpawn;
|
|
132
|
+
return {
|
|
133
|
+
version: state.version,
|
|
134
|
+
entry: state.entry,
|
|
135
|
+
run: (argv) => new Promise((done, fail) => {
|
|
136
|
+
const child = spawnImpl(process.execPath, [state.entry, ...argv], {
|
|
137
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
138
|
+
});
|
|
139
|
+
child.on("error", fail);
|
|
140
|
+
child.on("close", (code) => done(code ?? 1));
|
|
141
|
+
}),
|
|
142
|
+
json: async (argv) => {
|
|
143
|
+
const run = await capture(process.execPath, [state.entry, ...argv], spawnImpl, "inherit");
|
|
144
|
+
if (run.code !== 0) {
|
|
145
|
+
throw new UserError(`the engine exited ${run.code} for \`${argv.join(" ")}\``);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(run.output);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new UserError(`the engine answered \`${argv.join(" ")}\` with something that is not JSON`);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function capture(command, argv, spawnImpl = nodeSpawn, stderr = "pipe") {
|
|
157
|
+
return await new Promise((done, fail) => {
|
|
158
|
+
const child = spawnImpl(command, argv, { stdio: ["ignore", "pipe", stderr] });
|
|
159
|
+
let output = "";
|
|
160
|
+
child.stdout?.setEncoding("utf8");
|
|
161
|
+
child.stdout?.on("data", (chunk) => {
|
|
162
|
+
output += chunk;
|
|
163
|
+
});
|
|
164
|
+
child.stderr?.setEncoding("utf8");
|
|
165
|
+
child.stderr?.on("data", (chunk) => {
|
|
166
|
+
output += chunk;
|
|
167
|
+
});
|
|
168
|
+
child.on("error", fail);
|
|
169
|
+
child.on("close", (code) => done({ code: code ?? 1, output }));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
export async function updateEngine(options = {}) {
|
|
173
|
+
const prefix = engineDir(options.home ?? riglineHome());
|
|
174
|
+
const state = readEngineState(prefix);
|
|
175
|
+
const installed = state.kind === "none" ? null : state.version;
|
|
176
|
+
let resolved;
|
|
177
|
+
try {
|
|
178
|
+
resolved = await resolveEngine(options);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
outcome: "failed",
|
|
183
|
+
...(installed === null ? {} : { from: installed }),
|
|
184
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (state.kind === "ready" && resolved.version === state.version) {
|
|
188
|
+
return { outcome: "current", from: state.version };
|
|
189
|
+
}
|
|
190
|
+
const problem = majorProblem(options.version ?? wrapperVersion(), resolved.version);
|
|
191
|
+
if (problem !== null) {
|
|
192
|
+
return {
|
|
193
|
+
outcome: "failed",
|
|
194
|
+
...(installed === null ? {} : { from: installed }),
|
|
195
|
+
reason: problem,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const withheld = releaseAgeProblem(resolved, options.registry);
|
|
199
|
+
if (withheld !== null) {
|
|
200
|
+
return {
|
|
201
|
+
outcome: "withheld",
|
|
202
|
+
...(installed === null ? {} : { from: installed }),
|
|
203
|
+
to: resolved.version,
|
|
204
|
+
reason: withheld,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
await installEngine(prefix, `${ENGINE_PACKAGE}@${resolved.version}`, options);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
outcome: "failed",
|
|
213
|
+
...(installed === null ? {} : { from: installed }),
|
|
214
|
+
to: resolved.version,
|
|
215
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return installed === null
|
|
219
|
+
? { outcome: "installed", to: resolved.version }
|
|
220
|
+
: { outcome: "moved", from: installed, to: resolved.version };
|
|
221
|
+
}
|
|
222
|
+
export function formatEngineUpdate(update, home = riglineHome()) {
|
|
223
|
+
switch (update.outcome) {
|
|
224
|
+
case "installed":
|
|
225
|
+
return `engine: installed ${update.to}`;
|
|
226
|
+
case "moved":
|
|
227
|
+
return (`engine: ${update.from} -> ${update.to}\n` +
|
|
228
|
+
` to go back: npm install --prefix ${engineDir(home)} --save-exact ` +
|
|
229
|
+
`${ENGINE_PACKAGE}@${update.from}`);
|
|
230
|
+
case "current":
|
|
231
|
+
return `engine: ${update.from}, which is what its tag resolves to`;
|
|
232
|
+
case "withheld":
|
|
233
|
+
return `engine: staying on ${update.from} — ${update.reason}`;
|
|
234
|
+
default:
|
|
235
|
+
return `engine: FAILED — ${update.reason}`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=engine.js.map
|
package/dist/errors.js
ADDED
package/dist/index.js
CHANGED
|
@@ -1,110 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, watch as fsWatch, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
2
|
import { parseArgs } from "node:util";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
--check compares instead of writing and exits 1 when the file is out of date. Either
|
|
14
|
-
way it exits 1 if a curated anchor claims to name one element and this version
|
|
15
|
-
applies its class in more than one place. It reads the shipped anchor table, never
|
|
16
|
-
~/.rigline/anchors.json: its verdict is about this repository's table.
|
|
17
|
-
|
|
18
|
-
rigline diff DIR_A DIR_B
|
|
19
|
-
Compare the identifier layers of two extension directories.
|
|
20
|
-
|
|
21
|
-
rigline build [DIR] [--source FILE]
|
|
22
|
-
Bundle a plugin's src/index.ts (or --source) into the entry its rigline.json names.
|
|
23
|
-
|
|
24
|
-
rigline install [--ext DIR] [--payload DIR]
|
|
25
|
-
Inject the loader into every installed extension version (or DIR), baking the enabled
|
|
26
|
-
plugins, and report what moved since the baseline and which plugins each version
|
|
27
|
-
refuses. Any entry in ~/.rigline/anchors.json is applied and named, with what this
|
|
28
|
-
version makes of it. Rewrites ./generated.ts when the directory has one and records
|
|
29
|
-
the new baseline; never commits either. Run it after an extension update, and reload
|
|
30
|
-
webviews afterwards. Exits 1 when a person is needed.
|
|
31
|
-
|
|
32
|
-
rigline check [--ext DIR]
|
|
33
|
-
Read-only. Per installed version (or DIR): what moved since the baseline, which
|
|
34
|
-
plugins this version would refuse and by which identifier, which curated anchors it
|
|
35
|
-
lacks, and what it makes of each entry in ~/.rigline/anchors.json. Exits 1 when a
|
|
36
|
-
person is needed.
|
|
37
|
-
|
|
38
|
-
rigline watch [--interval SECONDS]
|
|
39
|
-
install, and again whenever the set of installed extension directories changes,
|
|
40
|
-
which is what an extension update looks like from outside VS Code.
|
|
41
|
-
|
|
42
|
-
rigline dev [DIR...]
|
|
43
|
-
Build the named plugin directories (or every first-party one), re-inject, and rebuild
|
|
44
|
-
on every source change. Reload webviews after each one.
|
|
45
|
-
|
|
46
|
-
rigline add PATH|SPEC [--now]
|
|
47
|
-
Install a plugin into ~/.rigline/plugins, say what it can do, and re-inject. PATH is a
|
|
48
|
-
directory; SPEC is an npm package, optionally @version or @tag. A published version
|
|
49
|
-
must be a day old before it is installed; --now takes it anyway. No package manager
|
|
50
|
-
runs, nothing is resolved, and none of the plugin's own code is evaluated.
|
|
51
|
-
|
|
52
|
-
rigline update [NAME...] [--now]
|
|
53
|
-
Move each plugin installed from npm to whatever its tag resolves to now, and
|
|
54
|
-
re-inject. A plugin pinned to a version, added from a directory, or placed by hand is
|
|
55
|
-
reported and left alone, as is a newer version too young to install.
|
|
56
|
-
|
|
57
|
-
rigline remove NAME
|
|
58
|
-
Delete a plugin rigline installed, and re-inject. A plugin you did not install this
|
|
59
|
-
way is switched off in ~/.rigline/config.json instead; this will not delete it.
|
|
60
|
-
|
|
61
|
-
rigline list
|
|
62
|
-
Every plugin found, in the order they load: where it came from, whether it is
|
|
63
|
-
switched off, and what its manifest says it can do.
|
|
64
|
-
|
|
65
|
-
rigline status
|
|
66
|
-
Per installed version: is each bundle vanilla or patched, judged against its backup.
|
|
67
|
-
|
|
68
|
-
rigline restore
|
|
69
|
-
Every installed version back to the extension's own bytes. Needs neither VS Code nor
|
|
70
|
-
the extension to be working.
|
|
71
|
-
|
|
72
|
-
rigline doctor [--out FILE] [--ext DIR]
|
|
73
|
-
A markdown diagnostic to paste into a bug report: per installed version, whether it is
|
|
74
|
-
patched, what the payload holds, which plugins are baked in and what any host patch
|
|
75
|
-
did. For what the panel itself was doing, copy the probe's report from the RIG badge.
|
|
76
|
-
`;
|
|
77
|
-
function defaultPayloadDir() {
|
|
78
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
79
|
-
return resolve(here, "..", "..", "host", "dist");
|
|
80
|
-
}
|
|
81
|
-
function repoPluginsDir() {
|
|
82
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
83
|
-
return resolve(here, "..", "..", "..", "plugins");
|
|
84
|
-
}
|
|
85
|
-
function pluginOptions() {
|
|
86
|
-
const paths = riglinePaths();
|
|
87
|
-
return { roots: [repoPluginsDir(), paths.plugins], last: ["probe"], configPath: paths.config };
|
|
88
|
-
}
|
|
89
|
-
function reinject(options = {}) {
|
|
90
|
-
const targets = options.exts ?? installedExtensions();
|
|
91
|
-
if (targets.length === 0) {
|
|
92
|
-
if (options.required)
|
|
93
|
-
throw new UserError("no Claude Code extension is installed");
|
|
94
|
-
console.log("No Claude Code extension is installed, so nothing was injected.");
|
|
95
|
-
return 0;
|
|
96
|
-
}
|
|
97
|
-
const report = update({
|
|
98
|
-
exts: targets,
|
|
99
|
-
payloadDir: options.payloadDir ?? defaultPayloadDir(),
|
|
100
|
-
plugins: pluginOptions(),
|
|
101
|
-
codegen: true,
|
|
102
|
-
});
|
|
103
|
-
console.log(formatFlow(report));
|
|
104
|
-
console.log(report.versions.some((v) => v.hostChanged)
|
|
105
|
-
? `\nA host patch changed: run Developer: Reload Window (this ends the window's sessions).`
|
|
106
|
-
: `\nReload with Developer: Reload Webviews (current window only).`);
|
|
107
|
-
return report.attention.length > 0 ? 1 : 0;
|
|
3
|
+
import { ENGINE_PACKAGE, engineDir, ensureEngine, formatEngineUpdate, readEngineState, updateEngine, wrapperVersion, } from "./engine.js";
|
|
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("\\");
|
|
108
10
|
}
|
|
109
11
|
async function addCommand(args) {
|
|
110
12
|
const { values, positionals } = parseArgs({
|
|
@@ -115,383 +17,70 @@ async function addCommand(args) {
|
|
|
115
17
|
if (positionals.length !== 1) {
|
|
116
18
|
throw new UserError("add needs exactly one plugin directory or npm package");
|
|
117
19
|
}
|
|
118
|
-
const paths = riglinePaths();
|
|
119
20
|
const spec = positionals[0];
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.log(`${result.replaced ? "replaced" : "added"} ${result.name} — ${result.dir}`);
|
|
129
|
-
console.log(` from ${result.from}`);
|
|
130
|
-
if (result.manifest.description)
|
|
131
|
-
console.log(` ${result.manifest.description}`);
|
|
132
|
-
for (const sentence of result.can)
|
|
133
|
-
console.log(` - ${sentence}`);
|
|
134
|
-
for (const patch of result.manifest.patches) {
|
|
135
|
-
console.log(` - patches extension.js${patch.required ? " (required)" : ""}: ${patch.why}`);
|
|
136
|
-
}
|
|
137
|
-
if (result.disabled) {
|
|
138
|
-
console.log(` "${result.name}" is switched off in ${paths.config}, so it will not load until you remove it from "disabled"`);
|
|
139
|
-
}
|
|
140
|
-
console.log("");
|
|
141
|
-
return reinject();
|
|
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
|
+
});
|
|
142
29
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return existsSync(join(resolve(spec), "rigline.json"));
|
|
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;
|
|
149
35
|
}
|
|
150
36
|
async function updateCommand(args) {
|
|
151
37
|
const { values, positionals } = parseArgs({
|
|
152
38
|
args,
|
|
153
|
-
options: { now: { type: "boolean", default: false } },
|
|
39
|
+
options: { now: { type: "boolean", default: false }, tag: { type: "string" } },
|
|
154
40
|
allowPositionals: true,
|
|
155
41
|
});
|
|
156
|
-
const
|
|
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 });
|
|
157
46
|
const updates = await updatePlugins({
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
registry: { ignoreReleaseAge: values.now },
|
|
163
|
-
});
|
|
164
|
-
console.log(formatUpdates(updates));
|
|
165
|
-
const moved = updates.some((u) => u.outcome === "updated");
|
|
166
|
-
if (!moved) {
|
|
167
|
-
return updates.some((u) => u.outcome === "failed") ? 1 : 0;
|
|
168
|
-
}
|
|
169
|
-
console.log("");
|
|
170
|
-
const code = reinject();
|
|
171
|
-
return updates.some((u) => u.outcome === "failed") ? 1 : code;
|
|
172
|
-
}
|
|
173
|
-
function removeCommand(args) {
|
|
174
|
-
const { positionals } = parseArgs({ args, options: {}, allowPositionals: true });
|
|
175
|
-
if (positionals.length !== 1)
|
|
176
|
-
throw new UserError("remove needs exactly one plugin name");
|
|
177
|
-
const paths = riglinePaths();
|
|
178
|
-
const result = removePlugin({
|
|
179
|
-
name: positionals[0],
|
|
180
|
-
pluginsDir: paths.plugins,
|
|
181
|
-
configPath: paths.config,
|
|
182
|
-
otherRoots: [repoPluginsDir()],
|
|
183
|
-
});
|
|
184
|
-
console.log(`removed ${result.name} — ${result.dir}`);
|
|
185
|
-
if (!result.hadSource)
|
|
186
|
-
console.log(" it had no source record, so it was placed here by hand");
|
|
187
|
-
if (result.wasDisabled)
|
|
188
|
-
console.log(` and dropped from "disabled" in ${paths.config}`);
|
|
189
|
-
console.log("");
|
|
190
|
-
return reinject();
|
|
191
|
-
}
|
|
192
|
-
function installCommand(args) {
|
|
193
|
-
const { values } = parseArgs({
|
|
194
|
-
args,
|
|
195
|
-
options: { ext: { type: "string" }, payload: { type: "string" } },
|
|
196
|
-
allowPositionals: false,
|
|
47
|
+
listed: await listed(engine),
|
|
48
|
+
engine: engine.run,
|
|
49
|
+
...(positionals.length > 0 ? { names: positionals } : {}),
|
|
50
|
+
registry,
|
|
197
51
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
required: true,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
function listCommand() {
|
|
205
|
-
const paths = riglinePaths();
|
|
206
|
-
console.log(formatPlugins(listPlugins({
|
|
207
|
-
roots: [
|
|
208
|
-
{ label: "this checkout", path: repoPluginsDir() },
|
|
209
|
-
{ label: paths.plugins, path: paths.plugins, managed: true },
|
|
210
|
-
],
|
|
211
|
-
last: ["probe"],
|
|
212
|
-
configPath: paths.config,
|
|
213
|
-
})));
|
|
214
|
-
return 0;
|
|
215
|
-
}
|
|
216
|
-
function statusCommand() {
|
|
217
|
-
const targets = installedExtensions();
|
|
218
|
-
if (targets.length === 0)
|
|
219
|
-
throw new UserError("no Claude Code extension is installed");
|
|
220
|
-
for (const ext of targets) {
|
|
221
|
-
const state = inspect(ext);
|
|
222
|
-
console.log(`${extensionVersion(ext)}: webview ${verdict(state)}${state.markerPresent ? " (marker present)" : ""}` +
|
|
223
|
-
`${state.backupExists ? "" : ", no backup"}; host ${hostVerdict(state)}` +
|
|
224
|
-
`${state.hostBackupExists ? " (backup present)" : ""}`);
|
|
225
|
-
console.log(` ${ext}`);
|
|
226
|
-
}
|
|
227
|
-
return 0;
|
|
228
|
-
}
|
|
229
|
-
function doctorCommand(args) {
|
|
230
|
-
const { values } = parseArgs({
|
|
231
|
-
args,
|
|
232
|
-
options: { out: { type: "string" }, ext: { type: "string" } },
|
|
233
|
-
allowPositionals: false,
|
|
234
|
-
});
|
|
235
|
-
const report = collect({ exts: values.ext ? [resolve(values.ext)] : undefined });
|
|
236
|
-
const markdown = formatDoctor(report);
|
|
237
|
-
if (values.out === undefined) {
|
|
238
|
-
process.stdout.write(markdown);
|
|
239
|
-
return 0;
|
|
240
|
-
}
|
|
241
|
-
const out = resolve(values.out);
|
|
242
|
-
writeFileSync(out, markdown);
|
|
243
|
-
const versions = report.installs.length;
|
|
244
|
-
console.log(`wrote ${out}: ${versions} extension ${versions === 1 ? "version" : "versions"}`);
|
|
245
|
-
return 0;
|
|
246
|
-
}
|
|
247
|
-
function restoreCommand() {
|
|
248
|
-
const results = restoreAll(installedExtensions());
|
|
249
|
-
let noBackup = 0;
|
|
250
|
-
let hostFailed = 0;
|
|
251
|
-
for (const result of results) {
|
|
252
|
-
if (result.restored)
|
|
253
|
-
console.log(`restored: ${result.ext}`);
|
|
254
|
-
else {
|
|
255
|
-
noBackup++;
|
|
256
|
-
console.log(`NOT restored: ${result.ext} (${result.reason})`);
|
|
257
|
-
}
|
|
258
|
-
if (result.hostReason) {
|
|
259
|
-
hostFailed++;
|
|
260
|
-
console.log(` extension.js NOT restored: ${result.hostReason}`);
|
|
261
|
-
}
|
|
52
|
+
let injected = 0;
|
|
53
|
+
if (engineUpdate.outcome === "moved" || engineUpdate.outcome === "installed") {
|
|
54
|
+
injected = await engine.run(["install"]);
|
|
262
55
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
function codegen(args) {
|
|
276
|
-
const { values, positionals } = parseArgs({
|
|
277
|
-
args,
|
|
278
|
-
options: { check: { type: "boolean", default: false }, out: { type: "string" } },
|
|
279
|
-
allowPositionals: true,
|
|
280
|
-
});
|
|
281
|
-
if (positionals.length > 1)
|
|
282
|
-
throw new UserError(`expected at most one directory: ${positionals}`);
|
|
283
|
-
const ext = positionals[0] ?? findExtension();
|
|
284
|
-
const out = values.out ?? defaultGeneratedPath();
|
|
285
|
-
const generated = generate(harvestAll(readBundles(ext)));
|
|
286
|
-
const label = relative(process.cwd(), out) || out;
|
|
287
|
-
const ambiguous = reportAmbiguousAnchors(generated);
|
|
288
|
-
if (values.check) {
|
|
289
|
-
const current = existsSync(out) ? readFileSync(out, "utf8") : "";
|
|
290
|
-
if (current !== generated.source) {
|
|
291
|
-
console.log(`${label} is out of date for ${generated.tables.version}. Run: rigline codegen`);
|
|
292
|
-
return 1;
|
|
293
|
-
}
|
|
294
|
-
console.log(`${label} is up to date for ${generated.tables.version}: ${generated.counts}`);
|
|
295
|
-
return ambiguous ? 1 : 0;
|
|
296
|
-
}
|
|
297
|
-
writeFileSync(out, generated.source);
|
|
298
|
-
console.log(`source: ${ext}`);
|
|
299
|
-
console.log(`${generated.tables.version}: ${generated.counts}`);
|
|
300
|
-
for (const module of generated.unreachableModules) {
|
|
301
|
-
console.log(` unreachable stylesheet module: ${module}`);
|
|
302
|
-
}
|
|
303
|
-
for (const name of generated.anchors.unverified) {
|
|
304
|
-
console.log(` unverified anchor: ${name} (its module's class map was never counted)`);
|
|
305
|
-
}
|
|
306
|
-
console.log(`wrote: ${label}`);
|
|
307
|
-
return ambiguous ? 1 : 0;
|
|
308
|
-
}
|
|
309
|
-
function reportAmbiguousAnchors(generated) {
|
|
310
|
-
if (generated.anchors.ambiguous.length === 0)
|
|
311
|
-
return false;
|
|
312
|
-
for (const { name, sites } of generated.anchors.ambiguous) {
|
|
313
|
-
console.error(` ambiguous anchor: ${name} names one element, and ${generated.tables.version} applies its class at ${sites} places`);
|
|
314
|
-
}
|
|
315
|
-
console.error("Refine each in packages/plugin-api/src/anchors.ts, or change its kind to collection if it was never one element.");
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
function diff(args) {
|
|
319
|
-
if (args.length !== 2)
|
|
320
|
-
throw new UserError("diff needs exactly two extension directories");
|
|
321
|
-
const [a, b] = args;
|
|
322
|
-
const from = scanOf(harvestAll(readBundles(a)));
|
|
323
|
-
const to = scanOf(harvestAll(readBundles(b)));
|
|
324
|
-
console.log(formatDiff(from, to, diffScans(from, to)));
|
|
325
|
-
return 0;
|
|
326
|
-
}
|
|
327
|
-
async function build(args) {
|
|
328
|
-
const { values, positionals } = parseArgs({
|
|
329
|
-
args,
|
|
330
|
-
options: { source: { type: "string" } },
|
|
331
|
-
allowPositionals: true,
|
|
332
|
-
});
|
|
333
|
-
if (positionals.length > 1)
|
|
334
|
-
throw new UserError(`expected at most one directory: ${positionals}`);
|
|
335
|
-
const built = await buildPlugin({ dir: positionals[0], source: values.source });
|
|
336
|
-
console.log(`built ${built.input} -> ${built.output}`);
|
|
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})` : ""));
|
|
337
68
|
return 0;
|
|
338
69
|
}
|
|
339
|
-
function extOption(args) {
|
|
340
|
-
const { values } = parseArgs({ args, options: { ext: { type: "string" } } });
|
|
341
|
-
return values.ext ? [resolve(values.ext)] : undefined;
|
|
342
|
-
}
|
|
343
|
-
function checkCommand(args) {
|
|
344
|
-
const report = check({ exts: extOption(args), plugins: pluginOptions() });
|
|
345
|
-
console.log(formatFlow(report));
|
|
346
|
-
return report.attention.length > 0 ? 1 : 0;
|
|
347
|
-
}
|
|
348
|
-
function watchCommand(args) {
|
|
349
|
-
const { values } = parseArgs({
|
|
350
|
-
args,
|
|
351
|
-
options: { interval: { type: "string" } },
|
|
352
|
-
allowPositionals: false,
|
|
353
|
-
});
|
|
354
|
-
const seconds = values.interval === undefined ? 30 : Number(values.interval);
|
|
355
|
-
if (!Number.isFinite(seconds) || seconds < 1) {
|
|
356
|
-
throw new UserError(`--interval needs a number of seconds, got ${values.interval}`);
|
|
357
|
-
}
|
|
358
|
-
return new Promise((resolveWith) => {
|
|
359
|
-
let code = 0;
|
|
360
|
-
const watcher = watch({
|
|
361
|
-
payloadDir: defaultPayloadDir(),
|
|
362
|
-
plugins: pluginOptions(),
|
|
363
|
-
codegen: true,
|
|
364
|
-
intervalMs: seconds * 1000,
|
|
365
|
-
onReport(report) {
|
|
366
|
-
console.log(`
|
|
367
|
-
[${new Date().toISOString()}]`);
|
|
368
|
-
console.log(formatFlow(report));
|
|
369
|
-
code = report.attention.length > 0 ? 1 : 0;
|
|
370
|
-
},
|
|
371
|
-
onError(error) {
|
|
372
|
-
console.error(`rigline watch: ${error instanceof Error ? error.message : String(error)}`);
|
|
373
|
-
code = 1;
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
console.log(`watching ${EXTENSIONS_DIR} every ${seconds}s; Ctrl-C to stop`);
|
|
377
|
-
const stop = () => {
|
|
378
|
-
watcher.stop();
|
|
379
|
-
resolveWith(code);
|
|
380
|
-
};
|
|
381
|
-
process.once("SIGINT", stop);
|
|
382
|
-
process.once("SIGTERM", stop);
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
async function dev(args) {
|
|
386
|
-
const { positionals } = parseArgs({ args, options: {}, allowPositionals: true });
|
|
387
|
-
const dirs = positionals.length > 0
|
|
388
|
-
? positionals.map((d) => resolve(d))
|
|
389
|
-
: readdirSync(repoPluginsDir(), { withFileTypes: true })
|
|
390
|
-
.filter((e) => e.isDirectory() && existsSync(join(repoPluginsDir(), e.name, "rigline.json")))
|
|
391
|
-
.map((e) => join(repoPluginsDir(), e.name));
|
|
392
|
-
if (dirs.length === 0)
|
|
393
|
-
throw new UserError("no plugin directories to develop");
|
|
394
|
-
const exts = installedExtensions();
|
|
395
|
-
if (exts.length === 0)
|
|
396
|
-
throw new UserError("no Claude Code extension is installed");
|
|
397
|
-
const overrides = readAnchorOverrides();
|
|
398
|
-
for (const problem of overrides.problems)
|
|
399
|
-
console.error(`rigline dev: ${problem}`);
|
|
400
|
-
async function once() {
|
|
401
|
-
for (const dir of dirs) {
|
|
402
|
-
const built = await buildPlugin({ dir });
|
|
403
|
-
console.log(`built ${basename(dir)}: ${built.input} -> ${built.output}`);
|
|
404
|
-
}
|
|
405
|
-
let hostChanged = false;
|
|
406
|
-
for (const ext of exts) {
|
|
407
|
-
const report = install(ext, {
|
|
408
|
-
payloadDir: defaultPayloadDir(),
|
|
409
|
-
plugins: pluginOptions(),
|
|
410
|
-
anchors: overrides,
|
|
411
|
-
});
|
|
412
|
-
hostChanged ||= report.hostChanged;
|
|
413
|
-
for (const override of report.anchorOverrides) {
|
|
414
|
-
console.log(` anchor override ${override.name}: ${overrides.path}`);
|
|
415
|
-
}
|
|
416
|
-
for (const verdict of report.verdicts) {
|
|
417
|
-
if (verdict.refusal)
|
|
418
|
-
console.log(` REFUSED ${verdict.plugin}: ${verdict.refusal}`);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
console.log(hostChanged
|
|
422
|
-
? "Developer: Reload Window (a host patch changed; this ends the window's sessions)"
|
|
423
|
-
: "Developer: Reload Webviews");
|
|
424
|
-
}
|
|
425
|
-
await once();
|
|
426
|
-
let pending = null;
|
|
427
|
-
let building = false;
|
|
428
|
-
const schedule = () => {
|
|
429
|
-
if (pending)
|
|
430
|
-
clearTimeout(pending);
|
|
431
|
-
pending = setTimeout(() => {
|
|
432
|
-
pending = null;
|
|
433
|
-
if (building)
|
|
434
|
-
return;
|
|
435
|
-
building = true;
|
|
436
|
-
once()
|
|
437
|
-
.catch((error) => {
|
|
438
|
-
console.error(`rigline dev: ${error instanceof Error ? error.message : String(error)}`);
|
|
439
|
-
})
|
|
440
|
-
.finally(() => {
|
|
441
|
-
building = false;
|
|
442
|
-
});
|
|
443
|
-
}, 150);
|
|
444
|
-
};
|
|
445
|
-
for (const dir of dirs) {
|
|
446
|
-
const src = join(dir, "src");
|
|
447
|
-
if (!existsSync(src))
|
|
448
|
-
continue;
|
|
449
|
-
fsWatch(src, { recursive: true }, schedule);
|
|
450
|
-
}
|
|
451
|
-
console.log(`watching ${dirs.length} plugin source director${dirs.length === 1 ? "y" : "ies"}; Ctrl-C to stop`);
|
|
452
|
-
return new Promise((resolveWith) => {
|
|
453
|
-
process.once("SIGINT", () => resolveWith(0));
|
|
454
|
-
process.once("SIGTERM", () => resolveWith(0));
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
70
|
async function main(argv) {
|
|
458
71
|
const [command, ...rest] = argv;
|
|
459
72
|
switch (command) {
|
|
460
|
-
case "
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return diff(rest);
|
|
464
|
-
case "build":
|
|
465
|
-
return build(rest);
|
|
466
|
-
case "install":
|
|
467
|
-
return installCommand(rest);
|
|
468
|
-
case "check":
|
|
469
|
-
return checkCommand(rest);
|
|
470
|
-
case "watch":
|
|
471
|
-
return watchCommand(rest);
|
|
472
|
-
case "dev":
|
|
473
|
-
return dev(rest);
|
|
73
|
+
case "--version":
|
|
74
|
+
case "-v":
|
|
75
|
+
return versionCommand();
|
|
474
76
|
case "add":
|
|
475
|
-
return addCommand(rest);
|
|
77
|
+
return await addCommand(rest);
|
|
476
78
|
case "update":
|
|
477
|
-
return updateCommand(rest);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
case "status":
|
|
483
|
-
return statusCommand();
|
|
484
|
-
case "restore":
|
|
485
|
-
return restoreCommand();
|
|
486
|
-
case "doctor":
|
|
487
|
-
return doctorCommand(rest);
|
|
488
|
-
case undefined:
|
|
489
|
-
case "--help":
|
|
490
|
-
case "-h":
|
|
491
|
-
console.log(USAGE);
|
|
492
|
-
return 0;
|
|
493
|
-
default:
|
|
494
|
-
throw new UserError(`unknown command "${command}"\n\n${USAGE}`);
|
|
79
|
+
return await updateCommand(rest);
|
|
80
|
+
default: {
|
|
81
|
+
const engine = await ensureEngine();
|
|
82
|
+
return await engine.run(argv);
|
|
83
|
+
}
|
|
495
84
|
}
|
|
496
85
|
}
|
|
497
86
|
main(process.argv.slice(2)).then((code) => {
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { UserError } from "./errors.js";
|
|
3
|
+
export const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
4
|
+
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
|
+
export async function resolveVersion(spec, options = {}) {
|
|
20
|
+
const packument = await fetchPackument(spec.name, options);
|
|
21
|
+
const tag = spec.version === null ? (spec.tag ?? "latest") : null;
|
|
22
|
+
const version = spec.version ?? distTag(packument, spec.name, tag);
|
|
23
|
+
const release = packument.versions?.[version];
|
|
24
|
+
if (release === undefined) {
|
|
25
|
+
throw new UserError(`${spec.name} has no version ${version} on ${registryOf(options)}`);
|
|
26
|
+
}
|
|
27
|
+
const published = packument.time?.[version];
|
|
28
|
+
const ageMinutes = typeof published === "string" && !Number.isNaN(Date.parse(published))
|
|
29
|
+
? Math.floor(((options.clock?.() ?? Date.now()) - Date.parse(published)) / 60_000)
|
|
30
|
+
: null;
|
|
31
|
+
const tarball = release.dist?.tarball;
|
|
32
|
+
if (typeof tarball !== "string" || tarball.length === 0) {
|
|
33
|
+
throw new UserError(`${spec.name}@${version} has no tarball on ${registryOf(options)}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
name: spec.name,
|
|
37
|
+
version,
|
|
38
|
+
tag,
|
|
39
|
+
tarball,
|
|
40
|
+
integrity: integrityOf(release, `${spec.name}@${version}`),
|
|
41
|
+
ageMinutes,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
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
|
+
export function releaseAgeProblem(resolved, options = {}) {
|
|
72
|
+
if (options.ignoreReleaseAge)
|
|
73
|
+
return null;
|
|
74
|
+
const minimum = options.minimumReleaseAge ?? MINIMUM_RELEASE_AGE_MINUTES;
|
|
75
|
+
if (resolved.ageMinutes === null || resolved.ageMinutes >= minimum)
|
|
76
|
+
return null;
|
|
77
|
+
return (`${resolved.name}@${resolved.version} was published ${describeAge(resolved.ageMinutes)} ago, ` +
|
|
78
|
+
`and rigline waits ${describeAge(minimum)} before installing a version. That window is where ` +
|
|
79
|
+
"a compromised publish is usually caught, and it costs little here because the urgent repair " +
|
|
80
|
+
"— an anchor an extension update retired — is a local edit rather than a release (D44). " +
|
|
81
|
+
"Wait, or pass --now.");
|
|
82
|
+
}
|
|
83
|
+
export function describeAge(minutes) {
|
|
84
|
+
if (minutes < 60)
|
|
85
|
+
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`;
|
|
86
|
+
const hours = Math.floor(minutes / 60);
|
|
87
|
+
if (hours < 48)
|
|
88
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
89
|
+
return `${Math.floor(hours / 24)} days`;
|
|
90
|
+
}
|
|
91
|
+
function distTag(packument, name, tag) {
|
|
92
|
+
const version = packument["dist-tags"]?.[tag];
|
|
93
|
+
if (typeof version === "string" && version.length > 0)
|
|
94
|
+
return version;
|
|
95
|
+
const tags = Object.keys(packument["dist-tags"] ?? {});
|
|
96
|
+
throw new UserError(`${name} has no "${tag}" tag${tags.length > 0 ? `; it has ${tags.join(", ")}` : ""}`);
|
|
97
|
+
}
|
|
98
|
+
function integrityOf(release, label) {
|
|
99
|
+
const { integrity, shasum } = release.dist ?? {};
|
|
100
|
+
if (typeof integrity === "string" && integrity.length > 0)
|
|
101
|
+
return integrity;
|
|
102
|
+
if (typeof shasum === "string" && /^[0-9a-f]{40}$/.test(shasum)) {
|
|
103
|
+
return `sha1-${Buffer.from(shasum, "hex").toString("base64")}`;
|
|
104
|
+
}
|
|
105
|
+
throw new UserError(`${label} carries no integrity hash, so its bytes cannot be checked`);
|
|
106
|
+
}
|
|
107
|
+
function registryOf(options) {
|
|
108
|
+
return (options.registry ?? process.env.npm_config_registry ?? DEFAULT_REGISTRY).replace(/\/+$/, "");
|
|
109
|
+
}
|
|
110
|
+
async function fetchPackument(name, options) {
|
|
111
|
+
const url = `${registryOf(options)}/${name.replace("/", "%2F")}`;
|
|
112
|
+
const response = await request(url, options, { Accept: "application/json" });
|
|
113
|
+
return (await response.json());
|
|
114
|
+
}
|
|
115
|
+
async function request(url, options, headers) {
|
|
116
|
+
const impl = options.fetchImpl ?? globalThis.fetch;
|
|
117
|
+
if (typeof impl !== "function") {
|
|
118
|
+
throw new UserError("this Node has no fetch, so nothing can be installed from a registry");
|
|
119
|
+
}
|
|
120
|
+
let response;
|
|
121
|
+
try {
|
|
122
|
+
response = await impl(url, headers ? { headers } : undefined);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw new UserError(`could not reach ${url}: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
throw new UserError(response.status === 404
|
|
129
|
+
? `${url} is not there (404). Check the name, and that it is published.`
|
|
130
|
+
: `${url} answered ${response.status}`);
|
|
131
|
+
}
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=registry.js.map
|
package/dist/remote.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
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 are installed";
|
|
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
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rigline",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"description": "Install, update and develop plugins for the Claude Code VS Code extension",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rigline",
|
|
@@ -29,12 +29,8 @@
|
|
|
29
29
|
"bin": {
|
|
30
30
|
"rigline": "./dist/index.js"
|
|
31
31
|
},
|
|
32
|
-
"dependencies": {
|
|
33
|
-
"@rigline/core": "1.0.0-alpha.4",
|
|
34
|
-
"rolldown": "^1.2.8"
|
|
35
|
-
},
|
|
36
32
|
"scripts": {
|
|
37
|
-
"build": "tsc -p tsconfig.build.json",
|
|
33
|
+
"build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
38
34
|
"typecheck": "tsc -p tsconfig.json"
|
|
39
35
|
}
|
|
40
36
|
}
|
package/dist/build.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, relative, resolve } from "node:path";
|
|
3
|
-
import { UserError } from "@rigline/core";
|
|
4
|
-
import { build as bundle } from "rolldown";
|
|
5
|
-
export async function buildPlugin(options = {}) {
|
|
6
|
-
const dir = resolve(options.dir ?? process.cwd());
|
|
7
|
-
const manifestPath = join(dir, "rigline.json");
|
|
8
|
-
if (!existsSync(manifestPath))
|
|
9
|
-
throw new UserError(`${dir} has no rigline.json`);
|
|
10
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
11
|
-
if (typeof manifest.entry !== "string" || manifest.entry.length === 0) {
|
|
12
|
-
throw new UserError(`${manifestPath} has no "entry"`);
|
|
13
|
-
}
|
|
14
|
-
const input = resolve(dir, options.source ?? "src/index.ts");
|
|
15
|
-
if (!existsSync(input))
|
|
16
|
-
throw new UserError(`no plugin source at ${input}`);
|
|
17
|
-
const output = resolve(dir, manifest.entry);
|
|
18
|
-
await bundle({
|
|
19
|
-
input,
|
|
20
|
-
platform: "browser",
|
|
21
|
-
output: { file: output, format: "esm", sourcemap: "inline", codeSplitting: false },
|
|
22
|
-
});
|
|
23
|
-
return { input: relative(dir, input), output: relative(dir, output) };
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=build.js.map
|