Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ddf5023ecb | |||
| e1d28bc10a | |||
| 2f3d67f9e3 | |||
| 6304953234 | |||
| 8d84620bc4 | |||
| efd6f04e63 | |||
| 97ce9db28e | |||
| 362b4c106e | |||
| 3efe385952 | |||
| f6886f172d | |||
| 81d6273346 | |||
| 7e6cf5f046 | |||
| 89cf7dca04 | |||
| 9639a64437 | |||
| 48305ebb6a | |||
| 485c0a3855 | |||
| adc828d9bb | |||
| fff1d39338 | |||
| 5afbe6ccbc | |||
| 9de17a428d | |||
| c9985102c3 | |||
| 73f98c1c3f | |||
| ae93e6f146 | |||
| 2abaeee500 | |||
| 0538ba2586 | |||
| a451779724 | |||
| cd3246d659 | |||
| d37ffd7177 | |||
| a69b613087 | |||
| 1ea186d233 | |||
| f5e7d43cf3 | |||
| d80faa044a | |||
| 64062e5c43 | |||
| bd22844280 | |||
| 366c4a0bc2 | |||
| 0d3b10bd00 | |||
| a41e3d5d2c | |||
| c45cff89de | |||
| 7bb43ad478 | |||
| 8dcaf1c631 | |||
| 422761806d | |||
| 31360240a9 | |||
| e338ee584f | |||
| 31d2e18830 | |||
| a162ddabbb | |||
| 5dfa1d72aa | |||
| 7074a19a7f | |||
| 5774fb4da2 | |||
| be45ce765d | |||
| 2a250b8823 | |||
| 9a436cb4be |
@@ -19,4 +19,8 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
dist_*/
|
dist_*/
|
||||||
|
|
||||||
|
# AI
|
||||||
|
.claude/
|
||||||
|
.serena/
|
||||||
|
|
||||||
#------# custom
|
#------# custom
|
||||||
9
assets/templates/multienv/deno.json
Normal file
9
assets/templates/multienv/deno.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"target": "ES2022",
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
"nodeModulesDir": true
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"useDefineForClassFields": false,
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
|||||||
168
changelog.md
168
changelog.md
@@ -1,5 +1,173 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-27 - 2.0.0 - BREAKING CHANGE(core)
|
||||||
|
Migrate filesystem to smartfs (async) and add Elasticsearch service support; refactor format/commit/meta modules
|
||||||
|
|
||||||
|
- Replace @push.rocks/smartfile usage with @push.rocks/smartfs across the codebase; all filesystem operations are now async (SmartFs.file(...).read()/write(), SmartFs.directory(...).list()/create()/delete(), etc.)
|
||||||
|
- Convert formerly synchronous helpers and APIs to async (notable: detectProjectType, getProjectName, readCurrentVersion and related version bumping logic). Callers updated accordingly.
|
||||||
|
- Add Elasticsearch support to services: new config fields (ELASTICSEARCH_*), Docker run/start/stop/logs/status handling, and ELASTICSEARCH_URL in service configuration.
|
||||||
|
- Refactor formatting subsystem: cache and rollback/backup systems removed/disabled for stability, format planner execution simplified (sequential), diff/stats reporting updated to use smartfs.
|
||||||
|
- Update package.json dependencies: bump @git.zone/tsbuild, tsrun, tstest; upgrade @push.rocks/smartfile to v13 and add @push.rocks/smartfs dependency; update @types/node.
|
||||||
|
- Update commit flow and changelog generation to use smartfs for reading/writing files and to await version/branch detection where necessary.
|
||||||
|
- Expose a SmartFs instance via plugins and adjust all mod.* plugin files to import/use smartfs where required.
|
||||||
|
- Breaking change: Public and internal APIs that previously used synchronous smartfile APIs are now asynchronous. Consumers and scripts must await these functions and use the new smartfs API.
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.5 - fix(tsconfig)
|
||||||
|
Remove emitDecoratorMetadata from tsconfig template
|
||||||
|
|
||||||
|
- Removed the "emitDecoratorMetadata" compiler option from assets/templates/tsconfig_update/tsconfig.json
|
||||||
|
- This updates the tsconfig template to avoid emitting decorator metadata when targeting ES2022
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.4 - fix(tsconfig template)
|
||||||
|
Remove experimentalDecorators and useDefineForClassFields from tsconfig template
|
||||||
|
|
||||||
|
- Removed experimentalDecorators option from assets/templates/tsconfig_update/tsconfig.json
|
||||||
|
- Removed useDefineForClassFields option from assets/templates/tsconfig_update/tsconfig.json
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.3 - fix(assets/templates/multienv)
|
||||||
|
Remove unused Bun configuration template (assets/templates/multienv/bunfig.toml)
|
||||||
|
|
||||||
|
- Deleted assets/templates/multienv/bunfig.toml which previously provided Bun TypeScript decorator configuration
|
||||||
|
- Cleans up stale/unused template to avoid shipping obsolete Bun config
|
||||||
|
- No functional code changes; removes an unused asset file
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.2 - fix(templates/multienv)
|
||||||
|
Disable useDefineForClassFields in multienv TypeScript configs to ensure decorator compatibility
|
||||||
|
|
||||||
|
- Set useDefineForClassFields = false in assets/templates/multienv/bunfig.toml to keep Bun's transpiler compatible with decorator usage
|
||||||
|
- Set "useDefineForClassFields": false in assets/templates/multienv/deno.json to ensure Deno/TypeScript compiler emits class fields compatible with decorators
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.1 - fix(templates.multienv)
|
||||||
|
Enable checkJs in multienv Deno template to enable JS type checking
|
||||||
|
|
||||||
|
- Added "checkJs": true to compilerOptions in assets/templates/multienv/deno.json to enable JavaScript type checking for the Deno multienv template
|
||||||
|
|
||||||
|
## 2025-11-17 - 1.21.0 - feat(multienv)
|
||||||
|
Add multi-env templates enabling TypeScript decorators for Bun and Deno; rename npmextra config key to szci
|
||||||
|
|
||||||
|
- Added assets/templates/multienv/bunfig.toml to enable Bun TypeScript transpiler experimentalDecorators
|
||||||
|
- Added assets/templates/multienv/deno.json with experimentalDecorators, lib and target set for ES2022
|
||||||
|
- Updated npmextra.json: renamed top-level config key from "npmci" to "szci" (keeps npmGlobalTools, npmAccessLevel and npmRegistryUrl unchanged)
|
||||||
|
|
||||||
|
## 2025-11-06 - 1.20.0 - feat(commit)
|
||||||
|
Add non-interactive --yes (-y) flag to commit command to auto-accept AI recommendations and optionally push with -p
|
||||||
|
|
||||||
|
- Add -y / --yes flag to gitzone commit to auto-accept AI-generated commit recommendations without interactive prompts
|
||||||
|
- Support -yp or -y -p combinations to auto-accept and push to origin; -p / --push remains the separate control for pushing
|
||||||
|
- Implementation creates a smartinteract AnswerBucket programmatically when -y is used and populates commitType, commitScope, commitDescription and pushToOrigin
|
||||||
|
- Preserves existing UI output and interactive flow when -y is not used; fully backward compatible and CI/CD friendly
|
||||||
|
- Updated CLI usage and documentation (readme.hints.md) to document the new flags
|
||||||
|
|
||||||
|
## 2025-11-05 - 1.19.9 - fix(mod_commit)
|
||||||
|
Refactor version bumping to a unified implementation for npm and Deno; remove npm-exec based helpers and add file-based version readers/updaters to avoid npm warning pollution
|
||||||
|
|
||||||
|
- Removed legacy npm/deno-specific helpers (bumpNpmVersion, syncVersionToDenoJson, bumpDenoVersion) that relied on executing npm and caused warning pollution
|
||||||
|
- Added readCurrentVersion() to read version from package.json or deno.json
|
||||||
|
- Added updateVersionFile() helper to write version directly into JSON files
|
||||||
|
- Added unified bumpProjectVersion() that handles npm, deno and both with a single code path; reuses calculateNewVersion()
|
||||||
|
- Stages updated files, commits v<newVersion> and creates a tag v<newVersion>
|
||||||
|
- Benefits: no npm warning pollution in deno.json, simpler git history, consistent behavior across project types
|
||||||
|
|
||||||
|
## 2025-11-04 - 1.19.8 - fix(package.json)
|
||||||
|
Bump @git.zone/tsdoc dependency to ^1.9.2
|
||||||
|
|
||||||
|
- Updated dependency @git.zone/tsdoc from ^1.9.1 to ^1.9.2 in package.json
|
||||||
|
|
||||||
|
## 2025-11-04 - 1.19.7 - fix(dependencies)
|
||||||
|
Bump @git.zone/tsdoc to ^1.9.1
|
||||||
|
|
||||||
|
- Updated package.json dependency @git.zone/tsdoc from ^1.9.0 to ^1.9.1
|
||||||
|
|
||||||
|
## 2025-11-04 - 1.19.6 - fix(cli)
|
||||||
|
Bump @git.zone/tsdoc dependency to ^1.9.0
|
||||||
|
|
||||||
|
- Updated dependency @git.zone/tsdoc from ^1.8.3 to ^1.9.0 in package.json
|
||||||
|
|
||||||
|
## 2025-11-04 - 1.19.5 - fix(cli)
|
||||||
|
Bump @git.zone/tsdoc to ^1.8.3 and add local .claude settings for allowed permissions
|
||||||
|
|
||||||
|
- Updated dependency @git.zone/tsdoc from ^1.8.2 to ^1.8.3
|
||||||
|
- Added .claude/settings.local.json to declare allowed permissions for local tooling (Bash commands, Docker, npm, WebFetch and MCP actions)
|
||||||
|
|
||||||
|
## 2025-11-03 - 1.19.3 - fix(tsdoc)
|
||||||
|
Bump @git.zone/tsdoc to ^1.8.0 and add .claude local settings
|
||||||
|
|
||||||
|
- Upgrade dependency @git.zone/tsdoc from ^1.6.1 to ^1.8.0 in package.json
|
||||||
|
- Add .claude/settings.local.json for local assistant permissions/configuration
|
||||||
|
|
||||||
|
## 2025-11-03 - 1.19.2 - fix(tsdoc)
|
||||||
|
Bump @git.zone/tsdoc to ^1.6.1 and add .claude/settings.local.json
|
||||||
|
|
||||||
|
- Update dependency @git.zone/tsdoc from ^1.6.0 to ^1.6.1
|
||||||
|
- Add .claude/settings.local.json to include local Claude settings/permissions
|
||||||
|
|
||||||
|
## 2025-11-02 - 1.19.1 - fix(dependencies)
|
||||||
|
Bump dependencies and add local Claude settings
|
||||||
|
|
||||||
|
- Bump devDependencies: @git.zone/tsbuild -> ^2.7.1, @git.zone/tsrun -> ^1.6.2, @git.zone/tstest -> ^2.7.0
|
||||||
|
- Upgrade runtime dependencies: @git.zone/tsdoc -> ^1.6.0; update @push.rocks packages (smartcli ^4.0.19, smartjson ^5.2.0, smartlog ^3.1.10, smartnetwork ^4.4.0, etc.)
|
||||||
|
- Add .claude/settings.local.json (local project permissions/settings file)
|
||||||
|
|
||||||
|
## 2025-10-23 - 1.19.0 - feat(mod_commit)
|
||||||
|
Add CLI UI helpers and improve commit workflow with progress, recommendations and summary
|
||||||
|
|
||||||
|
- Introduce ts/mod_commit/mod.ui.ts: reusable CLI UI helpers (pretty headers, sections, AI recommendation box, step printer, commit summary and helpers for consistent messaging).
|
||||||
|
- Refactor ts/mod_commit/index.ts: use new UI functions to display AI recommendations, show step-by-step progress for baking commit info, generating changelog, staging, committing, bumping version and optional push; include commit SHA in final summary.
|
||||||
|
- Enhance ts/mod_commit/mod.helpers.ts: bumpProjectVersion now accepts currentStep/totalSteps to report progress and returns a consistent newVersion after handling npm/deno/both cases.
|
||||||
|
- Add .claude/settings.local.json: local permissions configuration for development tooling.
|
||||||
|
|
||||||
|
## 2025-10-23 - 1.18.9 - fix(mod_commit)
|
||||||
|
Stage and commit deno.json when bumping/syncing versions and create/update git tags
|
||||||
|
|
||||||
|
- bumpDenoVersion now creates a Smartshell instance and runs git add deno.json, git commit -m "v<newVersion>", and git tag v<newVersion> to persist the version bump
|
||||||
|
- syncVersionToDenoJson now stages deno.json, amends the npm version commit with --no-edit, and recreates the tag with -fa to keep package.json and deno.json in sync
|
||||||
|
- Added informative logger messages after creating commits and tags
|
||||||
|
|
||||||
|
## 2025-10-23 - 1.18.8 - fix(mod_commit)
|
||||||
|
Improve commit workflow: detect project type and current branch; add robust version bump helpers for npm/deno
|
||||||
|
|
||||||
|
- Add mod_commit/mod.helpers.ts with utilities: detectCurrentBranch(), detectProjectType(), bumpProjectVersion(), bumpDenoVersion(), bumpNpmVersion(), syncVersionToDenoJson(), and calculateNewVersion()
|
||||||
|
- Refactor ts/mod_commit/index.ts to use the new helpers: bumpProjectVersion(projectType, ... ) instead of a hard npm version call and push the actual current branch instead of hardcoding 'master'
|
||||||
|
- Support bumping versions for npm-only, deno-only, and hybrid (both) projects and synchronize versions from package.json to deno.json when applicable
|
||||||
|
- Improve branch detection with a fallback to 'master' and informative logging on detection failures
|
||||||
|
- Add local Claude settings file (.claude/settings.local.json) (editor/CI config) — no code behavior change but included in diff
|
||||||
|
|
||||||
|
## 2025-09-07 - 1.18.7 - fix(claude)
|
||||||
|
Add .claude local settings to whitelist dev tool permissions
|
||||||
|
|
||||||
|
- Add .claude/settings.local.json to configure allowed permissions for local AI/tooling helpers (Bash commands, WebFetch, and mcp_serena actions).
|
||||||
|
- Disable enableAllProjectMcpServers (set to false) to limit automatic project MCP server usage.
|
||||||
|
|
||||||
|
## 2025-09-07 - 1.18.6 - fix(deps)
|
||||||
|
Bump dependency versions and add local Claude settings
|
||||||
|
|
||||||
|
- Updated devDependencies: @git.zone/tsbuild ^2.6.4 → ^2.6.8, @git.zone/tstest ^2.3.4 → ^2.3.6, @push.rocks/smartfile ^11.2.5 → ^11.2.7
|
||||||
|
- Updated dependencies: @git.zone/tsdoc ^1.5.1 → ^1.5.2, @git.zone/tspublish ^1.10.1 → ^1.10.3, @push.rocks/smartlog ^3.1.8 → ^3.1.9, @push.rocks/smartnpm ^2.0.4 → ^2.0.6, @push.rocks/smartscaf ^4.0.17 → ^4.0.19
|
||||||
|
- Added .claude/settings.local.json to configure local Claude permissions/settings
|
||||||
|
|
||||||
|
## 2025-08-17 - 1.18.5 - fix(dependencies)
|
||||||
|
Bump smartshell and smartscaf versions; add .claude local settings
|
||||||
|
|
||||||
|
- Update @push.rocks/smartshell from ^3.2.4 to ^3.3.0 in package.json
|
||||||
|
- Update @push.rocks/smartscaf from ^4.0.16 to ^4.0.17 in package.json
|
||||||
|
- Add .claude/settings.local.json for local assistant permissions/configuration
|
||||||
|
|
||||||
|
## 2025-08-17 - 1.18.4 - fix(cli)
|
||||||
|
Update dependencies, add local Claude settings, and update gitignore template
|
||||||
|
|
||||||
|
- Bump several dependencies: @git.zone/tsbuild -> ^2.6.4, @git.zone/tspublish -> ^1.10.1, @git.zone/tstest -> ^2.3.4, @push.rocks/smartfile -> ^11.2.5, @push.rocks/npmextra -> ^5.3.3, @push.rocks/smartchok -> ^1.1.1, @push.rocks/smartlog -> ^3.1.8, @push.rocks/smartpath -> ^6.0.0, prettier -> ^3.6.2
|
||||||
|
- Add .claude/settings.local.json with local permissions configuration for AI tooling
|
||||||
|
- Update assets/templates/gitignore to ignore .claude/ and .serena/ directories
|
||||||
|
- Add pnpm onlyBuiltDependencies entries: esbuild and mongodb-memory-server
|
||||||
|
|
||||||
|
## 2025-08-16 - 1.18.3 - fix(services)
|
||||||
|
Simplify S3 endpoint handling in ServiceConfiguration to store host only
|
||||||
|
|
||||||
|
- S3_ENDPOINT now stores the raw host (e.g. 'localhost') instead of a full URL with protocol and port.
|
||||||
|
- Default .nogit/env.json creation uses the host-only S3_ENDPOINT.
|
||||||
|
- Sync/update logic (when syncing with Docker or reconfiguring ports) sets S3_ENDPOINT to the host only.
|
||||||
|
- Consumers that previously relied on S3_ENDPOINT containing protocol and port should now construct the full endpoint URL using S3_USESSL, S3_HOST and S3_PORT.
|
||||||
|
|
||||||
## 2025-08-16 - 1.18.1 - fix(services)
|
## 2025-08-16 - 1.18.1 - fix(services)
|
||||||
Improve services and commit flow: stop AiDoc, use silent docker inspect, sync ports with logging, fix config loading, and bump deps
|
Improve services and commit flow: stop AiDoc, use silent docker inspect, sync ports with logging, fix config loading, and bump deps
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"npmci": {
|
"szci": {
|
||||||
"npmGlobalTools": [],
|
"npmGlobalTools": [],
|
||||||
"npmAccessLevel": "private",
|
"npmAccessLevel": "private",
|
||||||
"npmRegistryUrl": "verdaccio.lossless.one"
|
"npmRegistryUrl": "verdaccio.lossless.one"
|
||||||
|
|||||||
41
package.json
41
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.18.1",
|
"version": "2.0.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",
|
||||||
@@ -57,45 +57,46 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.3.2",
|
"@git.zone/tsbuild": "^3.1.0",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^2.0.0",
|
||||||
"@git.zone/tstest": "^1.0.96",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartfile": "^11.2.0",
|
|
||||||
"@push.rocks/smartinteract": "^2.0.16",
|
"@push.rocks/smartinteract": "^2.0.16",
|
||||||
"@push.rocks/smartnetwork": "^4.1.2",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartshell": "^3.2.4",
|
"@push.rocks/smartshell": "^3.3.0",
|
||||||
"@types/node": "^22.15.18"
|
"@types/node": "^24.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git.zone/tsdoc": "^1.5.1",
|
"@git.zone/tsdoc": "^1.9.2",
|
||||||
"@git.zone/tspublish": "^1.9.1",
|
"@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",
|
||||||
"@push.rocks/gulp-function": "^3.0.7",
|
"@push.rocks/gulp-function": "^3.0.7",
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@push.rocks/npmextra": "^5.1.2",
|
"@push.rocks/npmextra": "^5.3.3",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/smartchok": "^1.0.34",
|
"@push.rocks/smartchok": "^1.1.1",
|
||||||
"@push.rocks/smartcli": "^4.0.11",
|
"@push.rocks/smartcli": "^4.0.19",
|
||||||
"@push.rocks/smartdiff": "^1.0.3",
|
"@push.rocks/smartdiff": "^1.0.3",
|
||||||
|
"@push.rocks/smartfile": "^13.0.1",
|
||||||
|
"@push.rocks/smartfs": "^1.1.0",
|
||||||
"@push.rocks/smartgulp": "^3.0.4",
|
"@push.rocks/smartgulp": "^3.0.4",
|
||||||
"@push.rocks/smartjson": "^5.0.20",
|
"@push.rocks/smartjson": "^5.2.0",
|
||||||
"@push.rocks/smartlegal": "^1.0.27",
|
"@push.rocks/smartlegal": "^1.0.27",
|
||||||
"@push.rocks/smartlog": "^3.0.9",
|
"@push.rocks/smartlog": "^3.1.10",
|
||||||
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
||||||
"@push.rocks/smartmustache": "^3.0.2",
|
"@push.rocks/smartmustache": "^3.0.2",
|
||||||
"@push.rocks/smartnpm": "^2.0.4",
|
"@push.rocks/smartnpm": "^2.0.6",
|
||||||
"@push.rocks/smartobject": "^1.0.12",
|
"@push.rocks/smartobject": "^1.0.12",
|
||||||
"@push.rocks/smartopen": "^2.0.0",
|
"@push.rocks/smartopen": "^2.0.0",
|
||||||
"@push.rocks/smartpath": "^5.0.18",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartscaf": "^4.0.16",
|
"@push.rocks/smartscaf": "^4.0.19",
|
||||||
"@push.rocks/smartstream": "^3.2.5",
|
"@push.rocks/smartstream": "^3.2.5",
|
||||||
"@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.5.3",
|
"prettier": "^3.6.2",
|
||||||
"through2": "^4.0.2"
|
"through2": "^4.0.2"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -116,6 +117,8 @@
|
|||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {},
|
"overrides": {},
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"mongodb-memory-server",
|
||||||
"puppeteer",
|
"puppeteer",
|
||||||
"sharp"
|
"sharp"
|
||||||
]
|
]
|
||||||
|
|||||||
9951
pnpm-lock.yaml
generated
9951
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
113
readme.hints.md
113
readme.hints.md
@@ -89,6 +89,41 @@ The format module is responsible for project standardization:
|
|||||||
5. **Performance Optimizations**: Parallel execution and caching
|
5. **Performance Optimizations**: Parallel execution and caching
|
||||||
6. **Reporting**: Diff views, statistics, verbose logging
|
6. **Reporting**: Diff views, statistics, verbose logging
|
||||||
7. **Architecture**: Clean separation of concerns with new classes
|
7. **Architecture**: Clean separation of concerns with new classes
|
||||||
|
8. **Unified Version Bumping**: Self-managed version updates eliminating npm warning pollution in deno.json
|
||||||
|
|
||||||
|
### Version Bumping Refactor (Latest)
|
||||||
|
|
||||||
|
The commit module's version bumping has been refactored to eliminate npm command dependencies:
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Removed `bumpNpmVersion()` - was causing npm warnings to pollute deno.json
|
||||||
|
- Removed `syncVersionToDenoJson()` - no longer needed with unified approach
|
||||||
|
- Removed separate `bumpDenoVersion()` - replaced by unified implementation
|
||||||
|
- Added `readCurrentVersion()` helper - reads from either package.json or deno.json
|
||||||
|
- Added `updateVersionFile()` helper - updates JSON files directly
|
||||||
|
- Unified `bumpProjectVersion()` - handles npm/deno/both with single clean code path
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- No npm warning pollution in version fields
|
||||||
|
- Full control over version bumping process
|
||||||
|
- Simpler git history (no amending, no force-tagging)
|
||||||
|
- Same code path for all project types
|
||||||
|
- Reuses existing `calculateNewVersion()` function
|
||||||
|
|
||||||
|
### Auto-Accept Flag for Commits
|
||||||
|
|
||||||
|
The commit module now supports `-y/--yes` flag for non-interactive commits:
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- `gitzone commit -y` - Auto-accepts AI recommendations without prompts
|
||||||
|
- `gitzone commit -yp` - Auto-accepts and pushes to origin
|
||||||
|
- Separate `-p/--push` flag controls push behavior
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Creates AnswerBucket programmatically when `-y` flag detected
|
||||||
|
- Preserves all UI output for transparency
|
||||||
|
- Fully backward compatible with interactive mode
|
||||||
|
- CI/CD friendly for automated workflows
|
||||||
|
|
||||||
## Development Tips
|
## Development Tips
|
||||||
|
|
||||||
@@ -137,6 +172,27 @@ The format module is responsible for project standardization:
|
|||||||
|
|
||||||
## CLI Usage
|
## CLI Usage
|
||||||
|
|
||||||
|
### Commit Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive commit (default)
|
||||||
|
gitzone commit
|
||||||
|
|
||||||
|
# Auto-accept AI recommendations (no prompts)
|
||||||
|
gitzone commit -y
|
||||||
|
gitzone commit --yes
|
||||||
|
|
||||||
|
# Auto-accept and push to origin
|
||||||
|
gitzone commit -yp
|
||||||
|
gitzone commit -y -p
|
||||||
|
gitzone commit --yes --push
|
||||||
|
|
||||||
|
# Run format before commit
|
||||||
|
gitzone commit --format
|
||||||
|
```
|
||||||
|
|
||||||
|
### Format Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Basic format
|
# Basic format
|
||||||
gitzone format
|
gitzone format
|
||||||
@@ -187,7 +243,60 @@ gitzone format --clean-backups
|
|||||||
|
|
||||||
## API Changes
|
## API Changes
|
||||||
|
|
||||||
- smartfile API updated to use fs._ and memory._ namespaces
|
### Smartfile v13 Migration (Latest - Completed)
|
||||||
|
|
||||||
|
The project has been fully migrated from @push.rocks/smartfile v11 to v13, which introduced a major breaking change where filesystem operations were split into two separate packages:
|
||||||
|
|
||||||
|
**Packages:**
|
||||||
|
- `@push.rocks/smartfile` v13.0.1 - File representation classes (SmartFile, StreamFile, VirtualDirectory)
|
||||||
|
- `@push.rocks/smartfs` v1.1.0 - Filesystem operations (read, write, exists, stat, etc.)
|
||||||
|
|
||||||
|
**Key API Changes:**
|
||||||
|
1. **File Reading**:
|
||||||
|
- Old: `plugins.smartfile.fs.toStringSync(path)` or `plugins.smartfile.fs.toObjectSync(path)`
|
||||||
|
- New: `await plugins.smartfs.file(path).encoding('utf8').read()` + JSON.parse if needed
|
||||||
|
- Important: `read()` returns `string | Buffer` - use `as string` type assertion when encoding is set
|
||||||
|
|
||||||
|
2. **File Writing**:
|
||||||
|
- Old: `plugins.smartfile.memory.toFs(content, path)` or `plugins.smartfile.memory.toFsSync(content, path)`
|
||||||
|
- New: `await plugins.smartfs.file(path).encoding('utf8').write(content)`
|
||||||
|
|
||||||
|
3. **File Existence**:
|
||||||
|
- Old: `plugins.smartfile.fs.fileExists(path)` or `plugins.smartfile.fs.fileExistsSync(path)`
|
||||||
|
- New: `await plugins.smartfs.file(path).exists()`
|
||||||
|
|
||||||
|
4. **Directory Operations**:
|
||||||
|
- Old: `plugins.smartfile.fs.ensureDir(path)`
|
||||||
|
- New: `await plugins.smartfs.directory(path).recursive().create()`
|
||||||
|
- Old: `plugins.smartfile.fs.remove(path)`
|
||||||
|
- New: `await plugins.smartfs.directory(path).recursive().delete()` or `await plugins.smartfs.file(path).delete()`
|
||||||
|
|
||||||
|
5. **Directory Listing**:
|
||||||
|
- Old: `plugins.smartfile.fs.listFolders(path)` or `plugins.smartfile.fs.listFoldersSync(path)`
|
||||||
|
- New: `await plugins.smartfs.directory(path).list()` then filter by `stats.isDirectory`
|
||||||
|
- Note: `list()` returns `IDirectoryEntry[]` with `path` and `name` properties - use `stat()` to check if directory
|
||||||
|
|
||||||
|
6. **File Stats**:
|
||||||
|
- Old: `stats.isDirectory()` (method)
|
||||||
|
- New: `stats.isDirectory` (boolean property)
|
||||||
|
- Old: `stats.mtimeMs`
|
||||||
|
- New: `stats.mtime.getTime()`
|
||||||
|
|
||||||
|
7. **SmartFile Factory**:
|
||||||
|
- Old: Direct SmartFile instantiation
|
||||||
|
- New: `plugins.smartfile.SmartFileFactory.nodeFs()` then factory methods
|
||||||
|
|
||||||
|
**Migration Pattern:**
|
||||||
|
All sync methods must become async. Functions that were previously synchronous (like `getProjectName()`) now return `Promise<T>` and must be awaited.
|
||||||
|
|
||||||
|
**Affected Modules:**
|
||||||
|
- ts/mod_format/* (largest area - 15+ files)
|
||||||
|
- ts/mod_commit/* (version bumping)
|
||||||
|
- ts/mod_services/* (configuration management)
|
||||||
|
- ts/mod_meta/* (meta repository management)
|
||||||
|
- ts/mod_standard/* (template listing)
|
||||||
|
- ts/mod_template/* (template operations)
|
||||||
|
|
||||||
|
**Previous API Changes:**
|
||||||
- smartnpm requires instance creation: `new NpmRegistry()`
|
- smartnpm requires instance creation: `new NpmRegistry()`
|
||||||
- All file operations now use updated APIs
|
|
||||||
- Type imports use `import type` for proper verbatim module syntax
|
- Type imports use `import type` for proper verbatim module syntax
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '1.18.1',
|
version: '2.0.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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import * as paths from '../paths.js';
|
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 ui from './mod.ui.js';
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
export const run = async (argvArg: any) => {
|
||||||
if (argvArg.format) {
|
if (argvArg.format) {
|
||||||
@@ -10,7 +12,8 @@ export const run = async (argvArg: any) => {
|
|||||||
await formatMod.run();
|
await formatMod.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('info', `gathering facts...`);
|
ui.printHeader('🔍 Analyzing repository changes...');
|
||||||
|
|
||||||
const aidoc = new plugins.tsdoc.AiDoc();
|
const aidoc = new plugins.tsdoc.AiDoc();
|
||||||
await aidoc.start();
|
await aidoc.start();
|
||||||
|
|
||||||
@@ -18,16 +21,39 @@ export const run = async (argvArg: any) => {
|
|||||||
|
|
||||||
await aidoc.stop();
|
await aidoc.stop();
|
||||||
|
|
||||||
logger.log(
|
ui.printRecommendation({
|
||||||
'info',
|
recommendedNextVersion: nextCommitObject.recommendedNextVersion,
|
||||||
`---------
|
recommendedNextVersionLevel: nextCommitObject.recommendedNextVersionLevel,
|
||||||
Next recommended commit would be:
|
recommendedNextVersionScope: nextCommitObject.recommendedNextVersionScope,
|
||||||
===========
|
recommendedNextVersionMessage: nextCommitObject.recommendedNextVersionMessage,
|
||||||
-> ${nextCommitObject.recommendedNextVersion}:
|
});
|
||||||
-> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
|
|
||||||
===========
|
let answerBucket: plugins.smartinteract.AnswerBucket;
|
||||||
`,
|
|
||||||
);
|
// Check if -y or --yes flag is set to auto-accept recommendations
|
||||||
|
if (argvArg.y || argvArg.yes) {
|
||||||
|
// Auto-mode: create AnswerBucket programmatically
|
||||||
|
logger.log('info', '✓ Auto-accepting AI recommendations (--yes flag)');
|
||||||
|
|
||||||
|
answerBucket = new plugins.smartinteract.AnswerBucket();
|
||||||
|
answerBucket.addAnswer({
|
||||||
|
name: 'commitType',
|
||||||
|
value: nextCommitObject.recommendedNextVersionLevel,
|
||||||
|
});
|
||||||
|
answerBucket.addAnswer({
|
||||||
|
name: 'commitScope',
|
||||||
|
value: nextCommitObject.recommendedNextVersionScope,
|
||||||
|
});
|
||||||
|
answerBucket.addAnswer({
|
||||||
|
name: 'commitDescription',
|
||||||
|
value: nextCommitObject.recommendedNextVersionMessage,
|
||||||
|
});
|
||||||
|
answerBucket.addAnswer({
|
||||||
|
name: 'pushToOrigin',
|
||||||
|
value: !!(argvArg.p || argvArg.push), // Only push if -p flag also provided
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Interactive mode: prompt user for input
|
||||||
const commitInteract = new plugins.smartinteract.SmartInteract();
|
const commitInteract = new plugins.smartinteract.SmartInteract();
|
||||||
commitInteract.addQuestions([
|
commitInteract.addQuestions([
|
||||||
{
|
{
|
||||||
@@ -56,7 +82,8 @@ export const run = async (argvArg: any) => {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const answerBucket = await commitInteract.runQueue();
|
answerBucket = await commitInteract.runQueue();
|
||||||
|
}
|
||||||
const commitString = createCommitStringFromAnswerBucket(answerBucket);
|
const commitString = createCommitStringFromAnswerBucket(answerBucket);
|
||||||
const commitVersionType = (() => {
|
const commitVersionType = (() => {
|
||||||
switch (answerBucket.getAnswerFor('commitType')) {
|
switch (answerBucket.getAnswerFor('commitType')) {
|
||||||
@@ -69,20 +96,30 @@ export const run = async (argvArg: any) => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
logger.log('info', `OK! Creating commit with message '${commitString}'`);
|
ui.printHeader('✨ Creating Semantic Commit');
|
||||||
|
ui.printCommitMessage(commitString);
|
||||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
executor: 'bash',
|
executor: 'bash',
|
||||||
sourceFilePaths: [],
|
sourceFilePaths: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log('info', `Baking commitinfo into code ...`);
|
// Determine total steps (6 if pushing, 5 if not)
|
||||||
|
const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5;
|
||||||
|
let currentStep = 0;
|
||||||
|
|
||||||
|
// Step 1: Baking commitinfo
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'in-progress');
|
||||||
const commitInfo = new plugins.commitinfo.CommitInfo(
|
const commitInfo = new plugins.commitinfo.CommitInfo(
|
||||||
paths.cwd,
|
paths.cwd,
|
||||||
commitVersionType,
|
commitVersionType,
|
||||||
);
|
);
|
||||||
await commitInfo.writeIntoPotentialDirs();
|
await commitInfo.writeIntoPotentialDirs();
|
||||||
|
ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'done');
|
||||||
|
|
||||||
logger.log('info', `Writing changelog.md ...`);
|
// Step 2: Writing changelog
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'in-progress');
|
||||||
let changelog = nextCommitObject.changelog;
|
let changelog = nextCommitObject.changelog;
|
||||||
changelog = changelog.replaceAll(
|
changelog = changelog.replaceAll(
|
||||||
'{{nextVersion}}',
|
'{{nextVersion}}',
|
||||||
@@ -105,21 +142,58 @@ export const run = async (argvArg: any) => {
|
|||||||
changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
|
changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
changelog,
|
.file(plugins.path.join(paths.cwd, `changelog.md`))
|
||||||
plugins.path.join(paths.cwd, `changelog.md`),
|
.encoding('utf8')
|
||||||
);
|
.write(changelog);
|
||||||
|
ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'done');
|
||||||
|
|
||||||
logger.log('info', `Staging files for commit:`);
|
// Step 3: Staging files
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '📦 Staging files', 'in-progress');
|
||||||
await smartshellInstance.exec(`git add -A`);
|
await smartshellInstance.exec(`git add -A`);
|
||||||
|
ui.printStep(currentStep, totalSteps, '📦 Staging files', 'done');
|
||||||
|
|
||||||
|
// Step 4: Creating commit
|
||||||
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'in-progress');
|
||||||
await smartshellInstance.exec(`git commit -m "${commitString}"`);
|
await smartshellInstance.exec(`git commit -m "${commitString}"`);
|
||||||
await smartshellInstance.exec(`npm version ${commitVersionType}`);
|
ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'done');
|
||||||
|
|
||||||
|
// Step 5: Bumping version
|
||||||
|
currentStep++;
|
||||||
|
const projectType = await helpers.detectProjectType();
|
||||||
|
const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps);
|
||||||
|
|
||||||
|
// Step 6: Push to remote (optional)
|
||||||
|
const currentBranch = await helpers.detectCurrentBranch();
|
||||||
if (
|
if (
|
||||||
answerBucket.getAnswerFor('pushToOrigin') &&
|
answerBucket.getAnswerFor('pushToOrigin') &&
|
||||||
!(process.env.CI === 'true')
|
!(process.env.CI === 'true')
|
||||||
) {
|
) {
|
||||||
await smartshellInstance.exec(`git push origin master --follow-tags`);
|
currentStep++;
|
||||||
|
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress');
|
||||||
|
await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`);
|
||||||
|
ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(''); // Add spacing before summary
|
||||||
|
|
||||||
|
// Get commit SHA for summary
|
||||||
|
const commitShaResult = await smartshellInstance.exec('git rev-parse --short HEAD');
|
||||||
|
const commitSha = commitShaResult.stdout.trim();
|
||||||
|
|
||||||
|
// Print final summary
|
||||||
|
ui.printSummary({
|
||||||
|
projectType,
|
||||||
|
branch: currentBranch,
|
||||||
|
commitType: answerBucket.getAnswerFor('commitType'),
|
||||||
|
commitScope: answerBucket.getAnswerFor('commitScope'),
|
||||||
|
commitMessage: answerBucket.getAnswerFor('commitDescription'),
|
||||||
|
newVersion: newVersion,
|
||||||
|
commitSha: commitSha,
|
||||||
|
pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCommitStringFromAnswerBucket = (
|
const createCommitStringFromAnswerBucket = (
|
||||||
|
|||||||
218
ts/mod_commit/mod.helpers.ts
Normal file
218
ts/mod_commit/mod.helpers.ts
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import * as plugins from './mod.plugins.js';
|
||||||
|
import * as paths from '../paths.js';
|
||||||
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
import * as ui from './mod.ui.js';
|
||||||
|
|
||||||
|
export type ProjectType = 'npm' | 'deno' | 'both' | 'none';
|
||||||
|
export type VersionType = 'patch' | 'minor' | 'major';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the current git branch
|
||||||
|
* @returns The current branch name, defaults to 'master' if detection fails
|
||||||
|
*/
|
||||||
|
export async function detectCurrentBranch(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
|
executor: 'bash',
|
||||||
|
sourceFilePaths: [],
|
||||||
|
});
|
||||||
|
const result = await smartshellInstance.exec('git branch --show-current');
|
||||||
|
const branchName = result.stdout.trim();
|
||||||
|
|
||||||
|
if (!branchName) {
|
||||||
|
logger.log('warn', 'Could not detect current branch, falling back to "master"');
|
||||||
|
return 'master';
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('info', `Detected current branch: ${branchName}`);
|
||||||
|
return branchName;
|
||||||
|
} catch (error) {
|
||||||
|
logger.log('warn', `Failed to detect branch: ${error.message}, falling back to "master"`);
|
||||||
|
return 'master';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the project type based on presence of package.json and/or deno.json
|
||||||
|
* @returns The project type
|
||||||
|
*/
|
||||||
|
export async function detectProjectType(): Promise<ProjectType> {
|
||||||
|
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
|
||||||
|
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
|
||||||
|
|
||||||
|
const hasPackageJson = await plugins.smartfs.file(packageJsonPath).exists();
|
||||||
|
const hasDenoJson = await plugins.smartfs.file(denoJsonPath).exists();
|
||||||
|
|
||||||
|
if (hasPackageJson && hasDenoJson) {
|
||||||
|
logger.log('info', 'Detected dual project (npm + deno)');
|
||||||
|
return 'both';
|
||||||
|
} else if (hasPackageJson) {
|
||||||
|
logger.log('info', 'Detected npm project');
|
||||||
|
return 'npm';
|
||||||
|
} else if (hasDenoJson) {
|
||||||
|
logger.log('info', 'Detected deno project');
|
||||||
|
return 'deno';
|
||||||
|
} else {
|
||||||
|
throw new Error('No package.json or deno.json found in current directory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a semantic version string and bumps it according to the version type
|
||||||
|
* @param currentVersion Current version string (e.g., "1.2.3")
|
||||||
|
* @param versionType Type of version bump
|
||||||
|
* @returns New version string
|
||||||
|
*/
|
||||||
|
function calculateNewVersion(currentVersion: string, versionType: VersionType): string {
|
||||||
|
const versionMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||||
|
|
||||||
|
if (!versionMatch) {
|
||||||
|
throw new Error(`Invalid version format: ${currentVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let [, major, minor, patch] = versionMatch.map(Number);
|
||||||
|
|
||||||
|
switch (versionType) {
|
||||||
|
case 'major':
|
||||||
|
major += 1;
|
||||||
|
minor = 0;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
minor += 1;
|
||||||
|
patch = 0;
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
patch += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${major}.${minor}.${patch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the current version from package.json or deno.json
|
||||||
|
* @param projectType The project type to determine which file to read
|
||||||
|
* @returns The current version string
|
||||||
|
*/
|
||||||
|
async function readCurrentVersion(projectType: ProjectType): Promise<string> {
|
||||||
|
if (projectType === 'npm' || projectType === 'both') {
|
||||||
|
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
|
||||||
|
const content = (await plugins.smartfs
|
||||||
|
.file(packageJsonPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const packageJson = JSON.parse(content) as { version?: string };
|
||||||
|
|
||||||
|
if (!packageJson.version) {
|
||||||
|
throw new Error('package.json does not contain a version field');
|
||||||
|
}
|
||||||
|
return packageJson.version;
|
||||||
|
} else {
|
||||||
|
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
|
||||||
|
const content = (await plugins.smartfs
|
||||||
|
.file(denoJsonPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const denoConfig = JSON.parse(content) as { version?: string };
|
||||||
|
|
||||||
|
if (!denoConfig.version) {
|
||||||
|
throw new Error('deno.json does not contain a version field');
|
||||||
|
}
|
||||||
|
return denoConfig.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the version field in a JSON file (package.json or deno.json)
|
||||||
|
* @param filePath Path to the JSON file
|
||||||
|
* @param newVersion The new version to write
|
||||||
|
*/
|
||||||
|
async function updateVersionFile(filePath: string, newVersion: string): Promise<void> {
|
||||||
|
const content = (await plugins.smartfs
|
||||||
|
.file(filePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const config = JSON.parse(content) as { version?: string };
|
||||||
|
config.version = newVersion;
|
||||||
|
await plugins.smartfs
|
||||||
|
.file(filePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(JSON.stringify(config, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bumps the project version based on project type
|
||||||
|
* Handles npm-only, deno-only, and dual projects with unified logic
|
||||||
|
* @param projectType The detected project type
|
||||||
|
* @param versionType The type of version bump
|
||||||
|
* @param currentStep The current step number for progress display
|
||||||
|
* @param totalSteps The total number of steps for progress display
|
||||||
|
* @returns The new version string
|
||||||
|
*/
|
||||||
|
export async function bumpProjectVersion(
|
||||||
|
projectType: ProjectType,
|
||||||
|
versionType: VersionType,
|
||||||
|
currentStep?: number,
|
||||||
|
totalSteps?: number
|
||||||
|
): Promise<string> {
|
||||||
|
if (projectType === 'none') {
|
||||||
|
throw new Error('Cannot bump version: no package.json or deno.json found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectEmoji = projectType === 'npm' ? '📦' : projectType === 'deno' ? '🦕' : '🔀';
|
||||||
|
const description = `🏷️ Bumping version (${projectEmoji} ${projectType})`;
|
||||||
|
|
||||||
|
if (currentStep && totalSteps) {
|
||||||
|
ui.printStep(currentStep, totalSteps, description, 'in-progress');
|
||||||
|
}
|
||||||
|
|
||||||
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
|
executor: 'bash',
|
||||||
|
sourceFilePaths: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Read current version
|
||||||
|
const currentVersion = await readCurrentVersion(projectType);
|
||||||
|
|
||||||
|
// 2. Calculate new version (reuse existing function!)
|
||||||
|
const newVersion = calculateNewVersion(currentVersion, versionType);
|
||||||
|
|
||||||
|
logger.log('info', `Bumping version: ${currentVersion} → ${newVersion}`);
|
||||||
|
|
||||||
|
// 3. Determine which files to update
|
||||||
|
const filesToUpdate: string[] = [];
|
||||||
|
const packageJsonPath = plugins.path.join(paths.cwd, 'package.json');
|
||||||
|
const denoJsonPath = plugins.path.join(paths.cwd, 'deno.json');
|
||||||
|
|
||||||
|
if (projectType === 'npm' || projectType === 'both') {
|
||||||
|
await updateVersionFile(packageJsonPath, newVersion);
|
||||||
|
filesToUpdate.push('package.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectType === 'deno' || projectType === 'both') {
|
||||||
|
await updateVersionFile(denoJsonPath, newVersion);
|
||||||
|
filesToUpdate.push('deno.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Stage all updated files
|
||||||
|
await smartshellInstance.exec(`git add ${filesToUpdate.join(' ')}`);
|
||||||
|
|
||||||
|
// 5. Create version commit
|
||||||
|
await smartshellInstance.exec(`git commit -m "v${newVersion}"`);
|
||||||
|
|
||||||
|
// 6. Create version tag
|
||||||
|
await smartshellInstance.exec(`git tag v${newVersion} -m "v${newVersion}"`);
|
||||||
|
|
||||||
|
logger.log('info', `Created commit and tag v${newVersion}`);
|
||||||
|
|
||||||
|
if (currentStep && totalSteps) {
|
||||||
|
ui.printStep(currentStep, totalSteps, description, 'done');
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVersion;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to bump project version: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
ts/mod_commit/mod.ui.ts
Normal file
196
ts/mod_commit/mod.ui.ts
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI helper module for beautiful CLI output
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ICommitSummary {
|
||||||
|
projectType: string;
|
||||||
|
branch: string;
|
||||||
|
commitType: string;
|
||||||
|
commitScope: string;
|
||||||
|
commitMessage: string;
|
||||||
|
newVersion: string;
|
||||||
|
commitSha?: string;
|
||||||
|
pushed: boolean;
|
||||||
|
repoUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRecommendation {
|
||||||
|
recommendedNextVersion: string;
|
||||||
|
recommendedNextVersionLevel: string;
|
||||||
|
recommendedNextVersionScope: string;
|
||||||
|
recommendedNextVersionMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a header with a box around it
|
||||||
|
*/
|
||||||
|
export function printHeader(title: string): void {
|
||||||
|
const width = 57;
|
||||||
|
const padding = Math.max(0, width - title.length - 2);
|
||||||
|
const leftPad = Math.floor(padding / 2);
|
||||||
|
const rightPad = padding - leftPad;
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('╭─' + '─'.repeat(width) + '─╮');
|
||||||
|
console.log('│ ' + title + ' '.repeat(rightPad + leftPad) + ' │');
|
||||||
|
console.log('╰─' + '─'.repeat(width) + '─╯');
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a section with a border
|
||||||
|
*/
|
||||||
|
export function printSection(title: string, lines: string[]): void {
|
||||||
|
const width = 59;
|
||||||
|
|
||||||
|
console.log('┌─ ' + title + ' ' + '─'.repeat(Math.max(0, width - title.length - 3)) + '┐');
|
||||||
|
console.log('│' + ' '.repeat(width) + '│');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const padding = width - line.length;
|
||||||
|
console.log('│ ' + line + ' '.repeat(Math.max(0, padding - 2)) + '│');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('│' + ' '.repeat(width) + '│');
|
||||||
|
console.log('└─' + '─'.repeat(width) + '─┘');
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print AI recommendations in a nice box
|
||||||
|
*/
|
||||||
|
export function printRecommendation(recommendation: IRecommendation): void {
|
||||||
|
const lines = [
|
||||||
|
`Suggested Version: v${recommendation.recommendedNextVersion}`,
|
||||||
|
`Suggested Type: ${recommendation.recommendedNextVersionLevel}`,
|
||||||
|
`Suggested Scope: ${recommendation.recommendedNextVersionScope}`,
|
||||||
|
`Suggested Message: ${recommendation.recommendedNextVersionMessage}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
printSection('📊 AI Recommendations', lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a progress step
|
||||||
|
*/
|
||||||
|
export function printStep(
|
||||||
|
current: number,
|
||||||
|
total: number,
|
||||||
|
description: string,
|
||||||
|
status: 'in-progress' | 'done' | 'error'
|
||||||
|
): void {
|
||||||
|
const statusIcon = status === 'done' ? '✓' : status === 'error' ? '✗' : '⏳';
|
||||||
|
const dots = '.'.repeat(Math.max(0, 40 - description.length));
|
||||||
|
|
||||||
|
console.log(` [${current}/${total}] ${description}${dots} ${statusIcon}`);
|
||||||
|
|
||||||
|
// Clear the line on next update if in progress
|
||||||
|
if (status === 'in-progress') {
|
||||||
|
process.stdout.write('\x1b[1A'); // Move cursor up one line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get emoji for project type
|
||||||
|
*/
|
||||||
|
function getProjectTypeEmoji(projectType: string): string {
|
||||||
|
switch (projectType) {
|
||||||
|
case 'npm':
|
||||||
|
return '📦 npm';
|
||||||
|
case 'deno':
|
||||||
|
return '🦕 Deno';
|
||||||
|
case 'both':
|
||||||
|
return '🔀 npm + Deno';
|
||||||
|
default:
|
||||||
|
return '❓ Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get emoji for commit type
|
||||||
|
*/
|
||||||
|
function getCommitTypeEmoji(commitType: string): string {
|
||||||
|
switch (commitType) {
|
||||||
|
case 'fix':
|
||||||
|
return '🔧 fix';
|
||||||
|
case 'feat':
|
||||||
|
return '✨ feat';
|
||||||
|
case 'BREAKING CHANGE':
|
||||||
|
return '💥 BREAKING CHANGE';
|
||||||
|
default:
|
||||||
|
return commitType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print final commit summary
|
||||||
|
*/
|
||||||
|
export function printSummary(summary: ICommitSummary): void {
|
||||||
|
const lines = [
|
||||||
|
`Project Type: ${getProjectTypeEmoji(summary.projectType)}`,
|
||||||
|
`Branch: 🌿 ${summary.branch}`,
|
||||||
|
`Commit Type: ${getCommitTypeEmoji(summary.commitType)}`,
|
||||||
|
`Scope: 📍 ${summary.commitScope}`,
|
||||||
|
`New Version: 🏷️ v${summary.newVersion}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (summary.commitSha) {
|
||||||
|
lines.push(`Commit SHA: 📌 ${summary.commitSha}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.pushed) {
|
||||||
|
lines.push(`Remote: ✓ Pushed successfully`);
|
||||||
|
} else {
|
||||||
|
lines.push(`Remote: ⊘ Not pushed (local only)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.repoUrl && summary.commitSha) {
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`View at: ${summary.repoUrl}/commit/${summary.commitSha}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
printSection('✅ Commit Summary', lines);
|
||||||
|
|
||||||
|
if (summary.pushed) {
|
||||||
|
console.log('🎉 All done! Your changes are committed and pushed.\n');
|
||||||
|
} else {
|
||||||
|
console.log('✓ Commit created successfully.\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print an info message with consistent formatting
|
||||||
|
*/
|
||||||
|
export function printInfo(message: string): void {
|
||||||
|
console.log(` ℹ️ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a success message
|
||||||
|
*/
|
||||||
|
export function printSuccess(message: string): void {
|
||||||
|
console.log(` ✓ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a warning message
|
||||||
|
*/
|
||||||
|
export function printWarning(message: string): void {
|
||||||
|
logger.log('warn', `⚠️ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print an error message
|
||||||
|
*/
|
||||||
|
export function printError(message: string): void {
|
||||||
|
logger.log('error', `✗ ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print commit message being created
|
||||||
|
*/
|
||||||
|
export function printCommitMessage(commitString: string): void {
|
||||||
|
console.log(`\n 📝 Commit: ${commitString}\n`);
|
||||||
|
}
|
||||||
@@ -65,15 +65,15 @@ export abstract class BaseFormatter {
|
|||||||
normalizedPath = './' + filepath;
|
normalizedPath = './' + filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(content, normalizedPath);
|
await plugins.smartfs.file(normalizedPath).encoding('utf8').write(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createFile(filepath: string, content: string): Promise<void> {
|
protected async createFile(filepath: string, content: string): Promise<void> {
|
||||||
await plugins.smartfile.memory.toFs(content, filepath);
|
await plugins.smartfs.file(filepath).encoding('utf8').write(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteFile(filepath: string): Promise<void> {
|
protected async deleteFile(filepath: string): Promise<void> {
|
||||||
await plugins.smartfile.fs.remove(filepath);
|
await plugins.smartfs.file(filepath).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class ChangeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
await plugins.smartfs.directory(this.cacheDir).recursive().create();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getManifest(): Promise<ICacheManifest> {
|
async getManifest(): Promise<ICacheManifest> {
|
||||||
@@ -35,13 +35,16 @@ export class ChangeCache {
|
|||||||
files: [],
|
files: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
const exists = await plugins.smartfs.file(this.manifestPath).exists();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return defaultManifest;
|
return defaultManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
|
const content = (await plugins.smartfs
|
||||||
|
.file(this.manifestPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
const manifest = JSON.parse(content);
|
const manifest = JSON.parse(content);
|
||||||
|
|
||||||
// Validate the manifest structure
|
// Validate the manifest structure
|
||||||
@@ -57,7 +60,7 @@ export class ChangeCache {
|
|||||||
);
|
);
|
||||||
// Try to delete the corrupted file
|
// Try to delete the corrupted file
|
||||||
try {
|
try {
|
||||||
await plugins.smartfile.fs.remove(this.manifestPath);
|
await plugins.smartfs.file(this.manifestPath).delete();
|
||||||
} catch (removeError) {
|
} catch (removeError) {
|
||||||
// Ignore removal errors
|
// Ignore removal errors
|
||||||
}
|
}
|
||||||
@@ -72,11 +75,14 @@ export class ChangeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
await plugins.smartfs.directory(this.cacheDir).recursive().create();
|
||||||
|
|
||||||
// Write directly with proper JSON stringification
|
// Write directly with proper JSON stringification
|
||||||
const jsonContent = JSON.stringify(manifest, null, 2);
|
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||||
await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
|
await plugins.smartfs
|
||||||
|
.file(this.manifestPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(jsonContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasFileChanged(filePath: string): Promise<boolean> {
|
async hasFileChanged(filePath: string): Promise<boolean> {
|
||||||
@@ -85,20 +91,23 @@ export class ChangeCache {
|
|||||||
: plugins.path.join(paths.cwd, filePath);
|
: plugins.path.join(paths.cwd, filePath);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
const exists = await plugins.smartfs.file(absolutePath).exists();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return true; // File doesn't exist, so it's "changed" (will be created)
|
return true; // File doesn't exist, so it's "changed" (will be created)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current file stats
|
// Get current file stats
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||||
|
|
||||||
// Skip directories
|
// Skip directories
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory) {
|
||||||
return false; // Directories are not processed
|
return false; // Directories are not processed
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
const content = (await plugins.smartfs
|
||||||
|
.file(absolutePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
const currentChecksum = this.calculateChecksum(content);
|
const currentChecksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Get cached info
|
// Get cached info
|
||||||
@@ -113,7 +122,7 @@ export class ChangeCache {
|
|||||||
return (
|
return (
|
||||||
cachedFile.checksum !== currentChecksum ||
|
cachedFile.checksum !== currentChecksum ||
|
||||||
cachedFile.size !== stats.size ||
|
cachedFile.size !== stats.size ||
|
||||||
cachedFile.modified !== stats.mtimeMs
|
cachedFile.modified !== stats.mtime.getTime()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +132,17 @@ export class ChangeCache {
|
|||||||
: plugins.path.join(paths.cwd, filePath);
|
: plugins.path.join(paths.cwd, filePath);
|
||||||
|
|
||||||
// Get current file stats
|
// Get current file stats
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||||
|
|
||||||
// Skip directories
|
// Skip directories
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory) {
|
||||||
return; // Don't cache directories
|
return; // Don't cache directories
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
const content = (await plugins.smartfs
|
||||||
|
.file(absolutePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Update manifest
|
// Update manifest
|
||||||
@@ -140,7 +152,7 @@ export class ChangeCache {
|
|||||||
const cacheEntry: IFileCache = {
|
const cacheEntry: IFileCache = {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
checksum,
|
checksum,
|
||||||
modified: stats.mtimeMs,
|
modified: stats.mtime.getTime(),
|
||||||
size: stats.size,
|
size: stats.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,7 +188,7 @@ export class ChangeCache {
|
|||||||
? file.path
|
? file.path
|
||||||
: plugins.path.join(paths.cwd, file.path);
|
: plugins.path.join(paths.cwd, file.path);
|
||||||
|
|
||||||
if (await plugins.smartfile.fs.fileExists(absolutePath)) {
|
if (await plugins.smartfs.file(absolutePath).exists()) {
|
||||||
validFiles.push(file);
|
validFiles.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ export class DiffReporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exists = await plugins.smartfile.fs.fileExists(change.path);
|
const exists = await plugins.smartfs.file(change.path).exists();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentContent = await plugins.smartfile.fs.toStringSync(
|
const currentContent = (await plugins.smartfs
|
||||||
change.path,
|
.file(change.path)
|
||||||
);
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
|
||||||
// For planned changes, we need the new content
|
// For planned changes, we need the new content
|
||||||
if (!change.content) {
|
if (!change.content) {
|
||||||
@@ -107,10 +108,10 @@ export class DiffReporter {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
JSON.stringify(report, null, 2),
|
.file(outputPath)
|
||||||
outputPath,
|
.encoding('utf8')
|
||||||
);
|
.write(JSON.stringify(report, null, 2));
|
||||||
logger.log('info', `Diff report saved to ${outputPath}`);
|
logger.log('info', `Diff report saved to ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,10 +192,10 @@ export class FormatStats {
|
|||||||
moduleStats: Array.from(this.stats.moduleStats.values()),
|
moduleStats: Array.from(this.stats.moduleStats.values()),
|
||||||
};
|
};
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
JSON.stringify(report, null, 2),
|
.file(outputPath)
|
||||||
outputPath,
|
.encoding('utf8')
|
||||||
);
|
.write(JSON.stringify(report, null, 2));
|
||||||
logger.log('info', `Statistics report saved to ${outputPath}`);
|
logger.log('info', `Statistics report saved to ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,21 +36,27 @@ export class RollbackManager {
|
|||||||
: plugins.path.join(paths.cwd, filepath);
|
: plugins.path.join(paths.cwd, filepath);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
const exists = await plugins.smartfs.file(absolutePath).exists();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
// File doesn't exist yet (will be created), so we skip backup
|
// File doesn't exist yet (will be created), so we skip backup
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file content and metadata
|
// Read file content and metadata
|
||||||
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
const content = (await plugins.smartfs
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
.file(absolutePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Create backup
|
// Create backup
|
||||||
const backupPath = this.getBackupPath(operationId, filepath);
|
const backupPath = this.getBackupPath(operationId, filepath);
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
|
await plugins.smartfs
|
||||||
await plugins.smartfile.memory.toFs(content, backupPath);
|
.directory(plugins.path.dirname(backupPath))
|
||||||
|
.recursive()
|
||||||
|
.create();
|
||||||
|
await plugins.smartfs.file(backupPath).encoding('utf8').write(content);
|
||||||
|
|
||||||
// Update operation
|
// Update operation
|
||||||
operation.files.push({
|
operation.files.push({
|
||||||
@@ -84,7 +90,10 @@ export class RollbackManager {
|
|||||||
|
|
||||||
// Verify backup integrity
|
// Verify backup integrity
|
||||||
const backupPath = this.getBackupPath(operationId, file.path);
|
const backupPath = this.getBackupPath(operationId, file.path);
|
||||||
const backupContent = plugins.smartfile.fs.toStringSync(backupPath);
|
const backupContent = await plugins.smartfs
|
||||||
|
.file(backupPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read();
|
||||||
const backupChecksum = this.calculateChecksum(backupContent);
|
const backupChecksum = this.calculateChecksum(backupContent);
|
||||||
|
|
||||||
if (backupChecksum !== file.checksum) {
|
if (backupChecksum !== file.checksum) {
|
||||||
@@ -92,7 +101,10 @@ export class RollbackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore file
|
// Restore file
|
||||||
await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
|
await plugins.smartfs
|
||||||
|
.file(absolutePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(file.originalContent);
|
||||||
|
|
||||||
// Restore permissions
|
// Restore permissions
|
||||||
const mode = parseInt(file.permissions, 8);
|
const mode = parseInt(file.permissions, 8);
|
||||||
@@ -129,7 +141,7 @@ export class RollbackManager {
|
|||||||
'operations',
|
'operations',
|
||||||
operation.id,
|
operation.id,
|
||||||
);
|
);
|
||||||
await plugins.smartfile.fs.remove(operationDir);
|
await plugins.smartfs.directory(operationDir).recursive().delete();
|
||||||
|
|
||||||
// Remove from manifest
|
// Remove from manifest
|
||||||
manifest.operations = manifest.operations.filter(
|
manifest.operations = manifest.operations.filter(
|
||||||
@@ -148,13 +160,16 @@ export class RollbackManager {
|
|||||||
|
|
||||||
for (const file of operation.files) {
|
for (const file of operation.files) {
|
||||||
const backupPath = this.getBackupPath(operationId, file.path);
|
const backupPath = this.getBackupPath(operationId, file.path);
|
||||||
const exists = await plugins.smartfile.fs.fileExists(backupPath);
|
const exists = await plugins.smartfs.file(backupPath).exists();
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = plugins.smartfile.fs.toStringSync(backupPath);
|
const content = await plugins.smartfs
|
||||||
|
.file(backupPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read();
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
if (checksum !== file.checksum) {
|
if (checksum !== file.checksum) {
|
||||||
@@ -171,10 +186,11 @@ export class RollbackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async ensureBackupDir(): Promise<void> {
|
private async ensureBackupDir(): Promise<void> {
|
||||||
await plugins.smartfile.fs.ensureDir(this.backupDir);
|
await plugins.smartfs.directory(this.backupDir).recursive().create();
|
||||||
await plugins.smartfile.fs.ensureDir(
|
await plugins.smartfs
|
||||||
plugins.path.join(this.backupDir, 'operations'),
|
.directory(plugins.path.join(this.backupDir, 'operations'))
|
||||||
);
|
.recursive()
|
||||||
|
.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateOperationId(): string {
|
private generateOperationId(): string {
|
||||||
@@ -204,13 +220,16 @@ export class RollbackManager {
|
|||||||
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
||||||
const defaultManifest = { operations: [] };
|
const defaultManifest = { operations: [] };
|
||||||
|
|
||||||
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
const exists = await plugins.smartfs.file(this.manifestPath).exists();
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return defaultManifest;
|
return defaultManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
|
const content = (await plugins.smartfs
|
||||||
|
.file(this.manifestPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
const manifest = JSON.parse(content);
|
const manifest = JSON.parse(content);
|
||||||
|
|
||||||
// Validate the manifest structure
|
// Validate the manifest structure
|
||||||
@@ -228,7 +247,7 @@ export class RollbackManager {
|
|||||||
);
|
);
|
||||||
// Try to delete the corrupted file
|
// Try to delete the corrupted file
|
||||||
try {
|
try {
|
||||||
await plugins.smartfile.fs.remove(this.manifestPath);
|
await plugins.smartfs.file(this.manifestPath).delete();
|
||||||
} catch (removeError) {
|
} catch (removeError) {
|
||||||
// Ignore removal errors
|
// Ignore removal errors
|
||||||
}
|
}
|
||||||
@@ -249,7 +268,10 @@ export class RollbackManager {
|
|||||||
|
|
||||||
// Write directly with proper JSON stringification
|
// Write directly with proper JSON stringification
|
||||||
const jsonContent = JSON.stringify(manifest, null, 2);
|
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||||
await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
|
await plugins.smartfs
|
||||||
|
.file(this.manifestPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(jsonContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOperation(
|
private async getOperation(
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ const filesToDelete = [
|
|||||||
|
|
||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
for (const relativeFilePath of filesToDelete) {
|
for (const relativeFilePath of filesToDelete) {
|
||||||
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
|
const fileExists = await plugins.smartfs.file(relativeFilePath).exists();
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
||||||
plugins.smartfile.fs.removeSync(
|
await plugins.smartfs
|
||||||
plugins.path.join(paths.cwd, relativeFilePath),
|
.file(plugins.path.join(paths.cwd, relativeFilePath))
|
||||||
);
|
.delete();
|
||||||
} else {
|
} else {
|
||||||
logger.log('info', `Project is free of ${relativeFilePath}`);
|
logger.log('info', `Project is free of ${relativeFilePath}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ export const run = async (projectArg: Project) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle glob patterns
|
// Handle glob patterns
|
||||||
const files = await plugins.smartfile.fs.listFileTree('.', pattern.from);
|
const entries = await plugins.smartfs
|
||||||
|
.directory('.')
|
||||||
|
.recursive()
|
||||||
|
.filter(pattern.from)
|
||||||
|
.list();
|
||||||
|
const files = entries.map((entry) => entry.path);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const sourcePath = file;
|
const sourcePath = file;
|
||||||
@@ -46,10 +51,13 @@ export const run = async (projectArg: Project) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure destination directory exists
|
// Ensure destination directory exists
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(destPath));
|
await plugins.smartfs
|
||||||
|
.directory(plugins.path.dirname(destPath))
|
||||||
|
.recursive()
|
||||||
|
.create();
|
||||||
|
|
||||||
// Copy file
|
// Copy file
|
||||||
await plugins.smartfile.fs.copy(sourcePath, destPath);
|
await plugins.smartfs.file(sourcePath).copy(destPath);
|
||||||
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
|
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import { logger } from '../gitzone.logging.js';
|
|||||||
const gitignorePath = plugins.path.join(paths.cwd, './.gitignore');
|
const gitignorePath = plugins.path.join(paths.cwd, './.gitignore');
|
||||||
|
|
||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
const gitignoreExists = await plugins.smartfile.fs.fileExists(gitignorePath);
|
const gitignoreExists = await plugins.smartfs.file(gitignorePath).exists();
|
||||||
let customContent = '';
|
let customContent = '';
|
||||||
|
|
||||||
if (gitignoreExists) {
|
if (gitignoreExists) {
|
||||||
// lets get the existing gitignore file
|
// lets get the existing gitignore file
|
||||||
const existingGitIgnoreString =
|
const existingGitIgnoreString = (await plugins.smartfs
|
||||||
plugins.smartfile.fs.toStringSync(gitignorePath);
|
.file(gitignorePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
|
||||||
// Check for different custom section markers
|
// Check for different custom section markers
|
||||||
const customMarkers = ['#------# custom', '# custom'];
|
const customMarkers = ['#------# custom', '# custom'];
|
||||||
@@ -34,12 +36,17 @@ export const run = async (projectArg: Project) => {
|
|||||||
|
|
||||||
// Append the custom content if it exists
|
// Append the custom content if it exists
|
||||||
if (customContent) {
|
if (customContent) {
|
||||||
const newGitignoreContent =
|
const newGitignoreContent = (await plugins.smartfs
|
||||||
plugins.smartfile.fs.toStringSync(gitignorePath);
|
.file(gitignorePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
// The template already ends with "#------# custom", so just append the content
|
// The template already ends with "#------# custom", so just append the content
|
||||||
const finalContent =
|
const finalContent =
|
||||||
newGitignoreContent.trimEnd() + '\n' + customContent + '\n';
|
newGitignoreContent.trimEnd() + '\n' + customContent + '\n';
|
||||||
await plugins.smartfile.fs.toFs(finalContent, gitignorePath);
|
await plugins.smartfs
|
||||||
|
.file(gitignorePath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(finalContent);
|
||||||
logger.log('info', 'Updated .gitignore while preserving custom section!');
|
logger.log('info', 'Updated .gitignore while preserving custom section!');
|
||||||
} else {
|
} else {
|
||||||
logger.log('info', 'Added a .gitignore!');
|
logger.log('info', 'Added a .gitignore!');
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { logger } from '../gitzone.logging.js';
|
|||||||
const incompatibleLicenses: string[] = ['AGPL', 'GPL', 'SSPL'];
|
const incompatibleLicenses: string[] = ['AGPL', 'GPL', 'SSPL'];
|
||||||
|
|
||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
const nodeModulesInstalled = await plugins.smartfile.fs.isDirectory(
|
const nodeModulesInstalled = await plugins.smartfs
|
||||||
plugins.path.join(paths.cwd, 'node_modules'),
|
.directory(plugins.path.join(paths.cwd, 'node_modules'))
|
||||||
);
|
.exists();
|
||||||
if (!nodeModulesInstalled) {
|
if (!nodeModulesInstalled) {
|
||||||
logger.log('warn', 'No node_modules found. Skipping license check');
|
logger.log('warn', 'No node_modules found. Skipping license check');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -174,9 +174,11 @@ export const run = async (projectArg: Project) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// set overrides
|
// set overrides
|
||||||
const overrides = plugins.smartfile.fs.toObjectSync(
|
const overridesContent = (await plugins.smartfs
|
||||||
plugins.path.join(paths.assetsDir, 'overrides.json'),
|
.file(plugins.path.join(paths.assetsDir, 'overrides.json'))
|
||||||
);
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const overrides = JSON.parse(overridesContent);
|
||||||
packageJson.pnpm = packageJson.pnpm || {};
|
packageJson.pnpm = packageJson.pnpm || {};
|
||||||
packageJson.pnpm.overrides = overrides;
|
packageJson.pnpm.overrides = overrides;
|
||||||
|
|
||||||
|
|||||||
@@ -6,25 +6,22 @@ export const run = async () => {
|
|||||||
const readmeHintsPath = plugins.path.join(paths.cwd, 'readme.hints.md');
|
const readmeHintsPath = plugins.path.join(paths.cwd, 'readme.hints.md');
|
||||||
|
|
||||||
// Check and initialize readme.md if it doesn't exist
|
// Check and initialize readme.md if it doesn't exist
|
||||||
const readmeExists = await plugins.smartfile.fs.fileExists(readmePath);
|
const readmeExists = await plugins.smartfs.file(readmePath).exists();
|
||||||
if (!readmeExists) {
|
if (!readmeExists) {
|
||||||
await plugins.smartfile.fs.toFs(
|
await plugins.smartfs.file(readmePath)
|
||||||
'# Project Readme\n\nThis is the initial readme file.',
|
.encoding('utf8')
|
||||||
readmePath,
|
.write('# Project Readme\n\nThis is the initial readme file.');
|
||||||
);
|
|
||||||
console.log('Initialized readme.md');
|
console.log('Initialized readme.md');
|
||||||
} else {
|
} else {
|
||||||
console.log('readme.md already exists');
|
console.log('readme.md already exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and initialize readme.hints.md if it doesn't exist
|
// Check and initialize readme.hints.md if it doesn't exist
|
||||||
const readmeHintsExists =
|
const readmeHintsExists = await plugins.smartfs.file(readmeHintsPath).exists();
|
||||||
await plugins.smartfile.fs.fileExists(readmeHintsPath);
|
|
||||||
if (!readmeHintsExists) {
|
if (!readmeHintsExists) {
|
||||||
await plugins.smartfile.fs.toFs(
|
await plugins.smartfs.file(readmeHintsPath)
|
||||||
'# Project Readme Hints\n\nThis is the initial readme hints file.',
|
.encoding('utf8')
|
||||||
readmeHintsPath,
|
.write('# Project Readme Hints\n\nThis is the initial readme hints file.');
|
||||||
);
|
|
||||||
console.log('Initialized readme.hints.md');
|
console.log('Initialized readme.hints.md');
|
||||||
} else {
|
} else {
|
||||||
console.log('readme.hints.md already exists');
|
console.log('readme.hints.md already exists');
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import { Project } from '../classes.project.js';
|
|||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
// lets care about tsconfig.json
|
// lets care about tsconfig.json
|
||||||
logger.log('info', 'Formatting tsconfig.json...');
|
logger.log('info', 'Formatting tsconfig.json...');
|
||||||
const tsconfigSmartfile = await plugins.smartfile.SmartFile.fromFilePath(
|
const factory = plugins.smartfile.SmartFileFactory.nodeFs();
|
||||||
|
const tsconfigSmartfile = await factory.fromFilePath(
|
||||||
plugins.path.join(paths.cwd, 'tsconfig.json'),
|
plugins.path.join(paths.cwd, 'tsconfig.json'),
|
||||||
);
|
);
|
||||||
const tsconfigObject = JSON.parse(tsconfigSmartfile.contentBuffer.toString());
|
const tsconfigObject = JSON.parse(tsconfigSmartfile.parseContentAsString());
|
||||||
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
||||||
tsconfigObject.compilerOptions.baseUrl = '.';
|
tsconfigObject.compilerOptions.baseUrl = '.';
|
||||||
tsconfigObject.compilerOptions.paths = {};
|
tsconfigObject.compilerOptions.paths = {};
|
||||||
@@ -23,8 +24,8 @@ export const run = async (projectArg: Project) => {
|
|||||||
`./${publishModule}/index.js`,
|
`./${publishModule}/index.js`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
tsconfigSmartfile.setContentsFromString(
|
await tsconfigSmartfile.editContentAsString(async () => {
|
||||||
JSON.stringify(tsconfigObject, null, 2),
|
return JSON.stringify(tsconfigObject, null, 2);
|
||||||
);
|
});
|
||||||
await tsconfigSmartfile.write();
|
await tsconfigSmartfile.write();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class CleanupFormatter extends BaseFormatter {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const file of filesToRemove) {
|
for (const file of filesToRemove) {
|
||||||
const exists = await plugins.smartfile.fs.fileExists(file);
|
const exists = await plugins.smartfs.file(file).exists();
|
||||||
if (exists) {
|
if (exists) {
|
||||||
changes.push({
|
changes.push({
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
|
|||||||
@@ -41,16 +41,23 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
// Add files from TypeScript directories
|
// Add files from TypeScript directories
|
||||||
for (const dir of includeDirs) {
|
for (const dir of includeDirs) {
|
||||||
const globPattern = `${dir}/**/*.${extensions}`;
|
const globPattern = `${dir}/**/*.${extensions}`;
|
||||||
const dirFiles = await plugins.smartfile.fs.listFileTree(
|
const dirEntries = await plugins.smartfs
|
||||||
'.',
|
.directory('.')
|
||||||
globPattern,
|
.recursive()
|
||||||
);
|
.filter(globPattern)
|
||||||
|
.list();
|
||||||
|
const dirFiles = dirEntries.map((entry) => entry.path);
|
||||||
allFiles.push(...dirFiles);
|
allFiles.push(...dirFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add root config files
|
// Add root config files
|
||||||
for (const pattern of rootConfigFiles) {
|
for (const pattern of rootConfigFiles) {
|
||||||
const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern);
|
const rootEntries = await plugins.smartfs
|
||||||
|
.directory('.')
|
||||||
|
.recursive()
|
||||||
|
.filter(pattern)
|
||||||
|
.list();
|
||||||
|
const rootFiles = rootEntries.map((entry) => entry.path);
|
||||||
// Only include files at root level (no slashes in path)
|
// Only include files at root level (no slashes in path)
|
||||||
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
|
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
|
||||||
allFiles.push(...rootLevelFiles);
|
allFiles.push(...rootLevelFiles);
|
||||||
@@ -66,8 +73,8 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
const validFiles: string[] = [];
|
const validFiles: string[] = [];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
const stats = await plugins.smartfile.fs.stat(file);
|
const stats = await plugins.smartfs.file(file).stat();
|
||||||
if (!stats.isDirectory()) {
|
if (!stats.isDirectory) {
|
||||||
validFiles.push(file);
|
validFiles.push(file);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -148,7 +155,10 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read current content
|
// Read current content
|
||||||
const content = plugins.smartfile.fs.toStringSync(change.path);
|
const content = (await plugins.smartfs
|
||||||
|
.file(change.path)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
|
||||||
// Format with prettier
|
// Format with prettier
|
||||||
const prettier = await import('prettier');
|
const prettier = await import('prettier');
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ export let run = async (
|
|||||||
// Plan phase
|
// Plan phase
|
||||||
logger.log('info', 'Analyzing project for format operations...');
|
logger.log('info', 'Analyzing project for format operations...');
|
||||||
let plan = options.fromPlan
|
let plan = options.fromPlan
|
||||||
? JSON.parse(await plugins.smartfile.fs.toStringSync(options.fromPlan))
|
? JSON.parse(
|
||||||
|
(await plugins.smartfs
|
||||||
|
.file(options.fromPlan)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string,
|
||||||
|
)
|
||||||
: await planner.planFormat(activeFormatters);
|
: await planner.planFormat(activeFormatters);
|
||||||
|
|
||||||
// Display plan
|
// Display plan
|
||||||
@@ -109,10 +114,10 @@ export let run = async (
|
|||||||
|
|
||||||
// Save plan if requested
|
// Save plan if requested
|
||||||
if (options.savePlan) {
|
if (options.savePlan) {
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
JSON.stringify(plan, null, 2),
|
.file(options.savePlan)
|
||||||
options.savePlan,
|
.encoding('utf8')
|
||||||
);
|
.write(JSON.stringify(plan, null, 2));
|
||||||
logger.log('info', `Plan saved to ${options.savePlan}`);
|
logger.log('info', `Plan saved to ${options.savePlan}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,15 +48,17 @@ export class Meta {
|
|||||||
public async readDirectory() {
|
public async readDirectory() {
|
||||||
await this.syncToRemote(true);
|
await this.syncToRemote(true);
|
||||||
logger.log('info', `reading directory`);
|
logger.log('info', `reading directory`);
|
||||||
const metaFileExists = plugins.smartfile.fs.fileExistsSync(
|
const metaFileExists = await plugins.smartfs
|
||||||
this.filePaths.metaJson,
|
.file(this.filePaths.metaJson)
|
||||||
);
|
.exists();
|
||||||
if (!metaFileExists) {
|
if (!metaFileExists) {
|
||||||
throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
|
throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
|
||||||
}
|
}
|
||||||
this.metaRepoData = plugins.smartfile.fs.toObjectSync(
|
const content = (await plugins.smartfs
|
||||||
this.filePaths.metaJson,
|
.file(this.filePaths.metaJson)
|
||||||
);
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
this.metaRepoData = JSON.parse(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,15 +80,15 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async writeToDisk() {
|
public async writeToDisk() {
|
||||||
// write .meta.json to disk
|
// write .meta.json to disk
|
||||||
plugins.smartfile.memory.toFsSync(
|
await plugins.smartfs
|
||||||
JSON.stringify(this.metaRepoData, null, 2),
|
.file(this.filePaths.metaJson)
|
||||||
this.filePaths.metaJson,
|
.encoding('utf8')
|
||||||
);
|
.write(JSON.stringify(this.metaRepoData, null, 2));
|
||||||
// write .gitignore to disk
|
// write .gitignore to disk
|
||||||
plugins.smartfile.memory.toFsSync(
|
await plugins.smartfs
|
||||||
await this.generateGitignore(),
|
.file(this.filePaths.gitIgnore)
|
||||||
this.filePaths.gitIgnore,
|
.encoding('utf8')
|
||||||
);
|
.write(await this.generateGitignore());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,10 +114,25 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async updateLocalRepos() {
|
public async updateLocalRepos() {
|
||||||
await this.syncToRemote();
|
await this.syncToRemote();
|
||||||
const projects = plugins.smartfile.fs.toObjectSync(
|
const metaContent = (await plugins.smartfs
|
||||||
this.filePaths.metaJson,
|
.file(this.filePaths.metaJson)
|
||||||
).projects;
|
.encoding('utf8')
|
||||||
const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
|
.read()) as string;
|
||||||
|
const projects = JSON.parse(metaContent).projects;
|
||||||
|
const entries = await plugins.smartfs.directory(this.cwd).list();
|
||||||
|
const preExistingFolders: string[] = [];
|
||||||
|
for (const entry of entries) {
|
||||||
|
try {
|
||||||
|
const stats = await plugins.smartfs
|
||||||
|
.file(plugins.path.join(this.cwd, entry.path))
|
||||||
|
.stat();
|
||||||
|
if (stats.isDirectory) {
|
||||||
|
preExistingFolders.push(entry.name);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Skip entries that can't be accessed
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const preExistingFolderArg of preExistingFolders) {
|
for (const preExistingFolderArg of preExistingFolders) {
|
||||||
if (
|
if (
|
||||||
preExistingFolderArg !== '.git' &&
|
preExistingFolderArg !== '.git' &&
|
||||||
@@ -143,9 +160,17 @@ export class Meta {
|
|||||||
await this.sortMetaRepoData();
|
await this.sortMetaRepoData();
|
||||||
const missingRepos: string[] = [];
|
const missingRepos: string[] = [];
|
||||||
for (const key of Object.keys(this.metaRepoData.projects)) {
|
for (const key of Object.keys(this.metaRepoData.projects)) {
|
||||||
plugins.smartfile.fs.isDirectory(key)
|
const fullPath = plugins.path.join(this.cwd, key);
|
||||||
? logger.log('ok', `${key} -> is already cloned`)
|
try {
|
||||||
: missingRepos.push(key);
|
const stats = await plugins.smartfs.file(fullPath).stat();
|
||||||
|
if (stats.isDirectory) {
|
||||||
|
logger.log('ok', `${key} -> is already cloned`);
|
||||||
|
} else {
|
||||||
|
missingRepos.push(key);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
missingRepos.push(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('info', `found ${missingRepos.length} missing repos`);
|
logger.log('info', `found ${missingRepos.length} missing repos`);
|
||||||
@@ -165,7 +190,20 @@ export class Meta {
|
|||||||
await this.syncToRemote();
|
await this.syncToRemote();
|
||||||
|
|
||||||
// go recursive
|
// go recursive
|
||||||
const folders = await plugins.smartfile.fs.listFolders(this.cwd);
|
const listEntries = await plugins.smartfs.directory(this.cwd).list();
|
||||||
|
const folders: string[] = [];
|
||||||
|
for (const entry of listEntries) {
|
||||||
|
try {
|
||||||
|
const stats = await plugins.smartfs
|
||||||
|
.file(plugins.path.join(this.cwd, entry.path))
|
||||||
|
.stat();
|
||||||
|
if (stats.isDirectory) {
|
||||||
|
folders.push(entry.name);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Skip entries that can't be accessed
|
||||||
|
}
|
||||||
|
}
|
||||||
const childMetaRepositories: string[] = [];
|
const childMetaRepositories: string[] = [];
|
||||||
for (const folder of folders) {
|
for (const folder of folders) {
|
||||||
logger.log('info', folder);
|
logger.log('info', folder);
|
||||||
@@ -180,26 +218,30 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async initProject() {
|
public async initProject() {
|
||||||
await this.syncToRemote(true);
|
await this.syncToRemote(true);
|
||||||
const fileExists = await plugins.smartfile.fs.fileExists(
|
const fileExists = await plugins.smartfs
|
||||||
this.filePaths.metaJson,
|
.file(this.filePaths.metaJson)
|
||||||
);
|
.exists();
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
|
.file(this.filePaths.metaJson)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
projects: {},
|
projects: {},
|
||||||
}),
|
}),
|
||||||
this.filePaths.metaJson,
|
|
||||||
);
|
);
|
||||||
logger.log(
|
logger.log(
|
||||||
`success`,
|
`success`,
|
||||||
`created a new .meta.json in directory ${this.cwd}`,
|
`created a new .meta.json in directory ${this.cwd}`,
|
||||||
);
|
);
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
|
.file(this.filePaths.packageJson)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
name: this.dirName,
|
name: this.dirName,
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
}),
|
}),
|
||||||
this.filePaths.packageJson,
|
|
||||||
);
|
);
|
||||||
logger.log(
|
logger.log(
|
||||||
`success`,
|
`success`,
|
||||||
@@ -264,9 +306,10 @@ export class Meta {
|
|||||||
await this.writeToDisk();
|
await this.writeToDisk();
|
||||||
|
|
||||||
logger.log('info', 'removing directory from cwd');
|
logger.log('info', 'removing directory from cwd');
|
||||||
await plugins.smartfile.fs.remove(
|
await plugins.smartfs
|
||||||
plugins.path.join(paths.cwd, projectNameArg),
|
.directory(plugins.path.join(paths.cwd, projectNameArg))
|
||||||
);
|
.recursive()
|
||||||
|
.delete();
|
||||||
await this.updateLocalRepos();
|
await this.updateLocalRepos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export interface IServiceConfig {
|
|||||||
S3_BUCKET: string;
|
S3_BUCKET: string;
|
||||||
S3_ENDPOINT: string;
|
S3_ENDPOINT: string;
|
||||||
S3_USESSL: boolean;
|
S3_USESSL: boolean;
|
||||||
|
ELASTICSEARCH_HOST: string;
|
||||||
|
ELASTICSEARCH_PORT: string;
|
||||||
|
ELASTICSEARCH_USER: string;
|
||||||
|
ELASTICSEARCH_PASS: string;
|
||||||
|
ELASTICSEARCH_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServiceConfiguration {
|
export class ServiceConfiguration {
|
||||||
@@ -61,10 +66,10 @@ export class ServiceConfiguration {
|
|||||||
* Save the configuration to file
|
* Save the configuration to file
|
||||||
*/
|
*/
|
||||||
public async saveConfig(): Promise<void> {
|
public async saveConfig(): Promise<void> {
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfs
|
||||||
JSON.stringify(this.config, null, 2),
|
.file(this.configPath)
|
||||||
this.configPath
|
.encoding('utf8')
|
||||||
);
|
.write(JSON.stringify(this.config, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,21 +77,24 @@ export class ServiceConfiguration {
|
|||||||
*/
|
*/
|
||||||
private async ensureNogitDirectory(): Promise<void> {
|
private async ensureNogitDirectory(): Promise<void> {
|
||||||
const nogitPath = plugins.path.join(process.cwd(), '.nogit');
|
const nogitPath = plugins.path.join(process.cwd(), '.nogit');
|
||||||
await plugins.smartfile.fs.ensureDir(nogitPath);
|
await plugins.smartfs.directory(nogitPath).recursive().create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if configuration file exists
|
* Check if configuration file exists
|
||||||
*/
|
*/
|
||||||
private async configExists(): Promise<boolean> {
|
private async configExists(): Promise<boolean> {
|
||||||
return plugins.smartfile.fs.fileExists(this.configPath);
|
return plugins.smartfs.file(this.configPath).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load configuration from file
|
* Load configuration from file
|
||||||
*/
|
*/
|
||||||
private async loadConfig(): Promise<void> {
|
private async loadConfig(): Promise<void> {
|
||||||
const configContent = plugins.smartfile.fs.toStringSync(this.configPath);
|
const configContent = (await plugins.smartfs
|
||||||
|
.file(this.configPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
this.config = JSON.parse(configContent);
|
this.config = JSON.parse(configContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +102,7 @@ export class ServiceConfiguration {
|
|||||||
* Create default configuration
|
* Create default configuration
|
||||||
*/
|
*/
|
||||||
private async createDefaultConfig(): Promise<void> {
|
private async createDefaultConfig(): Promise<void> {
|
||||||
const projectName = helpers.getProjectName();
|
const projectName = await helpers.getProjectName();
|
||||||
const mongoPort = await helpers.getRandomAvailablePort();
|
const mongoPort = await helpers.getRandomAvailablePort();
|
||||||
const s3Port = await helpers.getRandomAvailablePort();
|
const s3Port = await helpers.getRandomAvailablePort();
|
||||||
let s3ConsolePort = s3Port + 1;
|
let s3ConsolePort = s3Port + 1;
|
||||||
@@ -111,6 +119,10 @@ export class ServiceConfiguration {
|
|||||||
const mongoPortStr = mongoPort.toString();
|
const mongoPortStr = mongoPort.toString();
|
||||||
const s3Host = 'localhost';
|
const s3Host = 'localhost';
|
||||||
const s3PortStr = s3Port.toString();
|
const s3PortStr = s3Port.toString();
|
||||||
|
const esHost = 'localhost';
|
||||||
|
const esPort = '9200';
|
||||||
|
const esUser = 'elastic';
|
||||||
|
const esPass = 'elastic';
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
PROJECT_NAME: projectName,
|
PROJECT_NAME: projectName,
|
||||||
@@ -126,8 +138,13 @@ export class ServiceConfiguration {
|
|||||||
S3_ACCESSKEY: 'defaultadmin',
|
S3_ACCESSKEY: 'defaultadmin',
|
||||||
S3_SECRETKEY: 'defaultpass',
|
S3_SECRETKEY: 'defaultpass',
|
||||||
S3_BUCKET: `${projectName}-documents`,
|
S3_BUCKET: `${projectName}-documents`,
|
||||||
S3_ENDPOINT: `http://${s3Host}:${s3PortStr}`,
|
S3_ENDPOINT: s3Host,
|
||||||
S3_USESSL: false
|
S3_USESSL: false,
|
||||||
|
ELASTICSEARCH_HOST: esHost,
|
||||||
|
ELASTICSEARCH_PORT: esPort,
|
||||||
|
ELASTICSEARCH_USER: esUser,
|
||||||
|
ELASTICSEARCH_PASS: esPass,
|
||||||
|
ELASTICSEARCH_URL: `http://${esUser}:${esPass}@${esHost}:${esPort}`
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
@@ -136,13 +153,14 @@ export class ServiceConfiguration {
|
|||||||
logger.log('info', `📍 MongoDB port: ${mongoPort}`);
|
logger.log('info', `📍 MongoDB port: ${mongoPort}`);
|
||||||
logger.log('info', `📍 S3 API port: ${s3Port}`);
|
logger.log('info', `📍 S3 API port: ${s3Port}`);
|
||||||
logger.log('info', `📍 S3 Console port: ${s3ConsolePort}`);
|
logger.log('info', `📍 S3 Console port: ${s3ConsolePort}`);
|
||||||
|
logger.log('info', `📍 Elasticsearch port: ${esPort}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update missing fields in existing configuration
|
* Update missing fields in existing configuration
|
||||||
*/
|
*/
|
||||||
private async updateMissingFields(): Promise<void> {
|
private async updateMissingFields(): Promise<void> {
|
||||||
const projectName = helpers.getProjectName();
|
const projectName = await helpers.getProjectName();
|
||||||
let updated = false;
|
let updated = false;
|
||||||
const fieldsAdded: string[] = [];
|
const fieldsAdded: string[] = [];
|
||||||
|
|
||||||
@@ -244,13 +262,44 @@ export class ServiceConfiguration {
|
|||||||
|
|
||||||
// Always update S3_ENDPOINT based on current settings
|
// Always update S3_ENDPOINT based on current settings
|
||||||
const oldEndpoint = this.config.S3_ENDPOINT;
|
const oldEndpoint = this.config.S3_ENDPOINT;
|
||||||
const protocol = this.config.S3_USESSL ? 'https' : 'http';
|
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
||||||
this.config.S3_ENDPOINT = `${protocol}://${this.config.S3_HOST}:${this.config.S3_PORT}`;
|
|
||||||
if (oldEndpoint !== this.config.S3_ENDPOINT) {
|
if (oldEndpoint !== this.config.S3_ENDPOINT) {
|
||||||
fieldsAdded.push('S3_ENDPOINT');
|
fieldsAdded.push('S3_ENDPOINT');
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.config.ELASTICSEARCH_HOST) {
|
||||||
|
this.config.ELASTICSEARCH_HOST = 'localhost';
|
||||||
|
fieldsAdded.push('ELASTICSEARCH_HOST');
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.config.ELASTICSEARCH_PORT) {
|
||||||
|
this.config.ELASTICSEARCH_PORT = '9200';
|
||||||
|
fieldsAdded.push('ELASTICSEARCH_PORT');
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.config.ELASTICSEARCH_USER) {
|
||||||
|
this.config.ELASTICSEARCH_USER = 'elastic';
|
||||||
|
fieldsAdded.push('ELASTICSEARCH_USER');
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.config.ELASTICSEARCH_PASS) {
|
||||||
|
this.config.ELASTICSEARCH_PASS = 'elastic';
|
||||||
|
fieldsAdded.push('ELASTICSEARCH_PASS');
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always update ELASTICSEARCH_URL based on current settings
|
||||||
|
const oldEsUrl = this.config.ELASTICSEARCH_URL;
|
||||||
|
this.config.ELASTICSEARCH_URL = `http://${this.config.ELASTICSEARCH_USER}:${this.config.ELASTICSEARCH_PASS}@${this.config.ELASTICSEARCH_HOST}:${this.config.ELASTICSEARCH_PORT}`;
|
||||||
|
if (oldEsUrl !== this.config.ELASTICSEARCH_URL) {
|
||||||
|
fieldsAdded.push('ELASTICSEARCH_URL');
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
logger.log('ok', `✅ Added missing fields: ${fieldsAdded.join(', ')}`);
|
logger.log('ok', `✅ Added missing fields: ${fieldsAdded.join(', ')}`);
|
||||||
@@ -273,7 +322,8 @@ export class ServiceConfiguration {
|
|||||||
public getContainerNames() {
|
public getContainerNames() {
|
||||||
return {
|
return {
|
||||||
mongo: `${this.config.PROJECT_NAME}-mongodb`,
|
mongo: `${this.config.PROJECT_NAME}-mongodb`,
|
||||||
minio: `${this.config.PROJECT_NAME}-minio`
|
minio: `${this.config.PROJECT_NAME}-minio`,
|
||||||
|
elasticsearch: `${this.config.PROJECT_NAME}-elasticsearch`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +333,8 @@ export class ServiceConfiguration {
|
|||||||
public getDataDirectories() {
|
public getDataDirectories() {
|
||||||
return {
|
return {
|
||||||
mongo: plugins.path.join(process.cwd(), '.nogit', 'mongodata'),
|
mongo: plugins.path.join(process.cwd(), '.nogit', 'mongodata'),
|
||||||
minio: plugins.path.join(process.cwd(), '.nogit', 'miniodata')
|
minio: plugins.path.join(process.cwd(), '.nogit', 'miniodata'),
|
||||||
|
elasticsearch: plugins.path.join(process.cwd(), '.nogit', 'esdata')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,11 +383,25 @@ export class ServiceConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check Elasticsearch container
|
||||||
|
const esStatus = await this.docker.getStatus(containers.elasticsearch);
|
||||||
|
if (esStatus !== 'not_exists') {
|
||||||
|
const portMappings = await this.docker.getPortMappings(containers.elasticsearch);
|
||||||
|
if (portMappings && portMappings['9200']) {
|
||||||
|
const dockerPort = portMappings['9200'];
|
||||||
|
if (this.config.ELASTICSEARCH_PORT !== dockerPort) {
|
||||||
|
logger.log('note', `📍 Syncing Elasticsearch port from Docker: ${dockerPort}`);
|
||||||
|
this.config.ELASTICSEARCH_PORT = dockerPort;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
// Update derived fields
|
// Update derived fields
|
||||||
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
||||||
const protocol = this.config.S3_USESSL ? 'https' : 'http';
|
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
||||||
this.config.S3_ENDPOINT = `${protocol}://${this.config.S3_HOST}:${this.config.S3_PORT}`;
|
this.config.ELASTICSEARCH_URL = `http://${this.config.ELASTICSEARCH_USER}:${this.config.ELASTICSEARCH_PASS}@${this.config.ELASTICSEARCH_HOST}:${this.config.ELASTICSEARCH_PORT}`;
|
||||||
|
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
logger.log('ok', '✅ Configuration synced with Docker containers');
|
logger.log('ok', '✅ Configuration synced with Docker containers');
|
||||||
@@ -353,6 +418,7 @@ export class ServiceConfiguration {
|
|||||||
// Check if containers exist - if they do, ports are fine
|
// Check if containers exist - if they do, ports are fine
|
||||||
const mongoExists = await this.docker.exists(containers.mongo);
|
const mongoExists = await this.docker.exists(containers.mongo);
|
||||||
const minioExists = await this.docker.exists(containers.minio);
|
const minioExists = await this.docker.exists(containers.minio);
|
||||||
|
const esExists = await this.docker.exists(containers.elasticsearch);
|
||||||
|
|
||||||
// Only check port availability if containers don't exist
|
// Only check port availability if containers don't exist
|
||||||
if (!mongoExists) {
|
if (!mongoExists) {
|
||||||
@@ -390,11 +456,22 @@ export class ServiceConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!esExists) {
|
||||||
|
const esPort = parseInt(this.config.ELASTICSEARCH_PORT);
|
||||||
|
if (!(await helpers.isPortAvailable(esPort))) {
|
||||||
|
logger.log('note', `⚠️ Elasticsearch port ${esPort} is in use, finding new port...`);
|
||||||
|
const newPort = await helpers.getRandomAvailablePort();
|
||||||
|
this.config.ELASTICSEARCH_PORT = newPort.toString();
|
||||||
|
logger.log('ok', `✅ New Elasticsearch port: ${newPort}`);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
// Update derived fields
|
// Update derived fields
|
||||||
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
||||||
const protocol = this.config.S3_USESSL ? 'https' : 'http';
|
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
||||||
this.config.S3_ENDPOINT = `${protocol}://${this.config.S3_HOST}:${this.config.S3_PORT}`;
|
this.config.ELASTICSEARCH_URL = `http://${this.config.ELASTICSEARCH_USER}:${this.config.ELASTICSEARCH_PASS}@${this.config.ELASTICSEARCH_HOST}:${this.config.ELASTICSEARCH_PORT}`;
|
||||||
|
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
@@ -417,14 +494,18 @@ export class ServiceConfiguration {
|
|||||||
s3ConsolePort++;
|
s3ConsolePort++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Elasticsearch uses standard port 9200
|
||||||
|
const esPort = '9200';
|
||||||
|
|
||||||
this.config.MONGODB_PORT = mongoPort.toString();
|
this.config.MONGODB_PORT = mongoPort.toString();
|
||||||
this.config.S3_PORT = s3Port.toString();
|
this.config.S3_PORT = s3Port.toString();
|
||||||
this.config.S3_CONSOLE_PORT = s3ConsolePort.toString();
|
this.config.S3_CONSOLE_PORT = s3ConsolePort.toString();
|
||||||
|
this.config.ELASTICSEARCH_PORT = esPort;
|
||||||
|
|
||||||
// Update derived fields
|
// Update derived fields
|
||||||
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
this.config.MONGODB_URL = `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${this.config.MONGODB_HOST}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`;
|
||||||
const protocol = this.config.S3_USESSL ? 'https' : 'http';
|
this.config.S3_ENDPOINT = this.config.S3_HOST;
|
||||||
this.config.S3_ENDPOINT = `${protocol}://${this.config.S3_HOST}:${this.config.S3_PORT}`;
|
this.config.ELASTICSEARCH_URL = `http://${this.config.ELASTICSEARCH_USER}:${this.config.ELASTICSEARCH_PASS}@${this.config.ELASTICSEARCH_HOST}:${this.config.ELASTICSEARCH_PORT}`;
|
||||||
|
|
||||||
await this.saveConfig();
|
await this.saveConfig();
|
||||||
|
|
||||||
@@ -432,5 +513,6 @@ export class ServiceConfiguration {
|
|||||||
logger.log('info', ` 📍 MongoDB: ${mongoPort}`);
|
logger.log('info', ` 📍 MongoDB: ${mongoPort}`);
|
||||||
logger.log('info', ` 📍 S3 API: ${s3Port}`);
|
logger.log('info', ` 📍 S3 API: ${s3Port}`);
|
||||||
logger.log('info', ` 📍 S3 Console: ${s3ConsolePort}`);
|
logger.log('info', ` 📍 S3 Console: ${s3ConsolePort}`);
|
||||||
|
logger.log('info', ` 📍 Elasticsearch: ${esPort}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ 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;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = new ServiceConfiguration();
|
this.config = new ServiceConfiguration();
|
||||||
@@ -27,10 +28,129 @@ export class ServiceManager {
|
|||||||
await this.config.loadOrCreate();
|
await this.config.loadOrCreate();
|
||||||
logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
|
logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
|
||||||
|
|
||||||
|
// Load service selection from npmextra.json
|
||||||
|
await this.loadServiceConfiguration();
|
||||||
|
|
||||||
// Validate and update ports if needed
|
// Validate and update ports if needed
|
||||||
await this.config.validateAndUpdatePorts();
|
await this.config.validateAndUpdatePorts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load service configuration from npmextra.json
|
||||||
|
*/
|
||||||
|
private async loadServiceConfiguration(): Promise<void> {
|
||||||
|
const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd());
|
||||||
|
const gitzoneConfig = npmextraConfig.dataFor<any>('gitzone', {});
|
||||||
|
|
||||||
|
// Check if services array exists
|
||||||
|
if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) {
|
||||||
|
// Prompt user to select services
|
||||||
|
const smartinteract = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await smartinteract.askQuestion({
|
||||||
|
name: 'services',
|
||||||
|
type: 'checkbox',
|
||||||
|
message: 'Which services do you want to enable for this project?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'MongoDB', value: 'mongodb' },
|
||||||
|
{ name: 'MinIO (S3)', value: 'minio' },
|
||||||
|
{ name: 'Elasticsearch', value: 'elasticsearch' }
|
||||||
|
],
|
||||||
|
default: ['mongodb', 'minio', 'elasticsearch']
|
||||||
|
});
|
||||||
|
|
||||||
|
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
|
||||||
|
// Save to npmextra.json
|
||||||
|
await this.saveServiceConfiguration(this.enabledServices);
|
||||||
|
} else {
|
||||||
|
this.enabledServices = gitzoneConfig.services;
|
||||||
|
logger.log('info', `🔧 Enabled services: ${this.enabledServices.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save service configuration to npmextra.json
|
||||||
|
*/
|
||||||
|
private async saveServiceConfiguration(services: string[]): Promise<void> {
|
||||||
|
const npmextraPath = plugins.path.join(process.cwd(), 'npmextra.json');
|
||||||
|
let npmextraData: any = {};
|
||||||
|
|
||||||
|
// Read existing npmextra.json if it exists
|
||||||
|
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
||||||
|
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
||||||
|
npmextraData = JSON.parse(content as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update gitzone.services
|
||||||
|
if (!npmextraData.gitzone) {
|
||||||
|
npmextraData.gitzone = {};
|
||||||
|
}
|
||||||
|
npmextraData.gitzone.services = services;
|
||||||
|
|
||||||
|
// Write back to npmextra.json
|
||||||
|
await plugins.smartfs
|
||||||
|
.file(npmextraPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.write(JSON.stringify(npmextraData, null, 2));
|
||||||
|
|
||||||
|
logger.log('ok', `✅ Saved service configuration to npmextra.json`);
|
||||||
|
logger.log('info', `🔧 Enabled services: ${services.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a service is enabled
|
||||||
|
*/
|
||||||
|
private isServiceEnabled(service: string): boolean {
|
||||||
|
if (!this.enabledServices) {
|
||||||
|
return true; // If no configuration, enable all
|
||||||
|
}
|
||||||
|
return this.enabledServices.includes(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start all enabled services
|
||||||
|
*/
|
||||||
|
public async startAll(): Promise<void> {
|
||||||
|
let first = true;
|
||||||
|
if (this.isServiceEnabled('mongodb')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.startMongoDB();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (this.isServiceEnabled('minio')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.startMinIO();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (this.isServiceEnabled('elasticsearch')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.startElasticsearch();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop all enabled services
|
||||||
|
*/
|
||||||
|
public async stopAll(): Promise<void> {
|
||||||
|
let first = true;
|
||||||
|
if (this.isServiceEnabled('mongodb')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.stopMongoDB();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (this.isServiceEnabled('minio')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.stopMinIO();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (this.isServiceEnabled('elasticsearch')) {
|
||||||
|
if (!first) console.log();
|
||||||
|
await this.stopElasticsearch();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start MongoDB service
|
* Start MongoDB service
|
||||||
*/
|
*/
|
||||||
@@ -42,7 +162,7 @@ export class ServiceManager {
|
|||||||
const directories = this.config.getDataDirectories();
|
const directories = this.config.getDataDirectories();
|
||||||
|
|
||||||
// Ensure data directory exists
|
// Ensure data directory exists
|
||||||
await plugins.smartfile.fs.ensureDir(directories.mongo);
|
await plugins.smartfs.directory(directories.mongo).recursive().create();
|
||||||
|
|
||||||
const status = await this.docker.getStatus(containers.mongo);
|
const status = await this.docker.getStatus(containers.mongo);
|
||||||
|
|
||||||
@@ -141,7 +261,7 @@ export class ServiceManager {
|
|||||||
const directories = this.config.getDataDirectories();
|
const directories = this.config.getDataDirectories();
|
||||||
|
|
||||||
// Ensure data directory exists
|
// Ensure data directory exists
|
||||||
await plugins.smartfile.fs.ensureDir(directories.minio);
|
await plugins.smartfs.directory(directories.minio).recursive().create();
|
||||||
|
|
||||||
const status = await this.docker.getStatus(containers.minio);
|
const status = await this.docker.getStatus(containers.minio);
|
||||||
|
|
||||||
@@ -260,6 +380,102 @@ export class ServiceManager {
|
|||||||
logger.log('info', ` Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_ACCESSKEY}/***)`);
|
logger.log('info', ` Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_ACCESSKEY}/***)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start Elasticsearch service
|
||||||
|
*/
|
||||||
|
public async startElasticsearch(): Promise<void> {
|
||||||
|
logger.log('note', '📦 Elasticsearch:');
|
||||||
|
|
||||||
|
const config = this.config.getConfig();
|
||||||
|
const containers = this.config.getContainerNames();
|
||||||
|
const directories = this.config.getDataDirectories();
|
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
await plugins.smartfs.directory(directories.elasticsearch).recursive().create();
|
||||||
|
|
||||||
|
const status = await this.docker.getStatus(containers.elasticsearch);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'running':
|
||||||
|
logger.log('ok', ' Already running ✓');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'stopped':
|
||||||
|
// Check if port mapping matches config
|
||||||
|
const esPortMappings = await this.docker.getPortMappings(containers.elasticsearch);
|
||||||
|
if (esPortMappings && esPortMappings['9200'] !== config.ELASTICSEARCH_PORT) {
|
||||||
|
logger.log('note', ' Port configuration changed, recreating container...');
|
||||||
|
await this.docker.remove(containers.elasticsearch, true);
|
||||||
|
// Fall through to create new container
|
||||||
|
const success = await this.docker.run({
|
||||||
|
name: containers.elasticsearch,
|
||||||
|
image: 'elasticsearch:8.11.0',
|
||||||
|
ports: {
|
||||||
|
[`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200'
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
[directories.elasticsearch]: '/usr/share/elasticsearch/data'
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
'discovery.type': 'single-node',
|
||||||
|
'xpack.security.enabled': 'true',
|
||||||
|
'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS,
|
||||||
|
'ES_JAVA_OPTS': '-Xms512m -Xmx512m'
|
||||||
|
},
|
||||||
|
restart: 'unless-stopped'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
logger.log('ok', ' Recreated with new port ✓');
|
||||||
|
} else {
|
||||||
|
logger.log('error', ' Failed to recreate container');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ports match, just start the container
|
||||||
|
if (await this.docker.start(containers.elasticsearch)) {
|
||||||
|
logger.log('ok', ' Started ✓');
|
||||||
|
} else {
|
||||||
|
logger.log('error', ' Failed to start');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'not_exists':
|
||||||
|
logger.log('note', ' Creating container...');
|
||||||
|
|
||||||
|
const success = await this.docker.run({
|
||||||
|
name: containers.elasticsearch,
|
||||||
|
image: 'elasticsearch:8.11.0',
|
||||||
|
ports: {
|
||||||
|
[`0.0.0.0:${config.ELASTICSEARCH_PORT}`]: '9200'
|
||||||
|
},
|
||||||
|
volumes: {
|
||||||
|
[directories.elasticsearch]: '/usr/share/elasticsearch/data'
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
'discovery.type': 'single-node',
|
||||||
|
'xpack.security.enabled': 'true',
|
||||||
|
'ELASTIC_PASSWORD': config.ELASTICSEARCH_PASS,
|
||||||
|
'ES_JAVA_OPTS': '-Xms512m -Xmx512m'
|
||||||
|
},
|
||||||
|
restart: 'unless-stopped'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
logger.log('ok', ' Created and started ✓');
|
||||||
|
} else {
|
||||||
|
logger.log('error', ' Failed to create container');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log('info', ` Container: ${containers.elasticsearch}`);
|
||||||
|
logger.log('info', ` Port: ${config.ELASTICSEARCH_PORT}`);
|
||||||
|
logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`);
|
||||||
|
logger.log('info', ` Username: ${config.ELASTICSEARCH_USER}`);
|
||||||
|
logger.log('info', ` Password: ${config.ELASTICSEARCH_PASS}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop MongoDB service
|
* Stop MongoDB service
|
||||||
*/
|
*/
|
||||||
@@ -300,6 +516,26 @@ export class ServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop Elasticsearch service
|
||||||
|
*/
|
||||||
|
public async stopElasticsearch(): Promise<void> {
|
||||||
|
logger.log('note', '📦 Elasticsearch:');
|
||||||
|
|
||||||
|
const containers = this.config.getContainerNames();
|
||||||
|
const status = await this.docker.getStatus(containers.elasticsearch);
|
||||||
|
|
||||||
|
if (status === 'running') {
|
||||||
|
if (await this.docker.stop(containers.elasticsearch)) {
|
||||||
|
logger.log('ok', ' Stopped ✓');
|
||||||
|
} else {
|
||||||
|
logger.log('error', ' Failed to stop');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.log('note', ' Not running');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show service status
|
* Show service status
|
||||||
*/
|
*/
|
||||||
@@ -385,6 +621,34 @@ export class ServiceManager {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Elasticsearch status
|
||||||
|
const esStatus = await this.docker.getStatus(containers.elasticsearch);
|
||||||
|
switch (esStatus) {
|
||||||
|
case 'running':
|
||||||
|
logger.log('ok', '📦 Elasticsearch: 🟢 Running');
|
||||||
|
logger.log('info', ` ├─ Container: ${containers.elasticsearch}`);
|
||||||
|
logger.log('info', ` ├─ Port: ${config.ELASTICSEARCH_PORT}`);
|
||||||
|
logger.log('info', ` ├─ Connection: ${config.ELASTICSEARCH_URL}`);
|
||||||
|
logger.log('info', ` └─ Credentials: ${config.ELASTICSEARCH_USER}/${config.ELASTICSEARCH_PASS}`);
|
||||||
|
break;
|
||||||
|
case 'stopped':
|
||||||
|
logger.log('note', '📦 Elasticsearch: 🟡 Stopped');
|
||||||
|
logger.log('info', ` ├─ Container: ${containers.elasticsearch}`);
|
||||||
|
logger.log('info', ` └─ Port: ${config.ELASTICSEARCH_PORT}`);
|
||||||
|
break;
|
||||||
|
case 'not_exists':
|
||||||
|
logger.log('info', '📦 Elasticsearch: ⚪ Not installed');
|
||||||
|
// Check port availability
|
||||||
|
const esPort = parseInt(config.ELASTICSEARCH_PORT);
|
||||||
|
const esAvailable = await helpers.isPortAvailable(esPort);
|
||||||
|
if (!esAvailable) {
|
||||||
|
logger.log('error', ` └─ ⚠️ Port ${esPort} is in use by another process`);
|
||||||
|
} else {
|
||||||
|
logger.log('info', ` └─ Port ${esPort} is available`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -420,6 +684,15 @@ export class ServiceManager {
|
|||||||
logger.log('info', ` Data: ${this.config.getDataDirectories().minio}`);
|
logger.log('info', ` Data: ${this.config.getDataDirectories().minio}`);
|
||||||
logger.log('info', ` Endpoint: ${config.S3_ENDPOINT}`);
|
logger.log('info', ` Endpoint: ${config.S3_ENDPOINT}`);
|
||||||
logger.log('info', ` Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`);
|
logger.log('info', ` Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`);
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
logger.log('note', 'Elasticsearch:');
|
||||||
|
logger.log('info', ` Host: ${config.ELASTICSEARCH_HOST}:${config.ELASTICSEARCH_PORT}`);
|
||||||
|
logger.log('info', ` User: ${config.ELASTICSEARCH_USER}`);
|
||||||
|
logger.log('info', ' Password: ***');
|
||||||
|
logger.log('info', ` Container: ${this.config.getContainerNames().elasticsearch}`);
|
||||||
|
logger.log('info', ` Data: ${this.config.getDataDirectories().elasticsearch}`);
|
||||||
|
logger.log('info', ` Connection: ${config.ELASTICSEARCH_URL}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -478,15 +751,28 @@ export class ServiceManager {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'elasticsearch':
|
||||||
|
case 'es':
|
||||||
|
if (await this.docker.isRunning(containers.elasticsearch)) {
|
||||||
|
helpers.printHeader(`Elasticsearch Logs (last ${lines} lines)`);
|
||||||
|
const logs = await this.docker.logs(containers.elasticsearch, lines);
|
||||||
|
console.log(logs);
|
||||||
|
} else {
|
||||||
|
logger.log('note', 'Elasticsearch container is not running');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'all':
|
case 'all':
|
||||||
case '':
|
case '':
|
||||||
await this.showLogs('mongo', lines);
|
await this.showLogs('mongo', lines);
|
||||||
console.log();
|
console.log();
|
||||||
await this.showLogs('minio', lines);
|
await this.showLogs('minio', lines);
|
||||||
|
console.log();
|
||||||
|
await this.showLogs('elasticsearch', lines);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.log('note', 'Usage: gitzone services logs [mongo|s3|all] [lines]');
|
logger.log('note', 'Usage: gitzone services logs [mongo|s3|elasticsearch|all] [lines]');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,6 +798,13 @@ export class ServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await this.docker.exists(containers.elasticsearch)) {
|
||||||
|
if (await this.docker.remove(containers.elasticsearch, true)) {
|
||||||
|
logger.log('ok', ' Elasticsearch container removed ✓');
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!removed) {
|
if (!removed) {
|
||||||
logger.log('note', ' No containers to remove');
|
logger.log('note', ' No containers to remove');
|
||||||
}
|
}
|
||||||
@@ -524,23 +817,59 @@ export class ServiceManager {
|
|||||||
const directories = this.config.getDataDirectories();
|
const directories = this.config.getDataDirectories();
|
||||||
let cleaned = false;
|
let cleaned = false;
|
||||||
|
|
||||||
if (await plugins.smartfile.fs.fileExists(directories.mongo)) {
|
if (await plugins.smartfs.directory(directories.mongo).exists()) {
|
||||||
await plugins.smartfile.fs.remove(directories.mongo);
|
await plugins.smartfs.directory(directories.mongo).recursive().delete();
|
||||||
logger.log('ok', ' MongoDB data removed ✓');
|
logger.log('ok', ' MongoDB data removed ✓');
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await plugins.smartfile.fs.fileExists(directories.minio)) {
|
if (await plugins.smartfs.directory(directories.minio).exists()) {
|
||||||
await plugins.smartfile.fs.remove(directories.minio);
|
await plugins.smartfs.directory(directories.minio).recursive().delete();
|
||||||
logger.log('ok', ' S3/MinIO data removed ✓');
|
logger.log('ok', ' S3/MinIO data removed ✓');
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await plugins.smartfs.directory(directories.elasticsearch).exists()) {
|
||||||
|
await plugins.smartfs.directory(directories.elasticsearch).recursive().delete();
|
||||||
|
logger.log('ok', ' Elasticsearch data removed ✓');
|
||||||
|
cleaned = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!cleaned) {
|
if (!cleaned) {
|
||||||
logger.log('note', ' No data to clean');
|
logger.log('note', ' No data to clean');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure which services are enabled
|
||||||
|
*/
|
||||||
|
public async configureServices(): Promise<void> {
|
||||||
|
logger.log('note', 'Select which services to enable for this project:');
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
const currentServices = this.enabledServices || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
|
||||||
|
const smartinteract = new plugins.smartinteract.SmartInteract();
|
||||||
|
const response = await smartinteract.askQuestion({
|
||||||
|
name: 'services',
|
||||||
|
type: 'checkbox',
|
||||||
|
message: 'Which services do you want to enable?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'MongoDB', value: 'mongodb' },
|
||||||
|
{ name: 'MinIO (S3)', value: 'minio' },
|
||||||
|
{ name: 'Elasticsearch', value: 'elasticsearch' }
|
||||||
|
],
|
||||||
|
default: currentServices
|
||||||
|
});
|
||||||
|
|
||||||
|
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
|
||||||
|
// Save to npmextra.json
|
||||||
|
await this.saveServiceConfiguration(this.enabledServices);
|
||||||
|
|
||||||
|
logger.log('ok', '✅ Service configuration updated');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconfigure services with new ports
|
* Reconfigure services with new ports
|
||||||
*/
|
*/
|
||||||
@@ -562,6 +891,11 @@ export class ServiceManager {
|
|||||||
logger.log('ok', ' S3/MinIO stopped ✓');
|
logger.log('ok', ' S3/MinIO stopped ✓');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await this.docker.exists(containers.elasticsearch)) {
|
||||||
|
await this.docker.stop(containers.elasticsearch);
|
||||||
|
logger.log('ok', ' Elasticsearch stopped ✓');
|
||||||
|
}
|
||||||
|
|
||||||
// Reconfigure ports
|
// Reconfigure ports
|
||||||
await this.config.reconfigurePorts();
|
await this.config.reconfigurePorts();
|
||||||
|
|
||||||
@@ -576,8 +910,7 @@ export class ServiceManager {
|
|||||||
|
|
||||||
if (response.value) {
|
if (response.value) {
|
||||||
console.log();
|
console.log();
|
||||||
await this.startMongoDB();
|
await this.startAll();
|
||||||
await this.startMinIO();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,11 +42,15 @@ export const getRandomAvailablePort = async (): Promise<number> => {
|
|||||||
/**
|
/**
|
||||||
* Get the project name from package.json or directory
|
* Get the project name from package.json or directory
|
||||||
*/
|
*/
|
||||||
export const getProjectName = (): string => {
|
export const getProjectName = async (): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
const packageJsonPath = plugins.path.join(process.cwd(), 'package.json');
|
const packageJsonPath = plugins.path.join(process.cwd(), 'package.json');
|
||||||
if (plugins.smartfile.fs.fileExistsSync(packageJsonPath)) {
|
if (await plugins.smartfs.file(packageJsonPath).exists()) {
|
||||||
const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath);
|
const content = (await plugins.smartfs
|
||||||
|
.file(packageJsonPath)
|
||||||
|
.encoding('utf8')
|
||||||
|
.read()) as string;
|
||||||
|
const packageJson = JSON.parse(content);
|
||||||
if (packageJson.name) {
|
if (packageJson.name) {
|
||||||
// Sanitize: @fin.cx/skr → fin-cx-skr
|
// Sanitize: @fin.cx/skr → fin-cx-skr
|
||||||
return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-');
|
return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-');
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ export const run = async (argvArg: any) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'config':
|
case 'config':
|
||||||
|
if (service === 'services' || argvArg._[2] === 'services') {
|
||||||
|
await handleConfigureServices(serviceManager);
|
||||||
|
} else {
|
||||||
await serviceManager.showConfig();
|
await serviceManager.showConfig();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'compass':
|
case 'compass':
|
||||||
@@ -73,16 +77,19 @@ async function handleStart(serviceManager: ServiceManager, service: string) {
|
|||||||
await serviceManager.startMinIO();
|
await serviceManager.startMinIO();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'elasticsearch':
|
||||||
|
case 'es':
|
||||||
|
await serviceManager.startElasticsearch();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'all':
|
case 'all':
|
||||||
case '':
|
case '':
|
||||||
await serviceManager.startMongoDB();
|
await serviceManager.startAll();
|
||||||
console.log();
|
|
||||||
await serviceManager.startMinIO();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.log('error', `Unknown service: ${service}`);
|
logger.log('error', `Unknown service: ${service}`);
|
||||||
logger.log('note', 'Use: mongo, s3, or all');
|
logger.log('note', 'Use: mongo, s3, elasticsearch, or all');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,16 +108,19 @@ async function handleStop(serviceManager: ServiceManager, service: string) {
|
|||||||
await serviceManager.stopMinIO();
|
await serviceManager.stopMinIO();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'elasticsearch':
|
||||||
|
case 'es':
|
||||||
|
await serviceManager.stopElasticsearch();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'all':
|
case 'all':
|
||||||
case '':
|
case '':
|
||||||
await serviceManager.stopMongoDB();
|
await serviceManager.stopAll();
|
||||||
console.log();
|
|
||||||
await serviceManager.stopMinIO();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.log('error', `Unknown service: ${service}`);
|
logger.log('error', `Unknown service: ${service}`);
|
||||||
logger.log('note', 'Use: mongo, s3, or all');
|
logger.log('note', 'Use: mongo, s3, elasticsearch, or all');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,14 +143,18 @@ async function handleRestart(serviceManager: ServiceManager, service: string) {
|
|||||||
await serviceManager.startMinIO();
|
await serviceManager.startMinIO();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'elasticsearch':
|
||||||
|
case 'es':
|
||||||
|
await serviceManager.stopElasticsearch();
|
||||||
|
await plugins.smartdelay.delayFor(2000);
|
||||||
|
await serviceManager.startElasticsearch();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'all':
|
case 'all':
|
||||||
case '':
|
case '':
|
||||||
await serviceManager.stopMongoDB();
|
await serviceManager.stopAll();
|
||||||
await serviceManager.stopMinIO();
|
|
||||||
await plugins.smartdelay.delayFor(2000);
|
await plugins.smartdelay.delayFor(2000);
|
||||||
await serviceManager.startMongoDB();
|
await serviceManager.startAll();
|
||||||
console.log();
|
|
||||||
await serviceManager.startMinIO();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -185,6 +199,11 @@ async function handleClean(serviceManager: ServiceManager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleConfigureServices(serviceManager: ServiceManager) {
|
||||||
|
helpers.printHeader('Configure Services');
|
||||||
|
await serviceManager.configureServices();
|
||||||
|
}
|
||||||
|
|
||||||
function showHelp() {
|
function showHelp() {
|
||||||
helpers.printHeader('GitZone Services Manager');
|
helpers.printHeader('GitZone Services Manager');
|
||||||
|
|
||||||
@@ -192,22 +211,30 @@ function showHelp() {
|
|||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
logger.log('note', 'Commands:');
|
logger.log('note', 'Commands:');
|
||||||
logger.log('info', ' start [service] Start services (mongo|s3|all)');
|
logger.log('info', ' start [service] Start services (mongo|s3|elasticsearch|all)');
|
||||||
logger.log('info', ' stop [service] Stop services (mongo|s3|all)');
|
logger.log('info', ' stop [service] Stop services (mongo|s3|elasticsearch|all)');
|
||||||
logger.log('info', ' restart [service] Restart services (mongo|s3|all)');
|
logger.log('info', ' restart [service] Restart services (mongo|s3|elasticsearch|all)');
|
||||||
logger.log('info', ' status Show service status');
|
logger.log('info', ' status Show service status');
|
||||||
logger.log('info', ' config Show current configuration');
|
logger.log('info', ' config Show current configuration');
|
||||||
|
logger.log('info', ' config services Configure which services are enabled');
|
||||||
logger.log('info', ' compass Show MongoDB Compass connection string');
|
logger.log('info', ' compass Show MongoDB Compass connection string');
|
||||||
logger.log('info', ' logs [service] Show logs (mongo|s3|all) [lines]');
|
logger.log('info', ' logs [service] Show logs (mongo|s3|elasticsearch|all) [lines]');
|
||||||
logger.log('info', ' reconfigure Reassign ports and restart services');
|
logger.log('info', ' reconfigure Reassign ports and restart services');
|
||||||
logger.log('info', ' remove Remove all containers');
|
logger.log('info', ' remove Remove all containers');
|
||||||
logger.log('info', ' clean Remove all containers and data ⚠️');
|
logger.log('info', ' clean Remove all containers and data ⚠️');
|
||||||
logger.log('info', ' help Show this help message');
|
logger.log('info', ' help Show this help message');
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
|
logger.log('note', 'Available Services:');
|
||||||
|
logger.log('info', ' • MongoDB (mongo) - Document database');
|
||||||
|
logger.log('info', ' • MinIO (s3) - S3-compatible object storage');
|
||||||
|
logger.log('info', ' • Elasticsearch (elasticsearch) - Search and analytics engine');
|
||||||
|
console.log();
|
||||||
|
|
||||||
logger.log('note', 'Features:');
|
logger.log('note', 'Features:');
|
||||||
logger.log('info', ' • Auto-creates .nogit/env.json with smart defaults');
|
logger.log('info', ' • Auto-creates .nogit/env.json with smart defaults');
|
||||||
logger.log('info', ' • Random ports (20000-30000) to avoid conflicts');
|
logger.log('info', ' • Random ports (20000-30000) for MongoDB/MinIO to avoid conflicts');
|
||||||
|
logger.log('info', ' • Elasticsearch uses standard port 9200');
|
||||||
logger.log('info', ' • Project-specific containers for multi-project support');
|
logger.log('info', ' • Project-specific containers for multi-project support');
|
||||||
logger.log('info', ' • Preserves custom configuration values');
|
logger.log('info', ' • Preserves custom configuration values');
|
||||||
logger.log('info', ' • MongoDB Compass connection support');
|
logger.log('info', ' • MongoDB Compass connection support');
|
||||||
@@ -216,9 +243,10 @@ function showHelp() {
|
|||||||
logger.log('note', 'Examples:');
|
logger.log('note', 'Examples:');
|
||||||
logger.log('info', ' gitzone services start # Start all services');
|
logger.log('info', ' gitzone services start # Start all services');
|
||||||
logger.log('info', ' gitzone services start mongo # Start only MongoDB');
|
logger.log('info', ' gitzone services start mongo # Start only MongoDB');
|
||||||
|
logger.log('info', ' gitzone services start elasticsearch # Start only Elasticsearch');
|
||||||
logger.log('info', ' gitzone services stop # Stop all services');
|
logger.log('info', ' gitzone services stop # Stop all services');
|
||||||
logger.log('info', ' gitzone services status # Check service status');
|
logger.log('info', ' gitzone services status # Check service status');
|
||||||
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 mongo 50 # Show last 50 lines of MongoDB logs');
|
logger.log('info', ' gitzone services logs elasticsearch # Show Elasticsearch logs');
|
||||||
}
|
}
|
||||||
@@ -6,23 +6,36 @@ import * as paths from '../paths.js';
|
|||||||
|
|
||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
|
|
||||||
export let run = () => {
|
export let run = async () => {
|
||||||
const done = plugins.smartpromise.defer();
|
const done = plugins.smartpromise.defer();
|
||||||
logger.log('warn', 'no action specified');
|
logger.log('warn', 'no action specified');
|
||||||
|
|
||||||
|
const dirEntries = await plugins.smartfs.directory(paths.templatesDir).list();
|
||||||
|
const templates: string[] = [];
|
||||||
|
for (const entry of dirEntries) {
|
||||||
|
try {
|
||||||
|
const stats = await plugins.smartfs
|
||||||
|
.file(plugins.path.join(paths.templatesDir, entry.path))
|
||||||
|
.stat();
|
||||||
|
if (stats.isDirectory) {
|
||||||
|
templates.push(entry.name);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Skip entries that can't be accessed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let projects = `\n`;
|
||||||
|
for (const template of templates) {
|
||||||
|
projects += ` - ${template}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
'info',
|
'info',
|
||||||
`
|
`
|
||||||
You can do one of the following things:
|
You can do one of the following things:
|
||||||
* create a new project with 'gitzone template [template]'
|
* create a new project with 'gitzone template [template]'
|
||||||
the following templates exist: ${(() => {
|
the following templates exist: ${projects}
|
||||||
let projects = `\n`;
|
|
||||||
for (const template of plugins.smartfile.fs.listFoldersSync(
|
|
||||||
paths.templatesDir,
|
|
||||||
)) {
|
|
||||||
projects += ` - ${template}\n`;
|
|
||||||
}
|
|
||||||
return projects;
|
|
||||||
})()}
|
|
||||||
* format a project with 'gitzone format'
|
* format a project with 'gitzone format'
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const getTemplatePath = (templateNameArg: string) => {
|
|||||||
* receives a template name and returns wether there is a corresponding template
|
* receives a template name and returns wether there is a corresponding template
|
||||||
*/
|
*/
|
||||||
export const isTemplate = async (templateNameArg: string) => {
|
export const isTemplate = async (templateNameArg: string) => {
|
||||||
return plugins.smartfile.fs.isDirectory(getTemplatePath(templateNameArg));
|
return plugins.smartfs.directory(getTemplatePath(templateNameArg)).exists();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTemplate = async (templateNameArg: string) => {
|
export const getTemplate = async (templateNameArg: string) => {
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ import * as smartupdate from '@push.rocks/smartupdate';
|
|||||||
import * as smartshell from '@push.rocks/smartshell';
|
import * as smartshell from '@push.rocks/smartshell';
|
||||||
import * as smartnetwork from '@push.rocks/smartnetwork';
|
import * as smartnetwork from '@push.rocks/smartnetwork';
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
|
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
|
||||||
import * as smartinteract from '@push.rocks/smartinteract';
|
import * as smartinteract from '@push.rocks/smartinteract';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
|
|
||||||
|
// Create smartfs instance for filesystem operations
|
||||||
|
export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||||
|
|
||||||
export {
|
export {
|
||||||
smartlog,
|
smartlog,
|
||||||
smartlogDestinationLocal,
|
smartlogDestinationLocal,
|
||||||
|
|||||||
Reference in New Issue
Block a user