machine-bridge-mcp 2.0.0 → 3.0.0-beta.11
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/CHANGELOG.md +120 -0
- package/CONTRIBUTING.md +20 -25
- package/README.md +21 -14
- package/SECURITY.md +147 -123
- package/browser-extension/manifest.json +3 -2
- package/browser-extension/service-worker.js +31 -12
- package/docs/ARCHITECTURE.md +28 -13
- package/docs/AUDIT.md +101 -2
- package/docs/CLIENTS.md +2 -2
- package/docs/ENGINEERING.md +9 -3
- package/docs/GETTING_STARTED.md +10 -9
- package/docs/LOCAL_AUTHORIZATION.md +115 -72
- package/docs/LOGGING.md +14 -3
- package/docs/MANAGED_JOBS.md +24 -29
- package/docs/MULTI_ACCOUNT.md +98 -37
- package/docs/OPERATIONS.md +34 -23
- package/docs/OVERVIEW.md +7 -7
- package/docs/PROJECT_STANDARDS.md +9 -5
- package/docs/RELEASING.md +171 -64
- package/docs/TESTING.md +21 -9
- package/docs/THREAT_MODEL.md +138 -67
- package/docs/TOOL_REFERENCE.md +2 -2
- package/docs/UPGRADING.md +102 -18
- package/native/macos/MachineBridgeTrustBroker.swift +233 -0
- package/package.json +37 -6
- package/scripts/candidate-runtime-store.mjs +38 -0
- package/scripts/check-plan.mjs +19 -0
- package/scripts/check-runner.mjs +4 -1
- package/scripts/commit-message-check.mjs +5 -2
- package/scripts/coverage-check.mjs +30 -2
- package/scripts/github-push.mjs +11 -0
- package/scripts/github-release.mjs +38 -7
- package/scripts/install-published-prerelease.mjs +167 -0
- package/scripts/local-release-acceptance.mjs +23 -12
- package/scripts/npm-publication-policy.mjs +16 -0
- package/scripts/prerelease-activation.mjs +79 -0
- package/scripts/privacy-check.mjs +7 -5
- package/scripts/promotion-digest.mjs +121 -0
- package/scripts/publish-npm.mjs +51 -0
- package/scripts/published-release.mjs +87 -0
- package/scripts/release-acceptance.mjs +30 -1
- package/scripts/release-candidate-manifest.mjs +40 -0
- package/scripts/release-channel.mjs +106 -0
- package/scripts/release-impact-check.mjs +12 -22
- package/scripts/release-soak.mjs +237 -0
- package/scripts/start-release-candidate.mjs +113 -7
- package/src/local/account-access.mjs +6 -0
- package/src/local/account-admin.mjs +34 -16
- package/src/local/agent-context.mjs +45 -42
- package/src/local/app-automation.mjs +9 -4
- package/src/local/authority-context.mjs +106 -0
- package/src/local/browser-bridge-http.mjs +4 -1
- package/src/local/browser-bridge.mjs +14 -9
- package/src/local/browser-broker-routes.mjs +4 -0
- package/src/local/browser-broker-server.mjs +5 -4
- package/src/local/browser-extension-identity.mjs +50 -0
- package/src/local/browser-extension-protocol.mjs +9 -4
- package/src/local/browser-operation-service.mjs +7 -0
- package/src/local/browser-pairing-http.mjs +36 -0
- package/src/local/browser-pairing-store.mjs +56 -47
- package/src/local/cli-account-admin.mjs +41 -7
- package/src/local/cli-activate.mjs +90 -0
- package/src/local/cli-approval.mjs +14 -58
- package/src/local/cli-local-admin.mjs +5 -15
- package/src/local/cli-options.mjs +11 -7
- package/src/local/cli-service.mjs +35 -5
- package/src/local/cli.mjs +119 -58
- package/src/local/daemon-process.mjs +6 -0
- package/src/local/delegated-process-sandbox.mjs +147 -0
- package/src/local/device-identity.mjs +157 -48
- package/src/local/device-root-provider.mjs +87 -0
- package/src/local/errors.mjs +25 -3
- package/src/local/git-service.mjs +16 -15
- package/src/local/job-runner.mjs +35 -27
- package/src/local/log.mjs +18 -44
- package/src/local/macos-trust-broker.mjs +320 -0
- package/src/local/managed-job-lock.mjs +18 -0
- package/src/local/managed-job-projection.mjs +9 -1
- package/src/local/managed-job-runner.mjs +13 -1
- package/src/local/managed-job-storage.mjs +2 -1
- package/src/local/managed-job-terminal.mjs +138 -0
- package/src/local/managed-jobs.mjs +88 -32
- package/src/local/operation-authorization.mjs +75 -239
- package/src/local/operation-risk.mjs +28 -0
- package/src/local/operation-state-lock.mjs +7 -86
- package/src/local/owner-state-lock.mjs +104 -0
- package/src/local/policy.mjs +19 -0
- package/src/local/process-execution.mjs +21 -9
- package/src/local/process-sessions.mjs +27 -17
- package/src/local/process-tracker.mjs +30 -6
- package/src/local/process-tree-ownership.mjs +59 -0
- package/src/local/process-tree.mjs +22 -18
- package/src/local/relay-connection.mjs +47 -35
- package/src/local/relay-diagnostics.mjs +65 -0
- package/src/local/remote-configuration.mjs +48 -0
- package/src/local/runtime-activation.mjs +141 -0
- package/src/local/runtime-diagnostics.mjs +21 -0
- package/src/local/runtime-relay.mjs +9 -8
- package/src/local/runtime-reporting.mjs +8 -3
- package/src/local/runtime-resource-service.mjs +77 -0
- package/src/local/runtime-tool-handlers.mjs +8 -8
- package/src/local/runtime.mjs +85 -95
- package/src/local/secure-file.mjs +5 -1
- package/src/local/security-audit-log.mjs +204 -0
- package/src/local/service-convergence.mjs +17 -1
- package/src/local/service-ownership.mjs +17 -0
- package/src/local/service-restart-handoff.mjs +50 -0
- package/src/local/service-restart-scheduler.mjs +49 -0
- package/src/local/service-status.mjs +47 -0
- package/src/local/service.mjs +142 -43
- package/src/local/state-inventory.mjs +21 -3
- package/src/local/state.mjs +155 -17
- package/src/local/stdio.mjs +5 -0
- package/src/local/tool-executor.mjs +39 -6
- package/src/local/tool-result-boundary.mjs +51 -0
- package/src/local/tools.mjs +1 -0
- package/src/local/trusted-executable.mjs +53 -0
- package/src/local/trusted-git-executable.mjs +34 -0
- package/src/local/trusted-github-cli.mjs +24 -0
- package/src/local/windows-service.mjs +20 -0
- package/src/local/worker-deployment.mjs +3 -4
- package/src/local/worker-secret-file.mjs +2 -2
- package/src/local/workspace-file-service.mjs +34 -31
- package/src/shared/admin-auth.d.mts +2 -1
- package/src/shared/admin-auth.mjs +3 -3
- package/src/shared/device-session-auth.d.mts +20 -0
- package/src/shared/device-session-auth.mjs +51 -0
- package/src/shared/log-redaction.d.mts +9 -0
- package/src/shared/log-redaction.mjs +51 -0
- package/src/shared/server-metadata.json +1 -1
- package/src/shared/tool-catalog.json +2 -2
- package/src/worker/account-admin.ts +23 -22
- package/src/worker/authority.ts +23 -4
- package/src/worker/daemon-auth.ts +45 -67
- package/src/worker/daemon-sockets.ts +12 -2
- package/src/worker/device-session-verifier.ts +129 -0
- package/src/worker/dpop.ts +151 -0
- package/src/worker/http.ts +47 -16
- package/src/worker/index.ts +53 -16
- package/src/worker/nonce-store.ts +1 -5
- package/src/worker/oauth-client-admin.ts +45 -0
- package/src/worker/oauth-controller.ts +55 -6
- package/src/worker/oauth-refresh-families.ts +21 -7
- package/src/worker/oauth-state.ts +6 -0
- package/src/worker/oauth-tokens.ts +28 -8
- package/src/worker/observability.ts +4 -2
- package/src/worker/websocket-protocol.ts +8 -2
|
@@ -16,6 +16,7 @@ const tests = [
|
|
|
16
16
|
"tests/runtime-handler-matrix-test.mjs",
|
|
17
17
|
"tests/cli-entrypoint-test.mjs",
|
|
18
18
|
"tests/cli-service-test.mjs",
|
|
19
|
+
"tests/service-restart-handoff-test.mjs",
|
|
19
20
|
"tests/local-self-test.mjs",
|
|
20
21
|
"tests/runtime-self-test.mjs",
|
|
21
22
|
"tests/numbers-test.mjs",
|
|
@@ -31,6 +32,12 @@ const tests = [
|
|
|
31
32
|
"tests/managed-jobs-test.mjs",
|
|
32
33
|
"tests/account-admin-test.mjs",
|
|
33
34
|
"tests/monotonic-deadline-test.mjs",
|
|
35
|
+
"tests/device-auth-test.mjs",
|
|
36
|
+
"tests/operation-authorization-test.mjs",
|
|
37
|
+
"tests/security-audit-log-test.mjs",
|
|
38
|
+
"tests/delegated-process-sandbox-test.mjs",
|
|
39
|
+
"tests/dpop-test.mjs",
|
|
40
|
+
"tests/worker-security-boundaries-test.mjs",
|
|
34
41
|
];
|
|
35
42
|
|
|
36
43
|
try {
|
|
@@ -50,17 +57,31 @@ try {
|
|
|
50
57
|
|
|
51
58
|
const coverage = collectCoverage(coverageDir);
|
|
52
59
|
const thresholds = {
|
|
60
|
+
"src/local/authority-context.mjs": [85, 55],
|
|
61
|
+
"src/local/device-identity.mjs": [90, null],
|
|
62
|
+
"src/local/operation-authorization.mjs": [85, 45],
|
|
63
|
+
"src/local/operation-risk.mjs": [85, 60],
|
|
64
|
+
"src/local/operation-state-lock.mjs": [100, 7],
|
|
65
|
+
"src/local/security-audit-log.mjs": [85, 55],
|
|
66
|
+
"src/local/delegated-process-sandbox.mjs": [80, 45],
|
|
67
|
+
"src/shared/device-session-auth.mjs": [100, null],
|
|
53
68
|
"src/local/policy.mjs": [90, 65],
|
|
54
69
|
"src/local/errors.mjs": [70, 50],
|
|
55
70
|
"src/local/call-registry.mjs": [85, 55],
|
|
56
71
|
"src/local/tool-executor.mjs": [90, 40],
|
|
72
|
+
"src/local/tool-result-boundary.mjs": [100, 75],
|
|
57
73
|
"src/local/observability.mjs": [95, 40],
|
|
58
74
|
"src/local/process-tracker.mjs": [65, 35],
|
|
59
75
|
"src/local/log.mjs": [60, 40],
|
|
60
76
|
"src/local/runtime.mjs": [75, 55],
|
|
61
77
|
"src/local/runtime-paths.mjs": [90, 50],
|
|
62
78
|
"src/local/cli.mjs": [48, 21.9],
|
|
63
|
-
"src/local/cli-service.mjs": [
|
|
79
|
+
"src/local/cli-service.mjs": [100, 75],
|
|
80
|
+
"src/local/service-convergence.mjs": [100, 75],
|
|
81
|
+
"src/local/service-status.mjs": [100, 75],
|
|
82
|
+
"src/local/service-ownership.mjs": [100, 75],
|
|
83
|
+
"src/local/service-restart-scheduler.mjs": [100, 70],
|
|
84
|
+
"src/local/service-restart-handoff.mjs": [100, 60],
|
|
64
85
|
"src/local/cli-options.mjs": [65, 35],
|
|
65
86
|
"src/local/cli-policy.mjs": [70, 35],
|
|
66
87
|
"src/local/numbers.mjs": [100, 100],
|
|
@@ -91,7 +112,14 @@ try {
|
|
|
91
112
|
"src/local/browser-request-registry.mjs": [95, 35],
|
|
92
113
|
"src/local/browser-broker-routes.mjs": [85, 55],
|
|
93
114
|
"src/local/browser-broker-server.mjs": [80, 50],
|
|
94
|
-
"src/worker/
|
|
115
|
+
"src/worker/account-admin.ts": [70, 35],
|
|
116
|
+
"src/worker/daemon-auth.ts": [85, 45],
|
|
117
|
+
"src/worker/dpop.ts": [90, 30],
|
|
118
|
+
"src/worker/nonce-store.ts": [85, 50],
|
|
119
|
+
"src/worker/oauth-client-admin.ts": [75, 45],
|
|
120
|
+
"src/worker/oauth-refresh-families.ts": [70, 35],
|
|
121
|
+
"src/worker/oauth-tokens.ts": [70, 35],
|
|
122
|
+
"src/worker/oauth-controller.ts": [84, 65],
|
|
95
123
|
"src/worker/oauth-authorization-page.ts": [90, 60],
|
|
96
124
|
"src/worker/pending-calls.ts": [90, 35],
|
|
97
125
|
"src/worker/policy.ts": [100, 25],
|
package/scripts/github-push.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
4
5
|
import { dirname, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
|
|
7
8
|
import { assertGitHubBacklogReady } from "./github-backlog.mjs";
|
|
9
|
+
import { parseReleaseVersion, requiresSoakForStable } from "./release-channel.mjs";
|
|
10
|
+
import { verifyCurrentStableSoak } from "./release-soak.mjs";
|
|
8
11
|
|
|
9
12
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
13
|
|
|
@@ -25,6 +28,14 @@ try {
|
|
|
25
28
|
console.log(`Verified interactive local candidate acceptance for ${acceptance.metadata.filename}.`);
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
const releaseVersion = parseReleaseVersion(JSON.parse(readFileSync(resolve(root, "package.json"), "utf8")).version);
|
|
32
|
+
if (!releaseVersion.prerelease && requiresSoakForStable(releaseVersion.raw)) {
|
|
33
|
+
const soak = verifyCurrentStableSoak(root);
|
|
34
|
+
const path = `release-soak/v${releaseVersion.raw}.json`;
|
|
35
|
+
run("git", ["ls-files", "--error-unmatch", path], { capture: true });
|
|
36
|
+
console.log(`Verified stable promotion from soaked ${soak.record.prerelease_version}.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
run("git", ["push", "--set-upstream", "origin", "HEAD"]);
|
|
29
40
|
console.log(`Pushed accepted branch ${branch} to origin.`);
|
|
30
41
|
} catch (error) {
|
|
@@ -13,6 +13,8 @@ import { runNetworkCommand } from "./network-retry.mjs";
|
|
|
13
13
|
import { requireSuccessfulWorkflowRun } from "./release-ci.mjs";
|
|
14
14
|
import { tagSyncError } from "./release-state.mjs";
|
|
15
15
|
import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
|
|
16
|
+
import { parseReleaseVersion, requiresSoakForStable } from "./release-channel.mjs";
|
|
17
|
+
import { verifyCurrentStableSoak } from "./release-soak.mjs";
|
|
16
18
|
import { fileURLToPath } from "node:url";
|
|
17
19
|
|
|
18
20
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -179,7 +181,9 @@ function releaseInfo(tag) {
|
|
|
179
181
|
|
|
180
182
|
function assertCoreSync({ requireReleaseAsset }) {
|
|
181
183
|
const pkg = packageMetadata();
|
|
184
|
+
const parsedVersion = parseReleaseVersion(pkg.version);
|
|
182
185
|
assertLocalAcceptance();
|
|
186
|
+
if (requiresSoakForStable(pkg.version)) assertStableSoak();
|
|
183
187
|
const tag = `v${pkg.version}`;
|
|
184
188
|
const head = output("git", ["rev-parse", "HEAD"]);
|
|
185
189
|
const originMain = output("git", ["rev-parse", "origin/main"]);
|
|
@@ -197,8 +201,10 @@ function assertCoreSync({ requireReleaseAsset }) {
|
|
|
197
201
|
if (remoteTagError) fail(remoteTagError);
|
|
198
202
|
|
|
199
203
|
const release = releaseInfo(tag);
|
|
200
|
-
if (!release || release.isDraft || release.isPrerelease) {
|
|
201
|
-
fail(
|
|
204
|
+
if (!release || release.isDraft || release.isPrerelease !== parsedVersion.prerelease) {
|
|
205
|
+
fail(parsedVersion.prerelease
|
|
206
|
+
? `published GitHub prerelease ${tag} is missing or not marked as prerelease`
|
|
207
|
+
: `published GitHub Release ${tag} is missing or not final`);
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
if (requireReleaseAsset) {
|
|
@@ -250,7 +256,7 @@ function normalizePackRecord(value, packageName) {
|
|
|
250
256
|
return Object.values(value).find((item) => item && typeof item === "object") ?? null;
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
function ensureRelease(tag, version, assetPath, latest) {
|
|
259
|
+
function ensureRelease(tag, version, assetPath, { latest, prerelease }) {
|
|
254
260
|
const temp = dirname(assetPath);
|
|
255
261
|
const notes = writeNotesFile(temp, version);
|
|
256
262
|
const existing = releaseInfo(tag);
|
|
@@ -266,6 +272,7 @@ function ensureRelease(tag, version, assetPath, latest) {
|
|
|
266
272
|
"--title",
|
|
267
273
|
title,
|
|
268
274
|
latest ? "--latest" : "--latest=false",
|
|
275
|
+
prerelease ? "--prerelease" : "--prerelease=false",
|
|
269
276
|
];
|
|
270
277
|
if (notes) args.push("--notes-file", notes);
|
|
271
278
|
else args.push("--generate-notes");
|
|
@@ -279,13 +286,14 @@ function ensureRelease(tag, version, assetPath, latest) {
|
|
|
279
286
|
"--title",
|
|
280
287
|
title,
|
|
281
288
|
latest ? "--latest" : "--latest=false",
|
|
289
|
+
prerelease ? "--prerelease" : "--prerelease=false",
|
|
282
290
|
...(notes ? ["--notes-file", notes] : []),
|
|
283
291
|
]);
|
|
284
292
|
runNetwork("gh", ["release", "upload", tag, assetPath, "--clobber"]);
|
|
285
293
|
}
|
|
286
294
|
}
|
|
287
295
|
|
|
288
|
-
function publishCurrent() {
|
|
296
|
+
function publishCurrent({ prereleaseMode = false } = {}) {
|
|
289
297
|
ensureClean();
|
|
290
298
|
fetchRemote();
|
|
291
299
|
|
|
@@ -293,6 +301,13 @@ function publishCurrent() {
|
|
|
293
301
|
if (branch !== "main") fail(`release must run from main, not ${branch || "detached HEAD"}`);
|
|
294
302
|
|
|
295
303
|
const pkg = packageMetadata();
|
|
304
|
+
const parsedVersion = parseReleaseVersion(pkg.version);
|
|
305
|
+
if (prereleaseMode !== parsedVersion.prerelease) {
|
|
306
|
+
fail(parsedVersion.prerelease
|
|
307
|
+
? "prerelease versions must use npm run prerelease:release"
|
|
308
|
+
: "stable versions must use npm run release");
|
|
309
|
+
}
|
|
310
|
+
if (!parsedVersion.prerelease && requiresSoakForStable(pkg.version)) assertStableSoak();
|
|
296
311
|
const tag = `v${pkg.version}`;
|
|
297
312
|
if (!changelogBody(pkg.version)) {
|
|
298
313
|
fail(`CHANGELOG.md has no section for ${pkg.version}`);
|
|
@@ -329,7 +344,10 @@ function publishCurrent() {
|
|
|
329
344
|
const temp = mkdtempSync(join(tmpdir(), "machine-bridge-mcp-release-"));
|
|
330
345
|
try {
|
|
331
346
|
const assetPath = packReleaseAsset(temp, pkg);
|
|
332
|
-
ensureRelease(tag, pkg.version, assetPath,
|
|
347
|
+
ensureRelease(tag, pkg.version, assetPath, {
|
|
348
|
+
latest: !parsedVersion.prerelease,
|
|
349
|
+
prerelease: parsedVersion.prerelease,
|
|
350
|
+
});
|
|
333
351
|
} finally {
|
|
334
352
|
rmSync(temp, { recursive: true, force: true });
|
|
335
353
|
}
|
|
@@ -338,6 +356,17 @@ function publishCurrent() {
|
|
|
338
356
|
assertCoreSync({ requireReleaseAsset: true });
|
|
339
357
|
}
|
|
340
358
|
|
|
359
|
+
function assertStableSoak() {
|
|
360
|
+
try {
|
|
361
|
+
const result = verifyCurrentStableSoak(root);
|
|
362
|
+
if (result.required) {
|
|
363
|
+
console.log(`Stable promotion matches soaked ${result.record.prerelease_version} (${result.promotionDigest}).`);
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
fail(String(error?.message || error));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
341
370
|
function assertLocalAcceptance() {
|
|
342
371
|
try {
|
|
343
372
|
const result = verifyCurrentReleaseAcceptance(root);
|
|
@@ -405,9 +434,11 @@ if (mode === "--check") {
|
|
|
405
434
|
fetchRemote();
|
|
406
435
|
assertCoreSync({ requireReleaseAsset: true });
|
|
407
436
|
} else if (mode === "--publish") {
|
|
408
|
-
publishCurrent();
|
|
437
|
+
publishCurrent({ prereleaseMode: false });
|
|
438
|
+
} else if (mode === "--publish-prerelease") {
|
|
439
|
+
publishCurrent({ prereleaseMode: true });
|
|
409
440
|
} else if (mode === "--backfill") {
|
|
410
441
|
backfillMissingReleases();
|
|
411
442
|
} else {
|
|
412
|
-
fail("usage: node scripts/github-release.mjs [--check|--publish|--backfill]");
|
|
443
|
+
fail("usage: node scripts/github-release.mjs [--check|--publish|--publish-prerelease|--backfill]");
|
|
413
444
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { defaultStateRoot, expandHome } from "../src/local/state.mjs";
|
|
9
|
+
import { writePrereleaseActivation } from "./prerelease-activation.mjs";
|
|
10
|
+
import { computePromotionContentDigest } from "./promotion-digest.mjs";
|
|
11
|
+
import { readPublishedNpmPrerelease } from "./published-release.mjs";
|
|
12
|
+
import { verifyCurrentReleaseAcceptance } from "./release-acceptance.mjs";
|
|
13
|
+
import { assertSoakEligiblePrerelease } from "./release-channel.mjs";
|
|
14
|
+
|
|
15
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
+
const npmCli = process.env.npm_execpath;
|
|
17
|
+
let previousInstallation = null;
|
|
18
|
+
let installedPrerelease = "";
|
|
19
|
+
if (!npmCli) fail("published prerelease installation must run through npm");
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!process.argv.includes("--allow-worker-deploy")) {
|
|
23
|
+
throw new Error("published prerelease activation updates the global package, same-name Worker, and login daemon; rerun with --allow-worker-deploy");
|
|
24
|
+
}
|
|
25
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
26
|
+
const requested = argumentValue("--version") || pkg.version;
|
|
27
|
+
const prerelease = assertSoakEligiblePrerelease(requested);
|
|
28
|
+
if (pkg.version !== prerelease.raw) {
|
|
29
|
+
throw new Error(`source checkout version ${pkg.version} does not match requested prerelease ${prerelease.raw}`);
|
|
30
|
+
}
|
|
31
|
+
const acceptance = verifyCurrentReleaseAcceptance(root);
|
|
32
|
+
if (!acceptance.required || acceptance.metadata.package_version !== prerelease.raw) {
|
|
33
|
+
throw new Error("published prerelease installation requires the locally accepted exact prerelease source");
|
|
34
|
+
}
|
|
35
|
+
const promotionDigest = computePromotionContentDigest(root);
|
|
36
|
+
if (acceptance.record.promotion_content_sha256 !== promotionDigest) {
|
|
37
|
+
throw new Error("current source promotion digest does not match local candidate acceptance");
|
|
38
|
+
}
|
|
39
|
+
const published = readPublishedNpmPrerelease(pkg.name, prerelease.raw, prerelease.npmTag);
|
|
40
|
+
if (published.integrity !== acceptance.metadata.integrity || published.shasum !== acceptance.metadata.shasum) {
|
|
41
|
+
throw new Error("npm prerelease bytes do not match the locally accepted candidate");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
previousInstallation = currentGlobalInstallation(pkg.name);
|
|
45
|
+
runNpm([
|
|
46
|
+
"install", "--global", "--omit=optional",
|
|
47
|
+
"--allow-scripts=esbuild,workerd,sharp,fsevents",
|
|
48
|
+
`${pkg.name}@${prerelease.raw}`,
|
|
49
|
+
]);
|
|
50
|
+
const installed = currentGlobalInstallation(pkg.name);
|
|
51
|
+
if (!installed || installed.version !== prerelease.raw) {
|
|
52
|
+
throw new Error(`global prerelease installation did not converge on ${prerelease.raw}`);
|
|
53
|
+
}
|
|
54
|
+
installedPrerelease = prerelease.raw;
|
|
55
|
+
|
|
56
|
+
const stateRoot = resolve(expandHome(argumentValue("--state-dir") || defaultStateRoot()));
|
|
57
|
+
const forwarded = forwardedActivationArgs();
|
|
58
|
+
const activation = runActivation(installed.entry, ["activate", ...forwarded, "--state-dir", stateRoot, "--json"]);
|
|
59
|
+
if (activation.version !== prerelease.raw || activation.daemon?.version !== prerelease.raw || activation.worker?.health?.version !== prerelease.raw) {
|
|
60
|
+
throw new Error("published prerelease activation did not converge on the exact registry version");
|
|
61
|
+
}
|
|
62
|
+
const recordPath = writePrereleaseActivation({
|
|
63
|
+
schema_version: 1,
|
|
64
|
+
package_name: pkg.name,
|
|
65
|
+
package_version: prerelease.raw,
|
|
66
|
+
source: "npm-prerelease",
|
|
67
|
+
shasum: published.shasum,
|
|
68
|
+
integrity: published.integrity,
|
|
69
|
+
promotion_content_sha256: promotionDigest,
|
|
70
|
+
activated_at: new Date().toISOString(),
|
|
71
|
+
published_at: published.publishedAt,
|
|
72
|
+
npm_dist_tag: prerelease.npmTag,
|
|
73
|
+
workspace_hash: workspaceHash(activation.workspace),
|
|
74
|
+
runtime_entry: installed.entry,
|
|
75
|
+
...(previousInstallation ? { previous: previousInstallation } : {}),
|
|
76
|
+
}, stateRoot);
|
|
77
|
+
|
|
78
|
+
console.log(`Published prerelease activated: ${prerelease.raw}`);
|
|
79
|
+
console.log(`Worker and login daemon version: ${activation.version}`);
|
|
80
|
+
console.log(`Soak activation record: ${recordPath}`);
|
|
81
|
+
console.log("Use this prerelease normally. Any blocking issue requires a new beta/rc version and restarts the soak clock.");
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const message = boundedDiagnostic(error?.message || error);
|
|
84
|
+
if (installedPrerelease) {
|
|
85
|
+
const previousVersion = previousInstallation?.version || "unknown";
|
|
86
|
+
fail(`${message}. The global package is now ${installedPrerelease}, but Worker/service activation may not have converged. Previous global version: ${previousVersion}. Preserve state and logs; fix forward with the exact prerelease or restore package, Worker, service definition, browser extension, and state as one verified unit.`);
|
|
87
|
+
}
|
|
88
|
+
fail(message);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function currentGlobalInstallation(packageName) {
|
|
92
|
+
try {
|
|
93
|
+
const globalRoot = runNpm(["root", "--global"]).stdout.trim();
|
|
94
|
+
const packageRoot = join(globalRoot, packageName);
|
|
95
|
+
const packagePath = join(packageRoot, "package.json");
|
|
96
|
+
const entry = join(packageRoot, "bin", "machine-mcp.mjs");
|
|
97
|
+
if (!existsSync(packagePath) || !existsSync(entry)) return null;
|
|
98
|
+
const value = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
99
|
+
return { version: String(value.version || ""), entry };
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runActivation(entry, args) {
|
|
106
|
+
const result = spawnSync(process.execPath, [entry, ...args], {
|
|
107
|
+
cwd: root,
|
|
108
|
+
env: process.env,
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
timeout: 300_000,
|
|
111
|
+
windowsHide: true,
|
|
112
|
+
});
|
|
113
|
+
if (result.error) throw result.error;
|
|
114
|
+
if (result.status !== 0) throw new Error(`prerelease runtime activation failed: ${boundedDiagnostic(result.stderr || result.stdout)}`);
|
|
115
|
+
try { return JSON.parse(result.stdout); } catch { throw new Error("prerelease runtime activation did not return valid JSON"); }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runNpm(args) {
|
|
119
|
+
const result = spawnSync(process.execPath, [npmCli, ...args], {
|
|
120
|
+
cwd: root,
|
|
121
|
+
env: process.env,
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
timeout: 300_000,
|
|
124
|
+
windowsHide: true,
|
|
125
|
+
});
|
|
126
|
+
if (result.error) throw result.error;
|
|
127
|
+
if (result.status !== 0) throw new Error(`npm ${args[0]} failed: ${boundedDiagnostic(result.stderr || result.stdout)}`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function forwardedActivationArgs() {
|
|
132
|
+
const out = [];
|
|
133
|
+
const skipWithValue = new Set(["--version", "--state-dir"]);
|
|
134
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
135
|
+
const value = process.argv[index];
|
|
136
|
+
if (value === "--allow-worker-deploy") continue;
|
|
137
|
+
if (skipWithValue.has(value)) { index += 1; continue; }
|
|
138
|
+
if (["--version=", "--state-dir="].some((prefix) => value.startsWith(prefix))) continue;
|
|
139
|
+
if (value === "--json" || value.startsWith("--json=")) continue;
|
|
140
|
+
out.push(value);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function argumentValue(name) {
|
|
146
|
+
const exact = process.argv.find((value) => value.startsWith(`${name}=`));
|
|
147
|
+
if (exact) return exact.slice(name.length + 1);
|
|
148
|
+
const index = process.argv.indexOf(name);
|
|
149
|
+
return index >= 0 ? process.argv[index + 1] : "";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function workspaceHash(workspace) {
|
|
153
|
+
const value = process.platform === "win32" ? String(workspace).toLowerCase() : String(workspace);
|
|
154
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function boundedDiagnostic(value) {
|
|
158
|
+
const home = String(process.env.HOME || process.env.USERPROFILE || "");
|
|
159
|
+
let text = String(value || "unknown error").replace(/[\r\n\t]+/g, " ").trim();
|
|
160
|
+
if (home) text = text.split(home).join("<home>");
|
|
161
|
+
return text.slice(0, 1600);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function fail(message) {
|
|
165
|
+
console.error(`published prerelease installation failed: ${message}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
@@ -5,14 +5,13 @@ import {
|
|
|
5
5
|
mkdtempSync,
|
|
6
6
|
readFileSync,
|
|
7
7
|
rmSync,
|
|
8
|
-
writeFileSync,
|
|
9
8
|
} from "node:fs";
|
|
10
9
|
import { spawnSync } from "node:child_process";
|
|
11
10
|
import { tmpdir } from "node:os";
|
|
12
11
|
import { dirname, join, resolve } from "node:path";
|
|
13
12
|
import { fileURLToPath } from "node:url";
|
|
14
13
|
import {
|
|
15
|
-
|
|
14
|
+
acceptanceConfirmationForVersion,
|
|
16
15
|
ACCEPTANCE_SCHEMA_VERSION,
|
|
17
16
|
acceptancePath,
|
|
18
17
|
packProject,
|
|
@@ -21,6 +20,10 @@ import {
|
|
|
21
20
|
verifyCurrentReleaseAcceptance,
|
|
22
21
|
verifyTarball,
|
|
23
22
|
} from "./release-acceptance.mjs";
|
|
23
|
+
import { computePromotionContentDigest } from "./promotion-digest.mjs";
|
|
24
|
+
import { parseReleaseVersion } from "./release-channel.mjs";
|
|
25
|
+
import { validateCandidateManifest } from "./release-candidate-manifest.mjs";
|
|
26
|
+
import { replaceFileAtomicallySync } from "../src/local/exclusive-file.mjs";
|
|
24
27
|
|
|
25
28
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
29
|
const candidateDirectory = join(root, ".release-candidate");
|
|
@@ -48,14 +51,16 @@ function prepareCandidate() {
|
|
|
48
51
|
schema_version: ACCEPTANCE_SCHEMA_VERSION,
|
|
49
52
|
result: "pending",
|
|
50
53
|
...metadata,
|
|
54
|
+
promotion_content_sha256: computePromotionContentDigest(root),
|
|
51
55
|
prepared_at: new Date().toISOString(),
|
|
52
56
|
};
|
|
53
|
-
|
|
57
|
+
replaceFileAtomicallySync(candidateManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
|
|
54
58
|
const phrase = confirmationPhrase(pkg.name, pkg.version);
|
|
55
59
|
console.log(`Release candidate created: ${join(candidateDirectory, metadata.filename)}`);
|
|
56
|
-
console.log("
|
|
57
|
-
|
|
58
|
-
console.log("
|
|
60
|
+
console.log("The repository owner activates this exact candidate with one persistent owner-side command:");
|
|
61
|
+
parseReleaseVersion(pkg.version);
|
|
62
|
+
console.log("npm run release:candidate:activate -- --allow-worker-deploy");
|
|
63
|
+
console.log("The owner runs this one command. It installs the exact candidate in the private state root, updates the same-name Worker, verifies candidate relay readiness, replaces the login daemon, verifies the background handoff, and exits while the service remains active.");
|
|
59
64
|
console.log("After that observed live verification succeeds, the coding agent records acceptance with:");
|
|
60
65
|
console.log(`npm run release:accept -- --confirm \"${phrase}\"`);
|
|
61
66
|
console.log("Automated tests alone do not authorize acceptance or the first GitHub push.");
|
|
@@ -68,10 +73,10 @@ function recordAcceptance() {
|
|
|
68
73
|
if (supplied !== expected) {
|
|
69
74
|
throw new Error(`interactive candidate verification confirmation must exactly match: ${expected}`);
|
|
70
75
|
}
|
|
71
|
-
const pending = readJson(candidateManifestPath, "release candidate manifest")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
76
|
+
const pending = validateCandidateManifest(readJson(candidateManifestPath, "release candidate manifest"), {
|
|
77
|
+
packageName: pkg.name,
|
|
78
|
+
packageVersion: pkg.version,
|
|
79
|
+
});
|
|
75
80
|
verifyTarball(join(candidateDirectory, pending.filename), pending);
|
|
76
81
|
|
|
77
82
|
const verificationDirectory = join(candidateDirectory, "verification");
|
|
@@ -84,10 +89,15 @@ function recordAcceptance() {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
const promotionDigest = computePromotionContentDigest(root);
|
|
93
|
+
if (pending.promotion_content_sha256 !== promotionDigest) {
|
|
94
|
+
throw new Error("source changed after candidate preparation: promotion content digest no longer matches");
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
const record = {
|
|
88
98
|
schema_version: ACCEPTANCE_SCHEMA_VERSION,
|
|
89
99
|
result: "passed",
|
|
90
|
-
confirmation:
|
|
100
|
+
confirmation: acceptanceConfirmationForVersion(pkg.version),
|
|
91
101
|
package_name: current.package_name,
|
|
92
102
|
package_version: current.package_version,
|
|
93
103
|
filename: current.filename,
|
|
@@ -95,11 +105,12 @@ function recordAcceptance() {
|
|
|
95
105
|
integrity: current.integrity,
|
|
96
106
|
accepted_at: new Date().toISOString(),
|
|
97
107
|
package_content_sha256: computePortablePackageDigest(),
|
|
108
|
+
promotion_content_sha256: promotionDigest,
|
|
98
109
|
};
|
|
99
110
|
verifyAcceptanceRecord(record, current);
|
|
100
111
|
const path = acceptancePath(root, pkg.version);
|
|
101
112
|
mkdirSync(dirname(path), { recursive: true });
|
|
102
|
-
|
|
113
|
+
replaceFileAtomicallySync(path, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o644 });
|
|
103
114
|
console.log(`Interactive local candidate acceptance recorded: ${path}`);
|
|
104
115
|
console.log("Commit this record with the candidate. Any packaged-file change invalidates it.");
|
|
105
116
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { assertPublishTag, parseReleaseVersion } from "./release-channel.mjs";
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const pkg = JSON.parse(readFileSync(resolve("package.json"), "utf8"));
|
|
9
|
+
const parsed = parseReleaseVersion(pkg.version);
|
|
10
|
+
const configured = String(process.env.npm_config_tag || "latest").trim().toLowerCase() || "latest";
|
|
11
|
+
assertPublishTag(parsed.raw, configured);
|
|
12
|
+
console.log(`npm publication channel verified: ${parsed.raw} -> ${parsed.npmTag}`);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(`npm publication policy failed: ${error?.message || error}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { defaultStateRoot, expandHome } from "../src/local/state.mjs";
|
|
4
|
+
import { replaceFileAtomicallySync } from "../src/local/exclusive-file.mjs";
|
|
5
|
+
import { ensureOwnerOnlyDirectorySync, readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
|
|
6
|
+
import { assertSoakEligiblePrerelease } from "./release-channel.mjs";
|
|
7
|
+
|
|
8
|
+
export const ACTIVATION_SCHEMA_VERSION = 1;
|
|
9
|
+
const MAX_ACTIVATION_BYTES = 64 * 1024;
|
|
10
|
+
const SOURCES = new Set(["local-candidate", "npm-prerelease"]);
|
|
11
|
+
|
|
12
|
+
export function prereleaseActivationPath(version, stateRoot = defaultStateRoot()) {
|
|
13
|
+
const parsed = assertSoakEligiblePrerelease(version);
|
|
14
|
+
return join(resolve(expandHome(stateRoot)), "release-channels", "activations", `v${parsed.raw}.json`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function writePrereleaseActivation(record, stateRoot = defaultStateRoot()) {
|
|
18
|
+
const normalized = validatePrereleaseActivation(record);
|
|
19
|
+
const file = prereleaseActivationPath(normalized.package_version, stateRoot);
|
|
20
|
+
ensureOwnerOnlyDirectorySync(dirname(file));
|
|
21
|
+
replaceFileAtomicallySync(file, `${JSON.stringify(normalized, null, 2)}\n`, { mode: 0o600 });
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function readPrereleaseActivation(version, stateRoot = defaultStateRoot()) {
|
|
26
|
+
const file = prereleaseActivationPath(version, stateRoot);
|
|
27
|
+
if (!existsSync(file)) throw new Error(`prerelease activation record is missing: ${file}`);
|
|
28
|
+
let value;
|
|
29
|
+
try { value = JSON.parse(readBoundedRegularFileSync(file, MAX_ACTIVATION_BYTES, "prerelease activation record", { verifyPathIdentity: true }).toString("utf8")); }
|
|
30
|
+
catch (error) { throw new Error(`prerelease activation record is unavailable or invalid: ${error.message}`); }
|
|
31
|
+
return validatePrereleaseActivation(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function validatePrereleaseActivation(value) {
|
|
35
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("prerelease activation record must be an object");
|
|
36
|
+
if (value.schema_version !== ACTIVATION_SCHEMA_VERSION) throw new Error("unsupported prerelease activation schema");
|
|
37
|
+
const parsed = assertSoakEligiblePrerelease(value.package_version);
|
|
38
|
+
if (value.package_name !== "machine-bridge-mcp") throw new Error("prerelease activation package name is invalid");
|
|
39
|
+
if (!SOURCES.has(value.source)) throw new Error("prerelease activation source is invalid");
|
|
40
|
+
if (!/^[0-9a-f]{40}$/.test(String(value.shasum || ""))) throw new Error("prerelease activation SHA-1 is invalid");
|
|
41
|
+
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(String(value.integrity || ""))) throw new Error("prerelease activation integrity is invalid");
|
|
42
|
+
if (!/^[0-9a-f]{64}$/.test(String(value.promotion_content_sha256 || ""))) throw new Error("prerelease activation promotion digest is invalid");
|
|
43
|
+
const activatedAt = Date.parse(String(value.activated_at || ""));
|
|
44
|
+
if (!Number.isFinite(activatedAt)) throw new Error("prerelease activation timestamp is invalid");
|
|
45
|
+
if (value.source === "npm-prerelease") {
|
|
46
|
+
if (value.npm_dist_tag !== parsed.npmTag) throw new Error("prerelease activation npm dist-tag is invalid");
|
|
47
|
+
const publishedAt = Date.parse(String(value.published_at || ""));
|
|
48
|
+
if (!Number.isFinite(publishedAt) || publishedAt > activatedAt + 5 * 60 * 1000) throw new Error("prerelease activation publication timestamp is invalid");
|
|
49
|
+
}
|
|
50
|
+
const workspaceHash = String(value.workspace_hash || "");
|
|
51
|
+
if (workspaceHash && !/^[0-9a-f]{24}$/.test(workspaceHash)) throw new Error("prerelease activation workspace hash is invalid");
|
|
52
|
+
const runtimeEntry = String(value.runtime_entry || "");
|
|
53
|
+
if (runtimeEntry && !isAbsolute(runtimeEntry)) throw new Error("prerelease activation runtime entry must be absolute");
|
|
54
|
+
let previous;
|
|
55
|
+
if (value.previous !== undefined) {
|
|
56
|
+
if (!value.previous || typeof value.previous !== "object" || Array.isArray(value.previous)) throw new Error("prerelease activation previous runtime is invalid");
|
|
57
|
+
const previousVersion = String(value.previous.version || "");
|
|
58
|
+
const previousEntry = String(value.previous.entry || "");
|
|
59
|
+
if (!previousVersion || !previousEntry || !isAbsolute(previousEntry)) throw new Error("prerelease activation previous runtime is invalid");
|
|
60
|
+
previous = { version: previousVersion, entry: previousEntry };
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
schema_version: ACTIVATION_SCHEMA_VERSION,
|
|
64
|
+
package_name: value.package_name,
|
|
65
|
+
package_version: parsed.raw,
|
|
66
|
+
source: value.source,
|
|
67
|
+
shasum: value.shasum,
|
|
68
|
+
integrity: value.integrity,
|
|
69
|
+
promotion_content_sha256: value.promotion_content_sha256,
|
|
70
|
+
activated_at: new Date(activatedAt).toISOString(),
|
|
71
|
+
...(value.source === "npm-prerelease" ? {
|
|
72
|
+
npm_dist_tag: value.npm_dist_tag,
|
|
73
|
+
published_at: new Date(Date.parse(value.published_at)).toISOString(),
|
|
74
|
+
} : {}),
|
|
75
|
+
...(workspaceHash ? { workspace_hash: workspaceHash } : {}),
|
|
76
|
+
...(runtimeEntry ? { runtime_entry: runtimeEntry } : {}),
|
|
77
|
+
...(previous ? { previous } : {}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -3,9 +3,11 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { readBoundedRegularFileSync } from "../src/local/secure-file.mjs";
|
|
6
|
+
import { createTrustedGitResolver } from "../src/local/trusted-git-executable.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const selfPath = "scripts/privacy-check.mjs";
|
|
10
|
+
const gitExecutable = createTrustedGitResolver({ workspace: root });
|
|
9
11
|
const requestedOptions = new Set(process.argv.slice(2));
|
|
10
12
|
for (const option of requestedOptions) {
|
|
11
13
|
if (option !== "--history") throw new Error(`unknown privacy-check option: ${option}`);
|
|
@@ -68,7 +70,7 @@ process.stderr.write(`privacy check ok (${candidates.length} tracked/unignored f
|
|
|
68
70
|
|
|
69
71
|
function collectCandidateFiles(directory) {
|
|
70
72
|
try {
|
|
71
|
-
return execFileSync(
|
|
73
|
+
return execFileSync(gitExecutable(), ["-C", directory, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
72
74
|
encoding: "buffer",
|
|
73
75
|
maxBuffer: 32 * 1024 * 1024,
|
|
74
76
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -98,7 +100,7 @@ function collectCandidateFiles(directory) {
|
|
|
98
100
|
function scanReachableHistory(directory, entries, out) {
|
|
99
101
|
let listing;
|
|
100
102
|
try {
|
|
101
|
-
listing = execFileSync(
|
|
103
|
+
listing = execFileSync(gitExecutable(), ["-C", directory, "rev-list", "--objects", "--all", "-z"], {
|
|
102
104
|
encoding: "buffer",
|
|
103
105
|
maxBuffer: 64 * 1024 * 1024,
|
|
104
106
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -130,7 +132,7 @@ function scanReachableHistory(directory, entries, out) {
|
|
|
130
132
|
const hashes = [...objectPaths.keys()];
|
|
131
133
|
let metadata;
|
|
132
134
|
try {
|
|
133
|
-
metadata = execFileSync(
|
|
135
|
+
metadata = execFileSync(gitExecutable(), ["-C", directory, "cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"], {
|
|
134
136
|
input: `${hashes.join("\n")}\n`,
|
|
135
137
|
encoding: "utf8",
|
|
136
138
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -162,7 +164,7 @@ function scanReachableHistory(directory, entries, out) {
|
|
|
162
164
|
}
|
|
163
165
|
let buffer;
|
|
164
166
|
try {
|
|
165
|
-
buffer = execFileSync(
|
|
167
|
+
buffer = execFileSync(gitExecutable(), ["-C", directory, "cat-file", "blob", hash], {
|
|
166
168
|
encoding: "buffer",
|
|
167
169
|
maxBuffer: 6 * 1024 * 1024,
|
|
168
170
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -194,7 +196,7 @@ function scanReachableHistory(directory, entries, out) {
|
|
|
194
196
|
function scanReachableCommitMessages(directory, entries, out) {
|
|
195
197
|
let output;
|
|
196
198
|
try {
|
|
197
|
-
output = execFileSync(
|
|
199
|
+
output = execFileSync(gitExecutable(), ["-C", directory, "log", "--all", "--format=%H%x00%B%x00"], {
|
|
198
200
|
encoding: "buffer",
|
|
199
201
|
maxBuffer: 64 * 1024 * 1024,
|
|
200
202
|
stdio: ["ignore", "pipe", "pipe"],
|