Compare commits

...

8 Commits

Author SHA1 Message Date
08f56ae0a4 v2.8.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-15 06:29:32 +00:00
b2d2684895 feat(commit): Add commit configuration and automatic pre-commit tests 2025-12-15 06:29:32 +00:00
1b328c3045 v2.7.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-14 16:53:18 +00:00
f444a04876 feat(mod_format): Add check-only formatting with interactive diff preview; make formatting default to dry-run and extend formatting API 2025-12-14 16:53:18 +00:00
6bd2d35992 v2.6.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-14 11:52:20 +00:00
d038b1d681 fix(npmextra): Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration 2025-12-14 11:52:20 +00:00
d60de5cef7 v2.6.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-14 11:46:42 +00:00
e1076a60ad feat(mod_commit): Add execution plan output to commit command 2025-12-14 11:46:42 +00:00
14 changed files with 562 additions and 43 deletions

View File

@@ -1,5 +1,44 @@
# Changelog
## 2025-12-15 - 2.8.0 - feat(commit)
Add commit configuration and automatic pre-commit tests
- Add CommitConfig class to manage @git.zone/cli.commit settings in npmextra.json (alwaysTest, alwaysBuild).
- Export CommitConfig from mod_config for use by the CLI.
- Add 'gitzone config commit' subcommand with interactive and direct-setting modes (alwaysTest, alwaysBuild).
- Merge CLI flags and npmextra config: -t/--test and -b/--build now respect commit.alwaysTest and commit.alwaysBuild.
- Run 'pnpm test' early in the commit flow when tests are enabled; abort the commit on failing tests and log results.
- Update commit UI/plan to show the test option and include the test step when enabled.
- Add 'gitzone config services' entry to configure services via ServiceManager.
## 2025-12-14 - 2.7.0 - feat(mod_format)
Add check-only formatting with interactive diff preview; make formatting default to dry-run and extend formatting API
- Add BaseFormatter.check(), displayDiff() and displayAllDiffs() to compute and render diffs without applying changes.
- Extend runFormatter API with new options: write (use to apply changes), checkOnly (only check for diffs), and showDiff (display diffs). When checkOnly is used, runFormatter returns an ICheckResult.
- Change default formatting behavior to dry-run. Use --write / -w to actually apply changes. CLI format command updated to respect --write/-w.
- Add formatNpmextraWithDiff in mod_config to preview diffs for npmextra.json and prompt the user before applying changes; calls to add/remove/clear registries and set access level now use this preview flow.
- Project.fromCwd now accepts an options object ({ requireProjectType?: boolean }) so callers can skip the projectType requirement when appropriate; runFormatter no longer requires projectType for certain formatters.
- Introduce a list of formatters that don't require projectType: npmextra, prettier, cleanup, packagejson.
- Export the ICheckResult type from the formatter module and update mod_format interfaces to include ICheckResult.
- Bump dependency @push.rocks/smartdiff to ^1.1.0.
## 2025-12-14 - 2.6.1 - fix(npmextra)
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration
- Move TSDoc legal text into a top-level "tsdoc.legal" property in npmextra.json
- Remove the misplaced "gitzone" object that previously contained the TSDoc legal block
- Reorder and properly nest the "@git.zone/cli" configuration to follow the new "tsdoc" section
- Pure configuration change (JSON structure) — no functional code changes
## 2025-12-14 - 2.6.0 - feat(mod_commit)
Add execution plan output to commit command
- Print an execution plan at the start of the commit flow (shows active options and planned steps)
- New printExecutionPlan(options) added to ts/mod_commit/mod.ui.ts
- Commit command now calls ui.printExecutionPlan(...) before running format or other steps
- Execution plan reflects flags: auto-accept (-y), push (-p), build (-b), release (-r), --format, and target registries
## 2025-12-14 - 2.5.0 - feat(mod_standard)
Add interactive main menu and help to standard CLI module; route commands via dynamic imports

View File

@@ -4,7 +4,10 @@
"npmAccessLevel": "private",
"npmRegistryUrl": "verdaccio.lossless.one"
},
"gitzone": {
"tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
@@ -31,15 +34,11 @@
"Docker",
"CI/CD"
]
}
},
"tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@git.zone/cli": {
},
"release": {
"registries": [
"https://verdaccio.lossless.digital"
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}

View File

@@ -1,7 +1,7 @@
{
"name": "@git.zone/cli",
"private": false,
"version": "2.5.0",
"version": "2.8.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.",
"main": "dist_ts/index.ts",
"typings": "dist_ts/index.d.ts",
@@ -76,7 +76,7 @@
"@push.rocks/npmextra": "^5.3.3",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/smartcli": "^4.0.19",
"@push.rocks/smartdiff": "^1.0.3",
"@push.rocks/smartdiff": "^1.1.0",
"@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smartfs": "^1.2.0",
"@push.rocks/smartgulp": "^3.0.4",

17
pnpm-lock.yaml generated
View File

@@ -36,8 +36,8 @@ importers:
specifier: ^4.0.19
version: 4.0.19
'@push.rocks/smartdiff':
specifier: ^1.0.3
version: 1.0.3
specifier: ^1.1.0
version: 1.1.0
'@push.rocks/smartfile':
specifier: ^13.1.2
version: 13.1.2
@@ -1042,8 +1042,8 @@ packages:
'@push.rocks/smartdelay@3.0.5':
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
'@push.rocks/smartdiff@1.0.3':
resolution: {integrity: sha512-cXUKj0KJBxnrZDN1Ztc2WiFRJM3vOTdQUdBfe6ar5NlKuXytSRMJqVL8IUbtWfMCSOx6HgWAUT7W68+/X2TG8w==}
'@push.rocks/smartdiff@1.1.0':
resolution: {integrity: sha512-AAz/unmko0C+g+60odOoK32PE3Ci3YLoB+zfg1LGLyVRCthcdzjqa1C2Km0MfG7IyJQKPdj8J5HPubtpm3ZeaQ==}
'@push.rocks/smartdns@7.6.1':
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
@@ -2536,9 +2536,6 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
@@ -6049,9 +6046,9 @@ snapshots:
dependencies:
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartdiff@1.0.3':
'@push.rocks/smartdiff@1.1.0':
dependencies:
fast-diff: 1.3.0
diff: 8.0.2
'@push.rocks/smartdns@7.6.1':
dependencies:
@@ -8145,8 +8142,6 @@ snapshots:
fast-deep-equal@3.1.3: {}
fast-diff@1.3.0: {}
fast-fifo@1.3.2: {}
fast-json-stable-stringify@2.1.0: {}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/cli',
version: '2.5.0',
version: '2.8.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

@@ -8,10 +8,11 @@ import type { TGitzoneProjectType } from './classes.gitzoneconfig.js';
* the Project class is a tool to work with a gitzone project
*/
export class Project {
public static async fromCwd() {
public static async fromCwd(options: { requireProjectType?: boolean } = {}) {
const gitzoneConfig = await GitzoneConfig.fromCwd();
const project = new Project(gitzoneConfig);
if (!project.gitzoneConfig.data.projectType) {
const requireProjectType = options.requireProjectType ?? true;
if (requireProjectType && !project.gitzoneConfig.data.projectType) {
throw new Error('Please define a project type');
}
return project;

View File

@@ -80,7 +80,9 @@ export let run = async () => {
}
// Handle format with options
// Default is dry-mode, use --write/-w to apply changes
await modFormat.run({
write: argvArg.write || argvArg.w,
dryRun: argvArg['dry-run'],
yes: argvArg.yes,
planOnly: argvArg['plan-only'],

View File

@@ -8,9 +8,20 @@ import * as ui from './mod.ui.js';
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
export const run = async (argvArg: any) => {
// Check if release flag is set and validate registries early
// Read commit config from npmextra.json
const npmextraConfig = new plugins.npmextra.Npmextra();
const gitzoneConfig = npmextraConfig.dataFor<{
commit?: {
alwaysTest?: boolean;
alwaysBuild?: boolean;
};
}>('@git.zone/cli', {});
const commitConfig = gitzoneConfig.commit || {};
// Check flags and merge with config options
const wantsRelease = !!(argvArg.r || argvArg.release);
const wantsBuild = !!(argvArg.b || argvArg.build);
const wantsTest = !!(argvArg.t || argvArg.test || commitConfig.alwaysTest);
const wantsBuild = !!(argvArg.b || argvArg.build || commitConfig.alwaysBuild);
let releaseConfig: ReleaseConfig | null = null;
if (wantsRelease) {
@@ -24,11 +35,37 @@ export const run = async (argvArg: any) => {
}
}
// Print execution plan at the start
ui.printExecutionPlan({
autoAccept: !!(argvArg.y || argvArg.yes),
push: !!(argvArg.p || argvArg.push),
test: wantsTest,
build: wantsBuild,
release: wantsRelease,
format: !!argvArg.format,
registries: releaseConfig?.getRegistries(),
});
if (argvArg.format) {
const formatMod = await import('../mod_format/index.js');
await formatMod.run();
}
// Run tests early to fail fast before analysis
if (wantsTest) {
ui.printHeader('🧪 Running tests...');
const smartshellForTest = new plugins.smartshell.Smartshell({
executor: 'bash',
sourceFilePaths: [],
});
const testResult = await smartshellForTest.exec('pnpm test');
if (testResult.exitCode !== 0) {
logger.log('error', 'Tests failed. Aborting commit.');
process.exit(1);
}
logger.log('success', 'All tests passed.');
}
ui.printHeader('🔍 Analyzing repository changes...');
const aidoc = new plugins.tsdoc.AiDoc();
@@ -151,6 +188,7 @@ export const run = async (argvArg: any) => {
}
// Determine total steps based on options
// Note: test runs early (like format) so not counted in numbered steps
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

View File

@@ -18,6 +18,16 @@ interface ICommitSummary {
releasedRegistries?: string[];
}
interface IExecutionPlanOptions {
autoAccept: boolean;
push: boolean;
test: boolean;
build: boolean;
release: boolean;
format: boolean;
registries?: string[];
}
interface IRecommendation {
recommendedNextVersion: string;
recommendedNextVersionLevel: string;
@@ -41,6 +51,58 @@ export function printHeader(title: string): void {
console.log('');
}
/**
* Print execution plan at the start of commit
*/
export function printExecutionPlan(options: IExecutionPlanOptions): void {
console.log('');
console.log('╭───────────────────────────────────────────────────────────────╮');
console.log('│ gitzone commit - Execution Plan │');
console.log('╰───────────────────────────────────────────────────────────────╯');
console.log('');
// Show active options
console.log(' Options:');
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
console.log(` Push to remote ${options.push ? '✓ enabled (-p)' : '○ disabled'}`);
console.log(` Test first ${options.test ? '✓ enabled (-t)' : '○ disabled'}`);
console.log(` Build & verify ${options.build ? '✓ enabled (-b)' : '○ disabled'}`);
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
if (options.format) {
console.log(` Format first ✓ enabled (--format)`);
}
console.log('');
// Show steps
console.log(' Steps:');
let stepNum = 1;
if (options.format) {
console.log(` ${stepNum++}. Format project files`);
}
if (options.test) {
console.log(` ${stepNum++}. Run tests`);
}
console.log(` ${stepNum++}. Analyze repository changes`);
console.log(` ${stepNum++}. Bake commit info into code`);
console.log(` ${stepNum++}. Generate changelog.md`);
console.log(` ${stepNum++}. Stage and commit files`);
console.log(` ${stepNum++}. Bump version`);
if (options.build) {
console.log(` ${stepNum++}. Run build`);
console.log(` ${stepNum++}. Verify clean working tree`);
}
if (options.push) {
console.log(` ${stepNum++}. Push to remote`);
}
if (options.release) {
console.log(` ${stepNum++}. Publish to npm registries`);
if (options.registries && options.registries.length > 0) {
options.registries.forEach(r => console.log(`${r}`));
}
}
console.log('');
}
/**
* Print a section with a border
*/

View File

@@ -0,0 +1,104 @@
import * as plugins from './mod.plugins.js';
export interface ICommitConfig {
alwaysTest: boolean;
alwaysBuild: boolean;
}
/**
* Manages commit configuration stored in npmextra.json
* under @git.zone/cli.commit namespace
*/
export class CommitConfig {
private cwd: string;
private config: ICommitConfig;
constructor(cwd: string = process.cwd()) {
this.cwd = cwd;
this.config = { alwaysTest: false, alwaysBuild: false };
}
/**
* Create a CommitConfig instance from current working directory
*/
public static async fromCwd(cwd: string = process.cwd()): Promise<CommitConfig> {
const instance = new CommitConfig(cwd);
await instance.load();
return instance;
}
/**
* Load configuration from npmextra.json
*/
public async load(): Promise<void> {
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
this.config = {
alwaysTest: gitzoneConfig?.commit?.alwaysTest ?? false,
alwaysBuild: gitzoneConfig?.commit?.alwaysBuild ?? false,
};
}
/**
* Save configuration to npmextra.json
*/
public async save(): Promise<void> {
const npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
let npmextraData: any = {};
// Read existing npmextra.json
if (await plugins.smartfs.file(npmextraPath).exists()) {
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
npmextraData = JSON.parse(content as string);
}
// Ensure @git.zone/cli namespace exists
if (!npmextraData['@git.zone/cli']) {
npmextraData['@git.zone/cli'] = {};
}
// Ensure commit object exists
if (!npmextraData['@git.zone/cli'].commit) {
npmextraData['@git.zone/cli'].commit = {};
}
// Update commit settings
npmextraData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
npmextraData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
// Write back to file
await plugins.smartfs
.file(npmextraPath)
.encoding('utf8')
.write(JSON.stringify(npmextraData, null, 2));
}
/**
* Get alwaysTest setting
*/
public getAlwaysTest(): boolean {
return this.config.alwaysTest;
}
/**
* Set alwaysTest setting
*/
public setAlwaysTest(value: boolean): void {
this.config.alwaysTest = value;
}
/**
* Get alwaysBuild setting
*/
public getAlwaysBuild(): boolean {
return this.config.alwaysBuild;
}
/**
* Set alwaysBuild setting
*/
public setAlwaysBuild(value: boolean): void {
this.config.alwaysBuild = value;
}
}

View File

@@ -2,9 +2,32 @@
import * as plugins from './mod.plugins.js';
import { ReleaseConfig } from './classes.releaseconfig.js';
import { runFormatter } from '../mod_format/index.js';
import { CommitConfig } from './classes.commitconfig.js';
import { runFormatter, type ICheckResult } from '../mod_format/index.js';
export { ReleaseConfig };
export { ReleaseConfig, CommitConfig };
/**
* Format npmextra.json with diff preview
* Shows diff first, asks for confirmation, then applies
*/
async function formatNpmextraWithDiff(): Promise<void> {
// Check for diffs first
const checkResult = await runFormatter('npmextra', {
checkOnly: true,
showDiff: true,
}) as ICheckResult | void;
if (checkResult && checkResult.hasDiff) {
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
'Apply formatting changes to npmextra.json?',
true
);
if (shouldApply) {
await runFormatter('npmextra', { silent: true });
}
}
}
export const run = async (argvArg: any) => {
const command = argvArg._?.[1];
@@ -33,6 +56,12 @@ export const run = async (argvArg: any) => {
case 'accessLevel':
await handleAccessLevel(value);
break;
case 'commit':
await handleCommit(argvArg._?.[2], argvArg._?.[3]);
break;
case 'services':
await handleServices();
break;
case 'help':
showHelp();
break;
@@ -48,7 +77,7 @@ export const run = async (argvArg: any) => {
async function handleInteractiveMenu(): Promise<void> {
console.log('');
console.log('╭─────────────────────────────────────────────────────────────╮');
console.log('│ gitzone config - Release Configuration │');
console.log('│ gitzone config - Project Configuration │');
console.log('╰─────────────────────────────────────────────────────────────╯');
console.log('');
@@ -64,6 +93,8 @@ async function handleInteractiveMenu(): Promise<void> {
{ name: 'Remove a registry', value: 'remove' },
{ name: 'Clear all registries', value: 'clear' },
{ name: 'Set access level (public/private)', value: 'access' },
{ name: 'Configure commit options', value: 'commit' },
{ name: 'Configure services', value: 'services' },
{ name: 'Show help', value: 'help' },
],
});
@@ -86,6 +117,12 @@ async function handleInteractiveMenu(): Promise<void> {
case 'access':
await handleAccessLevel();
break;
case 'commit':
await handleCommit();
break;
case 'services':
await handleServices();
break;
case 'help':
showHelp();
break;
@@ -149,8 +186,8 @@ async function handleAdd(url?: string): Promise<void> {
if (added) {
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Added registry: ${url}`);
await formatNpmextraWithDiff();
} else {
plugins.logger.log('warn', `Registry already exists: ${url}`);
}
@@ -185,8 +222,8 @@ async function handleRemove(url?: string): Promise<void> {
if (removed) {
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', `Removed registry: ${url}`);
await formatNpmextraWithDiff();
} else {
plugins.logger.log('warn', `Registry not found: ${url}`);
}
@@ -212,8 +249,8 @@ async function handleClear(): Promise<void> {
if (confirmed) {
config.clearRegistries();
await config.save();
await runFormatter('npmextra', { silent: true });
plugins.logger.log('success', 'All registries cleared.');
await formatNpmextraWithDiff();
} else {
plugins.logger.log('info', 'Operation cancelled.');
}
@@ -252,8 +289,115 @@ 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}`);
await formatNpmextraWithDiff();
}
/**
* Handle commit configuration
*/
async function handleCommit(setting?: string, value?: string): Promise<void> {
const config = await CommitConfig.fromCwd();
// No setting = interactive mode
if (!setting) {
await handleCommitInteractive(config);
return;
}
// Direct setting
switch (setting) {
case 'alwaysTest':
await handleCommitSetting(config, 'alwaysTest', value);
break;
case 'alwaysBuild':
await handleCommitSetting(config, 'alwaysBuild', value);
break;
default:
plugins.logger.log('error', `Unknown commit setting: ${setting}`);
showCommitHelp();
}
}
/**
* Interactive commit configuration
*/
async function handleCommitInteractive(config: CommitConfig): Promise<void> {
console.log('');
console.log('╭─────────────────────────────────────────────────────────────╮');
console.log('│ Commit Configuration │');
console.log('╰─────────────────────────────────────────────────────────────╯');
console.log('');
const interactInstance = new plugins.smartinteract.SmartInteract();
const response = await interactInstance.askQuestion({
type: 'checkbox',
name: 'commitOptions',
message: 'Select commit options to enable:',
choices: [
{ name: 'Always run tests before commit (-t)', value: 'alwaysTest' },
{ name: 'Always build after commit (-b)', value: 'alwaysBuild' },
],
default: [
...(config.getAlwaysTest() ? ['alwaysTest'] : []),
...(config.getAlwaysBuild() ? ['alwaysBuild'] : []),
],
});
const selected = (response as any).value || [];
config.setAlwaysTest(selected.includes('alwaysTest'));
config.setAlwaysBuild(selected.includes('alwaysBuild'));
await config.save();
plugins.logger.log('success', 'Commit configuration updated');
await formatNpmextraWithDiff();
}
/**
* Set a specific commit setting
*/
async function handleCommitSetting(config: CommitConfig, setting: string, value?: string): Promise<void> {
// Parse boolean value
const boolValue = value === 'true' || value === '1' || value === 'on';
if (setting === 'alwaysTest') {
config.setAlwaysTest(boolValue);
} else if (setting === 'alwaysBuild') {
config.setAlwaysBuild(boolValue);
}
await config.save();
plugins.logger.log('success', `Set ${setting} to ${boolValue}`);
await formatNpmextraWithDiff();
}
/**
* Show help for commit subcommand
*/
function showCommitHelp(): void {
console.log('');
console.log('Usage: gitzone config commit [setting] [value]');
console.log('');
console.log('Settings:');
console.log(' alwaysTest [true|false] Always run tests before commit');
console.log(' alwaysBuild [true|false] Always build after commit');
console.log('');
console.log('Examples:');
console.log(' gitzone config commit # Interactive mode');
console.log(' gitzone config commit alwaysTest true');
console.log(' gitzone config commit alwaysBuild false');
console.log('');
}
/**
* Handle services configuration
*/
async function handleServices(): Promise<void> {
// Import and use ServiceManager's configureServices
const { ServiceManager } = await import('../mod_services/classes.servicemanager.js');
const serviceManager = new ServiceManager();
await serviceManager.init();
await serviceManager.configureServices();
}
/**
@@ -269,6 +413,8 @@ function showHelp(): void {
console.log(' remove [url] Remove a registry URL');
console.log(' clear Clear all registries');
console.log(' access [public|private] Set npm access level for publishing');
console.log(' commit [setting] [value] Configure commit options');
console.log(' services Configure which services are enabled');
console.log('');
console.log('Examples:');
console.log(' gitzone config show');
@@ -278,5 +424,8 @@ function showHelp(): void {
console.log(' gitzone config clear');
console.log(' gitzone config access public');
console.log(' gitzone config access private');
console.log(' gitzone config commit # Interactive');
console.log(' gitzone config commit alwaysTest true');
console.log(' gitzone config services # Interactive');
console.log('');
}

View File

@@ -1,6 +1,6 @@
import * as plugins from './mod.plugins.js';
import { FormatContext } from './classes.formatcontext.js';
import type { IPlannedChange } from './interfaces.format.js';
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
import { Project } from '../classes.project.js';
export abstract class BaseFormatter {
@@ -79,4 +79,94 @@ export abstract class BaseFormatter {
protected async shouldProcessFile(filepath: string): Promise<boolean> {
return true;
}
/**
* Check for diffs without applying changes
* Returns information about what would change
*/
async check(): Promise<ICheckResult> {
const changes = await this.analyze();
const diffs: ICheckResult['diffs'] = [];
for (const change of changes) {
// Skip generic changes that don't have actual content
if (change.path === '<various files>') {
continue;
}
if (change.type === 'modify' || change.type === 'create') {
// Read current content if file exists
let currentContent: string | undefined;
try {
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
} catch {
// File doesn't exist yet
currentContent = undefined;
}
const newContent = change.content;
// Check if there's an actual diff
if (currentContent !== newContent && newContent !== undefined) {
diffs.push({
path: change.path,
type: change.type,
before: currentContent,
after: newContent,
});
}
} else if (change.type === 'delete') {
// Check if file exists before marking for deletion
try {
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
diffs.push({
path: change.path,
type: 'delete',
before: currentContent,
after: undefined,
});
} catch {
// File doesn't exist, nothing to delete
}
}
}
return {
hasDiff: diffs.length > 0,
diffs,
};
}
/**
* Display a single diff using smartdiff
*/
displayDiff(diff: ICheckResult['diffs'][0]): void {
console.log(`\n--- ${diff.path}`);
if (diff.before && diff.after) {
console.log(plugins.smartdiff.formatLineDiffForConsole(diff.before, diff.after));
} else if (diff.after && !diff.before) {
console.log(' (new file)');
// Show first few lines of new content
const lines = diff.after.split('\n').slice(0, 10);
lines.forEach(line => console.log(` + ${line}`));
if (diff.after.split('\n').length > 10) {
console.log(' ... (truncated)');
}
} else if (diff.before && !diff.after) {
console.log(' (file will be deleted)');
}
}
/**
* Display all diffs from a check result
*/
displayAllDiffs(result: ICheckResult): void {
if (!result.hasDiff) {
console.log(' No changes detected');
return;
}
for (const diff of result.diffs) {
this.displayDiff(diff);
}
}
}

View File

@@ -19,7 +19,8 @@ import { CopyFormatter } from './formatters/copy.formatter.js';
export let run = async (
options: {
dryRun?: boolean;
write?: boolean; // Explicitly write changes (default: false, dry-mode)
dryRun?: boolean; // Deprecated, kept for compatibility
yes?: boolean;
planOnly?: boolean;
savePlan?: string;
@@ -35,7 +36,11 @@ export let run = async (
setVerboseMode(true);
}
const project = await Project.fromCwd();
// Determine if we should write changes
// Default is dry-mode (no writing) unless --write/-w is specified
const shouldWrite = options.write ?? (options.dryRun === false);
const project = await Project.fromCwd({ requireProjectType: false });
const context = new FormatContext();
// Cache system removed - no longer needed
const planner = new FormatPlanner();
@@ -127,9 +132,9 @@ export let run = async (
return;
}
// Dry-run mode
if (options.dryRun) {
logger.log('info', 'Dry-run mode - no changes will be made');
// Dry-run mode (default behavior)
if (!shouldWrite) {
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
return;
}
@@ -197,14 +202,27 @@ export const handleCleanBackups = async (): Promise<void> => {
);
};
// Import the ICheckResult type for external use
import type { ICheckResult } from './interfaces.format.js';
export type { ICheckResult };
// Formatters that don't require projectType to be set
const formattersNotRequiringProjectType = ['npmextra', 'prettier', 'cleanup', 'packagejson'];
/**
* 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();
options: {
silent?: boolean;
checkOnly?: boolean; // Only check for diffs, don't apply
showDiff?: boolean; // Show the diff output
} = {}
): Promise<ICheckResult | void> => {
// Determine if this formatter requires projectType
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
const project = await Project.fromCwd({ requireProjectType });
const context = new FormatContext();
// Map formatter names to classes
@@ -227,6 +245,17 @@ export const runFormatter = async (
}
const formatter = new FormatterClass(context, project);
// Check-only mode: just check for diffs and optionally display them
if (options.checkOnly) {
const result = await formatter.check();
if (result.hasDiff && options.showDiff) {
formatter.displayAllDiffs(result);
}
return result;
}
// Normal mode: analyze and apply changes
const changes = await formatter.analyze();
for (const change of changes) {

View File

@@ -39,7 +39,18 @@ export type IPlannedChange = {
path: string;
module: string;
description: string;
content?: string; // For create/modify operations
content?: string; // New content for create/modify operations
originalContent?: string; // Original content for comparison
diff?: string;
size?: number;
};
export interface ICheckResult {
hasDiff: boolean;
diffs: Array<{
path: string;
type: 'create' | 'modify' | 'delete';
before?: string;
after?: string;
}>;
}