feat(cli): Add optional build step to release flow and auto-format npmextra config when registries change

This commit is contained in:
2025-12-14 10:51:16 +00:00
parent ccdca55c9a
commit 7348567a62
6 changed files with 97 additions and 3 deletions

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/cli',
version: '2.3.0',
version: '2.4.0',
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
}

View File

@@ -10,6 +10,7 @@ import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
export const run = async (argvArg: any) => {
// Check if release flag is set and validate registries early
const wantsRelease = !!(argvArg.r || argvArg.release);
const wantsBuild = !!(argvArg.b || argvArg.build);
let releaseConfig: ReleaseConfig | null = null;
if (wantsRelease) {
@@ -153,6 +154,7 @@ export const run = async (argvArg: any) => {
const willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
if (wantsBuild) totalSteps += 2; // build step + verification step
if (willPush) totalSteps++;
if (willRelease) totalSteps++;
let currentStep = 0;
@@ -215,7 +217,34 @@ export const run = async (argvArg: any) => {
const projectType = await helpers.detectProjectType();
const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
// Step 6: Push to remote (optional)
// Step 6: Run build (optional)
if (wantsBuild) {
currentStep++;
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'in-progress');
const buildResult = await smartshellInstance.exec('pnpm build');
if (buildResult.exitCode !== 0) {
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'error');
logger.log('error', 'Build failed. Aborting release.');
process.exit(1);
}
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'done');
// Step 7: Verify no uncommitted changes
currentStep++;
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'in-progress');
const statusResult = await smartshellInstance.exec('git status --porcelain');
if (statusResult.stdout.trim() !== '') {
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'error');
logger.log('error', 'Build produced uncommitted changes. This usually means build output is not gitignored.');
logger.log('error', 'Uncommitted files:');
console.log(statusResult.stdout);
logger.log('error', 'Aborting release. Please ensure build artifacts are in .gitignore');
process.exit(1);
}
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'done');
}
// Step: Push to remote (optional)
const currentBranch = await helpers.detectCurrentBranch();
if (willPush) {
currentStep++;

View File

@@ -2,6 +2,7 @@
import * as plugins from './mod.plugins.js';
import { ReleaseConfig } from './classes.releaseconfig.js';
import { runFormatter } from '../mod_format/index.js';
export { ReleaseConfig };
@@ -148,6 +149,7 @@ async function handleAdd(url?: string): Promise<void> {
if (added) {
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Added registry: ${url}`);
} else {
plugins.logger.log('warn', `Registry already exists: ${url}`);
@@ -183,6 +185,7 @@ async function handleRemove(url?: string): Promise<void> {
if (removed) {
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Removed registry: ${url}`);
} else {
plugins.logger.log('warn', `Registry not found: ${url}`);
@@ -209,6 +212,7 @@ async function handleClear(): Promise<void> {
if (confirmed) {
config.clearRegistries();
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', 'All registries cleared.');
} else {
plugins.logger.log('info', 'Operation cancelled.');
@@ -248,6 +252,7 @@ async function handleAccessLevel(level?: string): Promise<void> {
config.setAccessLevel(level as 'public' | 'private');
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Access level set to: ${level}`);
}

View File

@@ -2,6 +2,7 @@ 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 wrapper classes for formatters
@@ -195,3 +196,44 @@ export const handleCleanBackups = async (): Promise<void> => {
'Backup cleaning has been disabled - backup system removed',
);
};
/**
* Run a single formatter by name (for use by other modules)
*/
export const runFormatter = async (
formatterName: string,
options: { silent?: boolean } = {}
): Promise<void> => {
const project = await Project.fromCwd();
const context = new FormatContext();
// Map formatter names to classes
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
cleanup: CleanupFormatter,
npmextra: NpmextraFormatter,
license: LicenseFormatter,
packagejson: PackageJsonFormatter,
templates: TemplatesFormatter,
gitignore: GitignoreFormatter,
tsconfig: TsconfigFormatter,
prettier: PrettierFormatter,
readme: ReadmeFormatter,
copy: CopyFormatter,
};
const FormatterClass = formatterMap[formatterName];
if (!FormatterClass) {
throw new Error(`Unknown formatter: ${formatterName}`);
}
const formatter = new FormatterClass(context, project);
const changes = await formatter.analyze();
for (const change of changes) {
await formatter.applyChange(change);
}
if (!options.silent) {
logger.log('success', `Formatter '${formatterName}' completed`);
}
};