Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b55e75d169 | |||
| d0d922e53b | |||
| eda67395fe | |||
| 470e87eb79 | |||
| 3358a0eacc | |||
| b65fac6257 | |||
| 4ab59609e6 | |||
| 32f106291f | |||
| b8aa5d61f6 | |||
| 71759c276e | |||
| 7938f12d43 | |||
| 3722258d69 | |||
| 68859d0e97 | |||
| ecadbc7a86 | |||
| 0243bc5ec7 | |||
| 92e618104f | |||
| c089c1f80d | |||
| 10a394c7d8 | |||
| 5980308bb8 | |||
| 398e36bdf7 | |||
| 1e78517547 | |||
| 55700ad87e | |||
| 773df5268b | |||
| b51fa88283 |
@@ -19,6 +19,10 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# rust
|
||||
rust/target/
|
||||
dist_rust/
|
||||
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["ES2022", "DOM"],
|
||||
"target": "ES2022",
|
||||
"checkJs": true
|
||||
|
||||
@@ -12,15 +12,17 @@ fileName: package.json
|
||||
"author": "{{module.author}}",
|
||||
"license": "{{module.license}}",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"test": "(tstest test/ --verbose --logfile --timeout 60)",
|
||||
"build": "(tsbuild tsfolders --allowimplicitany)",
|
||||
"buildDocs": "(tsdoc)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^3.1.2",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^3.1.3",
|
||||
"@types/node": "^24.10.1"
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@types/node": "^25.2.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"@push.rocks/smartpath": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import * as plugins from './{{module.name}}.plugins.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export let demoExport = 'Hi there! :) This is an exported string';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
---
|
||||
fileName: .smartconfig.json
|
||||
---
|
||||
{
|
||||
"@git.zone/cli": {
|
||||
"projectType": "{{projectType}}",
|
||||
69
changelog.md
69
changelog.md
@@ -1,5 +1,74 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-24 - 2.13.10 - fix(config)
|
||||
migrate configuration handling from npmextra to smartconfig
|
||||
|
||||
- replace @push.rocks/npmextra with @push.rocks/smartconfig across config, commit, format, and service modules
|
||||
- switch managed project config file references from npmextra.json to smartconfig.json
|
||||
- update formatting and package metadata checks to include smartconfig.json
|
||||
- extend the gitignore template with Rust build output directories
|
||||
|
||||
## 2026-03-11 - 2.13.9 - fix(deps,readme)
|
||||
bump dependencies and update README to prefer pnpm and document semantic commit flags
|
||||
|
||||
- Dev dependency updates: @git.zone/tsbuild -> ^4.3.0, @git.zone/tstest -> ^3.3.2, @types/node -> ^25.4.0
|
||||
- Dependency upgrades: @git.zone/tsdoc -> ^2.0.0, @git.zone/tspublish -> ^1.11.2, @push.rocks/lik -> ^6.3.1, @push.rocks/smartfs -> ^1.5.0, @push.rocks/smartlog -> ^3.2.1, @push.rocks/smartstream -> ^3.4.0, prettier -> ^3.8.1 (and other minor/patch bumps)
|
||||
- README changes: prefer pnpm for global install, clarify format command dry-run behavior and --write flag, add and document gitzone commit flags (-y/--yes, -p/--push, -t/--test, -b/--build, -r/--release) and AI-powered commit workflow
|
||||
- No source code changes; this is a documentation and dependency refresh, recommend a patch release
|
||||
|
||||
## 2026-03-05 - 2.13.8 - fix(dependencies)
|
||||
move runtime tooling packages from devDependencies to dependencies
|
||||
|
||||
- Removed @push.rocks/smartdelay, @push.rocks/smartinteract, @push.rocks/smartnetwork, and @push.rocks/smartshell from devDependencies and added them to dependencies
|
||||
- No package version numbers were changed; this ensures the moved packages are installed for consumers at runtime
|
||||
|
||||
## 2026-03-05 - 2.13.7 - fix(deps)
|
||||
bump devDependencies: @git.zone/tsbuild to ^4.1.4 and @push.rocks/smartshell to ^3.3.7
|
||||
|
||||
- Updated @git.zone/tsbuild from ^4.1.2 to ^4.1.4 (patch)
|
||||
- Updated @push.rocks/smartshell from ^3.3.0 to ^3.3.7 (patch)
|
||||
|
||||
## 2026-02-01 - 2.13.6 - fix(templates/npm)
|
||||
use tsbuild tsfolders instead of --web flag in npm template build script
|
||||
|
||||
- Changed build script in assets/templates/npm/.package.json from "(tsbuild --web --allowimplicitany)" to "(tsbuild tsfolders --allowimplicitany)"
|
||||
- Replaces --web flag with explicit tsfolders argument to correctly target project folders during build
|
||||
|
||||
## 2026-02-01 - 2.13.5 - fix(templates/npm)
|
||||
update npm template: tweak test script, bump devDependencies, add smartpath dependency, and fix ts import path
|
||||
|
||||
- test script updated: '(tstest test/ --web)' -> '(tstest test/ --verbose --logfile --timeout 60)'
|
||||
- devDependencies bumped: @git.zone/tsbuild ^3.1.2 -> ^4.1.2, @git.zone/tsrun ^2.0.0 -> ^2.0.1, @git.zone/tstest ^3.1.3 -> ^3.1.8, @types/node ^24.10.1 -> ^25.2.0
|
||||
- dependencies: added @push.rocks/smartpath ^6.0.0
|
||||
- TypeScript template import fixed: './{{module.name}}.plugins.js' -> './plugins.js'
|
||||
|
||||
## 2025-12-18 - 2.13.3 - fix(tsconfig)
|
||||
remove experimentalDecorators and useDefineForClassFields from TypeScript configuration files
|
||||
|
||||
- Removed "experimentalDecorators": true from assets/templates/multienv/deno.json and tsconfig.json
|
||||
- Removed "useDefineForClassFields": false from tsconfig.json
|
||||
- This change alters TypeScript/Deno compiler behavior: decorator support and legacy class-field initialization semantics may be affected; code relying on those may need updates
|
||||
|
||||
## 2025-12-16 - 2.13.2 - fix(deps)
|
||||
bump @git.zone/tspublish to ^1.11.0
|
||||
|
||||
- Updated dependency @git.zone/tspublish from ^1.10.3 to ^1.11.0 in package.json
|
||||
|
||||
## 2025-12-16 - 2.13.1 - fix(npmextra)
|
||||
merge old npmextra keys into new keys during migration, preserving existing new values
|
||||
|
||||
- Changed migration logic to merge data when both old and new keys exist instead of skipping the merge.
|
||||
- Merge preserves existing new-key values (old values do not overwrite new ones) and still deletes the old key after migration.
|
||||
- Applied the fix in both ts/mod_format/format.npmextra.ts and ts/mod_format/formatters/npmextra.formatter.ts.
|
||||
- Adds a console log for successful migrations; behavior for single-key rename remains unchanged.
|
||||
|
||||
## 2025-12-16 - 2.13.0 - feat(tests)
|
||||
feat(tests): add sandbox test fixture, CI and editor configs; bump deps
|
||||
|
||||
- Added comprehensive test/ fixture (sandbox-npmts) including package.json, npmextra.json, readme, GitLab CI (.gitlab-ci.yml), .npmrc, VSCode launch/settings and qenv.yml
|
||||
- Added test sources and helper files under test/test and test/ts (browser and node tests, commitinfo data, simple library code)
|
||||
- Updated dependencies in package.json: @git.zone/tsdoc -> ^1.11.4, @push.rocks/smartfs -> ^1.3.1
|
||||
|
||||
## 2025-12-15 - 2.12.2 - fix(cli)
|
||||
noop: no changes
|
||||
|
||||
|
||||
38
package.json
38
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@git.zone/cli",
|
||||
"private": false,
|
||||
"version": "2.12.2",
|
||||
"version": "2.13.11",
|
||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||
"main": "dist_ts/index.ts",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
@@ -57,46 +57,40 @@
|
||||
},
|
||||
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.0.2",
|
||||
"@git.zone/tsbuild": "^4.3.0",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.1.3",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartinteract": "^2.0.16",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartshell": "^3.3.0",
|
||||
"@types/node": "^25.0.2"
|
||||
"@git.zone/tstest": "^3.3.2",
|
||||
"@types/node": "^25.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@git.zone/tsdoc": "^1.11.3",
|
||||
"@git.zone/tspublish": "^1.10.3",
|
||||
"@git.zone/tsdoc": "^2.0.0",
|
||||
"@git.zone/tspublish": "^1.11.2",
|
||||
"@push.rocks/commitinfo": "^1.0.12",
|
||||
"@push.rocks/early": "^4.0.4",
|
||||
"@push.rocks/gulp-function": "^3.0.7",
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/npmextra": "^5.3.3",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@push.rocks/smartcli": "^4.0.19",
|
||||
"@push.rocks/smartcli": "^4.0.20",
|
||||
"@push.rocks/smartconfig": "^6.0.1",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartdiff": "^1.1.0",
|
||||
"@push.rocks/smartfile": "^13.1.2",
|
||||
"@push.rocks/smartfs": "^1.2.0",
|
||||
"@push.rocks/smartgulp": "^3.0.4",
|
||||
"@push.rocks/smartfs": "^1.5.0",
|
||||
"@push.rocks/smartinteract": "^2.0.16",
|
||||
"@push.rocks/smartjson": "^6.0.0",
|
||||
"@push.rocks/smartlegal": "^1.0.27",
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/smartlog": "^3.2.1",
|
||||
"@push.rocks/smartlog-destination-local": "^9.0.2",
|
||||
"@push.rocks/smartmustache": "^3.0.2",
|
||||
"@push.rocks/smartnetwork": "^4.4.0",
|
||||
"@push.rocks/smartnpm": "^2.0.6",
|
||||
"@push.rocks/smartobject": "^1.0.12",
|
||||
"@push.rocks/smartopen": "^2.0.0",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartscaf": "^4.0.19",
|
||||
"@push.rocks/smartstream": "^3.2.5",
|
||||
"@push.rocks/smartshell": "^3.3.7",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/smartupdate": "^2.0.6",
|
||||
"@types/through2": "^2.0.41",
|
||||
"prettier": "^3.7.4",
|
||||
"through2": "^4.0.2"
|
||||
"prettier": "^3.8.1"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -107,7 +101,7 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
".smartconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
|
||||
2466
pnpm-lock.yaml
generated
2466
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
455
readme.md
455
readme.md
@@ -18,11 +18,11 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install globally via npm
|
||||
npm install -g @git.zone/cli
|
||||
|
||||
# Or with pnpm (recommended)
|
||||
# Install globally via pnpm (recommended)
|
||||
pnpm add -g @git.zone/cli
|
||||
|
||||
# Or with npm
|
||||
npm install -g @git.zone/cli
|
||||
```
|
||||
|
||||
Once installed, you can use either `gitzone` or the shorter `gzone` command from anywhere in your terminal.
|
||||
@@ -33,9 +33,12 @@ Once installed, you can use either `gitzone` or the shorter `gzone` command from
|
||||
# Create a new TypeScript npm package
|
||||
gitzone template npm
|
||||
|
||||
# Format your entire codebase
|
||||
# Format your entire codebase (dry-run by default)
|
||||
gitzone format
|
||||
|
||||
# Apply formatting changes
|
||||
gitzone format --write
|
||||
|
||||
# Start local MongoDB and MinIO services
|
||||
gitzone services start
|
||||
|
||||
@@ -45,34 +48,146 @@ gitzone commit
|
||||
|
||||
## 🛠️ Core Features
|
||||
|
||||
### 🔀 Semantic Commits & Versioning
|
||||
|
||||
Create standardized commits with AI-powered suggestions that automatically handle versioning:
|
||||
|
||||
```bash
|
||||
# Interactive commit with AI recommendations
|
||||
gitzone commit
|
||||
|
||||
# Auto-accept AI recommendations (skipped for BREAKING CHANGEs)
|
||||
gitzone commit -y
|
||||
|
||||
# Auto-accept, push, build, and release
|
||||
gitzone commit -ypbr
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | Long Form | Description |
|
||||
|------|-----------|-------------|
|
||||
| `-y` | `--yes` | Auto-accept AI recommendations |
|
||||
| `-p` | `--push` | Push to remote after commit |
|
||||
| `-t` | `--test` | Run tests before committing |
|
||||
| `-b` | `--build` | Build after commit, verify clean tree |
|
||||
| `-r` | `--release` | Publish to configured npm registries |
|
||||
| | `--format` | Run format before committing |
|
||||
|
||||
**Workflow steps:**
|
||||
|
||||
1. 🤖 **AI-powered analysis** — analyzes your changes and suggests commit type, scope, and message
|
||||
2. 📝 Interactive commit message builder (type: `fix`/`feat`/`BREAKING CHANGE`, scope, description)
|
||||
3. 📜 Automatic changelog generation
|
||||
4. 🏷️ Automatic version bumping (major/minor/patch) with git tag creation
|
||||
5. 🔨 Optional build & verification
|
||||
6. 🚀 Optional push to origin
|
||||
7. 📦 Optional publish to npm registries
|
||||
|
||||
Supports both npm (`package.json`) and Deno (`deno.json`) projects, including dual-type projects.
|
||||
|
||||
### 🎨 Intelligent Code Formatting
|
||||
|
||||
Automatically format and standardize your entire codebase. **Dry-run by default** — nothing changes until you explicitly use `--write`:
|
||||
|
||||
```bash
|
||||
# Preview what would change (default behavior)
|
||||
gitzone format
|
||||
|
||||
# Apply changes
|
||||
gitzone format --write
|
||||
|
||||
# Auto-approve without prompts
|
||||
gitzone format --yes --write
|
||||
|
||||
# Show detailed diffs
|
||||
gitzone format --diff
|
||||
|
||||
# Enable verbose logging
|
||||
gitzone format --verbose
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--write` / `-w` | Apply changes (default is dry-run) |
|
||||
| `--yes` | Auto-approve without interactive confirmation |
|
||||
| `--plan-only` | Only show what would be done |
|
||||
| `--save-plan <file>` | Save the format plan to a file |
|
||||
| `--from-plan <file>` | Load and execute a saved plan |
|
||||
| `--detailed` | Show detailed stats and save report |
|
||||
| `--parallel` / `--no-parallel` | Toggle parallel execution |
|
||||
| `--verbose` | Enable verbose logging |
|
||||
| `--diff` | Show file diffs |
|
||||
|
||||
**Formatters (executed in order):**
|
||||
|
||||
1. 🧹 **Cleanup** — removes obsolete files (yarn.lock, package-lock.json, tslint.json, etc.)
|
||||
2. ⚙️ **Npmextra** — formats and standardizes `npmextra.json`
|
||||
3. 📜 **License** — ensures proper licensing and checks dependency licenses
|
||||
4. 📦 **Package.json** — standardizes package configuration
|
||||
5. 📋 **Templates** — applies project template updates
|
||||
6. 🙈 **Gitignore** — updates repository ignore rules
|
||||
7. 🔧 **Tsconfig** — optimizes TypeScript configuration
|
||||
8. ✨ **Prettier** — applies code formatting
|
||||
9. 📖 **Readme** — ensures readme files exist
|
||||
10. 📂 **Copy** — copies configured files
|
||||
|
||||
### 🐳 Development Services Management
|
||||
|
||||
Effortlessly manage local MongoDB and MinIO (S3-compatible) services for your development environment:
|
||||
Effortlessly manage local development services (MongoDB, MinIO S3, Elasticsearch) with Docker:
|
||||
|
||||
```bash
|
||||
gitzone services [command]
|
||||
```
|
||||
|
||||
**Available commands:**
|
||||
**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)
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `start [service]` | Start services (`mongo`\|`s3`\|`elasticsearch`\|`all`) |
|
||||
| `stop [service]` | Stop services |
|
||||
| `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 (default: 20 lines) |
|
||||
| `reconfigure` | Reassign ports and restart all services |
|
||||
| `remove` | Remove containers (preserves data) |
|
||||
| `clean` | Remove containers AND data (⚠️ destructive) |
|
||||
|
||||
**Service aliases:**
|
||||
|
||||
- `mongo` / `mongodb` — MongoDB
|
||||
- `minio` / `s3` — MinIO (S3-compatible storage)
|
||||
- `elasticsearch` / `es` — Elasticsearch
|
||||
- `all` — All services (default)
|
||||
|
||||
**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
|
||||
- 🎲 **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/` survives container restarts
|
||||
- 🔗 **MongoDB Compass support** — instantly get connection strings for GUI access
|
||||
- 🌐 **Network IP detection** — detects your local network IP for remote connections
|
||||
- ⚙️ **Auto-configuration** — creates `.nogit/env.json` with smart defaults
|
||||
|
||||
**Global operations (`-g` flag):**
|
||||
|
||||
```bash
|
||||
# List all registered projects
|
||||
gitzone services list -g
|
||||
|
||||
# Show status across all projects
|
||||
gitzone services status -g
|
||||
|
||||
# Stop all containers across all projects
|
||||
gitzone services stop -g
|
||||
|
||||
# Remove stale registry entries
|
||||
gitzone services cleanup -g
|
||||
```
|
||||
|
||||
**Example workflow:**
|
||||
|
||||
@@ -94,7 +209,26 @@ gitzone services logs mongo 50
|
||||
gitzone services stop
|
||||
```
|
||||
|
||||
The services are configured via `.nogit/env.json` which is automatically created with secure defaults and random ports for each project.
|
||||
### ⚙️ Release & Commit Configuration
|
||||
|
||||
Manage release registries and commit settings:
|
||||
|
||||
```bash
|
||||
gitzone config [subcommand]
|
||||
```
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `show` | Display current release config (registries, access level) |
|
||||
| `add [url]` | Add a registry URL (default: `https://registry.npmjs.org`) |
|
||||
| `remove [url]` | Remove a registry URL (interactive selection if no URL) |
|
||||
| `clear` | Clear all registries (with confirmation) |
|
||||
| `access [public\|private]` | Set npm access level for publishing |
|
||||
| `commit alwaysTest [true\|false]` | Always run tests before commit |
|
||||
| `commit alwaysBuild [true\|false]` | Always build after commit |
|
||||
| `services` | Configure which services are enabled |
|
||||
|
||||
Configuration is stored in `npmextra.json` under the `@git.zone/cli` key.
|
||||
|
||||
### 📦 Project Templates
|
||||
|
||||
@@ -104,12 +238,12 @@ Instantly scaffold production-ready projects with best practices built-in:
|
||||
gitzone template [template-name]
|
||||
```
|
||||
|
||||
**Available templates:**
|
||||
**Interactive templates:**
|
||||
|
||||
- **`npm`** - TypeScript npm package with testing, CI/CD, and full tooling
|
||||
- **`service`** - Microservice architecture with Docker support
|
||||
- **`website`** - Modern web application with LitElement and service workers
|
||||
- **`wcc`** - Web Component Collection for reusable UI components
|
||||
- **`npm`** — TypeScript npm package with testing, CI/CD, and full tooling
|
||||
- **`service`** — Microservice architecture with Docker support
|
||||
- **`website`** — Modern web application with LitElement and service workers
|
||||
- **`wcc`** — Web Component Collection for reusable UI components
|
||||
|
||||
Each template comes pre-configured with:
|
||||
|
||||
@@ -119,94 +253,6 @@ Each template comes pre-configured with:
|
||||
- ✅ Code formatting and linting
|
||||
- ✅ Documentation structure
|
||||
|
||||
### 🎨 Intelligent Code Formatting
|
||||
|
||||
The most powerful feature of gitzone - automatically format and standardize your entire codebase:
|
||||
|
||||
```bash
|
||||
# Preview changes without applying them
|
||||
gitzone format --dry-run
|
||||
|
||||
# Format with automatic approval
|
||||
gitzone format --yes
|
||||
|
||||
# Save formatting plan for later execution
|
||||
gitzone format --save-plan format-plan.json
|
||||
|
||||
# Execute a saved plan
|
||||
gitzone format --from-plan format-plan.json
|
||||
|
||||
# Enable verbose output for debugging
|
||||
gitzone format --verbose
|
||||
```
|
||||
|
||||
**Format features:**
|
||||
|
||||
- 🔄 **Smart caching** - Only processes changed files
|
||||
- 🛡️ **Rollback support** - Undo formatting changes if needed
|
||||
- 📊 **Detailed reporting** - See exactly what changed
|
||||
- ⚡ **Parallel execution** - Format multiple files simultaneously
|
||||
- 🎯 **Module-specific formatting** - Target specific formatters
|
||||
|
||||
**Rollback capabilities:**
|
||||
|
||||
```bash
|
||||
# List all available backups
|
||||
gitzone format --list-backups
|
||||
|
||||
# Rollback to the last operation
|
||||
gitzone format --rollback
|
||||
|
||||
# Rollback to a specific operation
|
||||
gitzone format --rollback [operation-id]
|
||||
|
||||
# Clean old backups
|
||||
gitzone format --clean-backups
|
||||
```
|
||||
|
||||
**Formatters included:**
|
||||
|
||||
- **Prettier** - JavaScript/TypeScript code formatting
|
||||
- **License** - Ensure proper licensing
|
||||
- **Package.json** - Standardize package configurations
|
||||
- **Tsconfig** - TypeScript configuration optimization
|
||||
- **Readme** - Documentation formatting
|
||||
- **Gitignore** - Repository ignore rules
|
||||
- **Templates** - Project template updates
|
||||
- **Npmextra** - Extended npm configurations
|
||||
- **Cleanup** - Removes obsolete files (yarn.lock, package-lock.json, tslint.json, etc.)
|
||||
|
||||
### 🔀 Semantic Commits & Versioning
|
||||
|
||||
Create standardized commits with AI-powered suggestions that automatically handle versioning:
|
||||
|
||||
```bash
|
||||
# Interactive commit with AI recommendations
|
||||
gitzone commit
|
||||
|
||||
# Auto-accept AI recommendations
|
||||
gitzone commit -y
|
||||
|
||||
# Auto-accept and push
|
||||
gitzone commit -y -p
|
||||
```
|
||||
|
||||
Features:
|
||||
|
||||
- 🤖 **AI-powered analysis** - Analyzes your changes and suggests commit type, scope, and message
|
||||
- 📝 Interactive commit message builder with smart defaults
|
||||
- 🏷️ Automatic version bumping (major/minor/patch)
|
||||
- 📜 Changelog generation
|
||||
- 🚀 Optional auto-push to origin
|
||||
- 🎯 Conventional commit compliance
|
||||
|
||||
The commit wizard guides you through:
|
||||
|
||||
1. **Type selection** (fix/feat/BREAKING CHANGE) with AI recommendation
|
||||
2. **Scope definition** (component/module affected)
|
||||
3. **Description crafting**
|
||||
4. **Version bump determination**
|
||||
|
||||
### 🏗️ Meta Repository Management
|
||||
|
||||
Manage multiple related repositories as a cohesive unit:
|
||||
@@ -218,36 +264,22 @@ gitzone meta init
|
||||
# Add a sub-project
|
||||
gitzone meta add [name] [git-url]
|
||||
|
||||
# Update all sub-projects
|
||||
# Update all sub-projects (clone missing, clean superfluous)
|
||||
gitzone meta update
|
||||
|
||||
# Remove a sub-project
|
||||
gitzone meta remove [name]
|
||||
```
|
||||
|
||||
Perfect for:
|
||||
|
||||
- Monorepo management
|
||||
- Multi-package projects
|
||||
- Coordinated deployments
|
||||
- Synchronized versioning
|
||||
|
||||
### 🐳 Docker Management
|
||||
|
||||
Streamline your Docker workflow:
|
||||
|
||||
```bash
|
||||
# Clean up all Docker resources
|
||||
# Clean up all Docker resources (containers, images, volumes, networks)
|
||||
gitzone docker prune
|
||||
```
|
||||
|
||||
This command removes:
|
||||
|
||||
- Stopped containers
|
||||
- Unused images
|
||||
- Dangling volumes
|
||||
- Unused networks
|
||||
|
||||
### 🔗 Quick CI/CD Access
|
||||
|
||||
Jump directly to your CI/CD configurations:
|
||||
@@ -270,12 +302,7 @@ Smoothly transition users from old to new packages:
|
||||
gitzone deprecate
|
||||
```
|
||||
|
||||
Interactive wizard for:
|
||||
|
||||
- Setting deprecation notices
|
||||
- Guiding users to replacements
|
||||
- Updating registry metadata
|
||||
- Coordinating migration paths
|
||||
Interactive wizard that prompts for registry URLs, old package name, and new package name — then runs `npm deprecate` across all specified registries.
|
||||
|
||||
### 🚦 Project Initialization
|
||||
|
||||
@@ -285,17 +312,10 @@ Prepare existing projects for development:
|
||||
gitzone start
|
||||
```
|
||||
|
||||
Automatically:
|
||||
|
||||
- Checks out master branch
|
||||
- Pulls latest changes
|
||||
- Installs dependencies
|
||||
- Sets up development environment
|
||||
Automatically checks out master, pulls latest changes, and installs dependencies.
|
||||
|
||||
### 🔧 Helper Utilities
|
||||
|
||||
Quick utilities for common tasks:
|
||||
|
||||
```bash
|
||||
# Generate a unique short ID
|
||||
gitzone helpers shortid
|
||||
@@ -303,31 +323,44 @@ gitzone helpers shortid
|
||||
|
||||
## 📋 Configuration
|
||||
|
||||
### npmextra.json Configuration
|
||||
### npmextra.json
|
||||
|
||||
Customize gitzone behavior through `npmextra.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
},
|
||||
"commit": {
|
||||
"alwaysTest": false,
|
||||
"alwaysBuild": false
|
||||
}
|
||||
},
|
||||
"gitzone": {
|
||||
"format": {
|
||||
"interactive": true,
|
||||
"showDiffs": false,
|
||||
"autoApprove": false,
|
||||
"parallel": true,
|
||||
"rollback": {
|
||||
"showStats": true,
|
||||
"cache": {
|
||||
"enabled": true,
|
||||
"autoRollbackOnError": true,
|
||||
"backupRetentionDays": 7
|
||||
"clean": true
|
||||
},
|
||||
"modules": {
|
||||
"skip": ["prettier"],
|
||||
"only": [],
|
||||
"order": []
|
||||
},
|
||||
"cache": {
|
||||
"enabled": true,
|
||||
"clean": true
|
||||
"licenses": {
|
||||
"allowed": ["MIT", "Apache-2.0"],
|
||||
"exceptions": {
|
||||
"some-package": "GPL-3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,35 +369,9 @@ Customize gitzone behavior through `npmextra.json`:
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `CI` - Detect CI environment for automated workflows
|
||||
- `DEBUG` - Enable debug output
|
||||
- `GITZONE_FORMAT_PARALLEL` - Control parallel formatting
|
||||
|
||||
## 🏆 Best Practices
|
||||
|
||||
### For New Projects
|
||||
|
||||
1. Start with a template: `gitzone template npm`
|
||||
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. Set up services: `gitzone services start`
|
||||
5. Commit changes: `gitzone commit`
|
||||
|
||||
### For Teams
|
||||
|
||||
1. Document format preferences in `npmextra.json`
|
||||
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
|
||||
- `CI` — Detect CI environment for automated workflows
|
||||
- `DEBUG` — Enable debug output
|
||||
- `GITZONE_FORMAT_PARALLEL` — Control parallel formatting
|
||||
|
||||
## 🎯 Common Workflows
|
||||
|
||||
@@ -383,8 +390,9 @@ gitzone services start
|
||||
# 4. Check service logs if needed
|
||||
gitzone services logs mongo
|
||||
|
||||
# 5. Format code
|
||||
# 5. Preview format changes, then apply
|
||||
gitzone format
|
||||
gitzone format --write
|
||||
|
||||
# 6. Commit with semantic versioning
|
||||
gitzone commit
|
||||
@@ -393,6 +401,13 @@ gitzone commit
|
||||
gitzone services stop
|
||||
```
|
||||
|
||||
### Automated CI/CD Commit
|
||||
|
||||
```bash
|
||||
# Auto-accept, test, build, push, and release in one command
|
||||
gitzone commit -ytbpr
|
||||
```
|
||||
|
||||
### Multi-Repository Management
|
||||
|
||||
```bash
|
||||
@@ -408,29 +423,26 @@ gitzone meta add shared https://github.com/org/shared.git
|
||||
gitzone meta update
|
||||
```
|
||||
|
||||
### Safe Formatting with Rollback
|
||||
### Safe Formatting with Plan Review
|
||||
|
||||
```bash
|
||||
# 1. Preview changes
|
||||
gitzone format --dry-run
|
||||
# 1. Preview changes (default)
|
||||
gitzone format
|
||||
|
||||
# 2. Save plan for review
|
||||
gitzone format --save-plan format-changes.json
|
||||
|
||||
# 3. Apply formatting
|
||||
gitzone format --from-plan format-changes.json
|
||||
|
||||
# 4. If something goes wrong, rollback
|
||||
gitzone format --rollback
|
||||
# 3. Apply from saved plan
|
||||
gitzone format --from-plan format-changes.json --write
|
||||
```
|
||||
|
||||
### Database-Driven Development
|
||||
|
||||
```bash
|
||||
# 1. Start MongoDB and MinIO
|
||||
# 1. Start MongoDB, MinIO, and Elasticsearch
|
||||
gitzone services start
|
||||
|
||||
# 2. Get connection string for your app
|
||||
# 2. Get connection details
|
||||
gitzone services config
|
||||
|
||||
# 3. Connect with MongoDB Compass
|
||||
@@ -447,43 +459,44 @@ gitzone services clean # ⚠️ Warning: deletes data
|
||||
|
||||
### CI/CD Platforms
|
||||
|
||||
- **GitLab CI** - Full pipeline support with templates
|
||||
- **GitHub Actions** - Automated workflows
|
||||
- **Docker** - Container-based deployments
|
||||
- **GitLab CI** — full pipeline support with templates
|
||||
- **GitHub Actions** — automated workflows
|
||||
- **Docker** — container-based deployments
|
||||
|
||||
### Development Tools
|
||||
|
||||
- **TypeScript** - First-class support
|
||||
- **Prettier** - Code formatting
|
||||
- **npm/pnpm** - Package management
|
||||
- **MongoDB** - Local database service
|
||||
- **MinIO** - S3-compatible object storage
|
||||
- **MongoDB Compass** - Database GUI integration
|
||||
- **TypeScript** — first-class support
|
||||
- **Prettier** — code formatting
|
||||
- **pnpm** — package management
|
||||
- **MongoDB** — local database service
|
||||
- **MinIO** — S3-compatible object storage
|
||||
- **Elasticsearch** — search and analytics
|
||||
- **MongoDB Compass** — database GUI integration
|
||||
|
||||
### Version Control
|
||||
|
||||
- **Git** - Deep integration
|
||||
- **Semantic Versioning** - Automatic version bumping
|
||||
- **Conventional Commits** - Standardized commit messages
|
||||
- **Git** — deep integration
|
||||
- **Semantic Versioning** — automatic version bumping
|
||||
- **Conventional Commits** — standardized commit messages
|
||||
- **AI-Powered Analysis** — intelligent commit suggestions via `@git.zone/tsdoc`
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
1. **Use aliases**: Add `alias gz='gitzone'` to your shell profile
|
||||
2. **Combine commands**: `gitzone format --yes && gitzone commit`
|
||||
2. **Combine flags**: `gitzone commit -ypbr` for the full auto workflow
|
||||
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
|
||||
5. **Save format plans**: Review changes before applying
|
||||
6. **Port management**: Let services auto-assign ports to avoid conflicts
|
||||
7. **Use MongoDB Compass**: `gitzone services compass` for visual DB management
|
||||
8. **Global service management**: `gitzone services status -g` to see all projects' services at once
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Format Command Shows "Cancelled"
|
||||
|
||||
If the format command shows cancelled even after confirming:
|
||||
|
||||
- Check your `npmextra.json` configuration
|
||||
- Try with `--yes` flag to skip confirmation
|
||||
- Try with `--yes --write` flags
|
||||
- Use `--verbose` for detailed output
|
||||
|
||||
### Docker Commands Fail
|
||||
@@ -496,19 +509,20 @@ 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
|
||||
|
||||
# Reassign ports if there are conflicts
|
||||
gitzone services reconfigure
|
||||
```
|
||||
|
||||
### Template Creation Issues
|
||||
|
||||
Verify npm/pnpm is properly configured:
|
||||
Verify pnpm/npm is properly configured:
|
||||
|
||||
```bash
|
||||
npm config get registry
|
||||
@@ -524,12 +538,11 @@ npm config get registry
|
||||
|
||||
gitzone is optimized for speed:
|
||||
|
||||
- **Parallel processing** for format operations
|
||||
- **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
|
||||
- ⚡ **Parallel processing** for format operations
|
||||
- 🧠 **Smart caching** to avoid redundant work
|
||||
- 📊 **Incremental updates** for meta repositories
|
||||
- 🐳 **Isolated services** prevent resource conflicts
|
||||
- 🎲 **Auto port assignment** eliminates manual configuration
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
|
||||
1
test
Submodule
1
test
Submodule
Submodule test added at 0b89443584
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/cli',
|
||||
version: '2.12.2',
|
||||
version: '2.13.10',
|
||||
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.'
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ export class GitzoneConfig {
|
||||
public data: IGitzoneConfigData;
|
||||
|
||||
public async readConfigFromCwd() {
|
||||
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
|
||||
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||
this.data = smartconfigInstance.dataFor<IGitzoneConfigData>('@git.zone/cli', {});
|
||||
|
||||
// Read szci config for backward compatibility
|
||||
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
||||
const szciConfig = smartconfigInstance.dataFor<any>('@ship.zone/szci', {});
|
||||
|
||||
// Prefer accessLevel from @git.zone/cli.release, fallback to @ship.zone/szci.npmAccessLevel
|
||||
const accessLevel =
|
||||
|
||||
@@ -63,22 +63,6 @@ export let run = async () => {
|
||||
const config = GitzoneConfig.fromCwd();
|
||||
const modFormat = await import('./mod_format/index.js');
|
||||
|
||||
// Handle rollback commands
|
||||
if (argvArg.rollback) {
|
||||
await modFormat.handleRollback(argvArg.rollback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvArg['list-backups']) {
|
||||
await modFormat.handleListBackups();
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvArg['clean-backups']) {
|
||||
await modFormat.handleCleanBackups();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle format with options
|
||||
// Default is dry-mode, use --write/-w to apply changes
|
||||
await modFormat.run({
|
||||
@@ -90,7 +74,6 @@ export let run = async () => {
|
||||
fromPlan: argvArg['from-plan'],
|
||||
detailed: argvArg.detailed,
|
||||
interactive: argvArg.interactive !== false,
|
||||
parallel: argvArg.parallel !== false,
|
||||
verbose: argvArg.verbose,
|
||||
diff: argvArg.diff,
|
||||
});
|
||||
|
||||
@@ -8,9 +8,9 @@ import * as ui from './mod.ui.js';
|
||||
import { ReleaseConfig } from '../mod_config/classes.releaseconfig.js';
|
||||
|
||||
export const run = async (argvArg: any) => {
|
||||
// Read commit config from npmextra.json
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const gitzoneConfig = npmextraConfig.dataFor<{
|
||||
// Read commit config from .smartconfig.json
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||
const gitzoneConfig = smartconfigInstance.dataFor<{
|
||||
commit?: {
|
||||
alwaysTest?: boolean;
|
||||
alwaysBuild?: boolean;
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface ICommitConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages commit configuration stored in npmextra.json
|
||||
* Manages commit configuration stored in .smartconfig.json
|
||||
* under @git.zone/cli.commit namespace
|
||||
*/
|
||||
export class CommitConfig {
|
||||
@@ -28,11 +28,11 @@ export class CommitConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from npmextra.json
|
||||
* Load configuration from .smartconfig.json
|
||||
*/
|
||||
public async load(): Promise<void> {
|
||||
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
|
||||
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||
|
||||
this.config = {
|
||||
alwaysTest: gitzoneConfig?.commit?.alwaysTest ?? false,
|
||||
@@ -41,37 +41,37 @@ export class CommitConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration to npmextra.json
|
||||
* Save configuration to .smartconfig.json
|
||||
*/
|
||||
public async save(): Promise<void> {
|
||||
const npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
|
||||
let npmextraData: any = {};
|
||||
const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json');
|
||||
let smartconfigData: any = {};
|
||||
|
||||
// Read existing npmextra.json
|
||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
||||
npmextraData = JSON.parse(content as string);
|
||||
// Read existing .smartconfig.json
|
||||
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||
smartconfigData = JSON.parse(content as string);
|
||||
}
|
||||
|
||||
// Ensure @git.zone/cli namespace exists
|
||||
if (!npmextraData['@git.zone/cli']) {
|
||||
npmextraData['@git.zone/cli'] = {};
|
||||
if (!smartconfigData['@git.zone/cli']) {
|
||||
smartconfigData['@git.zone/cli'] = {};
|
||||
}
|
||||
|
||||
// Ensure commit object exists
|
||||
if (!npmextraData['@git.zone/cli'].commit) {
|
||||
npmextraData['@git.zone/cli'].commit = {};
|
||||
if (!smartconfigData['@git.zone/cli'].commit) {
|
||||
smartconfigData['@git.zone/cli'].commit = {};
|
||||
}
|
||||
|
||||
// Update commit settings
|
||||
npmextraData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
|
||||
npmextraData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
|
||||
smartconfigData['@git.zone/cli'].commit.alwaysTest = this.config.alwaysTest;
|
||||
smartconfigData['@git.zone/cli'].commit.alwaysBuild = this.config.alwaysBuild;
|
||||
|
||||
// Write back to file
|
||||
await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.file(smartconfigPath)
|
||||
.encoding('utf8')
|
||||
.write(JSON.stringify(npmextraData, null, 2));
|
||||
.write(JSON.stringify(smartconfigData, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@ export interface IReleaseConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages release configuration stored in npmextra.json
|
||||
* Manages release configuration stored in .smartconfig.json
|
||||
* under @git.zone/cli.release namespace
|
||||
*/
|
||||
export class ReleaseConfig {
|
||||
@@ -30,14 +30,14 @@ export class ReleaseConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from npmextra.json
|
||||
* Load configuration from .smartconfig.json
|
||||
*/
|
||||
public async load(): Promise<void> {
|
||||
const npmextraInstance = new plugins.npmextra.Npmextra(this.cwd);
|
||||
const gitzoneConfig = npmextraInstance.dataFor<any>('@git.zone/cli', {});
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(this.cwd);
|
||||
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||
|
||||
// Also check szci for backward compatibility
|
||||
const szciConfig = npmextraInstance.dataFor<any>('@ship.zone/szci', {});
|
||||
const szciConfig = smartconfigInstance.dataFor<any>('@ship.zone/szci', {});
|
||||
|
||||
this.config = {
|
||||
registries: gitzoneConfig?.release?.registries || [],
|
||||
@@ -46,37 +46,37 @@ export class ReleaseConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration to npmextra.json
|
||||
* Save configuration to .smartconfig.json
|
||||
*/
|
||||
public async save(): Promise<void> {
|
||||
const npmextraPath = plugins.path.join(this.cwd, 'npmextra.json');
|
||||
let npmextraData: any = {};
|
||||
const smartconfigPath = plugins.path.join(this.cwd, '.smartconfig.json');
|
||||
let smartconfigData: any = {};
|
||||
|
||||
// Read existing npmextra.json
|
||||
if (await plugins.smartfs.file(npmextraPath).exists()) {
|
||||
const content = await plugins.smartfs.file(npmextraPath).encoding('utf8').read();
|
||||
npmextraData = JSON.parse(content as string);
|
||||
// Read existing .smartconfig.json
|
||||
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||
smartconfigData = JSON.parse(content as string);
|
||||
}
|
||||
|
||||
// Ensure @git.zone/cli namespace exists
|
||||
if (!npmextraData['@git.zone/cli']) {
|
||||
npmextraData['@git.zone/cli'] = {};
|
||||
if (!smartconfigData['@git.zone/cli']) {
|
||||
smartconfigData['@git.zone/cli'] = {};
|
||||
}
|
||||
|
||||
// Ensure release object exists
|
||||
if (!npmextraData['@git.zone/cli'].release) {
|
||||
npmextraData['@git.zone/cli'].release = {};
|
||||
if (!smartconfigData['@git.zone/cli'].release) {
|
||||
smartconfigData['@git.zone/cli'].release = {};
|
||||
}
|
||||
|
||||
// Update registries and accessLevel
|
||||
npmextraData['@git.zone/cli'].release.registries = this.config.registries;
|
||||
npmextraData['@git.zone/cli'].release.accessLevel = this.config.accessLevel;
|
||||
smartconfigData['@git.zone/cli'].release.registries = this.config.registries;
|
||||
smartconfigData['@git.zone/cli'].release.accessLevel = this.config.accessLevel;
|
||||
|
||||
// Write back to file
|
||||
await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.file(smartconfigPath)
|
||||
.encoding('utf8')
|
||||
.write(JSON.stringify(npmextraData, null, 2));
|
||||
.write(JSON.stringify(smartconfigData, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,23 +8,23 @@ import { runFormatter, type ICheckResult } from '../mod_format/index.js';
|
||||
export { ReleaseConfig, CommitConfig };
|
||||
|
||||
/**
|
||||
* Format npmextra.json with diff preview
|
||||
* Format .smartconfig.json with diff preview
|
||||
* Shows diff first, asks for confirmation, then applies
|
||||
*/
|
||||
async function formatNpmextraWithDiff(): Promise<void> {
|
||||
async function formatSmartconfigWithDiff(): Promise<void> {
|
||||
// Check for diffs first
|
||||
const checkResult = await runFormatter('npmextra', {
|
||||
const checkResult = await runFormatter('smartconfig', {
|
||||
checkOnly: true,
|
||||
showDiff: true,
|
||||
}) as ICheckResult | void;
|
||||
|
||||
if (checkResult && checkResult.hasDiff) {
|
||||
const shouldApply = await plugins.smartinteract.SmartInteract.getCliConfirmation(
|
||||
'Apply formatting changes to npmextra.json?',
|
||||
'Apply formatting changes to .smartconfig.json?',
|
||||
true
|
||||
);
|
||||
if (shouldApply) {
|
||||
await runFormatter('npmextra', { silent: true });
|
||||
await runFormatter('smartconfig', { silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ async function handleAdd(url?: string): Promise<void> {
|
||||
if (added) {
|
||||
await config.save();
|
||||
plugins.logger.log('success', `Added registry: ${url}`);
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('warn', `Registry already exists: ${url}`);
|
||||
}
|
||||
@@ -223,7 +223,7 @@ async function handleRemove(url?: string): Promise<void> {
|
||||
if (removed) {
|
||||
await config.save();
|
||||
plugins.logger.log('success', `Removed registry: ${url}`);
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('warn', `Registry not found: ${url}`);
|
||||
}
|
||||
@@ -250,7 +250,7 @@ async function handleClear(): Promise<void> {
|
||||
config.clearRegistries();
|
||||
await config.save();
|
||||
plugins.logger.log('success', 'All registries cleared.');
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
} else {
|
||||
plugins.logger.log('info', 'Operation cancelled.');
|
||||
}
|
||||
@@ -290,7 +290,7 @@ async function handleAccessLevel(level?: string): Promise<void> {
|
||||
config.setAccessLevel(level as 'public' | 'private');
|
||||
await config.save();
|
||||
plugins.logger.log('success', `Access level set to: ${level}`);
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,7 +350,7 @@ async function handleCommitInteractive(config: CommitConfig): Promise<void> {
|
||||
await config.save();
|
||||
|
||||
plugins.logger.log('success', 'Commit configuration updated');
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +368,7 @@ async function handleCommitSetting(config: CommitConfig, setting: string, value?
|
||||
|
||||
await config.save();
|
||||
plugins.logger.log('success', `Set ${setting} to ${boolValue}`);
|
||||
await formatNpmextraWithDiff();
|
||||
await formatSmartconfigWithDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,11 +2,12 @@ import * as plugins from './mod.plugins.js';
|
||||
import { FormatContext } from './classes.formatcontext.js';
|
||||
import type { IPlannedChange, ICheckResult } from './interfaces.format.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
import { FormatStats } from './classes.formatstats.js';
|
||||
|
||||
export abstract class BaseFormatter {
|
||||
protected context: FormatContext;
|
||||
protected project: Project;
|
||||
protected stats: any; // Will be FormatStats from context
|
||||
protected stats: FormatStats;
|
||||
|
||||
constructor(context: FormatContext, project: Project) {
|
||||
this.context = context;
|
||||
@@ -36,9 +37,6 @@ export abstract class BaseFormatter {
|
||||
}
|
||||
|
||||
await this.postExecute();
|
||||
} catch (error) {
|
||||
// Don't rollback here - let the FormatPlanner handle it
|
||||
throw error;
|
||||
} finally {
|
||||
this.stats.endModule(this.name, startTime);
|
||||
}
|
||||
@@ -53,13 +51,10 @@ export abstract class BaseFormatter {
|
||||
}
|
||||
|
||||
protected async modifyFile(filepath: string, content: string): Promise<void> {
|
||||
// Validate filepath before writing
|
||||
if (!filepath || filepath.trim() === '') {
|
||||
throw new Error(`Invalid empty filepath in modifyFile`);
|
||||
}
|
||||
|
||||
// Ensure we have a proper path with directory component
|
||||
// If the path has no directory component (e.g., "package.json"), prepend "./"
|
||||
let normalizedPath = filepath;
|
||||
if (!plugins.path.parse(filepath).dir) {
|
||||
normalizedPath = './' + filepath;
|
||||
@@ -69,44 +64,46 @@ export abstract class BaseFormatter {
|
||||
}
|
||||
|
||||
protected async createFile(filepath: string, content: string): Promise<void> {
|
||||
await plugins.smartfs.file(filepath).encoding('utf8').write(content);
|
||||
let normalizedPath = filepath;
|
||||
if (!plugins.path.parse(filepath).dir) {
|
||||
normalizedPath = './' + filepath;
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
const dir = plugins.path.dirname(normalizedPath);
|
||||
if (dir && dir !== '.') {
|
||||
await plugins.smartfs.directory(dir).recursive().create();
|
||||
}
|
||||
|
||||
await plugins.smartfs.file(normalizedPath).encoding('utf8').write(content);
|
||||
}
|
||||
|
||||
protected async deleteFile(filepath: string): Promise<void> {
|
||||
await plugins.smartfs.file(filepath).delete();
|
||||
}
|
||||
|
||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for diffs without applying changes
|
||||
* Returns information about what would change
|
||||
*/
|
||||
async check(): Promise<ICheckResult> {
|
||||
const changes = await this.analyze();
|
||||
const diffs: ICheckResult['diffs'] = [];
|
||||
|
||||
for (const change of changes) {
|
||||
// Skip generic changes that don't have actual content
|
||||
if (change.path === '<various files>') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.type === 'modify' || change.type === 'create') {
|
||||
// Read current content if file exists
|
||||
let currentContent: string | undefined;
|
||||
try {
|
||||
currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||
} catch {
|
||||
// File doesn't exist yet
|
||||
currentContent = undefined;
|
||||
}
|
||||
|
||||
const newContent = change.content;
|
||||
|
||||
// Check if there's an actual diff
|
||||
if (currentContent !== newContent && newContent !== undefined) {
|
||||
diffs.push({
|
||||
path: change.path,
|
||||
@@ -116,7 +113,6 @@ export abstract class BaseFormatter {
|
||||
});
|
||||
}
|
||||
} else if (change.type === 'delete') {
|
||||
// Check if file exists before marking for deletion
|
||||
try {
|
||||
const currentContent = await plugins.smartfs.file(change.path).encoding('utf8').read() as string;
|
||||
diffs.push({
|
||||
@@ -137,9 +133,6 @@ export abstract class BaseFormatter {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a single diff using smartdiff
|
||||
*/
|
||||
displayDiff(diff: ICheckResult['diffs'][0]): void {
|
||||
console.log(`\n--- ${diff.path}`);
|
||||
if (diff.before && diff.after) {
|
||||
@@ -150,7 +143,6 @@ export abstract class BaseFormatter {
|
||||
}));
|
||||
} else if (diff.after && !diff.before) {
|
||||
console.log(' (new file)');
|
||||
// Show first few lines of new content
|
||||
const lines = diff.after.split('\n').slice(0, 10);
|
||||
lines.forEach(line => console.log(` + ${line}`));
|
||||
if (diff.after.split('\n').length > 10) {
|
||||
@@ -161,9 +153,6 @@ export abstract class BaseFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all diffs from a check result
|
||||
*/
|
||||
displayAllDiffs(result: ICheckResult): void {
|
||||
if (!result.hasDiff) {
|
||||
console.log(' No changes detected');
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
export interface IFileCache {
|
||||
path: string;
|
||||
checksum: string;
|
||||
modified: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface ICacheManifest {
|
||||
version: string;
|
||||
lastFormat: number;
|
||||
files: IFileCache[];
|
||||
}
|
||||
|
||||
export class ChangeCache {
|
||||
private cacheDir: string;
|
||||
private manifestPath: string;
|
||||
private cacheVersion = '1.0.0';
|
||||
|
||||
constructor() {
|
||||
this.cacheDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-cache');
|
||||
this.manifestPath = plugins.path.join(this.cacheDir, 'manifest.json');
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await plugins.smartfs.directory(this.cacheDir).recursive().create();
|
||||
}
|
||||
|
||||
async getManifest(): Promise<ICacheManifest> {
|
||||
const defaultManifest: ICacheManifest = {
|
||||
version: this.cacheVersion,
|
||||
lastFormat: 0,
|
||||
files: [],
|
||||
};
|
||||
|
||||
const exists = await plugins.smartfs.file(this.manifestPath).exists();
|
||||
if (!exists) {
|
||||
return defaultManifest;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = (await plugins.smartfs
|
||||
.file(this.manifestPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const manifest = JSON.parse(content);
|
||||
|
||||
// Validate the manifest structure
|
||||
if (this.isValidManifest(manifest)) {
|
||||
return manifest;
|
||||
} else {
|
||||
console.warn('Invalid manifest structure, returning default manifest');
|
||||
return defaultManifest;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to read cache manifest: ${error.message}, returning default manifest`,
|
||||
);
|
||||
// Try to delete the corrupted file
|
||||
try {
|
||||
await plugins.smartfs.file(this.manifestPath).delete();
|
||||
} catch (removeError) {
|
||||
// Ignore removal errors
|
||||
}
|
||||
return defaultManifest;
|
||||
}
|
||||
}
|
||||
|
||||
async saveManifest(manifest: ICacheManifest): Promise<void> {
|
||||
// Validate before saving
|
||||
if (!this.isValidManifest(manifest)) {
|
||||
throw new Error('Invalid manifest structure, cannot save');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
await plugins.smartfs.directory(this.cacheDir).recursive().create();
|
||||
|
||||
// Write directly with proper JSON stringification
|
||||
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||
await plugins.smartfs
|
||||
.file(this.manifestPath)
|
||||
.encoding('utf8')
|
||||
.write(jsonContent);
|
||||
}
|
||||
|
||||
async hasFileChanged(filePath: string): Promise<boolean> {
|
||||
const absolutePath = plugins.path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: plugins.path.join(paths.cwd, filePath);
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(absolutePath).exists();
|
||||
if (!exists) {
|
||||
return true; // File doesn't exist, so it's "changed" (will be created)
|
||||
}
|
||||
|
||||
// Get current file stats
|
||||
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||
|
||||
// Skip directories
|
||||
if (stats.isDirectory) {
|
||||
return false; // Directories are not processed
|
||||
}
|
||||
|
||||
const content = (await plugins.smartfs
|
||||
.file(absolutePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const currentChecksum = this.calculateChecksum(content);
|
||||
|
||||
// Get cached info
|
||||
const manifest = await this.getManifest();
|
||||
const cachedFile = manifest.files.find((f) => f.path === filePath);
|
||||
|
||||
if (!cachedFile) {
|
||||
return true; // Not in cache, so it's changed
|
||||
}
|
||||
|
||||
// Compare checksums
|
||||
return (
|
||||
cachedFile.checksum !== currentChecksum ||
|
||||
cachedFile.size !== stats.size ||
|
||||
cachedFile.modified !== stats.mtime.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
async updateFileCache(filePath: string): Promise<void> {
|
||||
const absolutePath = plugins.path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: plugins.path.join(paths.cwd, filePath);
|
||||
|
||||
// Get current file stats
|
||||
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||
|
||||
// Skip directories
|
||||
if (stats.isDirectory) {
|
||||
return; // Don't cache directories
|
||||
}
|
||||
|
||||
const content = (await plugins.smartfs
|
||||
.file(absolutePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const checksum = this.calculateChecksum(content);
|
||||
|
||||
// Update manifest
|
||||
const manifest = await this.getManifest();
|
||||
const existingIndex = manifest.files.findIndex((f) => f.path === filePath);
|
||||
|
||||
const cacheEntry: IFileCache = {
|
||||
path: filePath,
|
||||
checksum,
|
||||
modified: stats.mtime.getTime(),
|
||||
size: stats.size,
|
||||
};
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
manifest.files[existingIndex] = cacheEntry;
|
||||
} else {
|
||||
manifest.files.push(cacheEntry);
|
||||
}
|
||||
|
||||
manifest.lastFormat = Date.now();
|
||||
await this.saveManifest(manifest);
|
||||
}
|
||||
|
||||
async getChangedFiles(filePaths: string[]): Promise<string[]> {
|
||||
const changedFiles: string[] = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
if (await this.hasFileChanged(filePath)) {
|
||||
changedFiles.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return changedFiles;
|
||||
}
|
||||
|
||||
async clean(): Promise<void> {
|
||||
const manifest = await this.getManifest();
|
||||
const validFiles: IFileCache[] = [];
|
||||
|
||||
// Remove entries for files that no longer exist
|
||||
for (const file of manifest.files) {
|
||||
const absolutePath = plugins.path.isAbsolute(file.path)
|
||||
? file.path
|
||||
: plugins.path.join(paths.cwd, file.path);
|
||||
|
||||
if (await plugins.smartfs.file(absolutePath).exists()) {
|
||||
validFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
manifest.files = validFiles;
|
||||
await this.saveManifest(manifest);
|
||||
}
|
||||
|
||||
private calculateChecksum(content: string | Buffer): string {
|
||||
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
|
||||
private isValidManifest(manifest: any): manifest is ICacheManifest {
|
||||
// Check if manifest has the required structure
|
||||
if (!manifest || typeof manifest !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if (
|
||||
typeof manifest.version !== 'string' ||
|
||||
typeof manifest.lastFormat !== 'number' ||
|
||||
!Array.isArray(manifest.files)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each file entry
|
||||
for (const file of manifest.files) {
|
||||
if (
|
||||
!file ||
|
||||
typeof file !== 'object' ||
|
||||
typeof file.path !== 'string' ||
|
||||
typeof file.checksum !== 'string' ||
|
||||
typeof file.modified !== 'number' ||
|
||||
typeof file.size !== 'number'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { BaseFormatter } from './classes.baseformatter.js';
|
||||
|
||||
export interface IModuleDependency {
|
||||
module: string;
|
||||
dependencies: Set<string>;
|
||||
dependents: Set<string>;
|
||||
}
|
||||
|
||||
export class DependencyAnalyzer {
|
||||
private moduleDependencies: Map<string, IModuleDependency> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.initializeDependencies();
|
||||
}
|
||||
|
||||
private initializeDependencies(): void {
|
||||
// Define dependencies between format modules
|
||||
const dependencies = {
|
||||
cleanup: [], // No dependencies
|
||||
npmextra: [], // No dependencies
|
||||
license: ['npmextra'], // Depends on npmextra for config
|
||||
packagejson: ['npmextra'], // Depends on npmextra for config
|
||||
templates: ['npmextra', 'packagejson'], // Depends on both
|
||||
gitignore: ['templates'], // Depends on templates
|
||||
tsconfig: ['packagejson'], // Depends on package.json
|
||||
prettier: [
|
||||
'cleanup',
|
||||
'npmextra',
|
||||
'packagejson',
|
||||
'templates',
|
||||
'gitignore',
|
||||
'tsconfig',
|
||||
], // Runs after most others
|
||||
readme: ['npmextra', 'packagejson'], // Depends on project metadata
|
||||
copy: ['npmextra'], // Depends on config
|
||||
};
|
||||
|
||||
// Initialize all modules
|
||||
for (const [module, deps] of Object.entries(dependencies)) {
|
||||
this.moduleDependencies.set(module, {
|
||||
module,
|
||||
dependencies: new Set(deps),
|
||||
dependents: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
// Build reverse dependencies (dependents)
|
||||
for (const [module, deps] of Object.entries(dependencies)) {
|
||||
for (const dep of deps) {
|
||||
const depModule = this.moduleDependencies.get(dep);
|
||||
if (depModule) {
|
||||
depModule.dependents.add(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
|
||||
const modulesMap = new Map(modules.map((m) => [m.name, m]));
|
||||
const executed = new Set<string>();
|
||||
const groups: BaseFormatter[][] = [];
|
||||
|
||||
while (executed.size < modules.length) {
|
||||
const currentGroup: BaseFormatter[] = [];
|
||||
|
||||
for (const module of modules) {
|
||||
if (executed.has(module.name)) continue;
|
||||
|
||||
const dependency = this.moduleDependencies.get(module.name);
|
||||
if (!dependency) {
|
||||
// Unknown module, execute in isolation
|
||||
currentGroup.push(module);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if all dependencies have been executed
|
||||
const allDepsExecuted = Array.from(dependency.dependencies).every(
|
||||
(dep) => executed.has(dep) || !modulesMap.has(dep),
|
||||
);
|
||||
|
||||
if (allDepsExecuted) {
|
||||
currentGroup.push(module);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGroup.length === 0) {
|
||||
// Circular dependency or error - execute remaining modules
|
||||
for (const module of modules) {
|
||||
if (!executed.has(module.name)) {
|
||||
currentGroup.push(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentGroup.forEach((m) => executed.add(m.name));
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
canRunInParallel(module1: string, module2: string): boolean {
|
||||
const dep1 = this.moduleDependencies.get(module1);
|
||||
const dep2 = this.moduleDependencies.get(module2);
|
||||
|
||||
if (!dep1 || !dep2) return false;
|
||||
|
||||
// Check if module1 depends on module2 or vice versa
|
||||
return (
|
||||
!dep1.dependencies.has(module2) &&
|
||||
!dep2.dependencies.has(module1) &&
|
||||
!dep1.dependents.has(module2) &&
|
||||
!dep2.dependents.has(module1)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@ import * as plugins from './mod.plugins.js';
|
||||
import { FormatContext } from './classes.formatcontext.js';
|
||||
import { BaseFormatter } from './classes.baseformatter.js';
|
||||
import type { IFormatPlan, IPlannedChange } from './interfaces.format.js';
|
||||
import { getModuleIcon } from './interfaces.format.js';
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import { DependencyAnalyzer } from './classes.dependency-analyzer.js';
|
||||
import { DiffReporter } from './classes.diffreporter.js';
|
||||
|
||||
export class FormatPlanner {
|
||||
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
||||
private dependencyAnalyzer = new DependencyAnalyzer();
|
||||
private diffReporter = new DiffReporter();
|
||||
|
||||
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
||||
@@ -18,7 +17,6 @@ export class FormatPlanner {
|
||||
filesAdded: 0,
|
||||
filesModified: 0,
|
||||
filesRemoved: 0,
|
||||
estimatedTime: 0,
|
||||
},
|
||||
changes: [],
|
||||
warnings: [],
|
||||
@@ -32,7 +30,6 @@ export class FormatPlanner {
|
||||
for (const change of changes) {
|
||||
plan.changes.push(change);
|
||||
|
||||
// Update summary
|
||||
switch (change.type) {
|
||||
case 'create':
|
||||
plan.summary.filesAdded++;
|
||||
@@ -58,7 +55,6 @@ export class FormatPlanner {
|
||||
plan.summary.filesAdded +
|
||||
plan.summary.filesModified +
|
||||
plan.summary.filesRemoved;
|
||||
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
|
||||
|
||||
return plan;
|
||||
}
|
||||
@@ -67,27 +63,20 @@ export class FormatPlanner {
|
||||
plan: IFormatPlan,
|
||||
modules: BaseFormatter[],
|
||||
context: FormatContext,
|
||||
parallel: boolean = false,
|
||||
): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Always use sequential execution to avoid race conditions
|
||||
for (const module of modules) {
|
||||
const changes = this.plannedChanges.get(module.name) || [];
|
||||
for (const module of modules) {
|
||||
const changes = this.plannedChanges.get(module.name) || [];
|
||||
|
||||
if (changes.length > 0) {
|
||||
logger.log('info', `Executing ${module.name} formatter...`);
|
||||
await module.execute(changes);
|
||||
}
|
||||
if (changes.length > 0) {
|
||||
logger.log('info', `Executing ${module.name} formatter...`);
|
||||
await module.execute(changes);
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
logger.log('info', `Format operations completed in ${duration}ms`);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
logger.log('info', `Format operations completed in ${duration}ms`);
|
||||
}
|
||||
|
||||
async displayPlan(
|
||||
@@ -103,7 +92,6 @@ export class FormatPlanner {
|
||||
console.log('');
|
||||
console.log('Changes by module:');
|
||||
|
||||
// Group changes by module
|
||||
const changesByModule = new Map<string, IPlannedChange[]>();
|
||||
for (const change of plan.changes) {
|
||||
const moduleChanges = changesByModule.get(change.module) || [];
|
||||
@@ -113,14 +101,13 @@ export class FormatPlanner {
|
||||
|
||||
for (const [module, changes] of changesByModule) {
|
||||
console.log(
|
||||
`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`,
|
||||
`\n${getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`,
|
||||
);
|
||||
|
||||
for (const change of changes) {
|
||||
const icon = this.getChangeIcon(change.type);
|
||||
console.log(` ${icon} ${change.path} - ${change.description}`);
|
||||
|
||||
// Show diff for modified files if detailed view is requested
|
||||
if (detailed && change.type === 'modify') {
|
||||
const diff = await this.diffReporter.generateDiffForChange(change);
|
||||
if (diff) {
|
||||
@@ -141,22 +128,6 @@ export class FormatPlanner {
|
||||
console.log('\n' + '━'.repeat(50));
|
||||
}
|
||||
|
||||
private getModuleIcon(module: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
packagejson: '📦',
|
||||
license: '📝',
|
||||
tsconfig: '🔧',
|
||||
cleanup: '🚮',
|
||||
gitignore: '🔒',
|
||||
prettier: '✨',
|
||||
readme: '📖',
|
||||
templates: '📄',
|
||||
npmextra: '⚙️',
|
||||
copy: '📋',
|
||||
};
|
||||
return icons[module] || '📁';
|
||||
}
|
||||
|
||||
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
||||
switch (type) {
|
||||
case 'create':
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import { getModuleIcon } from './interfaces.format.js';
|
||||
|
||||
export interface IModuleStats {
|
||||
name: string;
|
||||
@@ -23,8 +24,6 @@ export interface IFormatStats {
|
||||
totalModified: number;
|
||||
totalDeleted: number;
|
||||
totalErrors: number;
|
||||
cacheHits: number;
|
||||
cacheMisses: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,8 +42,6 @@ export class FormatStats {
|
||||
totalModified: 0,
|
||||
totalDeleted: 0,
|
||||
totalErrors: 0,
|
||||
cacheHits: 0,
|
||||
cacheMisses: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -107,14 +104,6 @@ export class FormatStats {
|
||||
}
|
||||
}
|
||||
|
||||
recordCacheHit(): void {
|
||||
this.stats.overallStats.cacheHits++;
|
||||
}
|
||||
|
||||
recordCacheMiss(): void {
|
||||
this.stats.overallStats.cacheMisses++;
|
||||
}
|
||||
|
||||
finish(): void {
|
||||
this.stats.endTime = Date.now();
|
||||
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
||||
@@ -135,20 +124,6 @@ export class FormatStats {
|
||||
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
||||
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
||||
|
||||
if (
|
||||
this.stats.overallStats.cacheHits > 0 ||
|
||||
this.stats.overallStats.cacheMisses > 0
|
||||
) {
|
||||
const cacheHitRate =
|
||||
(this.stats.overallStats.cacheHits /
|
||||
(this.stats.overallStats.cacheHits +
|
||||
this.stats.overallStats.cacheMisses)) *
|
||||
100;
|
||||
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
|
||||
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
|
||||
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
|
||||
}
|
||||
|
||||
// Module stats
|
||||
console.log('\nModule Breakdown:');
|
||||
console.log('─'.repeat(50));
|
||||
@@ -159,7 +134,7 @@ export class FormatStats {
|
||||
|
||||
for (const moduleStats of sortedModules) {
|
||||
console.log(
|
||||
`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
||||
`\n${getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
||||
);
|
||||
console.log(
|
||||
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
|
||||
@@ -211,19 +186,4 @@ export class FormatStats {
|
||||
}
|
||||
}
|
||||
|
||||
private getModuleIcon(module: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
packagejson: '📦',
|
||||
license: '📝',
|
||||
tsconfig: '🔧',
|
||||
cleanup: '🚮',
|
||||
gitignore: '🔒',
|
||||
prettier: '✨',
|
||||
readme: '📖',
|
||||
templates: '📄',
|
||||
npmextra: '⚙️',
|
||||
copy: '📋',
|
||||
};
|
||||
return icons[module] || '📁';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import type { IFormatOperation } from './interfaces.format.js';
|
||||
|
||||
export class RollbackManager {
|
||||
private backupDir: string;
|
||||
private manifestPath: string;
|
||||
|
||||
constructor() {
|
||||
this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
|
||||
this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
|
||||
}
|
||||
|
||||
async createOperation(): Promise<IFormatOperation> {
|
||||
await this.ensureBackupDir();
|
||||
|
||||
const operation: IFormatOperation = {
|
||||
id: this.generateOperationId(),
|
||||
timestamp: Date.now(),
|
||||
files: [],
|
||||
status: 'pending',
|
||||
};
|
||||
|
||||
await this.updateManifest(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
async backupFile(filepath: string, operationId: string): Promise<void> {
|
||||
const operation = await this.getOperation(operationId);
|
||||
if (!operation) {
|
||||
throw new Error(`Operation ${operationId} not found`);
|
||||
}
|
||||
|
||||
const absolutePath = plugins.path.isAbsolute(filepath)
|
||||
? filepath
|
||||
: plugins.path.join(paths.cwd, filepath);
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(absolutePath).exists();
|
||||
if (!exists) {
|
||||
// File doesn't exist yet (will be created), so we skip backup
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file content and metadata
|
||||
const content = (await plugins.smartfs
|
||||
.file(absolutePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const stats = await plugins.smartfs.file(absolutePath).stat();
|
||||
const checksum = this.calculateChecksum(content);
|
||||
|
||||
// Create backup
|
||||
const backupPath = this.getBackupPath(operationId, filepath);
|
||||
await plugins.smartfs
|
||||
.directory(plugins.path.dirname(backupPath))
|
||||
.recursive()
|
||||
.create();
|
||||
await plugins.smartfs.file(backupPath).encoding('utf8').write(content);
|
||||
|
||||
// Update operation
|
||||
operation.files.push({
|
||||
path: filepath,
|
||||
originalContent: content,
|
||||
checksum,
|
||||
permissions: stats.mode.toString(8),
|
||||
});
|
||||
|
||||
await this.updateManifest(operation);
|
||||
}
|
||||
|
||||
async rollback(operationId: string): Promise<void> {
|
||||
const operation = await this.getOperation(operationId);
|
||||
if (!operation) {
|
||||
// Operation doesn't exist, might have already been rolled back or never created
|
||||
console.warn(`Operation ${operationId} not found for rollback, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation.status === 'rolled-back') {
|
||||
throw new Error(`Operation ${operationId} has already been rolled back`);
|
||||
}
|
||||
|
||||
// Restore files in reverse order
|
||||
for (let i = operation.files.length - 1; i >= 0; i--) {
|
||||
const file = operation.files[i];
|
||||
const absolutePath = plugins.path.isAbsolute(file.path)
|
||||
? file.path
|
||||
: plugins.path.join(paths.cwd, file.path);
|
||||
|
||||
// Verify backup integrity
|
||||
const backupPath = this.getBackupPath(operationId, file.path);
|
||||
const backupContent = await plugins.smartfs
|
||||
.file(backupPath)
|
||||
.encoding('utf8')
|
||||
.read();
|
||||
const backupChecksum = this.calculateChecksum(backupContent);
|
||||
|
||||
if (backupChecksum !== file.checksum) {
|
||||
throw new Error(`Backup integrity check failed for ${file.path}`);
|
||||
}
|
||||
|
||||
// Restore file
|
||||
await plugins.smartfs
|
||||
.file(absolutePath)
|
||||
.encoding('utf8')
|
||||
.write(file.originalContent);
|
||||
|
||||
// Restore permissions
|
||||
const mode = parseInt(file.permissions, 8);
|
||||
// Note: Permissions restoration may not work on all platforms
|
||||
}
|
||||
|
||||
// Update operation status
|
||||
operation.status = 'rolled-back';
|
||||
await this.updateManifest(operation);
|
||||
}
|
||||
|
||||
async markComplete(operationId: string): Promise<void> {
|
||||
const operation = await this.getOperation(operationId);
|
||||
if (!operation) {
|
||||
throw new Error(`Operation ${operationId} not found`);
|
||||
}
|
||||
|
||||
operation.status = 'completed';
|
||||
await this.updateManifest(operation);
|
||||
}
|
||||
|
||||
async cleanOldBackups(retentionDays: number): Promise<void> {
|
||||
const manifest = await this.getManifest();
|
||||
const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
||||
|
||||
const operationsToDelete = manifest.operations.filter(
|
||||
(op) => op.timestamp < cutoffTime && op.status === 'completed',
|
||||
);
|
||||
|
||||
for (const operation of operationsToDelete) {
|
||||
// Remove backup files
|
||||
const operationDir = plugins.path.join(
|
||||
this.backupDir,
|
||||
'operations',
|
||||
operation.id,
|
||||
);
|
||||
await plugins.smartfs.directory(operationDir).recursive().delete();
|
||||
|
||||
// Remove from manifest
|
||||
manifest.operations = manifest.operations.filter(
|
||||
(op) => op.id !== operation.id,
|
||||
);
|
||||
}
|
||||
|
||||
await this.saveManifest(manifest);
|
||||
}
|
||||
|
||||
async verifyBackup(operationId: string): Promise<boolean> {
|
||||
const operation = await this.getOperation(operationId);
|
||||
if (!operation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const file of operation.files) {
|
||||
const backupPath = this.getBackupPath(operationId, file.path);
|
||||
const exists = await plugins.smartfs.file(backupPath).exists();
|
||||
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const content = await plugins.smartfs
|
||||
.file(backupPath)
|
||||
.encoding('utf8')
|
||||
.read();
|
||||
const checksum = this.calculateChecksum(content);
|
||||
|
||||
if (checksum !== file.checksum) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async listBackups(): Promise<IFormatOperation[]> {
|
||||
const manifest = await this.getManifest();
|
||||
return manifest.operations;
|
||||
}
|
||||
|
||||
private async ensureBackupDir(): Promise<void> {
|
||||
await plugins.smartfs.directory(this.backupDir).recursive().create();
|
||||
await plugins.smartfs
|
||||
.directory(plugins.path.join(this.backupDir, 'operations'))
|
||||
.recursive()
|
||||
.create();
|
||||
}
|
||||
|
||||
private generateOperationId(): string {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const random = Math.random().toString(36).substring(2, 8);
|
||||
return `${timestamp}-${random}`;
|
||||
}
|
||||
|
||||
private getBackupPath(operationId: string, filepath: string): string {
|
||||
const filename = plugins.path.basename(filepath);
|
||||
const dir = plugins.path.dirname(filepath);
|
||||
const safeDir = dir.replace(/[/\\]/g, '__');
|
||||
return plugins.path.join(
|
||||
this.backupDir,
|
||||
'operations',
|
||||
operationId,
|
||||
'files',
|
||||
safeDir,
|
||||
`${filename}.backup`,
|
||||
);
|
||||
}
|
||||
|
||||
private calculateChecksum(content: string | Buffer): string {
|
||||
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
|
||||
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
||||
const defaultManifest = { operations: [] };
|
||||
|
||||
const exists = await plugins.smartfs.file(this.manifestPath).exists();
|
||||
if (!exists) {
|
||||
return defaultManifest;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = (await plugins.smartfs
|
||||
.file(this.manifestPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const manifest = JSON.parse(content);
|
||||
|
||||
// Validate the manifest structure
|
||||
if (this.isValidManifest(manifest)) {
|
||||
return manifest;
|
||||
} else {
|
||||
console.warn(
|
||||
'Invalid rollback manifest structure, returning default manifest',
|
||||
);
|
||||
return defaultManifest;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to read rollback manifest: ${error.message}, returning default manifest`,
|
||||
);
|
||||
// Try to delete the corrupted file
|
||||
try {
|
||||
await plugins.smartfs.file(this.manifestPath).delete();
|
||||
} catch (removeError) {
|
||||
// Ignore removal errors
|
||||
}
|
||||
return defaultManifest;
|
||||
}
|
||||
}
|
||||
|
||||
private async saveManifest(manifest: {
|
||||
operations: IFormatOperation[];
|
||||
}): Promise<void> {
|
||||
// Validate before saving
|
||||
if (!this.isValidManifest(manifest)) {
|
||||
throw new Error('Invalid rollback manifest structure, cannot save');
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
await this.ensureBackupDir();
|
||||
|
||||
// Write directly with proper JSON stringification
|
||||
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||
await plugins.smartfs
|
||||
.file(this.manifestPath)
|
||||
.encoding('utf8')
|
||||
.write(jsonContent);
|
||||
}
|
||||
|
||||
private async getOperation(
|
||||
operationId: string,
|
||||
): Promise<IFormatOperation | null> {
|
||||
const manifest = await this.getManifest();
|
||||
return manifest.operations.find((op) => op.id === operationId) || null;
|
||||
}
|
||||
|
||||
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
||||
const manifest = await this.getManifest();
|
||||
const existingIndex = manifest.operations.findIndex(
|
||||
(op) => op.id === operation.id,
|
||||
);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
manifest.operations[existingIndex] = operation;
|
||||
} else {
|
||||
manifest.operations.push(operation);
|
||||
}
|
||||
|
||||
await this.saveManifest(manifest);
|
||||
}
|
||||
|
||||
private isValidManifest(
|
||||
manifest: any,
|
||||
): manifest is { operations: IFormatOperation[] } {
|
||||
// Check if manifest has the required structure
|
||||
if (!manifest || typeof manifest !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if (!Array.isArray(manifest.operations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each operation entry
|
||||
for (const operation of manifest.operations) {
|
||||
if (
|
||||
!operation ||
|
||||
typeof operation !== 'object' ||
|
||||
typeof operation.id !== 'string' ||
|
||||
typeof operation.timestamp !== 'number' ||
|
||||
typeof operation.status !== 'string' ||
|
||||
!Array.isArray(operation.files)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each file in the operation
|
||||
for (const file of operation.files) {
|
||||
if (
|
||||
!file ||
|
||||
typeof file !== 'object' ||
|
||||
typeof file.path !== 'string' ||
|
||||
typeof file.checksum !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
const filesToDelete = [
|
||||
'defaults.yml',
|
||||
'yarn.lock',
|
||||
'package-lock.json',
|
||||
'tslint.json',
|
||||
];
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
for (const relativeFilePath of filesToDelete) {
|
||||
const fileExists = await plugins.smartfs.file(relativeFilePath).exists();
|
||||
if (fileExists) {
|
||||
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
||||
await plugins.smartfs
|
||||
.file(plugins.path.join(paths.cwd, relativeFilePath))
|
||||
.delete();
|
||||
} else {
|
||||
logger.log('info', `Project is free of ${relativeFilePath}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,93 +0,0 @@
|
||||
import type { Project } from '../classes.project.js';
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
const gitzoneConfig = await projectArg.gitzoneConfig;
|
||||
|
||||
// Get copy configuration from npmextra.json
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
|
||||
patterns: [],
|
||||
});
|
||||
|
||||
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
||||
logger.log('info', 'No copy patterns configured in npmextra.json');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pattern of copyConfig.patterns) {
|
||||
if (!pattern.from || !pattern.to) {
|
||||
logger.log('warn', 'Invalid copy pattern - missing "from" or "to" field');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle glob patterns
|
||||
const entries = await plugins.smartfs
|
||||
.directory('.')
|
||||
.recursive()
|
||||
.filter(pattern.from)
|
||||
.list();
|
||||
const files = entries.map((entry) => entry.path);
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = file;
|
||||
let destPath = pattern.to;
|
||||
|
||||
// If destination is a directory, preserve filename
|
||||
if (pattern.to.endsWith('/')) {
|
||||
const filename = plugins.path.basename(file);
|
||||
destPath = plugins.path.join(pattern.to, filename);
|
||||
}
|
||||
|
||||
// Handle template variables in destination path
|
||||
if (pattern.preservePath) {
|
||||
const relativePath = plugins.path.relative(
|
||||
plugins.path.dirname(pattern.from.replace(/\*/g, '')),
|
||||
file,
|
||||
);
|
||||
destPath = plugins.path.join(pattern.to, relativePath);
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
await plugins.smartfs
|
||||
.directory(plugins.path.dirname(destPath))
|
||||
.recursive()
|
||||
.create();
|
||||
|
||||
// Copy file
|
||||
await plugins.smartfs.file(sourcePath).copy(destPath);
|
||||
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
'error',
|
||||
`Failed to copy pattern ${pattern.from}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Example npmextra.json configuration:
|
||||
* {
|
||||
* "gitzone": {
|
||||
* "format": {
|
||||
* "copy": {
|
||||
* "patterns": [
|
||||
* {
|
||||
* "from": "src/assets/*",
|
||||
* "to": "dist/assets/",
|
||||
* "preservePath": true
|
||||
* },
|
||||
* {
|
||||
* "from": "config/*.json",
|
||||
* "to": "dist/"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@@ -1,54 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
const gitignorePath = plugins.path.join(paths.cwd, './.gitignore');
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
const gitignoreExists = await plugins.smartfs.file(gitignorePath).exists();
|
||||
let customContent = '';
|
||||
|
||||
if (gitignoreExists) {
|
||||
// lets get the existing gitignore file
|
||||
const existingGitIgnoreString = (await plugins.smartfs
|
||||
.file(gitignorePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Check for different custom section markers
|
||||
const customMarkers = ['#------# custom', '# custom'];
|
||||
for (const marker of customMarkers) {
|
||||
const splitResult = existingGitIgnoreString.split(marker);
|
||||
if (splitResult.length > 1) {
|
||||
// Get everything after the marker (excluding the marker itself)
|
||||
customContent = splitResult[1].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the template
|
||||
const templateModule = await import('../mod_template/index.js');
|
||||
const ciTemplate = await templateModule.getTemplate('gitignore');
|
||||
await ciTemplate.writeToDisk(paths.cwd);
|
||||
|
||||
// Append the custom content if it exists
|
||||
if (customContent) {
|
||||
const newGitignoreContent = (await plugins.smartfs
|
||||
.file(gitignorePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
// The template already ends with "#------# custom", so just append the content
|
||||
const finalContent =
|
||||
newGitignoreContent.trimEnd() + '\n' + customContent + '\n';
|
||||
await plugins.smartfs
|
||||
.file(gitignorePath)
|
||||
.encoding('utf8')
|
||||
.write(finalContent);
|
||||
logger.log('info', 'Updated .gitignore while preserving custom section!');
|
||||
} else {
|
||||
logger.log('info', 'Added a .gitignore!');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
|
||||
const incompatibleLicenses: string[] = ['AGPL', 'GPL', 'SSPL'];
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
const nodeModulesInstalled = await plugins.smartfs
|
||||
.directory(plugins.path.join(paths.cwd, 'node_modules'))
|
||||
.exists();
|
||||
if (!nodeModulesInstalled) {
|
||||
logger.log('warn', 'No node_modules found. Skipping license check');
|
||||
return;
|
||||
}
|
||||
const licenseChecker = await plugins.smartlegal.createLicenseChecker();
|
||||
const licenseCheckResult = await licenseChecker.excludeLicenseWithinPath(
|
||||
paths.cwd,
|
||||
incompatibleLicenses,
|
||||
);
|
||||
if (licenseCheckResult.failingModules.length === 0) {
|
||||
logger.log('info', 'Success -> licenses passed!');
|
||||
} else {
|
||||
logger.log('error', 'Error -> licenses failed. Here is why:');
|
||||
for (const failedModule of licenseCheckResult.failingModules) {
|
||||
console.log(
|
||||
`${failedModule.name} fails with license ${failedModule.license}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,142 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import * as gulpFunction from '@push.rocks/gulp-function';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
/**
|
||||
* Migrates npmextra.json from old namespace keys to new package-scoped keys
|
||||
*/
|
||||
const migrateNamespaceKeys = (npmextraJson: any): boolean => {
|
||||
let migrated = false;
|
||||
const migrations = [
|
||||
{ oldKey: 'gitzone', newKey: '@git.zone/cli' },
|
||||
{ oldKey: 'tsdoc', newKey: '@git.zone/tsdoc' },
|
||||
{ oldKey: 'npmdocker', newKey: '@git.zone/tsdocker' },
|
||||
{ oldKey: 'npmci', newKey: '@ship.zone/szci' },
|
||||
{ oldKey: 'szci', newKey: '@ship.zone/szci' },
|
||||
];
|
||||
for (const { oldKey, newKey } of migrations) {
|
||||
if (npmextraJson[oldKey] && !npmextraJson[newKey]) {
|
||||
npmextraJson[newKey] = npmextraJson[oldKey];
|
||||
delete npmextraJson[oldKey];
|
||||
migrated = true;
|
||||
console.log(`Migrated npmextra.json: ${oldKey} -> ${newKey}`);
|
||||
}
|
||||
}
|
||||
return migrated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel
|
||||
* This is a one-time migration for projects using the old location
|
||||
*/
|
||||
const migrateAccessLevel = (npmextraJson: any): boolean => {
|
||||
const szciConfig = npmextraJson['@ship.zone/szci'];
|
||||
|
||||
// Check if szci has npmAccessLevel that needs to be migrated
|
||||
if (!szciConfig?.npmAccessLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we already have the new location
|
||||
const gitzoneConfig = npmextraJson['@git.zone/cli'] || {};
|
||||
if (gitzoneConfig?.release?.accessLevel) {
|
||||
// Already migrated, just remove from szci
|
||||
delete szciConfig.npmAccessLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure @git.zone/cli and release exist
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!npmextraJson['@git.zone/cli'].release) {
|
||||
npmextraJson['@git.zone/cli'].release = {};
|
||||
}
|
||||
|
||||
// Migrate the value
|
||||
npmextraJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel;
|
||||
delete szciConfig.npmAccessLevel;
|
||||
|
||||
console.log(`Migrated npmAccessLevel to @git.zone/cli.release.accessLevel`);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* runs the npmextra file checking
|
||||
*/
|
||||
export const run = async (projectArg: Project) => {
|
||||
const formatSmartstream = new plugins.smartstream.StreamWrapper([
|
||||
plugins.smartgulp.src([`npmextra.json`]),
|
||||
gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => {
|
||||
const fileString = fileArg.contents.toString();
|
||||
const npmextraJson = JSON.parse(fileString);
|
||||
|
||||
// Migrate old namespace keys to new package-scoped keys
|
||||
migrateNamespaceKeys(npmextraJson);
|
||||
|
||||
// Migrate npmAccessLevel from szci to @git.zone/cli.release.accessLevel
|
||||
migrateAccessLevel(npmextraJson);
|
||||
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
|
||||
const expectedRepoInformation: string[] = [
|
||||
'projectType',
|
||||
'module.githost',
|
||||
'module.gitscope',
|
||||
'module.gitrepo',
|
||||
'module.description',
|
||||
'module.npmPackagename',
|
||||
'module.license',
|
||||
];
|
||||
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
if (
|
||||
!plugins.smartobject.smartGet(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
)
|
||||
) {
|
||||
interactInstance.addQuestions([
|
||||
{
|
||||
message: `What is the value of ${expectedRepoInformationItem}`,
|
||||
name: expectedRepoInformationItem,
|
||||
type: 'input',
|
||||
default: 'undefined variable',
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const answerbucket = await interactInstance.runQueue();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
const cliProvidedValue = answerbucket.getAnswerFor(
|
||||
expectedRepoInformationItem,
|
||||
);
|
||||
if (cliProvidedValue) {
|
||||
plugins.smartobject.smartAdd(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
cliProvidedValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// delete obsolete
|
||||
// tbd
|
||||
|
||||
if (!npmextraJson['@ship.zone/szci']) {
|
||||
npmextraJson['@ship.zone/szci'] = {};
|
||||
}
|
||||
|
||||
fileArg.setContentsFromString(JSON.stringify(npmextraJson, null, 2));
|
||||
}),
|
||||
plugins.smartgulp.replace(),
|
||||
]);
|
||||
await formatSmartstream.run().catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
@@ -1,196 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import * as gulpFunction from '@push.rocks/gulp-function';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
|
||||
/**
|
||||
* ensures a certain dependency
|
||||
*/
|
||||
const ensureDependency = async (
|
||||
packageJsonObjectArg: any,
|
||||
position: 'dep' | 'devDep' | 'everywhere',
|
||||
constraint: 'exclude' | 'include' | 'latest',
|
||||
dependencyArg: string,
|
||||
) => {
|
||||
const [packageName, version] = dependencyArg.includes('@')
|
||||
? dependencyArg.split('@').filter(Boolean)
|
||||
: [dependencyArg, 'latest'];
|
||||
|
||||
const targetSections: string[] = [];
|
||||
|
||||
switch (position) {
|
||||
case 'dep':
|
||||
targetSections.push('dependencies');
|
||||
break;
|
||||
case 'devDep':
|
||||
targetSections.push('devDependencies');
|
||||
break;
|
||||
case 'everywhere':
|
||||
targetSections.push('dependencies', 'devDependencies');
|
||||
break;
|
||||
}
|
||||
|
||||
for (const section of targetSections) {
|
||||
if (!packageJsonObjectArg[section]) {
|
||||
packageJsonObjectArg[section] = {};
|
||||
}
|
||||
|
||||
switch (constraint) {
|
||||
case 'exclude':
|
||||
delete packageJsonObjectArg[section][packageName];
|
||||
break;
|
||||
case 'include':
|
||||
if (!packageJsonObjectArg[section][packageName]) {
|
||||
packageJsonObjectArg[section][packageName] =
|
||||
version === 'latest' ? '^1.0.0' : version;
|
||||
}
|
||||
break;
|
||||
case 'latest':
|
||||
// Fetch latest version from npm
|
||||
try {
|
||||
const registry = new plugins.smartnpm.NpmRegistry();
|
||||
const packageInfo = await registry.getPackageInfo(packageName);
|
||||
const latestVersion = packageInfo['dist-tags'].latest;
|
||||
packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
'warn',
|
||||
`Could not fetch latest version for ${packageName}, using existing or default`,
|
||||
);
|
||||
if (!packageJsonObjectArg[section][packageName]) {
|
||||
packageJsonObjectArg[section][packageName] =
|
||||
version === 'latest' ? '^1.0.0' : version;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
const formatStreamWrapper = new plugins.smartstream.StreamWrapper([
|
||||
plugins.smartgulp.src([`package.json`]),
|
||||
gulpFunction.forEach(async (fileArg: plugins.smartfile.SmartFile) => {
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra(paths.cwd);
|
||||
const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {});
|
||||
const fileString = fileArg.contents.toString();
|
||||
const packageJson = JSON.parse(fileString);
|
||||
|
||||
// metadata
|
||||
packageJson.repository = {
|
||||
type: 'git',
|
||||
url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}.git`,
|
||||
};
|
||||
((packageJson.bugs = {
|
||||
url: `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}/issues`,
|
||||
}),
|
||||
(packageJson.homepage = `https://${gitzoneData.module.githost}/${gitzoneData.module.gitscope}/${gitzoneData.module.gitrepo}#readme`));
|
||||
|
||||
// Check for module type
|
||||
if (!packageJson.type) {
|
||||
logger.log('info', `setting packageJson.type to "module"`);
|
||||
packageJson.type = 'module';
|
||||
}
|
||||
|
||||
// Check for private or public
|
||||
if (packageJson.private !== undefined) {
|
||||
logger.log(
|
||||
'info',
|
||||
'Success -> found private/public info in package.json!',
|
||||
);
|
||||
} else {
|
||||
logger.log(
|
||||
'error',
|
||||
'found no private boolean! Setting it to private for now!',
|
||||
);
|
||||
packageJson.private = true;
|
||||
}
|
||||
|
||||
// Check for license
|
||||
if (packageJson.license) {
|
||||
logger.log('info', 'Success -> found license in package.json!');
|
||||
} else {
|
||||
logger.log(
|
||||
'error',
|
||||
'found no license! Setting it to UNLICENSED for now!',
|
||||
);
|
||||
packageJson.license = 'UNLICENSED';
|
||||
}
|
||||
|
||||
// Check for build script
|
||||
if (packageJson.scripts.build) {
|
||||
logger.log('info', 'Success -> found build script in package.json!');
|
||||
} else {
|
||||
logger.log(
|
||||
'error',
|
||||
'found no build script! Putting a placeholder there for now!',
|
||||
);
|
||||
packageJson.scripts.build = `echo "Not needed for now"`;
|
||||
}
|
||||
|
||||
// Check for buildDocs script
|
||||
if (!packageJson.scripts.buildDocs) {
|
||||
logger.log(
|
||||
'info',
|
||||
'found no buildDocs script! Putting tsdoc script there now.',
|
||||
);
|
||||
packageJson.scripts.buildDocs = `tsdoc`;
|
||||
}
|
||||
|
||||
// check for files
|
||||
packageJson.files = [
|
||||
'ts/**/*',
|
||||
'ts_web/**/*',
|
||||
'dist/**/*',
|
||||
'dist_*/**/*',
|
||||
'dist_ts/**/*',
|
||||
'dist_ts_web/**/*',
|
||||
'assets/**/*',
|
||||
'cli.js',
|
||||
'npmextra.json',
|
||||
'readme.md',
|
||||
];
|
||||
|
||||
// check for dependencies
|
||||
// Note: @push.rocks/tapbundle is deprecated - use @git.zone/tstest/tapbundle instead
|
||||
await ensureDependency(
|
||||
packageJson,
|
||||
'devDep',
|
||||
'exclude',
|
||||
'@push.rocks/tapbundle',
|
||||
);
|
||||
await ensureDependency(
|
||||
packageJson,
|
||||
'devDep',
|
||||
'latest',
|
||||
'@git.zone/tstest',
|
||||
);
|
||||
await ensureDependency(
|
||||
packageJson,
|
||||
'devDep',
|
||||
'latest',
|
||||
'@git.zone/tsbuild',
|
||||
);
|
||||
|
||||
// set overrides
|
||||
const overridesContent = (await plugins.smartfs
|
||||
.file(plugins.path.join(paths.assetsDir, 'overrides.json'))
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
const overrides = JSON.parse(overridesContent);
|
||||
packageJson.pnpm = packageJson.pnpm || {};
|
||||
packageJson.pnpm.overrides = overrides;
|
||||
|
||||
// exclude
|
||||
// TODO
|
||||
|
||||
fileArg.setContentsFromString(JSON.stringify(packageJson, null, 2));
|
||||
}),
|
||||
plugins.smartgulp.replace(),
|
||||
]);
|
||||
await formatStreamWrapper.run().catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import prettier from 'prettier';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
|
||||
const prettierDefaultTypeScriptConfig: prettier.Options = {
|
||||
printWidth: 100,
|
||||
parser: 'typescript',
|
||||
singleQuote: true,
|
||||
};
|
||||
|
||||
const prettierDefaultMarkdownConfig: prettier.Options = {
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
parser: 'markdown',
|
||||
};
|
||||
|
||||
const filesToFormat = [
|
||||
`ts/**/*.ts`,
|
||||
`test/**/*.ts`,
|
||||
`readme.md`,
|
||||
`docs/**/*.md`,
|
||||
];
|
||||
|
||||
const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
|
||||
switch (fileArg.parsedPath.ext) {
|
||||
case '.ts':
|
||||
return prettierDefaultTypeScriptConfig;
|
||||
case '.md':
|
||||
return prettierDefaultMarkdownConfig;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const prettierTypeScriptPipestop = plugins.through2.obj(
|
||||
async (fileArg: plugins.smartfile.SmartFile, enc, cb) => {
|
||||
const fileString = fileArg.contentBuffer.toString();
|
||||
const chosenConfig = choosePrettierConfig(fileArg);
|
||||
const filePasses = await prettier.check(fileString, chosenConfig);
|
||||
if (filePasses) {
|
||||
logger.log('info', `OK! -> ${fileArg.path} passes!`);
|
||||
cb(null);
|
||||
} else {
|
||||
logger.log('info', `${fileArg.path} is being reformated!`);
|
||||
const formatedFileString = await prettier.format(
|
||||
fileString,
|
||||
chosenConfig,
|
||||
);
|
||||
fileArg.setContentsFromString(formatedFileString);
|
||||
cb(null, fileArg);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
const formatStreamWrapper = new plugins.smartstream.StreamWrapper([
|
||||
plugins.smartgulp.src(filesToFormat),
|
||||
prettierTypeScriptPipestop,
|
||||
plugins.smartgulp.replace(),
|
||||
]);
|
||||
await formatStreamWrapper.run().catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
export const run = async () => {
|
||||
const readmePath = plugins.path.join(paths.cwd, 'readme.md');
|
||||
const readmeHintsPath = plugins.path.join(paths.cwd, 'readme.hints.md');
|
||||
|
||||
// Check and initialize readme.md if it doesn't exist
|
||||
const readmeExists = await plugins.smartfs.file(readmePath).exists();
|
||||
if (!readmeExists) {
|
||||
await plugins.smartfs.file(readmePath)
|
||||
.encoding('utf8')
|
||||
.write('# Project Readme\n\nThis is the initial readme file.');
|
||||
console.log('Initialized readme.md');
|
||||
} else {
|
||||
console.log('readme.md already exists');
|
||||
}
|
||||
|
||||
// Check and initialize readme.hints.md if it doesn't exist
|
||||
const readmeHintsExists = await plugins.smartfs.file(readmeHintsPath).exists();
|
||||
if (!readmeHintsExists) {
|
||||
await plugins.smartfs.file(readmeHintsPath)
|
||||
.encoding('utf8')
|
||||
.write('# Project Readme Hints\n\nThis is the initial readme hints file.');
|
||||
console.log('Initialized readme.hints.md');
|
||||
} else {
|
||||
console.log('readme.hints.md already exists');
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
/**
|
||||
* takes care of updating files from templates
|
||||
*/
|
||||
export const run = async (project: Project) => {
|
||||
const templateModule = await import('../mod_template/index.js');
|
||||
|
||||
// update vscode
|
||||
const vscodeTemplate = await templateModule.getTemplate('vscode');
|
||||
await vscodeTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', `Updated vscode template!`);
|
||||
|
||||
// update gitlab ci and Dockerfile
|
||||
switch (project.gitzoneConfig.data.projectType) {
|
||||
case 'npm':
|
||||
case 'wcc':
|
||||
if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
|
||||
const ciTemplateDefault =
|
||||
await templateModule.getTemplate('ci_default');
|
||||
ciTemplateDefault.writeToDisk(paths.cwd);
|
||||
} else {
|
||||
const ciTemplateDefault =
|
||||
await templateModule.getTemplate('ci_default_private');
|
||||
ciTemplateDefault.writeToDisk(paths.cwd);
|
||||
}
|
||||
logger.log('info', 'Updated .gitlabci.yml!');
|
||||
break;
|
||||
case 'service':
|
||||
case 'website':
|
||||
const ciTemplateDocker = await templateModule.getTemplate('ci_docker');
|
||||
await ciTemplateDocker.writeToDisk(paths.cwd);
|
||||
logger.log('info', 'Updated CI/CD config files!');
|
||||
|
||||
// lets care about docker
|
||||
const dockerTemplate =
|
||||
await templateModule.getTemplate('dockerfile_service');
|
||||
dockerTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', 'Updated Dockerfile!');
|
||||
|
||||
// lets care about cli
|
||||
const cliTemplate = await templateModule.getTemplate('cli');
|
||||
await cliTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', 'Updated cli.ts.js and cli.js!');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// update html
|
||||
if (project.gitzoneConfig.data.projectType === 'website') {
|
||||
const websiteUpdateTemplate =
|
||||
await templateModule.getTemplate('website_update');
|
||||
const variables = {
|
||||
assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
|
||||
legalUrl: project.gitzoneConfig.data.module.legalUrl,
|
||||
};
|
||||
console.log(
|
||||
'updating website template with variables\n',
|
||||
JSON.stringify(variables, null, 2),
|
||||
);
|
||||
websiteUpdateTemplate.supplyVariables(variables);
|
||||
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', `Updated html for website!`);
|
||||
} else if (project.gitzoneConfig.data.projectType === 'service') {
|
||||
const websiteUpdateTemplate =
|
||||
await templateModule.getTemplate('service_update');
|
||||
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', `Updated html for element template!`);
|
||||
} else if (project.gitzoneConfig.data.projectType === 'wcc') {
|
||||
const wccUpdateTemplate = await templateModule.getTemplate('wcc_update');
|
||||
await wccUpdateTemplate.writeToDisk(paths.cwd);
|
||||
logger.log('info', `Updated html for wcc template!`);
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import * as plugins from './mod.plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { logger } from '../gitzone.logging.js';
|
||||
import { Project } from '../classes.project.js';
|
||||
|
||||
export const run = async (projectArg: Project) => {
|
||||
// lets care about tsconfig.json
|
||||
logger.log('info', 'Formatting tsconfig.json...');
|
||||
const factory = plugins.smartfile.SmartFileFactory.nodeFs();
|
||||
const tsconfigSmartfile = await factory.fromFilePath(
|
||||
plugins.path.join(paths.cwd, 'tsconfig.json'),
|
||||
);
|
||||
const tsconfigObject = JSON.parse(tsconfigSmartfile.parseContentAsString());
|
||||
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
||||
tsconfigObject.compilerOptions.baseUrl = '.';
|
||||
tsconfigObject.compilerOptions.paths = {};
|
||||
const tsPublishMod = await import('@git.zone/tspublish');
|
||||
const tsPublishInstance = new tsPublishMod.TsPublish();
|
||||
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
|
||||
for (const publishModule of Object.keys(publishModules)) {
|
||||
const publishConfig = publishModules[publishModule];
|
||||
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
|
||||
`./${publishModule}/index.js`,
|
||||
];
|
||||
}
|
||||
await tsconfigSmartfile.editContentAsString(async () => {
|
||||
return JSON.stringify(tsconfigObject, null, 2);
|
||||
});
|
||||
await tsconfigSmartfile.write();
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as cleanupFormatter from '../format.cleanup.js';
|
||||
|
||||
export class CleanupFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
|
||||
@@ -17,15 +17,15 @@ export class CopyFormatter extends BaseFormatter {
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
|
||||
// Get copy configuration from npmextra.json
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const copyConfig = npmextraConfig.dataFor<{ patterns: ICopyPattern[] }>(
|
||||
// Get copy configuration from .smartconfig.json
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||
const copyConfig = smartconfigInstance.dataFor<{ patterns: ICopyPattern[] }>(
|
||||
'gitzone.format.copy',
|
||||
{ patterns: [] },
|
||||
);
|
||||
|
||||
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
||||
logVerbose('No copy patterns configured in npmextra.json');
|
||||
logVerbose('No copy patterns configured in .smartconfig.json');
|
||||
return changes;
|
||||
}
|
||||
|
||||
@@ -103,10 +103,6 @@ export class CopyFormatter extends BaseFormatter {
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (!change.content) return;
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = plugins.path.dirname(change.path);
|
||||
await plugins.smartfs.directory(destDir).recursive().create();
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, change.content);
|
||||
} else {
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import * as paths from '../../paths.js';
|
||||
import { logger } from '../../gitzone.logging.js';
|
||||
|
||||
// Standard gitignore template content (without front-matter)
|
||||
const GITIGNORE_TEMPLATE = `.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# AI
|
||||
.claude/
|
||||
.serena/
|
||||
|
||||
#------# custom`;
|
||||
|
||||
export class GitignoreFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'gitignore';
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the standard gitignore template from the asset file,
|
||||
* stripping the YAML frontmatter.
|
||||
*/
|
||||
private async getStandardTemplate(): Promise<string> {
|
||||
const templatePath = plugins.path.join(paths.templatesDir, 'gitignore', '_gitignore');
|
||||
const raw = (await plugins.smartfs
|
||||
.file(templatePath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Strip YAML frontmatter (---\n...\n---)
|
||||
const frontmatterEnd = raw.indexOf('---', 3);
|
||||
if (frontmatterEnd !== -1) {
|
||||
return raw.slice(frontmatterEnd + 3).trimStart();
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const gitignorePath = '.gitignore';
|
||||
|
||||
const standardTemplate = await this.getStandardTemplate();
|
||||
|
||||
// Check if file exists and extract custom content
|
||||
let customContent = '';
|
||||
const exists = await plugins.smartfs.file(gitignorePath).exists();
|
||||
@@ -59,11 +56,11 @@ export class GitignoreFormatter extends BaseFormatter {
|
||||
}
|
||||
|
||||
// Compute new content
|
||||
let newContent = GITIGNORE_TEMPLATE;
|
||||
let newContent = standardTemplate;
|
||||
if (customContent) {
|
||||
newContent = GITIGNORE_TEMPLATE + '\n' + customContent + '\n';
|
||||
newContent = standardTemplate + '\n' + customContent + '\n';
|
||||
} else {
|
||||
newContent = GITIGNORE_TEMPLATE + '\n';
|
||||
newContent = standardTemplate + '\n';
|
||||
}
|
||||
|
||||
// Read current content to compare
|
||||
@@ -75,7 +72,6 @@ export class GitignoreFormatter extends BaseFormatter {
|
||||
.read()) as string;
|
||||
}
|
||||
|
||||
// Determine change type
|
||||
if (!exists) {
|
||||
changes.push({
|
||||
type: 'create',
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
/**
|
||||
* Migrates npmextra.json from old namespace keys to new package-scoped keys
|
||||
*/
|
||||
const migrateNamespaceKeys = (npmextraJson: any): boolean => {
|
||||
let migrated = false;
|
||||
const migrations = [
|
||||
{ oldKey: 'gitzone', newKey: '@git.zone/cli' },
|
||||
{ oldKey: 'tsdoc', newKey: '@git.zone/tsdoc' },
|
||||
{ oldKey: 'npmdocker', newKey: '@git.zone/tsdocker' },
|
||||
{ oldKey: 'npmci', newKey: '@ship.zone/szci' },
|
||||
{ oldKey: 'szci', newKey: '@ship.zone/szci' },
|
||||
];
|
||||
for (const { oldKey, newKey } of migrations) {
|
||||
if (npmextraJson[oldKey] && !npmextraJson[newKey]) {
|
||||
npmextraJson[newKey] = npmextraJson[oldKey];
|
||||
delete npmextraJson[oldKey];
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
return migrated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel
|
||||
*/
|
||||
const migrateAccessLevel = (npmextraJson: any): boolean => {
|
||||
const szciConfig = npmextraJson['@ship.zone/szci'];
|
||||
|
||||
if (!szciConfig?.npmAccessLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gitzoneConfig = npmextraJson['@git.zone/cli'] || {};
|
||||
if (gitzoneConfig?.release?.accessLevel) {
|
||||
delete szciConfig.npmAccessLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!npmextraJson['@git.zone/cli'].release) {
|
||||
npmextraJson['@git.zone/cli'].release = {};
|
||||
}
|
||||
|
||||
npmextraJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel;
|
||||
delete szciConfig.npmAccessLevel;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export class NpmextraFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'npmextra';
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
const npmextraPath = 'npmextra.json';
|
||||
|
||||
// Check if file exists
|
||||
const exists = await plugins.smartfs.file(npmextraPath).exists();
|
||||
if (!exists) {
|
||||
logVerbose('npmextra.json does not exist, skipping');
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Parse and compute new content
|
||||
const npmextraJson = JSON.parse(currentContent);
|
||||
|
||||
// Apply migrations (these are automatic, non-interactive)
|
||||
migrateNamespaceKeys(npmextraJson);
|
||||
migrateAccessLevel(npmextraJson);
|
||||
|
||||
// Ensure namespaces exist
|
||||
if (!npmextraJson['@git.zone/cli']) {
|
||||
npmextraJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!npmextraJson['@ship.zone/szci']) {
|
||||
npmextraJson['@ship.zone/szci'] = {};
|
||||
}
|
||||
|
||||
const newContent = JSON.stringify(npmextraJson, null, 2);
|
||||
|
||||
// Only add change if content differs
|
||||
if (newContent !== currentContent) {
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: npmextraPath,
|
||||
module: this.name,
|
||||
description: 'Migrate and format npmextra.json',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (change.type !== 'modify' || !change.content) return;
|
||||
|
||||
// Parse the content to check for missing required fields
|
||||
const npmextraJson = JSON.parse(change.content);
|
||||
|
||||
// Check for missing required module information
|
||||
const expectedRepoInformation: string[] = [
|
||||
'projectType',
|
||||
'module.githost',
|
||||
'module.gitscope',
|
||||
'module.gitrepo',
|
||||
'module.description',
|
||||
'module.npmPackagename',
|
||||
'module.license',
|
||||
];
|
||||
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
if (
|
||||
!plugins.smartobject.smartGet(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
)
|
||||
) {
|
||||
interactInstance.addQuestions([
|
||||
{
|
||||
message: `What is the value of ${expectedRepoInformationItem}`,
|
||||
name: expectedRepoInformationItem,
|
||||
type: 'input',
|
||||
default: 'undefined variable',
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const answerbucket = await interactInstance.runQueue();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
const cliProvidedValue = answerbucket.getAnswerFor(
|
||||
expectedRepoInformationItem,
|
||||
);
|
||||
if (cliProvidedValue) {
|
||||
plugins.smartobject.smartAdd(
|
||||
npmextraJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
cliProvidedValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the final content
|
||||
const finalContent = JSON.stringify(npmextraJson, null, 2);
|
||||
await this.modifyFile(change.path, finalContent);
|
||||
logger.log('info', 'Updated npmextra.json');
|
||||
}
|
||||
}
|
||||
@@ -100,9 +100,9 @@ export class PackageJsonFormatter extends BaseFormatter {
|
||||
// Parse and compute new content
|
||||
const packageJson = JSON.parse(currentContent);
|
||||
|
||||
// Get gitzone config from npmextra
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra(paths.cwd);
|
||||
const gitzoneData: any = npmextraConfig.dataFor('@git.zone/cli', {});
|
||||
// Get gitzone config from smartconfig
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||
const gitzoneData: any = smartconfigInstance.dataFor('@git.zone/cli', {});
|
||||
|
||||
// Set metadata from gitzone config
|
||||
if (gitzoneData.module) {
|
||||
@@ -156,7 +156,7 @@ export class PackageJsonFormatter extends BaseFormatter {
|
||||
'dist_ts_web/**/*',
|
||||
'assets/**/*',
|
||||
'cli.js',
|
||||
'npmextra.json',
|
||||
'.smartconfig.json',
|
||||
'readme.md',
|
||||
];
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
const rootConfigFiles = [
|
||||
'package.json',
|
||||
'tsconfig.json',
|
||||
'npmextra.json',
|
||||
'.smartconfig.json',
|
||||
'.prettierrc',
|
||||
'.prettierrc.json',
|
||||
'.prettierrc.js',
|
||||
@@ -79,12 +79,9 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
// Remove duplicates
|
||||
const uniqueFiles = [...new Set(allFiles)];
|
||||
|
||||
// Get all files that match the pattern
|
||||
const files = uniqueFiles;
|
||||
|
||||
// Ensure we only process actual files (not directories)
|
||||
const validFiles: string[] = [];
|
||||
for (const file of files) {
|
||||
for (const file of uniqueFiles) {
|
||||
try {
|
||||
const stats = await plugins.smartfs.file(file).stat();
|
||||
if (!stats.isDirectory) {
|
||||
@@ -96,14 +93,7 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
// Check which files need formatting
|
||||
for (const file of validFiles) {
|
||||
// Skip files that haven't changed
|
||||
if (!(await this.shouldProcessFile(file))) {
|
||||
logVerbose(`Skipping ${file} - no changes detected`);
|
||||
continue;
|
||||
}
|
||||
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: file,
|
||||
@@ -232,7 +222,7 @@ export class PrettierFormatter extends BaseFormatter {
|
||||
|
||||
private async getPrettierConfig(): Promise<any> {
|
||||
// Try to load prettier config from the project
|
||||
const prettierConfig = new plugins.npmextra.Npmextra();
|
||||
const prettierConfig = new plugins.smartconfig.Smartconfig();
|
||||
return prettierConfig.dataFor('prettier', {
|
||||
// Default prettier config
|
||||
singleQuote: true,
|
||||
|
||||
213
ts/mod_format/formatters/smartconfig.formatter.ts
Normal file
213
ts/mod_format/formatters/smartconfig.formatter.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { BaseFormatter } from '../classes.baseformatter.js';
|
||||
import type { IPlannedChange } from '../interfaces.format.js';
|
||||
import * as plugins from '../mod.plugins.js';
|
||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||
|
||||
/**
|
||||
* Migrates .smartconfig.json from old namespace keys to new package-scoped keys
|
||||
*/
|
||||
const migrateNamespaceKeys = (smartconfigJson: any): boolean => {
|
||||
let migrated = false;
|
||||
const migrations = [
|
||||
{ oldKey: 'gitzone', newKey: '@git.zone/cli' },
|
||||
{ oldKey: 'tsdoc', newKey: '@git.zone/tsdoc' },
|
||||
{ oldKey: 'npmdocker', newKey: '@git.zone/tsdocker' },
|
||||
{ oldKey: 'npmci', newKey: '@ship.zone/szci' },
|
||||
{ oldKey: 'szci', newKey: '@ship.zone/szci' },
|
||||
];
|
||||
for (const { oldKey, newKey } of migrations) {
|
||||
if (smartconfigJson[oldKey]) {
|
||||
if (!smartconfigJson[newKey]) {
|
||||
smartconfigJson[newKey] = smartconfigJson[oldKey];
|
||||
} else {
|
||||
smartconfigJson[newKey] = {
|
||||
...smartconfigJson[oldKey],
|
||||
...smartconfigJson[newKey],
|
||||
};
|
||||
}
|
||||
delete smartconfigJson[oldKey];
|
||||
migrated = true;
|
||||
}
|
||||
}
|
||||
return migrated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates npmAccessLevel from @ship.zone/szci to @git.zone/cli.release.accessLevel
|
||||
*/
|
||||
const migrateAccessLevel = (smartconfigJson: any): boolean => {
|
||||
const szciConfig = smartconfigJson['@ship.zone/szci'];
|
||||
|
||||
if (!szciConfig?.npmAccessLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gitzoneConfig = smartconfigJson['@git.zone/cli'] || {};
|
||||
if (gitzoneConfig?.release?.accessLevel) {
|
||||
delete szciConfig.npmAccessLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!smartconfigJson['@git.zone/cli']) {
|
||||
smartconfigJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!smartconfigJson['@git.zone/cli'].release) {
|
||||
smartconfigJson['@git.zone/cli'].release = {};
|
||||
}
|
||||
|
||||
smartconfigJson['@git.zone/cli'].release.accessLevel = szciConfig.npmAccessLevel;
|
||||
delete szciConfig.npmAccessLevel;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Config file names in priority order (newest → oldest)
|
||||
const CONFIG_FILE_NAMES = ['.smartconfig.json', 'smartconfig.json', 'npmextra.json'];
|
||||
const TARGET_CONFIG_FILE = '.smartconfig.json';
|
||||
|
||||
export class SmartconfigFormatter extends BaseFormatter {
|
||||
get name(): string {
|
||||
return 'smartconfig';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the config file, checking in priority order.
|
||||
* Returns the path and whether it needs renaming.
|
||||
*/
|
||||
private async findConfigFile(): Promise<{ path: string; needsRename: boolean } | null> {
|
||||
for (const filename of CONFIG_FILE_NAMES) {
|
||||
const exists = await plugins.smartfs.file(filename).exists();
|
||||
if (exists) {
|
||||
return {
|
||||
path: filename,
|
||||
needsRename: filename !== TARGET_CONFIG_FILE,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async analyze(): Promise<IPlannedChange[]> {
|
||||
const changes: IPlannedChange[] = [];
|
||||
|
||||
const configFile = await this.findConfigFile();
|
||||
if (!configFile) {
|
||||
logVerbose('No config file found (.smartconfig.json, smartconfig.json, or npmextra.json), skipping');
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const currentContent = (await plugins.smartfs
|
||||
.file(configFile.path)
|
||||
.encoding('utf8')
|
||||
.read()) as string;
|
||||
|
||||
// Parse and apply migrations
|
||||
const smartconfigJson = JSON.parse(currentContent);
|
||||
migrateNamespaceKeys(smartconfigJson);
|
||||
migrateAccessLevel(smartconfigJson);
|
||||
|
||||
// Ensure namespaces exist
|
||||
if (!smartconfigJson['@git.zone/cli']) {
|
||||
smartconfigJson['@git.zone/cli'] = {};
|
||||
}
|
||||
if (!smartconfigJson['@ship.zone/szci']) {
|
||||
smartconfigJson['@ship.zone/szci'] = {};
|
||||
}
|
||||
|
||||
const newContent = JSON.stringify(smartconfigJson, null, 2);
|
||||
|
||||
// If file needs renaming, plan a create + delete
|
||||
if (configFile.needsRename) {
|
||||
changes.push({
|
||||
type: 'create',
|
||||
path: TARGET_CONFIG_FILE,
|
||||
module: this.name,
|
||||
description: `Migrate ${configFile.path} to ${TARGET_CONFIG_FILE}`,
|
||||
content: newContent,
|
||||
});
|
||||
changes.push({
|
||||
type: 'delete',
|
||||
path: configFile.path,
|
||||
module: this.name,
|
||||
description: `Remove old ${configFile.path}`,
|
||||
});
|
||||
} else if (newContent !== currentContent) {
|
||||
// File is already .smartconfig.json, just needs content update
|
||||
changes.push({
|
||||
type: 'modify',
|
||||
path: TARGET_CONFIG_FILE,
|
||||
module: this.name,
|
||||
description: 'Migrate and format .smartconfig.json',
|
||||
content: newContent,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (change.type === 'delete') {
|
||||
await this.deleteFile(change.path);
|
||||
logger.log('info', `Removed old config file ${change.path}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!change.content) return;
|
||||
|
||||
// Parse the content to check for missing required fields
|
||||
const smartconfigJson = JSON.parse(change.content);
|
||||
|
||||
const expectedRepoInformation: string[] = [
|
||||
'projectType',
|
||||
'module.githost',
|
||||
'module.gitscope',
|
||||
'module.gitrepo',
|
||||
'module.description',
|
||||
'module.npmPackagename',
|
||||
'module.license',
|
||||
];
|
||||
|
||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
if (
|
||||
!plugins.smartobject.smartGet(
|
||||
smartconfigJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
)
|
||||
) {
|
||||
interactInstance.addQuestions([
|
||||
{
|
||||
message: `What is the value of ${expectedRepoInformationItem}`,
|
||||
name: expectedRepoInformationItem,
|
||||
type: 'input',
|
||||
default: 'undefined variable',
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const answerbucket = await interactInstance.runQueue();
|
||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||
const cliProvidedValue = answerbucket.getAnswerFor(
|
||||
expectedRepoInformationItem,
|
||||
);
|
||||
if (cliProvidedValue) {
|
||||
plugins.smartobject.smartAdd(
|
||||
smartconfigJson['@git.zone/cli'],
|
||||
expectedRepoInformationItem,
|
||||
cliProvidedValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const finalContent = JSON.stringify(smartconfigJson, null, 2);
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, finalContent);
|
||||
} else {
|
||||
await this.modifyFile(change.path, finalContent);
|
||||
}
|
||||
logger.log('info', `Updated ${change.path}`);
|
||||
}
|
||||
}
|
||||
@@ -62,9 +62,6 @@ export class TemplatesFormatter extends BaseFormatter {
|
||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||
]);
|
||||
changes.push(...websiteChanges);
|
||||
} else if (projectType === 'service') {
|
||||
const serviceChanges = await this.analyzeTemplate('service_update', []);
|
||||
changes.push(...serviceChanges);
|
||||
} else if (projectType === 'wcc') {
|
||||
const wccChanges = await this.analyzeTemplate('wcc_update', [
|
||||
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
||||
@@ -139,12 +136,6 @@ export class TemplatesFormatter extends BaseFormatter {
|
||||
async applyChange(change: IPlannedChange): Promise<void> {
|
||||
if (!change.content) return;
|
||||
|
||||
// Ensure destination directory exists
|
||||
const destDir = plugins.path.dirname(change.path);
|
||||
if (destDir && destDir !== '.') {
|
||||
await plugins.smartfs.directory(destDir).recursive().create();
|
||||
}
|
||||
|
||||
if (change.type === 'create') {
|
||||
await this.createFile(change.path, change.content);
|
||||
} else {
|
||||
|
||||
@@ -30,9 +30,10 @@ export class TsconfigFormatter extends BaseFormatter {
|
||||
const tsconfigObject = JSON.parse(currentContent);
|
||||
tsconfigObject.compilerOptions = tsconfigObject.compilerOptions || {};
|
||||
tsconfigObject.compilerOptions.baseUrl = '.';
|
||||
tsconfigObject.compilerOptions.paths = {};
|
||||
const existingPaths = tsconfigObject.compilerOptions.paths || {};
|
||||
|
||||
// Get module paths from tspublish
|
||||
// Get module paths from tspublish, merging with existing custom paths
|
||||
const tspublishPaths: Record<string, string[]> = {};
|
||||
try {
|
||||
const tsPublishMod = await import('@git.zone/tspublish');
|
||||
const tsPublishInstance = new tsPublishMod.TsPublish();
|
||||
@@ -40,7 +41,7 @@ export class TsconfigFormatter extends BaseFormatter {
|
||||
|
||||
for (const publishModule of Object.keys(publishModules)) {
|
||||
const publishConfig = publishModules[publishModule];
|
||||
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
|
||||
tspublishPaths[`${publishConfig.name}`] = [
|
||||
`./${publishModule}/index.js`,
|
||||
];
|
||||
}
|
||||
@@ -48,6 +49,8 @@ export class TsconfigFormatter extends BaseFormatter {
|
||||
logVerbose(`Could not get tspublish modules: ${error.message}`);
|
||||
}
|
||||
|
||||
tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths };
|
||||
|
||||
const newContent = JSON.stringify(tsconfigObject, null, 2);
|
||||
|
||||
// Only add change if content differs
|
||||
|
||||
@@ -5,9 +5,8 @@ import { FormatPlanner } from './classes.formatplanner.js';
|
||||
import { BaseFormatter } from './classes.baseformatter.js';
|
||||
import { logger, setVerboseMode } from '../gitzone.logging.js';
|
||||
|
||||
// Import wrapper classes for formatters
|
||||
import { CleanupFormatter } from './formatters/cleanup.formatter.js';
|
||||
import { NpmextraFormatter } from './formatters/npmextra.formatter.js';
|
||||
import { SmartconfigFormatter } from './formatters/smartconfig.formatter.js';
|
||||
import { LicenseFormatter } from './formatters/license.formatter.js';
|
||||
import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
|
||||
import { TemplatesFormatter } from './formatters/templates.formatter.js';
|
||||
@@ -17,82 +16,66 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
|
||||
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
||||
import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||
|
||||
// Shared formatter class map used by both run() and runFormatter()
|
||||
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
||||
cleanup: CleanupFormatter,
|
||||
smartconfig: SmartconfigFormatter,
|
||||
license: LicenseFormatter,
|
||||
packagejson: PackageJsonFormatter,
|
||||
templates: TemplatesFormatter,
|
||||
gitignore: GitignoreFormatter,
|
||||
tsconfig: TsconfigFormatter,
|
||||
prettier: PrettierFormatter,
|
||||
readme: ReadmeFormatter,
|
||||
copy: CopyFormatter,
|
||||
};
|
||||
|
||||
// Formatters that don't require projectType to be set
|
||||
const formattersNotRequiringProjectType = ['smartconfig', 'prettier', 'cleanup', 'packagejson'];
|
||||
|
||||
export let run = async (
|
||||
options: {
|
||||
write?: boolean; // Explicitly write changes (default: false, dry-mode)
|
||||
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||
write?: boolean;
|
||||
dryRun?: boolean; // Deprecated, kept for compatibility
|
||||
yes?: boolean;
|
||||
planOnly?: boolean;
|
||||
savePlan?: string;
|
||||
fromPlan?: string;
|
||||
detailed?: boolean;
|
||||
interactive?: boolean;
|
||||
parallel?: boolean;
|
||||
verbose?: boolean;
|
||||
diff?: boolean; // Show file diffs
|
||||
diff?: boolean;
|
||||
} = {},
|
||||
): Promise<any> => {
|
||||
// Set verbose mode if requested
|
||||
if (options.verbose) {
|
||||
setVerboseMode(true);
|
||||
}
|
||||
|
||||
// Determine if we should write changes
|
||||
// Default is dry-mode (no writing) unless --write/-w is specified
|
||||
const shouldWrite = options.write ?? (options.dryRun === false);
|
||||
|
||||
const project = await Project.fromCwd({ requireProjectType: false });
|
||||
const context = new FormatContext();
|
||||
// Cache system removed - no longer needed
|
||||
const planner = new FormatPlanner();
|
||||
|
||||
// Get configuration from npmextra
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||
const formatConfig = npmextraConfig.dataFor<any>('@git.zone/cli.format', {
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
||||
const formatConfig = smartconfigInstance.dataFor<any>('@git.zone/cli.format', {
|
||||
interactive: true,
|
||||
showDiffs: false,
|
||||
autoApprove: false,
|
||||
planTimeout: 30000,
|
||||
rollback: {
|
||||
enabled: true,
|
||||
autoRollbackOnError: true,
|
||||
backupRetentionDays: 7,
|
||||
maxBackupSize: '100MB',
|
||||
excludePatterns: ['node_modules/**', '.git/**'],
|
||||
},
|
||||
modules: {
|
||||
skip: [],
|
||||
only: [],
|
||||
order: [],
|
||||
},
|
||||
parallel: true,
|
||||
cache: {
|
||||
enabled: true,
|
||||
clean: true, // Clean invalid entries from cache
|
||||
},
|
||||
});
|
||||
|
||||
// Cache cleaning removed - no longer using cache system
|
||||
|
||||
// Override config with command options
|
||||
const interactive = options.interactive ?? formatConfig.interactive;
|
||||
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
||||
const parallel = options.parallel ?? formatConfig.parallel;
|
||||
|
||||
try {
|
||||
// Initialize formatters
|
||||
const formatters = [
|
||||
new CleanupFormatter(context, project),
|
||||
new NpmextraFormatter(context, project),
|
||||
new LicenseFormatter(context, project),
|
||||
new PackageJsonFormatter(context, project),
|
||||
new TemplatesFormatter(context, project),
|
||||
new GitignoreFormatter(context, project),
|
||||
new TsconfigFormatter(context, project),
|
||||
new PrettierFormatter(context, project),
|
||||
new ReadmeFormatter(context, project),
|
||||
new CopyFormatter(context, project),
|
||||
];
|
||||
// Initialize formatters in execution order
|
||||
const formatters = Object.entries(formatterMap).map(
|
||||
([, FormatterClass]) => new FormatterClass(context, project),
|
||||
);
|
||||
|
||||
// Filter formatters based on configuration
|
||||
const activeFormatters = formatters.filter((formatter) => {
|
||||
@@ -128,13 +111,13 @@ export let run = async (
|
||||
logger.log('info', `Plan saved to ${options.savePlan}`);
|
||||
}
|
||||
|
||||
// Exit if plan-only mode
|
||||
if (options.planOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show diffs if requested (works in both dry-run and write modes)
|
||||
if (options.diff) {
|
||||
// Show diffs if explicitly requested or before interactive write confirmation
|
||||
const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove);
|
||||
if (showDiffs) {
|
||||
logger.log('info', 'Showing file diffs:');
|
||||
console.log('');
|
||||
|
||||
@@ -171,22 +154,16 @@ export let run = async (
|
||||
}
|
||||
|
||||
// Execute phase
|
||||
logger.log(
|
||||
'info',
|
||||
`Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`,
|
||||
);
|
||||
await planner.executePlan(plan, activeFormatters, context, parallel);
|
||||
logger.log('info', 'Executing format operations...');
|
||||
await planner.executePlan(plan, activeFormatters, context);
|
||||
|
||||
// Finish statistics tracking
|
||||
context.getFormatStats().finish();
|
||||
|
||||
// Display statistics
|
||||
const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true);
|
||||
const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true);
|
||||
if (showStats) {
|
||||
context.getFormatStats().displayStats();
|
||||
}
|
||||
|
||||
// Save stats if requested
|
||||
if (options.detailed) {
|
||||
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
||||
await context.getFormatStats().saveReport(statsPath);
|
||||
@@ -195,36 +172,13 @@ export let run = async (
|
||||
logger.log('success', 'Format operations completed successfully!');
|
||||
} catch (error) {
|
||||
logger.log('error', `Format operation failed: ${error.message}`);
|
||||
|
||||
// Rollback system has been removed for stability
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Export CLI command handlers
|
||||
export const handleRollback = async (operationId?: string): Promise<void> => {
|
||||
logger.log('info', 'Rollback system has been disabled for stability');
|
||||
};
|
||||
|
||||
export const handleListBackups = async (): Promise<void> => {
|
||||
logger.log('info', 'Backup system has been disabled for stability');
|
||||
};
|
||||
|
||||
export const handleCleanBackups = async (): Promise<void> => {
|
||||
logger.log(
|
||||
'info',
|
||||
'Backup cleaning has been disabled - backup system removed',
|
||||
);
|
||||
};
|
||||
|
||||
// Import the ICheckResult type for external use
|
||||
import type { ICheckResult } from './interfaces.format.js';
|
||||
export type { ICheckResult };
|
||||
|
||||
// Formatters that don't require projectType to be set
|
||||
const formattersNotRequiringProjectType = ['npmextra', 'prettier', 'cleanup', 'packagejson'];
|
||||
|
||||
/**
|
||||
* Run a single formatter by name (for use by other modules)
|
||||
*/
|
||||
@@ -232,29 +186,14 @@ export const runFormatter = async (
|
||||
formatterName: string,
|
||||
options: {
|
||||
silent?: boolean;
|
||||
checkOnly?: boolean; // Only check for diffs, don't apply
|
||||
showDiff?: boolean; // Show the diff output
|
||||
checkOnly?: boolean;
|
||||
showDiff?: boolean;
|
||||
} = {}
|
||||
): Promise<ICheckResult | void> => {
|
||||
// Determine if this formatter requires projectType
|
||||
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
||||
const project = await Project.fromCwd({ requireProjectType });
|
||||
const context = new FormatContext();
|
||||
|
||||
// Map formatter names to classes
|
||||
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
||||
cleanup: CleanupFormatter,
|
||||
npmextra: NpmextraFormatter,
|
||||
license: LicenseFormatter,
|
||||
packagejson: PackageJsonFormatter,
|
||||
templates: TemplatesFormatter,
|
||||
gitignore: GitignoreFormatter,
|
||||
tsconfig: TsconfigFormatter,
|
||||
prettier: PrettierFormatter,
|
||||
readme: ReadmeFormatter,
|
||||
copy: CopyFormatter,
|
||||
};
|
||||
|
||||
const FormatterClass = formatterMap[formatterName];
|
||||
if (!FormatterClass) {
|
||||
throw new Error(`Unknown formatter: ${formatterName}`);
|
||||
@@ -262,7 +201,6 @@ export const runFormatter = async (
|
||||
|
||||
const formatter = new FormatterClass(context, project);
|
||||
|
||||
// Check-only mode: just check for diffs and optionally display them
|
||||
if (options.checkOnly) {
|
||||
const result = await formatter.check();
|
||||
if (result.hasDiff && options.showDiff) {
|
||||
@@ -271,7 +209,6 @@ export const runFormatter = async (
|
||||
return result;
|
||||
}
|
||||
|
||||
// Normal mode: analyze and apply changes
|
||||
const changes = await formatter.analyze();
|
||||
|
||||
for (const change of changes) {
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
export type IFormatOperation = {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
files: Array<{
|
||||
path: string;
|
||||
originalContent: string;
|
||||
checksum: string;
|
||||
permissions: string;
|
||||
}>;
|
||||
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
export type IFormatPlan = {
|
||||
summary: {
|
||||
totalFiles: number;
|
||||
filesAdded: number;
|
||||
filesModified: number;
|
||||
filesRemoved: number;
|
||||
estimatedTime: number;
|
||||
};
|
||||
changes: Array<{
|
||||
type: 'create' | 'modify' | 'delete';
|
||||
path: string;
|
||||
module: string;
|
||||
description: string;
|
||||
diff?: string;
|
||||
size?: number;
|
||||
}>;
|
||||
warnings: Array<{
|
||||
level: 'info' | 'warning' | 'error';
|
||||
@@ -40,9 +24,6 @@ export type IPlannedChange = {
|
||||
module: string;
|
||||
description: string;
|
||||
content?: string; // New content for create/modify operations
|
||||
originalContent?: string; // Original content for comparison
|
||||
diff?: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
export interface ICheckResult {
|
||||
@@ -54,3 +35,19 @@ export interface ICheckResult {
|
||||
after?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function getModuleIcon(module: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
packagejson: '📦',
|
||||
license: '📝',
|
||||
tsconfig: '🔧',
|
||||
cleanup: '🚮',
|
||||
gitignore: '🔒',
|
||||
prettier: '✨',
|
||||
readme: '📖',
|
||||
templates: '📄',
|
||||
smartconfig: '⚙️',
|
||||
copy: '📋',
|
||||
};
|
||||
return icons[module] || '📁';
|
||||
}
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
export * from '../plugins.js';
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import * as path from 'path';
|
||||
import * as lik from '@push.rocks/lik';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartgulp from '@push.rocks/smartgulp';
|
||||
import * as smartinteract from '@push.rocks/smartinteract';
|
||||
import * as smartlegal from '@push.rocks/smartlegal';
|
||||
import * as smartobject from '@push.rocks/smartobject';
|
||||
import * as smartnpm from '@push.rocks/smartnpm';
|
||||
import * as smartstream from '@push.rocks/smartstream';
|
||||
import * as through2 from 'through2';
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as smartconfig from '@push.rocks/smartconfig';
|
||||
import * as smartdiff from '@push.rocks/smartdiff';
|
||||
|
||||
export {
|
||||
crypto,
|
||||
path,
|
||||
lik,
|
||||
smartfile,
|
||||
smartgulp,
|
||||
smartinteract,
|
||||
smartlegal,
|
||||
smartobject,
|
||||
smartnpm,
|
||||
smartstream,
|
||||
through2,
|
||||
npmextra,
|
||||
smartconfig,
|
||||
smartdiff,
|
||||
};
|
||||
|
||||
@@ -26,11 +26,11 @@ export interface IGlobalRegistryData {
|
||||
|
||||
export class GlobalRegistry {
|
||||
private static instance: GlobalRegistry | null = null;
|
||||
private kvStore: plugins.npmextra.KeyValueStore<IGlobalRegistryData>;
|
||||
private kvStore: plugins.smartconfig.KeyValueStore<IGlobalRegistryData>;
|
||||
private docker: DockerContainer;
|
||||
|
||||
private constructor() {
|
||||
this.kvStore = new plugins.npmextra.KeyValueStore({
|
||||
this.kvStore = new plugins.smartconfig.KeyValueStore({
|
||||
typeArg: 'userHomeDir',
|
||||
identityArg: 'gitzone-services',
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ServiceManager {
|
||||
await this.config.loadOrCreate();
|
||||
logger.log('info', `📋 Project: ${this.config.getConfig().PROJECT_NAME}`);
|
||||
|
||||
// Load service selection from npmextra.json
|
||||
// Load service selection from .smartconfig.json
|
||||
await this.loadServiceConfiguration();
|
||||
|
||||
// Validate and update ports if needed
|
||||
@@ -39,11 +39,11 @@ export class ServiceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load service configuration from npmextra.json
|
||||
* Load service configuration from .smartconfig.json
|
||||
*/
|
||||
private async loadServiceConfiguration(): Promise<void> {
|
||||
const npmextraConfig = new plugins.npmextra.Npmextra(process.cwd());
|
||||
const gitzoneConfig = npmextraConfig.dataFor<any>('@git.zone/cli', {});
|
||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(process.cwd());
|
||||
const gitzoneConfig = smartconfigInstance.dataFor<any>('@git.zone/cli', {});
|
||||
|
||||
// Check if services array exists
|
||||
if (!gitzoneConfig.services || !Array.isArray(gitzoneConfig.services) || gitzoneConfig.services.length === 0) {
|
||||
@@ -63,7 +63,7 @@ export class ServiceManager {
|
||||
|
||||
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||
|
||||
// Save to npmextra.json
|
||||
// Save to .smartconfig.json
|
||||
await this.saveServiceConfiguration(this.enabledServices);
|
||||
} else {
|
||||
this.enabledServices = gitzoneConfig.services;
|
||||
@@ -72,31 +72,31 @@ export class ServiceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save service configuration to npmextra.json
|
||||
* Save service configuration to .smartconfig.json
|
||||
*/
|
||||
private async saveServiceConfiguration(services: string[]): Promise<void> {
|
||||
const npmextraPath = plugins.path.join(process.cwd(), 'npmextra.json');
|
||||
let npmextraData: any = {};
|
||||
const smartconfigPath = plugins.path.join(process.cwd(), '.smartconfig.json');
|
||||
let smartconfigData: 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);
|
||||
// Read existing .smartconfig.json if it exists
|
||||
if (await plugins.smartfs.file(smartconfigPath).exists()) {
|
||||
const content = await plugins.smartfs.file(smartconfigPath).encoding('utf8').read();
|
||||
smartconfigData = JSON.parse(content as string);
|
||||
}
|
||||
|
||||
// Update @git.zone/cli.services
|
||||
if (!npmextraData['@git.zone/cli']) {
|
||||
npmextraData['@git.zone/cli'] = {};
|
||||
if (!smartconfigData['@git.zone/cli']) {
|
||||
smartconfigData['@git.zone/cli'] = {};
|
||||
}
|
||||
npmextraData['@git.zone/cli'].services = services;
|
||||
smartconfigData['@git.zone/cli'].services = services;
|
||||
|
||||
// Write back to npmextra.json
|
||||
// Write back to .smartconfig.json
|
||||
await plugins.smartfs
|
||||
.file(npmextraPath)
|
||||
.file(smartconfigPath)
|
||||
.encoding('utf8')
|
||||
.write(JSON.stringify(npmextraData, null, 2));
|
||||
.write(JSON.stringify(smartconfigData, null, 2));
|
||||
|
||||
logger.log('ok', `✅ Saved service configuration to npmextra.json`);
|
||||
logger.log('ok', `✅ Saved service configuration to .smartconfig.json`);
|
||||
logger.log('info', `🔧 Enabled services: ${services.join(', ')}`);
|
||||
}
|
||||
|
||||
@@ -904,7 +904,7 @@ export class ServiceManager {
|
||||
|
||||
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||
|
||||
// Save to npmextra.json
|
||||
// Save to .smartconfig.json
|
||||
await this.saveServiceConfiguration(this.enabledServices);
|
||||
|
||||
logger.log('ok', '✅ Service configuration updated');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as smartconfig from '@push.rocks/smartconfig';
|
||||
import * as path from 'path';
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
@@ -20,7 +20,7 @@ export const smartfs = new SmartFs(new SmartFsProviderNode());
|
||||
export {
|
||||
smartlog,
|
||||
smartlogDestinationLocal,
|
||||
npmextra,
|
||||
smartconfig,
|
||||
path,
|
||||
projectinfo,
|
||||
smartcli,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
|
||||
Reference in New Issue
Block a user