riftri 0.6.1 → 0.6.3
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/lib/client.d.ts +32 -1
- package/lib/client.js +37 -5
- package/lib/platform.js +35 -2
- package/package.json +9 -9
package/lib/client.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export const EXIT_OPERATIONAL: 1;
|
|
|
5
5
|
export const EXIT_USAGE: 2;
|
|
6
6
|
export const EXIT_POLICY: 3;
|
|
7
7
|
|
|
8
|
+
/** Resolve and validate the installed native executable, honoring RIFTRI_BINARY. Does not spawn. */
|
|
9
|
+
export function resolveBinary(): string;
|
|
10
|
+
|
|
8
11
|
/** One versioned failure receipt, as emitted by `--json-errors`. */
|
|
9
12
|
export interface FailureReceipt {
|
|
10
13
|
schemaVersion: number;
|
|
@@ -37,7 +40,9 @@ export class RiftriError extends Error {
|
|
|
37
40
|
/** The process was killed rather than exiting on its own. */
|
|
38
41
|
readonly wasSignalled: boolean;
|
|
39
42
|
readonly receipt: FailureReceipt | null;
|
|
40
|
-
/**
|
|
43
|
+
/** Parsed stdout on a nonzero exit (for example a failed post-checkout hook), or null. Validate before use. */
|
|
44
|
+
readonly report: unknown;
|
|
45
|
+
/** A native policy receipt confirms no mutation; an exit code alone is insufficient. */
|
|
41
46
|
readonly isPolicyRefusal: boolean;
|
|
42
47
|
/** The request was malformed; never retry it unchanged. */
|
|
43
48
|
readonly isUsageError: boolean;
|
|
@@ -110,6 +115,28 @@ export interface WorktreeInventory {
|
|
|
110
115
|
native_path_encoding: string;
|
|
111
116
|
}
|
|
112
117
|
|
|
118
|
+
export interface WorktreeOwner {
|
|
119
|
+
schema_version: 1;
|
|
120
|
+
/** Null only when no registered state claims the path. Errors reject. */
|
|
121
|
+
state_directory: string | null;
|
|
122
|
+
state_directory_native_hex: string | null;
|
|
123
|
+
native_path_encoding: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface WorktreeInspectionReport {
|
|
127
|
+
schema_version: 1;
|
|
128
|
+
native_path_encoding: string;
|
|
129
|
+
worktrees: Array<{
|
|
130
|
+
path: string;
|
|
131
|
+
path_native_hex: string;
|
|
132
|
+
state_directory: string | null;
|
|
133
|
+
state_directory_native_hex: string | null;
|
|
134
|
+
backend: string | null;
|
|
135
|
+
/** Null unless the managed backend needs a mount. Inspection never repairs. */
|
|
136
|
+
mount_status: "active" | "recovery-required" | "different-namespace" | "foreign" | "unavailable" | null;
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
|
|
113
140
|
export interface StateDiagnosticIssue {
|
|
114
141
|
path: string;
|
|
115
142
|
path_native_hex: string;
|
|
@@ -239,6 +266,10 @@ export class Riftri {
|
|
|
239
266
|
|
|
240
267
|
readonly worktree: {
|
|
241
268
|
add(destination: string, options?: AddOptions): Promise<AddReport>;
|
|
269
|
+
/** Advisory ownership lookup without disk accounting; mutations revalidate. */
|
|
270
|
+
owner(destination: string): Promise<WorktreeOwner>;
|
|
271
|
+
/** One read-only discovery pass; paths retain the requested order. */
|
|
272
|
+
inspect(destinations: readonly string[]): Promise<WorktreeInspectionReport>;
|
|
242
273
|
list(options: { allStates: true }): Promise<AllStatesWorktreeInventory>;
|
|
243
274
|
list(options?: { allStates?: false }): Promise<WorktreeInventory>;
|
|
244
275
|
/** Narrow schema_version when allStates is a runtime boolean. */
|
package/lib/client.js
CHANGED
|
@@ -29,7 +29,7 @@ const REPORTING = new Set([
|
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
31
|
class RiftriError extends Error {
|
|
32
|
-
constructor(exitCode, receipt, stderr, signal = null) {
|
|
32
|
+
constructor(exitCode, receipt, stderr, signal = null, report = null) {
|
|
33
33
|
super(
|
|
34
34
|
// JSON is not a guarantee of a valid receipt. Coercing an object here
|
|
35
35
|
// can throw inside the child close handler instead of rejecting run().
|
|
@@ -43,6 +43,7 @@ class RiftriError extends Error {
|
|
|
43
43
|
this.exitCode = exitCode;
|
|
44
44
|
this.signal = signal;
|
|
45
45
|
this.receipt = receipt ?? null;
|
|
46
|
+
this.report = report;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/** The process was killed rather than exiting on its own. */
|
|
@@ -52,7 +53,12 @@ class RiftriError extends Error {
|
|
|
52
53
|
|
|
53
54
|
/** Riftri declined before touching anything. Falling back is safe. */
|
|
54
55
|
get isPolicyRefusal() {
|
|
55
|
-
return
|
|
56
|
+
// Hooks and `exec` children can return 3 after performing work. Only a
|
|
57
|
+
// native refusal receipt proves the operation stopped before mutation.
|
|
58
|
+
return this.exitCode === EXIT_POLICY && this.signal === null && this.report === null &&
|
|
59
|
+
this.receipt?.schemaVersion === 1 && this.receipt?.outcome === "failed" &&
|
|
60
|
+
this.receipt?.category === "policy" && this.receipt?.cleanup === "not-needed" &&
|
|
61
|
+
typeof this.receipt?.code === "string";
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/** The request itself was malformed. Never retry unchanged. */
|
|
@@ -120,6 +126,8 @@ class Riftri {
|
|
|
120
126
|
this.worktree = {
|
|
121
127
|
add: this.#worktreeAdd.bind(this),
|
|
122
128
|
list: this.#worktreeList.bind(this),
|
|
129
|
+
owner: this.#worktreeOwner.bind(this),
|
|
130
|
+
inspect: this.#worktreeInspect.bind(this),
|
|
123
131
|
remove: this.#worktreeRemove.bind(this),
|
|
124
132
|
move: this.#worktreeMove.bind(this),
|
|
125
133
|
compact: this.#worktreeCompact.bind(this),
|
|
@@ -200,8 +208,11 @@ class Riftri {
|
|
|
200
208
|
// child wrote there first (a hook's output, a Git warning) must not
|
|
201
209
|
// cost the caller the receipt's code and category.
|
|
202
210
|
let receipt = null;
|
|
203
|
-
|
|
204
|
-
for
|
|
211
|
+
// Preserve the full diagnostic fallback without allocating an array
|
|
212
|
+
// and a string entry for every line of a verbose child's output.
|
|
213
|
+
const trimmed = stderr.trimEnd();
|
|
214
|
+
const lastLine = trimmed.slice(trimmed.lastIndexOf("\n") + 1);
|
|
215
|
+
for (const candidate of [lastLine, stderr]) {
|
|
205
216
|
try {
|
|
206
217
|
receipt = JSON.parse(candidate);
|
|
207
218
|
break;
|
|
@@ -209,7 +220,18 @@ class Riftri {
|
|
|
209
220
|
// Plain text, as from a binary that predates usage receipts.
|
|
210
221
|
}
|
|
211
222
|
}
|
|
212
|
-
|
|
223
|
+
let report = null;
|
|
224
|
+
if (json && stdout.trim()) {
|
|
225
|
+
// A failed post-checkout hook leaves a created worktree in place.
|
|
226
|
+
// Preserve that report even though the hook's status rejects run().
|
|
227
|
+
try {
|
|
228
|
+
report = JSON.parse(stdout);
|
|
229
|
+
} catch {
|
|
230
|
+
// Malformed output cannot prove that a fallback is safe either.
|
|
231
|
+
}
|
|
232
|
+
receipt = null;
|
|
233
|
+
}
|
|
234
|
+
reject(new RiftriError(exitCode, receipt, stderr, null, report));
|
|
213
235
|
});
|
|
214
236
|
});
|
|
215
237
|
}
|
|
@@ -297,6 +319,15 @@ class Riftri {
|
|
|
297
319
|
return this.run(["worktree", "list", ...flagsFor(options), ...state]);
|
|
298
320
|
}
|
|
299
321
|
|
|
322
|
+
#worktreeOwner(destination) {
|
|
323
|
+
// Ownership always consults every registered state, not this.stateDir.
|
|
324
|
+
return this.run(["worktree", "owner", "--", destination]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#worktreeInspect(destinations) {
|
|
328
|
+
return this.run(["worktree", "inspect", "--", ...destinations]);
|
|
329
|
+
}
|
|
330
|
+
|
|
300
331
|
#worktreeRemove(destination, { force = false } = {}) {
|
|
301
332
|
const args = ["worktree", "remove"];
|
|
302
333
|
if (force) args.push("--force", "--yes");
|
|
@@ -319,6 +350,7 @@ class Riftri {
|
|
|
319
350
|
module.exports = {
|
|
320
351
|
Riftri,
|
|
321
352
|
RiftriError,
|
|
353
|
+
resolveBinary,
|
|
322
354
|
EXIT_SUCCESS,
|
|
323
355
|
EXIT_OPERATIONAL,
|
|
324
356
|
EXIT_USAGE,
|
package/lib/platform.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
|
|
6
|
+
// Kept in step with the ELF release gate and standalone installer by tests.
|
|
7
|
+
const GLIBC_FLOOR = "2.34";
|
|
8
|
+
|
|
6
9
|
const PLATFORM_PACKAGES = Object.freeze({
|
|
7
10
|
"darwin-arm64": "riftri-darwin-arm64",
|
|
8
11
|
"darwin-x64": "riftri-darwin-x64",
|
|
@@ -20,7 +23,22 @@ function detectLinuxLibc(report = process.report) {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const header = report.getReport()?.header;
|
|
23
|
-
|
|
26
|
+
const version = header?.glibcVersionRuntime;
|
|
27
|
+
if (!version) return "musl";
|
|
28
|
+
const parts = typeof version === "string" && /^(\d+)\.(\d+)(?:\.\d+)?$/.exec(version);
|
|
29
|
+
const [major, minor] = parts ? parts.slice(1, 3).map(Number) : [];
|
|
30
|
+
const [floorMajor, floorMinor] = GLIBC_FLOOR.split(".").map(Number);
|
|
31
|
+
if (!Number.isSafeInteger(major) || !Number.isSafeInteger(minor) ||
|
|
32
|
+
major < floorMajor || (major === floorMajor && minor < floorMinor)) {
|
|
33
|
+
// npm excludes musl optional dependencies on glibc hosts. Do not select
|
|
34
|
+
// an absent package or attempt to execute an incompatible GNU binary.
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Riftri's GNU npm binary requires glibc ${GLIBC_FLOOR} or newer (detected ${version}); ` +
|
|
37
|
+
"use the standalone installer at https://riftri.dev/install.sh for the static musl binary, " +
|
|
38
|
+
"then set RIFTRI_BINARY to its absolute path when using the Node SDK",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return "gnu";
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
function platformKey(
|
|
@@ -44,6 +62,9 @@ function binaryName(platform = process.platform) {
|
|
|
44
62
|
|
|
45
63
|
function assertUsableBinary(candidate, source) {
|
|
46
64
|
try {
|
|
65
|
+
// Follow executable symlinks, but reject directories and special files
|
|
66
|
+
// that can pass an access check without being runnable binaries.
|
|
67
|
+
if (!fs.statSync(candidate).isFile()) throw new Error("not a regular file");
|
|
47
68
|
fs.accessSync(
|
|
48
69
|
candidate,
|
|
49
70
|
process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK,
|
|
@@ -54,6 +75,17 @@ function assertUsableBinary(candidate, source) {
|
|
|
54
75
|
return candidate;
|
|
55
76
|
}
|
|
56
77
|
|
|
78
|
+
function unpackedElectronBinary(candidate) {
|
|
79
|
+
if (!process.versions.electron) return candidate;
|
|
80
|
+
const segments = candidate.split(path.sep);
|
|
81
|
+
const archive = segments.findLastIndex((segment) => segment.endsWith(".asar"));
|
|
82
|
+
if (archive < 0) return candidate;
|
|
83
|
+
// Electron can read an archived executable, but the OS cannot spawn it.
|
|
84
|
+
// Validate the physical file below instead of relying on Electron's virtual fs.
|
|
85
|
+
segments[archive] += ".unpacked";
|
|
86
|
+
return segments.join(path.sep);
|
|
87
|
+
}
|
|
88
|
+
|
|
57
89
|
function launcherVersion() {
|
|
58
90
|
for (const candidate of [
|
|
59
91
|
path.join(__dirname, "..", "package.json"),
|
|
@@ -120,10 +152,11 @@ function resolveBinary(options = {}) {
|
|
|
120
152
|
);
|
|
121
153
|
|
|
122
154
|
const candidate = path.join(path.dirname(packageJson), "bin", binaryName(platform));
|
|
123
|
-
return assertUsableBinary(candidate, packageName);
|
|
155
|
+
return assertUsableBinary(unpackedElectronBinary(candidate), packageName);
|
|
124
156
|
}
|
|
125
157
|
|
|
126
158
|
module.exports = {
|
|
159
|
+
GLIBC_FLOOR,
|
|
127
160
|
PLATFORM_PACKAGES,
|
|
128
161
|
assertCompatiblePackageManifest,
|
|
129
162
|
binaryName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "riftri",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Experimental lightweight Git workspaces for parallel development",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Assistant UI contributors",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"typescript": "5.9.3"
|
|
23
23
|
},
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"riftri-darwin-arm64": "0.6.
|
|
26
|
-
"riftri-darwin-x64": "0.6.
|
|
27
|
-
"riftri-linux-arm64-gnu": "0.6.
|
|
28
|
-
"riftri-linux-arm64-musl": "0.6.
|
|
29
|
-
"riftri-linux-x64-gnu": "0.6.
|
|
30
|
-
"riftri-linux-x64-musl": "0.6.
|
|
31
|
-
"riftri-win32-arm64": "0.6.
|
|
32
|
-
"riftri-win32-x64": "0.6.
|
|
25
|
+
"riftri-darwin-arm64": "0.6.3",
|
|
26
|
+
"riftri-darwin-x64": "0.6.3",
|
|
27
|
+
"riftri-linux-arm64-gnu": "0.6.3",
|
|
28
|
+
"riftri-linux-arm64-musl": "0.6.3",
|
|
29
|
+
"riftri-linux-x64-gnu": "0.6.3",
|
|
30
|
+
"riftri-linux-x64-musl": "0.6.3",
|
|
31
|
+
"riftri-win32-arm64": "0.6.3",
|
|
32
|
+
"riftri-win32-x64": "0.6.3"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|