diff --git a/changelog.md b/changelog.md index 077d5a3..005f40c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,18 @@ # Changelog +## 2025-08-14 - 1.17.0 - feat(services) +Add comprehensive development services management for MongoDB and MinIO containers + +- Implemented `gitzone services` command for managing local development services +- Added MongoDB and MinIO (S3-compatible) container orchestration +- Smart port assignment (20000-30000 range) to avoid conflicts between projects +- Project-specific container names for complete isolation +- Data persistence in `.nogit/` directories +- MongoDB Compass connection string generation with network IP detection +- Auto-configuration via `.nogit/env.json` with secure defaults +- Commands: start, stop, restart, status, config, compass, logs, remove, clean +- Interactive confirmations for destructive operations + ## 2025-08-08 - 1.16.10 - fix(format) Improve concurrency control in caching and rollback modules, refine gitignore custom section handling, and enhance Prettier file processing. diff --git a/package.json b/package.json index 1404f91..6230aad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@git.zone/cli", "private": false, - "version": "1.16.10", + "version": "1.17.0", "description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.", "main": "dist_ts/index.ts", "typings": "dist_ts/index.d.ts", @@ -60,6 +60,11 @@ "@git.zone/tsbuild": "^2.3.2", "@git.zone/tsrun": "^1.3.3", "@git.zone/tstest": "^1.0.96", + "@push.rocks/smartdelay": "^3.0.5", + "@push.rocks/smartfile": "^11.2.0", + "@push.rocks/smartinteract": "^2.0.16", + "@push.rocks/smartnetwork": "^4.1.2", + "@push.rocks/smartshell": "^3.2.3", "@types/node": "^22.15.18" }, "dependencies": { @@ -73,11 +78,8 @@ "@push.rocks/projectinfo": "^5.0.2", "@push.rocks/smartchok": "^1.0.34", "@push.rocks/smartcli": "^4.0.11", - "@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdiff": "^1.0.3", - "@push.rocks/smartfile": "^11.2.0", "@push.rocks/smartgulp": "^3.0.4", - "@push.rocks/smartinteract": "^2.0.15", "@push.rocks/smartjson": "^5.0.20", "@push.rocks/smartlegal": "^1.0.27", "@push.rocks/smartlog": "^3.0.9", @@ -89,7 +91,6 @@ "@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartscaf": "^4.0.16", - "@push.rocks/smartshell": "^3.2.3", "@push.rocks/smartstream": "^3.2.5", "@push.rocks/smartunique": "^3.0.9", "@push.rocks/smartupdate": "^2.0.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a1c71d..fc3427d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - peek-readable: 5.3.1 - importers: .: @@ -41,18 +38,12 @@ importers: '@push.rocks/smartcli': specifier: ^4.0.11 version: 4.0.11 - '@push.rocks/smartdelay': - specifier: ^3.0.5 - version: 3.0.5 - '@push.rocks/smartfile': - specifier: ^11.2.0 - version: 11.2.0 + '@push.rocks/smartdiff': + specifier: ^1.0.3 + version: 1.0.3 '@push.rocks/smartgulp': specifier: ^3.0.4 version: 3.0.4 - '@push.rocks/smartinteract': - specifier: ^2.0.15 - version: 2.0.16 '@push.rocks/smartjson': specifier: ^5.0.20 version: 5.0.20 @@ -86,9 +77,6 @@ importers: '@push.rocks/smartscaf': specifier: ^4.0.16 version: 4.0.16 - '@push.rocks/smartshell': - specifier: ^3.2.3 - version: 3.2.3 '@push.rocks/smartstream': specifier: ^3.2.5 version: 3.2.5 @@ -117,9 +105,21 @@ importers: '@git.zone/tstest': specifier: ^1.0.96 version: 1.0.96(@aws-sdk/credential-providers@3.750.0)(socks@2.8.4)(typescript@5.8.3) - '@push.rocks/smartdiff': - specifier: ^1.0.3 - version: 1.0.3 + '@push.rocks/smartdelay': + specifier: ^3.0.5 + version: 3.0.5 + '@push.rocks/smartfile': + specifier: ^11.2.0 + version: 11.2.0 + '@push.rocks/smartinteract': + specifier: ^2.0.16 + version: 2.0.16 + '@push.rocks/smartnetwork': + specifier: ^4.1.2 + version: 4.1.2 + '@push.rocks/smartshell': + specifier: ^3.2.3 + version: 3.2.3 '@types/node': specifier: ^22.15.18 version: 22.15.18 @@ -1006,6 +1006,9 @@ packages: '@push.rocks/smartnetwork@3.0.2': resolution: {integrity: sha512-s6CNGzQ1n/d/6cOKXbxeW6/tO//dr1woLqI01g7XhqTriw0nsm2G2kWaZh2J0VOguGNWBgQVCIpR0LjdRNWb3g==} + '@push.rocks/smartnetwork@4.1.2': + resolution: {integrity: sha512-TjucG72ooHgzAUpNu2LAv4iFoettmZq2aEWhhzIa7AKcOvt4yxsk3Vl73guhKRohTfhdRauPcH5OHISLUHJbYA==} + '@push.rocks/smartnpm@2.0.4': resolution: {integrity: sha512-ljRPqnUsXzL5qnuAEt5POy0NnfKs7eYPuuJPJjYiK9VUdP/CyF4h14qTB4H816vNEuF7VU/ASRtz0qDlXmrztg==} @@ -1024,6 +1027,9 @@ packages: '@push.rocks/smartpdf@3.2.2': resolution: {integrity: sha512-SKGNHz7HsgU6uVSVrRCL13kIeAFMvd4oQBLI3VmPcMkxXfWNPJkb6jKknqP8bhobWA/ryJS+3Dj///UELUvVKQ==} + '@push.rocks/smartping@1.0.8': + resolution: {integrity: sha512-Fvx1Db6hSsDOI6pdiCuS9GjtOX8ugx865YQrPg5vK2iw6Qj/srwyXcWLFYt+19WVKtvtWDJIAKbW+q3bXFsCeA==} + '@push.rocks/smartpnpm@1.0.6': resolution: {integrity: sha512-AD0U4n53LBdBnj9MXAMF7cAqjyE0j3xbTH7Bd1v5ywjt3aFOJockAwDBOP+3dEK1QUHM17p+VP9HdX1faTCtzw==} @@ -1582,6 +1588,9 @@ packages: '@types/default-gateway@3.0.1': resolution: {integrity: sha512-tpu0hp+AOIzwdAHyZPzLE5pCf9uT0pb+xZ76T4S7MrY2YTVq918Q7Q2VQ3KCVQqYxM7nxuCK/SL3X97jBEIeKQ==} + '@types/default-gateway@7.2.2': + resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==} + '@types/diff@5.2.3': resolution: {integrity: sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==} @@ -2152,6 +2161,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clone-regexp@3.0.0: + resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==} + engines: {node: '>=12'} + clone@2.1.2: resolution: {integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=} engines: {node: '>=0.8'} @@ -2217,6 +2230,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-hrtime@5.0.0: + resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} + engines: {node: '>=12'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2796,6 +2813,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-timeout@0.1.1: + resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==} + engines: {node: '>=14.16'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2870,6 +2891,10 @@ packages: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} + got@13.0.0: + resolution: {integrity: sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==} + engines: {node: '>=16'} + gpt-tokenizer@2.9.0: resolution: {integrity: sha512-YSpexBL/k4bfliAzMrRqn3M6+it02LutVyhVpDeMKrC/O9+pCe/5s8U2hYKa2vFLD5/vHhsKc8sOn/qGqII8Kg==} @@ -3121,6 +3146,10 @@ packages: resolution: {integrity: sha512-4B4XA2HEIm/PY+OSpeMBXr8pGWBYbXuHgjMAqrwbLO3CPTCAd9ArEJzBUKGZtk9viY6+aSfadGnWyjY3ydYZkw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + is-ip@5.0.1: + resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==} + engines: {node: '>=14.16'} + is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} @@ -3145,6 +3174,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-regexp@3.1.0: + resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} + engines: {node: '>=12'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4091,6 +4124,10 @@ packages: resolution: {integrity: sha512-+6bkjnf0yQ4+tZV0zJv1017DiIF7y6R4yg17Mrhhkc25L7dtQtXWHgSCrz9BbLL4OeTFbPK4EALXqJUrwCIWXw==} engines: {node: '>=14.16'} + public-ip@7.0.1: + resolution: {integrity: sha512-DdNcqcIbI0wEeCBcqX+bmZpUCvrDMJHXE553zgyG1MZ8S1a/iCCxmK9iTjjql+SpHSv4cZkmRv5/zGYW93AlCw==} + engines: {node: '>=18'} + pump@2.0.1: resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} @@ -4526,6 +4563,10 @@ packages: stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + super-regex@0.2.0: + resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==} + engines: {node: '>=14.16'} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -4581,6 +4622,10 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + time-span@5.1.0: + resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} + engines: {node: '>=12'} + tiny-worker@2.3.0: resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} @@ -6650,6 +6695,16 @@ snapshots: public-ip: 6.0.2 systeminformation: 5.25.11 + '@push.rocks/smartnetwork@4.1.2': + dependencies: + '@push.rocks/smartping': 1.0.8 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartstring': 4.0.15 + '@types/default-gateway': 7.2.2 + isopen: 1.3.0 + public-ip: 7.0.1 + systeminformation: 5.25.11 + '@push.rocks/smartnpm@2.0.4': dependencies: '@push.rocks/consolecolor': 2.0.2 @@ -6707,6 +6762,11 @@ snapshots: - typescript - utf-8-validate + '@push.rocks/smartping@1.0.8': + dependencies: + '@types/ping': 0.4.4 + ping: 0.4.4 + '@push.rocks/smartpnpm@1.0.6': dependencies: '@push.rocks/smartshell': 3.2.3 @@ -7670,6 +7730,8 @@ snapshots: '@types/default-gateway@3.0.1': {} + '@types/default-gateway@7.2.2': {} + '@types/diff@5.2.3': {} '@types/express-serve-static-core@4.19.6': @@ -8326,6 +8388,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone-regexp@3.0.0: + dependencies: + is-regexp: 3.1.0 + clone@2.1.2: {} co-body@6.2.0: @@ -8390,6 +8456,8 @@ snapshots: content-type@1.0.5: {} + convert-hrtime@5.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} @@ -9008,6 +9076,8 @@ snapshots: function-bind@1.1.2: {} + function-timeout@0.1.1: {} + get-caller-file@2.0.5: {} get-east-asian-width@1.3.0: {} @@ -9123,6 +9193,20 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 + got@13.0.0: + dependencies: + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + gpt-tokenizer@2.9.0: {} graceful-fs@4.2.10: {} @@ -9393,6 +9477,11 @@ snapshots: dependencies: ip-regex: 5.0.0 + is-ip@5.0.1: + dependencies: + ip-regex: 5.0.0 + super-regex: 0.2.0 + is-nan@1.3.2: dependencies: call-bind: 1.0.7 @@ -9413,6 +9502,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + is-regexp@3.1.0: {} + is-stream@2.0.1: {} is-stream@4.0.1: {} @@ -10551,6 +10642,12 @@ snapshots: got: 12.6.1 is-ip: 4.0.0 + public-ip@7.0.1: + dependencies: + dns-socket: 4.2.2 + got: 13.0.0 + is-ip: 5.0.1 + pump@2.0.1: dependencies: end-of-stream: 1.4.4 @@ -11102,6 +11199,12 @@ snapshots: stubborn-fs@1.2.5: {} + super-regex@0.2.0: + dependencies: + clone-regexp: 3.0.0 + function-timeout: 0.1.1 + time-span: 5.1.0 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -11183,6 +11286,10 @@ snapshots: dependencies: readable-stream: 3.6.2 + time-span@5.1.0: + dependencies: + convert-hrtime: 5.0.0 + tiny-worker@2.3.0: dependencies: esm: 3.2.25 diff --git a/readme.md b/readme.md index 0f9900d..b140d1c 100644 --- a/readme.md +++ b/readme.md @@ -7,7 +7,7 @@ ## 🎯 What is gitzone? -gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, and seamless version control. Whether you're bootstrapping a new TypeScript project, maintaining code quality, or managing complex multi-repository setups, gitzone has got you covered. +gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, seamless version control, and development service orchestration. Whether you're bootstrapping a new TypeScript project, maintaining code quality, managing complex multi-repository setups, or spinning up local development databases, gitzone has got you covered. ## 🏃‍♂️ Quick Start @@ -23,7 +23,7 @@ pnpm add -g @git.zone/cli Once installed, you can use either `gitzone` or the shorter `gzone` command from anywhere in your terminal. -### Your First Command +### Your First Commands ```bash # Create a new TypeScript npm package @@ -32,12 +32,66 @@ gitzone template npm # Format your entire codebase gitzone format +# Start local MongoDB and MinIO services +gitzone services start + # Create a semantic commit gitzone commit ``` ## 🛠️ Core Features +### 🐳 Development Services Management (NEW!) + +Effortlessly manage local MongoDB and MinIO (S3-compatible) services for your development environment: + +```bash +gitzone services [command] +``` + +**Available commands:** + +- **`start [service]`** - Start services (mongo|s3|all) +- **`stop [service]`** - Stop services (mongo|s3|all) +- **`restart [service]`** - Restart services +- **`status`** - Show current service status +- **`config`** - Display configuration details +- **`compass`** - Get MongoDB Compass connection string with network IP +- **`logs [service] [lines]`** - View service logs +- **`remove`** - Remove containers (preserves data) +- **`clean`** - Remove containers AND data (⚠️ destructive) + +**Key features:** + +- 🎲 **Smart port assignment** - Automatically assigns random ports (20000-30000) to avoid conflicts +- 📦 **Project isolation** - Each project gets its own containers with unique names +- 💾 **Data persistence** - Data stored in `.nogit/` directories survives container restarts +- 🔗 **MongoDB Compass support** - Instantly get connection strings for GUI access +- 🌐 **Network IP detection** - Automatically detects your local network IP for remote connections +- ⚙️ **Auto-configuration** - Creates `.nogit/env.json` with smart defaults + +**Example workflow:** + +```bash +# Start all services for your project +gitzone services start + +# Check what's running +gitzone services status + +# Get MongoDB Compass connection string +gitzone services compass +# Output: mongodb://defaultadmin:defaultpass@192.168.1.100:27018/myproject?authSource=admin + +# View MongoDB logs +gitzone services logs mongo 50 + +# Stop services when done +gitzone services stop +``` + +The services are configured via `.nogit/env.json` which is automatically created with secure defaults and random ports for each project. + ### 📦 Project Templates Instantly scaffold production-ready projects with best practices built-in: @@ -279,43 +333,52 @@ Customize gitzone behavior through `npmextra.json`: ### For New Projects 1. Start with a template: `gitzone template npm` -2. Customize the generated structure -3. Run initial format: `gitzone format` -4. Set up CI/CD: `gitzone open ci` +2. Set up local services: `gitzone services start` +3. Customize the generated structure +4. Run initial format: `gitzone format` +5. Set up CI/CD: `gitzone open ci` ### For Existing Projects 1. Initialize: `gitzone start` 2. Format codebase: `gitzone format --dry-run` (preview first!) 3. Apply formatting: `gitzone format --yes` -4. Commit changes: `gitzone commit` +4. Set up services: `gitzone services start` +5. Commit changes: `gitzone commit` ### For Teams 1. Document format preferences in `npmextra.json` -2. Use `--save-plan` for reviewable format changes -3. Enable rollback for safety -4. Standardize commit conventions +2. Share `.nogit/env.json` template for consistent service setup +3. Use `--save-plan` for reviewable format changes +4. Enable rollback for safety +5. Standardize commit conventions ## 🎯 Common Workflows -### Clean Development Cycle +### Full-Stack Development Cycle ```bash # 1. Start fresh gitzone start -# 2. Make changes +# 2. Spin up databases and services +gitzone services start + +# 3. Make changes # ... your development work ... -# 3. Format code +# 4. Check service logs if needed +gitzone services logs mongo + +# 5. Format code gitzone format -# 4. Commit with semantic versioning +# 6. Commit with semantic versioning gitzone commit -# 5. Deploy (if CI/CD configured) -# Automatic via git push +# 7. Stop services when done +gitzone services stop ``` ### Multi-Repository Management @@ -349,6 +412,25 @@ gitzone format --from-plan format-changes.json gitzone format --rollback ``` +### Database-Driven Development + +```bash +# 1. Start MongoDB and MinIO +gitzone services start + +# 2. Get connection string for your app +gitzone services config + +# 3. Connect with MongoDB Compass +gitzone services compass + +# 4. Monitor services +gitzone services status + +# 5. Clean everything when done +gitzone services clean # ⚠️ Warning: deletes data +``` + ## 🔌 Integrations ### CI/CD Platforms @@ -363,6 +445,9 @@ gitzone format --rollback - **Prettier** - Code formatting - **ESLint** - Linting (via format modules) - **npm/pnpm** - Package management +- **MongoDB** - Local database service +- **MinIO** - S3-compatible object storage +- **MongoDB Compass** - Database GUI integration ### Version Control @@ -377,6 +462,8 @@ gitzone format --rollback 3. **Leverage templates**: Start projects right with proven structures 4. **Enable caching**: Dramatically speeds up formatting operations 5. **Save format plans**: Review changes before applying in production +6. **Port management**: Let services auto-assign ports to avoid conflicts +7. **Use MongoDB Compass**: `gitzone services compass` for visual DB management ## 🐛 Troubleshooting @@ -396,6 +483,18 @@ Ensure Docker daemon is running: docker info ``` +### Services Won't Start + +Check for port conflicts: + +```bash +# Services auto-assign ports, but you can check the config +cat .nogit/env.json + +# Verify Docker is running +docker ps +``` + ### Template Creation Issues Verify npm/pnpm is properly configured: @@ -404,6 +503,12 @@ Verify npm/pnpm is properly configured: npm config get registry ``` +### MongoDB Connection Issues + +- Ensure services are running: `gitzone services status` +- Check firewall settings for the assigned ports +- Use `gitzone services compass` for the correct connection string + ## 📈 Performance gitzone is optimized for speed: @@ -412,6 +517,8 @@ gitzone is optimized for speed: - **Smart caching** to avoid redundant work - **Incremental updates** for meta repositories - **Minimal dependencies** for fast installation +- **Isolated services** prevent resource conflicts +- **Auto port assignment** eliminates manual configuration ## License and Legal Information @@ -430,4 +537,4 @@ Registered at District court Bremen HRB 35230 HB, Germany For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. -By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. +By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. \ No newline at end of file diff --git a/readme.plan.md b/readme.plan.md index 53709ac..1445017 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -1,182 +1,121 @@ -# Gitzone Format Module Improvement Plan - -Please reread /home/philkunz/.claude/CLAUDE.md before proceeding with any implementation. +# GitZone Services Command Implementation Plan ## Overview +Implement the `gitzone services` command to manage MongoDB and MinIO containers for development projects. -This plan outlines improvements for the gitzone format module to enhance its functionality, reliability, and maintainability. +## Tasks -## Phase 1: Core Improvements (High Priority) - COMPLETED ✅ +### Module Structure Setup +- [x] Create `ts/mod_services/` directory +- [x] Create `mod.plugins.ts` with required imports +- [x] Create `helpers.ts` with utility functions +- [x] Create `classes.serviceconfiguration.ts` for config handling +- [x] Create `classes.dockercontainer.ts` for Docker operations +- [x] Create `classes.servicemanager.ts` for service management +- [x] Create `index.ts` with main command logic -### 1. Enhanced Error Handling & Recovery ✅ +### Core Functionality +- [x] Implement ServiceConfiguration class + - [x] Load/create `.nogit/env.json` configuration + - [x] Generate random available ports (20000-30000 range) + - [x] Preserve existing custom values + - [x] Provide default values for missing fields -- [x] Implement rollback mechanism for failed format operations -- [x] Add detailed error messages with recovery suggestions -- [x] Create a `--dry-run` flag to preview changes before applying -- [x] Add transaction-like behavior: all-or-nothing formatting -- [x] Implement plan → action workflow as default behavior +- [x] Implement DockerContainer class + - [x] Check container status + - [x] Start/stop/restart containers + - [x] Execute Docker commands + - [x] Handle container logs + - [x] Manage volumes and port bindings -### 2. Complete Missing Functionality ✅ +- [x] Implement ServiceManager class + - [x] Manage MongoDB containers + - [x] Manage MinIO containers + - [x] Handle container lifecycle + - [x] Generate project-specific container names + - [x] Manage data directories in `.nogit/` + - [x] Generate MongoDB Compass connection strings -- [x] Implement the `ensureDependency` function in format.packagejson.ts -- [x] Develop the copy module for file pattern-based copying -- [x] Add dependency version constraint management -- [x] Support workspace/monorepo configurations (via configuration) +### Commands Implementation +- [x] `start` command - Start services (mongo|s3|all) +- [x] `stop` command - Stop services (mongo|s3|all) +- [x] `restart` command - Restart services (mongo|s3|all) +- [x] `status` command - Show service status +- [x] `config` command - Show current configuration +- [x] `compass` command - Show MongoDB Compass connection string +- [x] `logs` command - Show service logs with line count +- [x] `remove` command - Remove containers (preserve data) +- [x] `clean` command - Remove containers and data -### 3. Configuration & Flexibility ✅ +### Integration +- [x] Add `@push.rocks/smartshell` to main plugins.ts +- [x] Add `@push.rocks/smartnetwork` to main plugins.ts +- [x] Add `@push.rocks/smartinteraction` to main plugins.ts +- [x] Register services command in `gitzone.cli.ts` -- [x] Extend npmextra.json gitzone configuration section -- [x] Allow custom license exclusion/inclusion lists -- [x] Make format steps configurable (skip/include specific modules) -- [x] Support custom template directories (via configuration) -- [x] Add format profiles for different project types +### Features +- [x] Auto-configuration with smart defaults +- [x] Random port assignment to avoid conflicts +- [x] Project isolation with unique container names +- [x] Data persistence in `.nogit/` directories +- [x] Status display (running/stopped/not installed) +- [x] Interactive confirmations for destructive operations +- [x] Colored console output +- [x] MinIO bucket auto-creation +- [x] MongoDB Compass connection string with network IP -### 4. Architecture Changes ✅ +### Testing +- [ ] Test service start/stop operations +- [ ] Test configuration creation and updates +- [ ] Test port collision handling +- [ ] Test data persistence +- [ ] Test MongoDB Compass connection string generation +- [ ] Test all command variations -- [x] Introduce a `FormatContext` class to manage state across modules -- [x] Create abstract `BaseFormatter` class for consistent module structure -- [x] Implement event system for inter-module communication (via context) -- [x] Add validation layer before format execution -- [x] Implement `FormatPlanner` class for plan → action workflow +## Configuration Format +```json +{ + "PROJECT_NAME": "derived-from-package-name", + "MONGODB_HOST": "localhost", + "MONGODB_NAME": "project-name", + "MONGODB_PORT": "random-port", + "MONGODB_USER": "defaultadmin", + "MONGODB_PASS": "defaultpass", + "S3_HOST": "localhost", + "S3_PORT": "random-port", + "S3_CONSOLE_PORT": "s3-port+1", + "S3_USER": "defaultadmin", + "S3_PASS": "defaultpass", + "S3_BUCKET": "project-name-documents" +} +``` -## Phase 2: Performance & Reporting (Medium Priority) - COMPLETED ✅ +## Command Examples +```bash +gitzone services start # Start all services +gitzone services start mongo # Start only MongoDB +gitzone services stop # Stop all services +gitzone services status # Check service status +gitzone services config # Show configuration +gitzone services compass # Show MongoDB Compass connection string +gitzone services logs mongo 50 # Show last 50 lines of MongoDB logs +gitzone services remove # Remove containers (preserve data) +gitzone services clean # Remove containers and data +``` -### 5. Performance Optimizations ✅ +## Progress Notes +Implementation started: 2025-08-14 +Implementation completed: 2025-08-14 -- [x] Implement parallel execution for independent format modules -- [x] Add file change detection to skip unchanged files -- [x] Create format cache to track last formatted state -- [x] Optimize Prettier runs by batching files +## Summary +Successfully implemented the `gitzone services` command in TypeScript, providing a complete replacement for the `services.sh` shell script. The implementation includes: -### 6. Enhanced Reporting & Visibility ✅ +1. **Complete Docker service management** for MongoDB and MinIO containers +2. **Smart configuration management** with automatic port assignment and conflict avoidance +3. **MongoDB Compass support** with network IP detection for remote connections +4. **Project isolation** using project-specific container names +5. **Data persistence** in `.nogit/` directories +6. **Interactive confirmations** for destructive operations +7. **Comprehensive command set** including start, stop, restart, status, config, compass, logs, remove, and clean commands -- [x] Generate comprehensive format report showing all changes -- [x] Add diff view for file modifications -- [x] Create verbose logging option -- [x] Add format statistics (files changed, time taken, etc.) - -## Phase 3: Advanced Features (Lower Priority) - PARTIALLY COMPLETED - -### 7. Better Integration & Extensibility ⏳ - -- [ ] Create plugin system for custom format modules -- [ ] Add hooks for pre/post format operations -- [ ] Support custom validation rules -- [ ] Integrate with git hooks for pre-commit formatting - -### 8. Improved Template Integration ⏳ - -- [ ] Better error handling when smartscaf operations fail -- [ ] Add pre/post template hooks for custom processing -- [ ] Validate template results before proceeding with format -- [ ] Support skipping template updates via configuration - -### 9. Enhanced License Management ⏳ - -- [ ] Make license checking configurable (partial) -- [ ] Add license compatibility matrix -- [x] Support license exceptions for specific packages -- [ ] Generate license report for compliance - -### 10. Better Package.json Management ⏳ - -- [ ] Smart dependency sorting and grouping -- [ ] Automated script generation based on project type -- [ ] Support for pnpm workspace configurations -- [ ] Validation of package.json schema - -### 11. Quality of Life Improvements ⏳ - -- [ ] Interactive mode for format configuration -- [ ] Undo/redo capability for format operations -- [ ] Format presets for common scenarios -- [x] Better progress indicators and user feedback - -## Implementation Status - -### ✅ Completed Features - -1. **Rollback Mechanism** - - Full backup/restore functionality - - Manifest tracking and integrity checks - - CLI commands for rollback operations - -2. **Plan → Action Workflow** - - Two-phase approach (analyze then execute) - - Interactive confirmation - - Dry-run support - -3. **Configuration System** - - Comprehensive npmextra.json support - - Module control (skip/only/order) - - Cache configuration - - Parallel execution settings - -4. **Performance Improvements** - - Parallel execution by dependency analysis - - File change caching - - Prettier batching - - Execution time tracking - -5. **Reporting & Statistics** - - Detailed diff views - - Execution statistics - - Verbose logging mode - - Save reports to file - -6. **Architecture Improvements** - - BaseFormatter abstract class - - FormatContext for state management - - DependencyAnalyzer for parallel execution - - Type-safe interfaces - -### 🚧 Partially Completed - -1. **License Management** - - Basic configuration support - - Exception handling for specific packages - - Need: compatibility matrix, compliance reports - -2. **Package.json Management** - - Basic ensureDependency implementation - - Need: smart sorting, script generation, validation - -### ⏳ Not Started - -1. **Plugin System** - - Need to design plugin API - - Hook system for pre/post operations - - Custom validation rules - -2. **Git Integration** - - Pre-commit hooks - - Automatic formatting on commit - -3. **Advanced UI** - - Interactive configuration mode - - Undo/redo capability - - Format presets - -## Technical Achievements - -1. **Type Safety**: All new code uses TypeScript interfaces and types -2. **Error Handling**: Comprehensive try-catch blocks with rollback -3. **API Compatibility**: Updated to use latest smartfile/smartnpm APIs -4. **Testing**: Ready for comprehensive test suite -5. **Performance**: Significant improvements through caching and parallelization - -## Next Steps - -1. Write comprehensive tests for all new functionality -2. Create user documentation for new features -3. Consider plugin API design for extensibility -4. Implement remaining Phase 3 features based on user feedback -5. Performance benchmarking and optimization - -## Success Metrics Achieved - -- ✅ Reduced error rates through rollback mechanism -- ✅ Faster execution through parallel processing and caching -- ✅ Enhanced user control through configuration -- ✅ Better visibility through reporting and statistics -- ✅ Improved maintainability through better architecture +The module is fully integrated into the gitzone CLI and ready for testing. \ No newline at end of file diff --git a/ts/gitzone.cli.ts b/ts/gitzone.cli.ts index e94083a..ad9d1a3 100644 --- a/ts/gitzone.cli.ts +++ b/ts/gitzone.cli.ts @@ -131,6 +131,14 @@ export let run = async () => { modHelpers.run(argvArg); }); + /** + * manage development services (MongoDB, S3/MinIO) + */ + gitzoneSmartcli.addCommand('services').subscribe(async (argvArg) => { + const modServices = await import('./mod_services/index.js'); + await modServices.run(argvArg); + }); + // start parsing of the cli gitzoneSmartcli.startParse(); return await done.promise; diff --git a/ts/mod_services/classes.dockercontainer.ts b/ts/mod_services/classes.dockercontainer.ts new file mode 100644 index 0000000..270594a --- /dev/null +++ b/ts/mod_services/classes.dockercontainer.ts @@ -0,0 +1,226 @@ +import * as plugins from './mod.plugins.js'; +import * as helpers from './helpers.js'; + +export type ContainerStatus = 'running' | 'stopped' | 'not_exists'; + +export interface IDockerRunOptions { + name: string; + image: string; + ports?: { [key: string]: string }; + volumes?: { [key: string]: string }; + environment?: { [key: string]: string }; + restart?: string; + command?: string; +} + +export class DockerContainer { + private smartshell: plugins.smartshell.Smartshell; + + constructor() { + this.smartshell = new plugins.smartshell.Smartshell({ + executor: 'bash', + }); + } + + /** + * Check if Docker is installed and available + */ + public async checkDocker(): Promise { + try { + const result = await this.smartshell.exec('docker --version'); + return result.exitCode === 0; + } catch (error) { + return false; + } + } + + /** + * Get container status + */ + public async getStatus(containerName: string): Promise { + try { + // Check if running + const runningResult = await this.smartshell.exec( + `docker ps --format '{{.Names}}' | grep -q "^${containerName}$"` + ); + + if (runningResult.exitCode === 0) { + return 'running'; + } + + // Check if exists but stopped + const existsResult = await this.smartshell.exec( + `docker ps -a --format '{{.Names}}' | grep -q "^${containerName}$"` + ); + + if (existsResult.exitCode === 0) { + return 'stopped'; + } + + return 'not_exists'; + } catch (error) { + return 'not_exists'; + } + } + + /** + * Start a container + */ + public async start(containerName: string): Promise { + try { + const result = await this.smartshell.exec(`docker start ${containerName}`); + return result.exitCode === 0; + } catch (error) { + return false; + } + } + + /** + * Stop a container + */ + public async stop(containerName: string): Promise { + try { + const result = await this.smartshell.exec(`docker stop ${containerName}`); + return result.exitCode === 0; + } catch (error) { + return false; + } + } + + /** + * Remove a container + */ + public async remove(containerName: string, force: boolean = false): Promise { + try { + const forceFlag = force ? '-f' : ''; + const result = await this.smartshell.exec(`docker rm ${forceFlag} ${containerName}`); + return result.exitCode === 0; + } catch (error) { + return false; + } + } + + /** + * Run a new container + */ + public async run(options: IDockerRunOptions): Promise { + let command = 'docker run -d'; + + // Add name + command += ` --name ${options.name}`; + + // Add ports + if (options.ports) { + for (const [hostPort, containerPort] of Object.entries(options.ports)) { + command += ` -p ${hostPort}:${containerPort}`; + } + } + + // Add volumes + if (options.volumes) { + for (const [hostPath, containerPath] of Object.entries(options.volumes)) { + command += ` -v "${hostPath}:${containerPath}"`; + } + } + + // Add environment variables + if (options.environment) { + for (const [key, value] of Object.entries(options.environment)) { + command += ` -e ${key}="${value}"`; + } + } + + // Add restart policy + if (options.restart) { + command += ` --restart ${options.restart}`; + } + + // Add image + command += ` ${options.image}`; + + // Add command if provided + if (options.command) { + command += ` ${options.command}`; + } + + try { + const result = await this.smartshell.exec(command); + return result.exitCode === 0; + } catch (error) { + helpers.printMessage(`Failed to run container: ${error.message}`, 'red'); + return false; + } + } + + /** + * Execute a command in a running container + */ + public async exec(containerName: string, command: string): Promise { + try { + const result = await this.smartshell.exec(`docker exec ${containerName} ${command}`); + if (result.exitCode === 0) { + return result.stdout; + } + return ''; + } catch (error) { + return ''; + } + } + + /** + * Get container logs + */ + public async logs(containerName: string, lines?: number): Promise { + try { + const tailFlag = lines ? `--tail ${lines}` : ''; + const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`); + return result.stdout; + } catch (error) { + return `Error getting logs: ${error.message}`; + } + } + + /** + * Check if a container exists + */ + public async exists(containerName: string): Promise { + const status = await this.getStatus(containerName); + return status !== 'not_exists'; + } + + /** + * Check if a container is running + */ + public async isRunning(containerName: string): Promise { + const status = await this.getStatus(containerName); + return status === 'running'; + } + + /** + * Wait for a container to be ready + */ + public async waitForReady(containerName: string, maxAttempts: number = 30): Promise { + for (let i = 0; i < maxAttempts; i++) { + if (await this.isRunning(containerName)) { + return true; + } + await plugins.smartdelay.delayFor(1000); + } + return false; + } + + /** + * Get container information + */ + public async inspect(containerName: string): Promise { + try { + const result = await this.smartshell.exec(`docker inspect ${containerName}`); + if (result.exitCode === 0) { + return JSON.parse(result.stdout); + } + return null; + } catch (error) { + return null; + } + } +} \ No newline at end of file diff --git a/ts/mod_services/classes.serviceconfiguration.ts b/ts/mod_services/classes.serviceconfiguration.ts new file mode 100644 index 0000000..3e4e0e6 --- /dev/null +++ b/ts/mod_services/classes.serviceconfiguration.ts @@ -0,0 +1,245 @@ +import * as plugins from './mod.plugins.js'; +import * as helpers from './helpers.js'; + +export interface IServiceConfig { + PROJECT_NAME: string; + MONGODB_HOST: string; + MONGODB_NAME: string; + MONGODB_PORT: string; + MONGODB_USER: string; + MONGODB_PASS: string; + S3_HOST: string; + S3_PORT: string; + S3_CONSOLE_PORT: string; + S3_USER: string; + S3_PASS: string; + S3_BUCKET: string; +} + +export class ServiceConfiguration { + private configPath: string; + private config: IServiceConfig; + + constructor() { + this.configPath = plugins.path.join(process.cwd(), '.nogit', 'env.json'); + } + + /** + * Load or create the configuration + */ + public async loadOrCreate(): Promise { + await this.ensureNogitDirectory(); + + if (await this.configExists()) { + await this.loadConfig(); + await this.updateMissingFields(); + } else { + await this.createDefaultConfig(); + } + + return this.config; + } + + /** + * Get the current configuration + */ + public getConfig(): IServiceConfig { + return this.config; + } + + /** + * Save the configuration to file + */ + public async saveConfig(): Promise { + await plugins.smartfile.memory.toFs( + JSON.stringify(this.config, null, 2), + this.configPath + ); + } + + /** + * Ensure .nogit directory exists + */ + private async ensureNogitDirectory(): Promise { + const nogitPath = plugins.path.join(process.cwd(), '.nogit'); + await plugins.smartfile.fs.ensureDir(nogitPath); + } + + /** + * Check if configuration file exists + */ + private async configExists(): Promise { + return plugins.smartfile.fs.fileExists(this.configPath); + } + + /** + * Load configuration from file + */ + private async loadConfig(): Promise { + const configContent = await plugins.smartfile.fs.toStringSync(this.configPath); + this.config = JSON.parse(configContent); + } + + /** + * Create default configuration + */ + private async createDefaultConfig(): Promise { + const projectName = helpers.getProjectName(); + const mongoPort = await helpers.getRandomAvailablePort(); + const s3Port = await helpers.getRandomAvailablePort(); + let s3ConsolePort = s3Port + 1; + + // Ensure console port is also available + while (!(await helpers.isPortAvailable(s3ConsolePort))) { + s3ConsolePort++; + } + + this.config = { + PROJECT_NAME: projectName, + MONGODB_HOST: 'localhost', + MONGODB_NAME: projectName, + MONGODB_PORT: mongoPort.toString(), + MONGODB_USER: 'defaultadmin', + MONGODB_PASS: 'defaultpass', + S3_HOST: 'localhost', + S3_PORT: s3Port.toString(), + S3_CONSOLE_PORT: s3ConsolePort.toString(), + S3_USER: 'defaultadmin', + S3_PASS: 'defaultpass', + S3_BUCKET: `${projectName}-documents` + }; + + await this.saveConfig(); + + helpers.printMessage('✅ Created .nogit/env.json with project defaults', 'green'); + helpers.printMessage(`📍 MongoDB port: ${mongoPort}`, 'blue'); + helpers.printMessage(`📍 S3 API port: ${s3Port}`, 'blue'); + helpers.printMessage(`📍 S3 Console port: ${s3ConsolePort}`, 'blue'); + } + + /** + * Update missing fields in existing configuration + */ + private async updateMissingFields(): Promise { + const projectName = helpers.getProjectName(); + let updated = false; + const fieldsAdded: string[] = []; + + // Check and add missing fields + if (!this.config.PROJECT_NAME) { + this.config.PROJECT_NAME = projectName; + fieldsAdded.push('PROJECT_NAME'); + updated = true; + } + + if (!this.config.MONGODB_HOST) { + this.config.MONGODB_HOST = 'localhost'; + fieldsAdded.push('MONGODB_HOST'); + updated = true; + } + + if (!this.config.MONGODB_NAME) { + this.config.MONGODB_NAME = projectName; + fieldsAdded.push('MONGODB_NAME'); + updated = true; + } + + if (!this.config.MONGODB_PORT) { + const port = await helpers.getRandomAvailablePort(); + this.config.MONGODB_PORT = port.toString(); + fieldsAdded.push(`MONGODB_PORT(${port})`); + updated = true; + } + + if (!this.config.MONGODB_USER) { + this.config.MONGODB_USER = 'defaultadmin'; + fieldsAdded.push('MONGODB_USER'); + updated = true; + } + + if (!this.config.MONGODB_PASS) { + this.config.MONGODB_PASS = 'defaultpass'; + fieldsAdded.push('MONGODB_PASS'); + updated = true; + } + + if (!this.config.S3_HOST) { + this.config.S3_HOST = 'localhost'; + fieldsAdded.push('S3_HOST'); + updated = true; + } + + if (!this.config.S3_PORT) { + const port = await helpers.getRandomAvailablePort(); + this.config.S3_PORT = port.toString(); + fieldsAdded.push(`S3_PORT(${port})`); + updated = true; + } + + if (!this.config.S3_CONSOLE_PORT) { + const s3Port = parseInt(this.config.S3_PORT); + let consolePort = s3Port + 1; + + while (!(await helpers.isPortAvailable(consolePort))) { + consolePort++; + } + + this.config.S3_CONSOLE_PORT = consolePort.toString(); + fieldsAdded.push(`S3_CONSOLE_PORT(${consolePort})`); + updated = true; + } + + if (!this.config.S3_USER) { + this.config.S3_USER = 'defaultadmin'; + fieldsAdded.push('S3_USER'); + updated = true; + } + + if (!this.config.S3_PASS) { + this.config.S3_PASS = 'defaultpass'; + fieldsAdded.push('S3_PASS'); + updated = true; + } + + if (!this.config.S3_BUCKET) { + this.config.S3_BUCKET = `${projectName}-documents`; + fieldsAdded.push('S3_BUCKET'); + updated = true; + } + + if (updated) { + await this.saveConfig(); + helpers.printMessage(`✅ Added missing fields: ${fieldsAdded.join(', ')}`, 'green'); + } else { + helpers.printMessage('✅ Configuration complete', 'green'); + } + } + + /** + * Get MongoDB connection string + */ + public getMongoConnectionString(useNetworkIp: boolean = false): string { + const host = useNetworkIp ? '${networkIp}' : this.config.MONGODB_HOST; + return `mongodb://${this.config.MONGODB_USER}:${this.config.MONGODB_PASS}@${host}:${this.config.MONGODB_PORT}/${this.config.MONGODB_NAME}?authSource=admin`; + } + + /** + * Get container names + */ + public getContainerNames() { + return { + mongo: `${this.config.PROJECT_NAME}-mongodb`, + minio: `${this.config.PROJECT_NAME}-minio` + }; + } + + /** + * Get data directories + */ + public getDataDirectories() { + return { + mongo: plugins.path.join(process.cwd(), '.nogit', 'mongodata'), + minio: plugins.path.join(process.cwd(), '.nogit', 'miniodata') + }; + } +} \ No newline at end of file diff --git a/ts/mod_services/classes.servicemanager.ts b/ts/mod_services/classes.servicemanager.ts new file mode 100644 index 0000000..5464cc7 --- /dev/null +++ b/ts/mod_services/classes.servicemanager.ts @@ -0,0 +1,412 @@ +import * as plugins from './mod.plugins.js'; +import * as helpers from './helpers.js'; +import { ServiceConfiguration } from './classes.serviceconfiguration.js'; +import { DockerContainer } from './classes.dockercontainer.js'; + +export class ServiceManager { + private config: ServiceConfiguration; + private docker: DockerContainer; + + constructor() { + this.config = new ServiceConfiguration(); + this.docker = new DockerContainer(); + } + + /** + * Initialize the service manager + */ + public async init(): Promise { + // Check Docker availability + if (!(await this.docker.checkDocker())) { + helpers.printMessage('Error: Docker is not installed. Please install Docker first.', 'red'); + process.exit(1); + } + + // Load or create configuration + await this.config.loadOrCreate(); + helpers.printMessage(`📋 Project: ${this.config.getConfig().PROJECT_NAME}`, 'magenta'); + } + + /** + * Start MongoDB service + */ + public async startMongoDB(): Promise { + helpers.printMessage('📦 MongoDB:', 'yellow'); + + const config = this.config.getConfig(); + const containers = this.config.getContainerNames(); + const directories = this.config.getDataDirectories(); + + // Ensure data directory exists + await plugins.smartfile.fs.ensureDir(directories.mongo); + + const status = await this.docker.getStatus(containers.mongo); + + switch (status) { + case 'running': + helpers.printMessage(' Already running ✓', 'green'); + break; + + case 'stopped': + if (await this.docker.start(containers.mongo)) { + helpers.printMessage(' Started ✓', 'green'); + } else { + helpers.printMessage(' Failed to start', 'red'); + } + break; + + case 'not_exists': + helpers.printMessage(' Creating container...', 'yellow'); + + const success = await this.docker.run({ + name: containers.mongo, + image: 'mongo:7.0', + ports: { + [`0.0.0.0:${config.MONGODB_PORT}`]: '27017' + }, + volumes: { + [directories.mongo]: '/data/db' + }, + environment: { + MONGO_INITDB_ROOT_USERNAME: config.MONGODB_USER, + MONGO_INITDB_ROOT_PASSWORD: config.MONGODB_PASS, + MONGO_INITDB_DATABASE: config.MONGODB_NAME + }, + restart: 'unless-stopped' + }); + + if (success) { + helpers.printMessage(' Created and started ✓', 'green'); + } else { + helpers.printMessage(' Failed to create container', 'red'); + } + break; + } + + helpers.printMessage(` Container: ${containers.mongo}`, 'cyan'); + helpers.printMessage(` Port: ${config.MONGODB_PORT}`, 'cyan'); + helpers.printMessage(` Connection: ${this.config.getMongoConnectionString()}`, 'blue'); + } + + /** + * Start MinIO service + */ + public async startMinIO(): Promise { + helpers.printMessage('📦 S3/MinIO:', 'yellow'); + + const config = this.config.getConfig(); + const containers = this.config.getContainerNames(); + const directories = this.config.getDataDirectories(); + + // Ensure data directory exists + await plugins.smartfile.fs.ensureDir(directories.minio); + + const status = await this.docker.getStatus(containers.minio); + + switch (status) { + case 'running': + helpers.printMessage(' Already running ✓', 'green'); + break; + + case 'stopped': + if (await this.docker.start(containers.minio)) { + helpers.printMessage(' Started ✓', 'green'); + } else { + helpers.printMessage(' Failed to start', 'red'); + } + break; + + case 'not_exists': + helpers.printMessage(' Creating container...', 'yellow'); + + const success = await this.docker.run({ + name: containers.minio, + image: 'minio/minio', + ports: { + [config.S3_PORT]: '9000', + [config.S3_CONSOLE_PORT]: '9001' + }, + volumes: { + [directories.minio]: '/data' + }, + environment: { + MINIO_ROOT_USER: config.S3_USER, + MINIO_ROOT_PASSWORD: config.S3_PASS + }, + restart: 'unless-stopped', + command: 'server /data --console-address ":9001"' + }); + + if (success) { + helpers.printMessage(' Created and started ✓', 'green'); + + // Wait for MinIO to be ready + await plugins.smartdelay.delayFor(3000); + + // Create default bucket + await this.docker.exec( + containers.minio, + `mc alias set local http://localhost:9000 ${config.S3_USER} ${config.S3_PASS}` + ); + + await this.docker.exec( + containers.minio, + `mc mb local/${config.S3_BUCKET}` + ); + + helpers.printMessage(` Bucket '${config.S3_BUCKET}' created ✓`, 'green'); + } else { + helpers.printMessage(' Failed to create container', 'red'); + } + break; + } + + helpers.printMessage(` Container: ${containers.minio}`, 'cyan'); + helpers.printMessage(` Port: ${config.S3_PORT}`, 'cyan'); + helpers.printMessage(` Bucket: ${config.S3_BUCKET}`, 'cyan'); + helpers.printMessage(` API: http://${config.S3_HOST}:${config.S3_PORT}`, 'blue'); + helpers.printMessage(` Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT} (login: ${config.S3_USER}/***)`, 'blue'); + } + + /** + * Stop MongoDB service + */ + public async stopMongoDB(): Promise { + helpers.printMessage('📦 MongoDB:', 'yellow'); + + const containers = this.config.getContainerNames(); + const status = await this.docker.getStatus(containers.mongo); + + if (status === 'running') { + if (await this.docker.stop(containers.mongo)) { + helpers.printMessage(' Stopped ✓', 'green'); + } else { + helpers.printMessage(' Failed to stop', 'red'); + } + } else { + helpers.printMessage(' Not running', 'yellow'); + } + } + + /** + * Stop MinIO service + */ + public async stopMinIO(): Promise { + helpers.printMessage('📦 S3/MinIO:', 'yellow'); + + const containers = this.config.getContainerNames(); + const status = await this.docker.getStatus(containers.minio); + + if (status === 'running') { + if (await this.docker.stop(containers.minio)) { + helpers.printMessage(' Stopped ✓', 'green'); + } else { + helpers.printMessage(' Failed to stop', 'red'); + } + } else { + helpers.printMessage(' Not running', 'yellow'); + } + } + + /** + * Show service status + */ + public async showStatus(): Promise { + helpers.printHeader('Service Status'); + + const config = this.config.getConfig(); + const containers = this.config.getContainerNames(); + + helpers.printMessage(`Project: ${config.PROJECT_NAME}`, 'magenta'); + console.log(); + + // MongoDB status + const mongoStatus = await this.docker.getStatus(containers.mongo); + switch (mongoStatus) { + case 'running': + helpers.printMessage('📦 MongoDB: 🟢 Running', 'green'); + helpers.printMessage(` ├─ Container: ${containers.mongo}`, 'cyan'); + helpers.printMessage(` └─ ${this.config.getMongoConnectionString()}`, 'cyan'); + break; + case 'stopped': + helpers.printMessage('📦 MongoDB: 🟡 Stopped', 'yellow'); + helpers.printMessage(` └─ Container: ${containers.mongo}`, 'cyan'); + break; + case 'not_exists': + helpers.printMessage('📦 MongoDB: ⚪ Not installed', 'magenta'); + break; + } + + // MinIO status + const minioStatus = await this.docker.getStatus(containers.minio); + switch (minioStatus) { + case 'running': + helpers.printMessage('📦 S3/MinIO: 🟢 Running', 'green'); + helpers.printMessage(` ├─ Container: ${containers.minio}`, 'cyan'); + helpers.printMessage(` ├─ API: http://${config.S3_HOST}:${config.S3_PORT}`, 'cyan'); + helpers.printMessage(` ├─ Console: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`, 'cyan'); + helpers.printMessage(` └─ Bucket: ${config.S3_BUCKET}`, 'cyan'); + break; + case 'stopped': + helpers.printMessage('📦 S3/MinIO: 🟡 Stopped', 'yellow'); + helpers.printMessage(` └─ Container: ${containers.minio}`, 'cyan'); + break; + case 'not_exists': + helpers.printMessage('📦 S3/MinIO: ⚪ Not installed', 'magenta'); + break; + } + } + + /** + * Show configuration + */ + public async showConfig(): Promise { + helpers.printHeader('Current Configuration'); + + const config = this.config.getConfig(); + + helpers.printMessage(`Project: ${config.PROJECT_NAME}`, 'magenta'); + console.log(); + + helpers.printMessage('MongoDB:', 'yellow'); + helpers.printMessage(` Host: ${config.MONGODB_HOST}:${config.MONGODB_PORT}`, undefined); + helpers.printMessage(` Database: ${config.MONGODB_NAME}`, undefined); + helpers.printMessage(` User: ${config.MONGODB_USER}`, undefined); + helpers.printMessage(' Password: ***', undefined); + helpers.printMessage(` Container: ${this.config.getContainerNames().mongo}`, undefined); + helpers.printMessage(` Data: ${this.config.getDataDirectories().mongo}`, undefined); + helpers.printMessage(` Connection: ${this.config.getMongoConnectionString()}`, 'blue'); + + console.log(); + helpers.printMessage('S3/MinIO:', 'yellow'); + helpers.printMessage(` Host: ${config.S3_HOST}`, undefined); + helpers.printMessage(` API Port: ${config.S3_PORT}`, undefined); + helpers.printMessage(` Console Port: ${config.S3_CONSOLE_PORT}`, undefined); + helpers.printMessage(` User: ${config.S3_USER}`, undefined); + helpers.printMessage(' Password: ***', undefined); + helpers.printMessage(` Bucket: ${config.S3_BUCKET}`, undefined); + helpers.printMessage(` Container: ${this.config.getContainerNames().minio}`, undefined); + helpers.printMessage(` Data: ${this.config.getDataDirectories().minio}`, undefined); + helpers.printMessage(` API URL: http://${config.S3_HOST}:${config.S3_PORT}`, 'blue'); + helpers.printMessage(` Console URL: http://${config.S3_HOST}:${config.S3_CONSOLE_PORT}`, 'blue'); + } + + /** + * Show MongoDB Compass connection string + */ + public async showCompassConnection(): Promise { + helpers.printHeader('MongoDB Compass Connection'); + + const config = this.config.getConfig(); + const networkIp = await helpers.getLocalNetworkIp(); + + const connectionString = `mongodb://${config.MONGODB_USER}:${config.MONGODB_PASS}@${networkIp}:${config.MONGODB_PORT}/${config.MONGODB_NAME}?authSource=admin`; + + helpers.printMessage('MongoDB Compass is a GUI tool for MongoDB. To connect:', 'cyan'); + console.log(); + helpers.printMessage('1. Download MongoDB Compass from:', undefined); + helpers.printMessage(' https://www.mongodb.com/products/compass', 'blue'); + console.log(); + helpers.printMessage('2. Open Compass and paste this connection string:', undefined); + helpers.printMessage(` ${connectionString}`, 'green'); + console.log(); + helpers.printMessage('Connection Details:', 'yellow'); + helpers.printMessage(` Network IP: ${networkIp}`, undefined); + helpers.printMessage(` Port: ${config.MONGODB_PORT}`, undefined); + helpers.printMessage(` Database: ${config.MONGODB_NAME}`, undefined); + helpers.printMessage(` Username: ${config.MONGODB_USER}`, undefined); + helpers.printMessage(` Auth Source: admin`, undefined); + } + + /** + * Show logs for a service + */ + public async showLogs(service: string, lines: number = 20): Promise { + const containers = this.config.getContainerNames(); + + switch (service) { + case 'mongo': + case 'mongodb': + if (await this.docker.isRunning(containers.mongo)) { + helpers.printHeader(`MongoDB Logs (last ${lines} lines)`); + const logs = await this.docker.logs(containers.mongo, lines); + console.log(logs); + } else { + helpers.printMessage('MongoDB container is not running', 'yellow'); + } + break; + + case 'minio': + case 's3': + if (await this.docker.isRunning(containers.minio)) { + helpers.printHeader(`S3/MinIO Logs (last ${lines} lines)`); + const logs = await this.docker.logs(containers.minio, lines); + console.log(logs); + } else { + helpers.printMessage('S3/MinIO container is not running', 'yellow'); + } + break; + + case 'all': + case '': + await this.showLogs('mongo', lines); + console.log(); + await this.showLogs('minio', lines); + break; + + default: + helpers.printMessage('Usage: gitzone services logs [mongo|s3|all] [lines]', 'yellow'); + break; + } + } + + /** + * Remove containers + */ + public async removeContainers(): Promise { + const containers = this.config.getContainerNames(); + let removed = false; + + if (await this.docker.exists(containers.mongo)) { + if (await this.docker.remove(containers.mongo, true)) { + helpers.printMessage(' MongoDB container removed ✓', 'green'); + removed = true; + } + } + + if (await this.docker.exists(containers.minio)) { + if (await this.docker.remove(containers.minio, true)) { + helpers.printMessage(' S3/MinIO container removed ✓', 'green'); + removed = true; + } + } + + if (!removed) { + helpers.printMessage(' No containers to remove', 'yellow'); + } + } + + /** + * Clean data directories + */ + public async cleanData(): Promise { + const directories = this.config.getDataDirectories(); + let cleaned = false; + + if (await plugins.smartfile.fs.fileExists(directories.mongo)) { + await plugins.smartfile.fs.remove(directories.mongo); + helpers.printMessage(' MongoDB data removed ✓', 'green'); + cleaned = true; + } + + if (await plugins.smartfile.fs.fileExists(directories.minio)) { + await plugins.smartfile.fs.remove(directories.minio); + helpers.printMessage(' S3/MinIO data removed ✓', 'green'); + cleaned = true; + } + + if (!cleaned) { + helpers.printMessage(' No data to clean', 'yellow'); + } + } +} \ No newline at end of file diff --git a/ts/mod_services/helpers.ts b/ts/mod_services/helpers.ts new file mode 100644 index 0000000..921a95f --- /dev/null +++ b/ts/mod_services/helpers.ts @@ -0,0 +1,148 @@ +import * as plugins from './mod.plugins.js'; +import * as net from 'net'; + +/** + * Check if a port is available + */ +export const isPortAvailable = async (port: number): Promise => { + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', () => { + resolve(false); + }); + + server.once('listening', () => { + server.close(); + resolve(true); + }); + + server.listen(port, '0.0.0.0'); + }); +}; + +/** + * Get a random available port between 20000 and 30000 + */ +export const getRandomAvailablePort = async (): Promise => { + const maxAttempts = 100; + + for (let i = 0; i < maxAttempts; i++) { + const port = Math.floor(Math.random() * 10001) + 20000; + if (await isPortAvailable(port)) { + return port; + } + } + + // Fallback: let the system assign a port + return 0; +}; + +/** + * Get the project name from package.json or directory + */ +export const getProjectName = (): string => { + try { + const packageJsonPath = plugins.path.join(process.cwd(), 'package.json'); + if (plugins.smartfile.fs.fileExistsSync(packageJsonPath)) { + const packageJson = plugins.smartfile.fs.toObjectSync(packageJsonPath); + if (packageJson.name) { + // Sanitize: @fin.cx/skr → fin-cx-skr + return packageJson.name.replace(/@/g, '').replace(/[\/\.]/g, '-'); + } + } + } catch (error) { + // Ignore errors and fall back to directory name + } + + return plugins.path.basename(process.cwd()); +}; + +/** + * Print colored message to console + */ +export const printMessage = (message: string, color?: 'green' | 'yellow' | 'red' | 'blue' | 'magenta' | 'cyan') => { + const logger = new plugins.smartlog.ConsoleLog(); + + switch (color) { + case 'green': + logger.log('ok', message); + break; + case 'yellow': + logger.log('note', message); + break; + case 'red': + logger.log('error', message); + break; + case 'blue': + case 'magenta': + case 'cyan': + logger.log('info', message); + break; + default: + logger.log('info', message); + } +}; + +/** + * Print a header with decorative lines + */ +export const printHeader = (title: string) => { + console.log(); + printMessage('═══════════════════════════════════════════════════════════════', 'cyan'); + printMessage(` ${title}`, 'cyan'); + printMessage('═══════════════════════════════════════════════════════════════', 'cyan'); + console.log(); +}; + +/** + * Format bytes to human readable string + */ +export const formatBytes = (bytes: number): string => { + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; +}; + +/** + * Get the local network IP address + */ +export const getLocalNetworkIp = async (): Promise => { + const smartnetworkInstance = new plugins.smartnetwork.SmartNetwork(); + const gateways = await smartnetworkInstance.getGateways(); + + // Find the best local IP from network interfaces + for (const interfaceName of Object.keys(gateways)) { + const interfaces = gateways[interfaceName]; + for (const iface of interfaces) { + // Skip loopback and internal interfaces + if (!iface.internal && iface.family === 'IPv4') { + const address = iface.address; + // Prefer LAN IPs + if (address.startsWith('192.168.') || address.startsWith('10.') || address.startsWith('172.')) { + return address; + } + } + } + } + + // Fallback: try to get any non-internal IPv4 + for (const interfaceName of Object.keys(gateways)) { + const interfaces = gateways[interfaceName]; + for (const iface of interfaces) { + if (!iface.internal && iface.family === 'IPv4') { + return iface.address; + } + } + } + + // Last resort: localhost + return 'localhost'; +}; \ No newline at end of file diff --git a/ts/mod_services/index.ts b/ts/mod_services/index.ts new file mode 100644 index 0000000..b532fb0 --- /dev/null +++ b/ts/mod_services/index.ts @@ -0,0 +1,218 @@ +import * as plugins from './mod.plugins.js'; +import * as helpers from './helpers.js'; +import { ServiceManager } from './classes.servicemanager.js'; + +export const run = async (argvArg: any) => { + const serviceManager = new ServiceManager(); + await serviceManager.init(); + + const command = argvArg._[1] || 'help'; + const service = argvArg._[2] || 'all'; + + switch (command) { + case 'start': + await handleStart(serviceManager, service); + break; + + case 'stop': + await handleStop(serviceManager, service); + break; + + case 'restart': + await handleRestart(serviceManager, service); + break; + + case 'status': + await serviceManager.showStatus(); + break; + + case 'config': + await serviceManager.showConfig(); + break; + + case 'compass': + await serviceManager.showCompassConnection(); + break; + + case 'logs': + const lines = parseInt(argvArg._[3]) || 20; + await serviceManager.showLogs(service, lines); + break; + + case 'remove': + await handleRemove(serviceManager); + break; + + case 'clean': + await handleClean(serviceManager); + break; + + case 'help': + default: + showHelp(); + break; + } +}; + +async function handleStart(serviceManager: ServiceManager, service: string) { + helpers.printHeader('Starting Services'); + + switch (service) { + case 'mongo': + case 'mongodb': + await serviceManager.startMongoDB(); + break; + + case 'minio': + case 's3': + await serviceManager.startMinIO(); + break; + + case 'all': + case '': + await serviceManager.startMongoDB(); + console.log(); + await serviceManager.startMinIO(); + break; + + default: + helpers.printMessage(`Unknown service: ${service}`, 'red'); + helpers.printMessage('Use: mongo, s3, or all', 'yellow'); + break; + } +} + +async function handleStop(serviceManager: ServiceManager, service: string) { + helpers.printHeader('Stopping Services'); + + switch (service) { + case 'mongo': + case 'mongodb': + await serviceManager.stopMongoDB(); + break; + + case 'minio': + case 's3': + await serviceManager.stopMinIO(); + break; + + case 'all': + case '': + await serviceManager.stopMongoDB(); + console.log(); + await serviceManager.stopMinIO(); + break; + + default: + helpers.printMessage(`Unknown service: ${service}`, 'red'); + helpers.printMessage('Use: mongo, s3, or all', 'yellow'); + break; + } +} + +async function handleRestart(serviceManager: ServiceManager, service: string) { + helpers.printHeader('Restarting Services'); + + switch (service) { + case 'mongo': + case 'mongodb': + await serviceManager.stopMongoDB(); + await plugins.smartdelay.delayFor(2000); + await serviceManager.startMongoDB(); + break; + + case 'minio': + case 's3': + await serviceManager.stopMinIO(); + await plugins.smartdelay.delayFor(2000); + await serviceManager.startMinIO(); + break; + + case 'all': + case '': + await serviceManager.stopMongoDB(); + await serviceManager.stopMinIO(); + await plugins.smartdelay.delayFor(2000); + await serviceManager.startMongoDB(); + console.log(); + await serviceManager.startMinIO(); + break; + + default: + helpers.printMessage(`Unknown service: ${service}`, 'red'); + break; + } +} + +async function handleRemove(serviceManager: ServiceManager) { + helpers.printHeader('Removing Containers'); + helpers.printMessage('⚠️ This will remove containers but preserve data', 'yellow'); + + const shouldContinue = await plugins.smartinteract.SmartInteract.getCliConfirmation('Continue?', false); + + if (shouldContinue) { + await serviceManager.removeContainers(); + } else { + helpers.printMessage('Cancelled', 'yellow'); + } +} + +async function handleClean(serviceManager: ServiceManager) { + helpers.printHeader('Clean All'); + helpers.printMessage('⚠️ WARNING: This will remove all containers and data!', 'red'); + helpers.printMessage('This action cannot be undone!', 'red'); + + const smartinteraction = new plugins.smartinteract.SmartInteract(); + const confirmAnswer = await smartinteraction.askQuestion({ + name: 'confirm', + type: 'input', + message: 'Type "yes" to confirm:', + default: 'no' + }); + + if (confirmAnswer.value === 'yes') { + await serviceManager.removeContainers(); + console.log(); + await serviceManager.cleanData(); + helpers.printMessage('All cleaned ✓', 'green'); + } else { + helpers.printMessage('Cancelled', 'yellow'); + } +} + +function showHelp() { + helpers.printHeader('GitZone Services Manager'); + + helpers.printMessage('Usage: gitzone services [command] [options]', 'green'); + console.log(); + + helpers.printMessage('Commands:', 'yellow'); + helpers.printMessage(' start [service] Start services (mongo|s3|all)', undefined); + helpers.printMessage(' stop [service] Stop services (mongo|s3|all)', undefined); + helpers.printMessage(' restart [service] Restart services (mongo|s3|all)', undefined); + helpers.printMessage(' status Show service status', undefined); + helpers.printMessage(' config Show current configuration', undefined); + helpers.printMessage(' compass Show MongoDB Compass connection string', undefined); + helpers.printMessage(' logs [service] Show logs (mongo|s3|all) [lines]', undefined); + helpers.printMessage(' remove Remove all containers', undefined); + helpers.printMessage(' clean Remove all containers and data ⚠️', undefined); + helpers.printMessage(' help Show this help message', undefined); + console.log(); + + helpers.printMessage('Features:', 'yellow'); + helpers.printMessage(' • Auto-creates .nogit/env.json with smart defaults', undefined); + helpers.printMessage(' • Random ports (20000-30000) to avoid conflicts', undefined); + helpers.printMessage(' • Project-specific containers for multi-project support', undefined); + helpers.printMessage(' • Preserves custom configuration values', undefined); + helpers.printMessage(' • MongoDB Compass connection support', undefined); + console.log(); + + helpers.printMessage('Examples:', 'yellow'); + helpers.printMessage(' gitzone services start # Start all services', undefined); + helpers.printMessage(' gitzone services start mongo # Start only MongoDB', undefined); + helpers.printMessage(' gitzone services stop # Stop all services', undefined); + helpers.printMessage(' gitzone services status # Check service status', undefined); + helpers.printMessage(' gitzone services config # Show configuration', undefined); + helpers.printMessage(' gitzone services compass # Get MongoDB Compass connection', undefined); + helpers.printMessage(' gitzone services logs mongo 50 # Show last 50 lines of MongoDB logs', undefined); +} \ No newline at end of file diff --git a/ts/mod_services/mod.plugins.ts b/ts/mod_services/mod.plugins.ts new file mode 100644 index 0000000..111611d --- /dev/null +++ b/ts/mod_services/mod.plugins.ts @@ -0,0 +1,9 @@ +export * from '../plugins.js'; + +import * as smartshell from '@push.rocks/smartshell'; +import * as smartfile from '@push.rocks/smartfile'; +import * as smartinteract from '@push.rocks/smartinteract'; +import * as smartnetwork from '@push.rocks/smartnetwork'; +import * as smartdelay from '@push.rocks/smartdelay'; + +export { smartshell, smartfile, smartinteract, smartnetwork, smartdelay }; \ No newline at end of file diff --git a/ts/plugins.ts b/ts/plugins.ts index e4ca84e..10b36d4 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -7,6 +7,11 @@ import * as smartcli from '@push.rocks/smartcli'; import * as smartpath from '@push.rocks/smartpath'; import * as smartpromise from '@push.rocks/smartpromise'; import * as smartupdate from '@push.rocks/smartupdate'; +import * as smartshell from '@push.rocks/smartshell'; +import * as smartnetwork from '@push.rocks/smartnetwork'; +import * as smartfile from '@push.rocks/smartfile'; +import * as smartinteract from '@push.rocks/smartinteract'; +import * as smartdelay from '@push.rocks/smartdelay'; export { smartlog, @@ -18,4 +23,9 @@ export { smartpath, smartpromise, smartupdate, + smartshell, + smartnetwork, + smartfile, + smartinteract, + smartdelay, };