Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b328c3045 | |||
| f444a04876 | |||
| 6bd2d35992 | |||
| d038b1d681 | |||
| d60de5cef7 | |||
| e1076a60ad | |||
| 6deba06443 | |||
| 00cd9dc8e7 | |||
| 6b0941eea9 | |||
| 7348567a62 | |||
| ccdca55c9a | |||
| 6c62f80c57 | |||
| 7bb2f65669 | |||
| 48c4b0c9b2 | |||
| 106b72748c | |||
| 03bf82d8ee | |||
| 6a936603bd | |||
| 2a91662e63 | |||
| ea0c026c7e | |||
| d508e1d06c | |||
| 27f2d265de | |||
| af3e15e922 | |||
| b44624f2e7 | |||
| 847e679e92 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
|
test/
|
||||||
|
test2/
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -17,12 +17,10 @@ fileName: package.json
|
|||||||
"buildDocs": "(tsdoc)"
|
"buildDocs": "(tsdoc)"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.25",
|
"@git.zone/tsbuild": "^3.1.2",
|
||||||
"@git.zone/tsbundle": "^2.0.5",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@git.zone/tsrun": "^1.2.46",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tstest": "^1.0.44",
|
"@types/node": "^24.10.1"
|
||||||
"@push.rocks/tapbundle": "^5.0.15",
|
|
||||||
"@types/node": "^20.8.7"
|
|
||||||
},
|
},
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as {{module.name}} from '../ts/index.js'
|
import * as {{module.name}} from '../ts/index.js'
|
||||||
|
|
||||||
tap.test('first test', async () => {
|
tap.test('first test', async () => {
|
||||||
console.log({{module.name}})
|
console.log({{module.name}})
|
||||||
})
|
})
|
||||||
|
|
||||||
tap.start()
|
export default tap.start()
|
||||||
|
|||||||
@@ -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}}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,18 +17,18 @@ fileName: package.json
|
|||||||
"build": "(tsbuild --web --allowimplicitany)"
|
"build": "(tsbuild --web --allowimplicitany)"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.17",
|
"@git.zone/tsbuild": "^3.1.2",
|
||||||
"@git.zone/tsrun": "^1.2.8",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@git.zone/tstest": "^1.0.28",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tswatch": "^2.0.1",
|
"@git.zone/tswatch": "^2.0.1",
|
||||||
"@push.rocks/tapbundle": "^5.5.4"
|
"@types/node": "^24.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedserver": "^3.0.53",
|
"@api.global/typedserver": "^3.0.53",
|
||||||
"@push.rocks/projectinfo": "^5.0.1",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/qenv": "^6.1.0",
|
"@push.rocks/qenv": "^6.1.0",
|
||||||
"@push.rocks/smartdata": "^5.0.7",
|
"@push.rocks/smartdata": "^5.0.7",
|
||||||
"@push.rocks/smartpath": "^5.0.5",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartstate": "^2.0.0"
|
"@push.rocks/smartstate": "^2.0.0"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}}"
|
||||||
|
|||||||
81
changelog.md
81
changelog.md
@@ -1,5 +1,86 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.7.0 - feat(mod_format)
|
||||||
|
Add check-only formatting with interactive diff preview; make formatting default to dry-run and extend formatting API
|
||||||
|
|
||||||
|
- Add BaseFormatter.check(), displayDiff() and displayAllDiffs() to compute and render diffs without applying changes.
|
||||||
|
- Extend runFormatter API with new options: write (use to apply changes), checkOnly (only check for diffs), and showDiff (display diffs). When checkOnly is used, runFormatter returns an ICheckResult.
|
||||||
|
- Change default formatting behavior to dry-run. Use --write / -w to actually apply changes. CLI format command updated to respect --write/-w.
|
||||||
|
- Add formatNpmextraWithDiff in mod_config to preview diffs for npmextra.json and prompt the user before applying changes; calls to add/remove/clear registries and set access level now use this preview flow.
|
||||||
|
- Project.fromCwd now accepts an options object ({ requireProjectType?: boolean }) so callers can skip the projectType requirement when appropriate; runFormatter no longer requires projectType for certain formatters.
|
||||||
|
- Introduce a list of formatters that don't require projectType: npmextra, prettier, cleanup, packagejson.
|
||||||
|
- Export the ICheckResult type from the formatter module and update mod_format interfaces to include ICheckResult.
|
||||||
|
- Bump dependency @push.rocks/smartdiff to ^1.1.0.
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.6.1 - fix(npmextra)
|
||||||
|
Normalize npmextra.json: move tsdoc legal entry and reposition @git.zone/cli configuration
|
||||||
|
|
||||||
|
- Move TSDoc legal text into a top-level "tsdoc.legal" property in npmextra.json
|
||||||
|
- Remove the misplaced "gitzone" object that previously contained the TSDoc legal block
|
||||||
|
- Reorder and properly nest the "@git.zone/cli" configuration to follow the new "tsdoc" section
|
||||||
|
- Pure configuration change (JSON structure) — no functional code changes
|
||||||
|
|
||||||
|
## 2025-12-14 - 2.6.0 - feat(mod_commit)
|
||||||
|
Add execution plan output to commit command
|
||||||
|
|
||||||
|
- Print an execution plan at the start of the commit flow (shows active options and planned steps)
|
||||||
|
- New printExecutionPlan(options) added to ts/mod_commit/mod.ui.ts
|
||||||
|
- Commit command now calls ui.printExecutionPlan(...) before running format or other steps
|
||||||
|
- Execution plan reflects flags: auto-accept (-y), push (-p), build (-b), release (-r), --format, and target registries
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
Improve services manager and configuration; switch test templates to @git.zone/tstest; bump dev dependencies and update docs
|
||||||
|
|
||||||
|
- services: Add robust ServiceConfiguration (creates .nogit/env.json with sane defaults, syncs ports from existing Docker containers, validates and can reconfigure ports)
|
||||||
|
- services CLI: improved start/stop/restart flows, better logging/help output and enhanced global commands (list/status/stop/cleanup)
|
||||||
|
- templates/tests: replace @push.rocks/tapbundle with @git.zone/tstest and update template test.ts to export default tap.start()
|
||||||
|
- format: stop auto-updating tslint template and mark @push.rocks/tapbundle as deprecated in package formatting logic
|
||||||
|
- dependencies: bump @git.zone/tsbuild, @git.zone/tsrun, @git.zone/tstest, @git.zone/tsdoc, @push.rocks/projectinfo, @push.rocks/smartpath, @push.rocks/smartfs, prettier and other dev deps
|
||||||
|
- docs: README updates — add issue reporting/security section, AI-powered commit recommendation notes, and clarify trademark/legal wording
|
||||||
|
|
||||||
|
## 2025-11-29 - 2.1.0 - feat(mod_services)
|
||||||
|
Add global service registry and global commands for managing project containers
|
||||||
|
|
||||||
|
- Introduce GlobalRegistry class to track registered projects, their containers, ports and last activity (ts/mod_services/classes.globalregistry.ts)
|
||||||
|
- Add global CLI mode for services (use -g/--global) with commands: list, status, stop, cleanup (ts/mod_services/index.ts)
|
||||||
|
- ServiceManager now registers the current project with the global registry when starting services and unregisters when all containers are removed (ts/mod_services/classes.servicemanager.ts)
|
||||||
|
- Global handlers to list projects, show aggregated status, stop containers across projects and cleanup stale entries
|
||||||
|
- Bump dependency @push.rocks/smartfile to ^13.1.0 in package.json
|
||||||
|
|
||||||
## 2025-11-27 - 2.0.0 - BREAKING CHANGE(core)
|
## 2025-11-27 - 2.0.0 - BREAKING CHANGE(core)
|
||||||
Migrate filesystem to smartfs (async) and add Elasticsearch service support; refactor format/commit/meta modules
|
Migrate filesystem to smartfs (async) and add Elasticsearch service support; refactor format/commit/meta modules
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
"npmAccessLevel": "private",
|
"npmAccessLevel": "private",
|
||||||
"npmRegistryUrl": "verdaccio.lossless.one"
|
"npmRegistryUrl": "verdaccio.lossless.one"
|
||||||
},
|
},
|
||||||
"gitzone": {
|
"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"
|
||||||
|
},
|
||||||
|
"@git.zone/cli": {
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "gitlab.com",
|
||||||
@@ -31,9 +34,13 @@
|
|||||||
"Docker",
|
"Docker",
|
||||||
"CI/CD"
|
"CI/CD"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
23
package.json
23
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "2.0.0",
|
"version": "2.7.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.0",
|
"@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.9.2",
|
"@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",
|
||||||
@@ -75,13 +75,12 @@
|
|||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@push.rocks/npmextra": "^5.3.3",
|
"@push.rocks/npmextra": "^5.3.3",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartchok": "^1.1.1",
|
|
||||||
"@push.rocks/smartcli": "^4.0.19",
|
"@push.rocks/smartcli": "^4.0.19",
|
||||||
"@push.rocks/smartdiff": "^1.0.3",
|
"@push.rocks/smartdiff": "^1.1.0",
|
||||||
"@push.rocks/smartfile": "^13.0.1",
|
"@push.rocks/smartfile": "^13.1.2",
|
||||||
"@push.rocks/smartfs": "^1.1.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",
|
||||||
@@ -96,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.6.2",
|
"prettier": "^3.7.4",
|
||||||
"through2": "^4.0.2"
|
"through2": "^4.0.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
3657
pnpm-lock.yaml
generated
3657
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
43
readme.md
43
readme.md
@@ -9,6 +9,10 @@
|
|||||||
|
|
||||||
gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, seamless version control, and development service orchestration. Whether you're bootstrapping a new TypeScript project, maintaining code quality, managing complex multi-repository setups, or spinning up local development databases, gitzone has got you covered.
|
gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, seamless version control, and development service orchestration. Whether you're bootstrapping a new TypeScript project, maintaining code quality, managing complex multi-repository setups, or spinning up local development databases, gitzone has got you covered.
|
||||||
|
|
||||||
|
## Issue Reporting and Security
|
||||||
|
|
||||||
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
## 🏃♂️ Quick Start
|
## 🏃♂️ Quick Start
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@@ -35,13 +39,13 @@ gitzone format
|
|||||||
# Start local MongoDB and MinIO services
|
# Start local MongoDB and MinIO services
|
||||||
gitzone services start
|
gitzone services start
|
||||||
|
|
||||||
# Create a semantic commit
|
# Create a semantic commit with AI-powered suggestions
|
||||||
gitzone commit
|
gitzone commit
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Core Features
|
## 🛠️ Core Features
|
||||||
|
|
||||||
### 🐳 Development Services Management (NEW!)
|
### 🐳 Development Services Management
|
||||||
|
|
||||||
Effortlessly manage local MongoDB and MinIO (S3-compatible) services for your development environment:
|
Effortlessly manage local MongoDB and MinIO (S3-compatible) services for your development environment:
|
||||||
|
|
||||||
@@ -110,7 +114,7 @@ gitzone template [template-name]
|
|||||||
Each template comes pre-configured with:
|
Each template comes pre-configured with:
|
||||||
|
|
||||||
- ✅ TypeScript with modern configurations
|
- ✅ TypeScript with modern configurations
|
||||||
- ✅ Automated testing setup
|
- ✅ Automated testing setup with `@git.zone/tstest`
|
||||||
- ✅ CI/CD pipelines (GitLab/GitHub)
|
- ✅ CI/CD pipelines (GitLab/GitHub)
|
||||||
- ✅ Code formatting and linting
|
- ✅ Code formatting and linting
|
||||||
- ✅ Documentation structure
|
- ✅ Documentation structure
|
||||||
@@ -170,18 +174,27 @@ gitzone format --clean-backups
|
|||||||
- **Gitignore** - Repository ignore rules
|
- **Gitignore** - Repository ignore rules
|
||||||
- **Templates** - Project template updates
|
- **Templates** - Project template updates
|
||||||
- **Npmextra** - Extended npm configurations
|
- **Npmextra** - Extended npm configurations
|
||||||
|
- **Cleanup** - Removes obsolete files (yarn.lock, package-lock.json, tslint.json, etc.)
|
||||||
|
|
||||||
### 🔀 Semantic Commits & Versioning
|
### 🔀 Semantic Commits & Versioning
|
||||||
|
|
||||||
Create standardized commits that automatically handle versioning:
|
Create standardized commits with AI-powered suggestions that automatically handle versioning:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Interactive commit with AI recommendations
|
||||||
gitzone commit
|
gitzone commit
|
||||||
|
|
||||||
|
# Auto-accept AI recommendations
|
||||||
|
gitzone commit -y
|
||||||
|
|
||||||
|
# Auto-accept and push
|
||||||
|
gitzone commit -y -p
|
||||||
```
|
```
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
- 📝 Interactive commit message builder
|
- 🤖 **AI-powered analysis** - Analyzes your changes and suggests commit type, scope, and message
|
||||||
|
- 📝 Interactive commit message builder with smart defaults
|
||||||
- 🏷️ Automatic version bumping (major/minor/patch)
|
- 🏷️ Automatic version bumping (major/minor/patch)
|
||||||
- 📜 Changelog generation
|
- 📜 Changelog generation
|
||||||
- 🚀 Optional auto-push to origin
|
- 🚀 Optional auto-push to origin
|
||||||
@@ -189,11 +202,10 @@ Features:
|
|||||||
|
|
||||||
The commit wizard guides you through:
|
The commit wizard guides you through:
|
||||||
|
|
||||||
1. **Type selection** (feat/fix/docs/style/refactor/perf/test/chore)
|
1. **Type selection** (fix/feat/BREAKING CHANGE) with AI recommendation
|
||||||
2. **Scope definition** (component/module affected)
|
2. **Scope definition** (component/module affected)
|
||||||
3. **Description crafting**
|
3. **Description crafting**
|
||||||
4. **Breaking change detection**
|
4. **Version bump determination**
|
||||||
5. **Version bump determination**
|
|
||||||
|
|
||||||
### 🏗️ Meta Repository Management
|
### 🏗️ Meta Repository Management
|
||||||
|
|
||||||
@@ -443,7 +455,6 @@ gitzone services clean # ⚠️ Warning: deletes data
|
|||||||
|
|
||||||
- **TypeScript** - First-class support
|
- **TypeScript** - First-class support
|
||||||
- **Prettier** - Code formatting
|
- **Prettier** - Code formatting
|
||||||
- **ESLint** - Linting (via format modules)
|
|
||||||
- **npm/pnpm** - Package management
|
- **npm/pnpm** - Package management
|
||||||
- **MongoDB** - Local database service
|
- **MongoDB** - Local database service
|
||||||
- **MinIO** - S3-compatible object storage
|
- **MinIO** - S3-compatible object storage
|
||||||
@@ -522,19 +533,21 @@ gitzone is optimized for speed:
|
|||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This 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.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
**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.
|
**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.
|
||||||
|
|
||||||
### Trademarks
|
### Trademarks
|
||||||
|
|
||||||
This 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.
|
This 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 or third parties, 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 or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
### Company Information
|
### Company Information
|
||||||
|
|
||||||
Task Venture Capital GmbH
|
Task Venture Capital GmbH
|
||||||
Registered at District court Bremen HRB 35230 HB, Germany
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By 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.
|
By 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.
|
||||||
|
|||||||
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.0.0',
|
version: '2.7.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() {}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import type { TGitzoneProjectType } from './classes.gitzoneconfig.js';
|
|||||||
* the Project class is a tool to work with a gitzone project
|
* the Project class is a tool to work with a gitzone project
|
||||||
*/
|
*/
|
||||||
export class Project {
|
export class Project {
|
||||||
public static async fromCwd() {
|
public static async fromCwd(options: { requireProjectType?: boolean } = {}) {
|
||||||
const gitzoneConfig = await GitzoneConfig.fromCwd();
|
const gitzoneConfig = await GitzoneConfig.fromCwd();
|
||||||
const project = new Project(gitzoneConfig);
|
const project = new Project(gitzoneConfig);
|
||||||
if (!project.gitzoneConfig.data.projectType) {
|
const requireProjectType = options.requireProjectType ?? true;
|
||||||
|
if (requireProjectType && !project.gitzoneConfig.data.projectType) {
|
||||||
throw new Error('Please define a project type');
|
throw new Error('Please define a project type');
|
||||||
}
|
}
|
||||||
return project;
|
return project;
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ export let run = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle format with options
|
// Handle format with options
|
||||||
|
// Default is dry-mode, use --write/-w to apply changes
|
||||||
await modFormat.run({
|
await modFormat.run({
|
||||||
|
write: argvArg.write || argvArg.w,
|
||||||
dryRun: argvArg['dry-run'],
|
dryRun: argvArg['dry-run'],
|
||||||
yes: argvArg.yes,
|
yes: argvArg.yes,
|
||||||
planOnly: argvArg['plan-only'],
|
planOnly: argvArg['plan-only'],
|
||||||
@@ -131,6 +133,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,35 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print execution plan at the start
|
||||||
|
ui.printExecutionPlan({
|
||||||
|
autoAccept: !!(argvArg.y || argvArg.yes),
|
||||||
|
push: !!(argvArg.p || argvArg.push),
|
||||||
|
build: wantsBuild,
|
||||||
|
release: wantsRelease,
|
||||||
|
format: !!argvArg.format,
|
||||||
|
registries: releaseConfig?.getRegistries(),
|
||||||
|
});
|
||||||
|
|
||||||
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 +57,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 +83,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 +120,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 +148,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 +227,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 +302,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,17 @@ interface ICommitSummary {
|
|||||||
commitSha?: string;
|
commitSha?: string;
|
||||||
pushed: boolean;
|
pushed: boolean;
|
||||||
repoUrl?: string;
|
repoUrl?: string;
|
||||||
|
released?: boolean;
|
||||||
|
releasedRegistries?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IExecutionPlanOptions {
|
||||||
|
autoAccept: boolean;
|
||||||
|
push: boolean;
|
||||||
|
build: boolean;
|
||||||
|
release: boolean;
|
||||||
|
format: boolean;
|
||||||
|
registries?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRecommendation {
|
interface IRecommendation {
|
||||||
@@ -39,6 +50,54 @@ export function printHeader(title: string): void {
|
|||||||
console.log('');
|
console.log('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print execution plan at the start of commit
|
||||||
|
*/
|
||||||
|
export function printExecutionPlan(options: IExecutionPlanOptions): void {
|
||||||
|
console.log('');
|
||||||
|
console.log('╭───────────────────────────────────────────────────────────────╮');
|
||||||
|
console.log('│ gitzone commit - Execution Plan │');
|
||||||
|
console.log('╰───────────────────────────────────────────────────────────────╯');
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Show active options
|
||||||
|
console.log(' Options:');
|
||||||
|
console.log(` Auto-accept ${options.autoAccept ? '✓ enabled (-y)' : '○ interactive mode'}`);
|
||||||
|
console.log(` Push to remote ${options.push ? '✓ enabled (-p)' : '○ disabled'}`);
|
||||||
|
console.log(` Build & verify ${options.build ? '✓ enabled (-b)' : '○ disabled'}`);
|
||||||
|
console.log(` Release to npm ${options.release ? '✓ enabled (-r)' : '○ disabled'}`);
|
||||||
|
if (options.format) {
|
||||||
|
console.log(` Format first ✓ enabled (--format)`);
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
// Show steps
|
||||||
|
console.log(' Steps:');
|
||||||
|
let stepNum = 1;
|
||||||
|
if (options.format) {
|
||||||
|
console.log(` ${stepNum++}. Format project files`);
|
||||||
|
}
|
||||||
|
console.log(` ${stepNum++}. Analyze repository changes`);
|
||||||
|
console.log(` ${stepNum++}. Bake commit info into code`);
|
||||||
|
console.log(` ${stepNum++}. Generate changelog.md`);
|
||||||
|
console.log(` ${stepNum++}. Stage and commit files`);
|
||||||
|
console.log(` ${stepNum++}. Bump version`);
|
||||||
|
if (options.build) {
|
||||||
|
console.log(` ${stepNum++}. Run build`);
|
||||||
|
console.log(` ${stepNum++}. Verify clean working tree`);
|
||||||
|
}
|
||||||
|
if (options.push) {
|
||||||
|
console.log(` ${stepNum++}. Push to remote`);
|
||||||
|
}
|
||||||
|
if (options.release) {
|
||||||
|
console.log(` ${stepNum++}. Publish to npm registries`);
|
||||||
|
if (options.registries && options.registries.length > 0) {
|
||||||
|
options.registries.forEach(r => console.log(` → ${r}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a section with a border
|
* Print a section with a border
|
||||||
*/
|
*/
|
||||||
@@ -146,6 +205,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 +219,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
304
ts/mod_config/index.ts
Normal file
304
ts/mod_config/index.ts
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
// gitzone config - manage release registry configuration
|
||||||
|
|
||||||
|
import * as plugins from './mod.plugins.js';
|
||||||
|
import { ReleaseConfig } from './classes.releaseconfig.js';
|
||||||
|
import { runFormatter, type ICheckResult } from '../mod_format/index.js';
|
||||||
|
|
||||||
|
export { ReleaseConfig };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format npmextra.json with diff preview
|
||||||
|
* Shows diff first, asks for confirmation, then applies
|
||||||
|
*/
|
||||||
|
async function formatNpmextraWithDiff(): Promise<void> {
|
||||||
|
// Check for diffs first
|
||||||
|
const checkResult = await runFormatter('npmextra', {
|
||||||
|
checkOnly: true,
|
||||||
|
showDiff: true,
|
||||||
|
}) as ICheckResult | void;
|
||||||
|
|
||||||
|
if (checkResult && checkResult.hasDiff) {
|
||||||
|
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||||
|
'Apply formatting changes to npmextra.json?',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (shouldApply) {
|
||||||
|
await runFormatter('npmextra', { silent: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const run = async (argvArg: any) => {
|
||||||
|
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}`);
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
|
} 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}`);
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
|
} 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.');
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
|
} 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}`);
|
||||||
|
await formatNpmextraWithDiff();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import { FormatContext } from './classes.formatcontext.js';
|
import { FormatContext } from './classes.formatcontext.js';
|
||||||
import type { IPlannedChange } from './interfaces.format.js';
|
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
|
||||||
import { Project } from '../classes.project.js';
|
import { Project } from '../classes.project.js';
|
||||||
|
|
||||||
export abstract class BaseFormatter {
|
export abstract class BaseFormatter {
|
||||||
@@ -79,4 +79,94 @@ export abstract class BaseFormatter {
|
|||||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for diffs without applying changes
|
||||||
|
* Returns information about what would change
|
||||||
|
*/
|
||||||
|
async check(): Promise<ICheckResult> {
|
||||||
|
const changes = await this.analyze();
|
||||||
|
const diffs: ICheckResult['diffs'] = [];
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
// Skip generic changes that don't have actual content
|
||||||
|
if (change.path === '<various files>') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.type === 'modify' || change.type === 'create') {
|
||||||
|
// Read current content if file exists
|
||||||
|
let currentContent: string | undefined;
|
||||||
|
try {
|
||||||
|
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||||
|
} catch {
|
||||||
|
// File doesn't exist yet
|
||||||
|
currentContent = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newContent = change.content;
|
||||||
|
|
||||||
|
// Check if there's an actual diff
|
||||||
|
if (currentContent !== newContent && newContent !== undefined) {
|
||||||
|
diffs.push({
|
||||||
|
path: change.path,
|
||||||
|
type: change.type,
|
||||||
|
before: currentContent,
|
||||||
|
after: newContent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (change.type === 'delete') {
|
||||||
|
// Check if file exists before marking for deletion
|
||||||
|
try {
|
||||||
|
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||||
|
diffs.push({
|
||||||
|
path: change.path,
|
||||||
|
type: 'delete',
|
||||||
|
before: currentContent,
|
||||||
|
after: undefined,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// File doesn't exist, nothing to delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDiff: diffs.length > 0,
|
||||||
|
diffs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a single diff using smartdiff
|
||||||
|
*/
|
||||||
|
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
||||||
|
console.log(`\n--- ${diff.path}`);
|
||||||
|
if (diff.before && diff.after) {
|
||||||
|
console.log(plugins.smartdiff.formatLineDiffForConsole(diff.before, diff.after));
|
||||||
|
} else if (diff.after && !diff.before) {
|
||||||
|
console.log(' (new file)');
|
||||||
|
// Show first few lines of new content
|
||||||
|
const lines = diff.after.split('\n').slice(0, 10);
|
||||||
|
lines.forEach(line => console.log(` + ${line}`));
|
||||||
|
if (diff.after.split('\n').length > 10) {
|
||||||
|
console.log(' ... (truncated)');
|
||||||
|
}
|
||||||
|
} else if (diff.before && !diff.after) {
|
||||||
|
console.log(' (file will be deleted)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display all diffs from a check result
|
||||||
|
*/
|
||||||
|
displayAllDiffs(result: ICheckResult): void {
|
||||||
|
if (!result.hasDiff) {
|
||||||
|
console.log(' No changes detected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const diff of result.diffs) {
|
||||||
|
this.displayDiff(diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -154,10 +154,11 @@ export const run = async (projectArg: Project) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// check for dependencies
|
// check for dependencies
|
||||||
|
// Note: @push.rocks/tapbundle is deprecated - use @git.zone/tstest/tapbundle instead
|
||||||
await ensureDependency(
|
await ensureDependency(
|
||||||
packageJson,
|
packageJson,
|
||||||
'devDep',
|
'devDep',
|
||||||
'latest',
|
'exclude',
|
||||||
'@push.rocks/tapbundle',
|
'@push.rocks/tapbundle',
|
||||||
);
|
);
|
||||||
await ensureDependency(
|
await ensureDependency(
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ import { Project } from '../classes.project.js';
|
|||||||
export const run = async (project: Project) => {
|
export const run = async (project: Project) => {
|
||||||
const templateModule = await import('../mod_template/index.js');
|
const templateModule = await import('../mod_template/index.js');
|
||||||
|
|
||||||
// update tslint
|
|
||||||
// getting template
|
|
||||||
const tslintTemplate = await templateModule.getTemplate('tslint');
|
|
||||||
await tslintTemplate.writeToDisk(paths.cwd);
|
|
||||||
logger.log('info', 'Updated tslint.json!');
|
|
||||||
|
|
||||||
// update vscode
|
// update vscode
|
||||||
const vscodeTemplate = await templateModule.getTemplate('vscode');
|
const vscodeTemplate = await templateModule.getTemplate('vscode');
|
||||||
await vscodeTemplate.writeToDisk(paths.cwd);
|
await vscodeTemplate.writeToDisk(paths.cwd);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -18,7 +19,8 @@ import { CopyFormatter } from './formatters/copy.formatter.js';
|
|||||||
|
|
||||||
export let run = async (
|
export let run = async (
|
||||||
options: {
|
options: {
|
||||||
dryRun?: boolean;
|
write?: boolean; // Explicitly write changes (default: false, dry-mode)
|
||||||
|
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
planOnly?: boolean;
|
planOnly?: boolean;
|
||||||
savePlan?: string;
|
savePlan?: string;
|
||||||
@@ -34,14 +36,18 @@ export let run = async (
|
|||||||
setVerboseMode(true);
|
setVerboseMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await Project.fromCwd();
|
// Determine if we should write changes
|
||||||
|
// Default is dry-mode (no writing) unless --write/-w is specified
|
||||||
|
const shouldWrite = options.write ?? (options.dryRun === false);
|
||||||
|
|
||||||
|
const project = await Project.fromCwd({ requireProjectType: false });
|
||||||
const context = new FormatContext();
|
const context = new FormatContext();
|
||||||
// Cache system removed - no longer needed
|
// Cache system removed - no longer needed
|
||||||
const planner = new FormatPlanner();
|
const planner = new FormatPlanner();
|
||||||
|
|
||||||
// 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,
|
||||||
@@ -126,9 +132,9 @@ export let run = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dry-run mode
|
// Dry-run mode (default behavior)
|
||||||
if (options.dryRun) {
|
if (!shouldWrite) {
|
||||||
logger.log('info', 'Dry-run mode - no changes will be made');
|
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,3 +201,68 @@ export const handleCleanBackups = async (): Promise<void> => {
|
|||||||
'Backup cleaning has been disabled - backup system removed',
|
'Backup cleaning has been disabled - backup system removed',
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Import the ICheckResult type for external use
|
||||||
|
import type { ICheckResult } from './interfaces.format.js';
|
||||||
|
export type { ICheckResult };
|
||||||
|
|
||||||
|
// Formatters that don't require projectType to be set
|
||||||
|
const formattersNotRequiringProjectType = ['npmextra', 'prettier', 'cleanup', 'packagejson'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a single formatter by name (for use by other modules)
|
||||||
|
*/
|
||||||
|
export const runFormatter = async (
|
||||||
|
formatterName: string,
|
||||||
|
options: {
|
||||||
|
silent?: boolean;
|
||||||
|
checkOnly?: boolean; // Only check for diffs, don't apply
|
||||||
|
showDiff?: boolean; // Show the diff output
|
||||||
|
} = {}
|
||||||
|
): Promise<ICheckResult | void> => {
|
||||||
|
// Determine if this formatter requires projectType
|
||||||
|
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
||||||
|
const project = await Project.fromCwd({ requireProjectType });
|
||||||
|
const context = new FormatContext();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Check-only mode: just check for diffs and optionally display them
|
||||||
|
if (options.checkOnly) {
|
||||||
|
const result = await formatter.check();
|
||||||
|
if (result.hasDiff && options.showDiff) {
|
||||||
|
formatter.displayAllDiffs(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal mode: analyze and apply changes
|
||||||
|
const changes = await formatter.analyze();
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
await formatter.applyChange(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
|
logger.log('success', `Formatter '${formatterName}' completed`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -39,7 +39,18 @@ export type IPlannedChange = {
|
|||||||
path: string;
|
path: string;
|
||||||
module: string;
|
module: string;
|
||||||
description: string;
|
description: string;
|
||||||
content?: string; // For create/modify operations
|
content?: string; // New content for create/modify operations
|
||||||
|
originalContent?: string; // Original content for comparison
|
||||||
diff?: string;
|
diff?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ICheckResult {
|
||||||
|
hasDiff: boolean;
|
||||||
|
diffs: Array<{
|
||||||
|
path: string;
|
||||||
|
type: 'create' | 'modify' | 'delete';
|
||||||
|
before?: string;
|
||||||
|
after?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|||||||
190
ts/mod_services/classes.globalregistry.ts
Normal file
190
ts/mod_services/classes.globalregistry.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import { DockerContainer } from './classes.dockercontainer.js';
|
||||||
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
|
export interface IRegisteredProject {
|
||||||
|
projectPath: string;
|
||||||
|
projectName: string;
|
||||||
|
containers: {
|
||||||
|
mongo?: string;
|
||||||
|
minio?: string;
|
||||||
|
elasticsearch?: string;
|
||||||
|
};
|
||||||
|
ports: {
|
||||||
|
mongo?: number;
|
||||||
|
s3?: number;
|
||||||
|
s3Console?: number;
|
||||||
|
elasticsearch?: number;
|
||||||
|
};
|
||||||
|
enabledServices: string[];
|
||||||
|
lastActive: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGlobalRegistryData {
|
||||||
|
projects: { [projectPath: string]: IRegisteredProject };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GlobalRegistry {
|
||||||
|
private static instance: GlobalRegistry | null = null;
|
||||||
|
private kvStore: plugins.npmextra.KeyValueStore<IGlobalRegistryData>;
|
||||||
|
private docker: DockerContainer;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.kvStore = new plugins.npmextra.KeyValueStore({
|
||||||
|
typeArg: 'userHomeDir',
|
||||||
|
identityArg: 'gitzone-services',
|
||||||
|
});
|
||||||
|
this.docker = new DockerContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance
|
||||||
|
*/
|
||||||
|
public static getInstance(): GlobalRegistry {
|
||||||
|
if (!GlobalRegistry.instance) {
|
||||||
|
GlobalRegistry.instance = new GlobalRegistry();
|
||||||
|
}
|
||||||
|
return GlobalRegistry.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register or update a project in the global registry
|
||||||
|
*/
|
||||||
|
public async registerProject(data: Omit<IRegisteredProject, 'lastActive'>): Promise<void> {
|
||||||
|
const allData = await this.kvStore.readAll();
|
||||||
|
const projects = allData.projects || {};
|
||||||
|
|
||||||
|
projects[data.projectPath] = {
|
||||||
|
...data,
|
||||||
|
lastActive: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.kvStore.writeKey('projects', projects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a project from the registry
|
||||||
|
*/
|
||||||
|
public async unregisterProject(projectPath: string): Promise<void> {
|
||||||
|
const allData = await this.kvStore.readAll();
|
||||||
|
const projects = allData.projects || {};
|
||||||
|
|
||||||
|
if (projects[projectPath]) {
|
||||||
|
delete projects[projectPath];
|
||||||
|
await this.kvStore.writeKey('projects', projects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the lastActive timestamp for a project
|
||||||
|
*/
|
||||||
|
public async touchProject(projectPath: string): Promise<void> {
|
||||||
|
const allData = await this.kvStore.readAll();
|
||||||
|
const projects = allData.projects || {};
|
||||||
|
|
||||||
|
if (projects[projectPath]) {
|
||||||
|
projects[projectPath].lastActive = Date.now();
|
||||||
|
await this.kvStore.writeKey('projects', projects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered projects
|
||||||
|
*/
|
||||||
|
public async getAllProjects(): Promise<{ [path: string]: IRegisteredProject }> {
|
||||||
|
const allData = await this.kvStore.readAll();
|
||||||
|
return allData.projects || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a project is registered
|
||||||
|
*/
|
||||||
|
public async isRegistered(projectPath: string): Promise<boolean> {
|
||||||
|
const projects = await this.getAllProjects();
|
||||||
|
return !!projects[projectPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get status of all containers across all registered projects
|
||||||
|
*/
|
||||||
|
public async getGlobalStatus(): Promise<
|
||||||
|
Array<{
|
||||||
|
projectPath: string;
|
||||||
|
projectName: string;
|
||||||
|
containers: Array<{ name: string; status: string }>;
|
||||||
|
lastActive: number;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
const projects = await this.getAllProjects();
|
||||||
|
const result: Array<{
|
||||||
|
projectPath: string;
|
||||||
|
projectName: string;
|
||||||
|
containers: Array<{ name: string; status: string }>;
|
||||||
|
lastActive: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const [path, project] of Object.entries(projects)) {
|
||||||
|
const containerStatuses: Array<{ name: string; status: string }> = [];
|
||||||
|
|
||||||
|
for (const containerName of Object.values(project.containers)) {
|
||||||
|
if (containerName) {
|
||||||
|
const status = await this.docker.getStatus(containerName);
|
||||||
|
containerStatuses.push({ name: containerName, status });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
projectPath: path,
|
||||||
|
projectName: project.projectName,
|
||||||
|
containers: containerStatuses,
|
||||||
|
lastActive: project.lastActive,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop all containers across all registered projects
|
||||||
|
*/
|
||||||
|
public async stopAll(): Promise<{ stopped: string[]; failed: string[] }> {
|
||||||
|
const projects = await this.getAllProjects();
|
||||||
|
const stopped: string[] = [];
|
||||||
|
const failed: string[] = [];
|
||||||
|
|
||||||
|
for (const project of Object.values(projects)) {
|
||||||
|
for (const containerName of Object.values(project.containers)) {
|
||||||
|
if (containerName) {
|
||||||
|
const status = await this.docker.getStatus(containerName);
|
||||||
|
if (status === 'running') {
|
||||||
|
if (await this.docker.stop(containerName)) {
|
||||||
|
stopped.push(containerName);
|
||||||
|
} else {
|
||||||
|
failed.push(containerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { stopped, failed };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove stale registry entries (projects that no longer exist on disk)
|
||||||
|
*/
|
||||||
|
public async cleanup(): Promise<string[]> {
|
||||||
|
const projects = await this.getAllProjects();
|
||||||
|
const removed: string[] = [];
|
||||||
|
|
||||||
|
for (const projectPath of Object.keys(projects)) {
|
||||||
|
const exists = await plugins.smartfs.directory(projectPath).exists();
|
||||||
|
if (!exists) {
|
||||||
|
await this.unregisterProject(projectPath);
|
||||||
|
removed.push(projectPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,19 @@ import * as plugins from './mod.plugins.js';
|
|||||||
import * as helpers from './helpers.js';
|
import * as helpers from './helpers.js';
|
||||||
import { ServiceConfiguration } from './classes.serviceconfiguration.js';
|
import { ServiceConfiguration } from './classes.serviceconfiguration.js';
|
||||||
import { DockerContainer } from './classes.dockercontainer.js';
|
import { DockerContainer } from './classes.dockercontainer.js';
|
||||||
|
import { GlobalRegistry } from './classes.globalregistry.js';
|
||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
export class ServiceManager {
|
export class ServiceManager {
|
||||||
private config: ServiceConfiguration;
|
private config: ServiceConfiguration;
|
||||||
private docker: DockerContainer;
|
private docker: DockerContainer;
|
||||||
private enabledServices: string[] | null = null;
|
private enabledServices: string[] | null = null;
|
||||||
|
private globalRegistry: GlobalRegistry;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = new ServiceConfiguration();
|
this.config = new ServiceConfiguration();
|
||||||
this.docker = new DockerContainer();
|
this.docker = new DockerContainer();
|
||||||
|
this.globalRegistry = GlobalRegistry.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,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) {
|
||||||
@@ -81,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
|
||||||
@@ -107,6 +110,31 @@ export class ServiceManager {
|
|||||||
return this.enabledServices.includes(service);
|
return this.enabledServices.includes(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this project with the global registry
|
||||||
|
*/
|
||||||
|
private async registerWithGlobalRegistry(): Promise<void> {
|
||||||
|
const config = this.config.getConfig();
|
||||||
|
const containers = this.config.getContainerNames();
|
||||||
|
|
||||||
|
await this.globalRegistry.registerProject({
|
||||||
|
projectPath: process.cwd(),
|
||||||
|
projectName: config.PROJECT_NAME,
|
||||||
|
containers: {
|
||||||
|
mongo: containers.mongo,
|
||||||
|
minio: containers.minio,
|
||||||
|
elasticsearch: containers.elasticsearch,
|
||||||
|
},
|
||||||
|
ports: {
|
||||||
|
mongo: parseInt(config.MONGODB_PORT),
|
||||||
|
s3: parseInt(config.S3_PORT),
|
||||||
|
s3Console: parseInt(config.S3_CONSOLE_PORT),
|
||||||
|
elasticsearch: parseInt(config.ELASTICSEARCH_PORT),
|
||||||
|
},
|
||||||
|
enabledServices: this.enabledServices || ['mongodb', 'minio', 'elasticsearch'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start all enabled services
|
* Start all enabled services
|
||||||
*/
|
*/
|
||||||
@@ -127,6 +155,9 @@ export class ServiceManager {
|
|||||||
await this.startElasticsearch();
|
await this.startElasticsearch();
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register with global registry
|
||||||
|
await this.registerWithGlobalRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -808,6 +839,15 @@ export class ServiceManager {
|
|||||||
if (!removed) {
|
if (!removed) {
|
||||||
logger.log('note', ' No containers to remove');
|
logger.log('note', ' No containers to remove');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if all containers are gone, then unregister from global registry
|
||||||
|
const mongoExists = await this.docker.exists(containers.mongo);
|
||||||
|
const minioExists = await this.docker.exists(containers.minio);
|
||||||
|
const esExists = await this.docker.exists(containers.elasticsearch);
|
||||||
|
|
||||||
|
if (!mongoExists && !minioExists && !esExists) {
|
||||||
|
await this.globalRegistry.unregisterProject(process.cwd());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import * as helpers from './helpers.js';
|
import * as helpers from './helpers.js';
|
||||||
import { ServiceManager } from './classes.servicemanager.js';
|
import { ServiceManager } from './classes.servicemanager.js';
|
||||||
|
import { GlobalRegistry } from './classes.globalregistry.js';
|
||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
export const run = async (argvArg: any) => {
|
||||||
|
const isGlobal = argvArg.g || argvArg.global;
|
||||||
|
const command = argvArg._[1] || 'help';
|
||||||
|
|
||||||
|
// Handle global commands first
|
||||||
|
if (isGlobal) {
|
||||||
|
await handleGlobalCommand(command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local project commands
|
||||||
const serviceManager = new ServiceManager();
|
const serviceManager = new ServiceManager();
|
||||||
await serviceManager.init();
|
await serviceManager.init();
|
||||||
|
|
||||||
const command = argvArg._[1] || 'help';
|
|
||||||
const service = argvArg._[2] || 'all';
|
const service = argvArg._[2] || 'all';
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'start':
|
case 'start':
|
||||||
await handleStart(serviceManager, service);
|
await handleStart(serviceManager, service);
|
||||||
@@ -249,4 +259,175 @@ function showHelp() {
|
|||||||
logger.log('info', ' gitzone services config # Show configuration');
|
logger.log('info', ' gitzone services config # Show configuration');
|
||||||
logger.log('info', ' gitzone services compass # Get MongoDB Compass connection');
|
logger.log('info', ' gitzone services compass # Get MongoDB Compass connection');
|
||||||
logger.log('info', ' gitzone services logs elasticsearch # Show Elasticsearch logs');
|
logger.log('info', ' gitzone services logs elasticsearch # Show Elasticsearch logs');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
logger.log('note', 'Global Commands (-g/--global):');
|
||||||
|
logger.log('info', ' list -g List all registered projects');
|
||||||
|
logger.log('info', ' status -g Show status across all projects');
|
||||||
|
logger.log('info', ' stop -g Stop all containers across all projects');
|
||||||
|
logger.log('info', ' cleanup -g Remove stale registry entries');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
logger.log('note', 'Global Examples:');
|
||||||
|
logger.log('info', ' gitzone services list -g # List all registered projects');
|
||||||
|
logger.log('info', ' gitzone services status -g # Show global container status');
|
||||||
|
logger.log('info', ' gitzone services stop -g # Stop all (prompts for confirmation)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Global Command Handlers ====================
|
||||||
|
|
||||||
|
async function handleGlobalCommand(command: string) {
|
||||||
|
const globalRegistry = GlobalRegistry.getInstance();
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'list':
|
||||||
|
await handleGlobalList(globalRegistry);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
await handleGlobalStatus(globalRegistry);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'stop':
|
||||||
|
await handleGlobalStop(globalRegistry);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cleanup':
|
||||||
|
await handleGlobalCleanup(globalRegistry);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'help':
|
||||||
|
default:
|
||||||
|
showHelp();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGlobalList(globalRegistry: GlobalRegistry) {
|
||||||
|
helpers.printHeader('Registered Projects (Global)');
|
||||||
|
|
||||||
|
const projects = await globalRegistry.getAllProjects();
|
||||||
|
const projectPaths = Object.keys(projects);
|
||||||
|
|
||||||
|
if (projectPaths.length === 0) {
|
||||||
|
logger.log('note', 'No projects registered');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const path of projectPaths) {
|
||||||
|
const project = projects[path];
|
||||||
|
const lastActive = new Date(project.lastActive).toLocaleString();
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
logger.log('ok', `📁 ${project.projectName}`);
|
||||||
|
logger.log('info', ` Path: ${project.projectPath}`);
|
||||||
|
logger.log('info', ` Services: ${project.enabledServices.join(', ')}`);
|
||||||
|
logger.log('info', ` Last Active: ${lastActive}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGlobalStatus(globalRegistry: GlobalRegistry) {
|
||||||
|
helpers.printHeader('Global Service Status');
|
||||||
|
|
||||||
|
const statuses = await globalRegistry.getGlobalStatus();
|
||||||
|
|
||||||
|
if (statuses.length === 0) {
|
||||||
|
logger.log('note', 'No projects registered');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let runningCount = 0;
|
||||||
|
let totalContainers = 0;
|
||||||
|
|
||||||
|
for (const project of statuses) {
|
||||||
|
console.log();
|
||||||
|
logger.log('ok', `📁 ${project.projectName}`);
|
||||||
|
logger.log('info', ` Path: ${project.projectPath}`);
|
||||||
|
|
||||||
|
if (project.containers.length === 0) {
|
||||||
|
logger.log('note', ' No containers configured');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const container of project.containers) {
|
||||||
|
totalContainers++;
|
||||||
|
const statusIcon = container.status === 'running' ? '🟢' : container.status === 'exited' ? '🟡' : '⚪';
|
||||||
|
if (container.status === 'running') runningCount++;
|
||||||
|
logger.log('info', ` ${statusIcon} ${container.name}: ${container.status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
logger.log('note', `Summary: ${runningCount}/${totalContainers} containers running across ${statuses.length} project(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGlobalStop(globalRegistry: GlobalRegistry) {
|
||||||
|
helpers.printHeader('Stop All Containers (Global)');
|
||||||
|
|
||||||
|
const statuses = await globalRegistry.getGlobalStatus();
|
||||||
|
|
||||||
|
// Count running containers
|
||||||
|
let runningCount = 0;
|
||||||
|
for (const project of statuses) {
|
||||||
|
for (const container of project.containers) {
|
||||||
|
if (container.status === 'running') runningCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningCount === 0) {
|
||||||
|
logger.log('note', 'No running containers found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('note', `Found ${runningCount} running container(s) across ${statuses.length} project(s)`);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// Show what will be stopped
|
||||||
|
for (const project of statuses) {
|
||||||
|
const runningContainers = project.containers.filter(c => c.status === 'running');
|
||||||
|
if (runningContainers.length > 0) {
|
||||||
|
logger.log('info', `${project.projectName}:`);
|
||||||
|
for (const container of runningContainers) {
|
||||||
|
logger.log('info', ` • ${container.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||||
|
'Stop all containers?',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!shouldContinue) {
|
||||||
|
logger.log('note', 'Cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('note', 'Stopping all containers...');
|
||||||
|
const result = await globalRegistry.stopAll();
|
||||||
|
|
||||||
|
if (result.stopped.length > 0) {
|
||||||
|
logger.log('ok', `Stopped: ${result.stopped.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (result.failed.length > 0) {
|
||||||
|
logger.log('error', `Failed to stop: ${result.failed.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGlobalCleanup(globalRegistry: GlobalRegistry) {
|
||||||
|
helpers.printHeader('Cleanup Registry (Global)');
|
||||||
|
|
||||||
|
logger.log('note', 'Checking for stale registry entries...');
|
||||||
|
const removed = await globalRegistry.cleanup();
|
||||||
|
|
||||||
|
if (removed.length === 0) {
|
||||||
|
logger.log('ok', 'No stale entries found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('ok', `Removed ${removed.length} stale entr${removed.length === 1 ? 'y' : 'ies'}:`);
|
||||||
|
for (const path of removed) {
|
||||||
|
logger.log('info', ` • ${path}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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