import { BaseFormatter } from "../classes.baseformatter.js"; import type { IPlannedChange } from "../interfaces.format.js"; import * as plugins from "../mod.plugins.js"; import { logger, logVerbose } from "../../gitzone.logging.js"; /** * Migrates .smartconfig.json from old namespace keys to new package-scoped keys */ const migrateNamespaceKeys = (smartconfigJson: any): boolean => { let migrated = false; const migrations = [ { oldKey: "gitzone", newKey: "@git.zone/cli" }, { oldKey: "tsdoc", newKey: "@git.zone/tsdoc" }, { oldKey: "npmdocker", newKey: "@git.zone/tsdocker" }, { oldKey: "npmci", newKey: "@ship.zone/szci" }, { oldKey: "szci", newKey: "@ship.zone/szci" }, ]; for (const { oldKey, newKey } of migrations) { if (smartconfigJson[oldKey]) { if (!smartconfigJson[newKey]) { smartconfigJson[newKey] = smartconfigJson[oldKey]; } else { smartconfigJson[newKey] = { ...smartconfigJson[oldKey], ...smartconfigJson[newKey], }; } delete smartconfigJson[oldKey]; migrated = true; } } return migrated; }; /** * Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel */ const migrateAccessLevel = (smartconfigJson: any): boolean => { const szciConfig = smartconfigJson["@ship.zone/szci"]; if (!szciConfig?.npmAccessLevel) { return false; } const gitzoneConfig = smartconfigJson["@git.zone/cli"] || {}; if (gitzoneConfig?.release?.accessLevel) { delete szciConfig.npmAccessLevel; return true; } if (!smartconfigJson["@git.zone/cli"]) { smartconfigJson["@git.zone/cli"] = {}; } if (!smartconfigJson["@git.zone/cli"].release) { smartconfigJson["@git.zone/cli"].release = {}; } smartconfigJson["@git.zone/cli"].release.accessLevel = szciConfig.npmAccessLevel; delete szciConfig.npmAccessLevel; return true; }; const CONFIG_FILE = ".smartconfig.json"; export class SmartconfigFormatter extends BaseFormatter { get name(): string { return "smartconfig"; } async analyze(): Promise { const changes: IPlannedChange[] = []; // File rename (npmextra.json/smartconfig.json → .smartconfig.json) // is handled by the orchestrator before analysis. // This formatter only operates on .smartconfig.json. const exists = await plugins.smartfs.file(CONFIG_FILE).exists(); if (!exists) { logVerbose(".smartconfig.json does not exist, skipping"); return changes; } const currentContent = (await plugins.smartfs .file(CONFIG_FILE) .encoding("utf8") .read()) as string; const smartconfigJson = JSON.parse(currentContent); // Apply key migrations migrateNamespaceKeys(smartconfigJson); migrateAccessLevel(smartconfigJson); // Ensure namespaces exist if (!smartconfigJson["@git.zone/cli"]) { smartconfigJson["@git.zone/cli"] = {}; } if (!smartconfigJson["@ship.zone/szci"]) { smartconfigJson["@ship.zone/szci"] = {}; } const newContent = JSON.stringify(smartconfigJson, null, 2); if (newContent !== currentContent) { changes.push({ type: "modify", path: CONFIG_FILE, module: this.name, description: "Migrate and format .smartconfig.json", content: newContent, }); } return changes; } async applyChange(change: IPlannedChange): Promise { if (change.type !== "modify" || !change.content) return; const smartconfigJson = JSON.parse(change.content); // Check for missing required module information const expectedRepoInformation: string[] = [ "projectType", "module.githost", "module.gitscope", "module.gitrepo", "module.description", "module.npmPackagename", "module.license", ]; const interactInstance = new plugins.smartinteract.SmartInteract(); const missingRepoInformation = expectedRepoInformation.filter( (expectedRepoInformationItem) => { return !plugins.smartobject.smartGet( smartconfigJson["@git.zone/cli"], expectedRepoInformationItem, ); }, ); if (missingRepoInformation.length > 0 && !this.context.isInteractive()) { throw new Error( `Missing required .smartconfig.json fields: ${missingRepoInformation.join(", ")}`, ); } for (const expectedRepoInformationItem of expectedRepoInformation) { if ( !plugins.smartobject.smartGet( smartconfigJson["@git.zone/cli"], expectedRepoInformationItem, ) ) { interactInstance.addQuestions([ { message: `What is the value of ${expectedRepoInformationItem}`, name: expectedRepoInformationItem, type: "input", default: "undefined variable", }, ]); } } const answerbucket = await interactInstance.runQueue(); for (const expectedRepoInformationItem of expectedRepoInformation) { const cliProvidedValue = answerbucket.getAnswerFor( expectedRepoInformationItem, ); if (cliProvidedValue) { plugins.smartobject.smartAdd( smartconfigJson["@git.zone/cli"], expectedRepoInformationItem, cliProvidedValue, ); } } const finalContent = JSON.stringify(smartconfigJson, null, 2); await this.modifyFile(change.path, finalContent); logger.log("info", "Updated .smartconfig.json"); } }