feat(cli): add machine-readable CLI help, recommendation, and configuration flows

This commit is contained in:
2026-04-16 18:54:07 +00:00
parent f43f88a3cb
commit fd7a73398c
14 changed files with 2482 additions and 786 deletions
@@ -1,7 +1,7 @@
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';
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
@@ -9,11 +9,11 @@ import { logger, logVerbose } from '../../gitzone.logging.js';
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' },
{ 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]) {
@@ -36,36 +36,37 @@ const migrateNamespaceKeys = (smartconfigJson: any): boolean => {
* Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel
*/
const migrateAccessLevel = (smartconfigJson: any): boolean => {
const szciConfig = smartconfigJson['@ship.zone/szci'];
const szciConfig = smartconfigJson["@ship.zone/szci"];
if (!szciConfig?.npmAccessLevel) {
return false;
}
const gitzoneConfig = smartconfigJson['@git.zone/cli'] || {};
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"]) {
smartconfigJson["@git.zone/cli"] = {};
}
if (!smartconfigJson['@git.zone/cli'].release) {
smartconfigJson['@git.zone/cli'].release = {};
if (!smartconfigJson["@git.zone/cli"].release) {
smartconfigJson["@git.zone/cli"].release = {};
}
smartconfigJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel;
smartconfigJson["@git.zone/cli"].release.accessLevel =
szciConfig.npmAccessLevel;
delete szciConfig.npmAccessLevel;
return true;
};
const CONFIG_FILE = '.smartconfig.json';
const CONFIG_FILE = ".smartconfig.json";
export class SmartconfigFormatter extends BaseFormatter {
get name(): string {
return 'smartconfig';
return "smartconfig";
}
async analyze(): Promise<IPlannedChange[]> {
@@ -76,13 +77,13 @@ export class SmartconfigFormatter extends BaseFormatter {
// 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');
logVerbose(".smartconfig.json does not exist, skipping");
return changes;
}
const currentContent = (await plugins.smartfs
.file(CONFIG_FILE)
.encoding('utf8')
.encoding("utf8")
.read()) as string;
const smartconfigJson = JSON.parse(currentContent);
@@ -92,21 +93,21 @@ export class SmartconfigFormatter extends BaseFormatter {
migrateAccessLevel(smartconfigJson);
// Ensure namespaces exist
if (!smartconfigJson['@git.zone/cli']) {
smartconfigJson['@git.zone/cli'] = {};
if (!smartconfigJson["@git.zone/cli"]) {
smartconfigJson["@git.zone/cli"] = {};
}
if (!smartconfigJson['@ship.zone/szci']) {
smartconfigJson['@ship.zone/szci'] = {};
if (!smartconfigJson["@ship.zone/szci"]) {
smartconfigJson["@ship.zone/szci"] = {};
}
const newContent = JSON.stringify(smartconfigJson, null, 2);
if (newContent !== currentContent) {
changes.push({
type: 'modify',
type: "modify",
path: CONFIG_FILE,
module: this.name,
description: 'Migrate and format .smartconfig.json',
description: "Migrate and format .smartconfig.json",
content: newContent,
});
}
@@ -115,26 +116,41 @@ export class SmartconfigFormatter extends BaseFormatter {
}
async applyChange(change: IPlannedChange): Promise<void> {
if (change.type !== 'modify' || !change.content) return;
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',
"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'],
smartconfigJson["@git.zone/cli"],
expectedRepoInformationItem,
)
) {
@@ -142,8 +158,8 @@ export class SmartconfigFormatter extends BaseFormatter {
{
message: `What is the value of ${expectedRepoInformationItem}`,
name: expectedRepoInformationItem,
type: 'input',
default: 'undefined variable',
type: "input",
default: "undefined variable",
},
]);
}
@@ -156,7 +172,7 @@ export class SmartconfigFormatter extends BaseFormatter {
);
if (cliProvidedValue) {
plugins.smartobject.smartAdd(
smartconfigJson['@git.zone/cli'],
smartconfigJson["@git.zone/cli"],
expectedRepoInformationItem,
cliProvidedValue,
);
@@ -165,6 +181,6 @@ export class SmartconfigFormatter extends BaseFormatter {
const finalContent = JSON.stringify(smartconfigJson, null, 2);
await this.modifyFile(change.path, finalContent);
logger.log('info', 'Updated .smartconfig.json');
logger.log("info", "Updated .smartconfig.json");
}
}