From d0d922e53bf1208c70cbc6527519c5d52ad6678c Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 24 Mar 2026 16:10:51 +0000 Subject: [PATCH] update to smartconfig --- npmextra.json => .smartconfig.json | 0 .../_smartconfig.json} | 3 + package.json | 12 +- pnpm-lock.yaml | 65 +--- ts/classes.gitzoneconfig.ts | 6 +- ts/gitzone.cli.ts | 17 - ts/mod_commit/index.ts | 6 +- ts/mod_config/classes.commitconfig.ts | 38 +- ts/mod_config/classes.releaseconfig.ts | 40 +-- ts/mod_config/index.ts | 22 +- ts/mod_format/classes.baseformatter.ts | 39 +- ts/mod_format/classes.changecache.ts | 235 ------------ ts/mod_format/classes.dependency-analyzer.ts | 117 ------ ts/mod_format/classes.formatplanner.ts | 49 +-- ts/mod_format/classes.formatstats.ts | 44 +-- ts/mod_format/classes.rollbackmanager.ts | 340 ------------------ ts/mod_format/format.cleanup.ts | 26 -- ts/mod_format/format.copy.ts | 93 ----- ts/mod_format/format.gitignore.ts | 54 --- ts/mod_format/format.license.ts | 32 -- ts/mod_format/format.npmextra.ts | 151 -------- ts/mod_format/format.packagejson.ts | 196 ---------- ts/mod_format/format.prettier.ts | 66 ---- ts/mod_format/format.readme.ts | 29 -- ts/mod_format/format.templates.ts | 79 ---- ts/mod_format/format.tsconfig.ts | 31 -- ts/mod_format/formatters/cleanup.formatter.ts | 1 - ts/mod_format/formatters/copy.formatter.ts | 12 +- .../formatters/gitignore.formatter.ts | 54 ++- .../formatters/npmextra.formatter.ts | 174 --------- .../formatters/packagejson.formatter.ts | 8 +- .../formatters/prettier.formatter.ts | 16 +- .../formatters/smartconfig.formatter.ts | 213 +++++++++++ .../formatters/templates.formatter.ts | 9 - .../formatters/tsconfig.formatter.ts | 9 +- ts/mod_format/index.ts | 133 ++----- ts/mod_format/interfaces.format.ts | 35 +- ts/mod_format/mod.plugins.ts | 14 +- ts/mod_services/classes.globalregistry.ts | 4 +- ts/mod_services/classes.servicemanager.ts | 40 +-- ts/plugins.ts | 4 +- 41 files changed, 425 insertions(+), 2091 deletions(-) rename npmextra.json => .smartconfig.json (100%) rename assets/templates/{npmextra/npmextra.json => smartconfig/_smartconfig.json} (93%) delete mode 100644 ts/mod_format/classes.changecache.ts delete mode 100644 ts/mod_format/classes.dependency-analyzer.ts delete mode 100644 ts/mod_format/classes.rollbackmanager.ts delete mode 100644 ts/mod_format/format.cleanup.ts delete mode 100644 ts/mod_format/format.copy.ts delete mode 100644 ts/mod_format/format.gitignore.ts delete mode 100644 ts/mod_format/format.license.ts delete mode 100644 ts/mod_format/format.npmextra.ts delete mode 100644 ts/mod_format/format.packagejson.ts delete mode 100644 ts/mod_format/format.prettier.ts delete mode 100644 ts/mod_format/format.readme.ts delete mode 100644 ts/mod_format/format.templates.ts delete mode 100644 ts/mod_format/format.tsconfig.ts delete mode 100644 ts/mod_format/formatters/npmextra.formatter.ts create mode 100644 ts/mod_format/formatters/smartconfig.formatter.ts diff --git a/npmextra.json b/.smartconfig.json similarity index 100% rename from npmextra.json rename to .smartconfig.json diff --git a/assets/templates/npmextra/npmextra.json b/assets/templates/smartconfig/_smartconfig.json similarity index 93% rename from assets/templates/npmextra/npmextra.json rename to assets/templates/smartconfig/_smartconfig.json index c47a692..30febf8 100644 --- a/assets/templates/npmextra/npmextra.json +++ b/assets/templates/smartconfig/_smartconfig.json @@ -1,3 +1,6 @@ +--- +fileName: .smartconfig.json +--- { "@git.zone/cli": { "projectType": "{{projectType}}", diff --git a/package.json b/package.json index f17996a..ec95ba9 100644 --- a/package.json +++ b/package.json @@ -67,16 +67,13 @@ "@git.zone/tspublish": "^1.11.2", "@push.rocks/commitinfo": "^1.0.12", "@push.rocks/early": "^4.0.4", - "@push.rocks/gulp-function": "^3.0.7", - "@push.rocks/lik": "^6.3.1", - "@push.rocks/smartconfig": "^6.0.0", "@push.rocks/projectinfo": "^5.0.2", "@push.rocks/smartcli": "^4.0.20", + "@push.rocks/smartconfig": "^6.0.1", "@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdiff": "^1.1.0", "@push.rocks/smartfile": "^13.1.2", "@push.rocks/smartfs": "^1.5.0", - "@push.rocks/smartgulp": "^3.0.4", "@push.rocks/smartinteract": "^2.0.16", "@push.rocks/smartjson": "^6.0.0", "@push.rocks/smartlegal": "^1.0.27", @@ -91,12 +88,9 @@ "@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartscaf": "^4.0.19", "@push.rocks/smartshell": "^3.3.7", - "@push.rocks/smartstream": "^3.4.0", "@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartupdate": "^2.0.6", - "@types/through2": "^2.0.41", - "prettier": "^3.8.1", - "through2": "^4.0.2" + "prettier": "^3.8.1" }, "files": [ "ts/**/*", @@ -107,7 +101,7 @@ "dist_ts_web/**/*", "assets/**/*", "cli.js", - "npmextra.json", + ".smartconfig.json", "readme.md" ], "browserslist": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f8058a..af0f58f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,12 +20,6 @@ importers: '@push.rocks/early': specifier: ^4.0.4 version: 4.0.4 - '@push.rocks/gulp-function': - specifier: ^3.0.7 - version: 3.0.7 - '@push.rocks/lik': - specifier: ^6.3.1 - version: 6.3.1 '@push.rocks/projectinfo': specifier: ^5.0.2 version: 5.0.2 @@ -33,8 +27,8 @@ importers: specifier: ^4.0.20 version: 4.0.20 '@push.rocks/smartconfig': - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^6.0.1 + version: 6.0.1 '@push.rocks/smartdelay': specifier: ^3.0.5 version: 3.0.5 @@ -47,9 +41,6 @@ importers: '@push.rocks/smartfs': specifier: ^1.5.0 version: 1.5.0 - '@push.rocks/smartgulp': - specifier: ^3.0.4 - version: 3.0.4 '@push.rocks/smartinteract': specifier: ^2.0.16 version: 2.0.16 @@ -92,24 +83,15 @@ importers: '@push.rocks/smartshell': specifier: ^3.3.7 version: 3.3.7 - '@push.rocks/smartstream': - specifier: ^3.4.0 - version: 3.4.0 '@push.rocks/smartunique': specifier: ^3.0.9 version: 3.0.9 '@push.rocks/smartupdate': specifier: ^2.0.6 version: 2.0.6 - '@types/through2': - specifier: ^2.0.41 - version: 2.0.41 prettier: specifier: ^3.8.1 version: 3.8.1 - through2: - specifier: ^4.0.2 - version: 4.0.2 devDependencies: '@git.zone/tsbuild': specifier: ^4.3.0 @@ -1018,9 +1000,6 @@ packages: '@push.rocks/early@4.0.4': resolution: {integrity: sha512-ak6/vqZ1PlFV08fSFQ6UwiBrr+K6IsfieZWWzT7eex1Ls6GvWEi8wZ3REFDPJq/qckNLWSgEy0EsqzRtltkaCA==} - '@push.rocks/gulp-function@3.0.7': - resolution: {integrity: sha512-jmGrCItaDU0vEWNWzGQxKJmSc7c66YS5qNJ5TRPOfmSAZekcucDcGoI0XBOvirX+/sb7SZWsK6/3Qo3wweNnUg==} - '@push.rocks/isounique@1.0.5': resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==} @@ -1072,8 +1051,8 @@ packages: '@push.rocks/smartclickhouse@2.2.0': resolution: {integrity: sha512-eTzKiREIPSzL1kPkVyD6vEbn+WV/DvQqDjP67VlhNlQGbRcemnJG/eLrUUR1ytmdIqnsZGEK6UYBgyj5nhzLNQ==} - '@push.rocks/smartconfig@6.0.0': - resolution: {integrity: sha512-ohXwJdbDXV2budErnZKWBOz01YkjP6gJsZ7QM9+6Wsh+r7O1CVT3JpV+mD8xJWy5tZRHI+3B9L8z0+WkIDtKzw==} + '@push.rocks/smartconfig@6.0.1': + resolution: {integrity: sha512-nuUbiOTy7WbDliZV2mG1VRaeJYygtBlLNVs3LmLoPmBzbZwwUMq+HFVS19BhXxmQnGZ5+JXW05dZXKfMqEDZnw==} '@push.rocks/smartcrypto@2.0.4': resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==} @@ -1099,9 +1078,6 @@ packages: '@push.rocks/smarterror@2.0.1': resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==} - '@push.rocks/smartevent@2.0.5': - resolution: {integrity: sha512-aU1hEoiMv8qDs+b3ln6e6GseyqM8sSqkGxhNTteLM6ve5dmTofnAdQ/tXshYNUUg2kPqi4ohcuf1/iACwjXNHw==} - '@push.rocks/smartexit@1.0.23': resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==} @@ -1135,9 +1111,6 @@ packages: '@push.rocks/smartguard@3.1.0': resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==} - '@push.rocks/smartgulp@3.0.4': - resolution: {integrity: sha512-RjMxhmELaG+OBeqQQ3xCgrBpslbpWiP523stCJ393B3Xg2E7mkzsOU4x7weJWyUw/G5SqotnaYXGj94WH8Yqtg==} - '@push.rocks/smarthash@3.2.6': resolution: {integrity: sha512-Mq/WNX0Tjjes3X1gHd/ZBwOOKSrAG/Z3Xoc0OcCm3P20WKpniihkMpsnlE7wGjvpHLi/ZRe/XkB3KC3d5r9X4g==} @@ -3764,9 +3737,6 @@ packages: threads@1.7.0: resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} - through2@3.0.2: - resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} - through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -5517,12 +5487,6 @@ snapshots: '@push.rocks/consolecolor': 2.0.3 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/gulp-function@3.0.7': - dependencies: - '@push.rocks/smartpromise': 4.2.3 - '@types/through2': 2.0.41 - through2: 3.0.2 - '@push.rocks/isounique@1.0.5': {} '@push.rocks/levelcache@3.2.0': @@ -5755,7 +5719,7 @@ snapshots: '@push.rocks/smarturl': 3.1.0 '@push.rocks/webrequest': 4.0.5 - '@push.rocks/smartconfig@6.0.0': + '@push.rocks/smartconfig@6.0.1': dependencies: '@push.rocks/qenv': 6.1.3 '@push.rocks/smartfile': 11.2.7 @@ -5844,11 +5808,6 @@ snapshots: clean-stack: 1.3.0 make-error-cause: 2.3.0 - '@push.rocks/smartevent@2.0.5': - dependencies: - '@pushrocks/smartpromise': 3.1.10 - '@pushrocks/smartrx': 2.0.27 - '@push.rocks/smartexit@1.0.23': dependencies: '@push.rocks/lik': 6.3.1 @@ -5951,15 +5910,6 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 2.1.0 - '@push.rocks/smartgulp@3.0.4': - dependencies: - '@push.rocks/smartevent': 2.0.5 - '@push.rocks/smartfile': 11.2.7 - '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartstream': 3.4.0 - '@types/through2': 2.0.41 - through2: 4.0.2 - '@push.rocks/smarthash@3.2.6': dependencies: '@push.rocks/smartenv': 5.0.13 @@ -9379,11 +9329,6 @@ snapshots: transitivePeerDependencies: - supports-color - through2@3.0.2: - dependencies: - inherits: 2.0.4 - readable-stream: 3.6.2 - through2@4.0.2: dependencies: readable-stream: 3.6.2 diff --git a/ts/classes.gitzoneconfig.ts b/ts/classes.gitzoneconfig.ts index a5e1018..973be69 100644 --- a/ts/classes.gitzoneconfig.ts +++ b/ts/classes.gitzoneconfig.ts @@ -38,11 +38,11 @@ export class GitzoneConfig { public data: IGitzoneConfigData; public async readConfigFromCwd() { - const npmextraInstance = new plugins.npmextra.Smartconfig(paths.cwd); - this.data = npmextraInstance.dataFor('@git.zone/cli', {}); + const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd); + this.data = smartconfigInstance.dataFor('@git.zone/cli', {}); // Read szci config for backward compatibility - const szciConfig = npmextraInstance.dataFor('@ship.zone/szci', {}); + const szciConfig = smartconfigInstance.dataFor('@ship.zone/szci', {}); // Prefer accessLevel from @git.zone/cli.release, fallback to @ship.zone/szci.npmAccessLevel const accessLevel = diff --git a/ts/gitzone.cli.ts b/ts/gitzone.cli.ts index 92f6882..5364707 100644 --- a/ts/gitzone.cli.ts +++ b/ts/gitzone.cli.ts @@ -63,22 +63,6 @@ export let run = async () => { const config = GitzoneConfig.fromCwd(); const modFormat = await import('./mod_format/index.js'); - // Handle rollback commands - if (argvArg.rollback) { - await modFormat.handleRollback(argvArg.rollback); - return; - } - - if (argvArg['list-backups']) { - await modFormat.handleListBackups(); - return; - } - - if (argvArg['clean-backups']) { - await modFormat.handleCleanBackups(); - return; - } - // Handle format with options // Default is dry-mode, use --write/-w to apply changes await modFormat.run({ @@ -90,7 +74,6 @@ export let run = async () => { fromPlan: argvArg['from-plan'], detailed: argvArg.detailed, interactive: argvArg.interactive !== false, - parallel: argvArg.parallel !== false, verbose: argvArg.verbose, diff: argvArg.diff, }); diff --git a/ts/mod_commit/index.ts b/ts/mod_commit/index.ts index 938fe15..cd27b40 100644 --- a/ts/mod_commit/index.ts +++ b/ts/mod_commit/index.ts @@ -8,9 +8,9 @@ import * as ui from './mod.ui.js'; import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js'; export const run = async (argvArg: any) => { - // Read commit config from npmextra.json - const npmextraConfig = new plugins.npmextra.Smartconfig(); - const gitzoneConfig = npmextraConfig.dataFor<{ + // Read commit config from .smartconfig.json + const smartconfigInstance = new plugins.smartconfig.Smartconfig(); + const gitzoneConfig = smartconfigInstance.dataFor<{ commit?: { alwaysTest?: boolean; alwaysBuild?: boolean; diff --git a/ts/mod_config/classes.commitconfig.ts b/ts/mod_config/classes.commitconfig.ts index 3433b29..8a490e4 100644 --- a/ts/mod_config/classes.commitconfig.ts +++ b/ts/mod_config/classes.commitconfig.ts @@ -6,7 +6,7 @@ export interface ICommitConfig { } /** - * Manages commit configuration stored in npmextra.json + * Manages commit configuration stored in .smartconfig.json * under @git.zone/cli.commit namespace */ export class CommitConfig { @@ -28,11 +28,11 @@ export class CommitConfig { } /** - * Load configuration from npmextra.json + * Load configuration from .smartconfig.json */ public async load(): Promise { - const npmextraInstance = new plugins.npmextra.Smartconfig(this.cwd); - const gitzoneConfig = npmextraInstance.dataFor('@git.zone/cli', {}); + const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd); + const gitzoneConfig = smartconfigInstance.dataFor('@git.zone/cli', {}); this.config = { alwaysTest: gitzoneConfig?.commit?.alwaysTest ?? false, @@ -41,37 +41,37 @@ export class CommitConfig { } /** - * Save configuration to npmextra.json + * Save configuration to .smartconfig.json */ public async save(): Promise { - const npmextraPath = plugins.path.join(this.cwd, 'smartconfig.json'); - let npmextraData: any = {}; + const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json'); + let smartconfigData: 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); + // Read existing .smartconfig.json + if (await plugins.smartfs.file(smartconfigPath).exists()) { + const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read(); + smartconfigData = JSON.parse(content as string); } // Ensure @git.zone/cli namespace exists - if (!npmextraData['@git.zone/cli']) { - npmextraData['@git.zone/cli'] = {}; + if (!smartconfigData['@git.zone/cli']) { + smartconfigData['@git.zone/cli'] = {}; } // Ensure commit object exists - if (!npmextraData['@git.zone/cli'].commit) { - npmextraData['@git.zone/cli'].commit = {}; + if (!smartconfigData['@git.zone/cli'].commit) { + smartconfigData['@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; + smartconfigData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest; + smartconfigData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild; // Write back to file await plugins.smartfs - .file(npmextraPath) + .file(smartconfigPath) .encoding('utf8') - .write(JSON.stringify(npmextraData, null, 2)); + .write(JSON.stringify(smartconfigData, null, 2)); } /** diff --git a/ts/mod_config/classes.releaseconfig.ts b/ts/mod_config/classes.releaseconfig.ts index 6fde11f..4888ea1 100644 --- a/ts/mod_config/classes.releaseconfig.ts +++ b/ts/mod_config/classes.releaseconfig.ts @@ -8,7 +8,7 @@ export interface IReleaseConfig { } /** - * Manages release configuration stored in npmextra.json + * Manages release configuration stored in .smartconfig.json * under @git.zone/cli.release namespace */ export class ReleaseConfig { @@ -30,14 +30,14 @@ export class ReleaseConfig { } /** - * Load configuration from npmextra.json + * Load configuration from .smartconfig.json */ public async load(): Promise { - const npmextraInstance = new plugins.npmextra.Smartconfig(this.cwd); - const gitzoneConfig = npmextraInstance.dataFor('@git.zone/cli', {}); + const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd); + const gitzoneConfig = smartconfigInstance.dataFor('@git.zone/cli', {}); // Also check szci for backward compatibility - const szciConfig = npmextraInstance.dataFor('@ship.zone/szci', {}); + const szciConfig = smartconfigInstance.dataFor('@ship.zone/szci', {}); this.config = { registries: gitzoneConfig?.release?.registries || [], @@ -46,37 +46,37 @@ export class ReleaseConfig { } /** - * Save configuration to npmextra.json + * Save configuration to .smartconfig.json */ public async save(): Promise { - const npmextraPath = plugins.path.join(this.cwd, 'smartconfig.json'); - let npmextraData: any = {}; + const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json'); + let smartconfigData: 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); + // Read existing .smartconfig.json + if (await plugins.smartfs.file(smartconfigPath).exists()) { + const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read(); + smartconfigData = JSON.parse(content as string); } // Ensure @git.zone/cli namespace exists - if (!npmextraData['@git.zone/cli']) { - npmextraData['@git.zone/cli'] = {}; + if (!smartconfigData['@git.zone/cli']) { + smartconfigData['@git.zone/cli'] = {}; } // Ensure release object exists - if (!npmextraData['@git.zone/cli'].release) { - npmextraData['@git.zone/cli'].release = {}; + if (!smartconfigData['@git.zone/cli'].release) { + smartconfigData['@git.zone/cli'].release = {}; } // Update registries and accessLevel - npmextraData['@git.zone/cli'].release.registries = this.config.registries; - npmextraData['@git.zone/cli'].release.accessLevel = this.config.accessLevel; + smartconfigData['@git.zone/cli'].release.registries = this.config.registries; + smartconfigData['@git.zone/cli'].release.accessLevel = this.config.accessLevel; // Write back to file await plugins.smartfs - .file(npmextraPath) + .file(smartconfigPath) .encoding('utf8') - .write(JSON.stringify(npmextraData, null, 2)); + .write(JSON.stringify(smartconfigData, null, 2)); } /** diff --git a/ts/mod_config/index.ts b/ts/mod_config/index.ts index ab292d8..417ed33 100644 --- a/ts/mod_config/index.ts +++ b/ts/mod_config/index.ts @@ -8,23 +8,23 @@ import { runFormatter, type ICheckResult } from '../mod_format/index.js'; export { ReleaseConfig, CommitConfig }; /** - * Format npmextra.json with diff preview + * Format .smartconfig.json with diff preview * Shows diff first, asks for confirmation, then applies */ -async function formatNpmextraWithDiff(): Promise { +async function formatSmartconfigWithDiff(): Promise { // Check for diffs first - const checkResult = await runFormatter('npmextra', { + const checkResult = await runFormatter('smartconfig', { checkOnly: true, showDiff: true, }) as ICheckResult | void; if (checkResult && checkResult.hasDiff) { const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation( - 'Apply formatting changes to npmextra.json?', + 'Apply formatting changes to .smartconfig.json?', true ); if (shouldApply) { - await runFormatter('npmextra', { silent: true }); + await runFormatter('smartconfig', { silent: true }); } } } @@ -187,7 +187,7 @@ async function handleAdd(url?: string): Promise { if (added) { await config.save(); plugins.logger.log('success', `Added registry: ${url}`); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } else { plugins.logger.log('warn', `Registry already exists: ${url}`); } @@ -223,7 +223,7 @@ async function handleRemove(url?: string): Promise { if (removed) { await config.save(); plugins.logger.log('success', `Removed registry: ${url}`); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } else { plugins.logger.log('warn', `Registry not found: ${url}`); } @@ -250,7 +250,7 @@ async function handleClear(): Promise { config.clearRegistries(); await config.save(); plugins.logger.log('success', 'All registries cleared.'); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } else { plugins.logger.log('info', 'Operation cancelled.'); } @@ -290,7 +290,7 @@ async function handleAccessLevel(level?: string): Promise { config.setAccessLevel(level as 'public' | 'private'); await config.save(); plugins.logger.log('success', `Access level set to: ${level}`); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } /** @@ -350,7 +350,7 @@ async function handleCommitInteractive(config: CommitConfig): Promise { await config.save(); plugins.logger.log('success', 'Commit configuration updated'); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } /** @@ -368,7 +368,7 @@ async function handleCommitSetting(config: CommitConfig, setting: string, value? await config.save(); plugins.logger.log('success', `Set ${setting} to ${boolValue}`); - await formatNpmextraWithDiff(); + await formatSmartconfigWithDiff(); } /** diff --git a/ts/mod_format/classes.baseformatter.ts b/ts/mod_format/classes.baseformatter.ts index f0fb47a..3d56c7d 100644 --- a/ts/mod_format/classes.baseformatter.ts +++ b/ts/mod_format/classes.baseformatter.ts @@ -2,11 +2,12 @@ import * as plugins from './mod.plugins.js'; import { FormatContext } from './classes.formatcontext.js'; import type { IPlannedChange, ICheckResult } from './interfaces.format.js'; import { Project } from '../classes.project.js'; +import { FormatStats } from './classes.formatstats.js'; export abstract class BaseFormatter { protected context: FormatContext; protected project: Project; - protected stats: any; // Will be FormatStats from context + protected stats: FormatStats; constructor(context: FormatContext, project: Project) { this.context = context; @@ -36,9 +37,6 @@ export abstract class BaseFormatter { } await this.postExecute(); - } catch (error) { - // Don't rollback here - let the FormatPlanner handle it - throw error; } finally { this.stats.endModule(this.name, startTime); } @@ -53,13 +51,10 @@ export abstract class BaseFormatter { } protected async modifyFile(filepath: string, content: string): Promise { - // Validate filepath before writing if (!filepath || filepath.trim() === '') { throw new Error(`Invalid empty filepath in modifyFile`); } - // Ensure we have a proper path with directory component - // If the path has no directory component (e.g., "package.json"), prepend "./" let normalizedPath = filepath; if (!plugins.path.parse(filepath).dir) { normalizedPath = './' + filepath; @@ -69,44 +64,46 @@ export abstract class BaseFormatter { } protected async createFile(filepath: string, content: string): Promise { - await plugins.smartfs.file(filepath).encoding('utf8').write(content); + let normalizedPath = filepath; + if (!plugins.path.parse(filepath).dir) { + normalizedPath = './' + filepath; + } + + // Ensure parent directory exists + const dir = plugins.path.dirname(normalizedPath); + if (dir && dir !== '.') { + await plugins.smartfs.directory(dir).recursive().create(); + } + + await plugins.smartfs.file(normalizedPath).encoding('utf8').write(content); } protected async deleteFile(filepath: string): Promise { await plugins.smartfs.file(filepath).delete(); } - protected async shouldProcessFile(filepath: string): Promise { - return true; - } - /** * Check for diffs without applying changes - * Returns information about what would change */ async check(): Promise { 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 === '') { 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, @@ -116,7 +113,6 @@ export abstract class BaseFormatter { }); } } 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({ @@ -137,9 +133,6 @@ export abstract class BaseFormatter { }; } - /** - * Display a single diff using smartdiff - */ displayDiff(diff: ICheckResult['diffs'][0]): void { console.log(`\n--- ${diff.path}`); if (diff.before && diff.after) { @@ -150,7 +143,6 @@ export abstract class BaseFormatter { })); } 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) { @@ -161,9 +153,6 @@ export abstract class BaseFormatter { } } - /** - * Display all diffs from a check result - */ displayAllDiffs(result: ICheckResult): void { if (!result.hasDiff) { console.log(' No changes detected'); diff --git a/ts/mod_format/classes.changecache.ts b/ts/mod_format/classes.changecache.ts deleted file mode 100644 index 4bc34f6..0000000 --- a/ts/mod_format/classes.changecache.ts +++ /dev/null @@ -1,235 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -export interface IFileCache { - path: string; - checksum: string; - modified: number; - size: number; -} - -export interface ICacheManifest { - version: string; - lastFormat: number; - files: IFileCache[]; -} - -export class ChangeCache { - private cacheDir: string; - private manifestPath: string; - private cacheVersion = '1.0.0'; - - constructor() { - this.cacheDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-cache'); - this.manifestPath = plugins.path.join(this.cacheDir, 'manifest.json'); - } - - async initialize(): Promise { - await plugins.smartfs.directory(this.cacheDir).recursive().create(); - } - - async getManifest(): Promise { - const defaultManifest: ICacheManifest = { - version: this.cacheVersion, - lastFormat: 0, - files: [], - }; - - const exists = await plugins.smartfs.file(this.manifestPath).exists(); - if (!exists) { - return defaultManifest; - } - - try { - const content = (await plugins.smartfs - .file(this.manifestPath) - .encoding('utf8') - .read()) as string; - const manifest = JSON.parse(content); - - // Validate the manifest structure - if (this.isValidManifest(manifest)) { - return manifest; - } else { - console.warn('Invalid manifest structure, returning default manifest'); - return defaultManifest; - } - } catch (error) { - console.warn( - `Failed to read cache manifest: ${error.message}, returning default manifest`, - ); - // Try to delete the corrupted file - try { - await plugins.smartfs.file(this.manifestPath).delete(); - } catch (removeError) { - // Ignore removal errors - } - return defaultManifest; - } - } - - async saveManifest(manifest: ICacheManifest): Promise { - // Validate before saving - if (!this.isValidManifest(manifest)) { - throw new Error('Invalid manifest structure, cannot save'); - } - - // Ensure directory exists - await plugins.smartfs.directory(this.cacheDir).recursive().create(); - - // Write directly with proper JSON stringification - const jsonContent = JSON.stringify(manifest, null, 2); - await plugins.smartfs - .file(this.manifestPath) - .encoding('utf8') - .write(jsonContent); - } - - async hasFileChanged(filePath: string): Promise { - const absolutePath = plugins.path.isAbsolute(filePath) - ? filePath - : plugins.path.join(paths.cwd, filePath); - - // Check if file exists - const exists = await plugins.smartfs.file(absolutePath).exists(); - if (!exists) { - return true; // File doesn't exist, so it's "changed" (will be created) - } - - // Get current file stats - const stats = await plugins.smartfs.file(absolutePath).stat(); - - // Skip directories - if (stats.isDirectory) { - return false; // Directories are not processed - } - - const content = (await plugins.smartfs - .file(absolutePath) - .encoding('utf8') - .read()) as string; - const currentChecksum = this.calculateChecksum(content); - - // Get cached info - const manifest = await this.getManifest(); - const cachedFile = manifest.files.find((f) => f.path === filePath); - - if (!cachedFile) { - return true; // Not in cache, so it's changed - } - - // Compare checksums - return ( - cachedFile.checksum !== currentChecksum || - cachedFile.size !== stats.size || - cachedFile.modified !== stats.mtime.getTime() - ); - } - - async updateFileCache(filePath: string): Promise { - const absolutePath = plugins.path.isAbsolute(filePath) - ? filePath - : plugins.path.join(paths.cwd, filePath); - - // Get current file stats - const stats = await plugins.smartfs.file(absolutePath).stat(); - - // Skip directories - if (stats.isDirectory) { - return; // Don't cache directories - } - - const content = (await plugins.smartfs - .file(absolutePath) - .encoding('utf8') - .read()) as string; - const checksum = this.calculateChecksum(content); - - // Update manifest - const manifest = await this.getManifest(); - const existingIndex = manifest.files.findIndex((f) => f.path === filePath); - - const cacheEntry: IFileCache = { - path: filePath, - checksum, - modified: stats.mtime.getTime(), - size: stats.size, - }; - - if (existingIndex !== -1) { - manifest.files[existingIndex] = cacheEntry; - } else { - manifest.files.push(cacheEntry); - } - - manifest.lastFormat = Date.now(); - await this.saveManifest(manifest); - } - - async getChangedFiles(filePaths: string[]): Promise { - const changedFiles: string[] = []; - - for (const filePath of filePaths) { - if (await this.hasFileChanged(filePath)) { - changedFiles.push(filePath); - } - } - - return changedFiles; - } - - async clean(): Promise { - const manifest = await this.getManifest(); - const validFiles: IFileCache[] = []; - - // Remove entries for files that no longer exist - for (const file of manifest.files) { - const absolutePath = plugins.path.isAbsolute(file.path) - ? file.path - : plugins.path.join(paths.cwd, file.path); - - if (await plugins.smartfs.file(absolutePath).exists()) { - validFiles.push(file); - } - } - - manifest.files = validFiles; - await this.saveManifest(manifest); - } - - private calculateChecksum(content: string | Buffer): string { - return plugins.crypto.createHash('sha256').update(content).digest('hex'); - } - - private isValidManifest(manifest: any): manifest is ICacheManifest { - // Check if manifest has the required structure - if (!manifest || typeof manifest !== 'object') { - return false; - } - - // Check required fields - if ( - typeof manifest.version !== 'string' || - typeof manifest.lastFormat !== 'number' || - !Array.isArray(manifest.files) - ) { - return false; - } - - // Check each file entry - for (const file of manifest.files) { - if ( - !file || - typeof file !== 'object' || - typeof file.path !== 'string' || - typeof file.checksum !== 'string' || - typeof file.modified !== 'number' || - typeof file.size !== 'number' - ) { - return false; - } - } - - return true; - } -} diff --git a/ts/mod_format/classes.dependency-analyzer.ts b/ts/mod_format/classes.dependency-analyzer.ts deleted file mode 100644 index f50cb88..0000000 --- a/ts/mod_format/classes.dependency-analyzer.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import { BaseFormatter } from './classes.baseformatter.js'; - -export interface IModuleDependency { - module: string; - dependencies: Set; - dependents: Set; -} - -export class DependencyAnalyzer { - private moduleDependencies: Map = new Map(); - - constructor() { - this.initializeDependencies(); - } - - private initializeDependencies(): void { - // Define dependencies between format modules - const dependencies = { - cleanup: [], // No dependencies - npmextra: [], // No dependencies - license: ['npmextra'], // Depends on npmextra for config - packagejson: ['npmextra'], // Depends on npmextra for config - templates: ['npmextra', 'packagejson'], // Depends on both - gitignore: ['templates'], // Depends on templates - tsconfig: ['packagejson'], // Depends on package.json - prettier: [ - 'cleanup', - 'npmextra', - 'packagejson', - 'templates', - 'gitignore', - 'tsconfig', - ], // Runs after most others - readme: ['npmextra', 'packagejson'], // Depends on project metadata - copy: ['npmextra'], // Depends on config - }; - - // Initialize all modules - for (const [module, deps] of Object.entries(dependencies)) { - this.moduleDependencies.set(module, { - module, - dependencies: new Set(deps), - dependents: new Set(), - }); - } - - // Build reverse dependencies (dependents) - for (const [module, deps] of Object.entries(dependencies)) { - for (const dep of deps) { - const depModule = this.moduleDependencies.get(dep); - if (depModule) { - depModule.dependents.add(module); - } - } - } - } - - getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] { - const modulesMap = new Map(modules.map((m) => [m.name, m])); - const executed = new Set(); - const groups: BaseFormatter[][] = []; - - while (executed.size < modules.length) { - const currentGroup: BaseFormatter[] = []; - - for (const module of modules) { - if (executed.has(module.name)) continue; - - const dependency = this.moduleDependencies.get(module.name); - if (!dependency) { - // Unknown module, execute in isolation - currentGroup.push(module); - continue; - } - - // Check if all dependencies have been executed - const allDepsExecuted = Array.from(dependency.dependencies).every( - (dep) => executed.has(dep) || !modulesMap.has(dep), - ); - - if (allDepsExecuted) { - currentGroup.push(module); - } - } - - if (currentGroup.length === 0) { - // Circular dependency or error - execute remaining modules - for (const module of modules) { - if (!executed.has(module.name)) { - currentGroup.push(module); - } - } - } - - currentGroup.forEach((m) => executed.add(m.name)); - groups.push(currentGroup); - } - - return groups; - } - - canRunInParallel(module1: string, module2: string): boolean { - const dep1 = this.moduleDependencies.get(module1); - const dep2 = this.moduleDependencies.get(module2); - - if (!dep1 || !dep2) return false; - - // Check if module1 depends on module2 or vice versa - return ( - !dep1.dependencies.has(module2) && - !dep2.dependencies.has(module1) && - !dep1.dependents.has(module2) && - !dep2.dependents.has(module1) - ); - } -} diff --git a/ts/mod_format/classes.formatplanner.ts b/ts/mod_format/classes.formatplanner.ts index 60bcc22..71a4cb0 100644 --- a/ts/mod_format/classes.formatplanner.ts +++ b/ts/mod_format/classes.formatplanner.ts @@ -2,13 +2,12 @@ import * as plugins from './mod.plugins.js'; import { FormatContext } from './classes.formatcontext.js'; import { BaseFormatter } from './classes.baseformatter.js'; import type { IFormatPlan, IPlannedChange } from './interfaces.format.js'; +import { getModuleIcon } from './interfaces.format.js'; import { logger } from '../gitzone.logging.js'; -import { DependencyAnalyzer } from './classes.dependency-analyzer.js'; import { DiffReporter } from './classes.diffreporter.js'; export class FormatPlanner { private plannedChanges: Map = new Map(); - private dependencyAnalyzer = new DependencyAnalyzer(); private diffReporter = new DiffReporter(); async planFormat(modules: BaseFormatter[]): Promise { @@ -18,7 +17,6 @@ export class FormatPlanner { filesAdded: 0, filesModified: 0, filesRemoved: 0, - estimatedTime: 0, }, changes: [], warnings: [], @@ -32,7 +30,6 @@ export class FormatPlanner { for (const change of changes) { plan.changes.push(change); - // Update summary switch (change.type) { case 'create': plan.summary.filesAdded++; @@ -58,7 +55,6 @@ export class FormatPlanner { plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved; - plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate return plan; } @@ -67,27 +63,20 @@ export class FormatPlanner { plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, - parallel: boolean = false, ): Promise { const startTime = Date.now(); - try { - // Always use sequential execution to avoid race conditions - for (const module of modules) { - const changes = this.plannedChanges.get(module.name) || []; + for (const module of modules) { + const changes = this.plannedChanges.get(module.name) || []; - if (changes.length > 0) { - logger.log('info', `Executing ${module.name} formatter...`); - await module.execute(changes); - } + if (changes.length > 0) { + logger.log('info', `Executing ${module.name} formatter...`); + await module.execute(changes); } - - const endTime = Date.now(); - const duration = endTime - startTime; - logger.log('info', `Format operations completed in ${duration}ms`); - } catch (error) { - throw error; } + + const duration = Date.now() - startTime; + logger.log('info', `Format operations completed in ${duration}ms`); } async displayPlan( @@ -103,7 +92,6 @@ export class FormatPlanner { console.log(''); console.log('Changes by module:'); - // Group changes by module const changesByModule = new Map(); for (const change of plan.changes) { const moduleChanges = changesByModule.get(change.module) || []; @@ -113,14 +101,13 @@ export class FormatPlanner { for (const [module, changes] of changesByModule) { console.log( - `\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`, + `\n${getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`, ); for (const change of changes) { const icon = this.getChangeIcon(change.type); console.log(` ${icon} ${change.path} - ${change.description}`); - // Show diff for modified files if detailed view is requested if (detailed && change.type === 'modify') { const diff = await this.diffReporter.generateDiffForChange(change); if (diff) { @@ -141,22 +128,6 @@ export class FormatPlanner { console.log('\n' + '━'.repeat(50)); } - private getModuleIcon(module: string): string { - const icons: Record = { - packagejson: '📦', - license: '📝', - tsconfig: '🔧', - cleanup: '🚮', - gitignore: '🔒', - prettier: '✨', - readme: '📖', - templates: '📄', - npmextra: '⚙️', - copy: '📋', - }; - return icons[module] || '📁'; - } - private getChangeIcon(type: 'create' | 'modify' | 'delete'): string { switch (type) { case 'create': diff --git a/ts/mod_format/classes.formatstats.ts b/ts/mod_format/classes.formatstats.ts index 7361494..d03f004 100644 --- a/ts/mod_format/classes.formatstats.ts +++ b/ts/mod_format/classes.formatstats.ts @@ -1,5 +1,6 @@ import * as plugins from './mod.plugins.js'; import { logger } from '../gitzone.logging.js'; +import { getModuleIcon } from './interfaces.format.js'; export interface IModuleStats { name: string; @@ -23,8 +24,6 @@ export interface IFormatStats { totalModified: number; totalDeleted: number; totalErrors: number; - cacheHits: number; - cacheMisses: number; }; } @@ -43,8 +42,6 @@ export class FormatStats { totalModified: 0, totalDeleted: 0, totalErrors: 0, - cacheHits: 0, - cacheMisses: 0, }, }; } @@ -107,14 +104,6 @@ export class FormatStats { } } - recordCacheHit(): void { - this.stats.overallStats.cacheHits++; - } - - recordCacheMiss(): void { - this.stats.overallStats.cacheMisses++; - } - finish(): void { this.stats.endTime = Date.now(); this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime; @@ -135,20 +124,6 @@ export class FormatStats { console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`); console.log(` Errors: ${this.stats.overallStats.totalErrors}`); - if ( - this.stats.overallStats.cacheHits > 0 || - this.stats.overallStats.cacheMisses > 0 - ) { - const cacheHitRate = - (this.stats.overallStats.cacheHits / - (this.stats.overallStats.cacheHits + - this.stats.overallStats.cacheMisses)) * - 100; - console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`); - console.log(` • Hits: ${this.stats.overallStats.cacheHits}`); - console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`); - } - // Module stats console.log('\nModule Breakdown:'); console.log('─'.repeat(50)); @@ -159,7 +134,7 @@ export class FormatStats { for (const moduleStats of sortedModules) { console.log( - `\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`, + `\n${getModuleIcon(moduleStats.name)} ${moduleStats.name}:`, ); console.log( ` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`, @@ -211,19 +186,4 @@ export class FormatStats { } } - private getModuleIcon(module: string): string { - const icons: Record = { - packagejson: '📦', - license: '📝', - tsconfig: '🔧', - cleanup: '🚮', - gitignore: '🔒', - prettier: '✨', - readme: '📖', - templates: '📄', - npmextra: '⚙️', - copy: '📋', - }; - return icons[module] || '📁'; - } } diff --git a/ts/mod_format/classes.rollbackmanager.ts b/ts/mod_format/classes.rollbackmanager.ts deleted file mode 100644 index d18d90c..0000000 --- a/ts/mod_format/classes.rollbackmanager.ts +++ /dev/null @@ -1,340 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; -import type { IFormatOperation } from './interfaces.format.js'; - -export class RollbackManager { - private backupDir: string; - private manifestPath: string; - - constructor() { - this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups'); - this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json'); - } - - async createOperation(): Promise { - await this.ensureBackupDir(); - - const operation: IFormatOperation = { - id: this.generateOperationId(), - timestamp: Date.now(), - files: [], - status: 'pending', - }; - - await this.updateManifest(operation); - return operation; - } - - async backupFile(filepath: string, operationId: string): Promise { - const operation = await this.getOperation(operationId); - if (!operation) { - throw new Error(`Operation ${operationId} not found`); - } - - const absolutePath = plugins.path.isAbsolute(filepath) - ? filepath - : plugins.path.join(paths.cwd, filepath); - - // Check if file exists - const exists = await plugins.smartfs.file(absolutePath).exists(); - if (!exists) { - // File doesn't exist yet (will be created), so we skip backup - return; - } - - // Read file content and metadata - const content = (await plugins.smartfs - .file(absolutePath) - .encoding('utf8') - .read()) as string; - const stats = await plugins.smartfs.file(absolutePath).stat(); - const checksum = this.calculateChecksum(content); - - // Create backup - const backupPath = this.getBackupPath(operationId, filepath); - await plugins.smartfs - .directory(plugins.path.dirname(backupPath)) - .recursive() - .create(); - await plugins.smartfs.file(backupPath).encoding('utf8').write(content); - - // Update operation - operation.files.push({ - path: filepath, - originalContent: content, - checksum, - permissions: stats.mode.toString(8), - }); - - await this.updateManifest(operation); - } - - async rollback(operationId: string): Promise { - const operation = await this.getOperation(operationId); - if (!operation) { - // Operation doesn't exist, might have already been rolled back or never created - console.warn(`Operation ${operationId} not found for rollback, skipping`); - return; - } - - if (operation.status === 'rolled-back') { - throw new Error(`Operation ${operationId} has already been rolled back`); - } - - // Restore files in reverse order - for (let i = operation.files.length - 1; i >= 0; i--) { - const file = operation.files[i]; - const absolutePath = plugins.path.isAbsolute(file.path) - ? file.path - : plugins.path.join(paths.cwd, file.path); - - // Verify backup integrity - const backupPath = this.getBackupPath(operationId, file.path); - const backupContent = await plugins.smartfs - .file(backupPath) - .encoding('utf8') - .read(); - const backupChecksum = this.calculateChecksum(backupContent); - - if (backupChecksum !== file.checksum) { - throw new Error(`Backup integrity check failed for ${file.path}`); - } - - // Restore file - await plugins.smartfs - .file(absolutePath) - .encoding('utf8') - .write(file.originalContent); - - // Restore permissions - const mode = parseInt(file.permissions, 8); - // Note: Permissions restoration may not work on all platforms - } - - // Update operation status - operation.status = 'rolled-back'; - await this.updateManifest(operation); - } - - async markComplete(operationId: string): Promise { - const operation = await this.getOperation(operationId); - if (!operation) { - throw new Error(`Operation ${operationId} not found`); - } - - operation.status = 'completed'; - await this.updateManifest(operation); - } - - async cleanOldBackups(retentionDays: number): Promise { - const manifest = await this.getManifest(); - const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000; - - const operationsToDelete = manifest.operations.filter( - (op) => op.timestamp < cutoffTime && op.status === 'completed', - ); - - for (const operation of operationsToDelete) { - // Remove backup files - const operationDir = plugins.path.join( - this.backupDir, - 'operations', - operation.id, - ); - await plugins.smartfs.directory(operationDir).recursive().delete(); - - // Remove from manifest - manifest.operations = manifest.operations.filter( - (op) => op.id !== operation.id, - ); - } - - await this.saveManifest(manifest); - } - - async verifyBackup(operationId: string): Promise { - const operation = await this.getOperation(operationId); - if (!operation) { - return false; - } - - for (const file of operation.files) { - const backupPath = this.getBackupPath(operationId, file.path); - const exists = await plugins.smartfs.file(backupPath).exists(); - - if (!exists) { - return false; - } - - const content = await plugins.smartfs - .file(backupPath) - .encoding('utf8') - .read(); - const checksum = this.calculateChecksum(content); - - if (checksum !== file.checksum) { - return false; - } - } - - return true; - } - - async listBackups(): Promise { - const manifest = await this.getManifest(); - return manifest.operations; - } - - private async ensureBackupDir(): Promise { - await plugins.smartfs.directory(this.backupDir).recursive().create(); - await plugins.smartfs - .directory(plugins.path.join(this.backupDir, 'operations')) - .recursive() - .create(); - } - - private generateOperationId(): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const random = Math.random().toString(36).substring(2, 8); - return `${timestamp}-${random}`; - } - - private getBackupPath(operationId: string, filepath: string): string { - const filename = plugins.path.basename(filepath); - const dir = plugins.path.dirname(filepath); - const safeDir = dir.replace(/[/\\]/g, '__'); - return plugins.path.join( - this.backupDir, - 'operations', - operationId, - 'files', - safeDir, - `${filename}.backup`, - ); - } - - private calculateChecksum(content: string | Buffer): string { - return plugins.crypto.createHash('sha256').update(content).digest('hex'); - } - - private async getManifest(): Promise<{ operations: IFormatOperation[] }> { - const defaultManifest = { operations: [] }; - - const exists = await plugins.smartfs.file(this.manifestPath).exists(); - if (!exists) { - return defaultManifest; - } - - try { - const content = (await plugins.smartfs - .file(this.manifestPath) - .encoding('utf8') - .read()) as string; - const manifest = JSON.parse(content); - - // Validate the manifest structure - if (this.isValidManifest(manifest)) { - return manifest; - } else { - console.warn( - 'Invalid rollback manifest structure, returning default manifest', - ); - return defaultManifest; - } - } catch (error) { - console.warn( - `Failed to read rollback manifest: ${error.message}, returning default manifest`, - ); - // Try to delete the corrupted file - try { - await plugins.smartfs.file(this.manifestPath).delete(); - } catch (removeError) { - // Ignore removal errors - } - return defaultManifest; - } - } - - private async saveManifest(manifest: { - operations: IFormatOperation[]; - }): Promise { - // Validate before saving - if (!this.isValidManifest(manifest)) { - throw new Error('Invalid rollback manifest structure, cannot save'); - } - - // Ensure directory exists - await this.ensureBackupDir(); - - // Write directly with proper JSON stringification - const jsonContent = JSON.stringify(manifest, null, 2); - await plugins.smartfs - .file(this.manifestPath) - .encoding('utf8') - .write(jsonContent); - } - - private async getOperation( - operationId: string, - ): Promise { - const manifest = await this.getManifest(); - return manifest.operations.find((op) => op.id === operationId) || null; - } - - private async updateManifest(operation: IFormatOperation): Promise { - const manifest = await this.getManifest(); - const existingIndex = manifest.operations.findIndex( - (op) => op.id === operation.id, - ); - - if (existingIndex !== -1) { - manifest.operations[existingIndex] = operation; - } else { - manifest.operations.push(operation); - } - - await this.saveManifest(manifest); - } - - private isValidManifest( - manifest: any, - ): manifest is { operations: IFormatOperation[] } { - // Check if manifest has the required structure - if (!manifest || typeof manifest !== 'object') { - return false; - } - - // Check required fields - if (!Array.isArray(manifest.operations)) { - return false; - } - - // Check each operation entry - for (const operation of manifest.operations) { - if ( - !operation || - typeof operation !== 'object' || - typeof operation.id !== 'string' || - typeof operation.timestamp !== 'number' || - typeof operation.status !== 'string' || - !Array.isArray(operation.files) - ) { - return false; - } - - // Check each file in the operation - for (const file of operation.files) { - if ( - !file || - typeof file !== 'object' || - typeof file.path !== 'string' || - typeof file.checksum !== 'string' - ) { - return false; - } - } - } - - return true; - } -} diff --git a/ts/mod_format/format.cleanup.ts b/ts/mod_format/format.cleanup.ts deleted file mode 100644 index fa0b198..0000000 --- a/ts/mod_format/format.cleanup.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -import { logger } from '../gitzone.logging.js'; -import { Project } from '../classes.project.js'; - -const filesToDelete = [ - 'defaults.yml', - 'yarn.lock', - 'package-lock.json', - 'tslint.json', -]; - -export const run = async (projectArg: Project) => { - for (const relativeFilePath of filesToDelete) { - const fileExists = await plugins.smartfs.file(relativeFilePath).exists(); - if (fileExists) { - logger.log('info', `Found ${relativeFilePath}! Removing it!`); - await plugins.smartfs - .file(plugins.path.join(paths.cwd, relativeFilePath)) - .delete(); - } else { - logger.log('info', `Project is free of ${relativeFilePath}`); - } - } -}; diff --git a/ts/mod_format/format.copy.ts b/ts/mod_format/format.copy.ts deleted file mode 100644 index a5d7424..0000000 --- a/ts/mod_format/format.copy.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { Project } from '../classes.project.js'; -import * as plugins from './mod.plugins.js'; -import { logger } from '../gitzone.logging.js'; - -export const run = async (projectArg: Project) => { - const gitzoneConfig = await projectArg.gitzoneConfig; - - // Get copy configuration from npmextra.json - const npmextraConfig = new plugins.npmextra.Smartconfig(); - const copyConfig = npmextraConfig.dataFor('gitzone.format.copy', { - patterns: [], - }); - - if (!copyConfig.patterns || copyConfig.patterns.length === 0) { - logger.log('info', 'No copy patterns configured in npmextra.json'); - return; - } - - for (const pattern of copyConfig.patterns) { - if (!pattern.from || !pattern.to) { - logger.log('warn', 'Invalid copy pattern - missing "from" or "to" field'); - continue; - } - - try { - // Handle glob patterns - const entries = await plugins.smartfs - .directory('.') - .recursive() - .filter(pattern.from) - .list(); - const files = entries.map((entry) => entry.path); - - for (const file of files) { - const sourcePath = file; - let destPath = pattern.to; - - // If destination is a directory, preserve filename - if (pattern.to.endsWith('/')) { - const filename = plugins.path.basename(file); - destPath = plugins.path.join(pattern.to, filename); - } - - // Handle template variables in destination path - if (pattern.preservePath) { - const relativePath = plugins.path.relative( - plugins.path.dirname(pattern.from.replace(/\*/g, '')), - file, - ); - destPath = plugins.path.join(pattern.to, relativePath); - } - - // Ensure destination directory exists - await plugins.smartfs - .directory(plugins.path.dirname(destPath)) - .recursive() - .create(); - - // Copy file - await plugins.smartfs.file(sourcePath).copy(destPath); - logger.log('info', `Copied ${sourcePath} to ${destPath}`); - } - } catch (error) { - logger.log( - 'error', - `Failed to copy pattern ${pattern.from}: ${error.message}`, - ); - } - } -}; - -/** - * Example npmextra.json configuration: - * { - * "gitzone": { - * "format": { - * "copy": { - * "patterns": [ - * { - * "from": "src/assets/*", - * "to": "dist/assets/", - * "preservePath": true - * }, - * { - * "from": "config/*.json", - * "to": "dist/" - * } - * ] - * } - * } - * } - * } - */ diff --git a/ts/mod_format/format.gitignore.ts b/ts/mod_format/format.gitignore.ts deleted file mode 100644 index 187d45b..0000000 --- a/ts/mod_format/format.gitignore.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -import { Project } from '../classes.project.js'; - -import { logger } from '../gitzone.logging.js'; -const gitignorePath = plugins.path.join(paths.cwd, './.gitignore'); - -export const run = async (projectArg: Project) => { - const gitignoreExists = await plugins.smartfs.file(gitignorePath).exists(); - let customContent = ''; - - if (gitignoreExists) { - // lets get the existing gitignore file - const existingGitIgnoreString = (await plugins.smartfs - .file(gitignorePath) - .encoding('utf8') - .read()) as string; - - // Check for different custom section markers - const customMarkers = ['#------# custom', '# custom']; - for (const marker of customMarkers) { - const splitResult = existingGitIgnoreString.split(marker); - if (splitResult.length > 1) { - // Get everything after the marker (excluding the marker itself) - customContent = splitResult[1].trim(); - break; - } - } - } - - // Write the template - const templateModule = await import('../mod_template/index.js'); - const ciTemplate = await templateModule.getTemplate('gitignore'); - await ciTemplate.writeToDisk(paths.cwd); - - // Append the custom content if it exists - if (customContent) { - const newGitignoreContent = (await plugins.smartfs - .file(gitignorePath) - .encoding('utf8') - .read()) as string; - // The template already ends with "#------# custom", so just append the content - const finalContent = - newGitignoreContent.trimEnd() + '\n' + customContent + '\n'; - await plugins.smartfs - .file(gitignorePath) - .encoding('utf8') - .write(finalContent); - logger.log('info', 'Updated .gitignore while preserving custom section!'); - } else { - logger.log('info', 'Added a .gitignore!'); - } -}; diff --git a/ts/mod_format/format.license.ts b/ts/mod_format/format.license.ts deleted file mode 100644 index fe9608b..0000000 --- a/ts/mod_format/format.license.ts +++ /dev/null @@ -1,32 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; -import { Project } from '../classes.project.js'; - -import { logger } from '../gitzone.logging.js'; - -const incompatibleLicenses: string[] = ['AGPL', 'GPL', 'SSPL']; - -export const run = async (projectArg: Project) => { - const nodeModulesInstalled = await plugins.smartfs - .directory(plugins.path.join(paths.cwd, 'node_modules')) - .exists(); - if (!nodeModulesInstalled) { - logger.log('warn', 'No node_modules found. Skipping license check'); - return; - } - const licenseChecker = await plugins.smartlegal.createLicenseChecker(); - const licenseCheckResult = await licenseChecker.excludeLicenseWithinPath( - paths.cwd, - incompatibleLicenses, - ); - if (licenseCheckResult.failingModules.length === 0) { - logger.log('info', 'Success -> licenses passed!'); - } else { - logger.log('error', 'Error -> licenses failed. Here is why:'); - for (const failedModule of licenseCheckResult.failingModules) { - console.log( - `${failedModule.name} fails with license ${failedModule.license}`, - ); - } - } -}; diff --git a/ts/mod_format/format.npmextra.ts b/ts/mod_format/format.npmextra.ts deleted file mode 100644 index a9e6253..0000000 --- a/ts/mod_format/format.npmextra.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; -import * as gulpFunction from '@push.rocks/gulp-function'; -import { Project } from '../classes.project.js'; - -/** - * Migrates npmextra.json from old namespace keys to new package-scoped keys - */ -const migrateNamespaceKeys = (npmextraJson: 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' }, - ]; - for (const { oldKey, newKey } of migrations) { - if (npmextraJson[oldKey]) { - if (!npmextraJson[newKey]) { - // New key doesn't exist - simple rename - npmextraJson[newKey] = npmextraJson[oldKey]; - } else { - // New key exists - merge old into new (old values don't overwrite new) - npmextraJson[newKey] = { - ...npmextraJson[oldKey], - ...npmextraJson[newKey], - }; - } - delete npmextraJson[oldKey]; - migrated = true; - console.log(`Migrated npmextra.json: ${oldKey} -> ${newKey}`); - } - } - return migrated; -}; - -/** - * Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel - * This is a one-time migration for projects using the old location - */ -const migrateAccessLevel = (npmextraJson: any): boolean => { - const szciConfig = npmextraJson['@ship.zone/szci']; - - // Check if szci has npmAccessLevel that needs to be migrated - if (!szciConfig?.npmAccessLevel) { - return false; - } - - // Check if we already have the new location - const gitzoneConfig = npmextraJson['@git.zone/cli'] || {}; - if (gitzoneConfig?.release?.accessLevel) { - // Already migrated, just remove from szci - delete szciConfig.npmAccessLevel; - return true; - } - - // Ensure @git.zone/cli and release exist - if (!npmextraJson['@git.zone/cli']) { - npmextraJson['@git.zone/cli'] = {}; - } - if (!npmextraJson['@git.zone/cli'].release) { - npmextraJson['@git.zone/cli'].release = {}; - } - - // Migrate the value - npmextraJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel; - delete szciConfig.npmAccessLevel; - - console.log(`Migrated npmAccessLevel to @git.zone/cli.release.accessLevel`); - return true; -}; - -/** - * runs the npmextra file checking - */ -export const run = async (projectArg: Project) => { - const formatSmartstream = new plugins.smartstream.StreamWrapper([ - plugins.smartgulp.src([`smartconfig.json`]), - gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => { - const fileString = fileArg.contents.toString(); - const npmextraJson = JSON.parse(fileString); - - // Migrate old namespace keys to new package-scoped keys - migrateNamespaceKeys(npmextraJson); - - // Migrate npmAccessLevel from szci to @git.zone/cli.release.accessLevel - migrateAccessLevel(npmextraJson); - - if (!npmextraJson['@git.zone/cli']) { - npmextraJson['@git.zone/cli'] = {}; - } - - const expectedRepoInformation: string[] = [ - 'projectType', - 'module.githost', - 'module.gitscope', - 'module.gitrepo', - 'module.description', - 'module.npmPackagename', - 'module.license', - ]; - - const interactInstance = new plugins.smartinteract.SmartInteract(); - for (const expectedRepoInformationItem of expectedRepoInformation) { - if ( - !plugins.smartobject.smartGet( - npmextraJson['@git.zone/cli'], - expectedRepoInformationItem, - ) - ) { - interactInstance.addQuestions([ - { - message: `What is the value of ${expectedRepoInformationItem}`, - name: expectedRepoInformationItem, - type: 'input', - default: 'undefined variable', - }, - ]); - } - } - - const answerbucket = await interactInstance.runQueue(); - for (const expectedRepoInformationItem of expectedRepoInformation) { - const cliProvidedValue = answerbucket.getAnswerFor( - expectedRepoInformationItem, - ); - if (cliProvidedValue) { - plugins.smartobject.smartAdd( - npmextraJson['@git.zone/cli'], - expectedRepoInformationItem, - cliProvidedValue, - ); - } - } - - // delete obsolete - // tbd - - if (!npmextraJson['@ship.zone/szci']) { - npmextraJson['@ship.zone/szci'] = {}; - } - - fileArg.setContentsFromString(JSON.stringify(npmextraJson, null, 2)); - }), - plugins.smartgulp.replace(), - ]); - await formatSmartstream.run().catch((error) => { - console.log(error); - }); -}; diff --git a/ts/mod_format/format.packagejson.ts b/ts/mod_format/format.packagejson.ts deleted file mode 100644 index 463a7e1..0000000 --- a/ts/mod_format/format.packagejson.ts +++ /dev/null @@ -1,196 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; -import * as gulpFunction from '@push.rocks/gulp-function'; -import { Project } from '../classes.project.js'; - -import { logger } from '../gitzone.logging.js'; - -/** - * ensures a certain dependency - */ -const ensureDependency = async ( - packageJsonObjectArg: any, - position: 'dep' | 'devDep' | 'everywhere', - constraint: 'exclude' | 'include' | 'latest', - dependencyArg: string, -) => { - const [packageName, version] = dependencyArg.includes('@') - ? dependencyArg.split('@').filter(Boolean) - : [dependencyArg, 'latest']; - - const targetSections: string[] = []; - - switch (position) { - case 'dep': - targetSections.push('dependencies'); - break; - case 'devDep': - targetSections.push('devDependencies'); - break; - case 'everywhere': - targetSections.push('dependencies', 'devDependencies'); - break; - } - - for (const section of targetSections) { - if (!packageJsonObjectArg[section]) { - packageJsonObjectArg[section] = {}; - } - - switch (constraint) { - case 'exclude': - delete packageJsonObjectArg[section][packageName]; - break; - case 'include': - if (!packageJsonObjectArg[section][packageName]) { - packageJsonObjectArg[section][packageName] = - version === 'latest' ? '^1.0.0' : version; - } - break; - case 'latest': - // Fetch latest version from npm - try { - const registry = new plugins.smartnpm.NpmRegistry(); - const packageInfo = await registry.getPackageInfo(packageName); - const latestVersion = packageInfo['dist-tags'].latest; - packageJsonObjectArg[section][packageName] = `^${latestVersion}`; - } catch (error) { - logger.log( - 'warn', - `Could not fetch latest version for ${packageName}, using existing or default`, - ); - if (!packageJsonObjectArg[section][packageName]) { - packageJsonObjectArg[section][packageName] = - version === 'latest' ? '^1.0.0' : version; - } - } - break; - } - } -}; - -export const run = async (projectArg: Project) => { - const formatStreamWrapper = new plugins.smartstream.StreamWrapper([ - plugins.smartgulp.src([`package.json`]), - gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => { - const npmextraConfig = new plugins.npmextra.Smartconfig(paths.cwd); - const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {}); - const fileString = fileArg.contents.toString(); - const packageJson = JSON.parse(fileString); - - // metadata - packageJson.repository = { - type: 'git', - url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`, - }; - ((packageJson.bugs = { - url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`, - }), - (packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`)); - - // Check for module type - if (!packageJson.type) { - logger.log('info', `setting packageJson.type to "module"`); - packageJson.type = 'module'; - } - - // Check for private or public - if (packageJson.private !== undefined) { - logger.log( - 'info', - 'Success -> found private/public info in package.json!', - ); - } else { - logger.log( - 'error', - 'found no private boolean! Setting it to private for now!', - ); - packageJson.private = true; - } - - // Check for license - if (packageJson.license) { - logger.log('info', 'Success -> found license in package.json!'); - } else { - logger.log( - 'error', - 'found no license! Setting it to UNLICENSED for now!', - ); - packageJson.license = 'UNLICENSED'; - } - - // Check for build script - if (packageJson.scripts.build) { - logger.log('info', 'Success -> found build script in package.json!'); - } else { - logger.log( - 'error', - 'found no build script! Putting a placeholder there for now!', - ); - packageJson.scripts.build = `echo "Not needed for now"`; - } - - // Check for buildDocs script - if (!packageJson.scripts.buildDocs) { - logger.log( - 'info', - 'found no buildDocs script! Putting tsdoc script there now.', - ); - packageJson.scripts.buildDocs = `tsdoc`; - } - - // check for files - packageJson.files = [ - 'ts/**/*', - 'ts_web/**/*', - 'dist/**/*', - 'dist_*/**/*', - 'dist_ts/**/*', - 'dist_ts_web/**/*', - 'assets/**/*', - 'cli.js', - 'smartconfig.json', - 'readme.md', - ]; - - // check for dependencies - // Note: @push.rocks/tapbundle is deprecated - use @git.zone/tstest/tapbundle instead - await ensureDependency( - packageJson, - 'devDep', - 'exclude', - '@push.rocks/tapbundle', - ); - await ensureDependency( - packageJson, - 'devDep', - 'latest', - '@git.zone/tstest', - ); - await ensureDependency( - packageJson, - 'devDep', - 'latest', - '@git.zone/tsbuild', - ); - - // set overrides - const overridesContent = (await plugins.smartfs - .file(plugins.path.join(paths.assetsDir, 'overrides.json')) - .encoding('utf8') - .read()) as string; - const overrides = JSON.parse(overridesContent); - packageJson.pnpm = packageJson.pnpm || {}; - packageJson.pnpm.overrides = overrides; - - // exclude - // TODO - - fileArg.setContentsFromString(JSON.stringify(packageJson, null, 2)); - }), - plugins.smartgulp.replace(), - ]); - await formatStreamWrapper.run().catch((error) => { - console.log(error); - }); -}; diff --git a/ts/mod_format/format.prettier.ts b/ts/mod_format/format.prettier.ts deleted file mode 100644 index dd79584..0000000 --- a/ts/mod_format/format.prettier.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import prettier from 'prettier'; -import { Project } from '../classes.project.js'; - -import { logger } from '../gitzone.logging.js'; - -const prettierDefaultTypeScriptConfig: prettier.Options = { - printWidth: 100, - parser: 'typescript', - singleQuote: true, -}; - -const prettierDefaultMarkdownConfig: prettier.Options = { - singleQuote: true, - printWidth: 100, - parser: 'markdown', -}; - -const filesToFormat = [ - `ts/**/*.ts`, - `test/**/*.ts`, - `readme.md`, - `docs/**/*.md`, -]; - -const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => { - switch (fileArg.parsedPath.ext) { - case '.ts': - return prettierDefaultTypeScriptConfig; - case '.md': - return prettierDefaultMarkdownConfig; - default: - return {}; - } -}; - -const prettierTypeScriptPipestop = plugins.through2.obj( - async (fileArg: plugins.smartfile.SmartFile, enc, cb) => { - const fileString = fileArg.contentBuffer.toString(); - const chosenConfig = choosePrettierConfig(fileArg); - const filePasses = await prettier.check(fileString, chosenConfig); - if (filePasses) { - logger.log('info', `OK! -> ${fileArg.path} passes!`); - cb(null); - } else { - logger.log('info', `${fileArg.path} is being reformated!`); - const formatedFileString = await prettier.format( - fileString, - chosenConfig, - ); - fileArg.setContentsFromString(formatedFileString); - cb(null, fileArg); - } - }, -); - -export const run = async (projectArg: Project) => { - const formatStreamWrapper = new plugins.smartstream.StreamWrapper([ - plugins.smartgulp.src(filesToFormat), - prettierTypeScriptPipestop, - plugins.smartgulp.replace(), - ]); - await formatStreamWrapper.run().catch((error) => { - console.log(error); - }); -}; diff --git a/ts/mod_format/format.readme.ts b/ts/mod_format/format.readme.ts deleted file mode 100644 index 3d5a126..0000000 --- a/ts/mod_format/format.readme.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -export const run = async () => { - const readmePath = plugins.path.join(paths.cwd, 'readme.md'); - const readmeHintsPath = plugins.path.join(paths.cwd, 'readme.hints.md'); - - // Check and initialize readme.md if it doesn't exist - const readmeExists = await plugins.smartfs.file(readmePath).exists(); - if (!readmeExists) { - await plugins.smartfs.file(readmePath) - .encoding('utf8') - .write('# Project Readme\n\nThis is the initial readme file.'); - console.log('Initialized readme.md'); - } else { - console.log('readme.md already exists'); - } - - // Check and initialize readme.hints.md if it doesn't exist - const readmeHintsExists = await plugins.smartfs.file(readmeHintsPath).exists(); - if (!readmeHintsExists) { - await plugins.smartfs.file(readmeHintsPath) - .encoding('utf8') - .write('# Project Readme Hints\n\nThis is the initial readme hints file.'); - console.log('Initialized readme.hints.md'); - } else { - console.log('readme.hints.md already exists'); - } -}; diff --git a/ts/mod_format/format.templates.ts b/ts/mod_format/format.templates.ts deleted file mode 100644 index 67c0274..0000000 --- a/ts/mod_format/format.templates.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -import { logger } from '../gitzone.logging.js'; -import { Project } from '../classes.project.js'; - -/** - * takes care of updating files from templates - */ -export const run = async (project: Project) => { - const templateModule = await import('../mod_template/index.js'); - - // update vscode - const vscodeTemplate = await templateModule.getTemplate('vscode'); - await vscodeTemplate.writeToDisk(paths.cwd); - logger.log('info', `Updated vscode template!`); - - // update gitlab ci and Dockerfile - switch (project.gitzoneConfig.data.projectType) { - case 'npm': - case 'wcc': - if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') { - const ciTemplateDefault = - await templateModule.getTemplate('ci_default'); - ciTemplateDefault.writeToDisk(paths.cwd); - } else { - const ciTemplateDefault = - await templateModule.getTemplate('ci_default_private'); - ciTemplateDefault.writeToDisk(paths.cwd); - } - logger.log('info', 'Updated .gitlabci.yml!'); - break; - case 'service': - case 'website': - const ciTemplateDocker = await templateModule.getTemplate('ci_docker'); - await ciTemplateDocker.writeToDisk(paths.cwd); - logger.log('info', 'Updated CI/CD config files!'); - - // lets care about docker - const dockerTemplate = - await templateModule.getTemplate('dockerfile_service'); - dockerTemplate.writeToDisk(paths.cwd); - logger.log('info', 'Updated Dockerfile!'); - - // lets care about cli - const cliTemplate = await templateModule.getTemplate('cli'); - await cliTemplate.writeToDisk(paths.cwd); - logger.log('info', 'Updated cli.ts.js and cli.js!'); - break; - default: - break; - } - - // update html - if (project.gitzoneConfig.data.projectType === 'website') { - const websiteUpdateTemplate = - await templateModule.getTemplate('website_update'); - const variables = { - assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl, - legalUrl: project.gitzoneConfig.data.module.legalUrl, - }; - console.log( - 'updating website template with variables\n', - JSON.stringify(variables, null, 2), - ); - websiteUpdateTemplate.supplyVariables(variables); - await websiteUpdateTemplate.writeToDisk(paths.cwd); - logger.log('info', `Updated html for website!`); - } else if (project.gitzoneConfig.data.projectType === 'service') { - const websiteUpdateTemplate = - await templateModule.getTemplate('service_update'); - await websiteUpdateTemplate.writeToDisk(paths.cwd); - logger.log('info', `Updated html for element template!`); - } else if (project.gitzoneConfig.data.projectType === 'wcc') { - const wccUpdateTemplate = await templateModule.getTemplate('wcc_update'); - await wccUpdateTemplate.writeToDisk(paths.cwd); - logger.log('info', `Updated html for wcc template!`); - } -}; diff --git a/ts/mod_format/format.tsconfig.ts b/ts/mod_format/format.tsconfig.ts deleted file mode 100644 index a956285..0000000 --- a/ts/mod_format/format.tsconfig.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as plugins from './mod.plugins.js'; -import * as paths from '../paths.js'; - -import { logger } from '../gitzone.logging.js'; -import { Project } from '../classes.project.js'; - -export const run = async (projectArg: Project) => { - // lets care about tsconfig.json - logger.log('info', 'Formatting tsconfig.json...'); - const factory = plugins.smartfile.SmartFileFactory.nodeFs(); - const tsconfigSmartfile = await factory.fromFilePath( - plugins.path.join(paths.cwd, 'tsconfig.json'), - ); - const tsconfigObject = JSON.parse(tsconfigSmartfile.parseContentAsString()); - tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {}; - tsconfigObject.compilerOptions.baseUrl = '.'; - tsconfigObject.compilerOptions.paths = {}; - const tsPublishMod = await import('@git.zone/tspublish'); - const tsPublishInstance = new tsPublishMod.TsPublish(); - const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd); - for (const publishModule of Object.keys(publishModules)) { - const publishConfig = publishModules[publishModule]; - tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [ - `./${publishModule}/index.js`, - ]; - } - await tsconfigSmartfile.editContentAsString(async () => { - return JSON.stringify(tsconfigObject, null, 2); - }); - await tsconfigSmartfile.write(); -}; diff --git a/ts/mod_format/formatters/cleanup.formatter.ts b/ts/mod_format/formatters/cleanup.formatter.ts index 8f00b14..f31e1c4 100644 --- a/ts/mod_format/formatters/cleanup.formatter.ts +++ b/ts/mod_format/formatters/cleanup.formatter.ts @@ -1,7 +1,6 @@ import { BaseFormatter } from '../classes.baseformatter.js'; import type { IPlannedChange } from '../interfaces.format.js'; import * as plugins from '../mod.plugins.js'; -import * as cleanupFormatter from '../format.cleanup.js'; export class CleanupFormatter extends BaseFormatter { get name(): string { diff --git a/ts/mod_format/formatters/copy.formatter.ts b/ts/mod_format/formatters/copy.formatter.ts index 13d7b3d..dee3499 100644 --- a/ts/mod_format/formatters/copy.formatter.ts +++ b/ts/mod_format/formatters/copy.formatter.ts @@ -17,15 +17,15 @@ export class CopyFormatter extends BaseFormatter { async analyze(): Promise { const changes: IPlannedChange[] = []; - // Get copy configuration from npmextra.json - const npmextraConfig = new plugins.npmextra.Smartconfig(); - const copyConfig = npmextraConfig.dataFor<{ patterns: ICopyPattern[] }>( + // Get copy configuration from .smartconfig.json + const smartconfigInstance = new plugins.smartconfig.Smartconfig(); + const copyConfig = smartconfigInstance.dataFor<{ patterns: ICopyPattern[] }>( 'gitzone.format.copy', { patterns: [] }, ); if (!copyConfig.patterns || copyConfig.patterns.length === 0) { - logVerbose('No copy patterns configured in npmextra.json'); + logVerbose('No copy patterns configured in .smartconfig.json'); return changes; } @@ -103,10 +103,6 @@ export class CopyFormatter extends BaseFormatter { async applyChange(change: IPlannedChange): Promise { if (!change.content) return; - // Ensure destination directory exists - const destDir = plugins.path.dirname(change.path); - await plugins.smartfs.directory(destDir).recursive().create(); - if (change.type === 'create') { await this.createFile(change.path, change.content); } else { diff --git a/ts/mod_format/formatters/gitignore.formatter.ts b/ts/mod_format/formatters/gitignore.formatter.ts index 4ea86c4..d583b31 100644 --- a/ts/mod_format/formatters/gitignore.formatter.ts +++ b/ts/mod_format/formatters/gitignore.formatter.ts @@ -1,42 +1,39 @@ import { BaseFormatter } from '../classes.baseformatter.js'; import type { IPlannedChange } from '../interfaces.format.js'; import * as plugins from '../mod.plugins.js'; +import * as paths from '../../paths.js'; import { logger } from '../../gitzone.logging.js'; -// Standard gitignore template content (without front-matter) -const GITIGNORE_TEMPLATE = `.nogit/ - -# artifacts -coverage/ -public/ - -# installs -node_modules/ - -# caches -.yarn/ -.cache/ -.rpt2_cache - -# builds -dist/ -dist_*/ - -# AI -.claude/ -.serena/ - -#------# custom`; - export class GitignoreFormatter extends BaseFormatter { get name(): string { return 'gitignore'; } + /** + * Read the standard gitignore template from the asset file, + * stripping the YAML frontmatter. + */ + private async getStandardTemplate(): Promise { + const templatePath = plugins.path.join(paths.templatesDir, 'gitignore', '_gitignore'); + const raw = (await plugins.smartfs + .file(templatePath) + .encoding('utf8') + .read()) as string; + + // Strip YAML frontmatter (---\n...\n---) + const frontmatterEnd = raw.indexOf('---', 3); + if (frontmatterEnd !== -1) { + return raw.slice(frontmatterEnd + 3).trimStart(); + } + return raw; + } + async analyze(): Promise { const changes: IPlannedChange[] = []; const gitignorePath = '.gitignore'; + const standardTemplate = await this.getStandardTemplate(); + // Check if file exists and extract custom content let customContent = ''; const exists = await plugins.smartfs.file(gitignorePath).exists(); @@ -59,11 +56,11 @@ export class GitignoreFormatter extends BaseFormatter { } // Compute new content - let newContent = GITIGNORE_TEMPLATE; + let newContent = standardTemplate; if (customContent) { - newContent = GITIGNORE_TEMPLATE + '\n' + customContent + '\n'; + newContent = standardTemplate + '\n' + customContent + '\n'; } else { - newContent = GITIGNORE_TEMPLATE + '\n'; + newContent = standardTemplate + '\n'; } // Read current content to compare @@ -75,7 +72,6 @@ export class GitignoreFormatter extends BaseFormatter { .read()) as string; } - // Determine change type if (!exists) { changes.push({ type: 'create', diff --git a/ts/mod_format/formatters/npmextra.formatter.ts b/ts/mod_format/formatters/npmextra.formatter.ts deleted file mode 100644 index a6681fc..0000000 --- a/ts/mod_format/formatters/npmextra.formatter.ts +++ /dev/null @@ -1,174 +0,0 @@ -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 npmextra.json from old namespace keys to new package-scoped keys - */ -const migrateNamespaceKeys = (npmextraJson: 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' }, - ]; - for (const { oldKey, newKey } of migrations) { - if (npmextraJson[oldKey]) { - if (!npmextraJson[newKey]) { - // New key doesn't exist - simple rename - npmextraJson[newKey] = npmextraJson[oldKey]; - } else { - // New key exists - merge old into new (old values don't overwrite new) - npmextraJson[newKey] = { - ...npmextraJson[oldKey], - ...npmextraJson[newKey], - }; - } - delete npmextraJson[oldKey]; - migrated = true; - } - } - return migrated; -}; - -/** - * Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel - */ -const migrateAccessLevel = (npmextraJson: any): boolean => { - const szciConfig = npmextraJson['@ship.zone/szci']; - - if (!szciConfig?.npmAccessLevel) { - return false; - } - - const gitzoneConfig = npmextraJson['@git.zone/cli'] || {}; - if (gitzoneConfig?.release?.accessLevel) { - delete szciConfig.npmAccessLevel; - return true; - } - - if (!npmextraJson['@git.zone/cli']) { - npmextraJson['@git.zone/cli'] = {}; - } - if (!npmextraJson['@git.zone/cli'].release) { - npmextraJson['@git.zone/cli'].release = {}; - } - - npmextraJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel; - delete szciConfig.npmAccessLevel; - - return true; -}; - -export class NpmextraFormatter extends BaseFormatter { - get name(): string { - return 'npmextra'; - } - - async analyze(): Promise { - const changes: IPlannedChange[] = []; - const npmextraPath = 'smartconfig.json'; - - // Check if file exists - const exists = await plugins.smartfs.file(npmextraPath).exists(); - if (!exists) { - logVerbose('npmextra.json does not exist, skipping'); - return changes; - } - - // Read current content - const currentContent = (await plugins.smartfs - .file(npmextraPath) - .encoding('utf8') - .read()) as string; - - // Parse and compute new content - const npmextraJson = JSON.parse(currentContent); - - // Apply migrations (these are automatic, non-interactive) - migrateNamespaceKeys(npmextraJson); - migrateAccessLevel(npmextraJson); - - // Ensure namespaces exist - if (!npmextraJson['@git.zone/cli']) { - npmextraJson['@git.zone/cli'] = {}; - } - if (!npmextraJson['@ship.zone/szci']) { - npmextraJson['@ship.zone/szci'] = {}; - } - - const newContent = JSON.stringify(npmextraJson, null, 2); - - // Only add change if content differs - if (newContent !== currentContent) { - changes.push({ - type: 'modify', - path: npmextraPath, - module: this.name, - description: 'Migrate and format npmextra.json', - content: newContent, - }); - } - - return changes; - } - - async applyChange(change: IPlannedChange): Promise { - if (change.type !== 'modify' || !change.content) return; - - // Parse the content to check for missing required fields - const npmextraJson = 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', - ]; - - const interactInstance = new plugins.smartinteract.SmartInteract(); - for (const expectedRepoInformationItem of expectedRepoInformation) { - if ( - !plugins.smartobject.smartGet( - npmextraJson['@git.zone/cli'], - expectedRepoInformationItem, - ) - ) { - interactInstance.addQuestions([ - { - message: `What is the value of ${expectedRepoInformationItem}`, - name: expectedRepoInformationItem, - type: 'input', - default: 'undefined variable', - }, - ]); - } - } - - const answerbucket = await interactInstance.runQueue(); - for (const expectedRepoInformationItem of expectedRepoInformation) { - const cliProvidedValue = answerbucket.getAnswerFor( - expectedRepoInformationItem, - ); - if (cliProvidedValue) { - plugins.smartobject.smartAdd( - npmextraJson['@git.zone/cli'], - expectedRepoInformationItem, - cliProvidedValue, - ); - } - } - - // Write the final content - const finalContent = JSON.stringify(npmextraJson, null, 2); - await this.modifyFile(change.path, finalContent); - logger.log('info', 'Updated npmextra.json'); - } -} diff --git a/ts/mod_format/formatters/packagejson.formatter.ts b/ts/mod_format/formatters/packagejson.formatter.ts index 38a9a18..585a255 100644 --- a/ts/mod_format/formatters/packagejson.formatter.ts +++ b/ts/mod_format/formatters/packagejson.formatter.ts @@ -100,9 +100,9 @@ export class PackageJsonFormatter extends BaseFormatter { // Parse and compute new content const packageJson = JSON.parse(currentContent); - // Get gitzone config from npmextra - const npmextraConfig = new plugins.npmextra.Smartconfig(paths.cwd); - const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {}); + // Get gitzone config from smartconfig + const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd); + const gitzoneData: any = smartconfigInstance.dataFor('@git.zone/cli', {}); // Set metadata from gitzone config if (gitzoneData.module) { @@ -156,7 +156,7 @@ export class PackageJsonFormatter extends BaseFormatter { 'dist_ts_web/**/*', 'assets/**/*', 'cli.js', - 'smartconfig.json', + '.smartconfig.json', 'readme.md', ]; diff --git a/ts/mod_format/formatters/prettier.formatter.ts b/ts/mod_format/formatters/prettier.formatter.ts index 70f015a..155066f 100644 --- a/ts/mod_format/formatters/prettier.formatter.ts +++ b/ts/mod_format/formatters/prettier.formatter.ts @@ -21,7 +21,7 @@ export class PrettierFormatter extends BaseFormatter { const rootConfigFiles = [ 'package.json', 'tsconfig.json', - 'smartconfig.json', + '.smartconfig.json', '.prettierrc', '.prettierrc.json', '.prettierrc.js', @@ -79,12 +79,9 @@ export class PrettierFormatter extends BaseFormatter { // Remove duplicates const uniqueFiles = [...new Set(allFiles)]; - // Get all files that match the pattern - const files = uniqueFiles; - // Ensure we only process actual files (not directories) const validFiles: string[] = []; - for (const file of files) { + for (const file of uniqueFiles) { try { const stats = await plugins.smartfs.file(file).stat(); if (!stats.isDirectory) { @@ -96,14 +93,7 @@ export class PrettierFormatter extends BaseFormatter { } } - // Check which files need formatting for (const file of validFiles) { - // Skip files that haven't changed - if (!(await this.shouldProcessFile(file))) { - logVerbose(`Skipping ${file} - no changes detected`); - continue; - } - changes.push({ type: 'modify', path: file, @@ -232,7 +222,7 @@ export class PrettierFormatter extends BaseFormatter { private async getPrettierConfig(): Promise { // Try to load prettier config from the project - const prettierConfig = new plugins.npmextra.Smartconfig(); + const prettierConfig = new plugins.smartconfig.Smartconfig(); return prettierConfig.dataFor('prettier', { // Default prettier config singleQuote: true, diff --git a/ts/mod_format/formatters/smartconfig.formatter.ts b/ts/mod_format/formatters/smartconfig.formatter.ts new file mode 100644 index 0000000..57842bb --- /dev/null +++ b/ts/mod_format/formatters/smartconfig.formatter.ts @@ -0,0 +1,213 @@ +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 + */ +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' }, + ]; + for (const { oldKey, newKey } of migrations) { + if (smartconfigJson[oldKey]) { + if (!smartconfigJson[newKey]) { + smartconfigJson[newKey] = smartconfigJson[oldKey]; + } else { + smartconfigJson[newKey] = { + ...smartconfigJson[oldKey], + ...smartconfigJson[newKey], + }; + } + delete smartconfigJson[oldKey]; + migrated = true; + } + } + return migrated; +}; + +/** + * Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel + */ +const migrateAccessLevel = (smartconfigJson: any): boolean => { + const szciConfig = smartconfigJson['@ship.zone/szci']; + + if (!szciConfig?.npmAccessLevel) { + return false; + } + + 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'].release) { + smartconfigJson['@git.zone/cli'].release = {}; + } + + smartconfigJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel; + delete szciConfig.npmAccessLevel; + + return true; +}; + +// Config file names in priority order (newest → oldest) +const CONFIG_FILE_NAMES = ['.smartconfig.json', 'smartconfig.json', 'npmextra.json']; +const TARGET_CONFIG_FILE = '.smartconfig.json'; + +export class SmartconfigFormatter extends BaseFormatter { + get name(): string { + return 'smartconfig'; + } + + /** + * Find the config file, checking in priority order. + * Returns the path and whether it needs renaming. + */ + private async findConfigFile(): Promise<{ path: string; needsRename: boolean } | null> { + for (const filename of CONFIG_FILE_NAMES) { + const exists = await plugins.smartfs.file(filename).exists(); + if (exists) { + return { + path: filename, + needsRename: filename !== TARGET_CONFIG_FILE, + }; + } + } + return null; + } + + async analyze(): Promise { + const changes: IPlannedChange[] = []; + + const configFile = await this.findConfigFile(); + if (!configFile) { + logVerbose('No config file found (.smartconfig.json, smartconfig.json, or npmextra.json), skipping'); + return changes; + } + + // Read current content + const currentContent = (await plugins.smartfs + .file(configFile.path) + .encoding('utf8') + .read()) as string; + + // Parse and apply migrations + const smartconfigJson = JSON.parse(currentContent); + migrateNamespaceKeys(smartconfigJson); + migrateAccessLevel(smartconfigJson); + + // Ensure namespaces exist + if (!smartconfigJson['@git.zone/cli']) { + smartconfigJson['@git.zone/cli'] = {}; + } + if (!smartconfigJson['@ship.zone/szci']) { + smartconfigJson['@ship.zone/szci'] = {}; + } + + const newContent = JSON.stringify(smartconfigJson, null, 2); + + // If file needs renaming, plan a create + delete + if (configFile.needsRename) { + changes.push({ + type: 'create', + path: TARGET_CONFIG_FILE, + module: this.name, + description: `Migrate ${configFile.path} to ${TARGET_CONFIG_FILE}`, + content: newContent, + }); + changes.push({ + type: 'delete', + path: configFile.path, + module: this.name, + description: `Remove old ${configFile.path}`, + }); + } else if (newContent !== currentContent) { + // File is already .smartconfig.json, just needs content update + changes.push({ + type: 'modify', + path: TARGET_CONFIG_FILE, + module: this.name, + description: 'Migrate and format .smartconfig.json', + content: newContent, + }); + } + + return changes; + } + + async applyChange(change: IPlannedChange): Promise { + if (change.type === 'delete') { + await this.deleteFile(change.path); + logger.log('info', `Removed old config file ${change.path}`); + return; + } + + if (!change.content) return; + + // Parse the content to check for missing required fields + const smartconfigJson = JSON.parse(change.content); + + const expectedRepoInformation: string[] = [ + 'projectType', + 'module.githost', + 'module.gitscope', + 'module.gitrepo', + 'module.description', + 'module.npmPackagename', + 'module.license', + ]; + + const interactInstance = new plugins.smartinteract.SmartInteract(); + for (const expectedRepoInformationItem of expectedRepoInformation) { + if ( + !plugins.smartobject.smartGet( + smartconfigJson['@git.zone/cli'], + expectedRepoInformationItem, + ) + ) { + interactInstance.addQuestions([ + { + message: `What is the value of ${expectedRepoInformationItem}`, + name: expectedRepoInformationItem, + type: 'input', + default: 'undefined variable', + }, + ]); + } + } + + const answerbucket = await interactInstance.runQueue(); + for (const expectedRepoInformationItem of expectedRepoInformation) { + const cliProvidedValue = answerbucket.getAnswerFor( + expectedRepoInformationItem, + ); + if (cliProvidedValue) { + plugins.smartobject.smartAdd( + smartconfigJson['@git.zone/cli'], + expectedRepoInformationItem, + cliProvidedValue, + ); + } + } + + const finalContent = JSON.stringify(smartconfigJson, null, 2); + + if (change.type === 'create') { + await this.createFile(change.path, finalContent); + } else { + await this.modifyFile(change.path, finalContent); + } + logger.log('info', `Updated ${change.path}`); + } +} diff --git a/ts/mod_format/formatters/templates.formatter.ts b/ts/mod_format/formatters/templates.formatter.ts index 088bbd8..8772472 100644 --- a/ts/mod_format/formatters/templates.formatter.ts +++ b/ts/mod_format/formatters/templates.formatter.ts @@ -62,9 +62,6 @@ export class TemplatesFormatter extends BaseFormatter { { templatePath: 'html/index.html', destPath: 'html/index.html' }, ]); changes.push(...websiteChanges); - } else if (projectType === 'service') { - const serviceChanges = await this.analyzeTemplate('service_update', []); - changes.push(...serviceChanges); } else if (projectType === 'wcc') { const wccChanges = await this.analyzeTemplate('wcc_update', [ { templatePath: 'html/index.html', destPath: 'html/index.html' }, @@ -139,12 +136,6 @@ export class TemplatesFormatter extends BaseFormatter { async applyChange(change: IPlannedChange): Promise { if (!change.content) return; - // Ensure destination directory exists - const destDir = plugins.path.dirname(change.path); - if (destDir && destDir !== '.') { - await plugins.smartfs.directory(destDir).recursive().create(); - } - if (change.type === 'create') { await this.createFile(change.path, change.content); } else { diff --git a/ts/mod_format/formatters/tsconfig.formatter.ts b/ts/mod_format/formatters/tsconfig.formatter.ts index 43c76ca..f3a3ac9 100644 --- a/ts/mod_format/formatters/tsconfig.formatter.ts +++ b/ts/mod_format/formatters/tsconfig.formatter.ts @@ -30,9 +30,10 @@ export class TsconfigFormatter extends BaseFormatter { const tsconfigObject = JSON.parse(currentContent); tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {}; tsconfigObject.compilerOptions.baseUrl = '.'; - tsconfigObject.compilerOptions.paths = {}; + const existingPaths = tsconfigObject.compilerOptions.paths || {}; - // Get module paths from tspublish + // Get module paths from tspublish, merging with existing custom paths + const tspublishPaths: Record = {}; try { const tsPublishMod = await import('@git.zone/tspublish'); const tsPublishInstance = new tsPublishMod.TsPublish(); @@ -40,7 +41,7 @@ export class TsconfigFormatter extends BaseFormatter { for (const publishModule of Object.keys(publishModules)) { const publishConfig = publishModules[publishModule]; - tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [ + tspublishPaths[`${publishConfig.name}`] = [ `./${publishModule}/index.js`, ]; } @@ -48,6 +49,8 @@ export class TsconfigFormatter extends BaseFormatter { logVerbose(`Could not get tspublish modules: ${error.message}`); } + tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths }; + const newContent = JSON.stringify(tsconfigObject, null, 2); // Only add change if content differs diff --git a/ts/mod_format/index.ts b/ts/mod_format/index.ts index a84a41e..98f187d 100644 --- a/ts/mod_format/index.ts +++ b/ts/mod_format/index.ts @@ -5,9 +5,8 @@ import { FormatPlanner } from './classes.formatplanner.js'; import { BaseFormatter } from './classes.baseformatter.js'; import { logger, setVerboseMode } from '../gitzone.logging.js'; -// Import wrapper classes for formatters import { CleanupFormatter } from './formatters/cleanup.formatter.js'; -import { NpmextraFormatter } from './formatters/npmextra.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'; @@ -17,82 +16,66 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js'; import { ReadmeFormatter } from './formatters/readme.formatter.js'; import { CopyFormatter } from './formatters/copy.formatter.js'; +// Shared formatter class map used by both run() and runFormatter() +const formatterMap: Record BaseFormatter> = { + cleanup: CleanupFormatter, + smartconfig: SmartconfigFormatter, + license: LicenseFormatter, + packagejson: PackageJsonFormatter, + templates: TemplatesFormatter, + gitignore: GitignoreFormatter, + tsconfig: TsconfigFormatter, + prettier: PrettierFormatter, + readme: ReadmeFormatter, + copy: CopyFormatter, +}; + +// Formatters that don't require projectType to be set +const formattersNotRequiringProjectType = ['smartconfig', 'prettier', 'cleanup', 'packagejson']; + export let run = async ( options: { - write?: boolean; // Explicitly write changes (default: false, dry-mode) - dryRun?: boolean; // Deprecated, kept for compatibility + write?: boolean; + dryRun?: boolean; // Deprecated, kept for compatibility yes?: boolean; planOnly?: boolean; savePlan?: string; fromPlan?: string; detailed?: boolean; interactive?: boolean; - parallel?: boolean; verbose?: boolean; - diff?: boolean; // Show file diffs + diff?: boolean; } = {}, ): Promise => { - // Set verbose mode if requested if (options.verbose) { setVerboseMode(true); } - // 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(); - // Get configuration from npmextra - const npmextraConfig = new plugins.npmextra.Smartconfig(); - const formatConfig = npmextraConfig.dataFor('@git.zone/cli.format', { + const smartconfigInstance = new plugins.smartconfig.Smartconfig(); + const formatConfig = smartconfigInstance.dataFor('@git.zone/cli.format', { interactive: true, showDiffs: false, autoApprove: false, - planTimeout: 30000, - rollback: { - enabled: true, - autoRollbackOnError: true, - backupRetentionDays: 7, - maxBackupSize: '100MB', - excludePatterns: ['node_modules/**', '.git/**'], - }, modules: { skip: [], only: [], - order: [], - }, - parallel: true, - cache: { - enabled: true, - clean: true, // Clean invalid entries from cache }, }); - // Cache cleaning removed - no longer using cache system - - // Override config with command options const interactive = options.interactive ?? formatConfig.interactive; const autoApprove = options.yes ?? formatConfig.autoApprove; - const parallel = options.parallel ?? formatConfig.parallel; try { - // Initialize formatters - const formatters = [ - new CleanupFormatter(context, project), - new NpmextraFormatter(context, project), - new LicenseFormatter(context, project), - new PackageJsonFormatter(context, project), - new TemplatesFormatter(context, project), - new GitignoreFormatter(context, project), - new TsconfigFormatter(context, project), - new PrettierFormatter(context, project), - new ReadmeFormatter(context, project), - new CopyFormatter(context, project), - ]; + // Initialize formatters in execution order + const formatters = Object.entries(formatterMap).map( + ([, FormatterClass]) => new FormatterClass(context, project), + ); // Filter formatters based on configuration const activeFormatters = formatters.filter((formatter) => { @@ -128,13 +111,13 @@ export let run = async ( logger.log('info', `Plan saved to ${options.savePlan}`); } - // Exit if plan-only mode if (options.planOnly) { return; } - // Show diffs if requested (works in both dry-run and write modes) - if (options.diff) { + // Show diffs if explicitly requested or before interactive write confirmation + const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove); + if (showDiffs) { logger.log('info', 'Showing file diffs:'); console.log(''); @@ -171,22 +154,16 @@ export let run = async ( } // Execute phase - logger.log( - 'info', - `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`, - ); - await planner.executePlan(plan, activeFormatters, context, parallel); + logger.log('info', 'Executing format operations...'); + await planner.executePlan(plan, activeFormatters, context); - // Finish statistics tracking context.getFormatStats().finish(); - // Display statistics - const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true); + const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true); if (showStats) { context.getFormatStats().displayStats(); } - // Save stats if requested if (options.detailed) { const statsPath = `.nogit/format-stats-${Date.now()}.json`; await context.getFormatStats().saveReport(statsPath); @@ -195,36 +172,13 @@ export let run = async ( logger.log('success', 'Format operations completed successfully!'); } catch (error) { logger.log('error', `Format operation failed: ${error.message}`); - - // Rollback system has been removed for stability - throw error; } }; -// Export CLI command handlers -export const handleRollback = async (operationId?: string): Promise => { - logger.log('info', 'Rollback system has been disabled for stability'); -}; - -export const handleListBackups = async (): Promise => { - logger.log('info', 'Backup system has been disabled for stability'); -}; - -export const handleCleanBackups = async (): Promise => { - logger.log( - 'info', - 'Backup cleaning has been disabled - backup system removed', - ); -}; - -// 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) */ @@ -232,29 +186,14 @@ export const runFormatter = async ( formatterName: string, options: { silent?: boolean; - checkOnly?: boolean; // Only check for diffs, don't apply - showDiff?: boolean; // Show the diff output + checkOnly?: boolean; + showDiff?: boolean; } = {} ): Promise => { - // 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 - const formatterMap: Record 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}`); @@ -262,7 +201,6 @@ 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) { @@ -271,7 +209,6 @@ export const runFormatter = async ( return result; } - // Normal mode: analyze and apply changes const changes = await formatter.analyze(); for (const change of changes) { diff --git a/ts/mod_format/interfaces.format.ts b/ts/mod_format/interfaces.format.ts index 264001b..9e30691 100644 --- a/ts/mod_format/interfaces.format.ts +++ b/ts/mod_format/interfaces.format.ts @@ -1,31 +1,15 @@ -export type IFormatOperation = { - id: string; - timestamp: number; - files: Array<{ - path: string; - originalContent: string; - checksum: string; - permissions: string; - }>; - status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back'; - error?: Error; -}; - export type IFormatPlan = { summary: { totalFiles: number; filesAdded: number; filesModified: number; filesRemoved: number; - estimatedTime: number; }; changes: Array<{ type: 'create' | 'modify' | 'delete'; path: string; module: string; description: string; - diff?: string; - size?: number; }>; warnings: Array<{ level: 'info' | 'warning' | 'error'; @@ -40,9 +24,6 @@ export type IPlannedChange = { module: string; description: string; content?: string; // New content for create/modify operations - originalContent?: string; // Original content for comparison - diff?: string; - size?: number; }; export interface ICheckResult { @@ -54,3 +35,19 @@ export interface ICheckResult { after?: string; }>; } + +export function getModuleIcon(module: string): string { + const icons: Record = { + packagejson: '📦', + license: '📝', + tsconfig: '🔧', + cleanup: '🚮', + gitignore: '🔒', + prettier: '✨', + readme: '📖', + templates: '📄', + smartconfig: '⚙️', + copy: '📋', + }; + return icons[module] || '📁'; +} diff --git a/ts/mod_format/mod.plugins.ts b/ts/mod_format/mod.plugins.ts index ef48b47..8f77ee9 100644 --- a/ts/mod_format/mod.plugins.ts +++ b/ts/mod_format/mod.plugins.ts @@ -1,31 +1,21 @@ export * from '../plugins.js'; -import * as crypto from 'crypto'; import * as path from 'path'; -import * as lik from '@push.rocks/lik'; import * as smartfile from '@push.rocks/smartfile'; -import * as smartgulp from '@push.rocks/smartgulp'; import * as smartinteract from '@push.rocks/smartinteract'; import * as smartlegal from '@push.rocks/smartlegal'; import * as smartobject from '@push.rocks/smartobject'; import * as smartnpm from '@push.rocks/smartnpm'; -import * as smartstream from '@push.rocks/smartstream'; -import * as through2 from 'through2'; -import * as npmextra from '@push.rocks/smartconfig'; +import * as smartconfig from '@push.rocks/smartconfig'; import * as smartdiff from '@push.rocks/smartdiff'; export { - crypto, path, - lik, smartfile, - smartgulp, smartinteract, smartlegal, smartobject, smartnpm, - smartstream, - through2, - npmextra, + smartconfig, smartdiff, }; diff --git a/ts/mod_services/classes.globalregistry.ts b/ts/mod_services/classes.globalregistry.ts index 2171558..b24a0ba 100644 --- a/ts/mod_services/classes.globalregistry.ts +++ b/ts/mod_services/classes.globalregistry.ts @@ -26,11 +26,11 @@ export interface IGlobalRegistryData { export class GlobalRegistry { private static instance: GlobalRegistry | null = null; - private kvStore: plugins.npmextra.KeyValueStore; + private kvStore: plugins.smartconfig.KeyValueStore; private docker: DockerContainer; private constructor() { - this.kvStore = new plugins.npmextra.KeyValueStore({ + this.kvStore = new plugins.smartconfig.KeyValueStore({ typeArg: 'userHomeDir', identityArg: 'gitzone-services', }); diff --git a/ts/mod_services/classes.servicemanager.ts b/ts/mod_services/classes.servicemanager.ts index 4f48f08..cf8792e 100644 --- a/ts/mod_services/classes.servicemanager.ts +++ b/ts/mod_services/classes.servicemanager.ts @@ -31,7 +31,7 @@ export class ServiceManager { await this.config.loadOrCreate(); logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`); - // Load service selection from npmextra.json + // Load service selection from .smartconfig.json await this.loadServiceConfiguration(); // Validate and update ports if needed @@ -39,11 +39,11 @@ export class ServiceManager { } /** - * Load service configuration from npmextra.json + * Load service configuration from .smartconfig.json */ private async loadServiceConfiguration(): Promise { - const npmextraConfig = new plugins.npmextra.Smartconfig(process.cwd()); - const gitzoneConfig = npmextraConfig.dataFor('@git.zone/cli', {}); + const smartconfigInstance = new plugins.smartconfig.Smartconfig(process.cwd()); + const gitzoneConfig = smartconfigInstance.dataFor('@git.zone/cli', {}); // Check if services array exists if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) { @@ -63,7 +63,7 @@ export class ServiceManager { this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch']; - // Save to npmextra.json + // Save to .smartconfig.json await this.saveServiceConfiguration(this.enabledServices); } else { this.enabledServices = gitzoneConfig.services; @@ -72,31 +72,31 @@ export class ServiceManager { } /** - * Save service configuration to npmextra.json + * Save service configuration to .smartconfig.json */ private async saveServiceConfiguration(services: string[]): Promise { - const npmextraPath = plugins.path.join(process.cwd(), 'smartconfig.json'); - let npmextraData: any = {}; + const smartconfigPath = plugins.path.join(process.cwd(), '.smartconfig.json'); + let smartconfigData: any = {}; - // Read existing npmextra.json if it exists - if (await plugins.smartfs.file(npmextraPath).exists()) { - const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read(); - npmextraData = JSON.parse(content as string); + // Read existing .smartconfig.json if it exists + if (await plugins.smartfs.file(smartconfigPath).exists()) { + const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read(); + smartconfigData = JSON.parse(content as string); } // Update @git.zone/cli.services - if (!npmextraData['@git.zone/cli']) { - npmextraData['@git.zone/cli'] = {}; + if (!smartconfigData['@git.zone/cli']) { + smartconfigData['@git.zone/cli'] = {}; } - npmextraData['@git.zone/cli'].services = services; + smartconfigData['@git.zone/cli'].services = services; - // Write back to npmextra.json + // Write back to .smartconfig.json await plugins.smartfs - .file(npmextraPath) + .file(smartconfigPath) .encoding('utf8') - .write(JSON.stringify(npmextraData, null, 2)); + .write(JSON.stringify(smartconfigData, null, 2)); - logger.log('ok', `✅ Saved service configuration to npmextra.json`); + logger.log('ok', `✅ Saved service configuration to .smartconfig.json`); logger.log('info', `🔧 Enabled services: ${services.join(', ')}`); } @@ -904,7 +904,7 @@ export class ServiceManager { this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch']; - // Save to npmextra.json + // Save to .smartconfig.json await this.saveServiceConfiguration(this.enabledServices); logger.log('ok', '✅ Service configuration updated'); diff --git a/ts/plugins.ts b/ts/plugins.ts index 851ae8b..a4611a9 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -1,6 +1,6 @@ import * as smartlog from '@push.rocks/smartlog'; import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local'; -import * as npmextra from '@push.rocks/smartconfig'; +import * as smartconfig from '@push.rocks/smartconfig'; import * as path from 'path'; import * as projectinfo from '@push.rocks/projectinfo'; import * as smartcli from '@push.rocks/smartcli'; @@ -20,7 +20,7 @@ export const smartfs = new SmartFs(new SmartFsProviderNode()); export { smartlog, smartlogDestinationLocal, - npmextra, + smartconfig, path, projectinfo, smartcli,