superpowers-manager 0.2.0 → 0.3.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.
Files changed (64) hide show
  1. package/README.md +112 -47
  2. package/bin/superpowers-manager.js +9 -283
  3. package/dist/adapter-result.js +204 -0
  4. package/dist/adapter.js +649 -0
  5. package/dist/atomic.js +142 -0
  6. package/dist/cli-arguments.js +32 -0
  7. package/dist/cli.js +293 -0
  8. package/dist/codex-json.js +89 -0
  9. package/dist/codex-state.js +78 -0
  10. package/dist/commands/context.js +1 -0
  11. package/dist/commands/install.js +376 -0
  12. package/dist/commands/pin.js +79 -0
  13. package/dist/commands/prepare.js +527 -0
  14. package/dist/commands/probe.js +392 -0
  15. package/dist/commands/track-latest.js +44 -0
  16. package/dist/commands/uninstall.js +308 -0
  17. package/dist/commands/unpin.js +95 -0
  18. package/dist/commands/update.js +138 -0
  19. package/dist/domain/fingerprint.js +4 -0
  20. package/dist/domain/refs.js +37 -0
  21. package/dist/effective-selection.js +123 -0
  22. package/dist/generated-plugin.js +748 -0
  23. package/dist/git.js +38 -0
  24. package/dist/hooks.js +290 -0
  25. package/dist/lifecycle.js +220 -0
  26. package/dist/manifest-overlay.js +125 -0
  27. package/dist/provenance.js +111 -0
  28. package/dist/python-json-format.js +83 -0
  29. package/dist/python-json.js +29 -0
  30. package/dist/python-text.js +74 -0
  31. package/dist/safe-path.js +137 -0
  32. package/dist/safety-error.js +10 -0
  33. package/dist/selection-state-cli.js +85 -0
  34. package/dist/selection-store.js +172 -0
  35. package/dist/selection.js +200 -0
  36. package/dist/status.js +24 -0
  37. package/dist/strict-json.js +248 -0
  38. package/dist/upstream-cli.js +110 -0
  39. package/dist/upstream-version.js +40 -0
  40. package/dist/upstream.js +253 -0
  41. package/dist/validate-generated-plugin-cli.js +118 -0
  42. package/dist/validator.js +317 -0
  43. package/dist/workspace.js +139 -0
  44. package/package.json +25 -2
  45. package/scripts/adapters/codex/adapter +0 -603
  46. package/scripts/adapters/codex/lib.sh +0 -449
  47. package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
  48. package/scripts/core/adapter.sh +0 -103
  49. package/scripts/core/common.sh +0 -59
  50. package/scripts/core/lifecycle.sh +0 -141
  51. package/scripts/core/provenance.sh +0 -135
  52. package/scripts/core/selection-state.py +0 -414
  53. package/scripts/core/selection.sh +0 -168
  54. package/scripts/core/status.sh +0 -27
  55. package/scripts/core/upstream.sh +0 -280
  56. package/scripts/core/validate-adapter-response.py +0 -284
  57. package/scripts/install +0 -59
  58. package/scripts/pin +0 -51
  59. package/scripts/prepare +0 -117
  60. package/scripts/probe +0 -82
  61. package/scripts/track-latest +0 -30
  62. package/scripts/uninstall +0 -35
  63. package/scripts/unpin +0 -41
  64. package/scripts/update +0 -34
@@ -0,0 +1,110 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { oneLine, parseFlags, UsageError } from "./cli-arguments.js";
3
+ import { COMMIT_INPUT_RE, TAG_RE } from "./domain/refs.js";
4
+ import { fetchExactCommit, gitSafeSource, resolveExactTag, resolveRef, verifyRawCommit, } from "./upstream.js";
5
+ import { manifestVersionForRef } from "./upstream-version.js";
6
+ const COMMAND_FLAGS = {
7
+ "resolve-ref": ["source", "ref"],
8
+ "resolve-exact-tag": ["source", "ref"],
9
+ "verify-raw-commit": ["source", "commit", "workspace-parent"],
10
+ "fetch-exact-commit": ["source", "commit", "repository", "workspace-parent"],
11
+ "manifest-version": [
12
+ "requested-ref",
13
+ "resolution-kind",
14
+ "resolved-ref",
15
+ "commit",
16
+ ],
17
+ "pin-kind": ["ref"],
18
+ "safe-source": ["source"],
19
+ };
20
+ const RESOLUTION_KINDS = [
21
+ "latest-release",
22
+ "tag",
23
+ "ref",
24
+ "raw-commit",
25
+ ];
26
+ function parseResolutionKind(value) {
27
+ const found = RESOLUTION_KINDS.find((kind) => kind === value);
28
+ if (found === undefined) {
29
+ throw new UsageError(`unknown resolution kind: ${value}`);
30
+ }
31
+ return found;
32
+ }
33
+ function parseCommand(argv) {
34
+ const [name, ...rest] = argv;
35
+ if (name === undefined)
36
+ throw new UsageError("a subcommand is required");
37
+ if (!Object.hasOwn(COMMAND_FLAGS, name)) {
38
+ throw new UsageError(`unknown subcommand: ${name}`);
39
+ }
40
+ const command = name;
41
+ return { name: command, flags: parseFlags(rest, COMMAND_FLAGS[command]) };
42
+ }
43
+ async function dispatch({ name, flags }) {
44
+ if (name === "resolve-ref") {
45
+ const resolution = await resolveRef(flags.source, flags.ref);
46
+ process.stdout.write(`${resolution.kind} ${resolution.ref} ${resolution.commit}\n`);
47
+ return;
48
+ }
49
+ if (name === "resolve-exact-tag") {
50
+ process.stdout.write(`${await resolveExactTag(flags.source, flags.ref)}\n`);
51
+ return;
52
+ }
53
+ if (name === "verify-raw-commit") {
54
+ const commit = await verifyRawCommit(flags.source, flags.commit, flags["workspace-parent"]);
55
+ process.stdout.write(`${commit}\n`);
56
+ return;
57
+ }
58
+ if (name === "fetch-exact-commit") {
59
+ await fetchExactCommit(flags.source, flags.commit, flags.repository, flags["workspace-parent"]);
60
+ return;
61
+ }
62
+ if (name === "manifest-version") {
63
+ const version = manifestVersionForRef({
64
+ requestedRef: flags["requested-ref"],
65
+ resolutionKind: parseResolutionKind(flags["resolution-kind"]),
66
+ resolvedRef: flags["resolved-ref"],
67
+ commit: flags.commit,
68
+ });
69
+ process.stdout.write(`${version}\n`);
70
+ return;
71
+ }
72
+ if (name === "pin-kind") {
73
+ const kind = TAG_RE.test(flags.ref)
74
+ ? "tag"
75
+ : COMMIT_INPUT_RE.test(flags.ref)
76
+ ? "raw-commit"
77
+ : "none";
78
+ process.stdout.write(`${kind}\n`);
79
+ return;
80
+ }
81
+ if (name === "safe-source") {
82
+ process.stdout.write(`${gitSafeSource(flags.source)}\n`);
83
+ return;
84
+ }
85
+ const unreachable = name;
86
+ throw new Error(`unhandled subcommand: ${String(unreachable)}`);
87
+ }
88
+ export async function runUpstreamCli(argv) {
89
+ let command;
90
+ try {
91
+ command = parseCommand(argv);
92
+ }
93
+ catch (cause) {
94
+ process.stderr.write(`usage: upstream-cli <subcommand> [options]\n`);
95
+ process.stderr.write(`error: ${oneLine(cause)}\n`);
96
+ return 2;
97
+ }
98
+ try {
99
+ await dispatch(command);
100
+ return 0;
101
+ }
102
+ catch (cause) {
103
+ process.stderr.write(`error: ${oneLine(cause)}\n`);
104
+ return cause instanceof UsageError ? 2 : 1;
105
+ }
106
+ }
107
+ const entry = process.argv[1];
108
+ if (entry !== undefined && import.meta.filename === realpathSync(entry)) {
109
+ process.exitCode = await runUpstreamCli(process.argv.slice(2));
110
+ }
@@ -0,0 +1,40 @@
1
+ import { SEMVER_BASE_RE } from "./domain/refs.js";
2
+ export function shortCommit(commit) {
3
+ return commit.slice(0, 7);
4
+ }
5
+ function trimDashes(value) {
6
+ return value.replace(/^-+/, "").replace(/-+$/, "");
7
+ }
8
+ export function sanitizeRefForVersion(ref) {
9
+ const collapsed = trimDashes(ref.replace(/[^0-9A-Za-z-]+/g, "-"));
10
+ const truncated = trimDashes(collapsed.slice(0, 48));
11
+ return truncated === "" ? "unknown" : truncated;
12
+ }
13
+ export function manifestVersionForRef(input) {
14
+ const short = shortCommit(input.commit);
15
+ switch (input.resolutionKind) {
16
+ case "latest-release":
17
+ case "tag": {
18
+ if (input.resolvedRef.startsWith("v")) {
19
+ const base = input.resolvedRef.slice(1);
20
+ if (base !== "" && SEMVER_BASE_RE.test(base)) {
21
+ return `${base}+manager.${short}`;
22
+ }
23
+ }
24
+ break;
25
+ }
26
+ case "ref": {
27
+ if (input.requestedRef === "main") {
28
+ return `0.0.0-main+manager.${short}`;
29
+ }
30
+ return `0.0.0-ref-${sanitizeRefForVersion(input.requestedRef)}+manager.${short}`;
31
+ }
32
+ case "raw-commit":
33
+ break;
34
+ default: {
35
+ const unreachable = input.resolutionKind;
36
+ throw new Error(`unhandled resolution kind: ${String(unreachable)}`);
37
+ }
38
+ }
39
+ return `0.0.0+manager.${short}`;
40
+ }
@@ -0,0 +1,253 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { COMMIT_INPUT_RE, compareStable, parseStableTag, } from "./domain/refs.js";
4
+ import { runGit } from "./git.js";
5
+ import { SafetyError } from "./safety-error.js";
6
+ import { displaySource } from "./selection.js";
7
+ import { withWorkspace } from "./workspace.js";
8
+ const UNAVAILABLE_OBJECT_RE = /not our ref|unadvertised object|couldn't find remote ref/;
9
+ function upstreamError(message) {
10
+ return new SafetyError("upstream", message);
11
+ }
12
+ // Mirrors
13
+ // `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/upstream.sh:6-13::spw_config_ref`:
14
+ // SUPERPOWERS_REF wins; otherwise the first line of config/upstream-ref with
15
+ // trailing whitespace stripped.
16
+ // The diagnostic names the path and does not interpolate the caught cause.
17
+ export async function readConfigRef(root, env) {
18
+ if (env.SUPERPOWERS_REF)
19
+ return env.SUPERPOWERS_REF;
20
+ const path = join(root, "config", "upstream-ref");
21
+ let text;
22
+ try {
23
+ text = await readFile(path, "utf8");
24
+ }
25
+ catch {
26
+ throw upstreamError(`cannot read packaged upstream ref ${path}`);
27
+ }
28
+ return (text.split("\n")[0] ?? "").replace(/\s+$/, "");
29
+ }
30
+ // Signal termination carries `status: null`, and `null !== 0`, so this one
31
+ // comparison covers both non-zero exits and killed children.
32
+ function failed(result) {
33
+ return result.status !== 0;
34
+ }
35
+ function combined(result) {
36
+ return `${result.stdout}${result.stderr}`.replace(/\n+$/, "");
37
+ }
38
+ // Mirrors `[ -d "$repository/.git" ]`: a regular file, a broken symlink, or
39
+ // an unreadable path is *not* a directory, so the cache is re-initialized.
40
+ async function isDirectory(path) {
41
+ try {
42
+ return (await stat(path)).isDirectory();
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ export function parseLsRemote(output) {
49
+ const entries = [];
50
+ for (const line of output.split("\n")) {
51
+ const fields = line.split(/[ \t]+/).filter((field) => field !== "");
52
+ if (fields.length < 2)
53
+ continue;
54
+ entries.push({ sha: fields[0], ref: fields[1] });
55
+ }
56
+ return entries;
57
+ }
58
+ export function selectLatestRelease(entries) {
59
+ const candidates = new Map();
60
+ for (const entry of entries) {
61
+ if (!entry.ref.startsWith("refs/tags/"))
62
+ continue;
63
+ let tag = entry.ref.slice("refs/tags/".length);
64
+ let peeled = false;
65
+ if (tag.endsWith("^{}")) {
66
+ peeled = true;
67
+ tag = tag.slice(0, -3);
68
+ }
69
+ const version = parseStableTag(tag);
70
+ if (version === null)
71
+ continue;
72
+ const existing = candidates.get(tag);
73
+ if (existing === undefined) {
74
+ candidates.set(tag, { tag, sha: entry.sha, version });
75
+ }
76
+ else if (peeled) {
77
+ existing.sha = entry.sha;
78
+ }
79
+ }
80
+ let best = null;
81
+ for (const candidate of candidates.values()) {
82
+ if (best === null || compareStable(candidate.version, best.version) > 0) {
83
+ best = candidate;
84
+ }
85
+ }
86
+ return best === null ? null : { tag: best.tag, sha: best.sha };
87
+ }
88
+ export function gitSafeSource(source) {
89
+ if (source.startsWith("/") ||
90
+ source.startsWith("~") ||
91
+ source.includes("://") ||
92
+ source.includes(":")) {
93
+ return source;
94
+ }
95
+ return `${process.cwd()}/${source}`;
96
+ }
97
+ export async function resolveRef(upstreamUrl, requestedRef) {
98
+ const querySource = gitSafeSource(upstreamUrl);
99
+ if (requestedRef === "latest-release") {
100
+ const listed = await runGit([
101
+ "ls-remote",
102
+ "--tags",
103
+ "--",
104
+ querySource,
105
+ "refs/tags/v*",
106
+ ]);
107
+ if (failed(listed)) {
108
+ throw upstreamError(`cannot query upstream tags from ${upstreamUrl}: ${combined(listed)}`);
109
+ }
110
+ const selected = selectLatestRelease(parseLsRemote(listed.stdout));
111
+ if (selected === null) {
112
+ throw upstreamError("no stable semver tag found for latest-release");
113
+ }
114
+ return { kind: "latest-release", ref: selected.tag, commit: selected.sha };
115
+ }
116
+ if (COMMIT_INPUT_RE.test(requestedRef)) {
117
+ return { kind: "raw-commit", ref: requestedRef, commit: requestedRef };
118
+ }
119
+ const wanted = `refs/tags/${requestedRef}`;
120
+ const tagged = await runGit([
121
+ "ls-remote",
122
+ "--tags",
123
+ "--",
124
+ querySource,
125
+ wanted,
126
+ `${wanted}^{}`,
127
+ ]);
128
+ if (failed(tagged)) {
129
+ throw upstreamError(`cannot query upstream tag ${requestedRef} from ${upstreamUrl}: ${combined(tagged)}`);
130
+ }
131
+ let tagSha = "";
132
+ for (const entry of parseLsRemote(tagged.stdout)) {
133
+ if (entry.ref === wanted || entry.ref === `${wanted}^{}`) {
134
+ tagSha = entry.sha;
135
+ }
136
+ }
137
+ if (tagSha !== "") {
138
+ return { kind: "tag", ref: requestedRef, commit: tagSha };
139
+ }
140
+ const generic = await runGit(["ls-remote", "--", querySource, requestedRef]);
141
+ if (failed(generic)) {
142
+ throw upstreamError(`cannot query upstream ref ${requestedRef} from ${upstreamUrl}: ${combined(generic)}`);
143
+ }
144
+ const first = parseLsRemote(generic.stdout)[0];
145
+ if (first !== undefined) {
146
+ return { kind: "ref", ref: requestedRef, commit: first.sha };
147
+ }
148
+ throw upstreamError(`cannot resolve upstream ref: ${requestedRef}`);
149
+ }
150
+ export async function resolveExactTag(source, ref) {
151
+ const display = displaySource(source);
152
+ const querySource = gitSafeSource(source);
153
+ const wanted = `refs/tags/${ref}`;
154
+ const listed = await runGit([
155
+ "ls-remote",
156
+ "--tags",
157
+ "--",
158
+ querySource,
159
+ wanted,
160
+ `${wanted}^{}`,
161
+ ]);
162
+ if (failed(listed)) {
163
+ throw upstreamError(`cannot query exact upstream tag ${ref} from ${display}: ${combined(listed)}`);
164
+ }
165
+ let direct = "";
166
+ let peeled = "";
167
+ for (const entry of parseLsRemote(listed.stdout)) {
168
+ if (entry.ref === wanted)
169
+ direct = entry.sha;
170
+ if (entry.ref === `${wanted}^{}`)
171
+ peeled = entry.sha;
172
+ }
173
+ const commit = peeled !== "" ? peeled : direct;
174
+ if (commit === "")
175
+ throw upstreamError(`upstream tag not found: ${ref}`);
176
+ return commit;
177
+ }
178
+ async function inWorkspace(parent, prefix, creationMessage, fn) {
179
+ try {
180
+ return await withWorkspace(parent, prefix, fn);
181
+ }
182
+ catch (cause) {
183
+ if (cause instanceof SafetyError &&
184
+ cause.module === "workspace" &&
185
+ cause.message === "cannot create workspace") {
186
+ throw upstreamError(creationMessage);
187
+ }
188
+ throw cause;
189
+ }
190
+ }
191
+ async function proveCommit(workspace, fetchSource, commit, display, initMessage) {
192
+ const init = await runGit(["init", workspace]);
193
+ if (failed(init)) {
194
+ throw upstreamError(`${initMessage}: ${combined(init)}`);
195
+ }
196
+ const fetched = await runGit([
197
+ "-C",
198
+ workspace,
199
+ "fetch",
200
+ "--no-tags",
201
+ "--",
202
+ fetchSource,
203
+ commit,
204
+ ]);
205
+ if (failed(fetched)) {
206
+ if (UNAVAILABLE_OBJECT_RE.test(combined(fetched))) {
207
+ throw upstreamError(`source cannot supply requested commit: ${commit}`);
208
+ }
209
+ throw upstreamError(`cannot fetch requested commit from ${display}`);
210
+ }
211
+ const typed = await runGit(["-C", workspace, "cat-file", "-t", commit]);
212
+ if (failed(typed) || typed.stdout.trim() !== "commit") {
213
+ throw upstreamError(`requested object is not a commit: ${commit}`);
214
+ }
215
+ }
216
+ export async function verifyRawCommit(source, rawCommit, workspaceParent) {
217
+ const commit = rawCommit.toLowerCase();
218
+ const display = displaySource(source);
219
+ const fetchSource = gitSafeSource(source);
220
+ await inWorkspace(workspaceParent, "superpowers-manager.commit.", `cannot create raw-commit verification workspace under ${workspaceParent}`, async (workspace) => {
221
+ await proveCommit(workspace, fetchSource, commit, display, "cannot initialize raw-commit verification workspace");
222
+ });
223
+ return commit;
224
+ }
225
+ export async function fetchExactCommit(source, commit, repository, workspaceParent) {
226
+ const display = displaySource(source);
227
+ const fetchSource = gitSafeSource(source);
228
+ await inWorkspace(workspaceParent, "superpowers-manager.fetch.", `cannot create exact-commit fetch workspace under ${workspaceParent}`, async (workspace) => {
229
+ await proveCommit(workspace, fetchSource, commit, display, "cannot initialize exact-commit fetch workspace");
230
+ if (!(await isDirectory(`${repository}/.git`))) {
231
+ const init = await runGit(["init", repository]);
232
+ if (failed(init)) {
233
+ throw upstreamError(`cannot initialize upstream cache repository: ${combined(init)}`);
234
+ }
235
+ }
236
+ const transfer = await runGit([
237
+ "-C",
238
+ repository,
239
+ "fetch",
240
+ "--no-tags",
241
+ "--",
242
+ workspace,
243
+ commit,
244
+ ]);
245
+ if (failed(transfer)) {
246
+ throw upstreamError(`cannot transfer requested commit into upstream cache: ${combined(transfer)}`);
247
+ }
248
+ const typed = await runGit(["-C", repository, "cat-file", "-t", commit]);
249
+ if (failed(typed) || typed.stdout.trim() !== "commit") {
250
+ throw upstreamError(`cannot verify requested commit in upstream cache: ${commit}`);
251
+ }
252
+ });
253
+ }
@@ -0,0 +1,118 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { oneLine, parseFlags, UsageError } from "./cli-arguments.js";
3
+ import { validateGeneratedPlugin, } from "./generated-plugin.js";
4
+ const FLAGS = [
5
+ "plugin-root",
6
+ "source",
7
+ "requested-ref",
8
+ "resolved-ref",
9
+ "commit",
10
+ "manifest-version",
11
+ "manifest-source",
12
+ "upstream-manifest-version",
13
+ ];
14
+ // `argparse` accepts a lone `-` and a bare negative number as a split value;
15
+ // every other dash-leading split value is treated as an option and rejected.
16
+ // CPython's `_negative_number_matcher` is `^-\d+$|^-\d*\.\d+$`, and `re` `\d` on
17
+ // a `str` pattern matches every character in Unicode category Nd. JavaScript
18
+ // `\d` is `[0-9]` whatever the flags, so `\p{Nd}` plus `u` is what mirrors it.
19
+ // The `\.` stays a literal ASCII period: `argparse` accepts no other separator.
20
+ const NEGATIVE_NUMBER_RE = /^-\p{Nd}+$|^-\p{Nd}*\.\p{Nd}+$/u;
21
+ /** True when `argparse` would accept `value` in `--flag value` form. */
22
+ export function isAcceptedSplitValue(value) {
23
+ if (!value.startsWith("-"))
24
+ return true;
25
+ return value === "-" || NEGATIVE_NUMBER_RE.test(value);
26
+ }
27
+ /**
28
+ * Node replaces an undecodable argv byte with U+FFFD before any JavaScript
29
+ * runs, so the original byte is unrecoverable. Rejecting at the boundary is
30
+ * the only portable fail-closed option.
31
+ */
32
+ export function hasReplacementCharacter(value) {
33
+ return value.includes("�");
34
+ }
35
+ /** Pre-scan argv for split-form values the dash rule rejects. */
36
+ function findRejectedSplitFlag(argv) {
37
+ for (let index = 0; index < argv.length; index += 1) {
38
+ const token = argv[index];
39
+ if (!token.startsWith("--") || token.includes("="))
40
+ continue;
41
+ const value = argv[index + 1];
42
+ if (value === undefined)
43
+ continue;
44
+ if (!isAcceptedSplitValue(value))
45
+ return token;
46
+ index += 1;
47
+ }
48
+ return undefined;
49
+ }
50
+ function parseOptions(argv) {
51
+ const rejected = findRejectedSplitFlag(argv);
52
+ if (rejected !== undefined) {
53
+ throw new UsageError(`option ${rejected} rejects a dash-leading value`);
54
+ }
55
+ const flags = parseFlags(argv, FLAGS);
56
+ if (hasReplacementCharacter(flags["plugin-root"])) {
57
+ throw new UsageError("option --plugin-root contains an undecodable character");
58
+ }
59
+ const manifestSource = flags["manifest-source"];
60
+ if (manifestSource !== "upstream" && manifestSource !== "fallback") {
61
+ throw new UsageError("option --manifest-source must be `upstream` or `fallback`");
62
+ }
63
+ return {
64
+ pluginRoot: flags["plugin-root"],
65
+ source: flags.source,
66
+ requestedRef: flags["requested-ref"],
67
+ resolvedRef: flags["resolved-ref"],
68
+ commit: flags.commit,
69
+ manifestVersion: flags["manifest-version"],
70
+ manifestSource,
71
+ upstreamManifestVersion: flags["upstream-manifest-version"],
72
+ };
73
+ }
74
+ export async function runValidateGeneratedPluginCli(argv) {
75
+ let options;
76
+ try {
77
+ options = parseOptions(argv);
78
+ }
79
+ catch (cause) {
80
+ process.stderr.write("usage: validate-generated-plugin-cli --plugin-root <path> --source <url> " +
81
+ "--requested-ref <ref> --resolved-ref <ref> --commit <sha> " +
82
+ "--manifest-version <version> --manifest-source <upstream|fallback> " +
83
+ "--upstream-manifest-version <version>\n");
84
+ process.stderr.write(`error: ${oneLine(cause)}\n`);
85
+ return 2;
86
+ }
87
+ let errors;
88
+ try {
89
+ errors = await validateGeneratedPlugin(options);
90
+ }
91
+ catch (cause) {
92
+ // A rejection must never escape as a traceback-shaped crash.
93
+ process.stderr.write(`error: ${oneLine(cause)}\n`);
94
+ return 2;
95
+ }
96
+ if (errors.length > 0) {
97
+ process.stderr.write("Generated plugin validation failed:\n");
98
+ for (const error of errors)
99
+ process.stderr.write(`- ${error}\n`);
100
+ return 1;
101
+ }
102
+ process.stdout.write(`generated plugin validation passed: ${options.pluginRoot}\n`);
103
+ return 0;
104
+ }
105
+ const entry = process.argv[1];
106
+ let entryRealpath;
107
+ if (entry !== undefined) {
108
+ try {
109
+ entryRealpath = realpathSync(entry);
110
+ }
111
+ catch {
112
+ // An unresolvable argv[1] means "not the entry point", not a crash.
113
+ entryRealpath = undefined;
114
+ }
115
+ }
116
+ if (entryRealpath !== undefined && import.meta.filename === entryRealpath) {
117
+ process.exitCode = await runValidateGeneratedPluginCli(process.argv.slice(2));
118
+ }