Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6deba06443 | |||
| 00cd9dc8e7 | |||
| 6b0941eea9 | |||
| 7348567a62 | |||
| ccdca55c9a | |||
| 6c62f80c57 | |||
| 7bb2f65669 | |||
| 48c4b0c9b2 | |||
| 106b72748c | |||
| 03bf82d8ee | |||
| 6a936603bd | |||
| 2a91662e63 | |||
| ea0c026c7e | |||
| d508e1d06c |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
|
test/
|
||||||
|
test2/
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "{{projectType}}",
|
"projectType": "{{projectType}}",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "{{module.githost}}",
|
"githost": "{{module.githost}}",
|
||||||
@@ -9,10 +9,12 @@
|
|||||||
"npmPackagename": "{{module.npmPackagename}}",
|
"npmPackagename": "{{module.npmPackagename}}",
|
||||||
"license": "{{module.license}}",
|
"license": "{{module.license}}",
|
||||||
"projectDomain": "{{module.projectDomain}}"
|
"projectDomain": "{{module.projectDomain}}"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"accessLevel": "{{module.npmAccessLevel}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@ship.zone/szci": {
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": []
|
||||||
"npmAccessLevel": "{{module.npmAccessLevel}}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "{{projectType}}",
|
"projectType": "{{projectType}}",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "{{module.githost}}",
|
"githost": "{{module.githost}}",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"projectDomain": "{{module.projectDomain}}"
|
"projectDomain": "{{module.projectDomain}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@ship.zone/szci": {
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": [],
|
||||||
"dockerRegistryRepoMap": {
|
"dockerRegistryRepoMap": {
|
||||||
"registry.gitlab.com": "{{dockerTargetImagePath}}"
|
"registry.gitlab.com": "{{dockerTargetImagePath}}"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "website",
|
"projectType": "website",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "{{module.githost}}",
|
"githost": "{{module.githost}}",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"projectDomain": "{{module.projectDomain}}"
|
"projectDomain": "{{module.projectDomain}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@ship.zone/szci": {
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": [],
|
||||||
"dockerRegistryRepoMap": {
|
"dockerRegistryRepoMap": {
|
||||||
"registry.gitlab.com": "{{docker.registryImageTag}}"
|
"registry.gitlab.com": "{{docker.registryImageTag}}"
|
||||||
|
|||||||
34
changelog.md
34
changelog.md
@@ -1,5 +1,39 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.5.0 - feat(mod_standard)
|
||||||
|
Add interactive main menu and help to standard CLI module; route commands via dynamic imports
|
||||||
|
|
||||||
|
- Introduce interactive CLI menu using @push.rocks/smartinteract to prompt user for actions.
|
||||||
|
- Add routing to commands (commit, format, config, template, services, open, help) using dynamic imports.
|
||||||
|
- Add showHelp() helper to display usage and available commands.
|
||||||
|
- Remove previous static template listing and logger.warn placeholder.
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.4.0 - feat(cli)
|
||||||
|
Add optional build step to release flow and auto-format npmextra config when registries change
|
||||||
|
|
||||||
|
- Introduce a --build/-b flag in the commit/release flow to run 'pnpm build' before pushing/releases
|
||||||
|
- Verify the working tree is clean after the build and abort the release if build produces uncommitted changes
|
||||||
|
- Increase total step counting to include build and verification steps in the UI progress output
|
||||||
|
- Add a runFormatter utility to the formatting module to execute a single formatter programmatically
|
||||||
|
- Wire runFormatter('npmextra') into mod_config so npmextra.json is formatted automatically after add/remove/clear/access operations
|
||||||
|
- Add npmextra registry config entry (https://verdaccio.lossless.digital) to npmextra.json
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.3.0 - feat(config)
|
||||||
|
Add interactive menu and help to config command, handle unknown commands, and bump dependencies
|
||||||
|
|
||||||
|
- When running the 'config' command with no arguments, show an interactive menu (via SmartInteract) to choose actions (show, add, remove, clear, access, help) instead of defaulting to 'show'.
|
||||||
|
- Add explicit 'help' subcommand and log an error for unknown commands before showing help.
|
||||||
|
- Update devDependencies: @git.zone/tsbuild -> ^4.0.2, @types/node -> ^25.0.2.
|
||||||
|
- Update dependency: @push.rocks/smartjson -> ^6.0.0.
|
||||||
|
|
||||||
|
## 2025-12-04 - 2.2.1 - fix(commit)
|
||||||
|
Prevent auto-accept for BREAKING CHANGE commits; require manual confirmation and warn when --yes is used
|
||||||
|
|
||||||
|
- Do not auto-accept AI commit recommendations when the suggested change is a BREAKING CHANGE (major bump).
|
||||||
|
- Only auto-accept recommendations when -y/--yes is provided and the change is not breaking.
|
||||||
|
- When --yes is provided but the recommendation is a breaking change, log a warning and fall back to interactive confirmation.
|
||||||
|
- Introduced isBreakingChange and canAutoAccept flags to centralize the auto-accept logic.
|
||||||
|
|
||||||
## 2025-12-02 - 2.2.0 - feat(services)
|
## 2025-12-02 - 2.2.0 - feat(services)
|
||||||
Improve services manager and configuration; switch test templates to @git.zone/tstest; bump dev dependencies and update docs
|
Improve services manager and configuration; switch test templates to @git.zone/tstest; bump dev dependencies and update docs
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,13 @@
|
|||||||
},
|
},
|
||||||
"tsdoc": {
|
"tsdoc": {
|
||||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@git.zone/cli": {
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
18
package.json
18
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "2.2.0",
|
"version": "2.5.0",
|
||||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||||
"main": "dist_ts/index.ts",
|
"main": "dist_ts/index.ts",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(npm run clean && npm run prepareTest && npm run testCli && npm run testFormat && npm run testCommit && npm run testDeprecate && npm run testVersion && npm run testReadme && npm run testUpdate && npm run testTemplateNpm && npm run testTemplateLit) && rm -rf test",
|
"test": "(npm run clean && npm run prepareTest && npm run testCli && npm run testFormat && npm run testCommit && npm run testDeprecate && npm run testVersion && npm run testReadme && npm run testUpdate && npm run testTemplateNpm && npm run testTemplateLit) && rm -rf test",
|
||||||
"build": "(tsbuild --web --allowimplicitany)",
|
"build": "tsbuild tsfolders",
|
||||||
"clean": "(rm -rf test/)",
|
"clean": "(rm -rf test/)",
|
||||||
"prepareTest": "(git clone https://gitlab.com/sandboxzone/sandbox-npmts.git test/)",
|
"prepareTest": "(git clone https://gitlab.com/sandboxzone/sandbox-npmts.git test/)",
|
||||||
"testBuild": "npm run build && rm -r dist/",
|
"testBuild": "npm run build && rm -r dist/",
|
||||||
@@ -57,17 +57,17 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^3.1.2",
|
"@git.zone/tsbuild": "^4.0.2",
|
||||||
"@git.zone/tsrun": "^2.0.0",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tstest": "^3.1.3",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartinteract": "^2.0.16",
|
"@push.rocks/smartinteract": "^2.0.16",
|
||||||
"@push.rocks/smartnetwork": "^4.4.0",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartshell": "^3.3.0",
|
"@push.rocks/smartshell": "^3.3.0",
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^25.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git.zone/tsdoc": "^1.10.0",
|
"@git.zone/tsdoc": "^1.10.2",
|
||||||
"@git.zone/tspublish": "^1.10.3",
|
"@git.zone/tspublish": "^1.10.3",
|
||||||
"@push.rocks/commitinfo": "^1.0.12",
|
"@push.rocks/commitinfo": "^1.0.12",
|
||||||
"@push.rocks/early": "^4.0.4",
|
"@push.rocks/early": "^4.0.4",
|
||||||
@@ -77,10 +77,10 @@
|
|||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartcli": "^4.0.19",
|
"@push.rocks/smartcli": "^4.0.19",
|
||||||
"@push.rocks/smartdiff": "^1.0.3",
|
"@push.rocks/smartdiff": "^1.0.3",
|
||||||
"@push.rocks/smartfile": "^13.1.0",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartfs": "^1.2.0",
|
"@push.rocks/smartfs": "^1.2.0",
|
||||||
"@push.rocks/smartgulp": "^3.0.4",
|
"@push.rocks/smartgulp": "^3.0.4",
|
||||||
"@push.rocks/smartjson": "^5.2.0",
|
"@push.rocks/smartjson": "^6.0.0",
|
||||||
"@push.rocks/smartlegal": "^1.0.27",
|
"@push.rocks/smartlegal": "^1.0.27",
|
||||||
"@push.rocks/smartlog": "^3.1.10",
|
"@push.rocks/smartlog": "^3.1.10",
|
||||||
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"@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",
|
"@types/through2": "^2.0.41",
|
||||||
"prettier": "^3.7.3",
|
"prettier": "^3.7.4",
|
||||||
"through2": "^4.0.2"
|
"through2": "^4.0.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
3618
pnpm-lock.yaml
generated
3618
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
test
1
test
Submodule test deleted from 0b89443584
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '2.2.0',
|
version: '2.5.0',
|
||||||
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,20 @@ export class GitzoneConfig {
|
|||||||
|
|
||||||
public async readConfigFromCwd() {
|
public async readConfigFromCwd() {
|
||||||
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
|
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
|
||||||
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
|
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
|
||||||
this.data.npmciOptions = npmextraInstance.dataFor<
|
|
||||||
IGitzoneConfigData['npmciOptions']
|
// Read szci config for backward compatibility
|
||||||
>('npmci', {
|
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
||||||
npmAccessLevel: 'public',
|
|
||||||
});
|
// Prefer accessLevel from @git.zone/cli.release, fallback to @ship.zone/szci.npmAccessLevel
|
||||||
|
const accessLevel =
|
||||||
|
(this.data as any)?.release?.accessLevel ||
|
||||||
|
szciConfig?.npmAccessLevel ||
|
||||||
|
'public';
|
||||||
|
|
||||||
|
this.data.npmciOptions = {
|
||||||
|
npmAccessLevel: accessLevel,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|||||||
@@ -131,6 +131,14 @@ export let run = async () => {
|
|||||||
modHelpers.run(argvArg);
|
modHelpers.run(argvArg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* manage release configuration
|
||||||
|
*/
|
||||||
|
gitzoneSmartcli.addCommand('config').subscribe(async (argvArg) => {
|
||||||
|
const modConfig = await import('./mod_config/index.js');
|
||||||
|
await modConfig.run(argvArg);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* manage development services (MongoDB, S3/MinIO)
|
* manage development services (MongoDB, S3/MinIO)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,8 +5,25 @@ import * as paths from '../paths.js';
|
|||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
import * as helpers from './mod.helpers.js';
|
import * as helpers from './mod.helpers.js';
|
||||||
import * as ui from './mod.ui.js';
|
import * as ui from './mod.ui.js';
|
||||||
|
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
export const run = async (argvArg: any) => {
|
||||||
|
// Check if release flag is set and validate registries early
|
||||||
|
const wantsRelease = !!(argvArg.r || argvArg.release);
|
||||||
|
const wantsBuild = !!(argvArg.b || argvArg.build);
|
||||||
|
let releaseConfig: ReleaseConfig | null = null;
|
||||||
|
|
||||||
|
if (wantsRelease) {
|
||||||
|
releaseConfig = await ReleaseConfig.fromCwd();
|
||||||
|
if (!releaseConfig.hasRegistries()) {
|
||||||
|
logger.log('error', 'No release registries configured.');
|
||||||
|
console.log('');
|
||||||
|
console.log(' Run `gitzone config add <registry-url>` to add registries.');
|
||||||
|
console.log('');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (argvArg.format) {
|
if (argvArg.format) {
|
||||||
const formatMod = await import('../mod_format/index.js');
|
const formatMod = await import('../mod_format/index.js');
|
||||||
await formatMod.run();
|
await formatMod.run();
|
||||||
@@ -30,8 +47,12 @@ export const run = async (argvArg: any) => {
|
|||||||
|
|
||||||
let answerBucket: plugins.smartinteract.AnswerBucket;
|
let answerBucket: plugins.smartinteract.AnswerBucket;
|
||||||
|
|
||||||
// Check if -y or --yes flag is set to auto-accept recommendations
|
// Check if -y/--yes flag is set AND version is not a breaking change
|
||||||
if (argvArg.y || argvArg.yes) {
|
// Breaking changes (major version bumps) always require manual confirmation
|
||||||
|
const isBreakingChange = nextCommitObject.recommendedNextVersionLevel === 'BREAKING CHANGE';
|
||||||
|
const canAutoAccept = (argvArg.y || argvArg.yes) && !isBreakingChange;
|
||||||
|
|
||||||
|
if (canAutoAccept) {
|
||||||
// Auto-mode: create AnswerBucket programmatically
|
// Auto-mode: create AnswerBucket programmatically
|
||||||
logger.log('info', '✓ Auto-accepting AI recommendations (--yes flag)');
|
logger.log('info', '✓ Auto-accepting AI recommendations (--yes flag)');
|
||||||
|
|
||||||
@@ -52,7 +73,15 @@ export const run = async (argvArg: any) => {
|
|||||||
name: 'pushToOrigin',
|
name: 'pushToOrigin',
|
||||||
value: !!(argvArg.p || argvArg.push), // Only push if -p flag also provided
|
value: !!(argvArg.p || argvArg.push), // Only push if -p flag also provided
|
||||||
});
|
});
|
||||||
|
answerBucket.addAnswer({
|
||||||
|
name: 'createRelease',
|
||||||
|
value: wantsRelease,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Warn if --yes was provided but we're requiring confirmation due to breaking change
|
||||||
|
if (isBreakingChange && (argvArg.y || argvArg.yes)) {
|
||||||
|
logger.log('warn', '⚠️ BREAKING CHANGE detected - manual confirmation required');
|
||||||
|
}
|
||||||
// Interactive mode: prompt user for input
|
// Interactive mode: prompt user for input
|
||||||
const commitInteract = new plugins.smartinteract.SmartInteract();
|
const commitInteract = new plugins.smartinteract.SmartInteract();
|
||||||
commitInteract.addQuestions([
|
commitInteract.addQuestions([
|
||||||
@@ -81,6 +110,12 @@ export const run = async (argvArg: any) => {
|
|||||||
message: `Do you want to push this version now?`,
|
message: `Do you want to push this version now?`,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: `createRelease`,
|
||||||
|
message: `Do you want to publish to npm registries?`,
|
||||||
|
default: wantsRelease,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
answerBucket = await commitInteract.runQueue();
|
answerBucket = await commitInteract.runQueue();
|
||||||
}
|
}
|
||||||
@@ -103,8 +138,25 @@ export const run = async (argvArg: any) => {
|
|||||||
sourceFilePaths: [],
|
sourceFilePaths: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Determine total steps (6 if pushing, 5 if not)
|
// Load release config if user wants to release (interactively selected)
|
||||||
const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5;
|
if (answerBucket.getAnswerFor('createRelease') && !releaseConfig) {
|
||||||
|
releaseConfig = await ReleaseConfig.fromCwd();
|
||||||
|
if (!releaseConfig.hasRegistries()) {
|
||||||
|
logger.log('error', 'No release registries configured.');
|
||||||
|
console.log('');
|
||||||
|
console.log(' Run `gitzone config add <registry-url>` to add registries.');
|
||||||
|
console.log('');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine total steps based on options
|
||||||
|
const willPush = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true');
|
||||||
|
const willRelease = answerBucket.getAnswerFor('createRelease') && releaseConfig?.hasRegistries();
|
||||||
|
let totalSteps = 5; // Base steps: commitinfo, changelog, staging, commit, version
|
||||||
|
if (wantsBuild) totalSteps += 2; // build step + verification step
|
||||||
|
if (willPush) totalSteps++;
|
||||||
|
if (willRelease) totalSteps++;
|
||||||
let currentStep = 0;
|
let currentStep = 0;
|
||||||
|
|
||||||
// Step 1: Baking commitinfo
|
// Step 1: Baking commitinfo
|
||||||
@@ -165,18 +217,66 @@ export const run = async (argvArg: any) => {
|
|||||||
const projectType = await helpers.detectProjectType();
|
const projectType = await helpers.detectProjectType();
|
||||||
const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
|
const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
|
||||||
|
|
||||||
// Step 6: Push to remote (optional)
|
// Step 6: Run build (optional)
|
||||||
|
if (wantsBuild) {
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'in-progress');
|
||||||
|
const buildResult = await smartshellInstance.exec('pnpm build');
|
||||||
|
if (buildResult.exitCode !== 0) {
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'error');
|
||||||
|
logger.log('error', 'Build failed. Aborting release.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔨 Running build', 'done');
|
||||||
|
|
||||||
|
// Step 7: Verify no uncommitted changes
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'in-progress');
|
||||||
|
const statusResult = await smartshellInstance.exec('git status --porcelain');
|
||||||
|
if (statusResult.stdout.trim() !== '') {
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'error');
|
||||||
|
logger.log('error', 'Build produced uncommitted changes. This usually means build output is not gitignored.');
|
||||||
|
logger.log('error', 'Uncommitted files:');
|
||||||
|
console.log(statusResult.stdout);
|
||||||
|
logger.log('error', 'Aborting release. Please ensure build artifacts are in .gitignore');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔍 Verifying clean working tree', 'done');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step: Push to remote (optional)
|
||||||
const currentBranch = await helpers.detectCurrentBranch();
|
const currentBranch = await helpers.detectCurrentBranch();
|
||||||
if (
|
if (willPush) {
|
||||||
answerBucket.getAnswerFor('pushToOrigin') &&
|
|
||||||
!(process.env.CI === 'true')
|
|
||||||
) {
|
|
||||||
currentStep++;
|
currentStep++;
|
||||||
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
|
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
|
||||||
await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
|
await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
|
||||||
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done');
|
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 7: Publish to npm registries (optional)
|
||||||
|
let releasedRegistries: string[] = [];
|
||||||
|
if (willRelease && releaseConfig) {
|
||||||
|
currentStep++;
|
||||||
|
const registries = releaseConfig.getRegistries();
|
||||||
|
ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'in-progress');
|
||||||
|
|
||||||
|
const accessLevel = releaseConfig.getAccessLevel();
|
||||||
|
for (const registry of registries) {
|
||||||
|
try {
|
||||||
|
await smartshellInstance.exec(`npm publish --registry=${registry} --access=${accessLevel}`);
|
||||||
|
releasedRegistries.push(registry);
|
||||||
|
} catch (error) {
|
||||||
|
logger.log('error', `Failed to publish to ${registry}: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releasedRegistries.length === registries.length) {
|
||||||
|
ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'done');
|
||||||
|
} else {
|
||||||
|
ui.printStep(currentStep, totalSteps, `📦 Publishing to ${registries.length} registr${registries.length === 1 ? 'y' : 'ies'}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(''); // Add spacing before summary
|
console.log(''); // Add spacing before summary
|
||||||
|
|
||||||
// Get commit SHA for summary
|
// Get commit SHA for summary
|
||||||
@@ -192,7 +292,9 @@ export const run = async (argvArg: any) => {
|
|||||||
commitMessage: answerBucket.getAnswerFor('commitDescription'),
|
commitMessage: answerBucket.getAnswerFor('commitDescription'),
|
||||||
newVersion: newVersion,
|
newVersion: newVersion,
|
||||||
commitSha: commitSha,
|
commitSha: commitSha,
|
||||||
pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'),
|
pushed: willPush,
|
||||||
|
released: releasedRegistries.length > 0,
|
||||||
|
releasedRegistries: releasedRegistries.length > 0 ? releasedRegistries : undefined,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface ICommitSummary {
|
|||||||
commitSha?: string;
|
commitSha?: string;
|
||||||
pushed: boolean;
|
pushed: boolean;
|
||||||
repoUrl?: string;
|
repoUrl?: string;
|
||||||
|
released?: boolean;
|
||||||
|
releasedRegistries?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRecommendation {
|
interface IRecommendation {
|
||||||
@@ -146,6 +148,13 @@ export function printSummary(summary: ICommitSummary): void {
|
|||||||
lines.push(`Remote: ⊘ Not pushed (local only)`);
|
lines.push(`Remote: ⊘ Not pushed (local only)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (summary.released && summary.releasedRegistries && summary.releasedRegistries.length > 0) {
|
||||||
|
lines.push(`Published: ✓ Released to ${summary.releasedRegistries.length} registr${summary.releasedRegistries.length === 1 ? 'y' : 'ies'}`);
|
||||||
|
summary.releasedRegistries.forEach((registry) => {
|
||||||
|
lines.push(` → ${registry}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (summary.repoUrl && summary.commitSha) {
|
if (summary.repoUrl && summary.commitSha) {
|
||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`);
|
lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`);
|
||||||
@@ -153,7 +162,9 @@ export function printSummary(summary: ICommitSummary): void {
|
|||||||
|
|
||||||
printSection('✅ Commit Summary', lines);
|
printSection('✅ Commit Summary', lines);
|
||||||
|
|
||||||
if (summary.pushed) {
|
if (summary.released) {
|
||||||
|
console.log('🎉 All done! Your changes are committed, pushed, and released.\n');
|
||||||
|
} else if (summary.pushed) {
|
||||||
console.log('🎉 All done! Your changes are committed and pushed.\n');
|
console.log('🎉 All done! Your changes are committed and pushed.\n');
|
||||||
} else {
|
} else {
|
||||||
console.log('✓ Commit created successfully.\n');
|
console.log('✓ Commit created successfully.\n');
|
||||||
|
|||||||
166
ts/mod_config/classes.releaseconfig.ts
Normal file
166
ts/mod_config/classes.releaseconfig.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import * as plugins from './mod.plugins.js';
|
||||||
|
|
||||||
|
export type TAccessLevel = 'public' | 'private';
|
||||||
|
|
||||||
|
export interface IReleaseConfig {
|
||||||
|
registries: string[];
|
||||||
|
accessLevel: TAccessLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages release configuration stored in npmextra.json
|
||||||
|
* under @git.zone/cli.release namespace
|
||||||
|
*/
|
||||||
|
export class ReleaseConfig {
|
||||||
|
private cwd: string;
|
||||||
|
private config: IReleaseConfig;
|
||||||
|
|
||||||
|
constructor(cwd: string = process.cwd()) {
|
||||||
|
this.cwd = cwd;
|
||||||
|
this.config = { registries: [], accessLevel: 'public' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ReleaseConfig instance from current working directory
|
||||||
|
*/
|
||||||
|
public static async fromCwd(cwd: string = process.cwd()): Promise<ReleaseConfig> {
|
||||||
|
const instance = new ReleaseConfig(cwd);
|
||||||
|
await instance.load();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load configuration from npmextra.json
|
||||||
|
*/
|
||||||
|
public async load(): Promise<void> {
|
||||||
|
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
|
||||||
|
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
||||||
|
|
||||||
|
// Also check szci for backward compatibility
|
||||||
|
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
||||||
|
|
||||||
|
this.config = {
|
||||||
|
registries: gitzoneConfig?.release?.registries || [],
|
||||||
|
accessLevel: gitzoneConfig?.release?.accessLevel || szciConfig?.npmAccessLevel || 'public',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save configuration to npmextra.json
|
||||||
|
*/
|
||||||
|
public async save(): Promise<void> {
|
||||||
|
const npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
|
||||||
|
let npmextraData: any = {};
|
||||||
|
|
||||||
|
// Read existing npmextra.json
|
||||||
|
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
||||||
|
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
||||||
|
npmextraData = JSON.parse(content as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure @git.zone/cli namespace exists
|
||||||
|
if (!npmextraData['@git.zone/cli']) {
|
||||||
|
npmextraData['@git.zone/cli'] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure release object exists
|
||||||
|
if (!npmextraData['@git.zone/cli'].release) {
|
||||||
|
npmextraData['@git.zone/cli'].release = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update registries and accessLevel
|
||||||
|
npmextraData['@git.zone/cli'].release.registries = this.config.registries;
|
||||||
|
npmextraData['@git.zone/cli'].release.accessLevel = this.config.accessLevel;
|
||||||
|
|
||||||
|
// Write back to file
|
||||||
|
await plugins.smartfs
|
||||||
|
.file(npmextraPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(JSON.stringify(npmextraData, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all configured registries
|
||||||
|
*/
|
||||||
|
public getRegistries(): string[] {
|
||||||
|
return [...this.config.registries];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any registries are configured
|
||||||
|
*/
|
||||||
|
public hasRegistries(): boolean {
|
||||||
|
return this.config.registries.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a registry URL
|
||||||
|
* @returns true if added, false if already exists
|
||||||
|
*/
|
||||||
|
public addRegistry(url: string): boolean {
|
||||||
|
const normalizedUrl = this.normalizeUrl(url);
|
||||||
|
|
||||||
|
if (this.config.registries.includes(normalizedUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.registries.push(normalizedUrl);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a registry URL
|
||||||
|
* @returns true if removed, false if not found
|
||||||
|
*/
|
||||||
|
public removeRegistry(url: string): boolean {
|
||||||
|
const normalizedUrl = this.normalizeUrl(url);
|
||||||
|
const index = this.config.registries.indexOf(normalizedUrl);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.registries.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all registries
|
||||||
|
*/
|
||||||
|
public clearRegistries(): void {
|
||||||
|
this.config.registries = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the npm access level
|
||||||
|
*/
|
||||||
|
public getAccessLevel(): TAccessLevel {
|
||||||
|
return this.config.accessLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the npm access level
|
||||||
|
*/
|
||||||
|
public setAccessLevel(level: TAccessLevel): void {
|
||||||
|
this.config.accessLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a registry URL (ensure it has https:// prefix)
|
||||||
|
*/
|
||||||
|
private normalizeUrl(url: string): string {
|
||||||
|
let normalized = url.trim();
|
||||||
|
|
||||||
|
// Add https:// if no protocol specified
|
||||||
|
if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
|
||||||
|
normalized = `https://${normalized}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash
|
||||||
|
if (normalized.endsWith('/')) {
|
||||||
|
normalized = normalized.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
282
ts/mod_config/index.ts
Normal file
282
ts/mod_config/index.ts
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
// gitzone config - manage release registry configuration
|
||||||
|
|
||||||
|
import * as plugins from './mod.plugins.js';
|
||||||
|
import { ReleaseConfig } from './classes.releaseconfig.js';
|
||||||
|
import { runFormatter } from '../mod_format/index.js';
|
||||||
|
|
||||||
|
export { ReleaseConfig };
|
||||||
|
|
||||||
|
export const run = async (argvArg: any) => {
|
||||||
|
const command = argvArg._?.[1];
|
||||||
|
const value = argvArg._?.[2];
|
||||||
|
|
||||||
|
// If no command provided, show interactive menu
|
||||||
|
if (!command) {
|
||||||
|
await handleInteractiveMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'show':
|
||||||
|
await handleShow();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
await handleAdd(value);
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
await handleRemove(value);
|
||||||
|
break;
|
||||||
|
case 'clear':
|
||||||
|
await handleClear();
|
||||||
|
break;
|
||||||
|
case 'access':
|
||||||
|
case 'accessLevel':
|
||||||
|
await handleAccessLevel(value);
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
showHelp();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
plugins.logger.log('error', `Unknown command: ${command}`);
|
||||||
|
showHelp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive menu for config command
|
||||||
|
*/
|
||||||
|
async function handleInteractiveMenu(): Promise<void> {
|
||||||
|
console.log('');
|
||||||
|
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||||
|
console.log('│ gitzone config - Release Configuration │');
|
||||||
|
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await interactInstance.askQuestion({
|
||||||
|
type: 'list',
|
||||||
|
name: 'action',
|
||||||
|
message: 'What would you like to do?',
|
||||||
|
default: 'show',
|
||||||
|
choices: [
|
||||||
|
{ name: 'Show current configuration', value: 'show' },
|
||||||
|
{ name: 'Add a registry', value: 'add' },
|
||||||
|
{ name: 'Remove a registry', value: 'remove' },
|
||||||
|
{ name: 'Clear all registries', value: 'clear' },
|
||||||
|
{ name: 'Set access level (public/private)', value: 'access' },
|
||||||
|
{ name: 'Show help', value: 'help' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = (response as any).value;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'show':
|
||||||
|
await handleShow();
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
await handleAdd();
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
await handleRemove();
|
||||||
|
break;
|
||||||
|
case 'clear':
|
||||||
|
await handleClear();
|
||||||
|
break;
|
||||||
|
case 'access':
|
||||||
|
await handleAccessLevel();
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
showHelp();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show current registry configuration
|
||||||
|
*/
|
||||||
|
async function handleShow(): Promise<void> {
|
||||||
|
const config = await ReleaseConfig.fromCwd();
|
||||||
|
const registries = config.getRegistries();
|
||||||
|
const accessLevel = config.getAccessLevel();
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||||
|
console.log('│ Release Configuration │');
|
||||||
|
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Show access level
|
||||||
|
plugins.logger.log('info', `Access Level: ${accessLevel}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
if (registries.length === 0) {
|
||||||
|
plugins.logger.log('info', 'No release registries configured.');
|
||||||
|
console.log('');
|
||||||
|
console.log(' Run `gitzone config add <registry-url>` to add one.');
|
||||||
|
console.log('');
|
||||||
|
} else {
|
||||||
|
plugins.logger.log('info', `Configured registries (${registries.length}):`);
|
||||||
|
console.log('');
|
||||||
|
registries.forEach((url, index) => {
|
||||||
|
console.log(` ${index + 1}. ${url}`);
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a registry URL
|
||||||
|
*/
|
||||||
|
async function handleAdd(url?: string): Promise<void> {
|
||||||
|
if (!url) {
|
||||||
|
// Interactive mode
|
||||||
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await interactInstance.askQuestion({
|
||||||
|
type: 'input',
|
||||||
|
name: 'registryUrl',
|
||||||
|
message: 'Enter registry URL:',
|
||||||
|
default: 'https://registry.npmjs.org',
|
||||||
|
validate: (input: string) => {
|
||||||
|
return !!(input && input.trim() !== '');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
url = (response as any).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await ReleaseConfig.fromCwd();
|
||||||
|
const added = config.addRegistry(url!);
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
await config.save();
|
||||||
|
await runFormatter('npmextra', { silent: true });
|
||||||
|
plugins.logger.log('success', `Added registry: ${url}`);
|
||||||
|
} else {
|
||||||
|
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a registry URL
|
||||||
|
*/
|
||||||
|
async function handleRemove(url?: string): Promise<void> {
|
||||||
|
const config = await ReleaseConfig.fromCwd();
|
||||||
|
const registries = config.getRegistries();
|
||||||
|
|
||||||
|
if (registries.length === 0) {
|
||||||
|
plugins.logger.log('warn', 'No registries configured to remove.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
// Interactive mode - show list to select from
|
||||||
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await interactInstance.askQuestion({
|
||||||
|
type: 'list',
|
||||||
|
name: 'registryUrl',
|
||||||
|
message: 'Select registry to remove:',
|
||||||
|
choices: registries,
|
||||||
|
default: registries[0],
|
||||||
|
});
|
||||||
|
url = (response as any).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removed = config.removeRegistry(url!);
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
await config.save();
|
||||||
|
await runFormatter('npmextra', { silent: true });
|
||||||
|
plugins.logger.log('success', `Removed registry: ${url}`);
|
||||||
|
} else {
|
||||||
|
plugins.logger.log('warn', `Registry not found: ${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all registries
|
||||||
|
*/
|
||||||
|
async function handleClear(): Promise<void> {
|
||||||
|
const config = await ReleaseConfig.fromCwd();
|
||||||
|
|
||||||
|
if (!config.hasRegistries()) {
|
||||||
|
plugins.logger.log('info', 'No registries to clear.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm before clearing
|
||||||
|
const confirmed = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||||
|
'Clear all configured registries?',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
config.clearRegistries();
|
||||||
|
await config.save();
|
||||||
|
await runFormatter('npmextra', { silent: true });
|
||||||
|
plugins.logger.log('success', 'All registries cleared.');
|
||||||
|
} else {
|
||||||
|
plugins.logger.log('info', 'Operation cancelled.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or toggle access level
|
||||||
|
*/
|
||||||
|
async function handleAccessLevel(level?: string): Promise<void> {
|
||||||
|
const config = await ReleaseConfig.fromCwd();
|
||||||
|
const currentLevel = config.getAccessLevel();
|
||||||
|
|
||||||
|
if (!level) {
|
||||||
|
// Interactive mode - toggle or ask
|
||||||
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await interactInstance.askQuestion({
|
||||||
|
type: 'list',
|
||||||
|
name: 'accessLevel',
|
||||||
|
message: 'Select npm access level for publishing:',
|
||||||
|
choices: ['public', 'private'],
|
||||||
|
default: currentLevel,
|
||||||
|
});
|
||||||
|
level = (response as any).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the level
|
||||||
|
if (level !== 'public' && level !== 'private') {
|
||||||
|
plugins.logger.log('error', `Invalid access level: ${level}. Must be 'public' or 'private'.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level === currentLevel) {
|
||||||
|
plugins.logger.log('info', `Access level is already set to: ${level}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.setAccessLevel(level as 'public' | 'private');
|
||||||
|
await config.save();
|
||||||
|
await runFormatter('npmextra', { silent: true });
|
||||||
|
plugins.logger.log('success', `Access level set to: ${level}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show help for config command
|
||||||
|
*/
|
||||||
|
function showHelp(): void {
|
||||||
|
console.log('');
|
||||||
|
console.log('Usage: gitzone config <command> [options]');
|
||||||
|
console.log('');
|
||||||
|
console.log('Commands:');
|
||||||
|
console.log(' show Display current release configuration');
|
||||||
|
console.log(' add [url] Add a registry URL');
|
||||||
|
console.log(' remove [url] Remove a registry URL');
|
||||||
|
console.log(' clear Clear all registries');
|
||||||
|
console.log(' access [public|private] Set npm access level for publishing');
|
||||||
|
console.log('');
|
||||||
|
console.log('Examples:');
|
||||||
|
console.log(' gitzone config show');
|
||||||
|
console.log(' gitzone config add https://registry.npmjs.org');
|
||||||
|
console.log(' gitzone config add https://verdaccio.example.com');
|
||||||
|
console.log(' gitzone config remove https://registry.npmjs.org');
|
||||||
|
console.log(' gitzone config clear');
|
||||||
|
console.log(' gitzone config access public');
|
||||||
|
console.log(' gitzone config access private');
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
3
ts/mod_config/mod.plugins.ts
Normal file
3
ts/mod_config/mod.plugins.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// mod_config plugins
|
||||||
|
export * from '../plugins.js';
|
||||||
|
export { logger } from '../gitzone.logging.js';
|
||||||
@@ -3,6 +3,65 @@ import * as paths from '../paths.js';
|
|||||||
import * as gulpFunction from '@push.rocks/gulp-function';
|
import * as gulpFunction from '@push.rocks/gulp-function';
|
||||||
import { Project } from '../classes.project.js';
|
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] && !npmextraJson[newKey]) {
|
||||||
|
npmextraJson[newKey] = npmextraJson[oldKey];
|
||||||
|
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
|
* runs the npmextra file checking
|
||||||
*/
|
*/
|
||||||
@@ -13,8 +72,14 @@ export const run = async (projectArg: Project) => {
|
|||||||
const fileString = fileArg.contents.toString();
|
const fileString = fileArg.contents.toString();
|
||||||
const npmextraJson = JSON.parse(fileString);
|
const npmextraJson = JSON.parse(fileString);
|
||||||
|
|
||||||
if (!npmextraJson.gitzone) {
|
// Migrate old namespace keys to new package-scoped keys
|
||||||
npmextraJson.gitzone = {};
|
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[] = [
|
const expectedRepoInformation: string[] = [
|
||||||
@@ -31,7 +96,7 @@ export const run = async (projectArg: Project) => {
|
|||||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||||
if (
|
if (
|
||||||
!plugins.smartobject.smartGet(
|
!plugins.smartobject.smartGet(
|
||||||
npmextraJson.gitzone,
|
npmextraJson['@git.zone/cli'],
|
||||||
expectedRepoInformationItem,
|
expectedRepoInformationItem,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -53,7 +118,7 @@ export const run = async (projectArg: Project) => {
|
|||||||
);
|
);
|
||||||
if (cliProvidedValue) {
|
if (cliProvidedValue) {
|
||||||
plugins.smartobject.smartAdd(
|
plugins.smartobject.smartAdd(
|
||||||
npmextraJson.gitzone,
|
npmextraJson['@git.zone/cli'],
|
||||||
expectedRepoInformationItem,
|
expectedRepoInformationItem,
|
||||||
cliProvidedValue,
|
cliProvidedValue,
|
||||||
);
|
);
|
||||||
@@ -63,8 +128,8 @@ export const run = async (projectArg: Project) => {
|
|||||||
// delete obsolete
|
// delete obsolete
|
||||||
// tbd
|
// tbd
|
||||||
|
|
||||||
if (!npmextraJson.npmci) {
|
if (!npmextraJson['@ship.zone/szci']) {
|
||||||
npmextraJson.npmci = {};
|
npmextraJson['@ship.zone/szci'] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
fileArg.setContentsFromString(JSON.stringify(npmextraJson, null, 2));
|
fileArg.setContentsFromString(JSON.stringify(npmextraJson, null, 2));
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const run = async (projectArg: Project) => {
|
|||||||
plugins.smartgulp.src([`package.json`]),
|
plugins.smartgulp.src([`package.json`]),
|
||||||
gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => {
|
gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => {
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra(paths.cwd);
|
const npmextraConfig = new plugins.npmextra.Npmextra(paths.cwd);
|
||||||
const gitzoneData: any = npmextraConfig.dataFor('gitzone', {});
|
const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {});
|
||||||
const fileString = fileArg.contents.toString();
|
const fileString = fileArg.contents.toString();
|
||||||
const packageJson = JSON.parse(fileString);
|
const packageJson = JSON.parse(fileString);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as plugins from './mod.plugins.js';
|
|||||||
import { Project } from '../classes.project.js';
|
import { Project } from '../classes.project.js';
|
||||||
import { FormatContext } from './classes.formatcontext.js';
|
import { FormatContext } from './classes.formatcontext.js';
|
||||||
import { FormatPlanner } from './classes.formatplanner.js';
|
import { FormatPlanner } from './classes.formatplanner.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 wrapper classes for formatters
|
||||||
@@ -41,7 +42,7 @@ export let run = async (
|
|||||||
|
|
||||||
// Get configuration from npmextra
|
// Get configuration from npmextra
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||||
const formatConfig = npmextraConfig.dataFor<any>('gitzone.format', {
|
const formatConfig = npmextraConfig.dataFor<any>('@git.zone/cli.format', {
|
||||||
interactive: true,
|
interactive: true,
|
||||||
showDiffs: false,
|
showDiffs: false,
|
||||||
autoApprove: false,
|
autoApprove: false,
|
||||||
@@ -195,3 +196,44 @@ export const handleCleanBackups = async (): Promise<void> => {
|
|||||||
'Backup cleaning has been disabled - backup system removed',
|
'Backup cleaning has been disabled - backup system removed',
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a single formatter by name (for use by other modules)
|
||||||
|
*/
|
||||||
|
export const runFormatter = async (
|
||||||
|
formatterName: string,
|
||||||
|
options: { silent?: boolean } = {}
|
||||||
|
): Promise<void> => {
|
||||||
|
const project = await Project.fromCwd();
|
||||||
|
const context = new FormatContext();
|
||||||
|
|
||||||
|
// Map formatter names to classes
|
||||||
|
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
||||||
|
cleanup: CleanupFormatter,
|
||||||
|
npmextra: NpmextraFormatter,
|
||||||
|
license: LicenseFormatter,
|
||||||
|
packagejson: PackageJsonFormatter,
|
||||||
|
templates: TemplatesFormatter,
|
||||||
|
gitignore: GitignoreFormatter,
|
||||||
|
tsconfig: TsconfigFormatter,
|
||||||
|
prettier: PrettierFormatter,
|
||||||
|
readme: ReadmeFormatter,
|
||||||
|
copy: CopyFormatter,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormatterClass = formatterMap[formatterName];
|
||||||
|
if (!FormatterClass) {
|
||||||
|
throw new Error(`Unknown formatter: ${formatterName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatter = new FormatterClass(context, project);
|
||||||
|
const changes = await formatter.analyze();
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
await formatter.applyChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
|
logger.log('success', `Formatter '${formatterName}' completed`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class ServiceManager {
|
|||||||
*/
|
*/
|
||||||
private async loadServiceConfiguration(): Promise<void> {
|
private async loadServiceConfiguration(): Promise<void> {
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd());
|
const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd());
|
||||||
const gitzoneConfig = npmextraConfig.dataFor<any>('gitzone', {});
|
const gitzoneConfig = npmextraConfig.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) {
|
||||||
@@ -84,11 +84,11 @@ export class ServiceManager {
|
|||||||
npmextraData = JSON.parse(content as string);
|
npmextraData = JSON.parse(content as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update gitzone.services
|
// Update @git.zone/cli.services
|
||||||
if (!npmextraData.gitzone) {
|
if (!npmextraData['@git.zone/cli']) {
|
||||||
npmextraData.gitzone = {};
|
npmextraData['@git.zone/cli'] = {};
|
||||||
}
|
}
|
||||||
npmextraData.gitzone.services = services;
|
npmextraData['@git.zone/cli'].services = services;
|
||||||
|
|
||||||
// Write back to npmextra.json
|
// Write back to npmextra.json
|
||||||
await plugins.smartfs
|
await plugins.smartfs
|
||||||
|
|||||||
@@ -7,38 +7,85 @@ import * as paths from '../paths.js';
|
|||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
export let run = async () => {
|
export let run = async () => {
|
||||||
const done = plugins.smartpromise.defer();
|
console.log('');
|
||||||
logger.log('warn', 'no action specified');
|
console.log('╭─────────────────────────────────────────────────────────────╮');
|
||||||
|
console.log('│ gitzone - Development Workflow CLI │');
|
||||||
|
console.log('╰─────────────────────────────────────────────────────────────╯');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
const dirEntries = await plugins.smartfs.directory(paths.templatesDir).list();
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
const templates: string[] = [];
|
const response = await interactInstance.askQuestion({
|
||||||
for (const entry of dirEntries) {
|
type: 'list',
|
||||||
try {
|
name: 'action',
|
||||||
const stats = await plugins.smartfs
|
message: 'What would you like to do?',
|
||||||
.file(plugins.path.join(paths.templatesDir, entry.path))
|
default: 'commit',
|
||||||
.stat();
|
choices: [
|
||||||
if (stats.isDirectory) {
|
{ name: 'Commit changes (semantic versioning)', value: 'commit' },
|
||||||
templates.push(entry.name);
|
{ name: 'Format project files', value: 'format' },
|
||||||
}
|
{ name: 'Configure release settings', value: 'config' },
|
||||||
} catch {
|
{ name: 'Create from template', value: 'template' },
|
||||||
// Skip entries that can't be accessed
|
{ name: 'Manage dev services (MongoDB, S3)', value: 'services' },
|
||||||
|
{ name: 'Open project assets', value: 'open' },
|
||||||
|
{ name: 'Show help', value: 'help' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = (response as any).value;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'commit': {
|
||||||
|
const modCommit = await import('../mod_commit/index.js');
|
||||||
|
await modCommit.run({ _: ['commit'] });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case 'format': {
|
||||||
|
const modFormat = await import('../mod_format/index.js');
|
||||||
|
await modFormat.run({ interactive: true });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'config': {
|
||||||
|
const modConfig = await import('../mod_config/index.js');
|
||||||
|
await modConfig.run({ _: ['config'] });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'template': {
|
||||||
|
const modTemplate = await import('../mod_template/index.js');
|
||||||
|
await modTemplate.run({ _: ['template'] });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'services': {
|
||||||
|
const modServices = await import('../mod_services/index.js');
|
||||||
|
await modServices.run({ _: ['services'] });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'open': {
|
||||||
|
const modOpen = await import('../mod_open/index.js');
|
||||||
|
await modOpen.run({ _: ['open'] });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'help':
|
||||||
|
showHelp();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let projects = `\n`;
|
|
||||||
for (const template of templates) {
|
|
||||||
projects += ` - ${template}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log(
|
|
||||||
'info',
|
|
||||||
`
|
|
||||||
You can do one of the following things:
|
|
||||||
* create a new project with 'gitzone template [template]'
|
|
||||||
the following templates exist: ${projects}
|
|
||||||
* format a project with 'gitzone format'
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
done.resolve();
|
|
||||||
return done.promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function showHelp(): void {
|
||||||
|
console.log('');
|
||||||
|
console.log('Usage: gitzone <command> [options]');
|
||||||
|
console.log('');
|
||||||
|
console.log('Commands:');
|
||||||
|
console.log(' commit Create a semantic commit with versioning');
|
||||||
|
console.log(' format Format and standardize project files');
|
||||||
|
console.log(' config Manage release registry configuration');
|
||||||
|
console.log(' template Create a new project from template');
|
||||||
|
console.log(' services Manage dev services (MongoDB, S3/MinIO)');
|
||||||
|
console.log(' open Open project assets (GitLab, npm, etc.)');
|
||||||
|
console.log(' docker Docker-related operations');
|
||||||
|
console.log(' deprecate Deprecate a package on npm');
|
||||||
|
console.log(' meta Run meta commands');
|
||||||
|
console.log(' start Start working on a project');
|
||||||
|
console.log(' helpers Run helper utilities');
|
||||||
|
console.log('');
|
||||||
|
console.log('Run gitzone <command> --help for more information on a command.');
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user