fix(config): migrate legacy release arrays during config fixes and validate release config shape

This commit is contained in:
2026-05-14 13:25:56 +00:00
parent 278df40ba7
commit b234ecc12a
3 changed files with 105 additions and 10 deletions
+60 -9
View File
@@ -168,7 +168,7 @@ async function handleInteractiveMenu(): Promise<void> {
{ name: "Configure release workflow", value: "release" },
{ name: "Configure services", value: "services" },
{ name: "Validate configuration (doctor)", value: "doctor" },
{ name: "Fix configuration with opencode", value: "fix" },
{ name: "Fix configuration", value: "fix" },
{ name: "Add an npm target registry", value: "add" },
{ name: "Remove an npm target registry", value: "remove" },
{ name: "Clear npm target registries", value: "clear" },
@@ -939,8 +939,8 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
return;
}
const findings = await collectDoctorFindings();
const counts = countDoctorFindings(findings);
let findings = await collectDoctorFindings();
let counts = countDoctorFindings(findings);
const extraInstructions = (argvArg._?.slice(2).join(" ") || "").trim();
const force = Boolean(argvArg.force);
@@ -954,10 +954,10 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
if (!mode.yes) {
if (!mode.interactive) {
throw new Error("Config fix requires an interactive terminal or `-y` to run opencode non-interactively.");
throw new Error("Config fix requires an interactive terminal or `-y` to run non-interactively.");
}
const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
`Run opencode to fix .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
`Run configuration fixes for .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
true,
);
if (!confirmed) {
@@ -966,6 +966,16 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
}
}
const appliedKnownFixes = await applyKnownConfigFixes(mode);
if (appliedKnownFixes) {
findings = await collectDoctorFindings();
counts = countDoctorFindings(findings);
if (counts.error === 0 && counts.warn === 0 && !extraInstructions && !force) {
printDoctorResult(findings, mode);
return;
}
}
const opencodeArgs = [
"run",
"--title",
@@ -1004,6 +1014,33 @@ async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
printDoctorResult(finalFindings, mode);
}
async function applyKnownConfigFixes(mode: ICliMode): Promise<boolean> {
const smartconfigPath = getSmartconfigPath();
if (!(await plugins.smartfs.file(smartconfigPath).exists())) {
return false;
}
let smartconfigData: Record<string, any>;
try {
smartconfigData = await readSmartconfigFile();
} catch {
return false;
}
const result = migrateSmartconfigData(smartconfigData);
if (!result.migrated) {
return false;
}
await writeSmartconfigFile(smartconfigData);
plugins.logger.log(
"success",
`Applied known .smartconfig.json migrations to schema v${result.toVersion}`,
);
await formatSmartconfigWithDiff(mode);
return true;
}
async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
const findings: IDoctorFinding[] = [];
const smartconfigPath = getSmartconfigPath();
@@ -1071,7 +1108,7 @@ async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
await validateDetectedProjectType(cliConfig, findings);
validateCommitConfig(cliConfig.commit || {}, findings);
await validateReleaseConfig(cliConfig.release || {}, smartconfigData, findings);
await validateReleaseConfig(cliConfig.release, smartconfigData, findings);
return findings;
}
@@ -1570,10 +1607,24 @@ function validateCommitConfig(
}
async function validateReleaseConfig(
releaseConfig: Record<string, any>,
rawReleaseConfig: unknown,
smartconfigData: Record<string, any>,
findings: IDoctorFinding[],
): Promise<void> {
const releaseConfig = rawReleaseConfig === undefined ? {} : rawReleaseConfig;
if (!isPlainObject(releaseConfig)) {
findings.push({
level: "error",
message: `Release config must be an object, found ${
Array.isArray(releaseConfig) ? "array" : typeof releaseConfig
}`,
fix: Array.isArray(releaseConfig)
? "Run `gitzone config migrate` to move legacy registry arrays into release.targets.npm.registries."
: "Set @git.zone/cli.release to an object or remove it.",
});
return;
}
const confirmation = releaseConfig.confirmation;
if (confirmation === undefined || validConfirmationModes.includes(confirmation)) {
findings.push({ level: "ok", message: "Release confirmation mode is valid" });
@@ -1993,7 +2044,7 @@ export function showHelp(mode?: ICliMode): void {
{ name: "cli", description: "Configure CLI behavior interactively" },
{ name: "release", description: "Configure release workflow interactively" },
{ name: "doctor", description: "Validate .smartconfig.json" },
{ name: "fix [instructions]", description: "Use opencode to repair .smartconfig.json" },
{ name: "fix [instructions]", description: "Repair .smartconfig.json" },
{ name: "get <path>", description: "Read a single config value" },
{ name: "set <path> <value>", description: "Write a config value" },
{ name: "unset <path>", description: "Delete a config value" },
@@ -2038,7 +2089,7 @@ export function showHelp(mode?: ICliMode): void {
console.log(" cli Configure CLI behavior interactively");
console.log(" release Configure release workflow interactively");
console.log(" doctor Validate .smartconfig.json");
console.log(" fix [instructions] Use opencode to repair .smartconfig.json");
console.log(" fix [instructions] Repair .smartconfig.json");
console.log(" get <path> Read a single config value");
console.log(" set <path> <value> Write a config value");
console.log(" unset <path> Delete a config value");