feat(cli): add machine-readable CLI help, recommendation, and configuration flows
This commit is contained in:
@@ -1,14 +1,31 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { FormatStats } from './classes.formatstats.js';
|
||||
import * as plugins from "./mod.plugins.js";
|
||||
import { FormatStats } from "./classes.formatstats.js";
|
||||
|
||||
interface IFormatContextOptions {
|
||||
interactive?: boolean;
|
||||
jsonOutput?: boolean;
|
||||
}
|
||||
|
||||
export class FormatContext {
|
||||
private formatStats: FormatStats;
|
||||
private interactive: boolean;
|
||||
private jsonOutput: boolean;
|
||||
|
||||
constructor() {
|
||||
constructor(options: IFormatContextOptions = {}) {
|
||||
this.formatStats = new FormatStats();
|
||||
this.interactive = options.interactive ?? true;
|
||||
this.jsonOutput = options.jsonOutput ?? false;
|
||||
}
|
||||
|
||||
getFormatStats(): FormatStats {
|
||||
return this.formatStats;
|
||||
}
|
||||
|
||||
isInteractive(): boolean {
|
||||
return this.interactive;
|
||||
}
|
||||
|
||||
isJsonOutput(): boolean {
|
||||
return this.jsonOutput;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+277
-88
@@ -1,44 +1,60 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
import { FormatContext } from './classes.formatcontext.js';
|
||||
import { FormatPlanner } from './classes.formatplanner.js';
|
||||
import { BaseFormatter } from './classes.baseformatter.js';
|
||||
import { logger, setVerboseMode } from '../gitzone.logging.js';
|
||||
import * as plugins from "./mod.plugins.js";
|
||||
import { Project } from "../classes.project.js";
|
||||
import { FormatContext } from "./classes.formatcontext.js";
|
||||
import { FormatPlanner } from "./classes.formatplanner.js";
|
||||
import { BaseFormatter } from "./classes.baseformatter.js";
|
||||
import { logger, setVerboseMode } from "../gitzone.logging.js";
|
||||
import type { ICliMode } from "../helpers.climode.js";
|
||||
import {
|
||||
getCliMode,
|
||||
printJson,
|
||||
runWithSuppressedOutput,
|
||||
} from "../helpers.climode.js";
|
||||
import { getCliConfigValue } from "../helpers.smartconfig.js";
|
||||
|
||||
import { CleanupFormatter } from './formatters/cleanup.formatter.js';
|
||||
import { SmartconfigFormatter } from './formatters/smartconfig.formatter.js';
|
||||
import { LicenseFormatter } from './formatters/license.formatter.js';
|
||||
import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
|
||||
import { TemplatesFormatter } from './formatters/templates.formatter.js';
|
||||
import { GitignoreFormatter } from './formatters/gitignore.formatter.js';
|
||||
import { TsconfigFormatter } from './formatters/tsconfig.formatter.js';
|
||||
import { PrettierFormatter } from './formatters/prettier.formatter.js';
|
||||
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
||||
import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||
import { CleanupFormatter } from "./formatters/cleanup.formatter.js";
|
||||
import { SmartconfigFormatter } from "./formatters/smartconfig.formatter.js";
|
||||
import { LicenseFormatter } from "./formatters/license.formatter.js";
|
||||
import { PackageJsonFormatter } from "./formatters/packagejson.formatter.js";
|
||||
import { TemplatesFormatter } from "./formatters/templates.formatter.js";
|
||||
import { GitignoreFormatter } from "./formatters/gitignore.formatter.js";
|
||||
import { TsconfigFormatter } from "./formatters/tsconfig.formatter.js";
|
||||
import { PrettierFormatter } from "./formatters/prettier.formatter.js";
|
||||
import { ReadmeFormatter } from "./formatters/readme.formatter.js";
|
||||
import { CopyFormatter } from "./formatters/copy.formatter.js";
|
||||
|
||||
/**
|
||||
* Rename npmextra.json or smartconfig.json to .smartconfig.json
|
||||
* before any formatter tries to read config.
|
||||
*/
|
||||
async function migrateConfigFile(): Promise<void> {
|
||||
const target = '.smartconfig.json';
|
||||
async function migrateConfigFile(allowWrite: boolean): Promise<void> {
|
||||
const target = ".smartconfig.json";
|
||||
const targetExists = await plugins.smartfs.file(target).exists();
|
||||
if (targetExists) return;
|
||||
|
||||
for (const oldName of ['smartconfig.json', 'npmextra.json']) {
|
||||
for (const oldName of ["smartconfig.json", "npmextra.json"]) {
|
||||
const exists = await plugins.smartfs.file(oldName).exists();
|
||||
if (exists) {
|
||||
const content = await plugins.smartfs.file(oldName).encoding('utf8').read() as string;
|
||||
await plugins.smartfs.file(`./${target}`).encoding('utf8').write(content);
|
||||
if (!allowWrite) {
|
||||
return;
|
||||
}
|
||||
const content = (await plugins.smartfs
|
||||
.file(oldName)
|
||||
.encoding("utf8")
|
||||
.read()) as string;
|
||||
await plugins.smartfs.file(`./${target}`).encoding("utf8").write(content);
|
||||
await plugins.smartfs.file(oldName).delete();
|
||||
logger.log('info', `Migrated ${oldName} to ${target}`);
|
||||
logger.log("info", `Migrated ${oldName} to ${target}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared formatter class map used by both run() and runFormatter()
|
||||
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
||||
const formatterMap: Record<
|
||||
string,
|
||||
new (ctx: FormatContext, proj: Project) => BaseFormatter
|
||||
> = {
|
||||
cleanup: CleanupFormatter,
|
||||
smartconfig: SmartconfigFormatter,
|
||||
license: LicenseFormatter,
|
||||
@@ -52,7 +68,104 @@ const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => Ba
|
||||
};
|
||||
|
||||
// Formatters that don't require projectType to be set
|
||||
const formattersNotRequiringProjectType = ['smartconfig', 'prettier', 'cleanup', 'packagejson'];
|
||||
const formattersNotRequiringProjectType = [
|
||||
"smartconfig",
|
||||
"prettier",
|
||||
"cleanup",
|
||||
"packagejson",
|
||||
];
|
||||
|
||||
const getFormatConfig = async () => {
|
||||
const rawFormatConfig = await getCliConfigValue<Record<string, any>>(
|
||||
"format",
|
||||
{},
|
||||
);
|
||||
return {
|
||||
interactive: true,
|
||||
showDiffs: false,
|
||||
autoApprove: false,
|
||||
showStats: true,
|
||||
modules: {
|
||||
skip: [],
|
||||
only: [],
|
||||
...(rawFormatConfig.modules || {}),
|
||||
},
|
||||
...rawFormatConfig,
|
||||
};
|
||||
};
|
||||
|
||||
const createActiveFormatters = async (options: {
|
||||
interactive: boolean;
|
||||
jsonOutput: boolean;
|
||||
}) => {
|
||||
const project = await Project.fromCwd({ requireProjectType: false });
|
||||
const context = new FormatContext(options);
|
||||
const planner = new FormatPlanner();
|
||||
|
||||
const formatConfig = await getFormatConfig();
|
||||
const formatters = Object.entries(formatterMap).map(
|
||||
([, FormatterClass]) => new FormatterClass(context, project),
|
||||
);
|
||||
|
||||
const activeFormatters = formatters.filter((formatter) => {
|
||||
if (formatConfig.modules.only.length > 0) {
|
||||
return formatConfig.modules.only.includes(formatter.name);
|
||||
}
|
||||
if (formatConfig.modules.skip.includes(formatter.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
context,
|
||||
planner,
|
||||
formatConfig,
|
||||
activeFormatters,
|
||||
};
|
||||
};
|
||||
|
||||
const buildFormatPlan = async (options: {
|
||||
fromPlan?: string;
|
||||
interactive: boolean;
|
||||
jsonOutput: boolean;
|
||||
}) => {
|
||||
const { context, planner, formatConfig, activeFormatters } =
|
||||
await createActiveFormatters({
|
||||
interactive: options.interactive,
|
||||
jsonOutput: options.jsonOutput,
|
||||
});
|
||||
|
||||
const plan = options.fromPlan
|
||||
? JSON.parse(
|
||||
(await plugins.smartfs
|
||||
.file(options.fromPlan)
|
||||
.encoding("utf8")
|
||||
.read()) as string,
|
||||
)
|
||||
: await planner.planFormat(activeFormatters);
|
||||
|
||||
return {
|
||||
context,
|
||||
planner,
|
||||
formatConfig,
|
||||
activeFormatters,
|
||||
plan,
|
||||
};
|
||||
};
|
||||
|
||||
const serializePlan = (plan: any) => {
|
||||
return {
|
||||
summary: plan.summary,
|
||||
warnings: plan.warnings,
|
||||
changes: plan.changes.map((change: any) => ({
|
||||
type: change.type,
|
||||
path: change.path,
|
||||
module: change.module,
|
||||
description: change.description,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
export let run = async (
|
||||
options: {
|
||||
@@ -66,62 +179,61 @@ export let run = async (
|
||||
interactive?: boolean;
|
||||
verbose?: boolean;
|
||||
diff?: boolean;
|
||||
[key: string]: any;
|
||||
} = {},
|
||||
): Promise<any> => {
|
||||
const mode = await getCliMode(options as any);
|
||||
const subcommand = (options as any)?._?.[1];
|
||||
|
||||
if (mode.help || subcommand === "help") {
|
||||
showHelp(mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
setVerboseMode(true);
|
||||
}
|
||||
|
||||
const shouldWrite = options.write ?? (options.dryRun === false);
|
||||
const shouldWrite = options.write ?? options.dryRun === false;
|
||||
const treatAsPlan = subcommand === "plan";
|
||||
|
||||
if (mode.json && shouldWrite) {
|
||||
printJson({
|
||||
ok: false,
|
||||
error:
|
||||
"JSON output is only supported for read-only format planning. Use `gitzone format plan --json` or omit `--json` when applying changes.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate config file before anything reads it
|
||||
await migrateConfigFile();
|
||||
await migrateConfigFile(shouldWrite);
|
||||
|
||||
const project = await Project.fromCwd({ requireProjectType: false });
|
||||
const context = new FormatContext();
|
||||
const planner = new FormatPlanner();
|
||||
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||
const formatConfig = smartconfigInstance.dataFor<any>('@git.zone/cli.format', {
|
||||
interactive: true,
|
||||
showDiffs: false,
|
||||
autoApprove: false,
|
||||
modules: {
|
||||
skip: [],
|
||||
only: [],
|
||||
},
|
||||
});
|
||||
|
||||
const interactive = options.interactive ?? formatConfig.interactive;
|
||||
const formatConfig = await getFormatConfig();
|
||||
const interactive =
|
||||
options.interactive ?? (mode.interactive && formatConfig.interactive);
|
||||
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
||||
|
||||
try {
|
||||
// Initialize formatters in execution order
|
||||
const formatters = Object.entries(formatterMap).map(
|
||||
([, FormatterClass]) => new FormatterClass(context, project),
|
||||
);
|
||||
const planBuilder = async () => {
|
||||
return await buildFormatPlan({
|
||||
fromPlan: options.fromPlan,
|
||||
interactive,
|
||||
jsonOutput: mode.json,
|
||||
});
|
||||
};
|
||||
|
||||
// Filter formatters based on configuration
|
||||
const activeFormatters = formatters.filter((formatter) => {
|
||||
if (formatConfig.modules.only.length > 0) {
|
||||
return formatConfig.modules.only.includes(formatter.name);
|
||||
}
|
||||
if (formatConfig.modules.skip.includes(formatter.name)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!mode.json) {
|
||||
logger.log("info", "Analyzing project for format operations...");
|
||||
}
|
||||
const { context, planner, activeFormatters, plan } = mode.json
|
||||
? await runWithSuppressedOutput(planBuilder)
|
||||
: await planBuilder();
|
||||
|
||||
// Plan phase
|
||||
logger.log('info', 'Analyzing project for format operations...');
|
||||
let plan = options.fromPlan
|
||||
? JSON.parse(
|
||||
(await plugins.smartfs
|
||||
.file(options.fromPlan)
|
||||
.encoding('utf8')
|
||||
.read()) as string,
|
||||
)
|
||||
: await planner.planFormat(activeFormatters);
|
||||
if (mode.json) {
|
||||
printJson(serializePlan(plan));
|
||||
return;
|
||||
}
|
||||
|
||||
// Display plan
|
||||
await planner.displayPlan(plan, options.detailed);
|
||||
@@ -130,34 +242,35 @@ export let run = async (
|
||||
if (options.savePlan) {
|
||||
await plugins.smartfs
|
||||
.file(options.savePlan)
|
||||
.encoding('utf8')
|
||||
.encoding("utf8")
|
||||
.write(JSON.stringify(plan, null, 2));
|
||||
logger.log('info', `Plan saved to ${options.savePlan}`);
|
||||
logger.log("info", `Plan saved to ${options.savePlan}`);
|
||||
}
|
||||
|
||||
if (options.planOnly) {
|
||||
if (options.planOnly || treatAsPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show diffs if explicitly requested or before interactive write confirmation
|
||||
const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove);
|
||||
const showDiffs =
|
||||
options.diff || (shouldWrite && interactive && !autoApprove);
|
||||
if (showDiffs) {
|
||||
logger.log('info', 'Showing file diffs:');
|
||||
console.log('');
|
||||
logger.log("info", "Showing file diffs:");
|
||||
console.log("");
|
||||
|
||||
for (const formatter of activeFormatters) {
|
||||
const checkResult = await formatter.check();
|
||||
if (checkResult.hasDiff) {
|
||||
logger.log('info', `[${formatter.name}]`);
|
||||
logger.log("info", `[${formatter.name}]`);
|
||||
formatter.displayAllDiffs(checkResult);
|
||||
console.log('');
|
||||
console.log("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dry-run mode (default behavior)
|
||||
if (!shouldWrite) {
|
||||
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
|
||||
logger.log("info", "Dry-run mode - use --write (-w) to apply changes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,25 +278,25 @@ export let run = async (
|
||||
if (interactive && !autoApprove) {
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
const response = await interactInstance.askQuestion({
|
||||
type: 'confirm',
|
||||
name: 'proceed',
|
||||
message: 'Proceed with formatting?',
|
||||
type: "confirm",
|
||||
name: "proceed",
|
||||
message: "Proceed with formatting?",
|
||||
default: true,
|
||||
});
|
||||
|
||||
if (!(response as any).value) {
|
||||
logger.log('info', 'Format operation cancelled by user');
|
||||
logger.log("info", "Format operation cancelled by user");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute phase
|
||||
logger.log('info', 'Executing format operations...');
|
||||
logger.log("info", "Executing format operations...");
|
||||
await planner.executePlan(plan, activeFormatters, context);
|
||||
|
||||
context.getFormatStats().finish();
|
||||
|
||||
const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true);
|
||||
const showStats = formatConfig.showStats ?? true;
|
||||
if (showStats) {
|
||||
context.getFormatStats().displayStats();
|
||||
}
|
||||
@@ -193,14 +306,15 @@ export let run = async (
|
||||
await context.getFormatStats().saveReport(statsPath);
|
||||
}
|
||||
|
||||
logger.log('success', 'Format operations completed successfully!');
|
||||
logger.log("success", "Format operations completed successfully!");
|
||||
} catch (error) {
|
||||
logger.log('error', `Format operation failed: ${error.message}`);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logger.log("error", `Format operation failed: ${errorMessage}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
import type { ICheckResult } from './interfaces.format.js';
|
||||
import type { ICheckResult } from "./interfaces.format.js";
|
||||
export type { ICheckResult };
|
||||
|
||||
/**
|
||||
@@ -212,11 +326,12 @@ export const runFormatter = async (
|
||||
silent?: boolean;
|
||||
checkOnly?: boolean;
|
||||
showDiff?: boolean;
|
||||
} = {}
|
||||
} = {},
|
||||
): Promise<ICheckResult | void> => {
|
||||
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
||||
const requireProjectType =
|
||||
!formattersNotRequiringProjectType.includes(formatterName);
|
||||
const project = await Project.fromCwd({ requireProjectType });
|
||||
const context = new FormatContext();
|
||||
const context = new FormatContext({ interactive: true, jsonOutput: false });
|
||||
|
||||
const FormatterClass = formatterMap[formatterName];
|
||||
if (!FormatterClass) {
|
||||
@@ -240,6 +355,80 @@ export const runFormatter = async (
|
||||
}
|
||||
|
||||
if (!options.silent) {
|
||||
logger.log('success', `Formatter '${formatterName}' completed`);
|
||||
logger.log("success", `Formatter '${formatterName}' completed`);
|
||||
}
|
||||
};
|
||||
|
||||
export function showHelp(mode?: ICliMode): void {
|
||||
if (mode?.json) {
|
||||
printJson({
|
||||
command: "format",
|
||||
usage: "gitzone format [plan] [options]",
|
||||
description:
|
||||
"Plans formatting changes by default and applies them only with --write.",
|
||||
flags: [
|
||||
{ flag: "--write, -w", description: "Apply planned changes" },
|
||||
{
|
||||
flag: "--yes",
|
||||
description: "Skip the interactive confirmation before writing",
|
||||
},
|
||||
{
|
||||
flag: "--plan-only",
|
||||
description: "Show the plan without applying changes",
|
||||
},
|
||||
{
|
||||
flag: "--save-plan <file>",
|
||||
description: "Write the format plan to a file",
|
||||
},
|
||||
{
|
||||
flag: "--from-plan <file>",
|
||||
description: "Load a previously saved plan",
|
||||
},
|
||||
{
|
||||
flag: "--detailed",
|
||||
description: "Show detailed diffs and save stats",
|
||||
},
|
||||
{ flag: "--verbose", description: "Enable verbose logging" },
|
||||
{
|
||||
flag: "--diff",
|
||||
description: "Show per-file diffs before applying changes",
|
||||
},
|
||||
{ flag: "--json", description: "Emit a read-only format plan as JSON" },
|
||||
],
|
||||
examples: [
|
||||
"gitzone format",
|
||||
"gitzone format plan --json",
|
||||
"gitzone format --write --yes",
|
||||
],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log("Usage: gitzone format [plan] [options]");
|
||||
console.log("");
|
||||
console.log(
|
||||
"Plans formatting changes by default and applies them only with --write.",
|
||||
);
|
||||
console.log("");
|
||||
console.log("Flags:");
|
||||
console.log(" --write, -w Apply planned changes");
|
||||
console.log(
|
||||
" --yes Skip the interactive confirmation before writing",
|
||||
);
|
||||
console.log(" --plan-only Show the plan without applying changes");
|
||||
console.log(" --save-plan <file> Write the format plan to a file");
|
||||
console.log(" --from-plan <file> Load a previously saved plan");
|
||||
console.log(" --detailed Show detailed diffs and save stats");
|
||||
console.log(" --verbose Enable verbose logging");
|
||||
console.log(
|
||||
" --diff Show per-file diffs before applying changes",
|
||||
);
|
||||
console.log(" --json Emit a read-only format plan as JSON");
|
||||
console.log("");
|
||||
console.log("Examples:");
|
||||
console.log(" gitzone format");
|
||||
console.log(" gitzone format plan --json");
|
||||
console.log(" gitzone format --write --yes");
|
||||
console.log("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user