@hashicorp/kits 0.1.11 → 0.1.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/README.md +14 -1
- package/dist/adapters/base-adapter.d.ts.map +1 -1
- package/dist/adapters/base-adapter.js +6 -1
- package/dist/adapters/base-adapter.js.map +1 -1
- package/dist/adapters/gemini-cli/index.d.ts.map +1 -1
- package/dist/adapters/gemini-cli/index.js +2 -1
- package/dist/adapters/gemini-cli/index.js.map +1 -1
- package/dist/adapters/github-copilot/index.d.ts.map +1 -1
- package/dist/adapters/github-copilot/index.js +2 -1
- package/dist/adapters/github-copilot/index.js.map +1 -1
- package/dist/adapters/github-copilot/installer.js +1 -1
- package/dist/adapters/github-copilot/installer.js.map +1 -1
- package/dist/adapters/opencode/index.d.ts.map +1 -1
- package/dist/adapters/opencode/index.js +2 -1
- package/dist/adapters/opencode/index.js.map +1 -1
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/install.js +101 -3
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/upgrade.d.ts.map +1 -1
- package/dist/cli/upgrade.js +476 -1
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/core/hook-input.d.ts +28 -0
- package/dist/core/hook-input.d.ts.map +1 -0
- package/dist/core/hook-input.js +86 -0
- package/dist/core/hook-input.js.map +1 -0
- package/dist/core/hook-instance.d.ts +4 -0
- package/dist/core/hook-instance.d.ts.map +1 -1
- package/dist/core/hook-instance.js +32 -0
- package/dist/core/hook-instance.js.map +1 -1
- package/dist/core/mcp-instance.d.ts +4 -0
- package/dist/core/mcp-instance.d.ts.map +1 -1
- package/dist/core/mcp-instance.js +33 -0
- package/dist/core/mcp-instance.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/upgrade-executor.d.ts +6 -1
- package/dist/core/upgrade-executor.d.ts.map +1 -1
- package/dist/core/upgrade-executor.js +107 -7
- package/dist/core/upgrade-executor.js.map +1 -1
- package/dist/lockfile/types.d.ts +2 -0
- package/dist/lockfile/types.d.ts.map +1 -1
- package/dist/lockfile/types.js.map +1 -1
- package/dist/tui/hook-prompt.d.ts +23 -0
- package/dist/tui/hook-prompt.d.ts.map +1 -0
- package/dist/tui/hook-prompt.js +239 -0
- package/dist/tui/hook-prompt.js.map +1 -0
- package/dist/tui/index.d.ts +2 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +3 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/upgrade.d.ts +9 -0
- package/dist/tui/upgrade.d.ts.map +1 -1
- package/dist/tui/upgrade.js +29 -0
- package/dist/tui/upgrade.js.map +1 -1
- package/dist/validation/validate-subagents.d.ts +0 -1
- package/dist/validation/validate-subagents.d.ts.map +1 -1
- package/dist/validation/validate-subagents.js +0 -8
- package/dist/validation/validate-subagents.js.map +1 -1
- package/package.json +1 -1
- package/schemas/hook-program.schema.v1.0.0.json +50 -3
- package/schemas/kit.schema.v1.0.0.json +2 -2
- package/schemas/kits-lock.schema.v1.0.0.json +8 -0
- package/schemas/kits-registry.schema.v1.0.0.json +3 -3
- package/schemas/subagent.schema.v1.0.0.json +4 -0
package/dist/cli/upgrade.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Checks for and applies primitive upgrades.
|
|
5
5
|
*/
|
|
6
6
|
import * as clack from "@clack/prompts";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
7
9
|
import pc from "picocolors";
|
|
8
10
|
import { ExitCode, } from "./types.js";
|
|
9
11
|
import { readManifest, writeManifest, getManifestPath } from "../manifest/index.js";
|
|
@@ -13,8 +15,14 @@ import { writeLockfile } from "../lockfile/write.js";
|
|
|
13
15
|
import { checkForUpgrades } from "../lockfile/upgrade-check.js";
|
|
14
16
|
import { fetchSource, scanKits, NoFetcherError, SourceParseError, } from "../discovery/index.js";
|
|
15
17
|
import { PrimitivesRegistryLoader } from "../resolution/primitives-registry.js";
|
|
18
|
+
import { resolveRegistryPrimitivePath } from "../resolution/primitive-paths.js";
|
|
19
|
+
import { resolveEnvVarsFromConfig } from "../resolution/index.js";
|
|
20
|
+
import { hashMcpConfig, hashMcpInputSchema } from "../core/mcp-instance.js";
|
|
21
|
+
import { hashHookProgram, hashHookInputSchema } from "../core/hook-instance.js";
|
|
22
|
+
import { buildHookInputDefaults, buildHookDefaultArgMap, buildHookDefaultEnvMap, } from "../core/hook-input.js";
|
|
16
23
|
import { toUpgradeDisplayEntry, selectUpgrades, displayUpgradeCheck, displayNoUpgrades, upgradesToJson, filterUpgradesByHarness, getUpgradeGroups, } from "../tui/upgrade-select.js";
|
|
17
24
|
import { renderUpgradeApplyStart, renderUpgradeApplyResults, renderUpgradeProcessComplete, renderUpgradeError, } from "../tui/upgrade.js";
|
|
25
|
+
import { promptForEnvVars, promptForHookInputs, promptForUpgradeConfigUpdates, } from "../tui/index.js";
|
|
18
26
|
import { globalRegistry } from "../adapters/registry.js";
|
|
19
27
|
import { executeUpgrades } from "../core/upgrade-executor.js";
|
|
20
28
|
import { debugLog, enableDebugLogging, getDebugLogPath, checkDebugLogWritable, } from "../core/debug.js";
|
|
@@ -48,6 +56,238 @@ function filterLockfileBySource(lockfile, source) {
|
|
|
48
56
|
}
|
|
49
57
|
return filtered;
|
|
50
58
|
}
|
|
59
|
+
function upgradeKey(check) {
|
|
60
|
+
return `${check.harness}:${check.kit}:${check.type}:${check.primitive}`;
|
|
61
|
+
}
|
|
62
|
+
async function loadJsonFile(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
65
|
+
return JSON.parse(content);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function resolvePrimitiveConfigPath(registry, type, name, version) {
|
|
72
|
+
const entry = await registry.getPrimitive(type, name);
|
|
73
|
+
if (!entry) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const versionEntry = entry.versions.find((v) => v.version === version);
|
|
77
|
+
if (!versionEntry) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
let basePath = versionEntry.path;
|
|
81
|
+
if (!path.isAbsolute(basePath)) {
|
|
82
|
+
basePath = entry.path
|
|
83
|
+
? path.join(registry.getBasePath(), entry.path, basePath)
|
|
84
|
+
: path.join(registry.getBasePath(), basePath);
|
|
85
|
+
}
|
|
86
|
+
const configPath = await resolveRegistryPrimitivePath(type, name, basePath);
|
|
87
|
+
return { configPath, basePath };
|
|
88
|
+
}
|
|
89
|
+
function detectNewRequiredEnv(oldDefs = {}, newDefs = {}) {
|
|
90
|
+
const required = [];
|
|
91
|
+
for (const [name, def] of Object.entries(newDefs)) {
|
|
92
|
+
if (!def.required || def.sensitive || def.default !== undefined) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const oldDef = oldDefs[name];
|
|
96
|
+
const oldRequired = oldDef?.required ?? false;
|
|
97
|
+
const oldDefault = oldDef?.default;
|
|
98
|
+
if (!oldDef || !oldRequired || oldDefault !== undefined) {
|
|
99
|
+
required.push(name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return required;
|
|
103
|
+
}
|
|
104
|
+
function buildEnvVarRequirements(resolutions) {
|
|
105
|
+
const requirements = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const resolution of resolutions) {
|
|
108
|
+
for (const resolved of resolution.resolved) {
|
|
109
|
+
if (seen.has(resolved.name))
|
|
110
|
+
continue;
|
|
111
|
+
seen.add(resolved.name);
|
|
112
|
+
requirements.push({
|
|
113
|
+
name: resolved.name,
|
|
114
|
+
description: resolved.description,
|
|
115
|
+
required: resolved.required,
|
|
116
|
+
sensitive: resolved.sensitive,
|
|
117
|
+
defaultValue: resolved.defaultValue,
|
|
118
|
+
currentValue: process.env[resolved.name],
|
|
119
|
+
source: resolution.mcpServer,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return requirements;
|
|
124
|
+
}
|
|
125
|
+
function applyPromptResultsToResolutions(resolutions, promptResults) {
|
|
126
|
+
for (const result of promptResults.variables) {
|
|
127
|
+
for (const resolution of resolutions) {
|
|
128
|
+
const resolved = resolution.resolved.find((r) => r.name === result.name);
|
|
129
|
+
if (!resolved)
|
|
130
|
+
continue;
|
|
131
|
+
switch (result.choice) {
|
|
132
|
+
case "use-existing": {
|
|
133
|
+
if ("value" in resolved) {
|
|
134
|
+
delete resolved.value;
|
|
135
|
+
}
|
|
136
|
+
resolved.usePassthrough = true;
|
|
137
|
+
resolved.source = "environment";
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "use-default": {
|
|
141
|
+
if (result.value !== undefined) {
|
|
142
|
+
resolved.value = result.value;
|
|
143
|
+
}
|
|
144
|
+
else if ("value" in resolved) {
|
|
145
|
+
delete resolved.value;
|
|
146
|
+
}
|
|
147
|
+
resolved.usePassthrough = false;
|
|
148
|
+
resolved.source = "default";
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "enter-new": {
|
|
152
|
+
if (result.value !== undefined) {
|
|
153
|
+
resolved.value = result.value;
|
|
154
|
+
}
|
|
155
|
+
else if ("value" in resolved) {
|
|
156
|
+
delete resolved.value;
|
|
157
|
+
}
|
|
158
|
+
resolved.usePassthrough = false;
|
|
159
|
+
resolved.source = "user-input";
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "skip": {
|
|
163
|
+
if ("value" in resolved) {
|
|
164
|
+
delete resolved.value;
|
|
165
|
+
}
|
|
166
|
+
resolved.usePassthrough = true;
|
|
167
|
+
resolved.source = "passthrough";
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
default:
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const idx = resolution.missingRequired.indexOf(result.name);
|
|
174
|
+
if (idx !== -1) {
|
|
175
|
+
resolution.missingRequired.splice(idx, 1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function buildMcpUpgradePlan(check, lockfile, registry) {
|
|
181
|
+
const kitEntry = lockfile.kits[check.kit];
|
|
182
|
+
const harnessEntry = kitEntry?.harnesses[check.harness];
|
|
183
|
+
const installed = harnessEntry?.primitives.mcp?.find((p) => p.name === check.primitive);
|
|
184
|
+
if (!installed?.instanceRef) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const instanceRef = installed.instanceRef;
|
|
188
|
+
const prefix = `${check.harness}:`;
|
|
189
|
+
const instanceKey = instanceRef.startsWith(prefix) ? instanceRef : `${check.harness}:${instanceRef}`;
|
|
190
|
+
const instanceName = instanceKey.startsWith(prefix)
|
|
191
|
+
? instanceKey.slice(prefix.length)
|
|
192
|
+
: instanceRef;
|
|
193
|
+
const existingInstance = lockfile.mcpInstances?.[instanceKey];
|
|
194
|
+
const oldConfig = existingInstance?.config;
|
|
195
|
+
const oldInputHash = existingInstance?.inputHash ?? (oldConfig ? hashMcpInputSchema(oldConfig) : undefined);
|
|
196
|
+
const resolved = await resolvePrimitiveConfigPath(registry, "mcp", check.primitive, check.availableVersion);
|
|
197
|
+
if (!resolved) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const config = await loadJsonFile(resolved.configPath);
|
|
201
|
+
if (!config) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const inputHash = hashMcpInputSchema(config);
|
|
205
|
+
const needsUpdate = !oldInputHash || oldInputHash !== inputHash;
|
|
206
|
+
const requiredInputs = needsUpdate
|
|
207
|
+
? detectNewRequiredEnv(oldConfig?.env ?? {}, config.env ?? {})
|
|
208
|
+
: [];
|
|
209
|
+
const envResolution = resolveEnvVarsFromConfig(instanceName, config, {});
|
|
210
|
+
const configHash = hashMcpConfig(config, envResolution.resolved, envResolution.resolvedHeaders);
|
|
211
|
+
return {
|
|
212
|
+
kind: "mcp",
|
|
213
|
+
upgradeKey: upgradeKey(check),
|
|
214
|
+
harness: check.harness,
|
|
215
|
+
kit: check.kit,
|
|
216
|
+
primitive: check.primitive,
|
|
217
|
+
instanceName,
|
|
218
|
+
config,
|
|
219
|
+
envResolution,
|
|
220
|
+
inputHash,
|
|
221
|
+
configHash,
|
|
222
|
+
needsUpdate,
|
|
223
|
+
requiredInputs,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async function buildHookUpgradePlan(check, lockfile, registry) {
|
|
227
|
+
const kitEntry = lockfile.kits[check.kit];
|
|
228
|
+
const harnessEntry = kitEntry?.harnesses[check.harness];
|
|
229
|
+
const installed = harnessEntry?.primitives.hooks?.find((p) => p.name === check.primitive);
|
|
230
|
+
if (!installed?.instanceRef || !installed.binding) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const instanceName = installed.instanceRef;
|
|
234
|
+
const existingInstance = lockfile.hookInstances?.[instanceName];
|
|
235
|
+
let oldInputHash = existingInstance?.inputHash;
|
|
236
|
+
if (!oldInputHash) {
|
|
237
|
+
const oldResolved = await resolvePrimitiveConfigPath(registry, "hooks", check.primitive, check.currentVersion);
|
|
238
|
+
if (oldResolved) {
|
|
239
|
+
const oldProgram = await loadJsonFile(oldResolved.configPath);
|
|
240
|
+
if (oldProgram) {
|
|
241
|
+
oldInputHash = hashHookInputSchema(oldProgram);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const resolved = await resolvePrimitiveConfigPath(registry, "hooks", check.primitive, check.availableVersion);
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const program = await loadJsonFile(resolved.configPath);
|
|
250
|
+
if (!program) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const inputHash = hashHookInputSchema(program);
|
|
254
|
+
const needsUpdate = !oldInputHash || oldInputHash !== inputHash;
|
|
255
|
+
const binding = {
|
|
256
|
+
...installed.binding,
|
|
257
|
+
...(installed.binding.env ? { env: { ...installed.binding.env } } : {}),
|
|
258
|
+
...(installed.binding.args ? { args: { ...installed.binding.args } } : {}),
|
|
259
|
+
};
|
|
260
|
+
const defaults = buildHookInputDefaults(program, binding);
|
|
261
|
+
const requiredInputs = needsUpdate ? defaults.missingRequiredEnv : [];
|
|
262
|
+
const programDir = path.dirname(resolved.configPath);
|
|
263
|
+
const entryPath = path.join(programDir, program.runtime.entrypoint);
|
|
264
|
+
let checksum = existingInstance?.checksum ?? "";
|
|
265
|
+
try {
|
|
266
|
+
const entryContents = await fs.readFile(entryPath, "utf-8");
|
|
267
|
+
checksum = hashHookProgram(program, entryContents);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
if (!checksum) {
|
|
271
|
+
checksum = hashHookProgram(program);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
kind: "hooks",
|
|
276
|
+
upgradeKey: upgradeKey(check),
|
|
277
|
+
harness: check.harness,
|
|
278
|
+
kit: check.kit,
|
|
279
|
+
primitive: check.primitive,
|
|
280
|
+
instanceName,
|
|
281
|
+
displayName: program.name ?? check.primitive,
|
|
282
|
+
program,
|
|
283
|
+
binding,
|
|
284
|
+
defaults,
|
|
285
|
+
inputHash,
|
|
286
|
+
checksum,
|
|
287
|
+
needsUpdate,
|
|
288
|
+
requiredInputs,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
51
291
|
/**
|
|
52
292
|
* Run the upgrade command.
|
|
53
293
|
*/
|
|
@@ -221,7 +461,7 @@ export async function runUpgrade(options) {
|
|
|
221
461
|
const kitVersionsBySource = new Map();
|
|
222
462
|
const registryBySource = new Map();
|
|
223
463
|
const checkSourceMap = new Map();
|
|
224
|
-
const checkKey =
|
|
464
|
+
const checkKey = upgradeKey;
|
|
225
465
|
const fetchSpinner = isInteractive ? clack.spinner() : undefined;
|
|
226
466
|
const fetchedSources = [];
|
|
227
467
|
if (fetchSpinner) {
|
|
@@ -552,12 +792,208 @@ export async function runUpgrade(options) {
|
|
|
552
792
|
existing.push(check);
|
|
553
793
|
upgradesBySelectedSource.set(sourceKey, existing);
|
|
554
794
|
}
|
|
795
|
+
const configPlans = [];
|
|
796
|
+
const configPlansByKey = new Map();
|
|
797
|
+
const configPlanErrors = [];
|
|
798
|
+
for (const check of selectedChecks) {
|
|
799
|
+
if (check.type !== "mcp" && check.type !== "hooks") {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const sourceKey = checkSourceMap.get(checkKey(check)) ?? options.source;
|
|
803
|
+
if (!sourceKey) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const registryLoader = registryBySource.get(sourceKey);
|
|
807
|
+
if (!registryLoader) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (check.type === "mcp") {
|
|
811
|
+
const plan = await buildMcpUpgradePlan(check, lockfile, registryLoader);
|
|
812
|
+
if (!plan) {
|
|
813
|
+
configPlanErrors.push(`Unable to resolve MCP configuration for ${check.primitive} (${check.kit})`);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
configPlans.push(plan);
|
|
817
|
+
configPlansByKey.set(plan.upgradeKey, plan);
|
|
818
|
+
}
|
|
819
|
+
else if (check.type === "hooks") {
|
|
820
|
+
const plan = await buildHookUpgradePlan(check, lockfile, registryLoader);
|
|
821
|
+
if (!plan) {
|
|
822
|
+
configPlanErrors.push(`Unable to resolve hook configuration for ${check.primitive} (${check.kit})`);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
configPlans.push(plan);
|
|
826
|
+
configPlansByKey.set(plan.upgradeKey, plan);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (configPlanErrors.length > 0) {
|
|
830
|
+
const message = configPlanErrors.join("; ");
|
|
831
|
+
if (isInteractive) {
|
|
832
|
+
clack.log.error(message);
|
|
833
|
+
clack.outro(pc.red("Upgrade failed"));
|
|
834
|
+
}
|
|
835
|
+
else if (options.json) {
|
|
836
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
console.error(message);
|
|
840
|
+
}
|
|
841
|
+
return { success: false, exitCode: ExitCode.InstallationFailed, error: message };
|
|
842
|
+
}
|
|
843
|
+
const plansNeedingUpdate = configPlans.filter((plan) => plan.needsUpdate);
|
|
844
|
+
if (plansNeedingUpdate.length > 0) {
|
|
845
|
+
const mcpNames = Array.from(new Set(plansNeedingUpdate
|
|
846
|
+
.filter((plan) => plan.kind === "mcp")
|
|
847
|
+
.map((plan) => plan.instanceName)));
|
|
848
|
+
const hookNames = Array.from(new Set(plansNeedingUpdate
|
|
849
|
+
.filter((plan) => plan.kind === "hooks")
|
|
850
|
+
.map((plan) => plan.displayName)));
|
|
851
|
+
const hasRequiredUpdates = plansNeedingUpdate.some((plan) => plan.requiredInputs.length > 0);
|
|
852
|
+
if (isInteractive) {
|
|
853
|
+
const configPrompt = await promptForUpgradeConfigUpdates({
|
|
854
|
+
mcpServers: mcpNames,
|
|
855
|
+
hooks: hookNames,
|
|
856
|
+
requireUpdate: hasRequiredUpdates,
|
|
857
|
+
});
|
|
858
|
+
if (configPrompt.cancelled) {
|
|
859
|
+
clack.outro(pc.yellow("Upgrade cancelled"));
|
|
860
|
+
return { success: true, exitCode: ExitCode.UserCancelled };
|
|
861
|
+
}
|
|
862
|
+
const shouldConfigure = hasRequiredUpdates || configPrompt.configure;
|
|
863
|
+
if (shouldConfigure) {
|
|
864
|
+
const mcpResolutions = plansNeedingUpdate
|
|
865
|
+
.filter((plan) => plan.kind === "mcp")
|
|
866
|
+
.map((plan) => plan.envResolution);
|
|
867
|
+
if (mcpResolutions.length > 0) {
|
|
868
|
+
const requirements = buildEnvVarRequirements(mcpResolutions);
|
|
869
|
+
if (requirements.length > 0) {
|
|
870
|
+
const promptResults = await promptForEnvVars(requirements);
|
|
871
|
+
if (promptResults.cancelled) {
|
|
872
|
+
clack.outro(pc.yellow("Upgrade cancelled"));
|
|
873
|
+
return { success: true, exitCode: ExitCode.UserCancelled };
|
|
874
|
+
}
|
|
875
|
+
applyPromptResultsToResolutions(mcpResolutions, promptResults);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const hookPlans = plansNeedingUpdate.filter((plan) => plan.kind === "hooks");
|
|
879
|
+
if (hookPlans.length > 0) {
|
|
880
|
+
const hookRequests = hookPlans.map((plan) => ({
|
|
881
|
+
key: plan.upgradeKey,
|
|
882
|
+
hookName: plan.displayName,
|
|
883
|
+
kitName: plan.kit,
|
|
884
|
+
defaults: plan.defaults,
|
|
885
|
+
}));
|
|
886
|
+
const promptResults = await promptForHookInputs(hookRequests);
|
|
887
|
+
if (promptResults.cancelled) {
|
|
888
|
+
clack.outro(pc.yellow("Upgrade cancelled"));
|
|
889
|
+
return { success: true, exitCode: ExitCode.UserCancelled };
|
|
890
|
+
}
|
|
891
|
+
for (const plan of hookPlans) {
|
|
892
|
+
const overrides = promptResults.results.get(plan.upgradeKey);
|
|
893
|
+
const resolvedEnv = overrides?.env ?? buildHookDefaultEnvMap(plan.defaults);
|
|
894
|
+
const resolvedArgs = overrides?.args ?? buildHookDefaultArgMap(plan.defaults);
|
|
895
|
+
const nextBinding = { ...plan.binding };
|
|
896
|
+
if (plan.defaults.env.length > 0) {
|
|
897
|
+
if (Object.keys(resolvedEnv).length > 0) {
|
|
898
|
+
nextBinding.env = resolvedEnv;
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
delete nextBinding.env;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (plan.defaults.args.length > 0) {
|
|
905
|
+
nextBinding.args = resolvedArgs;
|
|
906
|
+
}
|
|
907
|
+
plan.binding = nextBinding;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
for (const plan of plansNeedingUpdate) {
|
|
913
|
+
if (plan.kind !== "hooks") {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
const resolvedEnv = buildHookDefaultEnvMap(plan.defaults);
|
|
917
|
+
const resolvedArgs = buildHookDefaultArgMap(plan.defaults);
|
|
918
|
+
const nextBinding = { ...plan.binding };
|
|
919
|
+
if (plan.defaults.env.length > 0) {
|
|
920
|
+
if (Object.keys(resolvedEnv).length > 0) {
|
|
921
|
+
nextBinding.env = resolvedEnv;
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
delete nextBinding.env;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (plan.defaults.args.length > 0) {
|
|
928
|
+
nextBinding.args = resolvedArgs;
|
|
929
|
+
}
|
|
930
|
+
plan.binding = nextBinding;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else if (hasRequiredUpdates) {
|
|
935
|
+
const details = plansNeedingUpdate
|
|
936
|
+
.map((plan) => {
|
|
937
|
+
const label = plan.kind === "mcp" ? plan.instanceName : plan.displayName;
|
|
938
|
+
return `${label}: ${plan.requiredInputs.join(", ")}`;
|
|
939
|
+
})
|
|
940
|
+
.join("; ");
|
|
941
|
+
const message = `Configuration updates required for upgraded primitives: ${details}. ` +
|
|
942
|
+
"Run without -y to configure updated inputs.";
|
|
943
|
+
if (options.json) {
|
|
944
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
console.error(message);
|
|
948
|
+
}
|
|
949
|
+
return { success: false, exitCode: ExitCode.EnvVarRequired, error: message };
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
for (const plan of plansNeedingUpdate) {
|
|
953
|
+
if (plan.kind !== "hooks") {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
const resolvedEnv = buildHookDefaultEnvMap(plan.defaults);
|
|
957
|
+
const resolvedArgs = buildHookDefaultArgMap(plan.defaults);
|
|
958
|
+
const nextBinding = { ...plan.binding };
|
|
959
|
+
if (plan.defaults.env.length > 0) {
|
|
960
|
+
if (Object.keys(resolvedEnv).length > 0) {
|
|
961
|
+
nextBinding.env = resolvedEnv;
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
delete nextBinding.env;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (plan.defaults.args.length > 0) {
|
|
968
|
+
nextBinding.args = resolvedArgs;
|
|
969
|
+
}
|
|
970
|
+
plan.binding = nextBinding;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
for (const plan of plansNeedingUpdate) {
|
|
974
|
+
if (plan.kind === "mcp") {
|
|
975
|
+
plan.configHash = hashMcpConfig(plan.config, plan.envResolution.resolved, plan.envResolution.resolvedHeaders);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
const configOverrides = new Map();
|
|
980
|
+
for (const plan of configPlans) {
|
|
981
|
+
if (plan.kind === "mcp") {
|
|
982
|
+
configOverrides.set(plan.upgradeKey, {
|
|
983
|
+
envResolutions: [plan.envResolution],
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
else if (plan.kind === "hooks") {
|
|
987
|
+
configOverrides.set(plan.upgradeKey, { hookBinding: plan.binding });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
555
990
|
const aggregateResult = {
|
|
556
991
|
success: true,
|
|
557
992
|
upgraded: [],
|
|
558
993
|
failed: [],
|
|
559
994
|
backupPaths: [],
|
|
560
995
|
};
|
|
996
|
+
let configUpdatesApplied = false;
|
|
561
997
|
if (isInteractive) {
|
|
562
998
|
renderUpgradeApplyStart(selectedGroups.length);
|
|
563
999
|
}
|
|
@@ -599,6 +1035,7 @@ export async function runUpgrade(options) {
|
|
|
599
1035
|
}
|
|
600
1036
|
},
|
|
601
1037
|
scope,
|
|
1038
|
+
configOverrides,
|
|
602
1039
|
...(scope === "project" && projectRoot ? { projectRoot } : {}),
|
|
603
1040
|
};
|
|
604
1041
|
const result = await executeUpgrades(checks, adapterMap, registryLoader, lockfile, upgradeOptions);
|
|
@@ -619,6 +1056,41 @@ export async function runUpgrade(options) {
|
|
|
619
1056
|
aggregateResult.upgraded.push(...result.upgraded);
|
|
620
1057
|
aggregateResult.failed.push(...result.failed);
|
|
621
1058
|
aggregateResult.backupPaths.push(...result.backupPaths);
|
|
1059
|
+
for (const upgraded of result.upgraded) {
|
|
1060
|
+
const key = `${upgraded.harness}:${upgraded.kit}:${upgraded.type}:${upgraded.primitive}`;
|
|
1061
|
+
const plan = configPlansByKey.get(key);
|
|
1062
|
+
if (!plan) {
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (plan.kind === "mcp") {
|
|
1066
|
+
const instanceKey = `${plan.harness}:${plan.instanceName}`;
|
|
1067
|
+
const existing = lockfile.mcpInstances?.[instanceKey];
|
|
1068
|
+
if (existing) {
|
|
1069
|
+
existing.config = plan.config;
|
|
1070
|
+
existing.configHash = plan.configHash;
|
|
1071
|
+
existing.inputHash = plan.inputHash;
|
|
1072
|
+
existing.version = upgraded.newVersion;
|
|
1073
|
+
existing.installedAt = new Date().toISOString();
|
|
1074
|
+
configUpdatesApplied = true;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
else if (plan.kind === "hooks") {
|
|
1078
|
+
const existing = lockfile.hookInstances?.[plan.instanceName];
|
|
1079
|
+
if (existing) {
|
|
1080
|
+
existing.checksum = plan.checksum;
|
|
1081
|
+
existing.inputHash = plan.inputHash;
|
|
1082
|
+
existing.version = upgraded.newVersion;
|
|
1083
|
+
configUpdatesApplied = true;
|
|
1084
|
+
}
|
|
1085
|
+
const kitEntry = lockfile.kits[plan.kit];
|
|
1086
|
+
const harnessEntry = kitEntry?.harnesses[plan.harness];
|
|
1087
|
+
const hookEntry = harnessEntry?.primitives.hooks?.find((entry) => entry.name === plan.primitive);
|
|
1088
|
+
if (hookEntry) {
|
|
1089
|
+
hookEntry.binding = plan.binding;
|
|
1090
|
+
configUpdatesApplied = true;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
622
1094
|
}
|
|
623
1095
|
void debugLog({
|
|
624
1096
|
level: "info",
|
|
@@ -662,6 +1134,9 @@ export async function runUpgrade(options) {
|
|
|
662
1134
|
await writeLockfile(lockfile, scope, scope === "project" ? projectRoot : undefined);
|
|
663
1135
|
}
|
|
664
1136
|
}
|
|
1137
|
+
if (configUpdatesApplied) {
|
|
1138
|
+
await writeLockfile(lockfile, scope, scope === "project" ? projectRoot : undefined);
|
|
1139
|
+
}
|
|
665
1140
|
// Display results
|
|
666
1141
|
if (isInteractive) {
|
|
667
1142
|
const failuresByGroup = new Map();
|