@rightkit/release 0.2.65 → 0.2.67
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/addon-command.test.mjs +5 -5
- package/addon-contract.test.mjs +3 -3
- package/build-release.mjs +87 -41
- package/build-release.test.mjs +70 -9
- package/cache-command.mjs +16 -0
- package/cache-command.test.mjs +44 -1
- package/cache-policy.mjs +48 -10
- package/cache-policy.test.mjs +43 -1
- package/cargo-contract.mjs +5 -1
- package/cargo-guard.mjs +53 -7
- package/cargo-guard.test.mjs +64 -1
- package/cargo-target.mjs +47 -0
- package/cargo-target.test.mjs +133 -0
- package/cli/right-release.mjs +1 -0
- package/github-release.test.mjs +3 -3
- package/heavy-command.mjs +13 -7
- package/heavy-command.test.mjs +8 -0
- package/package.json +2 -1
- package/preflight.mjs +13 -5
- package/progress-control.mjs +68 -0
- package/progress-control.test.mjs +39 -0
- package/registry-parity.mjs +79 -0
- package/registry-parity.test.mjs +30 -0
- package/release-state.mjs +1 -1
- package/release.test.mjs +30 -0
- package/right-suite-contract.test.mjs +127 -2
- package/rightkit-versions.json +4 -2
- package/standalone-clone-verify.test.mjs +1 -1
- package/target-bridge.mjs +10 -0
- package/target-bridge.test.mjs +35 -0
package/cache-policy.test.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
resolveCacheLayout,
|
|
28
28
|
resolveSharedCacheIdentity,
|
|
29
29
|
resolveSharedCacheRoot,
|
|
30
|
+
resolveStableRoot,
|
|
30
31
|
} from "./cache-policy.mjs";
|
|
31
32
|
|
|
32
33
|
function root() { return path.join(os.tmpdir(), `rightkit-cache-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`); }
|
|
@@ -61,11 +62,52 @@ function migrationFixture() {
|
|
|
61
62
|
test("cache roots are platform-native and overrides must be absolute", () => {
|
|
62
63
|
assert.throws(() => resolveSharedCacheRoot({ platform: "mac", home: "/Users/test", env: {} }), /RIGHT_RELEASE_CACHE_ROOT is required/);
|
|
63
64
|
assert.equal(resolveSharedCacheRoot({ platform: "win", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }), "C:\\Users\\test\\AppData\\Local\\RightSuite\\Cache\\release");
|
|
64
|
-
assert.equal(resolveSharedCacheRoot({ platform: "linux", home: "/home/test",
|
|
65
|
+
assert.equal(resolveSharedCacheRoot({ platform: "linux", home: "/home/test", env: {} }), "/home/test/.cache/rightsuite/release");
|
|
65
66
|
assert.equal(resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "/tmp/cache" } }), "/tmp/cache");
|
|
66
67
|
assert.throws(() => resolveSharedCacheRoot({ platform: "mac", env: { RIGHT_RELEASE_CACHE_ROOT: "relative" } }), /absolute/i);
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
test("shared cache root ignores XDG_CACHE_HOME regardless of value", () => {
|
|
71
|
+
assert.equal(
|
|
72
|
+
resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }),
|
|
73
|
+
resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u" }),
|
|
74
|
+
);
|
|
75
|
+
assert.equal(resolveSharedCacheRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }), "/home/u/.cache/rightsuite/release");
|
|
76
|
+
// resolveSharedCacheRoot no longer accepts an xdgCacheHome option; passing one has no effect.
|
|
77
|
+
assert.equal(
|
|
78
|
+
resolveSharedCacheRoot({ platform: "linux", env: {}, home: "/home/u", xdgCacheHome: "/should/be/ignored" }),
|
|
79
|
+
"/home/u/.cache/rightsuite/release",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("resolveStableRoot: explicit override wins, win32 requires the env var, other platforms ignore arbitrary env", () => {
|
|
84
|
+
const opts = {
|
|
85
|
+
darwinSegments: ["Library", "Caches", "Example"],
|
|
86
|
+
win32Segments: ["Example"],
|
|
87
|
+
otherSegments: ["example"],
|
|
88
|
+
};
|
|
89
|
+
assert.equal(
|
|
90
|
+
resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "/explicit/path", overrideName: "EXAMPLE_ROOT", env: { EXAMPLE_ROOT: "ignored-by-caller-arg" } }),
|
|
91
|
+
"/explicit/path",
|
|
92
|
+
);
|
|
93
|
+
assert.throws(
|
|
94
|
+
() => resolveStableRoot({ ...opts, platform: "linux", explicitOverride: "relative/path", overrideName: "EXAMPLE_ROOT" }),
|
|
95
|
+
/EXAMPLE_ROOT must be an absolute path/,
|
|
96
|
+
);
|
|
97
|
+
assert.throws(
|
|
98
|
+
() => resolveStableRoot({ ...opts, platform: "win32", env: {} }),
|
|
99
|
+
/LOCALAPPDATA must be an absolute path for RightSuite/,
|
|
100
|
+
);
|
|
101
|
+
assert.equal(
|
|
102
|
+
resolveStableRoot({ ...opts, platform: "win32", env: { LOCALAPPDATA: "C:/Users/test/AppData/Local" } }),
|
|
103
|
+
"C:\\Users\\test\\AppData\\Local\\Example",
|
|
104
|
+
);
|
|
105
|
+
assert.equal(
|
|
106
|
+
resolveStableRoot({ ...opts, platform: "linux", env: { XDG_CACHE_HOME: "/ignored", npm_config_cache: "/ignored" }, home: "/home/u" }),
|
|
107
|
+
"/home/u/.cache/example",
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
69
111
|
test("layout isolates app, platform, kind, and fingerprint", () => {
|
|
70
112
|
const base = root();
|
|
71
113
|
const release = layout(base);
|
package/cargo-contract.mjs
CHANGED
|
@@ -27,10 +27,14 @@ export function managedCargoShimOnPath(env = process.env, platform = process.pla
|
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export function brokerManagedHost(env = process.env, platform = process.platform, exists = existsSync) {
|
|
31
|
+
return Boolean(env.RIGHTKIT_BUILD_BROKER_SOCKET) || managedCargoShimOnPath(env, platform, exists);
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
export function isolatedCargoMetadataEnv(cargoHome, env = process.env, platform = process.platform, exists = existsSync) {
|
|
31
35
|
const metadataEnv = { ...env };
|
|
32
36
|
for (const name of CACHE_V2_ENVIRONMENT) delete metadataEnv[name];
|
|
33
|
-
if (
|
|
37
|
+
if (brokerManagedHost(metadataEnv, platform, exists)) return metadataEnv;
|
|
34
38
|
return { ...metadataEnv, CARGO_HOME: cargoHome };
|
|
35
39
|
}
|
|
36
40
|
|
package/cargo-guard.mjs
CHANGED
|
@@ -6,7 +6,8 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
|
-
import { resolveSharedCacheRoot } from "./cache-policy.mjs";
|
|
9
|
+
import { resolveSharedCacheRoot, resolveStableRoot } from "./cache-policy.mjs";
|
|
10
|
+
import { brokerManagedHost } from "./cargo-contract.mjs";
|
|
10
11
|
import { runHeavyCommand } from "./heavy-command.mjs";
|
|
11
12
|
|
|
12
13
|
const LIGHT_COMMANDS = new Set(["fmt", "metadata", "tree", "fetch", "search", "locate-project", "read-manifest", "help", "version", "--version", "-V"]);
|
|
@@ -34,7 +35,14 @@ export function computePolicyPath({ env = process.env, platform = process.platfo
|
|
|
34
35
|
return path.win32.join(env.LOCALAPPDATA || path.win32.join(home, "AppData", "Local"), "RightSuite", "compute-policy.json");
|
|
35
36
|
}
|
|
36
37
|
if (platform === "darwin" || platform === "mac") return path.join(home, "Library", "Application Support", "RightSuite", "compute-policy.json");
|
|
37
|
-
|
|
38
|
+
const base = resolveStableRoot({
|
|
39
|
+
platform,
|
|
40
|
+
env,
|
|
41
|
+
home,
|
|
42
|
+
otherBaseSegments: [".config"],
|
|
43
|
+
otherSegments: ["rightsuite"],
|
|
44
|
+
});
|
|
45
|
+
return path.join(base, "compute-policy.json");
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
export function loadComputePolicy(options = {}) {
|
|
@@ -121,6 +129,24 @@ export function resolveRealRustc({ env = process.env, run = spawnSync } = {}) {
|
|
|
121
129
|
return path.resolve(rustc);
|
|
122
130
|
}
|
|
123
131
|
|
|
132
|
+
// Locate the broker's own managed cargo shim on PATH (the agent-bin launcher
|
|
133
|
+
// that routes into the build broker). Used to hand a whole invocation to the
|
|
134
|
+
// broker on a managed host instead of resolving & running the real toolchain
|
|
135
|
+
// ourselves — which would bypass managed build control entirely.
|
|
136
|
+
export function resolveManagedCargoShim({ env = process.env, platform = process.platform, exists = existsSync } = {}) {
|
|
137
|
+
const win = platform === "win32" || platform === "win";
|
|
138
|
+
const delimiter = win ? ";" : ":";
|
|
139
|
+
const agentBin = /(?:^|[\\/])(?:\.rightkit-managed|rightkitmanagedagent)[\\/]agent-bin[\\/]?$/i;
|
|
140
|
+
for (const directory of String(env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
141
|
+
if (!agentBin.test(directory)) continue;
|
|
142
|
+
for (const name of win ? ["cargo.exe", "cargo.cmd"] : ["cargo"]) {
|
|
143
|
+
const candidate = path.join(directory, name);
|
|
144
|
+
if (exists(candidate)) return candidate;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
124
150
|
function pathApi(platform) {
|
|
125
151
|
return platform === "win32" || platform === "win" ? path.win32 : path.posix;
|
|
126
152
|
}
|
|
@@ -164,10 +190,18 @@ export function cargoCacheEnvironment(args, {
|
|
|
164
190
|
const targetOption = args.find((value) => value.startsWith("--target-dir="));
|
|
165
191
|
const targetIndex = args.findIndex((value) => value === "--target-dir");
|
|
166
192
|
const explicitTarget = targetOption?.slice("--target-dir=".length) ?? (targetIndex >= 0 ? args[targetIndex + 1] : null);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
193
|
+
// On a broker-managed host, CARGO_TARGET_DIR is not this cache's to police: the
|
|
194
|
+
// build broker unconditionally overrides it to its own per-namespace workspace
|
|
195
|
+
// outside the shared cache root, so asserting it stays inside cacheRoot is a
|
|
196
|
+
// spurious failure, not a real violation. Pass the broker's own value through
|
|
197
|
+
// untouched instead of rejecting it.
|
|
198
|
+
const broker = brokerManagedHost(env, platform, exists);
|
|
199
|
+
if (explicitTarget && !broker) assertInsideCache(explicitTarget, cacheRoot, "--target-dir", { cwd, platform });
|
|
200
|
+
const targetDir = broker
|
|
201
|
+
? (env.CARGO_TARGET_DIR ?? explicitTarget ?? defaultTarget)
|
|
202
|
+
: env.CARGO_TARGET_DIR
|
|
203
|
+
? assertInsideCache(env.CARGO_TARGET_DIR, cacheRoot, "CARGO_TARGET_DIR", { cwd, platform })
|
|
204
|
+
: defaultTarget;
|
|
171
205
|
const sccacheDir = env.SCCACHE_DIR
|
|
172
206
|
? assertInsideCache(env.SCCACHE_DIR, cacheRoot, "SCCACHE_DIR", { cwd, platform })
|
|
173
207
|
: defaultSccache;
|
|
@@ -193,8 +227,20 @@ function spawnCargo(command, args, env) {
|
|
|
193
227
|
});
|
|
194
228
|
}
|
|
195
229
|
|
|
196
|
-
export async function runCargoGuard(args, { env = process.env, policyOptions = {}, resolveCargo = resolveRealCargo, resolveRustc = resolveRealRustc, runHeavy = runHeavyCommand, runLight = spawnCargo } = {}) {
|
|
230
|
+
export async function runCargoGuard(args, { env = process.env, policyOptions = {}, resolveCargo = resolveRealCargo, resolveRustc = resolveRealRustc, runHeavy = runHeavyCommand, runLight = spawnCargo, resolveShim = resolveManagedCargoShim, isBrokerHost = brokerManagedHost } = {}) {
|
|
197
231
|
assertComputePolicyAllowsCargo(args, { ...policyOptions, env });
|
|
232
|
+
// On a broker-managed host the build broker owns the toolchain, cache, and
|
|
233
|
+
// quotas. Resolving the real Cargo here and running it via runHeavy would
|
|
234
|
+
// bypass the broker (and, on Windows, resolveReal* itself fails: `rustup
|
|
235
|
+
// which` routes through the managed shim into the broker's isolated
|
|
236
|
+
// RUSTUP_HOME, which has no default toolchain). Hand the whole invocation to
|
|
237
|
+
// the broker's own cargo shim instead — everything through the broker.
|
|
238
|
+
if (isBrokerHost(env)) {
|
|
239
|
+
const shim = resolveShim({ env });
|
|
240
|
+
if (shim) return runLight(shim, args, env);
|
|
241
|
+
// No shim found despite a broker signal: fall through to legacy resolution
|
|
242
|
+
// rather than silently dropping the guard.
|
|
243
|
+
}
|
|
198
244
|
const cargo = resolveCargo({ env });
|
|
199
245
|
if (shouldGuardCargo(args)) {
|
|
200
246
|
const guarded = cargoCacheEnvironment(args, { env });
|
package/cargo-guard.test.mjs
CHANGED
|
@@ -2,7 +2,15 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
|
|
5
|
-
import { assertComputePolicyAllowsCargo, cargoCacheEnvironment, cargoProjectRoot, cargoSubcommand, checkHookInput, resolveRealCargo, resolveRealRustc, runCargoGuard, shellRunsCargoTest, shouldGuardCargo } from "./cargo-guard.mjs";
|
|
5
|
+
import { assertComputePolicyAllowsCargo, cargoCacheEnvironment, cargoProjectRoot, cargoSubcommand, checkHookInput, computePolicyPath, resolveRealCargo, resolveRealRustc, runCargoGuard, shellRunsCargoTest, shouldGuardCargo } from "./cargo-guard.mjs";
|
|
6
|
+
|
|
7
|
+
test("compute policy path ignores XDG_CONFIG_HOME regardless of value", () => {
|
|
8
|
+
assert.equal(
|
|
9
|
+
computePolicyPath({ platform: "linux", env: { XDG_CONFIG_HOME: "/one/path" }, home: "/home/u" }),
|
|
10
|
+
computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }),
|
|
11
|
+
);
|
|
12
|
+
assert.equal(computePolicyPath({ platform: "linux", env: {}, home: "/home/u" }), path.join("/home/u/.config/rightsuite", "compute-policy.json"));
|
|
13
|
+
});
|
|
6
14
|
|
|
7
15
|
test("Cargo guard serializes compiling commands but bypasses inspection and formatting", () => {
|
|
8
16
|
for (const args of [["build"], ["test"], ["check"], ["clippy"], ["nextest", "run"], ["clean"], ["+stable", "bench"]]) {
|
|
@@ -78,6 +86,37 @@ test("Cargo guard rejects cache escapes and alternate compiler wrappers", async
|
|
|
78
86
|
await assert.rejects(run(["build"], { RUSTC_WRAPPER: "rustc-wrapper" }), /RUSTC_WRAPPER/);
|
|
79
87
|
});
|
|
80
88
|
|
|
89
|
+
test("Cargo guard hands the whole invocation to the broker on a managed host", async () => {
|
|
90
|
+
const calls = [];
|
|
91
|
+
let resolvedReal = false;
|
|
92
|
+
const options = {
|
|
93
|
+
env: { TEST: "1" },
|
|
94
|
+
policyOptions: { exists: () => false },
|
|
95
|
+
isBrokerHost: () => true,
|
|
96
|
+
resolveShim: () => "/managed/agent-bin/cargo",
|
|
97
|
+
resolveCargo: () => { resolvedReal = true; return "/real/cargo"; },
|
|
98
|
+
resolveRustc: () => { resolvedReal = true; return "/real/rustc"; },
|
|
99
|
+
runHeavy: async () => { throw new Error("must not run heavy on a broker host"); },
|
|
100
|
+
runLight: async (command, args) => { calls.push([command, args]); return 0; },
|
|
101
|
+
};
|
|
102
|
+
// A guarded command (build) and a light command (--version) both delegate to
|
|
103
|
+
// the broker shim verbatim, and the real-toolchain resolver is never touched.
|
|
104
|
+
assert.equal(await runCargoGuard(["build", "--release"], options), 0);
|
|
105
|
+
assert.equal(await runCargoGuard(["--version"], options), 0);
|
|
106
|
+
assert.deepEqual(calls, [["/managed/agent-bin/cargo", ["build", "--release"]], ["/managed/agent-bin/cargo", ["--version"]]]);
|
|
107
|
+
assert.equal(resolvedReal, false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("Cargo guard falls back to real resolution when a broker signal has no shim", async () => {
|
|
111
|
+
const calls = [];
|
|
112
|
+
await runCargoGuard(["build"], {
|
|
113
|
+
env: cacheEnv(), isBrokerHost: () => true, resolveShim: () => null,
|
|
114
|
+
resolveCargo: () => "/real/cargo", resolveRustc: () => "/real/rustc",
|
|
115
|
+
runHeavy: async (args) => { calls.push(args); return 0; },
|
|
116
|
+
});
|
|
117
|
+
assert.deepEqual(calls[0].slice(0, 2), ["--", "/real/cargo"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
81
120
|
test("Cargo guard routes heavy and light commands without recursion", async () => {
|
|
82
121
|
const calls = [];
|
|
83
122
|
const options = {
|
|
@@ -122,6 +161,30 @@ test("Cargo guard rejects cache bypasses and permits RightKit-owned cache paths"
|
|
|
122
161
|
assert.equal(env.CARGO_TARGET_DIR, "/cache/test-targets/app");
|
|
123
162
|
});
|
|
124
163
|
|
|
164
|
+
test("Cargo guard passes a broker-owned CARGO_TARGET_DIR through instead of rejecting it", () => {
|
|
165
|
+
const base = { cwd: "/repo", platform: "mac", exists: () => false };
|
|
166
|
+
// Outside the shared cache root, which would be rejected on a non-broker host
|
|
167
|
+
// (see the previous test) — but the broker owns this value, not Cache V2.
|
|
168
|
+
const env = cargoCacheEnvironment(["test"], {
|
|
169
|
+
...base,
|
|
170
|
+
env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock", CARGO_TARGET_DIR: "/broker/workspace/target" },
|
|
171
|
+
});
|
|
172
|
+
assert.equal(env.CARGO_TARGET_DIR, "/broker/workspace/target");
|
|
173
|
+
|
|
174
|
+
// Same for an explicit --target-dir outside the cache root.
|
|
175
|
+
const explicit = cargoCacheEnvironment(["test", "--target-dir", "/broker/workspace/target"], {
|
|
176
|
+
...base,
|
|
177
|
+
env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", RIGHTKIT_BUILD_BROKER_SOCKET: "/managed/broker.sock" },
|
|
178
|
+
});
|
|
179
|
+
assert.doesNotThrow(() => explicit);
|
|
180
|
+
|
|
181
|
+
// Non-broker host: unchanged, still rejects the cache bypass.
|
|
182
|
+
assert.throws(
|
|
183
|
+
() => cargoCacheEnvironment(["test"], { ...base, env: { RIGHT_RELEASE_CACHE_ROOT: "/cache", CARGO_TARGET_DIR: "/tmp/target" } }),
|
|
184
|
+
/CARGO_TARGET_DIR must stay inside/,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
125
188
|
test("Cargo guard derives native Windows cache paths", () => {
|
|
126
189
|
const env = cargoCacheEnvironment(["build"], {
|
|
127
190
|
cwd: "D:\\Claude\\citadel", platform: "win", exists: (file) => file === "D:\\Claude\\citadel\\Cargo.toml",
|
package/cargo-target.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Resolves the Cargo target directory for a manifest by asking Cargo itself.
|
|
3
|
+
//
|
|
4
|
+
// `cargo metadata` is authoritative on BOTH broker-managed and unmanaged
|
|
5
|
+
// hosts: through a managed shim it reports the broker's own workspace target
|
|
6
|
+
// directory; without one it reports the local `target/` directory. LOCATING
|
|
7
|
+
// the target directory therefore never needs broker detection — the metadata
|
|
8
|
+
// command already reflects whichever host it runs on. `brokerManagedHost()`
|
|
9
|
+
// (see cargo-contract.mjs) remains a separate, cache-policy-only decision:
|
|
10
|
+
// whether Cache V2's lease/slot/prune/floor machinery should run at all, not
|
|
11
|
+
// where the target directory lives.
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
export function resolveTargetRoot(manifestPath) {
|
|
17
|
+
if (!manifestPath) throw new Error("resolveTargetRoot requires a Cargo.toml manifest path");
|
|
18
|
+
let output;
|
|
19
|
+
try {
|
|
20
|
+
output = execFileSync(
|
|
21
|
+
"cargo",
|
|
22
|
+
["metadata", "--offline", "--format-version", "1", "--no-deps", "--manifest-path", manifestPath],
|
|
23
|
+
{ cwd: path.dirname(manifestPath), encoding: "utf8", maxBuffer: 64 * 1024 * 1024 },
|
|
24
|
+
);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`metadata failed for ${manifestPath}: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
const { target_directory: directory } = JSON.parse(output);
|
|
29
|
+
if (typeof directory !== "string" || directory.length === 0 || !path.isAbsolute(directory)) {
|
|
30
|
+
throw new Error(`metadata for ${manifestPath} did not report an absolute target_directory`);
|
|
31
|
+
}
|
|
32
|
+
return path.resolve(directory);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
36
|
+
const manifestPath = process.argv[2];
|
|
37
|
+
if (!manifestPath) {
|
|
38
|
+
console.error("usage: cargo-target.mjs <path-to-Cargo.toml>");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
process.stdout.write(`${resolveTargetRoot(manifestPath)}\n`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(error.message);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { chmodSync, mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
import { resolveTargetRoot } from "./cargo-target.mjs";
|
|
10
|
+
|
|
11
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const cli = path.join(here, "cargo-target.mjs");
|
|
13
|
+
|
|
14
|
+
// A fake `cargo` on PATH, ahead of the real one, so metadata success/failure
|
|
15
|
+
// shapes are deterministic and don't depend on the host's actual crate graph.
|
|
16
|
+
// It branches on a marker embedded in the manifest path so each test controls
|
|
17
|
+
// its own outcome independently.
|
|
18
|
+
function fakeCargoBin() {
|
|
19
|
+
const bin = mkdtempSync(path.join(os.tmpdir(), "rightkit-fake-cargo-"));
|
|
20
|
+
const script = path.join(bin, process.platform === "win32" ? "cargo.cmd" : "cargo");
|
|
21
|
+
const body = process.platform === "win32"
|
|
22
|
+
? [
|
|
23
|
+
"@echo off",
|
|
24
|
+
"setlocal enabledelayedexpansion",
|
|
25
|
+
"set ARGS=%*",
|
|
26
|
+
"echo %ARGS% | findstr /C:\"fail-manifest\" >nul && (echo boom 1>&2 & exit /b 1)",
|
|
27
|
+
"echo %ARGS% | findstr /C:\"bad-target-manifest\" >nul && (echo {\"target_directory\":\"relative/target\"} & exit /b 0)",
|
|
28
|
+
"echo {\"target_directory\":\"" + path.join(bin, "target").replaceAll("\\", "\\\\") + "\"}",
|
|
29
|
+
].join("\r\n")
|
|
30
|
+
: [
|
|
31
|
+
"#!/bin/sh",
|
|
32
|
+
'case "$*" in',
|
|
33
|
+
' *fail-manifest*) echo boom 1>&2; exit 1 ;;',
|
|
34
|
+
' *bad-target-manifest*) echo \'{"target_directory":"relative/target"}\'; exit 0 ;;',
|
|
35
|
+
` *) echo '{"target_directory":"${path.join(bin, "target")}"}'; exit 0 ;;`,
|
|
36
|
+
"esac",
|
|
37
|
+
].join("\n");
|
|
38
|
+
writeFileSync(script, body);
|
|
39
|
+
if (process.platform !== "win32") chmodSync(script, 0o755);
|
|
40
|
+
return bin;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function withFakeCargo(fn) {
|
|
44
|
+
const bin = fakeCargoBin();
|
|
45
|
+
const env = { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH ?? ""}` };
|
|
46
|
+
return fn({ bin, env });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test("resolveTargetRoot returns the absolute target_directory Cargo metadata reports", () => {
|
|
50
|
+
withFakeCargo(({ bin, env }) => {
|
|
51
|
+
const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
|
|
52
|
+
const manifest = path.join(manifestDir, "Cargo.toml");
|
|
53
|
+
writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
|
|
54
|
+
const previousPath = process.env.PATH;
|
|
55
|
+
process.env.PATH = env.PATH;
|
|
56
|
+
try {
|
|
57
|
+
const result = resolveTargetRoot(manifest);
|
|
58
|
+
assert.equal(result, path.resolve(path.join(bin, "target")));
|
|
59
|
+
} finally {
|
|
60
|
+
process.env.PATH = previousPath;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("resolveTargetRoot fails closed, naming the manifest, when target_directory is not absolute", () => {
|
|
66
|
+
withFakeCargo(({ env }) => {
|
|
67
|
+
const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
|
|
68
|
+
const manifest = path.join(manifestDir, "bad-target-manifest-Cargo.toml");
|
|
69
|
+
writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
|
|
70
|
+
const previousPath = process.env.PATH;
|
|
71
|
+
process.env.PATH = env.PATH;
|
|
72
|
+
try {
|
|
73
|
+
assert.throws(
|
|
74
|
+
() => resolveTargetRoot(manifest),
|
|
75
|
+
new RegExp(`metadata for .*bad-target-manifest-Cargo\\.toml did not report an absolute target_directory`),
|
|
76
|
+
);
|
|
77
|
+
} finally {
|
|
78
|
+
process.env.PATH = previousPath;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("resolveTargetRoot fails closed, naming the manifest, when the metadata command exits non-zero", () => {
|
|
84
|
+
withFakeCargo(({ env }) => {
|
|
85
|
+
const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
|
|
86
|
+
const manifest = path.join(manifestDir, "fail-manifest-Cargo.toml");
|
|
87
|
+
writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
|
|
88
|
+
const previousPath = process.env.PATH;
|
|
89
|
+
process.env.PATH = env.PATH;
|
|
90
|
+
try {
|
|
91
|
+
assert.throws(
|
|
92
|
+
() => resolveTargetRoot(manifest),
|
|
93
|
+
new RegExp(`metadata failed for .*fail-manifest-Cargo\\.toml`),
|
|
94
|
+
);
|
|
95
|
+
} finally {
|
|
96
|
+
process.env.PATH = previousPath;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("resolveTargetRoot throws when no manifest path is given", () => {
|
|
102
|
+
assert.throws(() => resolveTargetRoot(), /resolveTargetRoot requires a Cargo.toml manifest path/);
|
|
103
|
+
assert.throws(() => resolveTargetRoot(""), /resolveTargetRoot requires a Cargo.toml manifest path/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("CLI mode writes only the resolved path to stdout on success", () => {
|
|
107
|
+
withFakeCargo(({ bin, env }) => {
|
|
108
|
+
const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
|
|
109
|
+
const manifest = path.join(manifestDir, "Cargo.toml");
|
|
110
|
+
writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
|
|
111
|
+
const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8", env });
|
|
112
|
+
assert.equal(result.status, 0, result.stderr);
|
|
113
|
+
assert.equal(result.stdout.trim(), path.resolve(path.join(bin, "target")));
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("CLI mode writes a message to stderr and exits 1 on failure", () => {
|
|
118
|
+
withFakeCargo(({ env }) => {
|
|
119
|
+
const manifestDir = mkdtempSync(path.join(os.tmpdir(), "rightkit-cargo-target-fixture-"));
|
|
120
|
+
const manifest = path.join(manifestDir, "fail-manifest-Cargo.toml");
|
|
121
|
+
writeFileSync(manifest, "[package]\nname = \"fixture\"\nversion = \"0.0.0\"\n");
|
|
122
|
+
const result = spawnSync(process.execPath, [cli, manifest], { encoding: "utf8", env });
|
|
123
|
+
assert.equal(result.status, 1);
|
|
124
|
+
assert.equal(result.stdout, "");
|
|
125
|
+
assert.match(result.stderr, /metadata failed for .*fail-manifest-Cargo\.toml/);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("CLI mode requires a manifest argument", () => {
|
|
130
|
+
const result = spawnSync(process.execPath, [cli], { encoding: "utf8" });
|
|
131
|
+
assert.equal(result.status, 1);
|
|
132
|
+
assert.match(result.stderr, /usage: cargo-target\.mjs/);
|
|
133
|
+
});
|
package/cli/right-release.mjs
CHANGED
|
@@ -20,6 +20,7 @@ const commands = new Map([
|
|
|
20
20
|
["create-mac-updater", "create-mac-updater.mjs"],
|
|
21
21
|
["mirror-root-artifact", "mirror-root-artifact.mjs"],
|
|
22
22
|
["github", "github-release.mjs"],
|
|
23
|
+
["cargo-target", "cargo-target.mjs"],
|
|
23
24
|
]);
|
|
24
25
|
|
|
25
26
|
const args = process.argv.slice(2);
|
package/github-release.test.mjs
CHANGED
|
@@ -27,13 +27,13 @@ function fixture() {
|
|
|
27
27
|
function addonFixture() {
|
|
28
28
|
const root = mkdtempSync(path.join(os.tmpdir(), "right-release-github-addon-"));
|
|
29
29
|
mkdirSync(path.join(root, "out"));
|
|
30
|
-
for (const [name, bytes] of [["membrane", "command"], ["
|
|
30
|
+
for (const [name, bytes] of [["membrane", "command"], ["membrane-service", "service"], ["icon.png", "icon"], ["LICENSE", "license"], ["EULA.txt", "eula"], ["PRIVACY.md", "privacy"], ["THIRD-PARTY-NOTICES.txt", "notices"]]) writeFileSync(path.join(root, "out", name), bytes);
|
|
31
31
|
execFileSync("git", ["init", "--initial-branch", "main"], { cwd: root });
|
|
32
32
|
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: root });
|
|
33
33
|
execFileSync("git", ["config", "user.name", "Test"], { cwd: root });
|
|
34
34
|
writeFileSync(path.join(root, "tracked"), "source"); execFileSync("git", ["add", "."], { cwd: root }); execFileSync("git", ["commit", "-m", "source"], { cwd: root });
|
|
35
|
-
const files = [["command", "membrane", true], ["service", "
|
|
36
|
-
const config = { schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", distribution: { provider: "github-releases", repository: "Orthic-Labs/Membrane" }, checks: [], buildInputs: { include: ["tracked"] }, consumer: { contract: "
|
|
35
|
+
const files = [["command", "membrane", true], ["service", "membrane-service", true], ["icon", "icon.png", false], ["license", "LICENSE", false], ["eula", "EULA.txt", false], ["privacy", "PRIVACY.md", false], ["third-party-notices", "THIRD-PARTY-NOTICES.txt", false]].map(([role, name, executable]) => ({ role, name, source: `out/${name}`, executable }));
|
|
36
|
+
const config = { schema: 1, kind: "headless-addon", addon: "membrane", version: "0.1.0", packageManager: "pnpm", distribution: { provider: "github-releases", repository: "Orthic-Labs/Membrane" }, checks: [], buildInputs: { include: ["tracked"] }, consumer: { contract: "membrane-product-v1" }, targets: { win: { targetTriple: "x86_64-pc-windows-msvc", build: { cmd: "false", args: [] }, signing: { contract: "azure-artifact-signing-v1" }, files } } };
|
|
37
37
|
const commit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
38
38
|
const manifest = createAddonManifest({ config, root, platform: "win", commit, signing: { command: { contract: "test-fixture-v1", status: "verified" }, service: { contract: "test-fixture-v1", status: "verified" } } });
|
|
39
39
|
const sealed = path.join(root, ".right-release", "addons", "membrane", "0.1.0", commit.slice(0, 8), "win"); mkdirSync(sealed, { recursive: true });
|
package/heavy-command.mjs
CHANGED
|
@@ -6,17 +6,23 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
|
|
9
|
+
import { resolveStableRoot } from "./cache-policy.mjs";
|
|
10
|
+
|
|
9
11
|
const GB = 1024 ** 3;
|
|
10
12
|
const SELF = fileURLToPath(import.meta.url);
|
|
11
13
|
|
|
12
14
|
export function heavyWorkRoot({ platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
return resolveStableRoot({
|
|
16
|
+
platform,
|
|
17
|
+
env,
|
|
18
|
+
home,
|
|
19
|
+
explicitOverride: env.RIGHTSUITE_HEAVY_WORK_ROOT,
|
|
20
|
+
overrideName: "RIGHTSUITE_HEAVY_WORK_ROOT",
|
|
21
|
+
darwinSegments: ["Library", "Caches", "RightSuite", "heavy-work"],
|
|
22
|
+
win32EnvVar: "LOCALAPPDATA",
|
|
23
|
+
win32Segments: ["RightSuite", "heavy-work"],
|
|
24
|
+
otherSegments: ["rightsuite", "heavy-work"],
|
|
25
|
+
});
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export function heavyCommandEnvironment(env = process.env) {
|
package/heavy-command.test.mjs
CHANGED
|
@@ -26,6 +26,14 @@ test("heavy-work root is machine-wide rather than repository-local", () => {
|
|
|
26
26
|
assert.equal(heavyWorkRoot({ platform: "win", env: { LOCALAPPDATA: "C:\\Users\\test\\AppData\\Local" } }), "C:\\Users\\test\\AppData\\Local\\RightSuite\\heavy-work");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
test("heavy-work root ignores XDG_CACHE_HOME regardless of value", () => {
|
|
30
|
+
assert.equal(
|
|
31
|
+
heavyWorkRoot({ platform: "linux", env: { XDG_CACHE_HOME: "/one/path" }, home: "/home/u" }),
|
|
32
|
+
heavyWorkRoot({ platform: "linux", env: {}, home: "/home/u" }),
|
|
33
|
+
);
|
|
34
|
+
assert.equal(heavyWorkRoot({ platform: "linux", env: {}, home: "/home/u" }), "/home/u/.cache/rightsuite/heavy-work");
|
|
35
|
+
});
|
|
36
|
+
|
|
29
37
|
test("heavy commands default compiler and test concurrency to two", () => {
|
|
30
38
|
const env = heavyCommandEnvironment({});
|
|
31
39
|
assert.equal(env.CARGO_BUILD_JOBS, "2");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.67",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: native-host signed installers, updater artifacts, hardening, immutable GitHub Release upload, and add-on adoption.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"test": "node --test *.test.mjs",
|
|
29
|
+
"test:registry-parity": "node registry-parity.mjs --allow-unpublished",
|
|
29
30
|
"doctor:all": "node --test right-suite-contract.test.mjs",
|
|
30
31
|
"verify:standalone": "node standalone-clone-verify.mjs"
|
|
31
32
|
}
|
package/preflight.mjs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
|
+
import { brokerManagedHost } from "./cargo-contract.mjs";
|
|
24
25
|
|
|
25
26
|
export const PREFLIGHT_OK = "ok";
|
|
26
27
|
export const PREFLIGHT_WARN = "warn";
|
|
@@ -186,11 +187,18 @@ export function collectPreflight({
|
|
|
186
187
|
checks.push(
|
|
187
188
|
!entry || entry.isSymbolicLink()
|
|
188
189
|
? ok("target-bridge", entry ? "src-tauri/target is a symbolic link" : "src-tauri/target is ready for the shared cache bridge")
|
|
189
|
-
:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
// A real directory here is not corruption on a broker-managed host: the
|
|
191
|
+
// build broker owns CARGO_TARGET_DIR and this is its own output (or a
|
|
192
|
+
// leftover from before it took ownership), not Cache V2's bridge target.
|
|
193
|
+
// Recommending `cache migrate` here is how an agent nearly renamed ~5.9 GB
|
|
194
|
+
// of warm broker intermediates out from under it.
|
|
195
|
+
: brokerManagedHost(env)
|
|
196
|
+
? ok("target-bridge", `${target} is a real directory; broker-managed host, left untouched`)
|
|
197
|
+
: fail(
|
|
198
|
+
"target-bridge",
|
|
199
|
+
`${target} is a real directory; bridge setup will refuse it`,
|
|
200
|
+
`run right-release cache migrate --config ${JSON.stringify(configPath ?? path.join(appRoot, "right-release.config.mjs"))} --dry-run, then repeat with --apply before building`,
|
|
201
|
+
),
|
|
194
202
|
);
|
|
195
203
|
}
|
|
196
204
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const MANAGED_REQUEST_PATTERN = /\brightkit(?: managed-agent)?: request ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/gi;
|
|
4
|
+
|
|
5
|
+
export function latestManagedRequestId(value, fallback = null) {
|
|
6
|
+
let latest = fallback;
|
|
7
|
+
for (const match of String(value ?? "").matchAll(MANAGED_REQUEST_PATTERN)) latest = match[1].toLowerCase();
|
|
8
|
+
return latest;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function firstManagedRequestId(value) {
|
|
12
|
+
MANAGED_REQUEST_PATTERN.lastIndex = 0;
|
|
13
|
+
return MANAGED_REQUEST_PATTERN.exec(String(value ?? ""))?.[1]?.toLowerCase() ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function releaseProgressLimits(env = process.env) {
|
|
17
|
+
const inactivityMs = Number(env.RIGHT_RELEASE_STALL_MS ?? 10 * 60 * 1000);
|
|
18
|
+
const absoluteMs = Number(env.RIGHT_RELEASE_ABSOLUTE_MS ?? 4 * 60 * 60 * 1000);
|
|
19
|
+
if (![inactivityMs, absoluteMs].every((value) => Number.isFinite(value) && value > 0)) throw new Error("release progress limits must be positive finite milliseconds");
|
|
20
|
+
if (absoluteMs <= inactivityMs) throw new Error("RIGHT_RELEASE_ABSOLUTE_MS must exceed RIGHT_RELEASE_STALL_MS");
|
|
21
|
+
return Object.freeze({ inactivityMs, absoluteMs });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runRightkitControl(args, { cwd, env, timeoutMs = 30_000 } = {}) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const control = spawn("rightkit", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, shell: process.platform === "win32" });
|
|
27
|
+
let output = "";
|
|
28
|
+
let timedOut = false;
|
|
29
|
+
const append = (chunk) => { output = `${output}${chunk}`.slice(-64 * 1024); };
|
|
30
|
+
control.stdout.on("data", append);
|
|
31
|
+
control.stderr.on("data", append);
|
|
32
|
+
const timer = setTimeout(() => {
|
|
33
|
+
timedOut = true;
|
|
34
|
+
control.kill("SIGTERM");
|
|
35
|
+
setTimeout(() => control.kill("SIGKILL"), 1_000).unref();
|
|
36
|
+
}, timeoutMs);
|
|
37
|
+
timer.unref();
|
|
38
|
+
control.once("error", (error) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve(Object.freeze({ code: 125, output, timedOut, error: error.message }));
|
|
41
|
+
});
|
|
42
|
+
control.once("exit", (code, signal) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
resolve(Object.freeze({ code: code ?? 125, signal, output, timedOut }));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function terminalResultFromOutput(output) {
|
|
50
|
+
for (const line of String(output ?? "").split(/\r?\n/).reverse()) {
|
|
51
|
+
try {
|
|
52
|
+
const candidate = JSON.parse(line);
|
|
53
|
+
if (candidate?.type === "RESULT" && typeof candidate.status === "string") return candidate;
|
|
54
|
+
} catch { /* streamed build output is not JSON */ }
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function cancelAndReapManagedRequest(id, { cwd = process.cwd(), env = process.env, run = runRightkitControl } = {}) {
|
|
60
|
+
if (!id) return Object.freeze({ status: "NO_MANAGED_REQUEST" });
|
|
61
|
+
const cancel = await run(["cancel", id], { cwd, env });
|
|
62
|
+
const attach = await run(["attach", id], { cwd, env });
|
|
63
|
+
const terminal = terminalResultFromOutput(attach.output);
|
|
64
|
+
if (!terminal) {
|
|
65
|
+
throw new Error(`broker cancellation for ${id} lacked terminal RESULT (cancel=${cancel.code}, attach=${attach.code}${attach.timedOut ? ", attach-timeout" : ""})`);
|
|
66
|
+
}
|
|
67
|
+
return Object.freeze({ status: terminal.status, id, exitCode: terminal.exitCode, receipt: terminal.receipt ?? null, cancelExitCode: cancel.code, attachExitCode: attach.code });
|
|
68
|
+
}
|