superpowers-manager 0.2.0 → 0.4.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/README.md +340 -66
- package/dist/adapter-result.js +204 -0
- package/dist/adapter.js +690 -0
- package/dist/atomic.js +142 -0
- package/dist/cli-arguments.js +32 -0
- package/dist/cli.js +313 -0
- package/dist/codex-harness.js +271 -0
- package/dist/codex-json.js +89 -0
- package/dist/codex-prepare.js +199 -0
- package/dist/codex-presentation.js +259 -0
- package/dist/codex-state.js +78 -0
- package/dist/commands/context.js +1 -0
- package/dist/commands/install.js +291 -0
- package/dist/commands/pin.js +79 -0
- package/dist/commands/prepare.js +420 -0
- package/dist/commands/probe.js +257 -0
- package/dist/commands/track-latest.js +44 -0
- package/dist/commands/uninstall.js +217 -0
- package/dist/commands/unpin.js +95 -0
- package/dist/commands/update.js +79 -0
- package/dist/domain/fingerprint.js +4 -0
- package/dist/domain/refs.js +37 -0
- package/dist/effective-selection.js +123 -0
- package/dist/generated-plugin.js +748 -0
- package/dist/git.js +38 -0
- package/dist/harness.js +1 -0
- package/dist/hooks.js +290 -0
- package/dist/lifecycle.js +135 -0
- package/dist/manifest-overlay.js +125 -0
- package/dist/provenance.js +111 -0
- package/dist/python-json-format.js +83 -0
- package/dist/python-json.js +29 -0
- package/dist/python-text.js +74 -0
- package/dist/safe-path.js +137 -0
- package/dist/safety-error.js +10 -0
- package/dist/selection-state-cli.js +85 -0
- package/dist/selection-store.js +172 -0
- package/dist/selection.js +200 -0
- package/dist/status.js +24 -0
- package/dist/strict-json.js +248 -0
- package/dist/upstream-cli.js +110 -0
- package/dist/upstream-version.js +40 -0
- package/dist/upstream.js +253 -0
- package/dist/validate-generated-plugin-cli.js +118 -0
- package/dist/validator.js +317 -0
- package/dist/workspace.js +139 -0
- package/package.json +25 -4
- package/bin/superpowers-manager.js +0 -290
- package/scripts/adapters/codex/adapter +0 -603
- package/scripts/adapters/codex/lib.sh +0 -449
- package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
- package/scripts/core/adapter.sh +0 -103
- package/scripts/core/common.sh +0 -59
- package/scripts/core/lifecycle.sh +0 -141
- package/scripts/core/provenance.sh +0 -135
- package/scripts/core/selection-state.py +0 -414
- package/scripts/core/selection.sh +0 -168
- package/scripts/core/status.sh +0 -27
- package/scripts/core/upstream.sh +0 -280
- package/scripts/core/validate-adapter-response.py +0 -284
- package/scripts/install +0 -59
- package/scripts/pin +0 -51
- package/scripts/prepare +0 -117
- package/scripts/probe +0 -82
- package/scripts/track-latest +0 -30
- package/scripts/uninstall +0 -35
- package/scripts/unpin +0 -41
- package/scripts/update +0 -34
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Ports scripts/update. The shell sourced common.sh and lifecycle.sh; the
|
|
2
|
+
// predicates now live in src/lifecycle.ts. Unlike install and uninstall,
|
|
3
|
+
// update issues no ctx.adapter call of its own: every adapter interaction
|
|
4
|
+
// this module needs already happens inside gatherProbe, runPrepare or
|
|
5
|
+
// runInstall, each of which owns its own §4.2a-conformant invoke() gate. That
|
|
6
|
+
// is also why update opens no temporary workspace of its own -- there is no
|
|
7
|
+
// mutation here for one to protect.
|
|
8
|
+
import { oneLine } from "../cli-arguments.js";
|
|
9
|
+
import { gatherProbe, replayOutcome } from "./probe.js";
|
|
10
|
+
import { runInstall } from "./install.js";
|
|
11
|
+
import { runPrepare } from "./prepare.js";
|
|
12
|
+
function writeOutput(output, ctx) {
|
|
13
|
+
for (const line of output.stdout)
|
|
14
|
+
ctx.stdout.write(`${line}\n`);
|
|
15
|
+
for (const line of output.stderr)
|
|
16
|
+
ctx.stderr.write(`${line}\n`);
|
|
17
|
+
}
|
|
18
|
+
export async function runUpdate(argv, ctx) {
|
|
19
|
+
// scripts/update never reads "$@", so extra arguments are silently
|
|
20
|
+
// ignored -- the same asymmetry runPrepare and runInstall document.
|
|
21
|
+
void argv;
|
|
22
|
+
let probe;
|
|
23
|
+
try {
|
|
24
|
+
probe = await gatherProbe(ctx);
|
|
25
|
+
}
|
|
26
|
+
catch (cause) {
|
|
27
|
+
// gatherProbe performs no writes of its own (src/commands/probe.ts), so
|
|
28
|
+
// this catch cannot also be reached by an EPIPE from update's own
|
|
29
|
+
// output -- update writes nothing before this call resolves, unlike
|
|
30
|
+
// install's NOTE line.
|
|
31
|
+
//
|
|
32
|
+
// This is a THIRD consumer of gatherProbe's throw channel --
|
|
33
|
+
// `src/commands/probe.ts:251-309::THREE exceptions, all inherited and none a regression:`'s
|
|
34
|
+
// runProbe catch is the first and src/commands/install.ts's runInstall catch
|
|
35
|
+
// is the second. Because all three wrap the identical function, runProbe's
|
|
36
|
+
// long comment there enumerates exactly what can reach this stream too; not
|
|
37
|
+
// repeated here.
|
|
38
|
+
ctx.stderr.write(`error: ${oneLine(cause)}\n`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
// Replay first, on both paths, before any decision -- clause 1.
|
|
42
|
+
for (const outcome of probe.outcomes)
|
|
43
|
+
replayOutcome(outcome, ctx);
|
|
44
|
+
if (probe.status === 1) {
|
|
45
|
+
if (probe.message !== null) {
|
|
46
|
+
ctx.stderr.write(`error: ${probe.message}\n`);
|
|
47
|
+
}
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
const facts = probe.facts;
|
|
51
|
+
if (facts.ownership.installEligibility.kind === "blocked") {
|
|
52
|
+
writeOutput(facts.ownership.installEligibility.output, ctx);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
if (facts.control.probeEligibility.kind === "blocked") {
|
|
56
|
+
writeOutput(facts.control.probeEligibility.output, ctx);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
switch (facts.status) {
|
|
60
|
+
case "current": {
|
|
61
|
+
if (facts.control.mutationEligibility.kind === "blocked") {
|
|
62
|
+
writeOutput(facts.control.mutationEligibility.output, ctx);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
const rendered = ctx.adapter.presentation.renderProbe(facts);
|
|
66
|
+
ctx.stdout.write(rendered.porcelain);
|
|
67
|
+
ctx.stdout.write(`${ctx.adapter.presentation.currentNotice}\n`);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
case "needs prepare": {
|
|
71
|
+
const prepareStatus = await runPrepare([], ctx);
|
|
72
|
+
if (prepareStatus !== 0)
|
|
73
|
+
return prepareStatus;
|
|
74
|
+
return await runInstall([], ctx);
|
|
75
|
+
}
|
|
76
|
+
case "needs install":
|
|
77
|
+
return await runInstall([], ctx);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const SEMVER_BASE_SOURCE = "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)" +
|
|
2
|
+
"(?:-(?:(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)" +
|
|
3
|
+
"(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?";
|
|
4
|
+
export const SEMVER_BASE_RE = new RegExp(`^${SEMVER_BASE_SOURCE}$`);
|
|
5
|
+
export const TAG_RE = new RegExp(`^v${SEMVER_BASE_SOURCE}$`);
|
|
6
|
+
export const COMMIT_RE = /^[0-9a-f]{40}$/;
|
|
7
|
+
export const COMMIT_INPUT_RE = /^[0-9A-Fa-f]{40}$/;
|
|
8
|
+
export function isTagRef(value) {
|
|
9
|
+
return TAG_RE.test(value);
|
|
10
|
+
}
|
|
11
|
+
export function isCommit(value) {
|
|
12
|
+
return COMMIT_RE.test(value);
|
|
13
|
+
}
|
|
14
|
+
export function normalizeCommitInput(value) {
|
|
15
|
+
return COMMIT_INPUT_RE.test(value) ? value.toLowerCase() : null;
|
|
16
|
+
}
|
|
17
|
+
export function parseStableTag(tag) {
|
|
18
|
+
if (!TAG_RE.test(tag))
|
|
19
|
+
return null;
|
|
20
|
+
const core = tag.slice(1);
|
|
21
|
+
// Prerelease filter. Sound only because TAG_RE already matched: the numeric
|
|
22
|
+
// components cannot contain "-", so the sole way one reaches `core` is a
|
|
23
|
+
// prerelease suffix. Stable-tag selection must exclude those.
|
|
24
|
+
if (core.includes("-"))
|
|
25
|
+
return null;
|
|
26
|
+
const [major, minor, patch] = core.split(".");
|
|
27
|
+
return { major: BigInt(major), minor: BigInt(minor), patch: BigInt(patch) };
|
|
28
|
+
}
|
|
29
|
+
export function compareStable(a, b) {
|
|
30
|
+
if (a.major !== b.major)
|
|
31
|
+
return a.major < b.major ? -1 : 1;
|
|
32
|
+
if (a.minor !== b.minor)
|
|
33
|
+
return a.minor < b.minor ? -1 : 1;
|
|
34
|
+
if (a.patch !== b.patch)
|
|
35
|
+
return a.patch < b.patch ? -1 : 1;
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// `selectionError` already exists at
|
|
2
|
+
// `src/selection.ts:41::export function selectionError` and tags errors with
|
|
3
|
+
// module "selection". Import it rather than defining a second one.
|
|
4
|
+
import { normalizeSaved, selectionError, validateSource, } from "./selection.js";
|
|
5
|
+
import { readSelectionState } from "./selection-store.js";
|
|
6
|
+
import { readConfigRef, resolveRef } from "./upstream.js";
|
|
7
|
+
import { COMMIT_RE } from "./domain/refs.js";
|
|
8
|
+
function requireAbsolute(value, variable) {
|
|
9
|
+
if (!value.startsWith("/")) {
|
|
10
|
+
throw selectionError(`${variable} must be absolute`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
// Precedence mirrors
|
|
15
|
+
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/selection.sh:4-29::spw_selection_config_dir(`.
|
|
16
|
+
// SUPERPOWERS_CONFIG_DIR is selected on *presence*, matching the shell's
|
|
17
|
+
// ${SUPERPOWERS_CONFIG_DIR+x}: an empty value takes this branch and then fails
|
|
18
|
+
// the absolute check, rather than falling through to XDG.
|
|
19
|
+
export function selectionConfigDir(env) {
|
|
20
|
+
const explicit = env.SUPERPOWERS_CONFIG_DIR;
|
|
21
|
+
if (explicit !== undefined) {
|
|
22
|
+
return requireAbsolute(explicit, "SUPERPOWERS_CONFIG_DIR");
|
|
23
|
+
}
|
|
24
|
+
const xdg = env.XDG_CONFIG_HOME;
|
|
25
|
+
if (xdg !== undefined && xdg.length > 0) {
|
|
26
|
+
return `${requireAbsolute(xdg, "XDG_CONFIG_HOME")}/superpowers-manager`;
|
|
27
|
+
}
|
|
28
|
+
const home = env.HOME;
|
|
29
|
+
if (home === undefined || home.length === 0) {
|
|
30
|
+
throw selectionError("HOME is required to locate selection state");
|
|
31
|
+
}
|
|
32
|
+
return `${requireAbsolute(home, "HOME")}/.config/superpowers-manager`;
|
|
33
|
+
}
|
|
34
|
+
export function selectionStatePath(env) {
|
|
35
|
+
return `${selectionConfigDir(env)}/selection.json`;
|
|
36
|
+
}
|
|
37
|
+
// Composes exactly what `src/selection-state-cli.ts:39::const normalized`
|
|
38
|
+
// composes. The shell wrote a normalized document to a mktemp file and read
|
|
39
|
+
// five fields back with five python3 invocations; in-process this is one call.
|
|
40
|
+
export async function loadSavedSelection(env) {
|
|
41
|
+
return normalizeSaved(await readSelectionState(selectionStatePath(env)));
|
|
42
|
+
}
|
|
43
|
+
export const UPSTREAM_URL_DEFAULT = "https://github.com/obra/superpowers";
|
|
44
|
+
// Ports the env > saved > package-default precedence ladder from
|
|
45
|
+
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/selection.sh:88-162::spw_compute_effective_selection`.
|
|
46
|
+
// validateSource runs before any ref resolution -- pinned by
|
|
47
|
+
// tests/unit/effective-selection.test.js's "source validation precedes ref
|
|
48
|
+
// resolution": a credential-bearing source must fail before Git is ever
|
|
49
|
+
// invoked.
|
|
50
|
+
export async function computeEffectiveSelection(root, env) {
|
|
51
|
+
const saved = await loadSavedSelection(env);
|
|
52
|
+
let upstreamSourceOrigin;
|
|
53
|
+
let effectiveSource;
|
|
54
|
+
if (env.SUPERPOWERS_UPSTREAM_URL) {
|
|
55
|
+
upstreamSourceOrigin = "environment";
|
|
56
|
+
effectiveSource = env.SUPERPOWERS_UPSTREAM_URL;
|
|
57
|
+
}
|
|
58
|
+
else if (saved.saved_mode !== "none") {
|
|
59
|
+
upstreamSourceOrigin = "user-config";
|
|
60
|
+
effectiveSource = saved.saved_source;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
upstreamSourceOrigin = "package-default";
|
|
64
|
+
effectiveSource = UPSTREAM_URL_DEFAULT;
|
|
65
|
+
}
|
|
66
|
+
// Before any Git access. Pinned by tests/unit/effective-selection.test.js's
|
|
67
|
+
// "source validation precedes ref resolution".
|
|
68
|
+
validateSource(effectiveSource);
|
|
69
|
+
let selectionOrigin;
|
|
70
|
+
let selectionMode;
|
|
71
|
+
let requestedRef;
|
|
72
|
+
let usesSavedPin = false;
|
|
73
|
+
if (env.SUPERPOWERS_REF) {
|
|
74
|
+
selectionOrigin = "environment";
|
|
75
|
+
selectionMode = "override";
|
|
76
|
+
requestedRef = env.SUPERPOWERS_REF;
|
|
77
|
+
}
|
|
78
|
+
else if (saved.saved_mode === "pinned") {
|
|
79
|
+
selectionOrigin = "user-config";
|
|
80
|
+
selectionMode = "pinned";
|
|
81
|
+
requestedRef = saved.saved_requested_ref;
|
|
82
|
+
usesSavedPin = true;
|
|
83
|
+
}
|
|
84
|
+
else if (saved.saved_mode === "track-latest") {
|
|
85
|
+
selectionOrigin = "user-config";
|
|
86
|
+
selectionMode = "track-latest";
|
|
87
|
+
requestedRef = "latest-release";
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
selectionOrigin = "package-default";
|
|
91
|
+
selectionMode = "default";
|
|
92
|
+
requestedRef = await readConfigRef(root, env);
|
|
93
|
+
}
|
|
94
|
+
if (usesSavedPin) {
|
|
95
|
+
return {
|
|
96
|
+
selectionOrigin,
|
|
97
|
+
selectionMode,
|
|
98
|
+
upstreamSourceOrigin,
|
|
99
|
+
effectiveSource,
|
|
100
|
+
requestedRef,
|
|
101
|
+
resolvedRef: saved.saved_resolved_ref,
|
|
102
|
+
desiredCommit: saved.saved_commit,
|
|
103
|
+
resolutionKind: COMMIT_RE.test(saved.saved_requested_ref)
|
|
104
|
+
? "raw-commit"
|
|
105
|
+
: "tag",
|
|
106
|
+
saved,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const resolution = await resolveRef(effectiveSource, requestedRef);
|
|
110
|
+
// `Resolution` (src/upstream.ts) is { kind, ref, commit } — the
|
|
111
|
+
// resolved ref field is named `ref`, not `resolvedRef`.
|
|
112
|
+
return {
|
|
113
|
+
selectionOrigin,
|
|
114
|
+
selectionMode,
|
|
115
|
+
upstreamSourceOrigin,
|
|
116
|
+
effectiveSource,
|
|
117
|
+
requestedRef,
|
|
118
|
+
resolvedRef: resolution.ref,
|
|
119
|
+
desiredCommit: resolution.commit,
|
|
120
|
+
resolutionKind: resolution.kind,
|
|
121
|
+
saved,
|
|
122
|
+
};
|
|
123
|
+
}
|