@smithers-orchestrator/vcs 0.24.2 → 0.25.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/package.json +7 -7
- package/src/find-root.js +11 -14
- package/src/index.d.ts +37 -5
- package/src/jj.js +37 -22
- package/src/resolveJjBinary.js +11 -6
- package/src/vcsToolingStatus.js +1 -1
- package/src/WorkspaceSnapshot.ts +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/vcs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "VCS discovery and jj workspace operations for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@effect/platform": "^0.96.0",
|
|
24
24
|
"effect": "^3.21.1",
|
|
25
|
-
"@smithers-orchestrator/observability": "0.
|
|
25
|
+
"@smithers-orchestrator/observability": "0.25.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@effect/platform-bun": "^0.89.0",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"typescript": "~5.9.3"
|
|
31
31
|
},
|
|
32
32
|
"optionalDependencies": {
|
|
33
|
-
"@smithers-orchestrator/jj-darwin-arm64": "0.
|
|
34
|
-
"@smithers-orchestrator/jj-darwin-x64": "0.
|
|
35
|
-
"@smithers-orchestrator/jj-linux-arm64": "0.
|
|
36
|
-
"@smithers-orchestrator/jj-linux-x64": "0.
|
|
37
|
-
"@smithers-orchestrator/jj-win32-x64": "0.
|
|
33
|
+
"@smithers-orchestrator/jj-darwin-arm64": "0.25.1",
|
|
34
|
+
"@smithers-orchestrator/jj-darwin-x64": "0.25.1",
|
|
35
|
+
"@smithers-orchestrator/jj-linux-arm64": "0.25.1",
|
|
36
|
+
"@smithers-orchestrator/jj-linux-x64": "0.25.1",
|
|
37
|
+
"@smithers-orchestrator/jj-win32-x64": "0.25.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "rm -f src/index.d.ts && tsup --dts-only",
|
package/src/find-root.js
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve, dirname, parse } from "node:path";
|
|
3
|
-
import { Effect } from "effect";
|
|
4
3
|
/**
|
|
5
4
|
* Walk up from `startDir` to find the nearest directory containing `.jj` or `.git`.
|
|
6
5
|
* Prefers `.jj` over `.git` so colocated repos (both exist) use jj semantics.
|
|
7
6
|
* Returns the VCS type and root path, or null if neither is found.
|
|
8
7
|
*
|
|
9
8
|
* @param {string} startDir
|
|
10
|
-
* @returns {
|
|
9
|
+
* @returns {{ type: "jj"; root: string } | { type: "git"; root: string } | null}
|
|
11
10
|
*/
|
|
12
11
|
export function findVcsRoot(startDir) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
while (dir !== fsRoot) {
|
|
17
|
-
if (existsSync(resolve(dir, ".jj")))
|
|
18
|
-
return /** @type {{ type: "jj"; root: string }} */ ({ type: "jj", root: dir });
|
|
19
|
-
if (existsSync(resolve(dir, ".git")))
|
|
20
|
-
return /** @type {{ type: "git"; root: string }} */ ({ type: "git", root: dir });
|
|
21
|
-
dir = dirname(dir);
|
|
22
|
-
}
|
|
12
|
+
let dir = resolve(startDir);
|
|
13
|
+
const { root: fsRoot } = parse(dir);
|
|
14
|
+
while (dir !== fsRoot) {
|
|
23
15
|
if (existsSync(resolve(dir, ".jj")))
|
|
24
16
|
return /** @type {{ type: "jj"; root: string }} */ ({ type: "jj", root: dir });
|
|
25
17
|
if (existsSync(resolve(dir, ".git")))
|
|
26
18
|
return /** @type {{ type: "git"; root: string }} */ ({ type: "git", root: dir });
|
|
27
|
-
|
|
28
|
-
}
|
|
19
|
+
dir = dirname(dir);
|
|
20
|
+
}
|
|
21
|
+
if (existsSync(resolve(dir, ".jj")))
|
|
22
|
+
return /** @type {{ type: "jj"; root: string }} */ ({ type: "jj", root: dir });
|
|
23
|
+
if (existsSync(resolve(dir, ".git")))
|
|
24
|
+
return /** @type {{ type: "git"; root: string }} */ ({ type: "git", root: dir });
|
|
25
|
+
return null;
|
|
29
26
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Effect } from 'effect';
|
|
2
1
|
import * as _effect_platform_CommandExecutor from '@effect/platform/CommandExecutor';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Walk up from `startDir` to find the nearest directory containing `.jj` or `.git`.
|
|
@@ -7,15 +8,15 @@ import * as _effect_platform_CommandExecutor from '@effect/platform/CommandExecu
|
|
|
7
8
|
* Returns the VCS type and root path, or null if neither is found.
|
|
8
9
|
*
|
|
9
10
|
* @param {string} startDir
|
|
10
|
-
* @returns {
|
|
11
|
+
* @returns {{ type: "jj"; root: string } | { type: "git"; root: string } | null}
|
|
11
12
|
*/
|
|
12
|
-
declare function findVcsRoot(startDir: string):
|
|
13
|
+
declare function findVcsRoot(startDir: string): {
|
|
13
14
|
type: "jj";
|
|
14
15
|
root: string;
|
|
15
16
|
} | {
|
|
16
17
|
type: "git";
|
|
17
18
|
root: string;
|
|
18
|
-
} | null
|
|
19
|
+
} | null;
|
|
19
20
|
|
|
20
21
|
type WorkspaceResult$1 = {
|
|
21
22
|
success: boolean;
|
|
@@ -65,6 +66,15 @@ declare function runJj(args: string[], opts?: RunJjOptions): Effect.Effect<RunJj
|
|
|
65
66
|
* @returns {Effect.Effect<string | null, never, import("@effect/platform/CommandExecutor").CommandExecutor>}
|
|
66
67
|
*/
|
|
67
68
|
declare function getJjPointer(cwd?: string): Effect.Effect<string | null, never, _effect_platform_CommandExecutor.CommandExecutor>;
|
|
69
|
+
/**
|
|
70
|
+
* Parse the snapshot values returned by the two jj commands in
|
|
71
|
+
* {@link captureWorkspaceSnapshot}.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} logStdout stdout from `jj log -r @ ...`
|
|
74
|
+
* @param {string} opStdout stdout from `jj operation log ...`
|
|
75
|
+
* @returns {WorkspaceSnapshot | null}
|
|
76
|
+
*/
|
|
77
|
+
declare function parseWorkspaceSnapshot(logStdout: string, opStdout: string): WorkspaceSnapshot | null;
|
|
68
78
|
/**
|
|
69
79
|
* Capture the current working-copy state as a restorable handle.
|
|
70
80
|
*
|
|
@@ -169,6 +179,20 @@ type ResolvedBinary = {
|
|
|
169
179
|
*/
|
|
170
180
|
declare function resolveGitBinary(): ResolvedBinary;
|
|
171
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Locate the bundled `jj` binary for the current host, or null when no platform
|
|
184
|
+
* package is installed (unsupported target, `--no-optional` install, or not yet
|
|
185
|
+
* published). Resolution goes through the package's `package.json` so it works
|
|
186
|
+
* regardless of hoisting layout.
|
|
187
|
+
*
|
|
188
|
+
* @returns {string | null}
|
|
189
|
+
*/
|
|
190
|
+
declare function resolveBundledJjPath({ platform, arch, resolvePackage, fileExists, }?: {
|
|
191
|
+
platform?: NodeJS.Platform | undefined;
|
|
192
|
+
arch?: NodeJS.Architecture | undefined;
|
|
193
|
+
resolvePackage?: NodeJS.RequireResolve | undefined;
|
|
194
|
+
fileExists?: typeof existsSync | undefined;
|
|
195
|
+
}): string | null;
|
|
172
196
|
/**
|
|
173
197
|
* Resolve the `jj` executable Smithers should spawn.
|
|
174
198
|
*
|
|
@@ -185,6 +209,14 @@ declare function resolveGitBinary(): ResolvedBinary;
|
|
|
185
209
|
*/
|
|
186
210
|
declare function resolveJjBinary(): ResolvedBinary;
|
|
187
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Whether `<bin> --version` exits 0. Best-effort: a missing binary, a non-zero
|
|
214
|
+
* exit, or a spawn error all read as "not usable".
|
|
215
|
+
*
|
|
216
|
+
* @param {import("./ResolvedBinary.js").ResolvedBinary} bin
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
*/
|
|
219
|
+
declare function runsVersion(bin: ResolvedBinary): boolean;
|
|
188
220
|
/**
|
|
189
221
|
* Probe whether a usable `jj` and/or `git` exists for the current host, using
|
|
190
222
|
* the override → bundled → PATH resolution for jj and override → PATH for git.
|
|
@@ -216,4 +248,4 @@ type VcsToolingStatus = {
|
|
|
216
248
|
ok: boolean;
|
|
217
249
|
};
|
|
218
250
|
|
|
219
|
-
export { type JjRevertResult, type RunJjOptions, type RunJjResult, type VcsToolingStatus, type WorkspaceAddOptions, type WorkspaceInfo, type WorkspaceResult, type WorkspaceSnapshot, captureWorkspaceSnapshot, findVcsRoot, getJjPointer, isJjRepo, resolveGitBinary, resolveJjBinary, revertToJjPointer, runJj, vcsToolingStatus, workspaceAdd, workspaceClose, workspaceList };
|
|
251
|
+
export { type JjRevertResult, type RunJjOptions, type RunJjResult, type VcsToolingStatus, type WorkspaceAddOptions, type WorkspaceInfo, type WorkspaceResult, type WorkspaceSnapshot, captureWorkspaceSnapshot, findVcsRoot, getJjPointer, isJjRepo, parseWorkspaceSnapshot, resolveBundledJjPath, resolveGitBinary, resolveJjBinary, revertToJjPointer, runJj, runsVersion, vcsToolingStatus, workspaceAdd, workspaceClose, workspaceList };
|
package/src/jj.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// @smithers-type-exports-end
|
|
15
15
|
|
|
16
16
|
import * as Command from "@effect/platform/Command";
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as nodePath from "node:path";
|
|
17
19
|
import { Duration, Effect, Fiber, Metric, Stream } from "effect";
|
|
18
20
|
import { vcsDuration } from "@smithers-orchestrator/observability/metrics";
|
|
19
21
|
import { resolveJjBinary } from "./resolveJjBinary.js";
|
|
@@ -43,19 +45,20 @@ export function runJj(args, opts = {}) {
|
|
|
43
45
|
}
|
|
44
46
|
return Effect.scoped(Effect.gen(function* () {
|
|
45
47
|
const start = performance.now();
|
|
46
|
-
yield* Effect.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
return yield* Effect.gen(function* () {
|
|
49
|
+
yield* Effect.logDebug(`jj ${args.join(" ")}`);
|
|
50
|
+
const process = yield* Command.start(command);
|
|
51
|
+
const stdoutFiber = yield* Effect.fork(collectUtf8(process.stdout));
|
|
52
|
+
const stderrFiber = yield* Effect.fork(collectUtf8(process.stderr));
|
|
53
|
+
const exitCode = yield* process.exitCode;
|
|
54
|
+
const stdout = yield* Fiber.join(stdoutFiber);
|
|
55
|
+
const stderr = yield* Fiber.join(stderrFiber);
|
|
56
|
+
return {
|
|
57
|
+
code: Number(exitCode),
|
|
58
|
+
stdout,
|
|
59
|
+
stderr,
|
|
60
|
+
};
|
|
61
|
+
}).pipe(Effect.ensuring(Effect.suspend(() => Metric.update(vcsDuration, performance.now() - start))));
|
|
59
62
|
})).pipe(Effect.annotateLogs({
|
|
60
63
|
vcs: "jj",
|
|
61
64
|
cwd: opts.cwd ?? "",
|
|
@@ -116,6 +119,26 @@ function withSnapshotTimeout(effect, label) {
|
|
|
116
119
|
}),
|
|
117
120
|
}));
|
|
118
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Parse the snapshot values returned by the two jj commands in
|
|
124
|
+
* {@link captureWorkspaceSnapshot}.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} logStdout stdout from `jj log -r @ ...`
|
|
127
|
+
* @param {string} opStdout stdout from `jj operation log ...`
|
|
128
|
+
* @returns {WorkspaceSnapshot | null}
|
|
129
|
+
*/
|
|
130
|
+
export function parseWorkspaceSnapshot(logStdout, opStdout) {
|
|
131
|
+
const [commitId, changeId] = logStdout.split("\n").map((part) => part.trim());
|
|
132
|
+
if (!commitId)
|
|
133
|
+
return null;
|
|
134
|
+
const operationId = opStdout
|
|
135
|
+
.split(/\r?\n/)
|
|
136
|
+
.map((part) => part.trim())
|
|
137
|
+
.find(Boolean);
|
|
138
|
+
if (!operationId)
|
|
139
|
+
return null;
|
|
140
|
+
return { commitId, changeId: changeId ?? "", operationId };
|
|
141
|
+
}
|
|
119
142
|
/**
|
|
120
143
|
* Capture the current working-copy state as a restorable handle.
|
|
121
144
|
*
|
|
@@ -133,16 +156,10 @@ export function captureWorkspaceSnapshot(cwd) {
|
|
|
133
156
|
const logRes = yield* withSnapshotTimeout(runJj(["log", "-r", "@", "--no-graph", "-T", 'commit_id ++ "\\n" ++ change_id'], { cwd }), "jj snapshot log");
|
|
134
157
|
if (logRes.code !== 0)
|
|
135
158
|
return null;
|
|
136
|
-
const [commitId, changeId] = logRes.stdout.split("\n").map((part) => part.trim());
|
|
137
|
-
if (!commitId)
|
|
138
|
-
return null;
|
|
139
159
|
const opRes = yield* withSnapshotTimeout(runJj(["--ignore-working-copy", "operation", "log", "--no-graph", "--limit", "1", "-T", "self.id()"], { cwd }), "jj snapshot op");
|
|
140
160
|
if (opRes.code !== 0)
|
|
141
161
|
return null;
|
|
142
|
-
|
|
143
|
-
if (!operationId)
|
|
144
|
-
return null;
|
|
145
|
-
return { commitId, changeId: changeId ?? "", operationId };
|
|
162
|
+
return parseWorkspaceSnapshot(logRes.stdout, opRes.stdout);
|
|
146
163
|
}).pipe(Effect.annotateLogs({ cwd: cwd ?? "" }), Effect.withLogSpan("vcs:jj-snapshot"));
|
|
147
164
|
}
|
|
148
165
|
/**
|
|
@@ -194,8 +211,6 @@ export function workspaceAdd(name, path, opts = {}) {
|
|
|
194
211
|
yield* runJj(["workspace", "forget", name], { cwd: opts.cwd });
|
|
195
212
|
}
|
|
196
213
|
try {
|
|
197
|
-
const fs = require("node:fs");
|
|
198
|
-
const nodePath = require("node:path");
|
|
199
214
|
if (fs.existsSync(path)) {
|
|
200
215
|
fs.rmSync(path, { recursive: true, force: true });
|
|
201
216
|
}
|
package/src/resolveJjBinary.js
CHANGED
|
@@ -31,14 +31,19 @@ const BUNDLED_PACKAGES = {
|
|
|
31
31
|
*
|
|
32
32
|
* @returns {string | null}
|
|
33
33
|
*/
|
|
34
|
-
function
|
|
35
|
-
|
|
34
|
+
export function resolveBundledJjPath({
|
|
35
|
+
platform = process.platform,
|
|
36
|
+
arch = process.arch,
|
|
37
|
+
resolvePackage = require.resolve,
|
|
38
|
+
fileExists = existsSync,
|
|
39
|
+
} = {}) {
|
|
40
|
+
const pkg = BUNDLED_PACKAGES[`${platform}-${arch}`];
|
|
36
41
|
if (!pkg) return null;
|
|
37
|
-
const binary =
|
|
42
|
+
const binary = platform === "win32" ? "jj.exe" : "jj";
|
|
38
43
|
try {
|
|
39
|
-
const manifest =
|
|
44
|
+
const manifest = resolvePackage(`${pkg}/package.json`);
|
|
40
45
|
const candidate = join(manifest, "..", "bin", binary);
|
|
41
|
-
return
|
|
46
|
+
return fileExists(candidate) ? candidate : null;
|
|
42
47
|
} catch {
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
@@ -61,7 +66,7 @@ function bundledJjPath() {
|
|
|
61
66
|
export function resolveJjBinary() {
|
|
62
67
|
const override = process.env.SMITHERS_JJ_PATH;
|
|
63
68
|
if (override && existsSync(override)) return { path: override, source: "env" };
|
|
64
|
-
const bundled =
|
|
69
|
+
const bundled = resolveBundledJjPath();
|
|
65
70
|
if (bundled) return { path: bundled, source: "bundled" };
|
|
66
71
|
return { path: "jj", source: "path" };
|
|
67
72
|
}
|
package/src/vcsToolingStatus.js
CHANGED
|
@@ -21,7 +21,7 @@ const VERSION_PROBE_TIMEOUT_MS = 2_000;
|
|
|
21
21
|
* @param {import("./ResolvedBinary.js").ResolvedBinary} bin
|
|
22
22
|
* @returns {boolean}
|
|
23
23
|
*/
|
|
24
|
-
function runsVersion(bin) {
|
|
24
|
+
export function runsVersion(bin) {
|
|
25
25
|
try {
|
|
26
26
|
const res = spawnSync(bin.path, ["--version"], {
|
|
27
27
|
stdio: "ignore",
|
package/src/WorkspaceSnapshot.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type WorkspaceSnapshot = {
|
|
2
|
-
/**
|
|
3
|
-
* Working-copy commit id at this snapshot. Advances on every snapshot, so it
|
|
4
|
-
* addresses an individual working-copy state (unlike `changeId`, which is
|
|
5
|
-
* stable across edits to the same working copy).
|
|
6
|
-
*/
|
|
7
|
-
commitId: string;
|
|
8
|
-
/** Change id of `@`. Stable across edits to one working copy; grouping metadata only. */
|
|
9
|
-
changeId: string;
|
|
10
|
-
/**
|
|
11
|
-
* jj operation id for this snapshot. The durable restore handle: the commit id
|
|
12
|
-
* of an abandoned working-copy commit can be garbage-collected, while the
|
|
13
|
-
* operation log is retained under the configured gc policy.
|
|
14
|
-
*/
|
|
15
|
-
operationId: string;
|
|
16
|
-
};
|