riftri 0.3.5 → 0.4.1
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 +15 -6
- package/bin/riftri.js +6 -10
- package/lib/client.d.ts +179 -0
- package/lib/client.js +289 -0
- package/lib/platform.js +16 -1
- package/package.json +18 -9
package/README.md
CHANGED
|
@@ -67,9 +67,16 @@ The interactive `riftri setup` terminal UI is behind an off-by-default `tui`
|
|
|
67
67
|
feature; add `--features tui` to build it in (the published binaries do). The
|
|
68
68
|
default build keeps `setup` working through plain text prompts.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
Or install through npm, which fetches the binary for your platform:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npm install --global riftri
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> **Windows on ARM64 is not yet available through npm.** Its platform package
|
|
77
|
+
> is held up in registry review, so npm installs there resolve without a
|
|
78
|
+
> binary. Use the PowerShell installer above until that clears. Every other
|
|
79
|
+
> platform — macOS, Linux, and Windows x64 — installs normally.
|
|
73
80
|
|
|
74
81
|
## Quick start
|
|
75
82
|
|
|
@@ -155,9 +162,11 @@ $ sudo riftri overlayfs install-helper
|
|
|
155
162
|
```
|
|
156
163
|
|
|
157
164
|
**Riftri fails closed.** When a checkout cannot be reproduced exactly — custom
|
|
158
|
-
filters, sparse
|
|
159
|
-
[Git LFS](docs/git-lfs.md) setups
|
|
160
|
-
than silently falling back to a full
|
|
165
|
+
filters, a repository's own sparse-checkout profile, submodules, external
|
|
166
|
+
attributes, checkout hooks, or non-canonical [Git LFS](docs/git-lfs.md) setups
|
|
167
|
+
— it stops before changing anything rather than silently falling back to a full
|
|
168
|
+
copy. Sparse worktrees are supported when you ask for them explicitly, with
|
|
169
|
+
[`--sparse-dir` cone selection](docs/sparse-checkout.md). See
|
|
161
170
|
[how Riftri stays safe](docs/safety.md).
|
|
162
171
|
|
|
163
172
|
## Common commands
|
package/bin/riftri.js
CHANGED
|
@@ -50,22 +50,18 @@ if (process.platform !== "win32") {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
}
|
|
53
|
+
// The handlers deliberately stay installed until the process exits. Removing
|
|
54
|
+
// the last listener for a signal restores its default disposition, so a SIGINT
|
|
55
|
+
// still in flight — the common case, since the terminal sent it to the whole
|
|
56
|
+
// foreground group — would kill the launcher in the window before
|
|
57
|
+
// `process.exit`, losing the command's real status. `process.exit` does not
|
|
58
|
+
// need them removed.
|
|
61
59
|
|
|
62
60
|
child.on("error", (error) => {
|
|
63
|
-
releaseSignals();
|
|
64
61
|
fail(`could not start the native executable at ${binary}: ${error.message}`);
|
|
65
62
|
});
|
|
66
63
|
|
|
67
64
|
child.on("exit", (code, signal) => {
|
|
68
|
-
releaseSignals();
|
|
69
65
|
if (signal !== null) {
|
|
70
66
|
const exitCode = signalExitCode(signal);
|
|
71
67
|
if (exitCode === null) {
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/** Programmatic API for the Riftri CLI. */
|
|
2
|
+
|
|
3
|
+
export const EXIT_SUCCESS: 0;
|
|
4
|
+
export const EXIT_OPERATIONAL: 1;
|
|
5
|
+
export const EXIT_USAGE: 2;
|
|
6
|
+
export const EXIT_POLICY: 3;
|
|
7
|
+
|
|
8
|
+
/** One versioned failure receipt, as emitted by `--json-errors`. */
|
|
9
|
+
export interface FailureReceipt {
|
|
10
|
+
schemaVersion: number;
|
|
11
|
+
outcome: "failed";
|
|
12
|
+
operation: string;
|
|
13
|
+
code: string;
|
|
14
|
+
category: "policy" | "operational";
|
|
15
|
+
message: string;
|
|
16
|
+
phase: string | null;
|
|
17
|
+
cleanup: string;
|
|
18
|
+
recovery: "required" | "not-required" | "retry" | "inspect" | "unknown";
|
|
19
|
+
nextCommand: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class RiftriError extends Error {
|
|
23
|
+
/**
|
|
24
|
+
* Always a number. A process killed by a signal reports 128 plus the signal
|
|
25
|
+
* number, the convention native `riftri exec` uses for its own children.
|
|
26
|
+
*/
|
|
27
|
+
readonly exitCode: number;
|
|
28
|
+
/** The signal that killed the process, or null if it exited on its own. */
|
|
29
|
+
readonly signal: NodeJS.Signals | null;
|
|
30
|
+
/** The process was killed rather than exiting on its own. */
|
|
31
|
+
readonly wasSignalled: boolean;
|
|
32
|
+
readonly receipt: FailureReceipt | null;
|
|
33
|
+
/** Riftri declined before touching anything; falling back is safe. */
|
|
34
|
+
readonly isPolicyRefusal: boolean;
|
|
35
|
+
/** The request was malformed; never retry it unchanged. */
|
|
36
|
+
readonly isUsageError: boolean;
|
|
37
|
+
/** A live process holds the lock; waiting and retrying is correct. */
|
|
38
|
+
readonly isBusy: boolean;
|
|
39
|
+
/** Run `receipt.nextCommand` before continuing. */
|
|
40
|
+
readonly needsRepair: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DoctorReport {
|
|
44
|
+
project_stage: string;
|
|
45
|
+
operating_system: string;
|
|
46
|
+
architecture: string;
|
|
47
|
+
/** Whether a usable copy-on-write backend exists here. */
|
|
48
|
+
cow_backend_active: boolean;
|
|
49
|
+
/** Whether this repository has opted in with `enable()`. */
|
|
50
|
+
repository_enabled: boolean;
|
|
51
|
+
git_shim_active: boolean;
|
|
52
|
+
destination_readiness: {
|
|
53
|
+
destination: string;
|
|
54
|
+
status: string;
|
|
55
|
+
backend: string | null;
|
|
56
|
+
copy_on_write: boolean;
|
|
57
|
+
};
|
|
58
|
+
storage_capabilities: unknown[];
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface AddReport {
|
|
63
|
+
schema_version: number;
|
|
64
|
+
backend: string;
|
|
65
|
+
destination: string;
|
|
66
|
+
destination_native_hex: string;
|
|
67
|
+
commit: string;
|
|
68
|
+
tree: string;
|
|
69
|
+
base_path: string;
|
|
70
|
+
/** False when this add materialized a new immutable base. */
|
|
71
|
+
reused_base: boolean;
|
|
72
|
+
journal_path: string;
|
|
73
|
+
native_path_encoding: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ManagedWorktree {
|
|
77
|
+
path: string;
|
|
78
|
+
path_native_hex: string;
|
|
79
|
+
repository: string;
|
|
80
|
+
head: string;
|
|
81
|
+
branch: string | null;
|
|
82
|
+
detached: boolean;
|
|
83
|
+
backend: string;
|
|
84
|
+
base_path: string;
|
|
85
|
+
/** Filesystem-accounted bytes this worktree actually occupies. */
|
|
86
|
+
allocated_bytes: number;
|
|
87
|
+
/** Bytes the same content would occupy without sharing. */
|
|
88
|
+
logical_bytes: number;
|
|
89
|
+
locked_reason: string | null;
|
|
90
|
+
prunable_reason: string | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface WorktreeInventory {
|
|
94
|
+
schema_version: number;
|
|
95
|
+
state_directory: string;
|
|
96
|
+
worktrees: ManagedWorktree[];
|
|
97
|
+
native_path_encoding: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface StatusReport {
|
|
101
|
+
schema_version: number;
|
|
102
|
+
state_directory: string;
|
|
103
|
+
bases: unknown[];
|
|
104
|
+
worktrees: unknown[];
|
|
105
|
+
operations: unknown[];
|
|
106
|
+
diagnostic_issues: unknown[];
|
|
107
|
+
total_allocated_bytes: number;
|
|
108
|
+
total_logical_bytes: number;
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RepairReport {
|
|
113
|
+
schema_version: number;
|
|
114
|
+
scanned: number;
|
|
115
|
+
active: number;
|
|
116
|
+
errors: unknown[];
|
|
117
|
+
[key: string]: unknown;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface AddOptions {
|
|
121
|
+
/** Create and check out a new branch. */
|
|
122
|
+
branch?: string;
|
|
123
|
+
/** Create a detached worktree instead of a branch. */
|
|
124
|
+
detach?: boolean;
|
|
125
|
+
/** Commit-ish to start from; defaults to HEAD. */
|
|
126
|
+
revision?: string;
|
|
127
|
+
/** Cone-mode sparse directories to include. */
|
|
128
|
+
sparseDir?: string[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface RiftriOptions {
|
|
132
|
+
/** Working directory for every command. Defaults to `process.cwd()`. */
|
|
133
|
+
repository?: string;
|
|
134
|
+
/** Explicit executable; defaults to the installed platform binary. */
|
|
135
|
+
binary?: string;
|
|
136
|
+
/** Passed as `--state-dir` where the command supports it. */
|
|
137
|
+
stateDir?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export class Riftri {
|
|
141
|
+
constructor(options?: RiftriOptions);
|
|
142
|
+
repository: string;
|
|
143
|
+
|
|
144
|
+
/** Inspect Git and storage. Creates nothing. */
|
|
145
|
+
doctor(options?: { destination?: string }): Promise<DoctorReport>;
|
|
146
|
+
backends(path?: string): Promise<unknown>;
|
|
147
|
+
/**
|
|
148
|
+
* True when this destination can be optimized.
|
|
149
|
+
*
|
|
150
|
+
* Resolves `false` only when Riftri answers the question: no active
|
|
151
|
+
* copy-on-write backend, a destination Riftri reports as `blocked`, or an
|
|
152
|
+
* outright refusal. A missing or non-executable binary, an unreadable
|
|
153
|
+
* repository, or output that is not JSON rejects instead.
|
|
154
|
+
*
|
|
155
|
+
* A `needs-activation` destination is optimizable: `worktree.add` works
|
|
156
|
+
* without `enable()`, which only gates Git interception.
|
|
157
|
+
*/
|
|
158
|
+
isOptimizable(destination?: string): Promise<boolean>;
|
|
159
|
+
|
|
160
|
+
enable(): Promise<null>;
|
|
161
|
+
disable(): Promise<null>;
|
|
162
|
+
|
|
163
|
+
status(): Promise<StatusReport>;
|
|
164
|
+
repair(): Promise<RepairReport>;
|
|
165
|
+
gc(options?: { apply?: boolean }): Promise<unknown>;
|
|
166
|
+
|
|
167
|
+
/** Escape hatch: run any command and parse its report. */
|
|
168
|
+
run(args: string[], options?: { json?: boolean; cwd?: string }): Promise<unknown>;
|
|
169
|
+
|
|
170
|
+
readonly worktree: {
|
|
171
|
+
add(destination: string, options?: AddOptions): Promise<AddReport>;
|
|
172
|
+
list(options?: { allStates?: boolean }): Promise<WorktreeInventory>;
|
|
173
|
+
/** Refuses a dirty worktree unless `force` is set. */
|
|
174
|
+
remove(destination: string, options?: { force?: boolean }): Promise<unknown>;
|
|
175
|
+
move(source: string, destination: string): Promise<unknown>;
|
|
176
|
+
compact(destination: string): Promise<unknown>;
|
|
177
|
+
prune(): Promise<unknown>;
|
|
178
|
+
};
|
|
179
|
+
}
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Programmatic API for the Riftri CLI.
|
|
4
|
+
//
|
|
5
|
+
// The binary is the implementation; this is a typed, promise-returning wrapper
|
|
6
|
+
// around it. Every method runs one command, returns its parsed `--json` report,
|
|
7
|
+
// and throws a RiftriError carrying the parsed `--json-errors` receipt on
|
|
8
|
+
// failure. Nothing here reimplements Riftri behaviour.
|
|
9
|
+
|
|
10
|
+
const { spawn } = require("node:child_process");
|
|
11
|
+
const { resolveBinary } = require("./platform.js");
|
|
12
|
+
const { signalExitCode } = require("./signals.js");
|
|
13
|
+
|
|
14
|
+
/** Process exit codes. Documented in docs/cli.md. */
|
|
15
|
+
const EXIT_SUCCESS = 0;
|
|
16
|
+
const EXIT_OPERATIONAL = 1;
|
|
17
|
+
const EXIT_USAGE = 2;
|
|
18
|
+
const EXIT_POLICY = 3;
|
|
19
|
+
|
|
20
|
+
/** Commands that accept `--json`. The rest print human text on success. */
|
|
21
|
+
const REPORTING = new Set([
|
|
22
|
+
"doctor",
|
|
23
|
+
"backends",
|
|
24
|
+
"status",
|
|
25
|
+
"repair",
|
|
26
|
+
"gc",
|
|
27
|
+
"worktree",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
class RiftriError extends Error {
|
|
31
|
+
constructor(exitCode, receipt, stderr, signal = null) {
|
|
32
|
+
super(
|
|
33
|
+
receipt?.message ||
|
|
34
|
+
stderr.trim() ||
|
|
35
|
+
(signal ? `riftri terminated by ${signal}` : `riftri exited ${exitCode}`),
|
|
36
|
+
);
|
|
37
|
+
this.name = "RiftriError";
|
|
38
|
+
// Always a number. A signalled process reports 128 + the signal number,
|
|
39
|
+
// the convention native `riftri exec` already uses for its own children.
|
|
40
|
+
this.exitCode = exitCode;
|
|
41
|
+
this.signal = signal;
|
|
42
|
+
this.receipt = receipt ?? null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The process was killed rather than exiting on its own. */
|
|
46
|
+
get wasSignalled() {
|
|
47
|
+
return this.signal !== null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Riftri declined before touching anything. Falling back is safe. */
|
|
51
|
+
get isPolicyRefusal() {
|
|
52
|
+
return this.exitCode === EXIT_POLICY;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The request itself was malformed. Never retry unchanged. */
|
|
56
|
+
get isUsageError() {
|
|
57
|
+
return this.exitCode === EXIT_USAGE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A live process holds the lock; unlike a refusal, waiting helps. */
|
|
61
|
+
get isBusy() {
|
|
62
|
+
return this.receipt?.code === "worktree-busy";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Run `nextCommand` (usually `riftri repair`) before continuing. */
|
|
66
|
+
get needsRepair() {
|
|
67
|
+
return this.receipt?.recovery === "required";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Place Riftri's own flags before any `--`. Appending them instead puts them
|
|
73
|
+
* inside the payload `riftri exec` hands to the child, which then fails on a
|
|
74
|
+
* flag meant for Riftri.
|
|
75
|
+
*/
|
|
76
|
+
function withRiftriFlags(args, flags) {
|
|
77
|
+
if (flags.length === 0) return [...args];
|
|
78
|
+
const boundary = args.indexOf("--");
|
|
79
|
+
if (boundary === -1) return [...args, ...flags];
|
|
80
|
+
return [...args.slice(0, boundary), ...flags, ...args.slice(boundary)];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function flagsFor(options) {
|
|
84
|
+
const flags = [];
|
|
85
|
+
for (const [key, value] of Object.entries(options)) {
|
|
86
|
+
if (value === undefined || value === null || value === false) continue;
|
|
87
|
+
const flag = `--${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`;
|
|
88
|
+
if (value === true) flags.push(flag);
|
|
89
|
+
else if (Array.isArray(value)) for (const item of value) flags.push(flag, String(item));
|
|
90
|
+
else flags.push(flag, String(value));
|
|
91
|
+
}
|
|
92
|
+
return flags;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class Riftri {
|
|
96
|
+
/**
|
|
97
|
+
* @param {object} [options]
|
|
98
|
+
* @param {string} [options.repository] working directory for every command
|
|
99
|
+
* @param {string} [options.binary] explicit riftri executable
|
|
100
|
+
* @param {string} [options.stateDir] passed as --state-dir where supported
|
|
101
|
+
*/
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.repository = options.repository ?? process.cwd();
|
|
104
|
+
this.binary = options.binary ?? null;
|
|
105
|
+
this.stateDir = options.stateDir ?? null;
|
|
106
|
+
this.worktree = {
|
|
107
|
+
add: this.#worktreeAdd.bind(this),
|
|
108
|
+
list: this.#worktreeList.bind(this),
|
|
109
|
+
remove: this.#worktreeRemove.bind(this),
|
|
110
|
+
move: this.#worktreeMove.bind(this),
|
|
111
|
+
compact: this.#worktreeCompact.bind(this),
|
|
112
|
+
prune: this.#worktreePrune.bind(this),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Resolve the executable: explicit, else the installed platform package. */
|
|
117
|
+
#executable() {
|
|
118
|
+
if (this.binary) return this.binary;
|
|
119
|
+
this.binary = resolveBinary();
|
|
120
|
+
return this.binary;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run one command and resolve with its parsed report, or null when the
|
|
125
|
+
* command does not support `--json`.
|
|
126
|
+
*/
|
|
127
|
+
run(args, { json = REPORTING.has(args[0]), cwd = this.repository } = {}) {
|
|
128
|
+
const flags = json ? ["--json", "--json-errors"] : ["--json-errors"];
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
let child;
|
|
131
|
+
try {
|
|
132
|
+
child = spawn(this.#executable(), withRiftriFlags(args, flags), {
|
|
133
|
+
cwd,
|
|
134
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
reject(error); // no installed binary for this platform
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
let stdout = "";
|
|
141
|
+
let stderr = "";
|
|
142
|
+
child.stdout.on("data", (chunk) => (stdout += chunk));
|
|
143
|
+
child.stderr.on("data", (chunk) => (stderr += chunk));
|
|
144
|
+
child.on("error", reject);
|
|
145
|
+
child.on("close", (exitCode, signal) => {
|
|
146
|
+
// A signalled child reports a null exit code. Translate it rather than
|
|
147
|
+
// letting `null` reach callers through a field typed as a number.
|
|
148
|
+
if (signal) {
|
|
149
|
+
const code = signalExitCode(signal) ?? EXIT_OPERATIONAL;
|
|
150
|
+
reject(new RiftriError(code, null, stderr, signal));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (exitCode === EXIT_SUCCESS) {
|
|
154
|
+
if (!json || !stdout.trim()) {
|
|
155
|
+
resolve(null);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
resolve(JSON.parse(stdout));
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// Thrown from a close handler this would be uncatchable and would
|
|
162
|
+
// take the host process down, so it has to reject instead.
|
|
163
|
+
reject(
|
|
164
|
+
new RiftriError(
|
|
165
|
+
EXIT_OPERATIONAL,
|
|
166
|
+
null,
|
|
167
|
+
`riftri ${args.join(" ")} returned output that is not JSON: ` +
|
|
168
|
+
`${error.message}\n${stdout.slice(0, 2000)}`,
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
let receipt = null;
|
|
175
|
+
try {
|
|
176
|
+
receipt = JSON.parse(stderr);
|
|
177
|
+
} catch {
|
|
178
|
+
// Usage errors come from the argument parser and carry no receipt.
|
|
179
|
+
}
|
|
180
|
+
reject(new RiftriError(exitCode, receipt, stderr));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#stateFlag() {
|
|
186
|
+
return this.stateDir ? ["--state-dir", this.stateDir] : [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Inspect Git and storage without creating anything. */
|
|
190
|
+
doctor(options = {}) {
|
|
191
|
+
return this.run(["doctor", ...flagsFor(options)]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Probe storage backends for a destination volume. */
|
|
195
|
+
backends(pathOrOptions = {}) {
|
|
196
|
+
const target = typeof pathOrOptions === "string" ? [pathOrOptions] : [];
|
|
197
|
+
return this.run(["backends", ...target]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* True when this destination can get an optimized worktree.
|
|
202
|
+
*
|
|
203
|
+
* `false` is an answer from Riftri: either the report says no backend is
|
|
204
|
+
* active, or Riftri refused this destination outright. A broken
|
|
205
|
+
* installation, an unreadable repository, or any other unexpected failure
|
|
206
|
+
* rejects instead, so a caller can tell "not supported here" apart from
|
|
207
|
+
* "this client cannot run at all".
|
|
208
|
+
*/
|
|
209
|
+
async isOptimizable(destination) {
|
|
210
|
+
try {
|
|
211
|
+
const report = await this.doctor(destination ? { destination } : {});
|
|
212
|
+
if (!report?.cow_backend_active) return false;
|
|
213
|
+
// cow_backend_active is a fact about the volume, not about this
|
|
214
|
+
// destination. Outside a Git repository it is still true while
|
|
215
|
+
// `worktree add` cannot possibly succeed, so the readiness verdict
|
|
216
|
+
// decides. `needs-activation` stays optimizable: the explicit interface
|
|
217
|
+
// works without `enable`, which only gates Git interception.
|
|
218
|
+
const readiness = report.destination_readiness?.status;
|
|
219
|
+
return readiness === undefined || readiness !== "blocked";
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error instanceof RiftriError && error.isPolicyRefusal) return false;
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Opt one repository in. Required before interception applies. */
|
|
227
|
+
enable() {
|
|
228
|
+
return this.run(["enable"], { json: false });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
disable() {
|
|
232
|
+
return this.run(["disable"], { json: false });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Bases, reference counts, storage use, and pending operations. */
|
|
236
|
+
status() {
|
|
237
|
+
return this.run(["status", ...this.#stateFlag()]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Resume or roll back interrupted operations. Safe to run repeatedly. */
|
|
241
|
+
repair() {
|
|
242
|
+
return this.run(["repair", ...this.#stateFlag()]);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Plan base collection; pass `{ apply: true }` to delete. */
|
|
246
|
+
gc({ apply = false } = {}) {
|
|
247
|
+
const args = ["gc", ...this.#stateFlag()];
|
|
248
|
+
if (apply) args.push("--apply", "--yes");
|
|
249
|
+
return this.run(args);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#worktreeAdd(destination, options = {}) {
|
|
253
|
+
const { revision, ...rest } = options;
|
|
254
|
+
const args = ["worktree", "add", destination];
|
|
255
|
+
if (revision) args.push(revision);
|
|
256
|
+
return this.run([...args, ...flagsFor(rest), ...this.#stateFlag()]);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#worktreeList(options = {}) {
|
|
260
|
+
return this.run(["worktree", "list", ...flagsFor(options), ...this.#stateFlag()]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#worktreeRemove(destination, { force = false } = {}) {
|
|
264
|
+
const args = ["worktree", "remove", destination];
|
|
265
|
+
if (force) args.push("--force", "--yes");
|
|
266
|
+
return this.run([...args, ...this.#stateFlag()]);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#worktreeMove(source, destination) {
|
|
270
|
+
return this.run(["worktree", "move", source, destination, ...this.#stateFlag()]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#worktreeCompact(destination) {
|
|
274
|
+
return this.run(["worktree", "compact", destination, ...this.#stateFlag()]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#worktreePrune() {
|
|
278
|
+
return this.run(["worktree", "prune", ...this.#stateFlag()]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
Riftri,
|
|
284
|
+
RiftriError,
|
|
285
|
+
EXIT_SUCCESS,
|
|
286
|
+
EXIT_OPERATIONAL,
|
|
287
|
+
EXIT_USAGE,
|
|
288
|
+
EXIT_POLICY,
|
|
289
|
+
};
|
package/lib/platform.js
CHANGED
|
@@ -14,6 +14,15 @@ const PLATFORM_PACKAGES = Object.freeze({
|
|
|
14
14
|
"win32-x64": "riftri-win32-x64",
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
// Native packages the registry has refused, mapped to the route that does work.
|
|
18
|
+
// A name only belongs here while it is genuinely absent from npm; remove the
|
|
19
|
+
// entry in the release that first publishes it.
|
|
20
|
+
const UNPUBLISHED_PACKAGES = Object.freeze({
|
|
21
|
+
"riftri-win32-arm64":
|
|
22
|
+
"install it with the PowerShell installer instead: " +
|
|
23
|
+
"https://riftri.dev/docs/installation",
|
|
24
|
+
});
|
|
25
|
+
|
|
17
26
|
function detectLinuxLibc(report = process.report) {
|
|
18
27
|
if (!report || typeof report.getReport !== "function") {
|
|
19
28
|
return "unknown";
|
|
@@ -102,8 +111,13 @@ function resolveBinary(options = {}) {
|
|
|
102
111
|
try {
|
|
103
112
|
packageJson = require.resolve(`${packageName}/package.json`);
|
|
104
113
|
} catch {
|
|
114
|
+
// Reinstalling cannot help when the package was never published, so say so
|
|
115
|
+
// instead of sending the user around the same loop.
|
|
116
|
+
const unpublished = UNPUBLISHED_PACKAGES[packageName];
|
|
105
117
|
throw new Error(
|
|
106
|
-
|
|
118
|
+
unpublished
|
|
119
|
+
? `${packageName} is not published to npm, so riftri cannot run on this platform through npm; ${unpublished}`
|
|
120
|
+
: `the optional native package ${packageName} is missing; reinstall without --omit=optional, or build Riftri from source`,
|
|
107
121
|
);
|
|
108
122
|
}
|
|
109
123
|
|
|
@@ -125,6 +139,7 @@ function resolveBinary(options = {}) {
|
|
|
125
139
|
|
|
126
140
|
module.exports = {
|
|
127
141
|
PLATFORM_PACKAGES,
|
|
142
|
+
UNPUBLISHED_PACKAGES,
|
|
128
143
|
assertCompatiblePackageManifest,
|
|
129
144
|
binaryName,
|
|
130
145
|
detectLinuxLibc,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "riftri",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Experimental lightweight Git workspaces for parallel development",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Assistant UI contributors",
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"node": ">=18.18"
|
|
19
19
|
},
|
|
20
20
|
"optionalDependencies": {
|
|
21
|
-
"riftri-darwin-arm64": "0.
|
|
22
|
-
"riftri-darwin-x64": "0.
|
|
23
|
-
"riftri-linux-arm64-gnu": "0.
|
|
24
|
-
"riftri-linux-arm64-musl": "0.
|
|
25
|
-
"riftri-linux-x64-gnu": "0.
|
|
26
|
-
"riftri-linux-x64-musl": "0.
|
|
27
|
-
"riftri-win32-arm64": "0.
|
|
28
|
-
"riftri-win32-x64": "0.
|
|
21
|
+
"riftri-darwin-arm64": "0.4.1",
|
|
22
|
+
"riftri-darwin-x64": "0.4.1",
|
|
23
|
+
"riftri-linux-arm64-gnu": "0.4.1",
|
|
24
|
+
"riftri-linux-arm64-musl": "0.4.1",
|
|
25
|
+
"riftri-linux-x64-gnu": "0.4.1",
|
|
26
|
+
"riftri-linux-x64-musl": "0.4.1",
|
|
27
|
+
"riftri-win32-arm64": "0.4.1",
|
|
28
|
+
"riftri-win32-x64": "0.4.1"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
@@ -45,5 +45,14 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public",
|
|
47
47
|
"provenance": true
|
|
48
|
+
},
|
|
49
|
+
"main": "lib/client.js",
|
|
50
|
+
"types": "lib/client.d.ts",
|
|
51
|
+
"exports": {
|
|
52
|
+
".": {
|
|
53
|
+
"types": "./lib/client.d.ts",
|
|
54
|
+
"require": "./lib/client.js",
|
|
55
|
+
"default": "./lib/client.js"
|
|
56
|
+
}
|
|
48
57
|
}
|
|
49
58
|
}
|