Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08f56ae0a4 | |||
| b2d2684895 | |||
| 1b328c3045 | |||
| f444a04876 |
23
changelog.md
23
changelog.md
@@ -1,5 +1,28 @@
|
|||||||
# Changelog
|
# 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)
|
## 2025-12-14 - 2.6.1 - fix(npmextra)
|
||||||
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration
|
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "2.6.1",
|
"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.",
|
"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",
|
"main": "dist_ts/index.ts",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"@push.rocks/npmextra": "^5.3.3",
|
"@push.rocks/npmextra": "^5.3.3",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartcli": "^4.0.19",
|
"@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/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartfs": "^1.2.0",
|
"@push.rocks/smartfs": "^1.2.0",
|
||||||
"@push.rocks/smartgulp": "^3.0.4",
|
"@push.rocks/smartgulp": "^3.0.4",
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -36,8 +36,8 @@ importers:
|
|||||||
specifier: ^4.0.19
|
specifier: ^4.0.19
|
||||||
version: 4.0.19
|
version: 4.0.19
|
||||||
'@push.rocks/smartdiff':
|
'@push.rocks/smartdiff':
|
||||||
specifier: ^1.0.3
|
specifier: ^1.1.0
|
||||||
version: 1.0.3
|
version: 1.1.0
|
||||||
'@push.rocks/smartfile':
|
'@push.rocks/smartfile':
|
||||||
specifier: ^13.1.2
|
specifier: ^13.1.2
|
||||||
version: 13.1.2
|
version: 13.1.2
|
||||||
@@ -1042,8 +1042,8 @@ packages:
|
|||||||
'@push.rocks/smartdelay@3.0.5':
|
'@push.rocks/smartdelay@3.0.5':
|
||||||
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
resolution: {integrity: sha512-mUuI7kj2f7ztjpic96FvRIlf2RsKBa5arw81AHNsndbxO6asRcxuWL8dTVxouEIK8YsBUlj0AsrCkHhMbLQdHw==}
|
||||||
|
|
||||||
'@push.rocks/smartdiff@1.0.3':
|
'@push.rocks/smartdiff@1.1.0':
|
||||||
resolution: {integrity: sha512-cXUKj0KJBxnrZDN1Ztc2WiFRJM3vOTdQUdBfe6ar5NlKuXytSRMJqVL8IUbtWfMCSOx6HgWAUT7W68+/X2TG8w==}
|
resolution: {integrity: sha512-AAz/unmko0C+g+60odOoK32PE3Ci3YLoB+zfg1LGLyVRCthcdzjqa1C2Km0MfG7IyJQKPdj8J5HPubtpm3ZeaQ==}
|
||||||
|
|
||||||
'@push.rocks/smartdns@7.6.1':
|
'@push.rocks/smartdns@7.6.1':
|
||||||
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
|
resolution: {integrity: sha512-nnP5+A2GOt0WsHrYhtKERmjdEHUchc+QbCCBEqlyeQTn+mNfx2WZvKVI1DFRJt8lamvzxP6Hr/BSe3WHdh4Snw==}
|
||||||
@@ -2536,9 +2536,6 @@ packages:
|
|||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
fast-diff@1.3.0:
|
|
||||||
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
|
|
||||||
|
|
||||||
fast-fifo@1.3.2:
|
fast-fifo@1.3.2:
|
||||||
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||||
|
|
||||||
@@ -6049,9 +6046,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
|
||||||
'@push.rocks/smartdiff@1.0.3':
|
'@push.rocks/smartdiff@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
diff: 8.0.2
|
||||||
|
|
||||||
'@push.rocks/smartdns@7.6.1':
|
'@push.rocks/smartdns@7.6.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8145,8 +8142,6 @@ snapshots:
|
|||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-diff@1.3.0: {}
|
|
||||||
|
|
||||||
fast-fifo@1.3.2: {}
|
fast-fifo@1.3.2: {}
|
||||||
|
|
||||||
fast-json-stable-stringify@2.1.0: {}
|
fast-json-stable-stringify@2.1.0: {}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '2.6.1',
|
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import type { TGitzoneProjectType } from './classes.gitzoneconfig.js';
|
|||||||
* the Project class is a tool to work with a gitzone project
|
* the Project class is a tool to work with a gitzone project
|
||||||
*/
|
*/
|
||||||
export class Project {
|
export class Project {
|
||||||
public static async fromCwd() {
|
public static async fromCwd(options: { requireProjectType?: boolean } = {}) {
|
||||||
const gitzoneConfig = await GitzoneConfig.fromCwd();
|
const gitzoneConfig = await GitzoneConfig.fromCwd();
|
||||||
const project = new Project(gitzoneConfig);
|
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');
|
throw new Error('Please define a project type');
|
||||||
}
|
}
|
||||||
return project;
|
return project;
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ export let run = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle format with options
|
// Handle format with options
|
||||||
|
// Default is dry-mode, use --write/-w to apply changes
|
||||||
await modFormat.run({
|
await modFormat.run({
|
||||||
|
write: argvArg.write || argvArg.w,
|
||||||
dryRun: argvArg['dry-run'],
|
dryRun: argvArg['dry-run'],
|
||||||
yes: argvArg.yes,
|
yes: argvArg.yes,
|
||||||
planOnly: argvArg['plan-only'],
|
planOnly: argvArg['plan-only'],
|
||||||
|
|||||||
@@ -8,9 +8,20 @@ import * as ui from './mod.ui.js';
|
|||||||
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
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 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;
|
let releaseConfig: ReleaseConfig | null = null;
|
||||||
|
|
||||||
if (wantsRelease) {
|
if (wantsRelease) {
|
||||||
@@ -28,6 +39,7 @@ export const run = async (argvArg: any) => {
|
|||||||
ui.printExecutionPlan({
|
ui.printExecutionPlan({
|
||||||
autoAccept: !!(argvArg.y || argvArg.yes),
|
autoAccept: !!(argvArg.y || argvArg.yes),
|
||||||
push: !!(argvArg.p || argvArg.push),
|
push: !!(argvArg.p || argvArg.push),
|
||||||
|
test: wantsTest,
|
||||||
build: wantsBuild,
|
build: wantsBuild,
|
||||||
release: wantsRelease,
|
release: wantsRelease,
|
||||||
format: !!argvArg.format,
|
format: !!argvArg.format,
|
||||||
@@ -39,6 +51,21 @@ export const run = async (argvArg: any) => {
|
|||||||
await formatMod.run();
|
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...');
|
ui.printHeader('🔍 Analyzing repository changes...');
|
||||||
|
|
||||||
const aidoc = new plugins.tsdoc.AiDoc();
|
const aidoc = new plugins.tsdoc.AiDoc();
|
||||||
@@ -161,6 +188,7 @@ export const run = async (argvArg: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine total steps based on options
|
// 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 willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
|
||||||
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
|
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
|
||||||
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
|
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface ICommitSummary {
|
|||||||
interface IExecutionPlanOptions {
|
interface IExecutionPlanOptions {
|
||||||
autoAccept: boolean;
|
autoAccept: boolean;
|
||||||
push: boolean;
|
push: boolean;
|
||||||
|
test: boolean;
|
||||||
build: boolean;
|
build: boolean;
|
||||||
release: boolean;
|
release: boolean;
|
||||||
format: boolean;
|
format: boolean;
|
||||||
@@ -64,6 +65,7 @@ export function printExecutionPlan(options: IExecutionPlanOptions): void {
|
|||||||
console.log(' Options:');
|
console.log(' Options:');
|
||||||
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
|
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
|
||||||
console.log(` Push to remote ${options.push ? '✓ enabled (-p)' : '○ disabled'}`);
|
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(` Build & verify ${options.build ? '✓ enabled (-b)' : '○ disabled'}`);
|
||||||
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
|
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
|
||||||
if (options.format) {
|
if (options.format) {
|
||||||
@@ -77,6 +79,9 @@ export function printExecutionPlan(options: IExecutionPlanOptions): void {
|
|||||||
if (options.format) {
|
if (options.format) {
|
||||||
console.log(` ${stepNum++}. Format project files`);
|
console.log(` ${stepNum++}. Format project files`);
|
||||||
}
|
}
|
||||||
|
if (options.test) {
|
||||||
|
console.log(` ${stepNum++}. Run tests`);
|
||||||
|
}
|
||||||
console.log(` ${stepNum++}. Analyze repository changes`);
|
console.log(` ${stepNum++}. Analyze repository changes`);
|
||||||
console.log(` ${stepNum++}. Bake commit info into code`);
|
console.log(` ${stepNum++}. Bake commit info into code`);
|
||||||
console.log(` ${stepNum++}. Generate changelog.md`);
|
console.log(` ${stepNum++}. Generate changelog.md`);
|
||||||
|
|||||||
104
ts/mod_config/classes.commitconfig.ts
Normal file
104
ts/mod_config/classes.commitconfig.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,32 @@
|
|||||||
|
|
||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import { ReleaseConfig } from './classes.releaseconfig.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) => {
|
export const run = async (argvArg: any) => {
|
||||||
const command = argvArg._?.[1];
|
const command = argvArg._?.[1];
|
||||||
@@ -33,6 +56,12 @@ export const run = async (argvArg: any) => {
|
|||||||
case 'accessLevel':
|
case 'accessLevel':
|
||||||
await handleAccessLevel(value);
|
await handleAccessLevel(value);
|
||||||
break;
|
break;
|
||||||
|
case 'commit':
|
||||||
|
await handleCommit(argvArg._?.[2], argvArg._?.[3]);
|
||||||
|
break;
|
||||||
|
case 'services':
|
||||||
|
await handleServices();
|
||||||
|
break;
|
||||||
case 'help':
|
case 'help':
|
||||||
showHelp();
|
showHelp();
|
||||||
break;
|
break;
|
||||||
@@ -48,7 +77,7 @@ export const run = async (argvArg: any) => {
|
|||||||
async function handleInteractiveMenu(): Promise<void> {
|
async function handleInteractiveMenu(): Promise<void> {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('╭─────────────────────────────────────────────────────────────╮');
|
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||||
console.log('│ gitzone config - Release Configuration │');
|
console.log('│ gitzone config - Project Configuration │');
|
||||||
console.log('╰─────────────────────────────────────────────────────────────╯');
|
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
@@ -64,6 +93,8 @@ async function handleInteractiveMenu(): Promise<void> {
|
|||||||
{ name: 'Remove a registry', value: 'remove' },
|
{ name: 'Remove a registry', value: 'remove' },
|
||||||
{ name: 'Clear all registries', value: 'clear' },
|
{ name: 'Clear all registries', value: 'clear' },
|
||||||
{ name: 'Set access level (public/private)', value: 'access' },
|
{ name: 'Set access level (public/private)', value: 'access' },
|
||||||
|
{ name: 'Configure commit options', value: 'commit' },
|
||||||
|
{ name: 'Configure services', value: 'services' },
|
||||||
{ name: 'Show help', value: 'help' },
|
{ name: 'Show help', value: 'help' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -86,6 +117,12 @@ async function handleInteractiveMenu(): Promise<void> {
|
|||||||
case 'access':
|
case 'access':
|
||||||
await handleAccessLevel();
|
await handleAccessLevel();
|
||||||
break;
|
break;
|
||||||
|
case 'commit':
|
||||||
|
await handleCommit();
|
||||||
|
break;
|
||||||
|
case 'services':
|
||||||
|
await handleServices();
|
||||||
|
break;
|
||||||
case 'help':
|
case 'help':
|
||||||
showHelp();
|
showHelp();
|
||||||
break;
|
break;
|
||||||
@@ -149,8 +186,8 @@ async function handleAdd(url?: string): Promise<void> {
|
|||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
await config.save();
|
await config.save();
|
||||||
await runFormatter('npmextra', { silent: true });
|
|
||||||
plugins.logger.log('success', `Added registry: ${url}`);
|
plugins.logger.log('success', `Added registry: ${url}`);
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
||||||
}
|
}
|
||||||
@@ -185,8 +222,8 @@ async function handleRemove(url?: string): Promise<void> {
|
|||||||
|
|
||||||
if (removed) {
|
if (removed) {
|
||||||
await config.save();
|
await config.save();
|
||||||
await runFormatter('npmextra', { silent: true });
|
|
||||||
plugins.logger.log('success', `Removed registry: ${url}`);
|
plugins.logger.log('success', `Removed registry: ${url}`);
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('warn', `Registry not found: ${url}`);
|
plugins.logger.log('warn', `Registry not found: ${url}`);
|
||||||
}
|
}
|
||||||
@@ -212,8 +249,8 @@ async function handleClear(): Promise<void> {
|
|||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
config.clearRegistries();
|
config.clearRegistries();
|
||||||
await config.save();
|
await config.save();
|
||||||
await runFormatter('npmextra', { silent: true });
|
|
||||||
plugins.logger.log('success', 'All registries cleared.');
|
plugins.logger.log('success', 'All registries cleared.');
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('info', 'Operation cancelled.');
|
plugins.logger.log('info', 'Operation cancelled.');
|
||||||
}
|
}
|
||||||
@@ -252,8 +289,115 @@ async function handleAccessLevel(level?: string): Promise<void> {
|
|||||||
|
|
||||||
config.setAccessLevel(level as 'public' | 'private');
|
config.setAccessLevel(level as 'public' | 'private');
|
||||||
await config.save();
|
await config.save();
|
||||||
await runFormatter('npmextra', { silent: true });
|
|
||||||
plugins.logger.log('success', `Access level set to: ${level}`);
|
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(' remove [url] Remove a registry URL');
|
||||||
console.log(' clear Clear all registries');
|
console.log(' clear Clear all registries');
|
||||||
console.log(' access [public|private] Set npm access level for publishing');
|
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('');
|
||||||
console.log('Examples:');
|
console.log('Examples:');
|
||||||
console.log(' gitzone config show');
|
console.log(' gitzone config show');
|
||||||
@@ -278,5 +424,8 @@ function showHelp(): void {
|
|||||||
console.log(' gitzone config clear');
|
console.log(' gitzone config clear');
|
||||||
console.log(' gitzone config access public');
|
console.log(' gitzone config access public');
|
||||||
console.log(' gitzone config access private');
|
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('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import { FormatContext } from './classes.formatcontext.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';
|
import { Project } from '../classes.project.js';
|
||||||
|
|
||||||
export abstract class BaseFormatter {
|
export abstract class BaseFormatter {
|
||||||
@@ -79,4 +79,94 @@ export abstract class BaseFormatter {
|
|||||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import { CopyFormatter } from './formatters/copy.formatter.js';
|
|||||||
|
|
||||||
export let run = async (
|
export let run = async (
|
||||||
options: {
|
options: {
|
||||||
dryRun?: boolean;
|
write?: boolean; // Explicitly write changes (default: false, dry-mode)
|
||||||
|
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
planOnly?: boolean;
|
planOnly?: boolean;
|
||||||
savePlan?: string;
|
savePlan?: string;
|
||||||
@@ -35,7 +36,11 @@ export let run = async (
|
|||||||
setVerboseMode(true);
|
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();
|
const context = new FormatContext();
|
||||||
// Cache system removed - no longer needed
|
// Cache system removed - no longer needed
|
||||||
const planner = new FormatPlanner();
|
const planner = new FormatPlanner();
|
||||||
@@ -127,9 +132,9 @@ export let run = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dry-run mode
|
// Dry-run mode (default behavior)
|
||||||
if (options.dryRun) {
|
if (!shouldWrite) {
|
||||||
logger.log('info', 'Dry-run mode - no changes will be made');
|
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
|
||||||
return;
|
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)
|
* Run a single formatter by name (for use by other modules)
|
||||||
*/
|
*/
|
||||||
export const runFormatter = async (
|
export const runFormatter = async (
|
||||||
formatterName: string,
|
formatterName: string,
|
||||||
options: { silent?: boolean } = {}
|
options: {
|
||||||
): Promise<void> => {
|
silent?: boolean;
|
||||||
const project = await Project.fromCwd();
|
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();
|
const context = new FormatContext();
|
||||||
|
|
||||||
// Map formatter names to classes
|
// Map formatter names to classes
|
||||||
@@ -227,6 +245,17 @@ export const runFormatter = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatter = new FormatterClass(context, project);
|
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();
|
const changes = await formatter.analyze();
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
|
|||||||
@@ -39,7 +39,18 @@ export type IPlannedChange = {
|
|||||||
path: string;
|
path: string;
|
||||||
module: string;
|
module: string;
|
||||||
description: 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;
|
diff?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ICheckResult {
|
||||||
|
hasDiff: boolean;
|
||||||
|
diffs: Array<{
|
||||||
|
path: string;
|
||||||
|
type: 'create' | 'modify' | 'delete';
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user