pi-rules-md 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +163 -2
- package/RULE-SPEC.md +191 -0
- package/dist/cli.mjs +842 -0
- package/dist/extension.js +11297 -0
- package/index.ts +165 -0
- package/package.json +52 -3
- package/shell/collectors.test.ts +172 -0
- package/shell/collectors.ts +109 -0
- package/shell/discovery.test.ts +597 -0
- package/shell/discovery.ts +348 -0
- package/shell/injection.test.ts +164 -0
- package/shell/injection.ts +88 -0
- package/shell/pipeline.test.ts +243 -0
- package/shell/pipeline.ts +200 -0
- package/shell/read-rule.test.ts +57 -0
- package/shell/read-rule.ts +49 -0
- package/shell/rules-paths.test.ts +26 -0
- package/shell/rules-paths.ts +17 -0
- package/src/cli/constants.ts +1 -0
- package/src/cli/debug.ts +6 -0
- package/src/cli/doctor.test.ts +161 -0
- package/src/cli/doctor.ts +109 -0
- package/src/cli/install.test.ts +97 -0
- package/src/cli/install.ts +63 -0
- package/src/cli/interlock.test.ts +29 -0
- package/src/cli/interlock.ts +6 -0
- package/src/cli/main.test.ts +88 -0
- package/src/cli/main.ts +123 -0
- package/src/cli/match.test.ts +86 -0
- package/src/cli/match.ts +76 -0
- package/src/cli/packaging.test.ts +47 -0
- package/src/cli/paths.test.ts +54 -0
- package/src/cli/paths.ts +45 -0
- package/src/cli/pi-runner.test.ts +79 -0
- package/src/cli/pi-runner.ts +62 -0
- package/src/cli/registry.test.ts +68 -0
- package/src/cli/registry.ts +53 -0
- package/src/cli/scopes.ts +19 -0
- package/src/cli/settings.test.ts +76 -0
- package/src/cli/settings.ts +68 -0
- package/src/cli/status.test.ts +107 -0
- package/src/cli/status.ts +73 -0
- package/src/cli/uninstall.test.ts +149 -0
- package/src/cli/uninstall.ts +123 -0
- package/src/cli/update.test.ts +126 -0
- package/src/cli/update.ts +76 -0
- package/src/conditions/predicates.test.ts +118 -0
- package/src/conditions/predicates.ts +79 -0
- package/src/conditions/table.test.ts +314 -0
- package/src/conditions/table.ts +215 -0
- package/src/engine.test.ts +476 -0
- package/src/engine.ts +165 -0
- package/src/frontmatter.test.ts +376 -0
- package/src/frontmatter.ts +303 -0
- package/src/intent.test.ts +20 -0
- package/src/intent.ts +25 -0
- package/src/match.test.ts +336 -0
- package/src/match.ts +146 -0
- package/src/render.golden/multi-rule-mixed-reasons.md +18 -0
- package/src/render.golden/no-reasons.md +6 -0
- package/src/render.golden/single-rule.md +7 -0
- package/src/render.test.ts +162 -0
- package/src/render.ts +103 -0
- package/src/result.test.ts +27 -0
- package/src/result.ts +16 -0
- package/src/select.test.ts +280 -0
- package/src/select.ts +116 -0
- package/src/state.test.ts +205 -0
- package/src/state.ts +78 -0
- package/src/types.test.ts +291 -0
- package/src/types.ts +219 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
//#region src/cli/constants.ts
|
|
11
|
+
const PACKAGE_NAME = "pi-rules-md";
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/cli/interlock.ts
|
|
15
|
+
function sandboxRefusal(opts) {
|
|
16
|
+
if ((opts.env.VITEST === "true" || opts.env.NODE_ENV === "test") && opts.agentDir === void 0) return "refusing to mutate Pi settings without an explicit agent directory under a test runtime; pass --agent-dir or set PI_RULES_AGENT_DIR";
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/cli/match.ts
|
|
22
|
+
async function findMatchingEntries(packages, opts) {
|
|
23
|
+
const matches = [];
|
|
24
|
+
const unmatchedLocal = [];
|
|
25
|
+
for (const entry of packages) {
|
|
26
|
+
if (entry.kind === "malformed") continue;
|
|
27
|
+
const { source } = entry;
|
|
28
|
+
if (source.startsWith("npm:")) {
|
|
29
|
+
const spec = source.slice(4);
|
|
30
|
+
if (spec === "pi-rules-md") matches.push({
|
|
31
|
+
entry,
|
|
32
|
+
source,
|
|
33
|
+
kind: "npm"
|
|
34
|
+
});
|
|
35
|
+
else if (spec.startsWith(`${"pi-rules-md"}@`) && isVersion(spec.slice("pi-rules-md".length + 1))) matches.push({
|
|
36
|
+
entry,
|
|
37
|
+
source,
|
|
38
|
+
kind: "npm-pinned",
|
|
39
|
+
version: spec.slice(PACKAGE_NAME.length + 1)
|
|
40
|
+
});
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (source.startsWith("git:")) {
|
|
44
|
+
const repo = source.slice(4).split(/[?#]/, 1)[0] ?? "";
|
|
45
|
+
if (path.posix.basename(repo.replace(/\\/g, "/")).replace(/\.git$/i, "") === "pi-rules-md") matches.push({
|
|
46
|
+
entry,
|
|
47
|
+
source,
|
|
48
|
+
kind: "git"
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const expanded = source.startsWith("~") ? path.join(homedir(), source.slice(1).replace(/^[/\\]/, "")) : source;
|
|
53
|
+
const target = path.resolve(path.isAbsolute(expanded) ? expanded : path.join(opts.settingsDir, expanded));
|
|
54
|
+
try {
|
|
55
|
+
const canonicalPath = await realpath(target);
|
|
56
|
+
const metadata = JSON.parse(await readFile(path.join(canonicalPath, "package.json"), "utf8"));
|
|
57
|
+
if (metadata && typeof metadata === "object" && !Array.isArray(metadata) && metadata.name === "pi-rules-md") matches.push({
|
|
58
|
+
entry,
|
|
59
|
+
source,
|
|
60
|
+
kind: "local",
|
|
61
|
+
canonicalPath
|
|
62
|
+
});
|
|
63
|
+
else unmatchedLocal.push(entry);
|
|
64
|
+
} catch {
|
|
65
|
+
unmatchedLocal.push(entry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const groups = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const match of matches) {
|
|
70
|
+
if (!match.canonicalPath) continue;
|
|
71
|
+
groups.set(match.canonicalPath, [...groups.get(match.canonicalPath) ?? [], match]);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
matches,
|
|
75
|
+
unmatchedLocal,
|
|
76
|
+
duplicates: new Map([...groups].filter(([, group]) => group.length > 1))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function isVersion(version) {
|
|
80
|
+
return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/cli/paths.ts
|
|
85
|
+
function resolveAgentDir(explicit, env) {
|
|
86
|
+
return explicit ?? env.PI_RULES_AGENT_DIR ?? path.join(homedir(), ".pi", "agent");
|
|
87
|
+
}
|
|
88
|
+
function resolvePersonalSettingsPath(agentDir) {
|
|
89
|
+
return path.join(agentDir, "settings.json");
|
|
90
|
+
}
|
|
91
|
+
function resolveProjectSettingsPath(opts) {
|
|
92
|
+
const cwd = path.resolve(opts.cwd);
|
|
93
|
+
const gitRoot = findGitRoot(cwd);
|
|
94
|
+
if (!gitRoot) return settingsPathIfPresent(cwd);
|
|
95
|
+
let current = cwd;
|
|
96
|
+
while (true) {
|
|
97
|
+
const settings = settingsPathIfPresent(current);
|
|
98
|
+
if (settings) return settings;
|
|
99
|
+
if (current === gitRoot) return void 0;
|
|
100
|
+
current = path.dirname(current);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function resolveNpmStorePath(agentDir) {
|
|
104
|
+
return path.join(agentDir, "npm", "node_modules", PACKAGE_NAME);
|
|
105
|
+
}
|
|
106
|
+
function settingsPathIfPresent(directory) {
|
|
107
|
+
const settings = path.join(directory, ".pi", "settings.json");
|
|
108
|
+
return existsSync(settings) ? settings : void 0;
|
|
109
|
+
}
|
|
110
|
+
function findGitRoot(start) {
|
|
111
|
+
let current = start;
|
|
112
|
+
while (true) {
|
|
113
|
+
if (existsSync(path.join(current, ".git"))) return current;
|
|
114
|
+
const parent = path.dirname(current);
|
|
115
|
+
if (parent === current) return void 0;
|
|
116
|
+
current = parent;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/cli/pi-runner.ts
|
|
122
|
+
function runPi({ argv, timeoutMs = 3e4, dryRun = false, capture = false, env = process.env, stdout = [] }) {
|
|
123
|
+
if (dryRun) {
|
|
124
|
+
stdout.push(`[pi-rules] [dry-run] Would run: pi ${argv.join(" ")}`);
|
|
125
|
+
return Promise.resolve({ exitCode: 0 });
|
|
126
|
+
}
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
let child;
|
|
129
|
+
try {
|
|
130
|
+
child = spawn("pi", argv, {
|
|
131
|
+
shell: false,
|
|
132
|
+
env,
|
|
133
|
+
stdio: capture ? [
|
|
134
|
+
"ignore",
|
|
135
|
+
"pipe",
|
|
136
|
+
"pipe"
|
|
137
|
+
] : "inherit"
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
resolve({
|
|
141
|
+
exitCode: 1,
|
|
142
|
+
error: error instanceof Error ? error.message : String(error)
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
let out = "";
|
|
147
|
+
let err = "";
|
|
148
|
+
let timedOut = false;
|
|
149
|
+
let spawnError;
|
|
150
|
+
if (capture) {
|
|
151
|
+
child.stdout?.setEncoding("utf8").on("data", (chunk) => {
|
|
152
|
+
out += chunk;
|
|
153
|
+
});
|
|
154
|
+
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
155
|
+
err += chunk;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
child.once("error", (error) => {
|
|
159
|
+
spawnError = error;
|
|
160
|
+
});
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
timedOut = true;
|
|
163
|
+
child.kill("SIGKILL");
|
|
164
|
+
}, timeoutMs);
|
|
165
|
+
child.once("close", (code, signal) => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
if (spawnError?.code === "ENOENT") {
|
|
168
|
+
resolve({
|
|
169
|
+
exitCode: 1,
|
|
170
|
+
error: "pi CLI not found on PATH",
|
|
171
|
+
...capture ? {
|
|
172
|
+
stdout: out,
|
|
173
|
+
stderr: err
|
|
174
|
+
} : {}
|
|
175
|
+
});
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
resolve({
|
|
179
|
+
exitCode: timedOut ? 1 : code ?? 1,
|
|
180
|
+
...capture ? {
|
|
181
|
+
stdout: out,
|
|
182
|
+
stderr: err
|
|
183
|
+
} : {},
|
|
184
|
+
...spawnError ? { error: spawnError.message } : {},
|
|
185
|
+
...timedOut ? {
|
|
186
|
+
timedOut: true,
|
|
187
|
+
signal: "SIGKILL"
|
|
188
|
+
} : signal ? { signal } : {}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/cli/settings.ts
|
|
196
|
+
async function loadSettings(target, opts) {
|
|
197
|
+
const settingsPath = target === "personal" ? resolvePersonalSettingsPath(resolveAgentDir(opts.agentDir, {})) : target === "project" ? resolveProjectSettingsPath({ cwd: opts.cwd }) ?? path.join(opts.cwd, ".pi", "settings.json") : path.resolve(target);
|
|
198
|
+
let text;
|
|
199
|
+
try {
|
|
200
|
+
text = await readFile(settingsPath, "utf8");
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (isMissing(error)) return {
|
|
203
|
+
path: settingsPath,
|
|
204
|
+
exists: false,
|
|
205
|
+
packages: []
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
path: settingsPath,
|
|
209
|
+
exists: true,
|
|
210
|
+
readError: errorMessage(error),
|
|
211
|
+
packages: []
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
let parsed;
|
|
215
|
+
try {
|
|
216
|
+
parsed = JSON.parse(text);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return {
|
|
219
|
+
path: settingsPath,
|
|
220
|
+
exists: true,
|
|
221
|
+
parseError: errorMessage(error),
|
|
222
|
+
packages: []
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.packages)) return {
|
|
226
|
+
path: settingsPath,
|
|
227
|
+
exists: true,
|
|
228
|
+
shapeError: "\"packages\" must be an array",
|
|
229
|
+
packages: []
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
path: settingsPath,
|
|
233
|
+
exists: true,
|
|
234
|
+
packages: parsed.packages.map((raw) => {
|
|
235
|
+
if (typeof raw === "string") return {
|
|
236
|
+
kind: "string",
|
|
237
|
+
source: raw
|
|
238
|
+
};
|
|
239
|
+
if (isRecord(raw) && typeof raw.source === "string") return {
|
|
240
|
+
kind: "object",
|
|
241
|
+
source: raw.source
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
kind: "malformed",
|
|
245
|
+
raw
|
|
246
|
+
};
|
|
247
|
+
})
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isRecord(value) {
|
|
251
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function isMissing(error) {
|
|
254
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
255
|
+
}
|
|
256
|
+
function errorMessage(error) {
|
|
257
|
+
return error instanceof Error ? error.message : String(error);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/cli/install.ts
|
|
262
|
+
function ioFor$4(options) {
|
|
263
|
+
return options.io ?? {
|
|
264
|
+
env: process.env,
|
|
265
|
+
stdout: { push: (...lines) => console.log(...lines) },
|
|
266
|
+
stderr: { push: (...lines) => console.error(...lines) }
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function settingsProblem$4(settings) {
|
|
270
|
+
if (settings.readError) return settings.readError;
|
|
271
|
+
if (settings.parseError) return settings.parseError;
|
|
272
|
+
return settings.shapeError;
|
|
273
|
+
}
|
|
274
|
+
async function installFlow(options) {
|
|
275
|
+
const io = ioFor$4(options);
|
|
276
|
+
const refusal = sandboxRefusal({
|
|
277
|
+
agentDir: options.agentDir,
|
|
278
|
+
env: io.env
|
|
279
|
+
});
|
|
280
|
+
if (refusal) {
|
|
281
|
+
io.stderr.push(refusal);
|
|
282
|
+
return 3;
|
|
283
|
+
}
|
|
284
|
+
const agentDir = resolveAgentDir(options.agentDir, process.env);
|
|
285
|
+
const local = options.local === true;
|
|
286
|
+
let matches = [];
|
|
287
|
+
const settings = await loadSettings(local ? "project" : "personal", {
|
|
288
|
+
agentDir,
|
|
289
|
+
cwd: process.cwd()
|
|
290
|
+
});
|
|
291
|
+
const { path: settingsPath, exists, packages } = settings;
|
|
292
|
+
const problem = settingsProblem$4(settings);
|
|
293
|
+
if (problem) io.stderr.push(`warning: unable to read settings at ${settingsPath}: ${problem}`);
|
|
294
|
+
else if (exists) {
|
|
295
|
+
const { matches: foundMatches } = await findMatchingEntries(packages, { settingsDir: path.dirname(settingsPath) });
|
|
296
|
+
matches = foundMatches;
|
|
297
|
+
}
|
|
298
|
+
let removed = 0;
|
|
299
|
+
for (const match of matches) {
|
|
300
|
+
const result = await runPi({
|
|
301
|
+
argv: [
|
|
302
|
+
"remove",
|
|
303
|
+
match.source,
|
|
304
|
+
...local ? ["--local"] : []
|
|
305
|
+
],
|
|
306
|
+
dryRun: options.dryRun,
|
|
307
|
+
env: io.env,
|
|
308
|
+
stdout: io.stdout
|
|
309
|
+
});
|
|
310
|
+
if (result.exitCode !== 0) return result.exitCode;
|
|
311
|
+
removed++;
|
|
312
|
+
}
|
|
313
|
+
const result = await runPi({
|
|
314
|
+
argv: [
|
|
315
|
+
"install",
|
|
316
|
+
`npm:${PACKAGE_NAME}`,
|
|
317
|
+
...local ? ["--local"] : []
|
|
318
|
+
],
|
|
319
|
+
dryRun: options.dryRun,
|
|
320
|
+
env: io.env,
|
|
321
|
+
stdout: io.stdout
|
|
322
|
+
});
|
|
323
|
+
if (result.exitCode !== 0) return result.exitCode;
|
|
324
|
+
const stale = removed > 0 ? `, removed ${removed} stale entr${removed === 1 ? "y" : "ies"}` : "";
|
|
325
|
+
io.stdout.push(`✓ Installed ${PACKAGE_NAME} (npm:${PACKAGE_NAME})${stale}\ntip: Run pi-rules doctor`);
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/cli/scopes.ts
|
|
331
|
+
async function loadScopes(scopes, opts) {
|
|
332
|
+
const loaded = [];
|
|
333
|
+
for (const scope of scopes) loaded.push({
|
|
334
|
+
scope,
|
|
335
|
+
settings: await loadSettings(scope, opts)
|
|
336
|
+
});
|
|
337
|
+
return loaded;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/cli/uninstall.ts
|
|
342
|
+
function ioFor$3(options) {
|
|
343
|
+
return options.io ?? {
|
|
344
|
+
env: process.env,
|
|
345
|
+
stdout: { push: (...lines) => console.log(...lines) },
|
|
346
|
+
stderr: { push: (...lines) => console.error(...lines) }
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function settingsProblem$3(settings) {
|
|
350
|
+
if (settings.readError) return settings.readError;
|
|
351
|
+
if (settings.parseError) return settings.parseError;
|
|
352
|
+
return settings.shapeError;
|
|
353
|
+
}
|
|
354
|
+
function isStrictlyInside(parent, child) {
|
|
355
|
+
const relative = path.relative(parent, child);
|
|
356
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
357
|
+
}
|
|
358
|
+
function purgeStore(agentDir, io, dryRun) {
|
|
359
|
+
const storePath = resolveNpmStorePath(agentDir);
|
|
360
|
+
let realAgent;
|
|
361
|
+
let npm;
|
|
362
|
+
let nodeModules;
|
|
363
|
+
try {
|
|
364
|
+
realAgent = realpathSync(agentDir);
|
|
365
|
+
npm = realpathSync(path.join(agentDir, "npm"));
|
|
366
|
+
nodeModules = realpathSync(path.join(agentDir, "npm", "node_modules"));
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
369
|
+
io.stdout.push("✓ package store already absent");
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
io.stderr.push(`refusing to purge unsafe package store path: ${storePath}`);
|
|
373
|
+
return 1;
|
|
374
|
+
}
|
|
375
|
+
if (!isStrictlyInside(realAgent, npm) || !isStrictlyInside(npm, nodeModules)) {
|
|
376
|
+
io.stderr.push(`refusing to purge unsafe package store path: ${storePath}`);
|
|
377
|
+
return 1;
|
|
378
|
+
}
|
|
379
|
+
let store;
|
|
380
|
+
try {
|
|
381
|
+
store = realpathSync(storePath);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
384
|
+
io.stdout.push("✓ package store already absent");
|
|
385
|
+
return 0;
|
|
386
|
+
}
|
|
387
|
+
io.stderr.push(`refusing to purge unsafe package store path: ${storePath}`);
|
|
388
|
+
return 1;
|
|
389
|
+
}
|
|
390
|
+
if (!isStrictlyInside(nodeModules, store) || path.basename(storePath) !== "pi-rules-md") {
|
|
391
|
+
io.stderr.push(`refusing to purge unsafe package store path: ${storePath}`);
|
|
392
|
+
return 1;
|
|
393
|
+
}
|
|
394
|
+
if (dryRun) {
|
|
395
|
+
io.stdout.push(`[pi-rules] [dry-run] Would remove ${storePath}`);
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
rmSync(storePath, {
|
|
399
|
+
recursive: true,
|
|
400
|
+
force: true
|
|
401
|
+
});
|
|
402
|
+
io.stdout.push(`✓ removed package store ${storePath}`);
|
|
403
|
+
io.stderr.push("run pi update --extensions to reconcile the package store");
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
406
|
+
async function uninstallFlow(options) {
|
|
407
|
+
const io = ioFor$3(options);
|
|
408
|
+
const refusal = sandboxRefusal({
|
|
409
|
+
agentDir: options.agentDir,
|
|
410
|
+
env: io.env
|
|
411
|
+
});
|
|
412
|
+
if (refusal) {
|
|
413
|
+
io.stderr.push(refusal);
|
|
414
|
+
return 3;
|
|
415
|
+
}
|
|
416
|
+
const agentDir = resolveAgentDir(options.agentDir, process.env);
|
|
417
|
+
const scopes = options.local ? ["project"] : ["personal", "project"];
|
|
418
|
+
const loaded = await loadScopes(scopes, {
|
|
419
|
+
agentDir,
|
|
420
|
+
cwd: process.cwd()
|
|
421
|
+
});
|
|
422
|
+
const matches = [];
|
|
423
|
+
for (const { scope, settings } of loaded) {
|
|
424
|
+
const problem = settingsProblem$3(settings);
|
|
425
|
+
if (problem) {
|
|
426
|
+
io.stderr.push(`error: unable to read settings at ${settings.path}: ${problem}`);
|
|
427
|
+
return 1;
|
|
428
|
+
}
|
|
429
|
+
if (!settings.exists) continue;
|
|
430
|
+
const result = await findMatchingEntries(settings.packages, { settingsDir: path.dirname(settings.path) });
|
|
431
|
+
for (const entry of result.unmatchedLocal) if (entry.kind !== "malformed") io.stderr.push(`warning: local entry does not look like pi-rules-md, leaving it: ${entry.source}`);
|
|
432
|
+
matches.push(...result.matches.map(({ source }) => ({
|
|
433
|
+
scope,
|
|
434
|
+
source
|
|
435
|
+
})));
|
|
436
|
+
}
|
|
437
|
+
const hasPersonalScope = scopes.includes("personal");
|
|
438
|
+
if (matches.length === 0) {
|
|
439
|
+
io.stdout.push("✓ pi-rules-md not installed");
|
|
440
|
+
if (options.purge && hasPersonalScope) return purgeStore(agentDir, io, options.dryRun);
|
|
441
|
+
if (options.purge && options.local) io.stdout.push("nothing to purge in project scope");
|
|
442
|
+
return 0;
|
|
443
|
+
}
|
|
444
|
+
let anyFailed = false;
|
|
445
|
+
for (const match of matches) if ((await runPi({
|
|
446
|
+
argv: [
|
|
447
|
+
"remove",
|
|
448
|
+
match.source,
|
|
449
|
+
...match.scope === "project" ? ["--local"] : []
|
|
450
|
+
],
|
|
451
|
+
dryRun: options.dryRun,
|
|
452
|
+
env: io.env,
|
|
453
|
+
stdout: io.stdout
|
|
454
|
+
})).exitCode !== 0) anyFailed = true;
|
|
455
|
+
if (options.purge) {
|
|
456
|
+
if (purgeStore(agentDir, io, options.dryRun) !== 0) anyFailed = true;
|
|
457
|
+
}
|
|
458
|
+
return anyFailed ? 1 : 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/cli/registry.ts
|
|
463
|
+
const REGISTRY_LATEST_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
464
|
+
const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/;
|
|
465
|
+
async function fetchLatestVersion(fetchImpl = fetch, timeoutMs = 3e3) {
|
|
466
|
+
let timer;
|
|
467
|
+
const timeout = new Promise((resolve) => {
|
|
468
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
469
|
+
});
|
|
470
|
+
const request = (async () => {
|
|
471
|
+
const response = await fetchImpl(REGISTRY_LATEST_URL, { signal: AbortSignal.timeout(timeoutMs) });
|
|
472
|
+
if (!response.ok) return null;
|
|
473
|
+
const body = await response.json();
|
|
474
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
475
|
+
const { version } = body;
|
|
476
|
+
return typeof version === "string" && VERSION_PATTERN.test(version) ? version : null;
|
|
477
|
+
})();
|
|
478
|
+
try {
|
|
479
|
+
return await Promise.race([request.catch(() => null), timeout]);
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
} finally {
|
|
483
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function parseSemver(version) {
|
|
487
|
+
if (!VERSION_PATTERN.test(version)) return null;
|
|
488
|
+
const [core] = version.split("-");
|
|
489
|
+
const parts = core?.split(".").map(Number);
|
|
490
|
+
if (!parts || parts.length !== 3 || parts.some((part) => !Number.isSafeInteger(part))) return null;
|
|
491
|
+
return {
|
|
492
|
+
major: parts[0],
|
|
493
|
+
minor: parts[1],
|
|
494
|
+
patch: parts[2]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function compareSemver(a, b) {
|
|
498
|
+
if (a == null || b == null) return null;
|
|
499
|
+
const left = parseSemver(a);
|
|
500
|
+
const right = parseSemver(b);
|
|
501
|
+
if (!left || !right) return null;
|
|
502
|
+
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
|
|
503
|
+
}
|
|
504
|
+
function isStale(installed, latest) {
|
|
505
|
+
const comparison = compareSemver(latest, installed);
|
|
506
|
+
return comparison !== null && comparison > 0;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/cli/update.ts
|
|
511
|
+
function ioFor$2(options) {
|
|
512
|
+
return options.io ?? {
|
|
513
|
+
env: process.env,
|
|
514
|
+
stdout: { push: (...lines) => console.log(...lines) },
|
|
515
|
+
stderr: { push: (...lines) => console.error(...lines) }
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function settingsProblem$2(settings) {
|
|
519
|
+
return settings.readError ?? settings.parseError ?? settings.shapeError;
|
|
520
|
+
}
|
|
521
|
+
async function updateFlow(options) {
|
|
522
|
+
const io = ioFor$2(options);
|
|
523
|
+
const refusal = sandboxRefusal({
|
|
524
|
+
agentDir: options.agentDir,
|
|
525
|
+
env: io.env
|
|
526
|
+
});
|
|
527
|
+
if (refusal) {
|
|
528
|
+
io.stderr.push(refusal);
|
|
529
|
+
return 3;
|
|
530
|
+
}
|
|
531
|
+
const agentDir = resolveAgentDir(options.agentDir, process.env);
|
|
532
|
+
const scopes = options.local ? ["project"] : ["personal", "project"];
|
|
533
|
+
const loaded = await loadScopes(scopes, {
|
|
534
|
+
agentDir,
|
|
535
|
+
cwd: process.cwd()
|
|
536
|
+
});
|
|
537
|
+
const matches = [];
|
|
538
|
+
for (const { scope, settings } of loaded) {
|
|
539
|
+
const problem = settingsProblem$2(settings);
|
|
540
|
+
if (problem) {
|
|
541
|
+
io.stderr.push(`error: unable to read settings at ${settings.path}: ${problem}`);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (!settings.exists) continue;
|
|
545
|
+
const result = await findMatchingEntries(settings.packages, { settingsDir: path.dirname(settings.path) });
|
|
546
|
+
matches.push(...result.matches.map(({ source, kind, version }) => ({
|
|
547
|
+
scope,
|
|
548
|
+
source,
|
|
549
|
+
kind,
|
|
550
|
+
...version === void 0 ? {} : { version }
|
|
551
|
+
})));
|
|
552
|
+
}
|
|
553
|
+
if (matches.length === 0) {
|
|
554
|
+
io.stdout.push("nothing to update (no pi-rules-md entries found)");
|
|
555
|
+
return 0;
|
|
556
|
+
}
|
|
557
|
+
if (matches.some(({ kind }) => kind === "npm" || kind === "npm-pinned")) {
|
|
558
|
+
const latest = await fetchLatestVersion(options.fetchImpl ?? fetch);
|
|
559
|
+
if (latest) io.stdout.push(`latest: ${latest}`);
|
|
560
|
+
}
|
|
561
|
+
let anyFailed = false;
|
|
562
|
+
const seen = /* @__PURE__ */ new Set();
|
|
563
|
+
for (const match of matches) {
|
|
564
|
+
if (match.kind === "local") {
|
|
565
|
+
io.stdout.push(`${match.scope}: ${match.source} tracks the working tree (not updated)`);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (match.kind === "npm-pinned") {
|
|
569
|
+
io.stdout.push(`${match.scope}: ${match.source} pinned to ${match.version}, not updating`);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (match.kind !== "npm") continue;
|
|
573
|
+
const key = `${match.scope}:${match.source}`;
|
|
574
|
+
if (seen.has(key)) continue;
|
|
575
|
+
seen.add(key);
|
|
576
|
+
if ((await runPi({
|
|
577
|
+
argv: [
|
|
578
|
+
"update",
|
|
579
|
+
match.source,
|
|
580
|
+
...match.scope === "project" ? ["--local"] : []
|
|
581
|
+
],
|
|
582
|
+
dryRun: options.dryRun,
|
|
583
|
+
env: io.env,
|
|
584
|
+
stdout: io.stdout
|
|
585
|
+
})).exitCode !== 0) anyFailed = true;
|
|
586
|
+
}
|
|
587
|
+
return anyFailed ? 1 : 0;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/cli/status.ts
|
|
592
|
+
function ioFor$1(options) {
|
|
593
|
+
return options.io ?? {
|
|
594
|
+
env: process.env,
|
|
595
|
+
stdout: { push: (...lines) => console.log(...lines) },
|
|
596
|
+
stderr: { push: (...lines) => console.error(...lines) }
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function settingsProblem$1(settings) {
|
|
600
|
+
return settings.readError ?? settings.parseError ?? settings.shapeError;
|
|
601
|
+
}
|
|
602
|
+
function readPackageVersion(pkgDir) {
|
|
603
|
+
try {
|
|
604
|
+
const metadata = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8"));
|
|
605
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return void 0;
|
|
606
|
+
const { version } = metadata;
|
|
607
|
+
return typeof version === "string" ? version : void 0;
|
|
608
|
+
} catch {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function installedVersion$1(match, agentDir) {
|
|
613
|
+
const packageDir = match.kind === "local" ? match.canonicalPath : resolveNpmStorePath(agentDir);
|
|
614
|
+
return packageDir ? readPackageVersion(packageDir) : void 0;
|
|
615
|
+
}
|
|
616
|
+
function kindLabel$1(kind) {
|
|
617
|
+
return kind === "npm" ? "npm" : kind === "npm-pinned" ? "npm-pinned" : kind;
|
|
618
|
+
}
|
|
619
|
+
async function statusFlow(options) {
|
|
620
|
+
const io = ioFor$1(options);
|
|
621
|
+
const agentDir = resolveAgentDir(options.agentDir, process.env);
|
|
622
|
+
const loaded = await loadScopes(["personal", "project"], {
|
|
623
|
+
agentDir,
|
|
624
|
+
cwd: process.cwd()
|
|
625
|
+
});
|
|
626
|
+
const matches = [];
|
|
627
|
+
for (const { scope, settings } of loaded) {
|
|
628
|
+
const problem = settingsProblem$1(settings);
|
|
629
|
+
if (problem) {
|
|
630
|
+
io.stderr.push(`error: unable to read settings at ${settings.path}: ${problem}`);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (!settings.exists) continue;
|
|
634
|
+
const result = await findMatchingEntries(settings.packages, { settingsDir: path.dirname(settings.path) });
|
|
635
|
+
for (const match of result.matches) {
|
|
636
|
+
const installed = installedVersion$1(match, agentDir);
|
|
637
|
+
matches.push({
|
|
638
|
+
scope,
|
|
639
|
+
match,
|
|
640
|
+
installed
|
|
641
|
+
});
|
|
642
|
+
io.stdout.push(`${scope}: ${match.source} (${kindLabel$1(match.kind)}, installed ${installed ?? "unknown"})`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (matches.length === 0) {
|
|
646
|
+
io.stdout.push("pi-rules-md not installed");
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
const latest = await fetchLatestVersion(options.fetchImpl ?? fetch);
|
|
650
|
+
if (latest) {
|
|
651
|
+
io.stdout.push(`latest: ${latest}`);
|
|
652
|
+
for (const { scope, match, installed } of matches) if ((match.kind === "npm" || match.kind === "npm-pinned") && installed && isStale(installed, latest)) io.stdout.push(`stale: ${scope} ${match.source} installed ${installed} < latest ${latest}`);
|
|
653
|
+
}
|
|
654
|
+
return 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/cli/doctor.ts
|
|
659
|
+
const MIN_NODE_VERSION = "22.19.0";
|
|
660
|
+
const DIST_FILES = ["dist/extension.js", "dist/cli.mjs"];
|
|
661
|
+
function ioFor(options) {
|
|
662
|
+
return options.io ?? {
|
|
663
|
+
env: process.env,
|
|
664
|
+
stdout: { push: (...lines) => console.log(...lines) },
|
|
665
|
+
stderr: { push: (...lines) => console.error(...lines) }
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function settingsProblem(settings) {
|
|
669
|
+
return settings.readError ?? settings.parseError ?? settings.shapeError;
|
|
670
|
+
}
|
|
671
|
+
function checkNodeVersion(current) {
|
|
672
|
+
const comparison = compareSemver(current, MIN_NODE_VERSION);
|
|
673
|
+
if (comparison === null) return "unknown";
|
|
674
|
+
return comparison >= 0 ? "ok" : "outdated";
|
|
675
|
+
}
|
|
676
|
+
function installedVersion(match, agentDir) {
|
|
677
|
+
const packageDir = match.kind === "local" ? match.canonicalPath : resolveNpmStorePath(agentDir);
|
|
678
|
+
return packageDir ? readPackageVersion(packageDir) : void 0;
|
|
679
|
+
}
|
|
680
|
+
function kindLabel(kind) {
|
|
681
|
+
return kind === "npm-pinned" ? "npm-pinned" : kind;
|
|
682
|
+
}
|
|
683
|
+
async function doctorFlow(options) {
|
|
684
|
+
const io = ioFor(options);
|
|
685
|
+
const agentDir = resolveAgentDir(options.agentDir, process.env);
|
|
686
|
+
const loaded = await loadScopes(["personal", "project"], {
|
|
687
|
+
agentDir,
|
|
688
|
+
cwd: process.cwd()
|
|
689
|
+
});
|
|
690
|
+
const structural = [];
|
|
691
|
+
const advisory = [];
|
|
692
|
+
const matches = [];
|
|
693
|
+
for (const { scope, settings } of loaded) {
|
|
694
|
+
const problem = settingsProblem(settings);
|
|
695
|
+
if (problem) {
|
|
696
|
+
advisory.push(`advisory: ${scope} settings unreadable (${problem})`);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (!settings.exists) {
|
|
700
|
+
io.stdout.push(`${scope}: no settings`);
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
const result = await findMatchingEntries(settings.packages, { settingsDir: path.dirname(settings.path) });
|
|
704
|
+
for (const match of result.matches) {
|
|
705
|
+
const installed = installedVersion(match, agentDir);
|
|
706
|
+
matches.push({
|
|
707
|
+
scope,
|
|
708
|
+
match,
|
|
709
|
+
installed
|
|
710
|
+
});
|
|
711
|
+
io.stdout.push(`${scope}: ${match.source} (${kindLabel(match.kind)}, installed ${installed ?? "unknown"})`);
|
|
712
|
+
if (match.kind === "local") {
|
|
713
|
+
for (const file of DIST_FILES) if (!match.canonicalPath || !existsSync(path.join(match.canonicalPath, file))) advisory.push(`advisory: ${scope} ${match.source} missing ${file} (run pnpm build in ${match.canonicalPath ?? "unknown"})`);
|
|
714
|
+
} else for (const file of DIST_FILES) if (!existsSync(path.join(resolveNpmStorePath(agentDir), file))) structural.push(`structural: ${scope} ${match.source} package store missing ${file}`);
|
|
715
|
+
}
|
|
716
|
+
for (const [canonicalPath, group] of result.duplicates) advisory.push(`advisory: duplicate entries resolve to ${canonicalPath} (${group.length} entries)`);
|
|
717
|
+
}
|
|
718
|
+
const nodeVersion = process.versions.node;
|
|
719
|
+
const nodeStatus = checkNodeVersion(nodeVersion);
|
|
720
|
+
if (nodeStatus === "outdated") structural.push(`structural: node ${nodeVersion} < ${MIN_NODE_VERSION} (engines requirement)`);
|
|
721
|
+
if (nodeStatus === "unknown") advisory.push(`advisory: unable to verify node version (${nodeVersion})`);
|
|
722
|
+
if (matches.length === 0) structural.push("structural: pi-rules-md is not installed (run pi-rules install)");
|
|
723
|
+
let latestLine;
|
|
724
|
+
if (matches.length > 0) {
|
|
725
|
+
const latest = await fetchLatestVersion(options.fetchImpl ?? fetch);
|
|
726
|
+
if (latest) {
|
|
727
|
+
latestLine = `latest: ${latest}`;
|
|
728
|
+
for (const { scope, match, installed } of matches) if (match.kind === "npm-pinned") advisory.push(`advisory: ${scope} ${match.source} pinned to ${match.version} — update manually if stale`);
|
|
729
|
+
else if (match.kind === "npm" && installed && isStale(installed, latest)) advisory.push(`advisory: ${scope} ${match.source} stale (installed ${installed} < latest ${latest})`);
|
|
730
|
+
} else advisory.push("advisory: registry unreachable, staleness skipped");
|
|
731
|
+
}
|
|
732
|
+
io.stdout.push(...structural);
|
|
733
|
+
if (latestLine) io.stdout.push(latestLine);
|
|
734
|
+
io.stdout.push(...advisory);
|
|
735
|
+
const structuralCount = structural.length;
|
|
736
|
+
io.stdout.push(structuralCount === 0 ? "summary: healthy" : `summary: ${structuralCount} structural issue(s)`);
|
|
737
|
+
return structuralCount === 0 ? 0 : 1;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/cli/main.ts
|
|
742
|
+
const USAGE = `Usage: pi-rules <command> [options]
|
|
743
|
+
|
|
744
|
+
Commands:
|
|
745
|
+
install Install pi-rules-md
|
|
746
|
+
uninstall Remove pi-rules-md
|
|
747
|
+
update Update pi-rules-md
|
|
748
|
+
status Show installation status
|
|
749
|
+
doctor Diagnose installation health
|
|
750
|
+
|
|
751
|
+
Options:
|
|
752
|
+
--local Use the project scope (install/uninstall/update)
|
|
753
|
+
--purge Remove the package directory (uninstall)
|
|
754
|
+
--agent-dir <dir> Use a specific Pi agent directory
|
|
755
|
+
--dry-run Print actions without changing anything
|
|
756
|
+
--help Show this help`;
|
|
757
|
+
var NotImplementedFlowError = class extends Error {
|
|
758
|
+
constructor(name) {
|
|
759
|
+
super(`${name} flow not implemented`);
|
|
760
|
+
this.name = "NotImplementedFlowError";
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
const defaultFlows = {
|
|
764
|
+
install: installFlow,
|
|
765
|
+
uninstall: uninstallFlow,
|
|
766
|
+
update: updateFlow,
|
|
767
|
+
status: statusFlow,
|
|
768
|
+
doctor: doctorFlow
|
|
769
|
+
};
|
|
770
|
+
async function runCli(args, io, flows = defaultFlows) {
|
|
771
|
+
const [command, ...commandArgs] = args;
|
|
772
|
+
if (command === "--help") {
|
|
773
|
+
io.stdout.push(USAGE);
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
if (!command) {
|
|
777
|
+
io.stderr.push(USAGE);
|
|
778
|
+
return 2;
|
|
779
|
+
}
|
|
780
|
+
if (!(command in defaultFlows)) {
|
|
781
|
+
io.stderr.push(USAGE);
|
|
782
|
+
return 2;
|
|
783
|
+
}
|
|
784
|
+
let options;
|
|
785
|
+
try {
|
|
786
|
+
const { values } = parseArgs({
|
|
787
|
+
args: commandArgs,
|
|
788
|
+
strict: true,
|
|
789
|
+
allowPositionals: false,
|
|
790
|
+
options: {
|
|
791
|
+
...command === "install" || command === "uninstall" || command === "update" ? { local: { type: "boolean" } } : {},
|
|
792
|
+
...command === "uninstall" ? { purge: { type: "boolean" } } : {},
|
|
793
|
+
"agent-dir": { type: "string" },
|
|
794
|
+
"dry-run": { type: "boolean" },
|
|
795
|
+
help: { type: "boolean" }
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
if (values.help) {
|
|
799
|
+
io.stdout.push(USAGE);
|
|
800
|
+
return 0;
|
|
801
|
+
}
|
|
802
|
+
const agentDir = values["agent-dir"] ?? io.env.PI_RULES_AGENT_DIR;
|
|
803
|
+
if (agentDir !== void 0 && agentDir.trim() === "") {
|
|
804
|
+
io.stderr.push(`error: --agent-dir requires a non-empty path\n${USAGE}`);
|
|
805
|
+
return 2;
|
|
806
|
+
}
|
|
807
|
+
options = {
|
|
808
|
+
...agentDir === void 0 ? {} : { agentDir },
|
|
809
|
+
local: values.local === true,
|
|
810
|
+
purge: values.purge === true,
|
|
811
|
+
dryRun: values["dry-run"] ?? false
|
|
812
|
+
};
|
|
813
|
+
} catch (error) {
|
|
814
|
+
io.stderr.push(`${error instanceof Error ? error.message : String(error)}\n${USAGE}`);
|
|
815
|
+
return 2;
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
return await flows[command]({
|
|
819
|
+
...options,
|
|
820
|
+
io
|
|
821
|
+
});
|
|
822
|
+
} catch (error) {
|
|
823
|
+
if (error instanceof NotImplementedFlowError) {
|
|
824
|
+
io.stderr.push(`error: ${error.message} in this build; it lands in an upcoming release.\n${USAGE}`);
|
|
825
|
+
return 2;
|
|
826
|
+
}
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
831
|
+
const io = {
|
|
832
|
+
env: process.env,
|
|
833
|
+
stdout: [],
|
|
834
|
+
stderr: []
|
|
835
|
+
};
|
|
836
|
+
process.exitCode = await runCli(process.argv.slice(2), io);
|
|
837
|
+
for (const line of io.stdout) console.log(line);
|
|
838
|
+
for (const line of io.stderr) console.error(line);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
//#endregion
|
|
842
|
+
export { NotImplementedFlowError, USAGE, runCli };
|