|
|
|
@@ -117,6 +117,9 @@ export const run = async (argvArg: any) => {
|
|
|
|
|
case "doctor":
|
|
|
|
|
await handleDoctor(mode);
|
|
|
|
|
break;
|
|
|
|
|
case "fix":
|
|
|
|
|
await handleFix(argvArg, mode);
|
|
|
|
|
break;
|
|
|
|
|
case "migrate":
|
|
|
|
|
await handleMigrate(value, mode);
|
|
|
|
|
break;
|
|
|
|
@@ -165,6 +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: "Add an npm target registry", value: "add" },
|
|
|
|
|
{ name: "Remove an npm target registry", value: "remove" },
|
|
|
|
|
{ name: "Clear npm target registries", value: "clear" },
|
|
|
|
@@ -213,6 +217,9 @@ async function handleInteractiveMenu(): Promise<void> {
|
|
|
|
|
case "doctor":
|
|
|
|
|
await handleDoctor(defaultCliMode);
|
|
|
|
|
break;
|
|
|
|
|
case "fix":
|
|
|
|
|
await handleFix({ _: ["config", "fix"] }, defaultCliMode);
|
|
|
|
|
break;
|
|
|
|
|
case "help":
|
|
|
|
|
showHelp();
|
|
|
|
|
break;
|
|
|
|
@@ -890,6 +897,86 @@ async function handleRelease(mode: ICliMode): Promise<void> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleDoctor(mode: ICliMode): Promise<void> {
|
|
|
|
|
const findings = await collectDoctorFindings();
|
|
|
|
|
printDoctorResult(findings, mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleFix(argvArg: any, mode: ICliMode): Promise<void> {
|
|
|
|
|
if (mode.json) {
|
|
|
|
|
printJson({
|
|
|
|
|
ok: false,
|
|
|
|
|
error: "JSON output is not supported for `gitzone config fix`. Use `gitzone config doctor --json` for machine-readable diagnostics.",
|
|
|
|
|
});
|
|
|
|
|
process.exitCode = 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const findings = await collectDoctorFindings();
|
|
|
|
|
const counts = countDoctorFindings(findings);
|
|
|
|
|
const extraInstructions = (argvArg._?.slice(2).join(" ") || "").trim();
|
|
|
|
|
const force = Boolean(argvArg.force);
|
|
|
|
|
|
|
|
|
|
if (counts.error === 0 && counts.warn === 0 && !extraInstructions && !force) {
|
|
|
|
|
plugins.logger.log(
|
|
|
|
|
"success",
|
|
|
|
|
"Configuration doctor found no issues. Use `gitzone config fix --force` to run opencode anyway.",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mode.yes) {
|
|
|
|
|
if (!mode.interactive) {
|
|
|
|
|
throw new Error("Config fix requires an interactive terminal or `-y` to run opencode non-interactively.");
|
|
|
|
|
}
|
|
|
|
|
const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
|
|
|
|
`Run opencode to fix .smartconfig.json? (${counts.error} error, ${counts.warn} warning)`,
|
|
|
|
|
true,
|
|
|
|
|
);
|
|
|
|
|
if (!confirmed) {
|
|
|
|
|
plugins.logger.log("info", "Config fix cancelled.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const opencodeArgs = [
|
|
|
|
|
"run",
|
|
|
|
|
"--title",
|
|
|
|
|
"gitzone config fix",
|
|
|
|
|
"--dir",
|
|
|
|
|
process.cwd(),
|
|
|
|
|
];
|
|
|
|
|
if (mode.yes) {
|
|
|
|
|
opencodeArgs.push("--dangerously-skip-permissions");
|
|
|
|
|
}
|
|
|
|
|
opencodeArgs.push(buildConfigFixPrompt(findings, extraInstructions));
|
|
|
|
|
|
|
|
|
|
plugins.logger.log("info", "Starting opencode configuration fix...");
|
|
|
|
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
|
|
|
|
executor: "bash",
|
|
|
|
|
sourceFilePaths: [],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let result: plugins.smartshell.IExecResult;
|
|
|
|
|
try {
|
|
|
|
|
result = await smartshellInstance.execSpawn("opencode", opencodeArgs, {
|
|
|
|
|
passthrough: true,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new Error(`Failed to run opencode: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result.exitCode !== 0) {
|
|
|
|
|
plugins.logger.log("error", `opencode exited with code ${result.exitCode}`);
|
|
|
|
|
process.exitCode = result.exitCode || 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await formatSmartconfigWithDiff(mode);
|
|
|
|
|
const finalFindings = await collectDoctorFindings();
|
|
|
|
|
printDoctorResult(finalFindings, mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function collectDoctorFindings(): Promise<IDoctorFinding[]> {
|
|
|
|
|
const findings: IDoctorFinding[] = [];
|
|
|
|
|
const smartconfigPath = getSmartconfigPath();
|
|
|
|
|
const smartconfigExists = await plugins.smartfs.file(smartconfigPath).exists();
|
|
|
|
@@ -900,7 +987,7 @@ async function handleDoctor(mode: ICliMode): Promise<void> {
|
|
|
|
|
message: ".smartconfig.json does not exist",
|
|
|
|
|
fix: "Run `gitzone config project` to create project basics.",
|
|
|
|
|
});
|
|
|
|
|
return printDoctorResult(findings, mode);
|
|
|
|
|
return findings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let smartconfigData: Record<string, any>;
|
|
|
|
@@ -912,7 +999,7 @@ async function handleDoctor(mode: ICliMode): Promise<void> {
|
|
|
|
|
message: ".smartconfig.json is not valid JSON",
|
|
|
|
|
fix: error instanceof Error ? error.message : String(error),
|
|
|
|
|
});
|
|
|
|
|
return printDoctorResult(findings, mode);
|
|
|
|
|
return findings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cliConfig = getCliConfigValueFromData(smartconfigData, "") || {};
|
|
|
|
@@ -958,7 +1045,7 @@ async function handleDoctor(mode: ICliMode): Promise<void> {
|
|
|
|
|
validateCommitConfig(cliConfig.commit || {}, findings);
|
|
|
|
|
await validateReleaseConfig(cliConfig.release || {}, findings);
|
|
|
|
|
|
|
|
|
|
printDoctorResult(findings, mode);
|
|
|
|
|
return findings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1274,14 +1361,56 @@ function getDefaultEnabledTargets(currentTargets: Record<string, any>): string[]
|
|
|
|
|
return enabledTargets;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printDoctorResult(findings: IDoctorFinding[], mode: ICliMode): void {
|
|
|
|
|
const counts = findings.reduce(
|
|
|
|
|
function countDoctorFindings(
|
|
|
|
|
findings: IDoctorFinding[],
|
|
|
|
|
): Record<TDoctorFindingLevel, number> {
|
|
|
|
|
return findings.reduce(
|
|
|
|
|
(accumulator, finding) => {
|
|
|
|
|
accumulator[finding.level] += 1;
|
|
|
|
|
return accumulator;
|
|
|
|
|
},
|
|
|
|
|
{ ok: 0, warn: 0, error: 0 } as Record<TDoctorFindingLevel, number>,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildConfigFixPrompt(
|
|
|
|
|
findings: IDoctorFinding[],
|
|
|
|
|
extraInstructions: string,
|
|
|
|
|
): string {
|
|
|
|
|
const promptParts = [
|
|
|
|
|
"Other /c-* commands can be found at ~/.config/opencode/commands/*",
|
|
|
|
|
"# gitzone config fix",
|
|
|
|
|
"",
|
|
|
|
|
`Working directory: ${process.cwd()}`,
|
|
|
|
|
"",
|
|
|
|
|
"Repair the project configuration so `gitzone config doctor --json` passes.",
|
|
|
|
|
"",
|
|
|
|
|
"Rules:",
|
|
|
|
|
"- Read `.smartconfig.json`, `package.json`, and nearby project metadata before editing.",
|
|
|
|
|
"- Keep gitzone CLI config under `@git.zone/cli` in `.smartconfig.json`.",
|
|
|
|
|
`- Use schemaVersion ${CURRENT_GITZONE_CLI_SCHEMA_VERSION} for ` +
|
|
|
|
|
"`@git.zone/cli`.",
|
|
|
|
|
"- Use target-based release config: `release.targets.git`, `release.targets.npm`, and `release.targets.docker`.",
|
|
|
|
|
"- Keep npm registries only at `@git.zone/cli.release.targets.npm.registries`.",
|
|
|
|
|
"- Do not add runtime legacy compatibility code. If legacy config exists, migrate it explicitly.",
|
|
|
|
|
"- Do not commit, release, install dependencies, or modify unrelated files.",
|
|
|
|
|
"- Use pnpm commands only if commands are needed.",
|
|
|
|
|
"- Run `gitzone config doctor --json` after changes and keep fixing until no errors remain.",
|
|
|
|
|
"- Run `git diff --check` after changes to catch whitespace problems.",
|
|
|
|
|
"",
|
|
|
|
|
"Current doctor findings:",
|
|
|
|
|
JSON.stringify(findings, null, 2),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (extraInstructions) {
|
|
|
|
|
promptParts.push("", "Additional user instructions:", extraInstructions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return promptParts.join("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printDoctorResult(findings: IDoctorFinding[], mode: ICliMode): void {
|
|
|
|
|
const counts = countDoctorFindings(findings);
|
|
|
|
|
|
|
|
|
|
if (mode.json) {
|
|
|
|
|
printJson({
|
|
|
|
@@ -1666,6 +1795,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: "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" },
|
|
|
|
@@ -1689,6 +1819,8 @@ export function showHelp(mode?: ICliMode): void {
|
|
|
|
|
"gitzone config show --json",
|
|
|
|
|
"gitzone config project",
|
|
|
|
|
"gitzone config doctor --json",
|
|
|
|
|
"gitzone config fix",
|
|
|
|
|
"gitzone config fix -y",
|
|
|
|
|
"gitzone config get release.targets.npm.accessLevel",
|
|
|
|
|
"gitzone config set cli.interactive false",
|
|
|
|
|
"gitzone config set cli.output json",
|
|
|
|
@@ -1708,6 +1840,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(" get <path> Read a single config value");
|
|
|
|
|
console.log(" set <path> <value> Write a config value");
|
|
|
|
|
console.log(" unset <path> Delete a config value");
|
|
|
|
@@ -1730,6 +1863,8 @@ export function showHelp(mode?: ICliMode): void {
|
|
|
|
|
console.log(" gitzone config cli");
|
|
|
|
|
console.log(" gitzone config release");
|
|
|
|
|
console.log(" gitzone config doctor --json");
|
|
|
|
|
console.log(" gitzone config fix");
|
|
|
|
|
console.log(" gitzone config fix -y");
|
|
|
|
|
console.log(" gitzone config get release.targets.npm.accessLevel");
|
|
|
|
|
console.log(" gitzone config set cli.interactive false");
|
|
|
|
|
console.log(" gitzone config set cli.output json");
|
|
|
|
|