update to smartconfig
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
---
|
||||||
|
fileName: .smartconfig.json
|
||||||
|
---
|
||||||
{
|
{
|
||||||
"@git.zone/cli": {
|
"@git.zone/cli": {
|
||||||
"projectType": "{{projectType}}",
|
"projectType": "{{projectType}}",
|
||||||
12
package.json
12
package.json
@@ -67,16 +67,13 @@
|
|||||||
"@git.zone/tspublish": "^1.11.2",
|
"@git.zone/tspublish": "^1.11.2",
|
||||||
"@push.rocks/commitinfo": "^1.0.12",
|
"@push.rocks/commitinfo": "^1.0.12",
|
||||||
"@push.rocks/early": "^4.0.4",
|
"@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/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartcli": "^4.0.20",
|
"@push.rocks/smartcli": "^4.0.20",
|
||||||
|
"@push.rocks/smartconfig": "^6.0.1",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartdiff": "^1.1.0",
|
"@push.rocks/smartdiff": "^1.1.0",
|
||||||
"@push.rocks/smartfile": "^13.1.2",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartfs": "^1.5.0",
|
"@push.rocks/smartfs": "^1.5.0",
|
||||||
"@push.rocks/smartgulp": "^3.0.4",
|
|
||||||
"@push.rocks/smartinteract": "^2.0.16",
|
"@push.rocks/smartinteract": "^2.0.16",
|
||||||
"@push.rocks/smartjson": "^6.0.0",
|
"@push.rocks/smartjson": "^6.0.0",
|
||||||
"@push.rocks/smartlegal": "^1.0.27",
|
"@push.rocks/smartlegal": "^1.0.27",
|
||||||
@@ -91,12 +88,9 @@
|
|||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartscaf": "^4.0.19",
|
"@push.rocks/smartscaf": "^4.0.19",
|
||||||
"@push.rocks/smartshell": "^3.3.7",
|
"@push.rocks/smartshell": "^3.3.7",
|
||||||
"@push.rocks/smartstream": "^3.4.0",
|
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@push.rocks/smartupdate": "^2.0.6",
|
"@push.rocks/smartupdate": "^2.0.6",
|
||||||
"@types/through2": "^2.0.41",
|
"prettier": "^3.8.1"
|
||||||
"prettier": "^3.8.1",
|
|
||||||
"through2": "^4.0.2"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
@@ -107,7 +101,7 @@
|
|||||||
"dist_ts_web/**/*",
|
"dist_ts_web/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
".smartconfig.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -20,12 +20,6 @@ importers:
|
|||||||
'@push.rocks/early':
|
'@push.rocks/early':
|
||||||
specifier: ^4.0.4
|
specifier: ^4.0.4
|
||||||
version: 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':
|
'@push.rocks/projectinfo':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
@@ -33,8 +27,8 @@ importers:
|
|||||||
specifier: ^4.0.20
|
specifier: ^4.0.20
|
||||||
version: 4.0.20
|
version: 4.0.20
|
||||||
'@push.rocks/smartconfig':
|
'@push.rocks/smartconfig':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.1
|
||||||
version: 6.0.0
|
version: 6.0.1
|
||||||
'@push.rocks/smartdelay':
|
'@push.rocks/smartdelay':
|
||||||
specifier: ^3.0.5
|
specifier: ^3.0.5
|
||||||
version: 3.0.5
|
version: 3.0.5
|
||||||
@@ -47,9 +41,6 @@ importers:
|
|||||||
'@push.rocks/smartfs':
|
'@push.rocks/smartfs':
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0
|
version: 1.5.0
|
||||||
'@push.rocks/smartgulp':
|
|
||||||
specifier: ^3.0.4
|
|
||||||
version: 3.0.4
|
|
||||||
'@push.rocks/smartinteract':
|
'@push.rocks/smartinteract':
|
||||||
specifier: ^2.0.16
|
specifier: ^2.0.16
|
||||||
version: 2.0.16
|
version: 2.0.16
|
||||||
@@ -92,24 +83,15 @@ importers:
|
|||||||
'@push.rocks/smartshell':
|
'@push.rocks/smartshell':
|
||||||
specifier: ^3.3.7
|
specifier: ^3.3.7
|
||||||
version: 3.3.7
|
version: 3.3.7
|
||||||
'@push.rocks/smartstream':
|
|
||||||
specifier: ^3.4.0
|
|
||||||
version: 3.4.0
|
|
||||||
'@push.rocks/smartunique':
|
'@push.rocks/smartunique':
|
||||||
specifier: ^3.0.9
|
specifier: ^3.0.9
|
||||||
version: 3.0.9
|
version: 3.0.9
|
||||||
'@push.rocks/smartupdate':
|
'@push.rocks/smartupdate':
|
||||||
specifier: ^2.0.6
|
specifier: ^2.0.6
|
||||||
version: 2.0.6
|
version: 2.0.6
|
||||||
'@types/through2':
|
|
||||||
specifier: ^2.0.41
|
|
||||||
version: 2.0.41
|
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.8.1
|
specifier: ^3.8.1
|
||||||
version: 3.8.1
|
version: 3.8.1
|
||||||
through2:
|
|
||||||
specifier: ^4.0.2
|
|
||||||
version: 4.0.2
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^4.3.0
|
specifier: ^4.3.0
|
||||||
@@ -1018,9 +1000,6 @@ packages:
|
|||||||
'@push.rocks/early@4.0.4':
|
'@push.rocks/early@4.0.4':
|
||||||
resolution: {integrity: sha512-ak6/vqZ1PlFV08fSFQ6UwiBrr+K6IsfieZWWzT7eex1Ls6GvWEi8wZ3REFDPJq/qckNLWSgEy0EsqzRtltkaCA==}
|
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':
|
'@push.rocks/isounique@1.0.5':
|
||||||
resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==}
|
resolution: {integrity: sha512-Z0BVqZZOCif1THTbIKWMgg0wxCzt9CyBtBBqQJiZ+jJ0KlQFrQHNHrPt81/LXe/L4x0cxWsn0bpL6W5DNSvNLw==}
|
||||||
|
|
||||||
@@ -1072,8 +1051,8 @@ packages:
|
|||||||
'@push.rocks/smartclickhouse@2.2.0':
|
'@push.rocks/smartclickhouse@2.2.0':
|
||||||
resolution: {integrity: sha512-eTzKiREIPSzL1kPkVyD6vEbn+WV/DvQqDjP67VlhNlQGbRcemnJG/eLrUUR1ytmdIqnsZGEK6UYBgyj5nhzLNQ==}
|
resolution: {integrity: sha512-eTzKiREIPSzL1kPkVyD6vEbn+WV/DvQqDjP67VlhNlQGbRcemnJG/eLrUUR1ytmdIqnsZGEK6UYBgyj5nhzLNQ==}
|
||||||
|
|
||||||
'@push.rocks/smartconfig@6.0.0':
|
'@push.rocks/smartconfig@6.0.1':
|
||||||
resolution: {integrity: sha512-ohXwJdbDXV2budErnZKWBOz01YkjP6gJsZ7QM9+6Wsh+r7O1CVT3JpV+mD8xJWy5tZRHI+3B9L8z0+WkIDtKzw==}
|
resolution: {integrity: sha512-nuUbiOTy7WbDliZV2mG1VRaeJYygtBlLNVs3LmLoPmBzbZwwUMq+HFVS19BhXxmQnGZ5+JXW05dZXKfMqEDZnw==}
|
||||||
|
|
||||||
'@push.rocks/smartcrypto@2.0.4':
|
'@push.rocks/smartcrypto@2.0.4':
|
||||||
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
|
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
|
||||||
@@ -1099,9 +1078,6 @@ packages:
|
|||||||
'@push.rocks/smarterror@2.0.1':
|
'@push.rocks/smarterror@2.0.1':
|
||||||
resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==}
|
resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==}
|
||||||
|
|
||||||
'@push.rocks/smartevent@2.0.5':
|
|
||||||
resolution: {integrity: sha512-aU1hEoiMv8qDs+b3ln6e6GseyqM8sSqkGxhNTteLM6ve5dmTofnAdQ/tXshYNUUg2kPqi4ohcuf1/iACwjXNHw==}
|
|
||||||
|
|
||||||
'@push.rocks/smartexit@1.0.23':
|
'@push.rocks/smartexit@1.0.23':
|
||||||
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
||||||
|
|
||||||
@@ -1135,9 +1111,6 @@ packages:
|
|||||||
'@push.rocks/smartguard@3.1.0':
|
'@push.rocks/smartguard@3.1.0':
|
||||||
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
|
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':
|
'@push.rocks/smarthash@3.2.6':
|
||||||
resolution: {integrity: sha512-Mq/WNX0Tjjes3X1gHd/ZBwOOKSrAG/Z3Xoc0OcCm3P20WKpniihkMpsnlE7wGjvpHLi/ZRe/XkB3KC3d5r9X4g==}
|
resolution: {integrity: sha512-Mq/WNX0Tjjes3X1gHd/ZBwOOKSrAG/Z3Xoc0OcCm3P20WKpniihkMpsnlE7wGjvpHLi/ZRe/XkB3KC3d5r9X4g==}
|
||||||
|
|
||||||
@@ -3764,9 +3737,6 @@ packages:
|
|||||||
threads@1.7.0:
|
threads@1.7.0:
|
||||||
resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
|
resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
|
||||||
|
|
||||||
through2@3.0.2:
|
|
||||||
resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==}
|
|
||||||
|
|
||||||
through2@4.0.2:
|
through2@4.0.2:
|
||||||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||||
|
|
||||||
@@ -5517,12 +5487,6 @@ snapshots:
|
|||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
'@push.rocks/smartpromise': 4.2.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/isounique@1.0.5': {}
|
||||||
|
|
||||||
'@push.rocks/levelcache@3.2.0':
|
'@push.rocks/levelcache@3.2.0':
|
||||||
@@ -5755,7 +5719,7 @@ snapshots:
|
|||||||
'@push.rocks/smarturl': 3.1.0
|
'@push.rocks/smarturl': 3.1.0
|
||||||
'@push.rocks/webrequest': 4.0.5
|
'@push.rocks/webrequest': 4.0.5
|
||||||
|
|
||||||
'@push.rocks/smartconfig@6.0.0':
|
'@push.rocks/smartconfig@6.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
@@ -5844,11 +5808,6 @@ snapshots:
|
|||||||
clean-stack: 1.3.0
|
clean-stack: 1.3.0
|
||||||
make-error-cause: 2.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':
|
'@push.rocks/smartexit@1.0.23':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.3.1
|
'@push.rocks/lik': 6.3.1
|
||||||
@@ -5951,15 +5910,6 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 2.1.0
|
'@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':
|
'@push.rocks/smarthash@3.2.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 5.0.13
|
||||||
@@ -9379,11 +9329,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
through2@3.0.2:
|
|
||||||
dependencies:
|
|
||||||
inherits: 2.0.4
|
|
||||||
readable-stream: 3.6.2
|
|
||||||
|
|
||||||
through2@4.0.2:
|
through2@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 3.6.2
|
readable-stream: 3.6.2
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ export class GitzoneConfig {
|
|||||||
public data: IGitzoneConfigData;
|
public data: IGitzoneConfigData;
|
||||||
|
|
||||||
public async readConfigFromCwd() {
|
public async readConfigFromCwd() {
|
||||||
const npmextraInstance = new plugins.npmextra.Smartconfig(paths.cwd);
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||||
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
|
this.data = smartconfigInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
|
||||||
|
|
||||||
// Read szci config for backward compatibility
|
// Read szci config for backward compatibility
|
||||||
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
const szciConfig = smartconfigInstance.dataFor<any>('@ship.zone/szci', {});
|
||||||
|
|
||||||
// Prefer accessLevel from @git.zone/cli.release, fallback to @ship.zone/szci.npmAccessLevel
|
// Prefer accessLevel from @git.zone/cli.release, fallback to @ship.zone/szci.npmAccessLevel
|
||||||
const accessLevel =
|
const accessLevel =
|
||||||
|
|||||||
@@ -63,22 +63,6 @@ export let run = async () => {
|
|||||||
const config = GitzoneConfig.fromCwd();
|
const config = GitzoneConfig.fromCwd();
|
||||||
const modFormat = await import('./mod_format/index.js');
|
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
|
// Handle format with options
|
||||||
// Default is dry-mode, use --write/-w to apply changes
|
// Default is dry-mode, use --write/-w to apply changes
|
||||||
await modFormat.run({
|
await modFormat.run({
|
||||||
@@ -90,7 +74,6 @@ export let run = async () => {
|
|||||||
fromPlan: argvArg['from-plan'],
|
fromPlan: argvArg['from-plan'],
|
||||||
detailed: argvArg.detailed,
|
detailed: argvArg.detailed,
|
||||||
interactive: argvArg.interactive !== false,
|
interactive: argvArg.interactive !== false,
|
||||||
parallel: argvArg.parallel !== false,
|
|
||||||
verbose: argvArg.verbose,
|
verbose: argvArg.verbose,
|
||||||
diff: argvArg.diff,
|
diff: argvArg.diff,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import * as ui from './mod.ui.js';
|
|||||||
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
export const run = async (argvArg: any) => {
|
||||||
// Read commit config from npmextra.json
|
// Read commit config from .smartconfig.json
|
||||||
const npmextraConfig = new plugins.npmextra.Smartconfig();
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||||
const gitzoneConfig = npmextraConfig.dataFor<{
|
const gitzoneConfig = smartconfigInstance.dataFor<{
|
||||||
commit?: {
|
commit?: {
|
||||||
alwaysTest?: boolean;
|
alwaysTest?: boolean;
|
||||||
alwaysBuild?: boolean;
|
alwaysBuild?: boolean;
|
||||||
|
|||||||
@@ -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
|
* under @git.zone/cli.commit namespace
|
||||||
*/
|
*/
|
||||||
export class CommitConfig {
|
export class CommitConfig {
|
||||||
@@ -28,11 +28,11 @@ export class CommitConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration from npmextra.json
|
* Load configuration from .smartconfig.json
|
||||||
*/
|
*/
|
||||||
public async load(): Promise<void> {
|
public async load(): Promise<void> {
|
||||||
const npmextraInstance = new plugins.npmextra.Smartconfig(this.cwd);
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||||
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
alwaysTest: gitzoneConfig?.commit?.alwaysTest ?? false,
|
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<void> {
|
public async save(): Promise<void> {
|
||||||
const npmextraPath = plugins.path.join(this.cwd, 'smartconfig.json');
|
const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json');
|
||||||
let npmextraData: any = {};
|
let smartconfigData: any = {};
|
||||||
|
|
||||||
// Read existing npmextra.json
|
// Read existing .smartconfig.json
|
||||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||||
npmextraData = JSON.parse(content as string);
|
smartconfigData = JSON.parse(content as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure @git.zone/cli namespace exists
|
// Ensure @git.zone/cli namespace exists
|
||||||
if (!npmextraData['@git.zone/cli']) {
|
if (!smartconfigData['@git.zone/cli']) {
|
||||||
npmextraData['@git.zone/cli'] = {};
|
smartconfigData['@git.zone/cli'] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure commit object exists
|
// Ensure commit object exists
|
||||||
if (!npmextraData['@git.zone/cli'].commit) {
|
if (!smartconfigData['@git.zone/cli'].commit) {
|
||||||
npmextraData['@git.zone/cli'].commit = {};
|
smartconfigData['@git.zone/cli'].commit = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update commit settings
|
// Update commit settings
|
||||||
npmextraData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
|
smartconfigData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
|
||||||
npmextraData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
|
smartconfigData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
|
||||||
|
|
||||||
// Write back to file
|
// Write back to file
|
||||||
await plugins.smartfs
|
await plugins.smartfs
|
||||||
.file(npmextraPath)
|
.file(smartconfigPath)
|
||||||
.encoding('utf8')
|
.encoding('utf8')
|
||||||
.write(JSON.stringify(npmextraData, null, 2));
|
.write(JSON.stringify(smartconfigData, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* under @git.zone/cli.release namespace
|
||||||
*/
|
*/
|
||||||
export class ReleaseConfig {
|
export class ReleaseConfig {
|
||||||
@@ -30,14 +30,14 @@ export class ReleaseConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration from npmextra.json
|
* Load configuration from .smartconfig.json
|
||||||
*/
|
*/
|
||||||
public async load(): Promise<void> {
|
public async load(): Promise<void> {
|
||||||
const npmextraInstance = new plugins.npmextra.Smartconfig(this.cwd);
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||||
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||||
|
|
||||||
// Also check szci for backward compatibility
|
// Also check szci for backward compatibility
|
||||||
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
const szciConfig = smartconfigInstance.dataFor<any>('@ship.zone/szci', {});
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
registries: gitzoneConfig?.release?.registries || [],
|
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<void> {
|
public async save(): Promise<void> {
|
||||||
const npmextraPath = plugins.path.join(this.cwd, 'smartconfig.json');
|
const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json');
|
||||||
let npmextraData: any = {};
|
let smartconfigData: any = {};
|
||||||
|
|
||||||
// Read existing npmextra.json
|
// Read existing .smartconfig.json
|
||||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||||
npmextraData = JSON.parse(content as string);
|
smartconfigData = JSON.parse(content as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure @git.zone/cli namespace exists
|
// Ensure @git.zone/cli namespace exists
|
||||||
if (!npmextraData['@git.zone/cli']) {
|
if (!smartconfigData['@git.zone/cli']) {
|
||||||
npmextraData['@git.zone/cli'] = {};
|
smartconfigData['@git.zone/cli'] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure release object exists
|
// Ensure release object exists
|
||||||
if (!npmextraData['@git.zone/cli'].release) {
|
if (!smartconfigData['@git.zone/cli'].release) {
|
||||||
npmextraData['@git.zone/cli'].release = {};
|
smartconfigData['@git.zone/cli'].release = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update registries and accessLevel
|
// Update registries and accessLevel
|
||||||
npmextraData['@git.zone/cli'].release.registries = this.config.registries;
|
smartconfigData['@git.zone/cli'].release.registries = this.config.registries;
|
||||||
npmextraData['@git.zone/cli'].release.accessLevel = this.config.accessLevel;
|
smartconfigData['@git.zone/cli'].release.accessLevel = this.config.accessLevel;
|
||||||
|
|
||||||
// Write back to file
|
// Write back to file
|
||||||
await plugins.smartfs
|
await plugins.smartfs
|
||||||
.file(npmextraPath)
|
.file(smartconfigPath)
|
||||||
.encoding('utf8')
|
.encoding('utf8')
|
||||||
.write(JSON.stringify(npmextraData, null, 2));
|
.write(JSON.stringify(smartconfigData, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ import { runFormatter, type ICheckResult } from '../mod_format/index.js';
|
|||||||
export { ReleaseConfig, CommitConfig };
|
export { ReleaseConfig, CommitConfig };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format npmextra.json with diff preview
|
* Format .smartconfig.json with diff preview
|
||||||
* Shows diff first, asks for confirmation, then applies
|
* Shows diff first, asks for confirmation, then applies
|
||||||
*/
|
*/
|
||||||
async function formatNpmextraWithDiff(): Promise<void> {
|
async function formatSmartconfigWithDiff(): Promise<void> {
|
||||||
// Check for diffs first
|
// Check for diffs first
|
||||||
const checkResult = await runFormatter('npmextra', {
|
const checkResult = await runFormatter('smartconfig', {
|
||||||
checkOnly: true,
|
checkOnly: true,
|
||||||
showDiff: true,
|
showDiff: true,
|
||||||
}) as ICheckResult | void;
|
}) as ICheckResult | void;
|
||||||
|
|
||||||
if (checkResult && checkResult.hasDiff) {
|
if (checkResult && checkResult.hasDiff) {
|
||||||
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||||
'Apply formatting changes to npmextra.json?',
|
'Apply formatting changes to .smartconfig.json?',
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (shouldApply) {
|
if (shouldApply) {
|
||||||
await runFormatter('npmextra', { silent: true });
|
await runFormatter('smartconfig', { silent: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ async function handleAdd(url?: string): Promise<void> {
|
|||||||
if (added) {
|
if (added) {
|
||||||
await config.save();
|
await config.save();
|
||||||
plugins.logger.log('success', `Added registry: ${url}`);
|
plugins.logger.log('success', `Added registry: ${url}`);
|
||||||
await formatNpmextraWithDiff();
|
await formatSmartconfigWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ async function handleRemove(url?: string): Promise<void> {
|
|||||||
if (removed) {
|
if (removed) {
|
||||||
await config.save();
|
await config.save();
|
||||||
plugins.logger.log('success', `Removed registry: ${url}`);
|
plugins.logger.log('success', `Removed registry: ${url}`);
|
||||||
await formatNpmextraWithDiff();
|
await formatSmartconfigWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('warn', `Registry not found: ${url}`);
|
plugins.logger.log('warn', `Registry not found: ${url}`);
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ async function handleClear(): Promise<void> {
|
|||||||
config.clearRegistries();
|
config.clearRegistries();
|
||||||
await config.save();
|
await config.save();
|
||||||
plugins.logger.log('success', 'All registries cleared.');
|
plugins.logger.log('success', 'All registries cleared.');
|
||||||
await formatNpmextraWithDiff();
|
await formatSmartconfigWithDiff();
|
||||||
} else {
|
} else {
|
||||||
plugins.logger.log('info', 'Operation cancelled.');
|
plugins.logger.log('info', 'Operation cancelled.');
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ async function handleAccessLevel(level?: string): Promise<void> {
|
|||||||
config.setAccessLevel(level as 'public' | 'private');
|
config.setAccessLevel(level as 'public' | 'private');
|
||||||
await config.save();
|
await config.save();
|
||||||
plugins.logger.log('success', `Access level set to: ${level}`);
|
plugins.logger.log('success', `Access level set to: ${level}`);
|
||||||
await formatNpmextraWithDiff();
|
await formatSmartconfigWithDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,7 +350,7 @@ async function handleCommitInteractive(config: CommitConfig): Promise<void> {
|
|||||||
await config.save();
|
await config.save();
|
||||||
|
|
||||||
plugins.logger.log('success', 'Commit configuration updated');
|
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();
|
await config.save();
|
||||||
plugins.logger.log('success', `Set ${setting} to ${boolValue}`);
|
plugins.logger.log('success', `Set ${setting} to ${boolValue}`);
|
||||||
await formatNpmextraWithDiff();
|
await formatSmartconfigWithDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import * as plugins from './mod.plugins.js';
|
|||||||
import { FormatContext } from './classes.formatcontext.js';
|
import { FormatContext } from './classes.formatcontext.js';
|
||||||
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
|
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
|
||||||
import { Project } from '../classes.project.js';
|
import { Project } from '../classes.project.js';
|
||||||
|
import { FormatStats } from './classes.formatstats.js';
|
||||||
|
|
||||||
export abstract class BaseFormatter {
|
export abstract class BaseFormatter {
|
||||||
protected context: FormatContext;
|
protected context: FormatContext;
|
||||||
protected project: Project;
|
protected project: Project;
|
||||||
protected stats: any; // Will be FormatStats from context
|
protected stats: FormatStats;
|
||||||
|
|
||||||
constructor(context: FormatContext, project: Project) {
|
constructor(context: FormatContext, project: Project) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -36,9 +37,6 @@ export abstract class BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.postExecute();
|
await this.postExecute();
|
||||||
} catch (error) {
|
|
||||||
// Don't rollback here - let the FormatPlanner handle it
|
|
||||||
throw error;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.stats.endModule(this.name, startTime);
|
this.stats.endModule(this.name, startTime);
|
||||||
}
|
}
|
||||||
@@ -53,13 +51,10 @@ export abstract class BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async modifyFile(filepath: string, content: string): Promise<void> {
|
protected async modifyFile(filepath: string, content: string): Promise<void> {
|
||||||
// Validate filepath before writing
|
|
||||||
if (!filepath || filepath.trim() === '') {
|
if (!filepath || filepath.trim() === '') {
|
||||||
throw new Error(`Invalid empty filepath in modifyFile`);
|
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;
|
let normalizedPath = filepath;
|
||||||
if (!plugins.path.parse(filepath).dir) {
|
if (!plugins.path.parse(filepath).dir) {
|
||||||
normalizedPath = './' + filepath;
|
normalizedPath = './' + filepath;
|
||||||
@@ -69,44 +64,46 @@ export abstract class BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async createFile(filepath: string, content: string): Promise<void> {
|
protected async createFile(filepath: string, content: string): Promise<void> {
|
||||||
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<void> {
|
protected async deleteFile(filepath: string): Promise<void> {
|
||||||
await plugins.smartfs.file(filepath).delete();
|
await plugins.smartfs.file(filepath).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for diffs without applying changes
|
* Check for diffs without applying changes
|
||||||
* Returns information about what would change
|
|
||||||
*/
|
*/
|
||||||
async check(): Promise<ICheckResult> {
|
async check(): Promise<ICheckResult> {
|
||||||
const changes = await this.analyze();
|
const changes = await this.analyze();
|
||||||
const diffs: ICheckResult['diffs'] = [];
|
const diffs: ICheckResult['diffs'] = [];
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
// Skip generic changes that don't have actual content
|
|
||||||
if (change.path === '<various files>') {
|
if (change.path === '<various files>') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.type === 'modify' || change.type === 'create') {
|
if (change.type === 'modify' || change.type === 'create') {
|
||||||
// Read current content if file exists
|
|
||||||
let currentContent: string | undefined;
|
let currentContent: string | undefined;
|
||||||
try {
|
try {
|
||||||
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||||
} catch {
|
} catch {
|
||||||
// File doesn't exist yet
|
|
||||||
currentContent = undefined;
|
currentContent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newContent = change.content;
|
const newContent = change.content;
|
||||||
|
|
||||||
// Check if there's an actual diff
|
|
||||||
if (currentContent !== newContent && newContent !== undefined) {
|
if (currentContent !== newContent && newContent !== undefined) {
|
||||||
diffs.push({
|
diffs.push({
|
||||||
path: change.path,
|
path: change.path,
|
||||||
@@ -116,7 +113,6 @@ export abstract class BaseFormatter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (change.type === 'delete') {
|
} else if (change.type === 'delete') {
|
||||||
// Check if file exists before marking for deletion
|
|
||||||
try {
|
try {
|
||||||
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||||
diffs.push({
|
diffs.push({
|
||||||
@@ -137,9 +133,6 @@ export abstract class BaseFormatter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a single diff using smartdiff
|
|
||||||
*/
|
|
||||||
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
||||||
console.log(`\n--- ${diff.path}`);
|
console.log(`\n--- ${diff.path}`);
|
||||||
if (diff.before && diff.after) {
|
if (diff.before && diff.after) {
|
||||||
@@ -150,7 +143,6 @@ export abstract class BaseFormatter {
|
|||||||
}));
|
}));
|
||||||
} else if (diff.after && !diff.before) {
|
} else if (diff.after && !diff.before) {
|
||||||
console.log(' (new file)');
|
console.log(' (new file)');
|
||||||
// Show first few lines of new content
|
|
||||||
const lines = diff.after.split('\n').slice(0, 10);
|
const lines = diff.after.split('\n').slice(0, 10);
|
||||||
lines.forEach(line => console.log(` + ${line}`));
|
lines.forEach(line => console.log(` + ${line}`));
|
||||||
if (diff.after.split('\n').length > 10) {
|
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 {
|
displayAllDiffs(result: ICheckResult): void {
|
||||||
if (!result.hasDiff) {
|
if (!result.hasDiff) {
|
||||||
console.log(' No changes detected');
|
console.log(' No changes detected');
|
||||||
|
|||||||
@@ -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<void> {
|
|
||||||
await plugins.smartfs.directory(this.cacheDir).recursive().create();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getManifest(): Promise<ICacheManifest> {
|
|
||||||
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<void> {
|
|
||||||
// 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<boolean> {
|
|
||||||
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<void> {
|
|
||||||
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<string[]> {
|
|
||||||
const changedFiles: string[] = [];
|
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
if (await this.hasFileChanged(filePath)) {
|
|
||||||
changedFiles.push(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
async clean(): Promise<void> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
|
||||||
import { BaseFormatter } from './classes.baseformatter.js';
|
|
||||||
|
|
||||||
export interface IModuleDependency {
|
|
||||||
module: string;
|
|
||||||
dependencies: Set<string>;
|
|
||||||
dependents: Set<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DependencyAnalyzer {
|
|
||||||
private moduleDependencies: Map<string, IModuleDependency> = 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<string>();
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,12 @@ import * as plugins from './mod.plugins.js';
|
|||||||
import { FormatContext } from './classes.formatcontext.js';
|
import { FormatContext } from './classes.formatcontext.js';
|
||||||
import { BaseFormatter } from './classes.baseformatter.js';
|
import { BaseFormatter } from './classes.baseformatter.js';
|
||||||
import type { IFormatPlan, IPlannedChange } from './interfaces.format.js';
|
import type { IFormatPlan, IPlannedChange } from './interfaces.format.js';
|
||||||
|
import { getModuleIcon } from './interfaces.format.js';
|
||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
import { DependencyAnalyzer } from './classes.dependency-analyzer.js';
|
|
||||||
import { DiffReporter } from './classes.diffreporter.js';
|
import { DiffReporter } from './classes.diffreporter.js';
|
||||||
|
|
||||||
export class FormatPlanner {
|
export class FormatPlanner {
|
||||||
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
||||||
private dependencyAnalyzer = new DependencyAnalyzer();
|
|
||||||
private diffReporter = new DiffReporter();
|
private diffReporter = new DiffReporter();
|
||||||
|
|
||||||
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
||||||
@@ -18,7 +17,6 @@ export class FormatPlanner {
|
|||||||
filesAdded: 0,
|
filesAdded: 0,
|
||||||
filesModified: 0,
|
filesModified: 0,
|
||||||
filesRemoved: 0,
|
filesRemoved: 0,
|
||||||
estimatedTime: 0,
|
|
||||||
},
|
},
|
||||||
changes: [],
|
changes: [],
|
||||||
warnings: [],
|
warnings: [],
|
||||||
@@ -32,7 +30,6 @@ export class FormatPlanner {
|
|||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
plan.changes.push(change);
|
plan.changes.push(change);
|
||||||
|
|
||||||
// Update summary
|
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
plan.summary.filesAdded++;
|
plan.summary.filesAdded++;
|
||||||
@@ -58,7 +55,6 @@ export class FormatPlanner {
|
|||||||
plan.summary.filesAdded +
|
plan.summary.filesAdded +
|
||||||
plan.summary.filesModified +
|
plan.summary.filesModified +
|
||||||
plan.summary.filesRemoved;
|
plan.summary.filesRemoved;
|
||||||
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
|
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
@@ -67,27 +63,20 @@ export class FormatPlanner {
|
|||||||
plan: IFormatPlan,
|
plan: IFormatPlan,
|
||||||
modules: BaseFormatter[],
|
modules: BaseFormatter[],
|
||||||
context: FormatContext,
|
context: FormatContext,
|
||||||
parallel: boolean = false,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
for (const module of modules) {
|
||||||
// Always use sequential execution to avoid race conditions
|
const changes = this.plannedChanges.get(module.name) || [];
|
||||||
for (const module of modules) {
|
|
||||||
const changes = this.plannedChanges.get(module.name) || [];
|
|
||||||
|
|
||||||
if (changes.length > 0) {
|
if (changes.length > 0) {
|
||||||
logger.log('info', `Executing ${module.name} formatter...`);
|
logger.log('info', `Executing ${module.name} formatter...`);
|
||||||
await module.execute(changes);
|
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(
|
async displayPlan(
|
||||||
@@ -103,7 +92,6 @@ export class FormatPlanner {
|
|||||||
console.log('');
|
console.log('');
|
||||||
console.log('Changes by module:');
|
console.log('Changes by module:');
|
||||||
|
|
||||||
// Group changes by module
|
|
||||||
const changesByModule = new Map<string, IPlannedChange[]>();
|
const changesByModule = new Map<string, IPlannedChange[]>();
|
||||||
for (const change of plan.changes) {
|
for (const change of plan.changes) {
|
||||||
const moduleChanges = changesByModule.get(change.module) || [];
|
const moduleChanges = changesByModule.get(change.module) || [];
|
||||||
@@ -113,14 +101,13 @@ export class FormatPlanner {
|
|||||||
|
|
||||||
for (const [module, changes] of changesByModule) {
|
for (const [module, changes] of changesByModule) {
|
||||||
console.log(
|
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) {
|
for (const change of changes) {
|
||||||
const icon = this.getChangeIcon(change.type);
|
const icon = this.getChangeIcon(change.type);
|
||||||
console.log(` ${icon} ${change.path} - ${change.description}`);
|
console.log(` ${icon} ${change.path} - ${change.description}`);
|
||||||
|
|
||||||
// Show diff for modified files if detailed view is requested
|
|
||||||
if (detailed && change.type === 'modify') {
|
if (detailed && change.type === 'modify') {
|
||||||
const diff = await this.diffReporter.generateDiffForChange(change);
|
const diff = await this.diffReporter.generateDiffForChange(change);
|
||||||
if (diff) {
|
if (diff) {
|
||||||
@@ -141,22 +128,6 @@ export class FormatPlanner {
|
|||||||
console.log('\n' + '━'.repeat(50));
|
console.log('\n' + '━'.repeat(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getModuleIcon(module: string): string {
|
|
||||||
const icons: Record<string, string> = {
|
|
||||||
packagejson: '📦',
|
|
||||||
license: '📝',
|
|
||||||
tsconfig: '🔧',
|
|
||||||
cleanup: '🚮',
|
|
||||||
gitignore: '🔒',
|
|
||||||
prettier: '✨',
|
|
||||||
readme: '📖',
|
|
||||||
templates: '📄',
|
|
||||||
npmextra: '⚙️',
|
|
||||||
copy: '📋',
|
|
||||||
};
|
|
||||||
return icons[module] || '📁';
|
|
||||||
}
|
|
||||||
|
|
||||||
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
import { getModuleIcon } from './interfaces.format.js';
|
||||||
|
|
||||||
export interface IModuleStats {
|
export interface IModuleStats {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -23,8 +24,6 @@ export interface IFormatStats {
|
|||||||
totalModified: number;
|
totalModified: number;
|
||||||
totalDeleted: number;
|
totalDeleted: number;
|
||||||
totalErrors: number;
|
totalErrors: number;
|
||||||
cacheHits: number;
|
|
||||||
cacheMisses: number;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +42,6 @@ export class FormatStats {
|
|||||||
totalModified: 0,
|
totalModified: 0,
|
||||||
totalDeleted: 0,
|
totalDeleted: 0,
|
||||||
totalErrors: 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 {
|
finish(): void {
|
||||||
this.stats.endTime = Date.now();
|
this.stats.endTime = Date.now();
|
||||||
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
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(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
||||||
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
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
|
// Module stats
|
||||||
console.log('\nModule Breakdown:');
|
console.log('\nModule Breakdown:');
|
||||||
console.log('─'.repeat(50));
|
console.log('─'.repeat(50));
|
||||||
@@ -159,7 +134,7 @@ export class FormatStats {
|
|||||||
|
|
||||||
for (const moduleStats of sortedModules) {
|
for (const moduleStats of sortedModules) {
|
||||||
console.log(
|
console.log(
|
||||||
`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
`\n${getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
|
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
|
||||||
@@ -211,19 +186,4 @@ export class FormatStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getModuleIcon(module: string): string {
|
|
||||||
const icons: Record<string, string> = {
|
|
||||||
packagejson: '📦',
|
|
||||||
license: '📝',
|
|
||||||
tsconfig: '🔧',
|
|
||||||
cleanup: '🚮',
|
|
||||||
gitignore: '🔒',
|
|
||||||
prettier: '✨',
|
|
||||||
readme: '📖',
|
|
||||||
templates: '📄',
|
|
||||||
npmextra: '⚙️',
|
|
||||||
copy: '📋',
|
|
||||||
};
|
|
||||||
return icons[module] || '📁';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<IFormatOperation> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<boolean> {
|
|
||||||
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<IFormatOperation[]> {
|
|
||||||
const manifest = await this.getManifest();
|
|
||||||
return manifest.operations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureBackupDir(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
// 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<IFormatOperation | null> {
|
|
||||||
const manifest = await this.getManifest();
|
|
||||||
return manifest.operations.find((op) => op.id === operationId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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<any>('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/"
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
@@ -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!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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!`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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();
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||||
import type { IPlannedChange } from '../interfaces.format.js';
|
import type { IPlannedChange } from '../interfaces.format.js';
|
||||||
import * as plugins from '../mod.plugins.js';
|
import * as plugins from '../mod.plugins.js';
|
||||||
import * as cleanupFormatter from '../format.cleanup.js';
|
|
||||||
|
|
||||||
export class CleanupFormatter extends BaseFormatter {
|
export class CleanupFormatter extends BaseFormatter {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ export class CopyFormatter extends BaseFormatter {
|
|||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
|
|
||||||
// Get copy configuration from npmextra.json
|
// Get copy configuration from .smartconfig.json
|
||||||
const npmextraConfig = new plugins.npmextra.Smartconfig();
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||||
const copyConfig = npmextraConfig.dataFor<{ patterns: ICopyPattern[] }>(
|
const copyConfig = smartconfigInstance.dataFor<{ patterns: ICopyPattern[] }>(
|
||||||
'gitzone.format.copy',
|
'gitzone.format.copy',
|
||||||
{ patterns: [] },
|
{ patterns: [] },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
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;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +103,6 @@ export class CopyFormatter extends BaseFormatter {
|
|||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
if (!change.content) return;
|
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') {
|
if (change.type === 'create') {
|
||||||
await this.createFile(change.path, change.content);
|
await this.createFile(change.path, change.content);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,42 +1,39 @@
|
|||||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||||
import type { IPlannedChange } from '../interfaces.format.js';
|
import type { IPlannedChange } from '../interfaces.format.js';
|
||||||
import * as plugins from '../mod.plugins.js';
|
import * as plugins from '../mod.plugins.js';
|
||||||
|
import * as paths from '../../paths.js';
|
||||||
import { logger } from '../../gitzone.logging.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 {
|
export class GitignoreFormatter extends BaseFormatter {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return 'gitignore';
|
return 'gitignore';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the standard gitignore template from the asset file,
|
||||||
|
* stripping the YAML frontmatter.
|
||||||
|
*/
|
||||||
|
private async getStandardTemplate(): Promise<string> {
|
||||||
|
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<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
const gitignorePath = '.gitignore';
|
const gitignorePath = '.gitignore';
|
||||||
|
|
||||||
|
const standardTemplate = await this.getStandardTemplate();
|
||||||
|
|
||||||
// Check if file exists and extract custom content
|
// Check if file exists and extract custom content
|
||||||
let customContent = '';
|
let customContent = '';
|
||||||
const exists = await plugins.smartfs.file(gitignorePath).exists();
|
const exists = await plugins.smartfs.file(gitignorePath).exists();
|
||||||
@@ -59,11 +56,11 @@ export class GitignoreFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute new content
|
// Compute new content
|
||||||
let newContent = GITIGNORE_TEMPLATE;
|
let newContent = standardTemplate;
|
||||||
if (customContent) {
|
if (customContent) {
|
||||||
newContent = GITIGNORE_TEMPLATE + '\n' + customContent + '\n';
|
newContent = standardTemplate + '\n' + customContent + '\n';
|
||||||
} else {
|
} else {
|
||||||
newContent = GITIGNORE_TEMPLATE + '\n';
|
newContent = standardTemplate + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read current content to compare
|
// Read current content to compare
|
||||||
@@ -75,7 +72,6 @@ export class GitignoreFormatter extends BaseFormatter {
|
|||||||
.read()) as string;
|
.read()) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine change type
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
changes.push({
|
changes.push({
|
||||||
type: 'create',
|
type: 'create',
|
||||||
|
|||||||
@@ -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<IPlannedChange[]> {
|
|
||||||
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<void> {
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -100,9 +100,9 @@ export class PackageJsonFormatter extends BaseFormatter {
|
|||||||
// Parse and compute new content
|
// Parse and compute new content
|
||||||
const packageJson = JSON.parse(currentContent);
|
const packageJson = JSON.parse(currentContent);
|
||||||
|
|
||||||
// Get gitzone config from npmextra
|
// Get gitzone config from smartconfig
|
||||||
const npmextraConfig = new plugins.npmextra.Smartconfig(paths.cwd);
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||||
const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {});
|
const gitzoneData: any = smartconfigInstance.dataFor('@git.zone/cli', {});
|
||||||
|
|
||||||
// Set metadata from gitzone config
|
// Set metadata from gitzone config
|
||||||
if (gitzoneData.module) {
|
if (gitzoneData.module) {
|
||||||
@@ -156,7 +156,7 @@ export class PackageJsonFormatter extends BaseFormatter {
|
|||||||
'dist_ts_web/**/*',
|
'dist_ts_web/**/*',
|
||||||
'assets/**/*',
|
'assets/**/*',
|
||||||
'cli.js',
|
'cli.js',
|
||||||
'smartconfig.json',
|
'.smartconfig.json',
|
||||||
'readme.md',
|
'readme.md',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
const rootConfigFiles = [
|
const rootConfigFiles = [
|
||||||
'package.json',
|
'package.json',
|
||||||
'tsconfig.json',
|
'tsconfig.json',
|
||||||
'smartconfig.json',
|
'.smartconfig.json',
|
||||||
'.prettierrc',
|
'.prettierrc',
|
||||||
'.prettierrc.json',
|
'.prettierrc.json',
|
||||||
'.prettierrc.js',
|
'.prettierrc.js',
|
||||||
@@ -79,12 +79,9 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
const uniqueFiles = [...new Set(allFiles)];
|
const uniqueFiles = [...new Set(allFiles)];
|
||||||
|
|
||||||
// Get all files that match the pattern
|
|
||||||
const files = uniqueFiles;
|
|
||||||
|
|
||||||
// Ensure we only process actual files (not directories)
|
// Ensure we only process actual files (not directories)
|
||||||
const validFiles: string[] = [];
|
const validFiles: string[] = [];
|
||||||
for (const file of files) {
|
for (const file of uniqueFiles) {
|
||||||
try {
|
try {
|
||||||
const stats = await plugins.smartfs.file(file).stat();
|
const stats = await plugins.smartfs.file(file).stat();
|
||||||
if (!stats.isDirectory) {
|
if (!stats.isDirectory) {
|
||||||
@@ -96,14 +93,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check which files need formatting
|
|
||||||
for (const file of validFiles) {
|
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({
|
changes.push({
|
||||||
type: 'modify',
|
type: 'modify',
|
||||||
path: file,
|
path: file,
|
||||||
@@ -232,7 +222,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
|
|
||||||
private async getPrettierConfig(): Promise<any> {
|
private async getPrettierConfig(): Promise<any> {
|
||||||
// Try to load prettier config from the project
|
// Try to load prettier config from the project
|
||||||
const prettierConfig = new plugins.npmextra.Smartconfig();
|
const prettierConfig = new plugins.smartconfig.Smartconfig();
|
||||||
return prettierConfig.dataFor('prettier', {
|
return prettierConfig.dataFor('prettier', {
|
||||||
// Default prettier config
|
// Default prettier config
|
||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
|
|||||||
213
ts/mod_format/formatters/smartconfig.formatter.ts
Normal file
213
ts/mod_format/formatters/smartconfig.formatter.ts
Normal file
@@ -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<IPlannedChange[]> {
|
||||||
|
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<void> {
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,9 +62,6 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||||
]);
|
]);
|
||||||
changes.push(...websiteChanges);
|
changes.push(...websiteChanges);
|
||||||
} else if (projectType === 'service') {
|
|
||||||
const serviceChanges = await this.analyzeTemplate('service_update', []);
|
|
||||||
changes.push(...serviceChanges);
|
|
||||||
} else if (projectType === 'wcc') {
|
} else if (projectType === 'wcc') {
|
||||||
const wccChanges = await this.analyzeTemplate('wcc_update', [
|
const wccChanges = await this.analyzeTemplate('wcc_update', [
|
||||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||||
@@ -139,12 +136,6 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
if (!change.content) return;
|
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') {
|
if (change.type === 'create') {
|
||||||
await this.createFile(change.path, change.content);
|
await this.createFile(change.path, change.content);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ export class TsconfigFormatter extends BaseFormatter {
|
|||||||
const tsconfigObject = JSON.parse(currentContent);
|
const tsconfigObject = JSON.parse(currentContent);
|
||||||
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
||||||
tsconfigObject.compilerOptions.baseUrl = '.';
|
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<string, string[]> = {};
|
||||||
try {
|
try {
|
||||||
const tsPublishMod = await import('@git.zone/tspublish');
|
const tsPublishMod = await import('@git.zone/tspublish');
|
||||||
const tsPublishInstance = new tsPublishMod.TsPublish();
|
const tsPublishInstance = new tsPublishMod.TsPublish();
|
||||||
@@ -40,7 +41,7 @@ export class TsconfigFormatter extends BaseFormatter {
|
|||||||
|
|
||||||
for (const publishModule of Object.keys(publishModules)) {
|
for (const publishModule of Object.keys(publishModules)) {
|
||||||
const publishConfig = publishModules[publishModule];
|
const publishConfig = publishModules[publishModule];
|
||||||
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
|
tspublishPaths[`${publishConfig.name}`] = [
|
||||||
`./${publishModule}/index.js`,
|
`./${publishModule}/index.js`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -48,6 +49,8 @@ export class TsconfigFormatter extends BaseFormatter {
|
|||||||
logVerbose(`Could not get tspublish modules: ${error.message}`);
|
logVerbose(`Could not get tspublish modules: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths };
|
||||||
|
|
||||||
const newContent = JSON.stringify(tsconfigObject, null, 2);
|
const newContent = JSON.stringify(tsconfigObject, null, 2);
|
||||||
|
|
||||||
// Only add change if content differs
|
// Only add change if content differs
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { FormatPlanner } from './classes.formatplanner.js';
|
|||||||
import { BaseFormatter } from './classes.baseformatter.js';
|
import { BaseFormatter } from './classes.baseformatter.js';
|
||||||
import { logger, setVerboseMode } from '../gitzone.logging.js';
|
import { logger, setVerboseMode } from '../gitzone.logging.js';
|
||||||
|
|
||||||
// Import wrapper classes for formatters
|
|
||||||
import { CleanupFormatter } from './formatters/cleanup.formatter.js';
|
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 { LicenseFormatter } from './formatters/license.formatter.js';
|
||||||
import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
|
import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
|
||||||
import { TemplatesFormatter } from './formatters/templates.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 { ReadmeFormatter } from './formatters/readme.formatter.js';
|
||||||
import { CopyFormatter } from './formatters/copy.formatter.js';
|
import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||||
|
|
||||||
|
// Shared formatter class map used by both run() and runFormatter()
|
||||||
|
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => 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 (
|
export let run = async (
|
||||||
options: {
|
options: {
|
||||||
write?: boolean; // Explicitly write changes (default: false, dry-mode)
|
write?: boolean;
|
||||||
dryRun?: boolean; // Deprecated, kept for compatibility
|
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
planOnly?: boolean;
|
planOnly?: boolean;
|
||||||
savePlan?: string;
|
savePlan?: string;
|
||||||
fromPlan?: string;
|
fromPlan?: string;
|
||||||
detailed?: boolean;
|
detailed?: boolean;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
parallel?: boolean;
|
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
diff?: boolean; // Show file diffs
|
diff?: boolean;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
// Set verbose mode if requested
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
setVerboseMode(true);
|
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 shouldWrite = options.write ?? (options.dryRun === false);
|
||||||
|
|
||||||
const project = await Project.fromCwd({ requireProjectType: false });
|
const project = await Project.fromCwd({ requireProjectType: false });
|
||||||
const context = new FormatContext();
|
const context = new FormatContext();
|
||||||
// Cache system removed - no longer needed
|
|
||||||
const planner = new FormatPlanner();
|
const planner = new FormatPlanner();
|
||||||
|
|
||||||
// Get configuration from npmextra
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||||
const npmextraConfig = new plugins.npmextra.Smartconfig();
|
const formatConfig = smartconfigInstance.dataFor<any>('@git.zone/cli.format', {
|
||||||
const formatConfig = npmextraConfig.dataFor<any>('@git.zone/cli.format', {
|
|
||||||
interactive: true,
|
interactive: true,
|
||||||
showDiffs: false,
|
showDiffs: false,
|
||||||
autoApprove: false,
|
autoApprove: false,
|
||||||
planTimeout: 30000,
|
|
||||||
rollback: {
|
|
||||||
enabled: true,
|
|
||||||
autoRollbackOnError: true,
|
|
||||||
backupRetentionDays: 7,
|
|
||||||
maxBackupSize: '100MB',
|
|
||||||
excludePatterns: ['node_modules/**', '.git/**'],
|
|
||||||
},
|
|
||||||
modules: {
|
modules: {
|
||||||
skip: [],
|
skip: [],
|
||||||
only: [],
|
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 interactive = options.interactive ?? formatConfig.interactive;
|
||||||
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
||||||
const parallel = options.parallel ?? formatConfig.parallel;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize formatters
|
// Initialize formatters in execution order
|
||||||
const formatters = [
|
const formatters = Object.entries(formatterMap).map(
|
||||||
new CleanupFormatter(context, project),
|
([, FormatterClass]) => new FormatterClass(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),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filter formatters based on configuration
|
// Filter formatters based on configuration
|
||||||
const activeFormatters = formatters.filter((formatter) => {
|
const activeFormatters = formatters.filter((formatter) => {
|
||||||
@@ -128,13 +111,13 @@ export let run = async (
|
|||||||
logger.log('info', `Plan saved to ${options.savePlan}`);
|
logger.log('info', `Plan saved to ${options.savePlan}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if plan-only mode
|
|
||||||
if (options.planOnly) {
|
if (options.planOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show diffs if requested (works in both dry-run and write modes)
|
// Show diffs if explicitly requested or before interactive write confirmation
|
||||||
if (options.diff) {
|
const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove);
|
||||||
|
if (showDiffs) {
|
||||||
logger.log('info', 'Showing file diffs:');
|
logger.log('info', 'Showing file diffs:');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
@@ -171,22 +154,16 @@ export let run = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute phase
|
// Execute phase
|
||||||
logger.log(
|
logger.log('info', 'Executing format operations...');
|
||||||
'info',
|
await planner.executePlan(plan, activeFormatters, context);
|
||||||
`Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`,
|
|
||||||
);
|
|
||||||
await planner.executePlan(plan, activeFormatters, context, parallel);
|
|
||||||
|
|
||||||
// Finish statistics tracking
|
|
||||||
context.getFormatStats().finish();
|
context.getFormatStats().finish();
|
||||||
|
|
||||||
// Display statistics
|
const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true);
|
||||||
const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true);
|
|
||||||
if (showStats) {
|
if (showStats) {
|
||||||
context.getFormatStats().displayStats();
|
context.getFormatStats().displayStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save stats if requested
|
|
||||||
if (options.detailed) {
|
if (options.detailed) {
|
||||||
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
||||||
await context.getFormatStats().saveReport(statsPath);
|
await context.getFormatStats().saveReport(statsPath);
|
||||||
@@ -195,36 +172,13 @@ export let run = async (
|
|||||||
logger.log('success', 'Format operations completed successfully!');
|
logger.log('success', 'Format operations completed successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('error', `Format operation failed: ${error.message}`);
|
logger.log('error', `Format operation failed: ${error.message}`);
|
||||||
|
|
||||||
// Rollback system has been removed for stability
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Export CLI command handlers
|
|
||||||
export const handleRollback = async (operationId?: string): Promise<void> => {
|
|
||||||
logger.log('info', 'Rollback system has been disabled for stability');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleListBackups = async (): Promise<void> => {
|
|
||||||
logger.log('info', 'Backup system has been disabled for stability');
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleCleanBackups = async (): Promise<void> => {
|
|
||||||
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';
|
import type { ICheckResult } from './interfaces.format.js';
|
||||||
export type { ICheckResult };
|
export type { ICheckResult };
|
||||||
|
|
||||||
// Formatters that don't require projectType to be set
|
|
||||||
const formattersNotRequiringProjectType = ['npmextra', 'prettier', 'cleanup', 'packagejson'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a single formatter by name (for use by other modules)
|
* Run a single formatter by name (for use by other modules)
|
||||||
*/
|
*/
|
||||||
@@ -232,29 +186,14 @@ export const runFormatter = async (
|
|||||||
formatterName: string,
|
formatterName: string,
|
||||||
options: {
|
options: {
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
checkOnly?: boolean; // Only check for diffs, don't apply
|
checkOnly?: boolean;
|
||||||
showDiff?: boolean; // Show the diff output
|
showDiff?: boolean;
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<ICheckResult | void> => {
|
): Promise<ICheckResult | void> => {
|
||||||
// Determine if this formatter requires projectType
|
|
||||||
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
||||||
const project = await Project.fromCwd({ requireProjectType });
|
const project = await Project.fromCwd({ requireProjectType });
|
||||||
const context = new FormatContext();
|
const context = new FormatContext();
|
||||||
|
|
||||||
// Map formatter names to classes
|
|
||||||
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
|
||||||
cleanup: CleanupFormatter,
|
|
||||||
npmextra: NpmextraFormatter,
|
|
||||||
license: LicenseFormatter,
|
|
||||||
packagejson: PackageJsonFormatter,
|
|
||||||
templates: TemplatesFormatter,
|
|
||||||
gitignore: GitignoreFormatter,
|
|
||||||
tsconfig: TsconfigFormatter,
|
|
||||||
prettier: PrettierFormatter,
|
|
||||||
readme: ReadmeFormatter,
|
|
||||||
copy: CopyFormatter,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormatterClass = formatterMap[formatterName];
|
const FormatterClass = formatterMap[formatterName];
|
||||||
if (!FormatterClass) {
|
if (!FormatterClass) {
|
||||||
throw new Error(`Unknown formatter: ${formatterName}`);
|
throw new Error(`Unknown formatter: ${formatterName}`);
|
||||||
@@ -262,7 +201,6 @@ export const runFormatter = async (
|
|||||||
|
|
||||||
const formatter = new FormatterClass(context, project);
|
const formatter = new FormatterClass(context, project);
|
||||||
|
|
||||||
// Check-only mode: just check for diffs and optionally display them
|
|
||||||
if (options.checkOnly) {
|
if (options.checkOnly) {
|
||||||
const result = await formatter.check();
|
const result = await formatter.check();
|
||||||
if (result.hasDiff && options.showDiff) {
|
if (result.hasDiff && options.showDiff) {
|
||||||
@@ -271,7 +209,6 @@ export const runFormatter = async (
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal mode: analyze and apply changes
|
|
||||||
const changes = await formatter.analyze();
|
const changes = await formatter.analyze();
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
|
|||||||
@@ -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 = {
|
export type IFormatPlan = {
|
||||||
summary: {
|
summary: {
|
||||||
totalFiles: number;
|
totalFiles: number;
|
||||||
filesAdded: number;
|
filesAdded: number;
|
||||||
filesModified: number;
|
filesModified: number;
|
||||||
filesRemoved: number;
|
filesRemoved: number;
|
||||||
estimatedTime: number;
|
|
||||||
};
|
};
|
||||||
changes: Array<{
|
changes: Array<{
|
||||||
type: 'create' | 'modify' | 'delete';
|
type: 'create' | 'modify' | 'delete';
|
||||||
path: string;
|
path: string;
|
||||||
module: string;
|
module: string;
|
||||||
description: string;
|
description: string;
|
||||||
diff?: string;
|
|
||||||
size?: number;
|
|
||||||
}>;
|
}>;
|
||||||
warnings: Array<{
|
warnings: Array<{
|
||||||
level: 'info' | 'warning' | 'error';
|
level: 'info' | 'warning' | 'error';
|
||||||
@@ -40,9 +24,6 @@ export type IPlannedChange = {
|
|||||||
module: string;
|
module: string;
|
||||||
description: string;
|
description: string;
|
||||||
content?: string; // New content for create/modify operations
|
content?: string; // New content for create/modify operations
|
||||||
originalContent?: string; // Original content for comparison
|
|
||||||
diff?: string;
|
|
||||||
size?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ICheckResult {
|
export interface ICheckResult {
|
||||||
@@ -54,3 +35,19 @@ export interface ICheckResult {
|
|||||||
after?: string;
|
after?: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getModuleIcon(module: string): string {
|
||||||
|
const icons: Record<string, string> = {
|
||||||
|
packagejson: '📦',
|
||||||
|
license: '📝',
|
||||||
|
tsconfig: '🔧',
|
||||||
|
cleanup: '🚮',
|
||||||
|
gitignore: '🔒',
|
||||||
|
prettier: '✨',
|
||||||
|
readme: '📖',
|
||||||
|
templates: '📄',
|
||||||
|
smartconfig: '⚙️',
|
||||||
|
copy: '📋',
|
||||||
|
};
|
||||||
|
return icons[module] || '📁';
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,31 +1,21 @@
|
|||||||
export * from '../plugins.js';
|
export * from '../plugins.js';
|
||||||
|
|
||||||
import * as crypto from 'crypto';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as lik from '@push.rocks/lik';
|
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartgulp from '@push.rocks/smartgulp';
|
|
||||||
import * as smartinteract from '@push.rocks/smartinteract';
|
import * as smartinteract from '@push.rocks/smartinteract';
|
||||||
import * as smartlegal from '@push.rocks/smartlegal';
|
import * as smartlegal from '@push.rocks/smartlegal';
|
||||||
import * as smartobject from '@push.rocks/smartobject';
|
import * as smartobject from '@push.rocks/smartobject';
|
||||||
import * as smartnpm from '@push.rocks/smartnpm';
|
import * as smartnpm from '@push.rocks/smartnpm';
|
||||||
import * as smartstream from '@push.rocks/smartstream';
|
import * as smartconfig from '@push.rocks/smartconfig';
|
||||||
import * as through2 from 'through2';
|
|
||||||
import * as npmextra from '@push.rocks/smartconfig';
|
|
||||||
import * as smartdiff from '@push.rocks/smartdiff';
|
import * as smartdiff from '@push.rocks/smartdiff';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
crypto,
|
|
||||||
path,
|
path,
|
||||||
lik,
|
|
||||||
smartfile,
|
smartfile,
|
||||||
smartgulp,
|
|
||||||
smartinteract,
|
smartinteract,
|
||||||
smartlegal,
|
smartlegal,
|
||||||
smartobject,
|
smartobject,
|
||||||
smartnpm,
|
smartnpm,
|
||||||
smartstream,
|
smartconfig,
|
||||||
through2,
|
|
||||||
npmextra,
|
|
||||||
smartdiff,
|
smartdiff,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ export interface IGlobalRegistryData {
|
|||||||
|
|
||||||
export class GlobalRegistry {
|
export class GlobalRegistry {
|
||||||
private static instance: GlobalRegistry | null = null;
|
private static instance: GlobalRegistry | null = null;
|
||||||
private kvStore: plugins.npmextra.KeyValueStore<IGlobalRegistryData>;
|
private kvStore: plugins.smartconfig.KeyValueStore<IGlobalRegistryData>;
|
||||||
private docker: DockerContainer;
|
private docker: DockerContainer;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.kvStore = new plugins.npmextra.KeyValueStore({
|
this.kvStore = new plugins.smartconfig.KeyValueStore({
|
||||||
typeArg: 'userHomeDir',
|
typeArg: 'userHomeDir',
|
||||||
identityArg: 'gitzone-services',
|
identityArg: 'gitzone-services',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class ServiceManager {
|
|||||||
await this.config.loadOrCreate();
|
await this.config.loadOrCreate();
|
||||||
logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
|
logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
|
||||||
|
|
||||||
// Load service selection from npmextra.json
|
// Load service selection from .smartconfig.json
|
||||||
await this.loadServiceConfiguration();
|
await this.loadServiceConfiguration();
|
||||||
|
|
||||||
// Validate and update ports if needed
|
// 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<void> {
|
private async loadServiceConfiguration(): Promise<void> {
|
||||||
const npmextraConfig = new plugins.npmextra.Smartconfig(process.cwd());
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(process.cwd());
|
||||||
const gitzoneConfig = npmextraConfig.dataFor<any>('@git.zone/cli', {});
|
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||||
|
|
||||||
// Check if services array exists
|
// Check if services array exists
|
||||||
if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) {
|
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'];
|
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
|
||||||
// Save to npmextra.json
|
// Save to .smartconfig.json
|
||||||
await this.saveServiceConfiguration(this.enabledServices);
|
await this.saveServiceConfiguration(this.enabledServices);
|
||||||
} else {
|
} else {
|
||||||
this.enabledServices = gitzoneConfig.services;
|
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<void> {
|
private async saveServiceConfiguration(services: string[]): Promise<void> {
|
||||||
const npmextraPath = plugins.path.join(process.cwd(), 'smartconfig.json');
|
const smartconfigPath = plugins.path.join(process.cwd(), '.smartconfig.json');
|
||||||
let npmextraData: any = {};
|
let smartconfigData: any = {};
|
||||||
|
|
||||||
// Read existing npmextra.json if it exists
|
// Read existing .smartconfig.json if it exists
|
||||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||||
npmextraData = JSON.parse(content as string);
|
smartconfigData = JSON.parse(content as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update @git.zone/cli.services
|
// Update @git.zone/cli.services
|
||||||
if (!npmextraData['@git.zone/cli']) {
|
if (!smartconfigData['@git.zone/cli']) {
|
||||||
npmextraData['@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
|
await plugins.smartfs
|
||||||
.file(npmextraPath)
|
.file(smartconfigPath)
|
||||||
.encoding('utf8')
|
.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(', ')}`);
|
logger.log('info', `🔧 Enabled services: ${services.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,7 +904,7 @@ export class ServiceManager {
|
|||||||
|
|
||||||
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
|
||||||
// Save to npmextra.json
|
// Save to .smartconfig.json
|
||||||
await this.saveServiceConfiguration(this.enabledServices);
|
await this.saveServiceConfiguration(this.enabledServices);
|
||||||
|
|
||||||
logger.log('ok', '✅ Service configuration updated');
|
logger.log('ok', '✅ Service configuration updated');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as smartlog from '@push.rocks/smartlog';
|
import * as smartlog from '@push.rocks/smartlog';
|
||||||
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
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 path from 'path';
|
||||||
import * as projectinfo from '@push.rocks/projectinfo';
|
import * as projectinfo from '@push.rocks/projectinfo';
|
||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
@@ -20,7 +20,7 @@ export const smartfs = new SmartFs(new SmartFsProviderNode());
|
|||||||
export {
|
export {
|
||||||
smartlog,
|
smartlog,
|
||||||
smartlogDestinationLocal,
|
smartlogDestinationLocal,
|
||||||
npmextra,
|
smartconfig,
|
||||||
path,
|
path,
|
||||||
projectinfo,
|
projectinfo,
|
||||||
smartcli,
|
smartcli,
|
||||||
|
|||||||
Reference in New Issue
Block a user