Compare commits

...

4 Commits

Author SHA1 Message Date
ccdca55c9a v2.3.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-14 10:32:58 +00:00
6c62f80c57 feat(config): Add interactive menu and help to config command, handle unknown commands, and bump dependencies 2025-12-14 10:32:58 +00:00
7bb2f65669 update 2025-12-14 01:42:59 +00:00
48c4b0c9b2 update 2025-12-14 01:31:06 +00:00
13 changed files with 653 additions and 56 deletions

View File

@@ -9,10 +9,12 @@
"npmPackagename": "{{module.npmPackagename}}",
"license": "{{module.license}}",
"projectDomain": "{{module.projectDomain}}"
},
"release": {
"accessLevel": "{{module.npmAccessLevel}}"
}
},
"@ship.zone/szci": {
"npmGlobalTools": [],
"npmAccessLevel": "{{module.npmAccessLevel}}"
"npmGlobalTools": []
}
}

View File

@@ -1,5 +1,13 @@
# Changelog
## 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

View File

@@ -1,7 +1,7 @@
{
"name": "@git.zone/cli",
"private": false,
"version": "2.2.2",
"version": "2.3.0",
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
"main": "dist_ts/index.ts",
"typings": "dist_ts/index.d.ts",
@@ -57,14 +57,14 @@
},
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
"devDependencies": {
"@git.zone/tsbuild": "^4.0.1",
"@git.zone/tsbuild": "^4.0.2",
"@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.3",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartinteract": "^2.0.16",
"@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartshell": "^3.3.0",
"@types/node": "^24.10.3"
"@types/node": "^25.0.2"
},
"dependencies": {
"@git.zone/tsdoc": "^1.10.2",
@@ -80,7 +80,7 @@
"@push.rocks/smartfile": "^13.1.2",
"@push.rocks/smartfs": "^1.2.0",
"@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/smartlog": "^3.1.10",
"@push.rocks/smartlog-destination-local": "^9.0.2",

82
pnpm-lock.yaml generated
View File

@@ -48,8 +48,8 @@ importers:
specifier: ^3.0.4
version: 3.0.4
'@push.rocks/smartjson':
specifier: ^5.2.0
version: 5.2.0
specifier: ^6.0.0
version: 6.0.0
'@push.rocks/smartlegal':
specifier: ^1.0.27
version: 1.0.27
@@ -100,8 +100,8 @@ importers:
version: 4.0.2
devDependencies:
'@git.zone/tsbuild':
specifier: ^4.0.1
version: 4.0.1
specifier: ^4.0.2
version: 4.0.2
'@git.zone/tsrun':
specifier: ^2.0.1
version: 2.0.1
@@ -121,8 +121,8 @@ importers:
specifier: ^3.3.0
version: 3.3.0
'@types/node':
specifier: ^24.10.3
version: 24.10.3
specifier: ^25.0.2
version: 25.0.2
packages:
@@ -512,8 +512,8 @@ packages:
'@gerrit0/mini-shiki@3.20.0':
resolution: {integrity: sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==}
'@git.zone/tsbuild@4.0.1':
resolution: {integrity: sha512-Iqpdkt32TzhtR2HeIHPvCTl2TusJcthYAWeCjgSQGmLbbw91OnwtGEWrVAUBu54b9xc9q9KnUKZ4UP1fC7IA1A==}
'@git.zone/tsbuild@4.0.2':
resolution: {integrity: sha512-LcRlFnDbcUe53Pdoob585iXq9TAT90TyEaYl/wml/etFoPeBX+oQLm6GryejUPXrUP7i1opyTonadkQN1OyXOA==}
hasBin: true
'@git.zone/tsbundle@2.6.3':
@@ -1111,6 +1111,9 @@ packages:
'@push.rocks/smartjson@5.2.0':
resolution: {integrity: sha512-710e8UwovRfPgUtaBHcd6unaODUjV5fjxtGcGCqtaTcmvOV6VpasdVfT66xMDzQmWH2E9ZfHDJeso9HdDQzNQA==}
'@push.rocks/smartjson@6.0.0':
resolution: {integrity: sha512-FYfJnmukt66WePn6xrVZ3BLmRQl9W82LcsICK3VU9sGW7kasig090jKXPm+yX8ibQcZAO/KyR/Q8tMIYZNxGew==}
'@push.rocks/smartlegal@1.0.27':
resolution: {integrity: sha512-0LjSY5pc2ljvR4PST0l1mvrB27Cj9NqpDDYf8eo9kgtgsb5m4Te+ZIzVnAj8W9P/uXJOLTOXt2OLKH4z6RGw6g==}
@@ -1865,11 +1868,11 @@ packages:
'@types/node@16.9.1':
resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==}
'@types/node@22.19.2':
resolution: {integrity: sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==}
'@types/node@22.19.3':
resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==}
'@types/node@24.10.3':
resolution: {integrity: sha512-gqkrWUsS8hcm0r44yn7/xZeV1ERva/nLgrLxFRUGb7aoNMIJfZJ3AC261zDQuOAKC7MiXai1WCpYc48jAHoShQ==}
'@types/node@25.0.2':
resolution: {integrity: sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==}
'@types/ping@0.4.4':
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
@@ -5018,7 +5021,7 @@ snapshots:
'@shikijs/types': 3.20.0
'@shikijs/vscode-textmate': 10.0.2
'@git.zone/tsbuild@4.0.1':
'@git.zone/tsbuild@4.0.2':
dependencies:
'@git.zone/tspublish': 1.10.3
'@push.rocks/early': 4.0.4
@@ -5288,7 +5291,7 @@ snapshots:
'@inquirer/figures': 1.0.15
'@inquirer/type': 2.0.0
'@types/mute-stream': 0.0.4
'@types/node': 22.19.2
'@types/node': 22.19.3
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
cli-width: 4.1.0
@@ -6239,6 +6242,13 @@ snapshots:
fast-json-stable-stringify: 2.1.0
lodash.clonedeep: 4.5.0
'@push.rocks/smartjson@6.0.0':
dependencies:
'@push.rocks/smartenv': 6.0.0
'@push.rocks/smartstring': 4.1.0
fast-json-stable-stringify: 2.1.0
lodash.clonedeep: 4.5.0
'@push.rocks/smartlegal@1.0.27':
dependencies:
'@push.rocks/smartmarkdown': 3.0.3
@@ -7350,27 +7360,27 @@ snapshots:
'@types/bn.js@5.2.0':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/buffer-json@2.0.3': {}
'@types/clean-css@4.2.11':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
source-map: 0.6.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/cors@2.8.19':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/debug@4.1.12':
dependencies:
@@ -7382,7 +7392,7 @@ snapshots:
'@types/dns-packet@5.6.5':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/elliptic@6.4.18':
dependencies:
@@ -7390,7 +7400,7 @@ snapshots:
'@types/express-serve-static-core@5.1.0':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
@@ -7403,17 +7413,17 @@ snapshots:
'@types/from2@2.3.6':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/hast@3.0.4':
dependencies:
@@ -7435,7 +7445,7 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/mdast@4.0.4':
dependencies:
@@ -7449,19 +7459,19 @@ snapshots:
'@types/mute-stream@0.0.4':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/node-forge@1.3.14':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/node@16.9.1': {}
'@types/node@22.19.2':
'@types/node@22.19.3':
dependencies:
undici-types: 6.21.0
'@types/node@24.10.3':
'@types/node@25.0.2':
dependencies:
undici-types: 7.16.0
@@ -7479,22 +7489,22 @@ snapshots:
'@types/send@1.2.1':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/symbol-tree@3.2.5': {}
'@types/tar-stream@3.1.4':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/through2@2.0.41':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/trusted-types@2.0.7': {}
@@ -7520,11 +7530,11 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 24.10.3
'@types/node': 25.0.2
optional: true
'@ungap/structured-clone@1.3.0': {}
@@ -7967,7 +7977,7 @@ snapshots:
engine.io@6.6.4:
dependencies:
'@types/cors': 2.8.19
'@types/node': 24.10.3
'@types/node': 25.0.2
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/cli',
version: '2.2.1',
version: '2.3.0',
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
}

View File

@@ -40,11 +40,19 @@ export class GitzoneConfig {
public async readConfigFromCwd() {
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
this.data.npmciOptions = npmextraInstance.dataFor<
IGitzoneConfigData['npmciOptions']
>('@ship.zone/szci', {
npmAccessLevel: 'public',
});
// Read szci config for backward compatibility
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
// 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() {}

View File

@@ -131,6 +131,14 @@ export let run = async () => {
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)
*/

View File

@@ -5,8 +5,24 @@ import * as paths from '../paths.js';
import { logger } from '../gitzone.logging.js';
import * as helpers from './mod.helpers.js';
import * as ui from './mod.ui.js';
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
export const run = async (argvArg: any) => {
// Check if release flag is set and validate registries early
const wantsRelease = !!(argvArg.r || argvArg.release);
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) {
const formatMod = await import('../mod_format/index.js');
await formatMod.run();
@@ -56,6 +72,10 @@ export const run = async (argvArg: any) => {
name: 'pushToOrigin',
value: !!(argvArg.p || argvArg.push), // Only push if -p flag also provided
});
answerBucket.addAnswer({
name: 'createRelease',
value: wantsRelease,
});
} else {
// Warn if --yes was provided but we're requiring confirmation due to breaking change
if (isBreakingChange && (argvArg.y || argvArg.yes)) {
@@ -89,6 +109,12 @@ export const run = async (argvArg: any) => {
message: `Do you want to push this version now?`,
default: true,
},
{
type: 'confirm',
name: `createRelease`,
message: `Do you want to publish to npm registries?`,
default: wantsRelease,
},
]);
answerBucket = await commitInteract.runQueue();
}
@@ -111,8 +137,24 @@ export const run = async (argvArg: any) => {
sourceFilePaths: [],
});
// Determine total steps (6 if pushing, 5 if not)
const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5;
// Load release config if user wants to release (interactively selected)
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 (willPush) totalSteps++;
if (willRelease) totalSteps++;
let currentStep = 0;
// Step 1: Baking commitinfo
@@ -175,16 +217,37 @@ export const run = async (argvArg: any) => {
// Step 6: Push to remote (optional)
const currentBranch = await helpers.detectCurrentBranch();
if (
answerBucket.getAnswerFor('pushToOrigin') &&
!(process.env.CI === 'true')
) {
if (willPush) {
currentStep++;
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
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
// Get commit SHA for summary
@@ -200,7 +263,9 @@ export const run = async (argvArg: any) => {
commitMessage: answerBucket.getAnswerFor('commitDescription'),
newVersion: newVersion,
commitSha: commitSha,
pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'),
pushed: willPush,
released: releasedRegistries.length > 0,
releasedRegistries: releasedRegistries.length > 0 ? releasedRegistries : undefined,
});
};

View File

@@ -14,6 +14,8 @@ interface ICommitSummary {
commitSha?: string;
pushed: boolean;
repoUrl?: string;
released?: boolean;
releasedRegistries?: string[];
}
interface IRecommendation {
@@ -146,6 +148,13 @@ export function printSummary(summary: ICommitSummary): void {
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) {
lines.push('');
lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`);
@@ -153,7 +162,9 @@ export function printSummary(summary: ICommitSummary): void {
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');
} else {
console.log('✓ Commit created successfully.\n');

View 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;
}
}

277
ts/mod_config/index.ts Normal file
View File

@@ -0,0 +1,277 @@
// gitzone config - manage release registry configuration
import * as plugins from './mod.plugins.js';
import { ReleaseConfig } from './classes.releaseconfig.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();
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();
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();
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();
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('');
}

View File

@@ -0,0 +1,3 @@
// mod_config plugins
export * from '../plugins.js';
export { logger } from '../gitzone.logging.js';

View File

@@ -26,6 +26,42 @@ const migrateNamespaceKeys = (npmextraJson: any): boolean => {
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
*/
@@ -39,6 +75,9 @@ export const run = async (projectArg: Project) => {
// 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'] = {};
}