@nurix/nustack 0.23.12 → 0.23.14
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/dist/commands/agent.js +1 -53
- package/dist/commands/check.js +73 -0
- package/dist/commands/deploy.js +2 -1
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/harness.js +127 -48
- package/dist/commands/init.js +103 -3
- package/dist/commands/login.js +9 -2
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/project-sync.js +2 -1
- package/dist/commands/read.js +19 -0
- package/dist/commands/records.js +10 -0
- package/dist/commands/review.js +3 -2
- package/dist/commands/sessions.js +4 -1
- package/dist/commands/sync.js +2 -1
- package/dist/index.js +4 -0
- package/dist/lib/agent/claudeHooks.js +4 -1
- package/dist/lib/agent/desktopDoor.js +55 -0
- package/dist/lib/auth_grant.js +15 -3
- package/dist/lib/auth_recovery.js +10 -3
- package/dist/lib/etna/manager.js +24 -6
- package/dist/lib/etna/profile.js +6 -0
- package/dist/lib/etna/reconcile.js +6 -0
- package/dist/lib/gate/bulkReadRelay.js +354 -0
- package/dist/lib/gate/hook.js +30 -3
- package/dist/lib/gate/outline.js +12 -0
- package/dist/lib/gate/settings.js +24 -1
- package/dist/lib/gate/tierOne.js +22 -0
- package/dist/lib/harness/codes.js +24 -0
- package/dist/lib/harness/hookCheck.js +325 -0
- package/dist/lib/harness/hookEntry.js +27 -0
- package/dist/lib/harness/legacyMount.js +231 -0
- package/dist/lib/harness/mount.js +199 -131
- package/dist/lib/harness/storeWaivers.js +43 -0
- package/dist/lib/harness/vendor.js +29 -0
- package/dist/lib/harness/versionsClient.js +54 -0
- package/dist/lib/local_store/database.js +18 -0
- package/dist/lib/local_store/routing.js +26 -0
- package/dist/lib/nustack_client.js +35 -2
- package/dist/lib/onboarding/attach_existing_git.js +14 -2
- package/dist/lib/onboarding/binding.js +46 -2
- package/dist/lib/onboarding/commit_trailer_hook.js +66 -0
- package/dist/lib/onboarding/manifest_writer.js +19 -3
- package/dist/lib/onboarding/session_hook.js +76 -0
- package/dist/lib/origin_guard.js +43 -0
- package/dist/lib/project_contract.js +104 -0
- package/dist/lib/records/door.js +26 -19
- package/dist/lib/records/drain.js +10 -4
- package/dist/lib/records/refusalLog.js +30 -1
- package/dist/lib/session-engine/supervisor.js +6 -18
- package/dist/lib/sessions/client.js +0 -12
- package/dist/program.js +27 -4
- package/dist/vendor/harness/assemble/runtimes.js +32 -0
- package/dist/vendor/harness/assemble/settingsMerge.js +136 -0
- package/dist/vendor/harness/digest/index.js +38 -0
- package/dist/vendor/harness/lib/findings.js +32 -0
- package/dist/vendor/harness/lib/frontmatter.js +79 -0
- package/dist/vendor/harness/lib/paths.js +31 -0
- package/dist/vendor/harness/mount/cache.js +65 -0
- package/dist/vendor/harness/mount/exclude.js +77 -0
- package/dist/vendor/harness/mount/files.js +38 -0
- package/dist/vendor/harness/mount/index.js +352 -0
- package/dist/vendor/harness/rules/changeRules.js +162 -0
- package/dist/vendor/harness/rules/context.js +38 -0
- package/dist/vendor/harness/rules/docsRoot.js +34 -0
- package/dist/vendor/harness/rules/index.js +109 -0
- package/dist/vendor/harness/rules/node.js +144 -0
- package/dist/vendor/harness/rules/outputGate.js +28 -0
- package/dist/vendor/harness/rules/ownership.js +178 -0
- package/dist/vendor/harness/rules/packageTable.js +113 -0
- package/dist/vendor/harness/rules/plans.js +96 -0
- package/dist/vendor/harness/rules/sessionWalls.js +31 -0
- package/package.json +5 -5
package/dist/commands/review.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ensureAccessToken } from "../lib/auth_grant.js";
|
|
|
2
2
|
import { resolveNuStackUrl } from "../lib/nustack_client.js";
|
|
3
3
|
import { makeReviewClient, ReviewUnreachableError } from "../lib/review/client.js";
|
|
4
4
|
import { printJsonSuccess } from "../lib/json_output.js";
|
|
5
|
+
import { nullUnlessOriginRefused } from "../lib/origin_guard.js";
|
|
5
6
|
const EXIT_FOR = {
|
|
6
7
|
signed: 0,
|
|
7
8
|
refused: 1,
|
|
@@ -56,7 +57,7 @@ export async function runReviewCheck(options, globals) {
|
|
|
56
57
|
process.exitCode = EXIT_FOR[state];
|
|
57
58
|
};
|
|
58
59
|
const baseUrl = resolveNuStackUrl(globals);
|
|
59
|
-
const deviceAuth = await ensureAccessToken().catch(
|
|
60
|
+
const deviceAuth = await ensureAccessToken().catch(nullUnlessOriginRefused);
|
|
60
61
|
if (!deviceAuth)
|
|
61
62
|
return report("unreachable");
|
|
62
63
|
try {
|
|
@@ -124,7 +125,7 @@ export async function runReviewMirror(options, globals) {
|
|
|
124
125
|
if (!sessionId)
|
|
125
126
|
return mirrorReport(json, "skipped", null, "--session is required");
|
|
126
127
|
const baseUrl = resolveNuStackUrl(globals);
|
|
127
|
-
const deviceAuth = await ensureAccessToken().catch(
|
|
128
|
+
const deviceAuth = await ensureAccessToken().catch(nullUnlessOriginRefused);
|
|
128
129
|
if (!deviceAuth)
|
|
129
130
|
return mirrorReport(json, "skipped", null, "signed out");
|
|
130
131
|
const client = makeReviewClient(baseUrl, deviceAuth.accessToken);
|
|
@@ -5,17 +5,20 @@ import { ensureAccessToken } from "../lib/auth_grant.js";
|
|
|
5
5
|
import { isInteractive } from "../lib/interactive.js";
|
|
6
6
|
import { resolveNuStackUrl } from "../lib/nustack_client.js";
|
|
7
7
|
import { makeSessionsClient, sessionsClientFromEnv } from "../lib/sessions/client.js";
|
|
8
|
+
import { mountProjectHarness } from "../lib/harness/mount.js";
|
|
8
9
|
import { recordOutsideSessions, } from "../lib/sessions/outsideSessions.js";
|
|
9
10
|
import { inspectSession, readOrphanedWorktrees, reclaimSession, } from "../lib/session-engine/prune.js";
|
|
10
11
|
import { readSessionRegistry, sessionRegistryPath } from "../lib/session-engine/registry.js";
|
|
11
12
|
import { SessionSupervisor } from "../lib/session-engine/supervisor.js";
|
|
12
13
|
import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
|
|
14
|
+
import { nullUnlessOriginRefused } from "../lib/origin_guard.js";
|
|
13
15
|
export async function runSessionsServe(engineVersion) {
|
|
14
16
|
const supervisor = new SessionSupervisor({
|
|
15
17
|
input: process.stdin,
|
|
16
18
|
output: process.stdout,
|
|
17
19
|
engineVersion,
|
|
18
20
|
sessions: sessionsClientFromEnv(process.env),
|
|
21
|
+
mountHarness: mountProjectHarness,
|
|
19
22
|
});
|
|
20
23
|
await supervisor.run();
|
|
21
24
|
process.exit(0);
|
|
@@ -69,7 +72,7 @@ export async function runSessionsRecord(options, globals) {
|
|
|
69
72
|
const transcripts = readTranscripts(payload?.sessions);
|
|
70
73
|
if (transcripts.length === 0)
|
|
71
74
|
return report("sessions record", EMPTY, json, "nothing to record");
|
|
72
|
-
const deviceAuth = await ensureAccessToken().catch(
|
|
75
|
+
const deviceAuth = await ensureAccessToken().catch(nullUnlessOriginRefused);
|
|
73
76
|
if (!deviceAuth)
|
|
74
77
|
return report("sessions record", EMPTY, json, "signed out");
|
|
75
78
|
const summary = await recordOutsideSessions(makeSessionsClient(baseUrl, deviceAuth.accessToken), {
|
package/dist/commands/sync.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ensureAccessToken, signedOutCopy } from "../lib/auth_grant.js";
|
|
|
6
6
|
import { resolveProjectId } from "../lib/project_context.js";
|
|
7
7
|
import { readManagedValues, writeManagedValues } from "../lib/env_file.js";
|
|
8
8
|
import { printJsonError, printJsonSuccess } from "../lib/json_output.js";
|
|
9
|
+
import { nullUnlessOriginRefused } from "../lib/origin_guard.js";
|
|
9
10
|
const COMMAND = "sync";
|
|
10
11
|
const signedOut = () => signedOutCopy();
|
|
11
12
|
export async function runSync(options) {
|
|
@@ -14,7 +15,7 @@ export async function runSync(options) {
|
|
|
14
15
|
const json = options.json === true;
|
|
15
16
|
if (!json)
|
|
16
17
|
p.intro("nustack sync");
|
|
17
|
-
const deviceAuth = await ensureAccessToken().catch(
|
|
18
|
+
const deviceAuth = await ensureAccessToken().catch(nullUnlessOriginRefused);
|
|
18
19
|
if (!deviceAuth)
|
|
19
20
|
return failSync(json, "SIGNED_OUT", signedOut(), "The managed block was not touched.");
|
|
20
21
|
const accessToken = deviceAuth.accessToken;
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,10 @@ if (command === "gate" && subcommand !== undefined && HOOK_SUBCOMMANDS.includes(
|
|
|
5
5
|
const { runGateHook } = await import("./lib/gate/hook.js");
|
|
6
6
|
await runGateHook(subcommand);
|
|
7
7
|
}
|
|
8
|
+
else if (command === "check" && process.argv.includes("--hook")) {
|
|
9
|
+
const { runHookMode } = await import("./lib/harness/hookEntry.js");
|
|
10
|
+
await runHookMode(process.argv[process.argv.indexOf("--hook") + 1] ?? "", process.argv.includes("--protection"));
|
|
11
|
+
}
|
|
8
12
|
else {
|
|
9
13
|
await import("./program.js");
|
|
10
14
|
}
|
|
@@ -9,6 +9,9 @@ function managedEntry(matcher, command, options) {
|
|
|
9
9
|
hook.timeout = options.timeout;
|
|
10
10
|
return { matcher, hooks: [hook] };
|
|
11
11
|
}
|
|
12
|
+
function isManagedCommand(command) {
|
|
13
|
+
return command.trimEnd().endsWith(`# ${MANAGED_MARKER}`);
|
|
14
|
+
}
|
|
12
15
|
function carriesMarker(entry) {
|
|
13
16
|
if (entry === null || typeof entry !== "object")
|
|
14
17
|
return false;
|
|
@@ -18,7 +21,7 @@ function carriesMarker(entry) {
|
|
|
18
21
|
return hooks.some((hook) => hook !== null &&
|
|
19
22
|
typeof hook === "object" &&
|
|
20
23
|
typeof hook.command === "string" &&
|
|
21
|
-
hook.command
|
|
24
|
+
isManagedCommand(hook.command));
|
|
22
25
|
}
|
|
23
26
|
function isManagedEntry(entry, matcher) {
|
|
24
27
|
if (!carriesMarker(entry))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const PROBE_TIMEOUT_MS = 1500;
|
|
5
|
+
export function desktopConfigDir() {
|
|
6
|
+
return (process.env.NUSTACK_DESKTOP_CONFIG_DIR ||
|
|
7
|
+
path.join(os.homedir(), "Library", "Application Support", "co.nurix.nustack"));
|
|
8
|
+
}
|
|
9
|
+
export async function readServerRecord() {
|
|
10
|
+
const recordPath = path.join(desktopConfigDir(), "server.json");
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(await readFile(recordPath, "utf8"));
|
|
13
|
+
if (typeof parsed.port !== "number" ||
|
|
14
|
+
typeof parsed.token !== "string" ||
|
|
15
|
+
typeof parsed.pid !== "number" ||
|
|
16
|
+
typeof parsed.startedAt !== "string") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function isPidAlive(pid) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return error.code === "EPERM";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function isRosterProject(value) {
|
|
35
|
+
return (value !== null &&
|
|
36
|
+
typeof value === "object" &&
|
|
37
|
+
typeof value.path === "string" &&
|
|
38
|
+
typeof value.label === "string");
|
|
39
|
+
}
|
|
40
|
+
export async function probeProjects(record) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(`http://127.0.0.1:${record.port}/api/projects`, {
|
|
43
|
+
headers: { Authorization: `Bearer ${record.token}` },
|
|
44
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok)
|
|
47
|
+
return null;
|
|
48
|
+
const body = (await response.json());
|
|
49
|
+
const projects = body.data?.projects;
|
|
50
|
+
return Array.isArray(projects) ? projects.filter(isRosterProject) : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/lib/auth_grant.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
3
3
|
import { getOrCreateDeviceId } from "./device_identity.js";
|
|
4
4
|
import { deleteDeviceCredential, readCredentialFailure, readDeviceCredential, writeDeviceCredential } from "./device_keychain.js";
|
|
5
5
|
import { openLoopbackListener } from "./loopback.js";
|
|
6
|
+
import { originMismatchError, refusedContractOrigin } from "./origin_guard.js";
|
|
6
7
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
7
8
|
const BOOTSTRAP_TIMEOUT_MS = 25_000;
|
|
8
9
|
const LOOPBACK_TIMEOUT_MS = 10 * 60_000;
|
|
@@ -160,11 +161,14 @@ export async function readStoredCredential() {
|
|
|
160
161
|
credential: { deviceToken: credential.device_token, studioBaseUrl: credential.studio_base_url, deviceId, userEmail: credential.user_email },
|
|
161
162
|
};
|
|
162
163
|
}
|
|
163
|
-
export async function resolveAccessToken() {
|
|
164
|
+
export async function resolveAccessToken(origin) {
|
|
164
165
|
const fromEnv = envToken();
|
|
165
166
|
if (fromEnv) {
|
|
166
167
|
if (fromEnv.kind === "malformed")
|
|
167
168
|
return { kind: "malformed_token", message: fromEnv.message };
|
|
169
|
+
const refused = refusedContractOrigin(null, origin);
|
|
170
|
+
if (refused)
|
|
171
|
+
return { kind: "origin_mismatch", error: originMismatchError(refused, null, "pat") };
|
|
168
172
|
const deviceId = resolvedDeviceId ?? (await getOrCreateDeviceId());
|
|
169
173
|
resolvedDeviceId = deviceId;
|
|
170
174
|
return { kind: "ok", session: { accessToken: fromEnv.token, userEmail: "", deviceId } };
|
|
@@ -173,12 +177,20 @@ export async function resolveAccessToken() {
|
|
|
173
177
|
if (!credential) {
|
|
174
178
|
if (failure)
|
|
175
179
|
lastDeadReason = failure;
|
|
180
|
+
const refused = refusedContractOrigin(null, origin);
|
|
181
|
+
if (refused)
|
|
182
|
+
return { kind: "origin_mismatch", error: originMismatchError(refused, null, "none") };
|
|
176
183
|
return { kind: "none", reason: failure };
|
|
177
184
|
}
|
|
185
|
+
const refused = refusedContractOrigin(credential.studioBaseUrl, origin);
|
|
186
|
+
if (refused)
|
|
187
|
+
return { kind: "origin_mismatch", error: originMismatchError(refused, credential.studioBaseUrl, "device") };
|
|
178
188
|
return { kind: "ok", session: { accessToken: credential.deviceToken, userEmail: credential.userEmail, deviceId } };
|
|
179
189
|
}
|
|
180
|
-
export async function ensureAccessToken() {
|
|
181
|
-
const outcome = await resolveAccessToken();
|
|
190
|
+
export async function ensureAccessToken(origin) {
|
|
191
|
+
const outcome = await resolveAccessToken(origin);
|
|
192
|
+
if (outcome.kind === "origin_mismatch")
|
|
193
|
+
throw outcome.error;
|
|
182
194
|
return outcome.kind === "ok" ? { accessToken: outcome.session.accessToken, userEmail: outcome.session.userEmail } : null;
|
|
183
195
|
}
|
|
184
196
|
export async function forgetRefusedToken(staleToken) {
|
|
@@ -2,6 +2,7 @@ import * as p from "@clack/prompts";
|
|
|
2
2
|
import { deadCredentialCopy, ensureAccessToken, resolveAccessToken } from "./auth_grant.js";
|
|
3
3
|
import { performDeviceLogin } from "../commands/login.js";
|
|
4
4
|
import { isInteractive } from "./interactive.js";
|
|
5
|
+
import { originMismatchError, refusedContractOrigin } from "./origin_guard.js";
|
|
5
6
|
let lastFailure = { code: "SIGNED_OUT", message: deadCredentialCopy(null), retryable: false };
|
|
6
7
|
export function lastAuthFailure() {
|
|
7
8
|
return lastFailure;
|
|
@@ -12,6 +13,8 @@ function describeFailure(outcome) {
|
|
|
12
13
|
}
|
|
13
14
|
if (outcome.kind === "malformed_token")
|
|
14
15
|
return { code: "TOKEN_MALFORMED", message: outcome.message, retryable: false };
|
|
16
|
+
if (outcome.kind === "origin_mismatch")
|
|
17
|
+
return { code: "ORIGIN_MISMATCH", message: outcome.error.message, retryable: false };
|
|
15
18
|
return { code: "SIGNED_OUT", message: deadCredentialCopy(outcome.reason), retryable: false };
|
|
16
19
|
}
|
|
17
20
|
function reconnectingCopy(outcome) {
|
|
@@ -24,7 +27,7 @@ function reconnectingCopy(outcome) {
|
|
|
24
27
|
export async function ensureAuthWithRecovery(baseUrl, json) {
|
|
25
28
|
let outcome;
|
|
26
29
|
try {
|
|
27
|
-
outcome = await resolveAccessToken();
|
|
30
|
+
outcome = await resolveAccessToken(baseUrl);
|
|
28
31
|
}
|
|
29
32
|
catch {
|
|
30
33
|
lastFailure = describeFailure("unreachable");
|
|
@@ -33,13 +36,17 @@ export async function ensureAuthWithRecovery(baseUrl, json) {
|
|
|
33
36
|
if (outcome.kind === "ok")
|
|
34
37
|
return { accessToken: outcome.session.accessToken, userEmail: outcome.session.userEmail };
|
|
35
38
|
lastFailure = describeFailure(outcome);
|
|
36
|
-
if (json || !isInteractive() || outcome.kind === "malformed_token")
|
|
39
|
+
if (json || !isInteractive() || outcome.kind === "malformed_token" || outcome.kind === "origin_mismatch")
|
|
37
40
|
return null;
|
|
41
|
+
if (refusedContractOrigin(null, baseUrl)) {
|
|
42
|
+
lastFailure = describeFailure({ kind: "origin_mismatch", error: originMismatchError(baseUrl, null, "none") });
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
38
45
|
p.log.warn(reconnectingCopy(outcome));
|
|
39
46
|
const didLogin = await performDeviceLogin(baseUrl, process.cwd(), { reauthorize: true });
|
|
40
47
|
if (!didLogin)
|
|
41
48
|
return null;
|
|
42
|
-
return ensureAccessToken().catch(() => {
|
|
49
|
+
return ensureAccessToken(baseUrl).catch(() => {
|
|
43
50
|
lastFailure = describeFailure("unreachable");
|
|
44
51
|
return null;
|
|
45
52
|
});
|
package/dist/lib/etna/manager.js
CHANGED
|
@@ -2,7 +2,17 @@ import { detectPackageManager, installPackage } from "../package_manager.js";
|
|
|
2
2
|
import { assertSafePackageName } from "../guards.js";
|
|
3
3
|
import { updateBindingEtnaProfile } from "../local_store/bindings.js";
|
|
4
4
|
import { serializeProfileForBinding } from "./profile.js";
|
|
5
|
-
import {
|
|
5
|
+
import { mountProjectHarness } from "../harness/mount.js";
|
|
6
|
+
export function profileNamesHarness(profile) {
|
|
7
|
+
return Boolean(profile.harnessDigest || profile.versionManifestPath || profile.manifestUrl);
|
|
8
|
+
}
|
|
9
|
+
export function mountReportOf(outcome) {
|
|
10
|
+
return { status: outcome.status, source: outcome.source, version: outcome.version, digest: outcome.digest };
|
|
11
|
+
}
|
|
12
|
+
export function mountFailureReport(error) {
|
|
13
|
+
const code = error?.code;
|
|
14
|
+
return { status: "failed", code: typeof code === "string" ? code : "HARNESS_MOUNT_FAILED", message: error instanceof Error ? error.message : String(error) };
|
|
15
|
+
}
|
|
6
16
|
export function toolkitPackageName(profile) {
|
|
7
17
|
if (!profile.name.startsWith("@"))
|
|
8
18
|
return null;
|
|
@@ -16,14 +26,22 @@ export function toolkitPackageName(profile) {
|
|
|
16
26
|
export async function installToolkit(store, bindingId, profile, cwd, deps = {}) {
|
|
17
27
|
const packageName = toolkitPackageName(profile);
|
|
18
28
|
let result;
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
if (deps.skipMount) {
|
|
30
|
+
result = { ok: true, installed: false, skipped: true, reason: "skipped_etna_mount", mount: { status: "skipped_etna_mount" } };
|
|
31
|
+
}
|
|
32
|
+
else if (profileNamesHarness(profile)) {
|
|
21
33
|
try {
|
|
22
|
-
await (deps.mount ??
|
|
23
|
-
|
|
34
|
+
const outcome = await (deps.mount ?? mountProjectHarness)(cwd, {
|
|
35
|
+
projectId: deps.projectId,
|
|
36
|
+
globals: deps.globals,
|
|
37
|
+
writeContract: deps.writeContract ?? "never",
|
|
38
|
+
runtime: "claude-code",
|
|
39
|
+
});
|
|
40
|
+
result = { ok: true, installed: true, skipped: false, reason: "mounted", mount: mountReportOf(outcome) };
|
|
24
41
|
}
|
|
25
42
|
catch (error) {
|
|
26
|
-
|
|
43
|
+
const mount = mountFailureReport(error);
|
|
44
|
+
result = { ok: false, installed: false, skipped: false, reason: mount.message, mount };
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
else if (!packageName) {
|
package/dist/lib/etna/profile.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getProject } from "../nustack_client.js";
|
|
2
|
+
import { contractFieldsFromSummary } from "../project_contract.js";
|
|
2
3
|
export class ToolkitProfileUnavailableError extends Error {
|
|
3
4
|
projectId;
|
|
4
5
|
constructor(projectId) {
|
|
@@ -21,6 +22,11 @@ export async function fetchToolkitProfile(baseUrl, accessToken, projectId) {
|
|
|
21
22
|
catalogVersionId: null,
|
|
22
23
|
harnessVersion: project.harness?.version ?? null,
|
|
23
24
|
manifestUrl: project.harness?.manifestUrl ?? null,
|
|
25
|
+
harnessTrack: project.harness?.track ?? null,
|
|
26
|
+
harnessDigest: project.harness?.digest ?? null,
|
|
27
|
+
versionManifestPath: project.harness?.versionManifestPath ?? null,
|
|
28
|
+
branchPolicy: project.branchPolicy === "trunk" || project.branchPolicy === "branch" ? project.branchPolicy : null,
|
|
29
|
+
contractFields: contractFieldsFromSummary(project),
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
export function toolkitProfileFromManifest(manifest) {
|
|
@@ -22,6 +22,12 @@ export function parseRecordedToolkit(binding) {
|
|
|
22
22
|
export function toolkitCompatibility(mounted, cloud) {
|
|
23
23
|
if (!mounted || !cloud)
|
|
24
24
|
return { compatible: true, blocksWrites: false, reason: null };
|
|
25
|
+
if (mounted.digest && cloud.harnessDigest) {
|
|
26
|
+
if (mounted.digest !== cloud.harnessDigest) {
|
|
27
|
+
return { compatible: false, blocksWrites: true, reason: `mounted harness digest ${mounted.digest.slice(0, 12)} differs from the expected ${cloud.harnessDigest.slice(0, 12)}` };
|
|
28
|
+
}
|
|
29
|
+
return { compatible: true, blocksWrites: false, reason: null };
|
|
30
|
+
}
|
|
25
31
|
const pinned = cloud.harnessVersion ?? cloud.version;
|
|
26
32
|
if (mounted.version !== pinned) {
|
|
27
33
|
return { compatible: false, blocksWrites: true, reason: `mounted harness ${mounted.version} differs from the cloud pin ${pinned}` };
|