superpowers-manager 0.5.2 → 0.7.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 +44 -26
- package/dist/adapter-result.js +5 -9
- package/dist/atomic.js +4 -14
- package/dist/cli-arguments.js +1 -27
- package/dist/cli.js +32 -52
- package/dist/commands/adapter-call.js +28 -0
- package/dist/commands/install.js +26 -107
- package/dist/commands/mutation.js +7 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/prepare.js +39 -129
- package/dist/commands/probe.js +35 -264
- package/dist/commands/track-latest.js +1 -1
- package/dist/commands/uninstall.js +16 -92
- package/dist/commands/update.js +5 -25
- package/dist/effective-selection.js +7 -9
- package/dist/git-tree.js +85 -0
- package/dist/harness-command-result.js +47 -0
- package/dist/harnesses/codex/adapter.js +231 -510
- package/dist/harnesses/codex/compatibility.js +2 -17
- package/dist/harnesses/codex/conflicts.js +2 -3
- package/dist/harnesses/codex/harness.js +18 -263
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +1 -11
- package/dist/harnesses/codex/lifecycle.js +80 -72
- package/dist/harnesses/codex/manifest-overlay.js +23 -18
- package/dist/harnesses/codex/paths.js +14 -29
- package/dist/harnesses/codex/presentation.js +8 -37
- package/dist/harnesses/codex/publication.js +13 -37
- package/dist/harnesses/codex/recovery.js +5 -5
- package/dist/harnesses/codex/stored-state.js +1 -1
- package/dist/harnesses/opencode/config.js +256 -0
- package/dist/harnesses/opencode/discovery.js +587 -0
- package/dist/harnesses/opencode/harness.js +37 -0
- package/dist/harnesses/opencode/install.js +601 -0
- package/dist/harnesses/opencode/native.js +57 -0
- package/dist/harnesses/opencode/package.js +89 -0
- package/dist/harnesses/opencode/paths.js +38 -0
- package/dist/harnesses/opencode/prepare.js +30 -0
- package/dist/harnesses/opencode/presentation.js +28 -0
- package/dist/harnesses/opencode/state.js +174 -0
- package/dist/harnesses/pi/compatibility.js +5 -37
- package/dist/harnesses/pi/install.js +7 -5
- package/dist/harnesses/pi/native.js +3 -47
- package/dist/harnesses/pi/package.js +5 -107
- package/dist/harnesses/pi/prepare.js +13 -104
- package/dist/harnesses/pi/presentation.js +23 -66
- package/dist/harnesses/pi/shared-skills.js +28 -39
- package/dist/harnesses/pi/state.js +7 -27
- package/dist/provenance.js +2 -2
- package/dist/python-json-format.js +0 -69
- package/dist/python-json.js +2 -27
- package/dist/python-text.js +9 -69
- package/dist/safe-path.js +2 -2
- package/dist/selection-store.js +2 -22
- package/dist/snapshot-package.js +78 -0
- package/dist/snapshot-prepare.js +99 -0
- package/dist/snapshot-presentation.js +44 -0
- package/dist/validator.js +39 -73
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +8 -4
- package/dist/domain/fingerprint.js +0 -4
- package/dist/selection-state-cli.js +0 -85
- package/dist/status.js +0 -24
- package/dist/upstream-cli.js +0 -110
- package/dist/validate-generated-plugin-cli.js +0 -118
|
@@ -1,72 +1,21 @@
|
|
|
1
|
-
// Ports scripts/uninstall. The shell sourced common.sh, provenance.sh,
|
|
2
|
-
// lifecycle.sh and adapter.sh; the predicates now live in src/harnesses/codex/lifecycle.ts
|
|
3
|
-
// and the adapter arrives through ctx.adapter.
|
|
4
1
|
import { tmpdir } from "node:os";
|
|
5
2
|
import { oneLine } from "../cli-arguments.js";
|
|
6
3
|
import { withWorkspace, workspaceRemovalFailure } from "../workspace.js";
|
|
4
|
+
import { GatherFailure, callAdapter, } from "./adapter-call.js";
|
|
7
5
|
import { runWithMutation } from "./mutation.js";
|
|
8
6
|
import { replayOutcome } from "./probe.js";
|
|
9
7
|
async function invoke(call, failure, outcomes) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
result
|
|
13
|
-
|
|
14
|
-
catch {
|
|
15
|
-
return { ok: false, message: failure.unexpected };
|
|
16
|
-
}
|
|
17
|
-
outcomes.push(result.outcome);
|
|
18
|
-
const outcome = result.outcome;
|
|
19
|
-
if (result.status !== 0 || !outcome.ok) {
|
|
20
|
-
return {
|
|
21
|
-
ok: false,
|
|
22
|
-
message: outcome.ok ? failure.invalidStatus : null,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
ok: true,
|
|
27
|
-
result: { status: result.status, outcome },
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
// withWorkspace throws for mkdtemp failure before the callback ever runs
|
|
31
|
-
// ("cannot create workspace",
|
|
32
|
-
// `src/workspace.ts:120::throw new SafetyError("workspace", "cannot create workspace"`).
|
|
33
|
-
// A bare re-throw would silently drop every outcome collected before that
|
|
34
|
-
// point -- a narrow
|
|
35
|
-
// DIAG-ADAPTER-01 regression the shell never had, since it replayed each
|
|
36
|
-
// adapter response as it went rather than batching replay to the end. This
|
|
37
|
-
// carries the outcomes collected so far alongside the original cause, so
|
|
38
|
-
// runUninstall's catch can still replay them before reporting the cause.
|
|
39
|
-
//
|
|
40
|
-
// The post-success cleanup failure no longer reaches here: onCleanupFailure
|
|
41
|
-
// below suppresses withWorkspace's throw for that case and records the
|
|
42
|
-
// warning as data, so the computed UninstallOutcome survives it. The
|
|
43
|
-
// outcome-carrying is still load-bearing for mkdtemp, and the shape stays
|
|
44
|
-
// identical to src/commands/install.ts's GatherFailure.
|
|
45
|
-
class GatherFailure extends Error {
|
|
46
|
-
inner;
|
|
47
|
-
outcomes;
|
|
48
|
-
constructor(inner, outcomes) {
|
|
49
|
-
super("uninstall gather failed");
|
|
50
|
-
this.inner = inner;
|
|
51
|
-
this.outcomes = outcomes;
|
|
52
|
-
}
|
|
8
|
+
const result = await callAdapter(call, failure);
|
|
9
|
+
if (result.result !== null)
|
|
10
|
+
outcomes.push(result.result.outcome);
|
|
11
|
+
return result;
|
|
53
12
|
}
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// (`src/commands/probe.ts::readonly facts: ProbeSnapshot<R>;`) and for the same
|
|
57
|
-
// reason: a write inside this try could raise EPIPE, be caught here, and be
|
|
58
|
-
// relabelled as a domain failure.
|
|
13
|
+
// Collect outcomes without writing so output failures cannot be classified as
|
|
14
|
+
// inspection failures. Replay collected outcomes after gathering completes.
|
|
59
15
|
async function gatherUninstall(ctx) {
|
|
60
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/uninstall:20-21::TMPDIR=` exported TMPDIR="$uninstall_workspace" so every
|
|
61
|
-
// child confined its temporary files to the tree the workspace trap
|
|
62
|
-
// removed. The AdapterContext passed to ctx.adapter below does the same.
|
|
63
16
|
const parent = ctx.env.TMPDIR ?? tmpdir();
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// withWorkspace call, not just the callback -- can still see whatever was
|
|
67
|
-
// collected before a workspace throw. Both `mkdtemp` failure (nothing
|
|
68
|
-
// collected yet) and a post-success cleanup failure (everything the
|
|
69
|
-
// callback collected) reach this same array.
|
|
17
|
+
// Kept outside the workspace callback so a workspace failure preserves every
|
|
18
|
+
// outcome collected before it.
|
|
70
19
|
const outcomes = [];
|
|
71
20
|
let cleanupWarning = null;
|
|
72
21
|
try {
|
|
@@ -90,9 +39,8 @@ async function gatherUninstall(ctx) {
|
|
|
90
39
|
const removed = await invoke(() => ctx.adapter.remove(removalInput, adapterContext), removeFailure, outcomes);
|
|
91
40
|
if (!removed.ok)
|
|
92
41
|
return failed(removed.message);
|
|
93
|
-
// Stage 3: inspect ownership
|
|
94
|
-
//
|
|
95
|
-
// POST-uninstall state, not the pre-uninstall one read above.
|
|
42
|
+
// Stage 3: inspect ownership again. Everything below reads this
|
|
43
|
+
// post-removal state, not the inspection before removal.
|
|
96
44
|
const afterFailure = ctx.adapter.presentation.callFailure("post-remove", adapterContext);
|
|
97
45
|
const after = await invoke(() => ctx.adapter.inspectOwnership(adapterContext), afterFailure, outcomes);
|
|
98
46
|
if (!after.ok)
|
|
@@ -125,13 +73,13 @@ async function gatherUninstall(ctx) {
|
|
|
125
73
|
catch (cause) {
|
|
126
74
|
// Reachable only for mkdtemp failure, with nothing collected yet: this
|
|
127
75
|
// callback never throws -- every ctx.adapter throw is already caught
|
|
128
|
-
// inside invoke(), and presenceFlag/
|
|
76
|
+
// inside invoke(), and presenceFlag/
|
|
129
77
|
// reportLegacyState are pure (src/harnesses/codex/lifecycle.ts's header comment) -- so a
|
|
130
78
|
// post-success cleanup failure is handled by onCleanupFailure above and
|
|
131
79
|
// cannot reach here. Wrapping with `outcomes` anyway keeps the class
|
|
132
80
|
// total over its declared contract rather than assuming the callback's
|
|
133
81
|
// purity at the throw site.
|
|
134
|
-
throw new GatherFailure(cause, outcomes);
|
|
82
|
+
throw new GatherFailure("uninstall gather failed", cause, outcomes);
|
|
135
83
|
}
|
|
136
84
|
}
|
|
137
85
|
export async function runUninstall(argv, ctx) {
|
|
@@ -147,30 +95,8 @@ async function performUninstall(argv, ctx) {
|
|
|
147
95
|
run = await gatherUninstall(ctx);
|
|
148
96
|
}
|
|
149
97
|
catch (cause) {
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
// (`src/workspace.ts:120::throw new SafetyError("workspace", "cannot create workspace"`),
|
|
153
|
-
// alongside whatever outcomes were collected before that throw -- none,
|
|
154
|
-
// for that cause. Replaying first, before
|
|
155
|
-
// reporting the cause, keeps the arm honest for any outcome-bearing
|
|
156
|
-
// throw the class is declared to carry (DIAG-ADAPTER-01). The
|
|
157
|
-
// post-success cleanup failure no longer arrives here: gatherUninstall's
|
|
158
|
-
// onCleanupFailure records it as `cleanupWarning` and the computed
|
|
159
|
-
// outcome survives, which is what `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/uninstall:34-35::complete` did. The
|
|
160
|
-
// `instanceof` guard is defensive rather than load-bearing --
|
|
161
|
-
// gatherUninstall's own catch is the only thing that can throw here, and
|
|
162
|
-
// it always wraps -- but this catch does not assume that invariant
|
|
163
|
-
// blindly.
|
|
164
|
-
//
|
|
165
|
-
// A cause outside ctx.adapter's AdapterFailure guard
|
|
166
|
-
// (`src/harnesses/codex/adapter.ts:1081::async function runCodexOperation<T = JsonValue>(`) does NOT
|
|
167
|
-
// reach here: invoke() catches it inside gatherUninstall and converts it
|
|
168
|
-
// to a hand-written message carried as UninstallOutcome data, exactly as
|
|
169
|
-
// src/commands/probe.ts's inspect() does for the same cause.
|
|
170
|
-
//
|
|
171
|
-
// gatherUninstall performs no writes of its own, so this catch cannot
|
|
172
|
-
// also be reached by an EPIPE from uninstall's own output -- every write
|
|
173
|
-
// below runs only after this try/catch has resolved.
|
|
98
|
+
// Replay collected outcomes before reporting a gather failure. The
|
|
99
|
+
// cleanup reporter retains post-success cleanup failures as data.
|
|
174
100
|
const outcomes = cause instanceof GatherFailure ? cause.outcomes : [];
|
|
175
101
|
for (const outcome of outcomes)
|
|
176
102
|
replayOutcome(outcome, ctx);
|
|
@@ -179,9 +105,7 @@ async function performUninstall(argv, ctx) {
|
|
|
179
105
|
return 1;
|
|
180
106
|
}
|
|
181
107
|
const { outcome, cleanupWarning } = run;
|
|
182
|
-
// Replay
|
|
183
|
-
// response's messages whether or not that response was a failure
|
|
184
|
-
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/validate-adapter-response.py:268::replay(messages)`).
|
|
108
|
+
// Replay before reporting a command-level result on either path.
|
|
185
109
|
for (const each of outcome.outcomes)
|
|
186
110
|
replayOutcome(each, ctx);
|
|
187
111
|
let status;
|
package/dist/commands/update.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
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.
|
|
1
|
+
// Update delegates adapter calls to probe, prepare, and install. Those
|
|
2
|
+
// mutating commands own their workspaces, so update opens none itself.
|
|
8
3
|
import { oneLine } from "../cli-arguments.js";
|
|
4
|
+
import { runInstall, writeOutput } from "./install.js";
|
|
9
5
|
import { gatherProbe, replayOutcome } from "./probe.js";
|
|
10
|
-
import { runInstall } from "./install.js";
|
|
11
6
|
import { runPrepare } from "./prepare.js";
|
|
12
7
|
import { runWithMutation } from "./mutation.js";
|
|
13
|
-
function writeOutput(output, ctx) {
|
|
14
|
-
for (const line of output.stdout)
|
|
15
|
-
ctx.stdout.write(`${line}\n`);
|
|
16
|
-
for (const line of output.stderr)
|
|
17
|
-
ctx.stderr.write(`${line}\n`);
|
|
18
|
-
}
|
|
19
8
|
export async function runUpdate(argv, ctx) {
|
|
20
9
|
return await runWithMutation("update", ctx, async (scoped) => performUpdate(argv, scoped));
|
|
21
10
|
}
|
|
@@ -28,17 +17,8 @@ async function performUpdate(argv, ctx) {
|
|
|
28
17
|
probe = await gatherProbe(ctx);
|
|
29
18
|
}
|
|
30
19
|
catch (cause) {
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// output -- update writes nothing before this call resolves, unlike
|
|
34
|
-
// install's NOTE line.
|
|
35
|
-
//
|
|
36
|
-
// This is a THIRD consumer of gatherProbe's throw channel --
|
|
37
|
-
// `src/commands/probe.ts:469-527::THREE exceptions, all inherited and none a regression:`'s
|
|
38
|
-
// runProbe catch is the first and src/commands/install.ts's runInstall catch
|
|
39
|
-
// is the second. Because all three wrap the identical function, runProbe's
|
|
40
|
-
// long comment there enumerates exactly what can reach this stream too; not
|
|
41
|
-
// repeated here.
|
|
20
|
+
// Gathering does not write, so this catch cannot misclassify an output
|
|
21
|
+
// failure. oneLine() bounds any inherited diagnostic to one line.
|
|
42
22
|
ctx.stderr.write(`error: ${oneLine(cause)}\n`);
|
|
43
23
|
return 1;
|
|
44
24
|
}
|
|
@@ -34,19 +34,18 @@ export function selectionConfigDir(env) {
|
|
|
34
34
|
export function selectionStatePath(env) {
|
|
35
35
|
return `${selectionConfigDir(env)}/selection.json`;
|
|
36
36
|
}
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// five
|
|
37
|
+
// Normalizes the record returned by readSelectionState directly. The shell
|
|
38
|
+
// wrote a normalized document to a mktemp file and read five fields back with
|
|
39
|
+
// five python3 invocations; in-process this is one call.
|
|
40
40
|
export async function loadSavedSelection(env) {
|
|
41
41
|
return normalizeSaved(await readSelectionState(selectionStatePath(env)));
|
|
42
42
|
}
|
|
43
43
|
export const UPSTREAM_URL_DEFAULT = "https://github.com/obra/superpowers";
|
|
44
44
|
// Ports the env > saved > package-default precedence ladder from
|
|
45
45
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/selection.sh:88-162::spw_compute_effective_selection`.
|
|
46
|
-
// validateSource runs before any ref resolution
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
// invoked.
|
|
46
|
+
// validateSource runs before any ref resolution. A credential-bearing source
|
|
47
|
+
// must fail before Git is invoked; the resolver-log check lives in
|
|
48
|
+
// `tests/baseline/selection-location.test.ts::SEL-PRECEDENCE-VALIDATE-01 invalid saved state stops resolution`.
|
|
50
49
|
export async function computeEffectiveSelection(root, env) {
|
|
51
50
|
const saved = await loadSavedSelection(env);
|
|
52
51
|
let upstreamSourceOrigin;
|
|
@@ -63,8 +62,7 @@ export async function computeEffectiveSelection(root, env) {
|
|
|
63
62
|
upstreamSourceOrigin = "package-default";
|
|
64
63
|
effectiveSource = UPSTREAM_URL_DEFAULT;
|
|
65
64
|
}
|
|
66
|
-
// Before any Git access
|
|
67
|
-
// "source validation precedes ref resolution".
|
|
65
|
+
// Before any Git access; the selection-location test above checks the log.
|
|
68
66
|
validateSource(effectiveSource);
|
|
69
67
|
let selectionOrigin;
|
|
70
68
|
let selectionMode;
|
package/dist/git-tree.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { ARTIFACT_RECEIPT } from "./artifact-tree.js";
|
|
6
|
+
import { COMMIT_RE } from "./domain/refs.js";
|
|
7
|
+
import { assertNoFollowType, assertSymlinkTargetContained, } from "./safe-path.js";
|
|
8
|
+
import { SafetyError } from "./safety-error.js";
|
|
9
|
+
const exec = promisify(execFile);
|
|
10
|
+
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
11
|
+
async function gitBytes(repository, args) {
|
|
12
|
+
try {
|
|
13
|
+
return (await exec("git", ["--no-replace-objects", "-C", repository, ...args], {
|
|
14
|
+
encoding: null,
|
|
15
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
16
|
+
})).stdout;
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
throw new SafetyError("git-tree", `cannot read Git tree objects from repository: ${repository}`, { cause });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function materializeGitTree(repository, commit, destination) {
|
|
23
|
+
try {
|
|
24
|
+
if (!COMMIT_RE.test(commit))
|
|
25
|
+
throw new Error("commit");
|
|
26
|
+
if (!(await gitBytes(repository, ["cat-file", "-t", commit])).equals(Buffer.from("commit\n")))
|
|
27
|
+
throw new Error("not a commit");
|
|
28
|
+
await assertNoFollowType(destination, ["missing"]);
|
|
29
|
+
const bytes = await gitBytes(repository, [
|
|
30
|
+
"ls-tree",
|
|
31
|
+
"-rz",
|
|
32
|
+
"--full-tree",
|
|
33
|
+
commit,
|
|
34
|
+
]);
|
|
35
|
+
const entries = [];
|
|
36
|
+
let offset = 0;
|
|
37
|
+
while (offset < bytes.length) {
|
|
38
|
+
const end = bytes.indexOf(0, offset);
|
|
39
|
+
if (end < 0)
|
|
40
|
+
throw new Error("tree framing");
|
|
41
|
+
const record = DECODER.decode(bytes.subarray(offset, end));
|
|
42
|
+
offset = end + 1;
|
|
43
|
+
const match = /^(100644|100755|120000) blob ([a-f0-9]{40})\t([\s\S]+)$/.exec(record);
|
|
44
|
+
if (!match)
|
|
45
|
+
throw new Error("tree entry");
|
|
46
|
+
const path = match[3];
|
|
47
|
+
if (path
|
|
48
|
+
.split("/")
|
|
49
|
+
.some((part) => !part ||
|
|
50
|
+
part === "." ||
|
|
51
|
+
part === ".." ||
|
|
52
|
+
part.toLowerCase() === ".git") ||
|
|
53
|
+
isAbsolute(path) ||
|
|
54
|
+
path.includes("\\") ||
|
|
55
|
+
path.split("/")[0] === ARTIFACT_RECEIPT)
|
|
56
|
+
throw new Error("tree path");
|
|
57
|
+
entries.push({ mode: match[1], oid: match[2], path });
|
|
58
|
+
}
|
|
59
|
+
await mkdir(destination, { recursive: true });
|
|
60
|
+
for (const entry of entries)
|
|
61
|
+
await mkdir(dirname(join(destination, entry.path)), { recursive: true });
|
|
62
|
+
for (const entry of entries.filter((entry) => entry.mode !== "120000")) {
|
|
63
|
+
const path = join(destination, entry.path), mode = entry.mode === "100755" ? 0o755 : 0o644;
|
|
64
|
+
await writeFile(path, await gitBytes(repository, ["cat-file", "blob", entry.oid]), { flag: "wx", mode });
|
|
65
|
+
await chmod(path, mode);
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries.filter((entry) => entry.mode === "120000")) {
|
|
68
|
+
const target = DECODER.decode(await gitBytes(repository, ["cat-file", "blob", entry.oid]));
|
|
69
|
+
const path = join(destination, entry.path), suffix = relative(resolve(destination), resolve(dirname(path), target));
|
|
70
|
+
if (!target ||
|
|
71
|
+
target.includes("\0") ||
|
|
72
|
+
isAbsolute(target) ||
|
|
73
|
+
suffix === ".." ||
|
|
74
|
+
suffix.startsWith("../") ||
|
|
75
|
+
isAbsolute(suffix))
|
|
76
|
+
throw new Error("link escape");
|
|
77
|
+
await symlink(target, path);
|
|
78
|
+
}
|
|
79
|
+
for (const entry of entries.filter((entry) => entry.mode === "120000"))
|
|
80
|
+
await assertSymlinkTargetContained(destination, join(destination, entry.path));
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
throw new SafetyError("git-tree", `cannot materialize Git tree from ${repository} into ${destination}`, { cause });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AdapterMessageLog, failureResult, successResult, } from "./adapter-result.js";
|
|
2
|
+
import { SEMVER_RE } from "./domain/refs.js";
|
|
3
|
+
function appendCaptured(log, label, channel, captured) {
|
|
4
|
+
log.appendBytes(channel, Buffer.from(captured.text));
|
|
5
|
+
if (captured.droppedBytes > 0)
|
|
6
|
+
log.appendText(channel, `${label} ${channel} omitted ${captured.droppedBytes} bytes at the capture limit`);
|
|
7
|
+
}
|
|
8
|
+
export function nativeCommandResult(label, result) {
|
|
9
|
+
const operation = `${label.toLowerCase()}-command`;
|
|
10
|
+
if (result.kind === "launchFailed") {
|
|
11
|
+
const errno = /^[A-Z][A-Z0-9_]*$/u.test(result.errno)
|
|
12
|
+
? result.errno
|
|
13
|
+
: "UNKNOWN";
|
|
14
|
+
return failureResult(operation, "launch-failed", `cannot launch configured ${label} executable (${errno})`, [], []);
|
|
15
|
+
}
|
|
16
|
+
const log = new AdapterMessageLog();
|
|
17
|
+
if (result.kind === "timedOut") {
|
|
18
|
+
appendCaptured(log, label, "stdout", result.stdout);
|
|
19
|
+
appendCaptured(log, label, "stderr", result.stderr);
|
|
20
|
+
return failureResult(operation, "timeout", `${label} command timed out after ${result.afterMs} ms`, [], log.snapshot());
|
|
21
|
+
}
|
|
22
|
+
if (result.code !== 0) {
|
|
23
|
+
appendCaptured(log, label, "stdout", result.stdout);
|
|
24
|
+
appendCaptured(log, label, "stderr", result.stderr);
|
|
25
|
+
return failureResult(operation, "nonzero-exit", result.code === null
|
|
26
|
+
? `${label} command exited without a status`
|
|
27
|
+
: `${label} command exited with status ${result.code}`, [], log.snapshot());
|
|
28
|
+
}
|
|
29
|
+
if (result.stdout.droppedBytes > 0 || result.stderr.droppedBytes > 0) {
|
|
30
|
+
appendCaptured(log, label, "stdout", result.stdout);
|
|
31
|
+
appendCaptured(log, label, "stderr", result.stderr);
|
|
32
|
+
return failureResult(operation, "output-limit", `${label} command output exceeded the capture limit`, [], log.snapshot());
|
|
33
|
+
}
|
|
34
|
+
appendCaptured(log, label, "stderr", result.stderr);
|
|
35
|
+
return successResult(operation, { stdout: result.stdout.text }, log.snapshot());
|
|
36
|
+
}
|
|
37
|
+
export function normalizeSnapshotRuntimeVersion(label, result) {
|
|
38
|
+
if (!result.outcome.ok)
|
|
39
|
+
return { status: result.status, outcome: result.outcome };
|
|
40
|
+
const operation = `${label.toLowerCase()}-runtime`;
|
|
41
|
+
if (result.status !== 0)
|
|
42
|
+
return failureResult(operation, "invalid-status", `${label} runtime inspection returned a nonzero status`, [], result.outcome.messages);
|
|
43
|
+
const version = result.outcome.result.stdout.trim();
|
|
44
|
+
if (!SEMVER_RE.test(version))
|
|
45
|
+
return failureResult(operation, "invalid-version", `${label} runtime inspection returned an invalid version response`, [], result.outcome.messages);
|
|
46
|
+
return successResult(operation, version, result.outcome.messages);
|
|
47
|
+
}
|