Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
74a8229e43 | |||
859cbc733d | |||
d32d47b706 | |||
fd90cfe895 | |||
c48f48fc8b | |||
e21e7f0850 | |||
5f561527f9 | |||
9f5f568c3f | |||
39a31a4304 | |||
b629a7d70b | |||
4003944139 | |||
83d374dffd | |||
49a5a66440 | |||
20a53d4d92 | |||
fe02b990b3 |
@@ -27,8 +27,8 @@ auditProductionDependencies:
|
|||||||
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||||
stage: security
|
stage: security
|
||||||
script:
|
script:
|
||||||
- npmci command npm config set registry https://registry.npmjs.org
|
- npmci command npm config set registry https://registry.npmjs.org
|
||||||
- npmci command pnpm audit --audit-level=high --prod
|
- npmci command pnpm audit --audit-level=high --prod
|
||||||
tags:
|
tags:
|
||||||
- private
|
- private
|
||||||
- docker
|
- docker
|
||||||
|
@@ -27,8 +27,8 @@ auditProductionDependencies:
|
|||||||
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||||
stage: security
|
stage: security
|
||||||
script:
|
script:
|
||||||
- npmci command npm config set registry https://registry.npmjs.org
|
- npmci command npm config set registry https://registry.npmjs.org
|
||||||
- npmci command pnpm audit --audit-level=high --prod
|
- npmci command pnpm audit --audit-level=high --prod
|
||||||
tags:
|
tags:
|
||||||
- private
|
- private
|
||||||
- docker
|
- docker
|
||||||
|
@@ -25,8 +25,8 @@ auditProductionDependencies:
|
|||||||
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
image: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||||
stage: security
|
stage: security
|
||||||
script:
|
script:
|
||||||
- npmci command npm config set registry https://registry.npmjs.org
|
- npmci command npm config set registry https://registry.npmjs.org
|
||||||
- npmci command pnpm audit --audit-level=high --prod
|
- npmci command pnpm audit --audit-level=high --prod
|
||||||
tags:
|
tags:
|
||||||
- private
|
- private
|
||||||
- docker
|
- docker
|
||||||
|
@@ -2,4 +2,3 @@ runafter:
|
|||||||
- git add -A && git commit -m initial
|
- git add -A && git commit -m initial
|
||||||
- git push origin master
|
- git push origin master
|
||||||
- gitzone meta update
|
- gitzone meta update
|
||||||
|
|
57
changelog.md
57
changelog.md
@@ -1,5 +1,62 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.8 - fix(format)
|
||||||
|
Improve concurrency control in cache and rollback management with mutex locking and refine formatting details
|
||||||
|
|
||||||
|
- Added 'withMutex' functions in ChangeCache and RollbackManager to synchronize file I/O operations
|
||||||
|
- Introduced static mutex maps to prevent race conditions during manifest updates
|
||||||
|
- Fixed minor formatting issues in commit info and package.json
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.7 - fix(core)
|
||||||
|
Improve formatting, logging, and rollback integrity in core modules
|
||||||
|
|
||||||
|
- Add .claude/settings.local.json with defined permissions for allowed commands
|
||||||
|
- Standardize formatting in package.json, commit info, and configuration files
|
||||||
|
- Refactor rollback manager to use atomic manifest writes and validate manifest structure
|
||||||
|
- Enhance logging messages and overall code clarity in CLI and commit modules
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.6 - fix(changecache)
|
||||||
|
Improve cache manifest validation and atomic file writes; add local settings and overrides
|
||||||
|
|
||||||
|
- Add manifest structure validation and default fallback in getManifest
|
||||||
|
- Implement atomic write in saveManifest using a temporary file and rename strategy
|
||||||
|
- Enhance error handling and cleanup for corrupted manifest files
|
||||||
|
- Introduce new .claude/settings.local.json for project-specific permission configuration
|
||||||
|
- Add an empty assets/overrides.json file for future overrides
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.5 - fix(prettier)
|
||||||
|
Improve file selection in Prettier formatter, remove legacy package overrides, and update CI template indentation
|
||||||
|
|
||||||
|
- Added .claude/settings.local.json with updated permission settings for local commands
|
||||||
|
- Removed unnecessary overrides from assets/overrides.json and cleared packageManager overrides in package.json
|
||||||
|
- Adjusted CI template files (ci_default_gitlab, ci_default_private_gitlab, ci_docker_gitlab) for consistent indentation and formatting
|
||||||
|
- Refined Prettier formatter logic by defining include directories, root config files, and filtering duplicates instead of manual exclusion
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.4 - fix(prettier)
|
||||||
|
Improve file exclusion in the Prettier formatter to skip unnecessary files and directories.
|
||||||
|
|
||||||
|
- Added exclusion patterns for node_modules, .git, dist, .nogit, coverage, .nyc_output, vendor, bower_components, jspm_packages, and minified files.
|
||||||
|
- Optimized filtering logic to ensure only valid files are processed.
|
||||||
|
|
||||||
|
## 2025-08-08 - 1.16.3 - fix(changecache/prettier)
|
||||||
|
Skip directories during file processing to prevent errors in changecache and prettier formatting
|
||||||
|
|
||||||
|
- Removed unnecessary await on synchronous file reads in changecache
|
||||||
|
- Added directory checks in changecache to immediately skip directories
|
||||||
|
- Filtered out directories in prettier formatter to avoid processing non-files
|
||||||
|
|
||||||
|
## 2025-08-07 - 1.16.2 - fix(format)
|
||||||
|
Fix format command confirmation prompt to correctly check user response
|
||||||
|
|
||||||
|
- Fixed bug where format command always showed "cancelled" even when user confirmed
|
||||||
|
- Changed response check from `response.proceed` to `response.value` for SmartInteract compatibility
|
||||||
|
|
||||||
|
## 2025-08-04 - 1.16.1 - fix(package/config)
|
||||||
|
Move smartdiff dependency to runtime and add local bash permissions settings
|
||||||
|
|
||||||
|
- Moved '@push.rocks/smartdiff' from devDependencies to dependencies in package.json
|
||||||
|
- Added .claude/settings.local.json with allowed bash commands (grep, mkdir, find, ls)
|
||||||
|
|
||||||
## 2025-05-19 - 1.16.0 - feat(format)
|
## 2025-05-19 - 1.16.0 - feat(format)
|
||||||
Enhance format module with rollback, diff reporting, and improved parallel execution
|
Enhance format module with rollback, diff reporting, and improved parallel execution
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.16.0",
|
"version": "1.16.8",
|
||||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||||
"main": "dist_ts/index.ts",
|
"main": "dist_ts/index.ts",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -60,7 +60,6 @@
|
|||||||
"@git.zone/tsbuild": "^2.3.2",
|
"@git.zone/tsbuild": "^2.3.2",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^1.3.3",
|
||||||
"@git.zone/tstest": "^1.0.96",
|
"@git.zone/tstest": "^1.0.96",
|
||||||
"@push.rocks/smartdiff": "^1.0.3",
|
|
||||||
"@types/node": "^22.15.18"
|
"@types/node": "^22.15.18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -75,6 +74,7 @@
|
|||||||
"@push.rocks/smartchok": "^1.0.34",
|
"@push.rocks/smartchok": "^1.0.34",
|
||||||
"@push.rocks/smartcli": "^4.0.11",
|
"@push.rocks/smartcli": "^4.0.11",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
|
"@push.rocks/smartdiff": "^1.0.3",
|
||||||
"@push.rocks/smartfile": "^11.2.0",
|
"@push.rocks/smartfile": "^11.2.0",
|
||||||
"@push.rocks/smartgulp": "^3.0.4",
|
"@push.rocks/smartgulp": "^3.0.4",
|
||||||
"@push.rocks/smartinteract": "^2.0.15",
|
"@push.rocks/smartinteract": "^2.0.15",
|
||||||
@@ -113,9 +113,7 @@
|
|||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
],
|
],
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {}
|
||||||
"peek-readable": "5.3.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||||
}
|
}
|
||||||
|
421
readme.md
421
readme.md
@@ -1,155 +1,390 @@
|
|||||||
# @git.zone/cli
|
# @git.zone/cli 🚀
|
||||||
|
|
||||||
A CLI toolbelt to streamline local development cycles by utilizing various gitzone utilities.
|
**The ultimate CLI toolbelt for modern TypeScript development workflows**
|
||||||
|
|
||||||
## Install
|
[](https://www.npmjs.com/package/@git.zone/cli)
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
To begin using `@git.zone/cli`, ensure that Node.js and npm are installed on your system. Once they are set up, install the CLI tool globally with the following command:
|
## 🎯 What is gitzone?
|
||||||
|
|
||||||
```shell
|
gitzone is a powerful command-line interface that supercharges your development workflow with automated project management, intelligent code formatting, and seamless version control. Whether you're bootstrapping a new TypeScript project, maintaining code quality, or managing complex multi-repository setups, gitzone has got you covered.
|
||||||
|
|
||||||
|
## 🏃♂️ Quick Start
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install globally via npm
|
||||||
npm install -g @git.zone/cli
|
npm install -g @git.zone/cli
|
||||||
|
|
||||||
|
# Or with pnpm (recommended)
|
||||||
|
pnpm add -g @git.zone/cli
|
||||||
```
|
```
|
||||||
|
|
||||||
This command makes `gitzone` or `gzone` accessible from any directory on your system.
|
Once installed, you can use either `gitzone` or the shorter `gzone` command from anywhere in your terminal.
|
||||||
|
|
||||||
## Usage
|
### Your First Command
|
||||||
|
|
||||||
The `gitzone` CLI provides a comprehensive set of utilities to optimize and manage the development lifecycle of projects, ranging from project initialization to version control, code formatting, and deployment processes. This usage guide will walk you through the available commands, their use cases, and provide detailed examples.
|
```bash
|
||||||
|
# Create a new TypeScript npm package
|
||||||
|
gitzone template npm
|
||||||
|
|
||||||
### Initializing a New Project
|
# Format your entire codebase
|
||||||
|
gitzone format
|
||||||
|
|
||||||
To bootstrap a new project aligned with modern TypeScript practices, use gitzone's template command. Choose from a range of templates tailored for different project types:
|
# Create a semantic commit
|
||||||
|
|
||||||
```shell
|
|
||||||
gitzone template [templatename]
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace `[templatename]` with any of the following:
|
|
||||||
- `npm`: Starts an npm module with TypeScript, complete with test and CI/CD integration.
|
|
||||||
- `website`: Initializes a LitElement-based frontend application with features such as e2e testing and service worker setup.
|
|
||||||
- `element`: Prepares a new standard setup for creating reusable web components with LitElement.
|
|
||||||
|
|
||||||
**Example: Starting a New npm Project**
|
|
||||||
1. Navigate to your desired directory in the terminal.
|
|
||||||
2. Execute:
|
|
||||||
```shell
|
|
||||||
gitzone template npm
|
|
||||||
```
|
|
||||||
3. Respond to the prompts to customize the project settings. This includes setting up essential metadata such as name and repository information.
|
|
||||||
|
|
||||||
### Committing Changes
|
|
||||||
|
|
||||||
Standardizing commit messages and versionning with gitzone:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
gitzone commit
|
gitzone commit
|
||||||
```
|
```
|
||||||
|
|
||||||
This command provides an interactive prompt to create a conventional commit message, which will be automatically versioned based on the significance of the changes like `fix`, `feat`, or `BREAKING CHANGE`.
|
## 🛠️ Core Features
|
||||||
|
|
||||||
### Handling Package Deprecation
|
### 📦 Project Templates
|
||||||
|
|
||||||
Replace an outdated package with a new one for your team and users:
|
Instantly scaffold production-ready projects with best practices built-in:
|
||||||
|
|
||||||
```shell
|
```bash
|
||||||
gitzone deprecate
|
gitzone template [template-name]
|
||||||
```
|
```
|
||||||
|
|
||||||
The utility collects information about old and new package names through interactive prompts, and proceeds with deprecation notices in npm, guiding users towards the updated package.
|
**Available 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
|
||||||
|
|
||||||
### CI/CD Utilities
|
Each template comes pre-configured with:
|
||||||
|
- ✅ TypeScript with modern configurations
|
||||||
|
- ✅ Automated testing setup
|
||||||
|
- ✅ CI/CD pipelines (GitLab/GitHub)
|
||||||
|
- ✅ Code formatting and linting
|
||||||
|
- ✅ Documentation structure
|
||||||
|
|
||||||
Access your project’s CI/CD settings instantly:
|
### 🎨 Intelligent Code Formatting
|
||||||
|
|
||||||
```shell
|
The most powerful feature of gitzone - automatically format and standardize your entire codebase:
|
||||||
gitzone open ci
|
|
||||||
|
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
This command opens the settings page of the repository’s CI/CD configuration directly from your CLI, thus swiftly transitioning from local tasks to cloud configurations.
|
**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
|
||||||
|
|
||||||
### Maintaining Project Cleanliness
|
**Rollback capabilities:**
|
||||||
|
```bash
|
||||||
|
# List all available backups
|
||||||
|
gitzone format --list-backups
|
||||||
|
|
||||||
For consistent code formatting across your project:
|
# Rollback to the last operation
|
||||||
|
gitzone format --rollback
|
||||||
|
|
||||||
```shell
|
# Rollback to a specific operation
|
||||||
gitzone format
|
gitzone format --rollback [operation-id]
|
||||||
|
|
||||||
|
# Clean old backups
|
||||||
|
gitzone format --clean-backups
|
||||||
```
|
```
|
||||||
|
|
||||||
This command applies uniform formatting styles using Prettier, ensuring your JS/TS and markdown files align with the project's style guide.
|
**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
|
||||||
|
|
||||||
### Lifecycle Management Commands
|
### 🔀 Semantic Commits & Versioning
|
||||||
|
|
||||||
#### Clean Builds
|
Create standardized commits that automatically handle versioning:
|
||||||
|
|
||||||
Remove generated files and prepare for a fresh build:
|
```bash
|
||||||
|
gitzone commit
|
||||||
```shell
|
|
||||||
gitzone clean
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This utility cleans up build artifacts from your project directory so that you can run a fresh build without residual clutter affecting outcomes.
|
Features:
|
||||||
|
- 📝 Interactive commit message builder
|
||||||
|
- 🏷️ Automatic version bumping (major/minor/patch)
|
||||||
|
- 📜 Changelog generation
|
||||||
|
- 🚀 Optional auto-push to origin
|
||||||
|
- 🎯 Conventional commit compliance
|
||||||
|
|
||||||
#### Sync and meta Configuration
|
The commit wizard guides you through:
|
||||||
|
1. **Type selection** (feat/fix/docs/style/refactor/perf/test/chore)
|
||||||
|
2. **Scope definition** (component/module affected)
|
||||||
|
3. **Description crafting**
|
||||||
|
4. **Breaking change detection**
|
||||||
|
5. **Version bump determination**
|
||||||
|
|
||||||
To manage metadata that guides your project configurations and module syncing:
|
### 🏗️ Meta Repository Management
|
||||||
|
|
||||||
```shell
|
Manage multiple related repositories as a cohesive unit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Initialize a meta repository
|
||||||
gitzone meta init
|
gitzone meta init
|
||||||
|
|
||||||
|
# Add a sub-project
|
||||||
|
gitzone meta add [name] [git-url]
|
||||||
|
|
||||||
|
# Update all sub-projects
|
||||||
|
gitzone meta update
|
||||||
|
|
||||||
|
# Remove a sub-project
|
||||||
|
gitzone meta remove [name]
|
||||||
```
|
```
|
||||||
|
|
||||||
This initialization sets the groundwork for supporting gitzone projects by helping you create a `.meta.json` file that tracks subprojects.
|
Perfect for:
|
||||||
|
- Monorepo management
|
||||||
|
- Multi-package projects
|
||||||
|
- Coordinated deployments
|
||||||
|
- Synchronized versioning
|
||||||
|
|
||||||
### Advanced Operations
|
### 🐳 Docker Management
|
||||||
|
|
||||||
#### Cloud and Docker Integrations
|
Streamline your Docker workflow:
|
||||||
|
|
||||||
For projects integrating cloud deployment or containerization:
|
```bash
|
||||||
|
# Clean up all Docker resources
|
||||||
```shell
|
|
||||||
gitzone docker prune
|
gitzone docker prune
|
||||||
```
|
```
|
||||||
|
|
||||||
This utility manages Docker resources by cleaning stale volumes, containers, and images, ensuring efficient resource use.
|
This command removes:
|
||||||
|
- Stopped containers
|
||||||
|
- Unused images
|
||||||
|
- Dangling volumes
|
||||||
|
- Unused networks
|
||||||
|
|
||||||
### Detailed Use Cases
|
### 🔗 Quick CI/CD Access
|
||||||
|
|
||||||
#### **Project Setup and Template Management**
|
Jump directly to your CI/CD configurations:
|
||||||
|
|
||||||
Whether creating an npm module or a web component, the command `gitzone template [templatename]` invokes smart scaffolding processes that automatically set up a robust project environment equipped with modern tooling like TypeScript, Prettier configurations, and testing frameworks.
|
```bash
|
||||||
|
# Open CI/CD settings
|
||||||
|
gitzone open ci
|
||||||
|
|
||||||
#### **Commit Management**
|
# Open pipelines view
|
||||||
|
gitzone open pipelines
|
||||||
The `gitzone commit` command assures that your commit messages complement semantic versioning practices. It proposes version bumps based on detected changes and helps maintain an accurate project change log.
|
|
||||||
|
|
||||||
- **Interactive Commit:** Users engage in dialogues to define commit type (`fix`, `feat`, `BREAKING CHANGE`), scope, and description, resulting in uniform and meaningful commit logs.
|
|
||||||
- **Automatic Versioning:** After message crafting, the versioning is attended by bumps similar to npm’s `npm version` with stages such as `major`, `minor`, and `patch`.
|
|
||||||
|
|
||||||
#### **Formatting and Configurations**
|
|
||||||
|
|
||||||
A consistent format yields a professional and readable codebase:
|
|
||||||
```shell
|
|
||||||
gitzone format
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Prettier Integration:** Formats not only TypeScript and JavaScript files but also documents such as `readme.md`, maintaining consistency in both code and documentation style.
|
Works with GitLab repositories to provide instant access to your deployment configurations.
|
||||||
|
|
||||||
#### **Synced Meta Projects**
|
### 📝 Package Deprecation
|
||||||
|
|
||||||
Effectively supervise interconnected repositories or module collections:
|
Smoothly transition users from old to new packages:
|
||||||
- **Meta Initialization (`meta init`)**: Lay down a foundation for a meta repository that orchestrates multiple projects by overseeing them through a centralized metadata file.
|
|
||||||
- **Subproject Operations:** Commands such as `meta update`, `meta add`, `meta remove` enable dynamic management of subprojects, ensuring they remain updated with current features and aligned to the meta repository's standards.
|
|
||||||
|
|
||||||
#### **CI/CD Integration**
|
```bash
|
||||||
|
gitzone deprecate
|
||||||
|
```
|
||||||
|
|
||||||
Directly linking your local development processes to continuous integration settings with `gitzone open ci` provides seamless switches between code and configuration in cloud-based environments.
|
Interactive wizard for:
|
||||||
|
- Setting deprecation notices
|
||||||
|
- Guiding users to replacements
|
||||||
|
- Updating registry metadata
|
||||||
|
- Coordinating migration paths
|
||||||
|
|
||||||
#### **Real-time Docker Management**
|
### 🚦 Project Initialization
|
||||||
|
|
||||||
For developers employing Docker for service deployments, `gitzone docker prune` offers a quick remedy to residual services, optimizing local/docker-storage for a more streamlined experience in container management.
|
Prepare existing projects for development:
|
||||||
|
|
||||||
### Leveraging gitzone
|
```bash
|
||||||
|
gitzone start
|
||||||
|
```
|
||||||
|
|
||||||
By mastering the `gitzone` CLI commands, you gain unparalleled efficiency in managing your projects from conception to deployment. Encompassing a wide gamut of functionalities, from initial project scaffolding to synchronization of modularized repositories and beyond, `gitzone` serves as a linchpin for developers intent on optimizing their workflow. Understanding and utilizing its full potential catalyzes development cycles, granting the freedom to focus on innovation, collaboration, and delivering outstanding software products.
|
Automatically:
|
||||||
|
- Checks out master branch
|
||||||
|
- Pulls latest changes
|
||||||
|
- Installs dependencies
|
||||||
|
- Sets up development environment
|
||||||
|
|
||||||
|
### 🔧 Helper Utilities
|
||||||
|
|
||||||
|
Quick utilities for common tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a unique short ID
|
||||||
|
gitzone helpers shortid
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Configuration
|
||||||
|
|
||||||
|
### npmextra.json Configuration
|
||||||
|
|
||||||
|
Customize gitzone behavior through `npmextra.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"gitzone": {
|
||||||
|
"format": {
|
||||||
|
"interactive": true,
|
||||||
|
"showDiffs": false,
|
||||||
|
"autoApprove": false,
|
||||||
|
"parallel": true,
|
||||||
|
"rollback": {
|
||||||
|
"enabled": true,
|
||||||
|
"autoRollbackOnError": true,
|
||||||
|
"backupRetentionDays": 7
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"skip": ["prettier"],
|
||||||
|
"only": [],
|
||||||
|
"order": []
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"enabled": true,
|
||||||
|
"clean": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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. Customize the generated structure
|
||||||
|
3. Run initial format: `gitzone format`
|
||||||
|
4. Set up CI/CD: `gitzone open ci`
|
||||||
|
|
||||||
|
### For Existing Projects
|
||||||
|
1. Initialize: `gitzone start`
|
||||||
|
2. Format codebase: `gitzone format --dry-run` (preview first!)
|
||||||
|
3. Apply formatting: `gitzone format --yes`
|
||||||
|
4. Commit changes: `gitzone commit`
|
||||||
|
|
||||||
|
### For Teams
|
||||||
|
1. Document format preferences in `npmextra.json`
|
||||||
|
2. Use `--save-plan` for reviewable format changes
|
||||||
|
3. Enable rollback for safety
|
||||||
|
4. Standardize commit conventions
|
||||||
|
|
||||||
|
## 🎯 Common Workflows
|
||||||
|
|
||||||
|
### Clean Development Cycle
|
||||||
|
```bash
|
||||||
|
# 1. Start fresh
|
||||||
|
gitzone start
|
||||||
|
|
||||||
|
# 2. Make changes
|
||||||
|
# ... your development work ...
|
||||||
|
|
||||||
|
# 3. Format code
|
||||||
|
gitzone format
|
||||||
|
|
||||||
|
# 4. Commit with semantic versioning
|
||||||
|
gitzone commit
|
||||||
|
|
||||||
|
# 5. Deploy (if CI/CD configured)
|
||||||
|
# Automatic via git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Repository Management
|
||||||
|
```bash
|
||||||
|
# 1. Set up meta repository
|
||||||
|
gitzone meta init
|
||||||
|
|
||||||
|
# 2. Add all related projects
|
||||||
|
gitzone meta add frontend https://github.com/org/frontend.git
|
||||||
|
gitzone meta add backend https://github.com/org/backend.git
|
||||||
|
gitzone meta add shared https://github.com/org/shared.git
|
||||||
|
|
||||||
|
# 3. Synchronize updates
|
||||||
|
gitzone meta update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Safe Formatting with Rollback
|
||||||
|
```bash
|
||||||
|
# 1. Preview changes
|
||||||
|
gitzone format --dry-run
|
||||||
|
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔌 Integrations
|
||||||
|
|
||||||
|
### CI/CD Platforms
|
||||||
|
- **GitLab CI** - Full pipeline support with templates
|
||||||
|
- **GitHub Actions** - Automated workflows
|
||||||
|
- **Docker** - Container-based deployments
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- **TypeScript** - First-class support
|
||||||
|
- **Prettier** - Code formatting
|
||||||
|
- **ESLint** - Linting (via format modules)
|
||||||
|
- **npm/pnpm** - Package management
|
||||||
|
|
||||||
|
### Version Control
|
||||||
|
- **Git** - Deep integration
|
||||||
|
- **Semantic Versioning** - Automatic version bumping
|
||||||
|
- **Conventional Commits** - Standardized commit messages
|
||||||
|
|
||||||
|
## 💡 Pro Tips
|
||||||
|
|
||||||
|
1. **Use aliases**: Add `alias gz='gitzone'` to your shell profile
|
||||||
|
2. **Combine commands**: `gitzone format --yes && gitzone commit`
|
||||||
|
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
|
||||||
|
|
||||||
|
## 🐛 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
|
||||||
|
- Use `--verbose` for detailed output
|
||||||
|
|
||||||
|
### Docker Commands Fail
|
||||||
|
Ensure Docker daemon is running:
|
||||||
|
```bash
|
||||||
|
docker info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template Creation Issues
|
||||||
|
Verify npm/pnpm is properly configured:
|
||||||
|
```bash
|
||||||
|
npm config get registry
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 Performance
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
@@ -168,4 +403,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '1.16.0',
|
version: '1.16.8',
|
||||||
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,9 @@ export class GitzoneConfig {
|
|||||||
public async readConfigFromCwd() {
|
public async readConfigFromCwd() {
|
||||||
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
|
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
|
||||||
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
|
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
|
||||||
this.data.npmciOptions = npmextraInstance.dataFor<IGitzoneConfigData['npmciOptions']>('npmci', {
|
this.data.npmciOptions = npmextraInstance.dataFor<
|
||||||
|
IGitzoneConfigData['npmciOptions']
|
||||||
|
>('npmci', {
|
||||||
npmAccessLevel: 'public',
|
npmAccessLevel: 'public',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -62,23 +62,23 @@ export let run = async () => {
|
|||||||
gitzoneSmartcli.addCommand('format').subscribe(async (argvArg) => {
|
gitzoneSmartcli.addCommand('format').subscribe(async (argvArg) => {
|
||||||
const config = GitzoneConfig.fromCwd();
|
const config = GitzoneConfig.fromCwd();
|
||||||
const modFormat = await import('./mod_format/index.js');
|
const modFormat = await import('./mod_format/index.js');
|
||||||
|
|
||||||
// Handle rollback commands
|
// Handle rollback commands
|
||||||
if (argvArg.rollback) {
|
if (argvArg.rollback) {
|
||||||
await modFormat.handleRollback(argvArg.rollback);
|
await modFormat.handleRollback(argvArg.rollback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argvArg['list-backups']) {
|
if (argvArg['list-backups']) {
|
||||||
await modFormat.handleListBackups();
|
await modFormat.handleListBackups();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argvArg['clean-backups']) {
|
if (argvArg['clean-backups']) {
|
||||||
await modFormat.handleCleanBackups();
|
await modFormat.handleCleanBackups();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle format with options
|
// Handle format with options
|
||||||
await modFormat.run({
|
await modFormat.run({
|
||||||
dryRun: argvArg['dry-run'],
|
dryRun: argvArg['dry-run'],
|
||||||
@@ -89,7 +89,7 @@ export let run = async () => {
|
|||||||
detailed: argvArg.detailed,
|
detailed: argvArg.detailed,
|
||||||
interactive: argvArg.interactive !== false,
|
interactive: argvArg.interactive !== false,
|
||||||
parallel: argvArg.parallel !== false,
|
parallel: argvArg.parallel !== false,
|
||||||
verbose: argvArg.verbose
|
verbose: argvArg.verbose,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,7 +5,8 @@ import * as plugins from './plugins.js';
|
|||||||
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
|
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
|
||||||
|
|
||||||
// Add console destination
|
// Add console destination
|
||||||
const consoleDestination = new plugins.smartlogDestinationLocal.DestinationLocal();
|
const consoleDestination =
|
||||||
|
new plugins.smartlogDestinationLocal.DestinationLocal();
|
||||||
logger.addLogDestination(consoleDestination);
|
logger.addLogDestination(consoleDestination);
|
||||||
|
|
||||||
// Verbose logging helper
|
// Verbose logging helper
|
||||||
|
@@ -10,20 +10,22 @@ export const run = async (argvArg: any) => {
|
|||||||
await formatMod.run();
|
await formatMod.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
logger.log('info', `gathering facts...`);
|
logger.log('info', `gathering facts...`);
|
||||||
const aidoc = new plugins.tsdoc.AiDoc();
|
const aidoc = new plugins.tsdoc.AiDoc();
|
||||||
await aidoc.start();
|
await aidoc.start();
|
||||||
|
|
||||||
const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
|
const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
|
||||||
|
|
||||||
logger.log('info', `---------
|
logger.log(
|
||||||
|
'info',
|
||||||
|
`---------
|
||||||
Next recommended commit would be:
|
Next recommended commit would be:
|
||||||
===========
|
===========
|
||||||
-> ${nextCommitObject.recommendedNextVersion}:
|
-> ${nextCommitObject.recommendedNextVersion}:
|
||||||
-> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
|
-> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
|
||||||
===========
|
===========
|
||||||
`);
|
`,
|
||||||
|
);
|
||||||
const commitInteract = new plugins.smartinteract.SmartInteract();
|
const commitInteract = new plugins.smartinteract.SmartInteract();
|
||||||
commitInteract.addQuestions([
|
commitInteract.addQuestions([
|
||||||
{
|
{
|
||||||
@@ -72,32 +74,55 @@ export const run = async (argvArg: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
logger.log('info', `Baking commitinfo into code ...`);
|
logger.log('info', `Baking commitinfo into code ...`);
|
||||||
const commitInfo = new plugins.commitinfo.CommitInfo(paths.cwd, commitVersionType);
|
const commitInfo = new plugins.commitinfo.CommitInfo(
|
||||||
|
paths.cwd,
|
||||||
|
commitVersionType,
|
||||||
|
);
|
||||||
await commitInfo.writeIntoPotentialDirs();
|
await commitInfo.writeIntoPotentialDirs();
|
||||||
|
|
||||||
logger.log('info', `Writing changelog.md ...`);
|
logger.log('info', `Writing changelog.md ...`);
|
||||||
let changelog = nextCommitObject.changelog;
|
let changelog = nextCommitObject.changelog;
|
||||||
changelog = changelog.replaceAll('{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString);
|
changelog = changelog.replaceAll(
|
||||||
changelog = changelog.replaceAll('{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`);
|
'{{nextVersion}}',
|
||||||
changelog = changelog.replaceAll('{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage);
|
(await commitInfo.getNextPlannedVersion()).versionString,
|
||||||
|
);
|
||||||
|
changelog = changelog.replaceAll(
|
||||||
|
'{{nextVersionScope}}',
|
||||||
|
`${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`,
|
||||||
|
);
|
||||||
|
changelog = changelog.replaceAll(
|
||||||
|
'{{nextVersionMessage}}',
|
||||||
|
nextCommitObject.recommendedNextVersionMessage,
|
||||||
|
);
|
||||||
if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
|
if (nextCommitObject.recommendedNextVersionDetails?.length > 0) {
|
||||||
changelog = changelog.replaceAll('{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '));
|
changelog = changelog.replaceAll(
|
||||||
|
'{{nextVersionDetails}}',
|
||||||
|
'- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
|
changelog = changelog.replaceAll('\n{{nextVersionDetails}}', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(changelog, plugins.path.join(paths.cwd, `changelog.md`));
|
await plugins.smartfile.memory.toFs(
|
||||||
|
changelog,
|
||||||
|
plugins.path.join(paths.cwd, `changelog.md`),
|
||||||
|
);
|
||||||
|
|
||||||
logger.log('info', `Staging files for commit:`);
|
logger.log('info', `Staging files for commit:`);
|
||||||
await smartshellInstance.exec(`git add -A`);
|
await smartshellInstance.exec(`git add -A`);
|
||||||
await smartshellInstance.exec(`git commit -m "${commitString}"`);
|
await smartshellInstance.exec(`git commit -m "${commitString}"`);
|
||||||
await smartshellInstance.exec(`npm version ${commitVersionType}`);
|
await smartshellInstance.exec(`npm version ${commitVersionType}`);
|
||||||
if (answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true')) {
|
if (
|
||||||
|
answerBucket.getAnswerFor('pushToOrigin') &&
|
||||||
|
!(process.env.CI === 'true')
|
||||||
|
) {
|
||||||
await smartshellInstance.exec(`git push origin master --follow-tags`);
|
await smartshellInstance.exec(`git push origin master --follow-tags`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCommitStringFromAnswerBucket = (answerBucket: plugins.smartinteract.AnswerBucket) => {
|
const createCommitStringFromAnswerBucket = (
|
||||||
|
answerBucket: plugins.smartinteract.AnswerBucket,
|
||||||
|
) => {
|
||||||
const commitType = answerBucket.getAnswerFor('commitType');
|
const commitType = answerBucket.getAnswerFor('commitType');
|
||||||
const commitScope = answerBucket.getAnswerFor('commitScope');
|
const commitScope = answerBucket.getAnswerFor('commitScope');
|
||||||
const commitDescription = answerBucket.getAnswerFor('commitDescription');
|
const commitDescription = answerBucket.getAnswerFor('commitDescription');
|
||||||
|
@@ -36,7 +36,10 @@ export const run = async () => {
|
|||||||
const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
|
const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
|
||||||
const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
|
const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
|
||||||
const newPackageName = answerBucket.getAnswerFor(`newPackageName`);
|
const newPackageName = answerBucket.getAnswerFor(`newPackageName`);
|
||||||
logger.log('info', `Deprecating package ${oldPackageName} in favour of ${newPackageName}`);
|
logger.log(
|
||||||
|
'info',
|
||||||
|
`Deprecating package ${oldPackageName} in favour of ${newPackageName}`,
|
||||||
|
);
|
||||||
const smartshellInstance = new plugins.smartshell.Smartshell({
|
const smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
executor: 'bash',
|
executor: 'bash',
|
||||||
});
|
});
|
||||||
|
@@ -2,32 +2,29 @@ import * as plugins from './mod.plugins.js';
|
|||||||
import { FormatContext } from './classes.formatcontext.js';
|
import { FormatContext } from './classes.formatcontext.js';
|
||||||
import type { IPlannedChange } from './interfaces.format.js';
|
import type { IPlannedChange } from './interfaces.format.js';
|
||||||
import { Project } from '../classes.project.js';
|
import { Project } from '../classes.project.js';
|
||||||
import { ChangeCache } from './classes.changecache.js';
|
|
||||||
|
|
||||||
export abstract class BaseFormatter {
|
export abstract class BaseFormatter {
|
||||||
protected context: FormatContext;
|
protected context: FormatContext;
|
||||||
protected project: Project;
|
protected project: Project;
|
||||||
protected cache: ChangeCache;
|
|
||||||
protected stats: any; // Will be FormatStats from context
|
protected stats: any; // Will be FormatStats from context
|
||||||
|
|
||||||
constructor(context: FormatContext, project: Project) {
|
constructor(context: FormatContext, project: Project) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.project = project;
|
this.project = project;
|
||||||
this.cache = context.getChangeCache();
|
|
||||||
this.stats = context.getFormatStats();
|
this.stats = context.getFormatStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract get name(): string;
|
abstract get name(): string;
|
||||||
abstract analyze(): Promise<IPlannedChange[]>;
|
abstract analyze(): Promise<IPlannedChange[]>;
|
||||||
abstract applyChange(change: IPlannedChange): Promise<void>;
|
abstract applyChange(change: IPlannedChange): Promise<void>;
|
||||||
|
|
||||||
async execute(changes: IPlannedChange[]): Promise<void> {
|
async execute(changes: IPlannedChange[]): Promise<void> {
|
||||||
const startTime = this.stats.moduleStartTime(this.name);
|
const startTime = this.stats.moduleStartTime(this.name);
|
||||||
this.stats.startModule(this.name);
|
this.stats.startModule(this.name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.preExecute();
|
await this.preExecute();
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
try {
|
try {
|
||||||
await this.applyChange(change);
|
await this.applyChange(change);
|
||||||
@@ -37,57 +34,37 @@ export abstract class BaseFormatter {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.postExecute();
|
await this.postExecute();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.context.rollbackOperation();
|
// Don't rollback here - let the FormatPlanner handle it
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
this.stats.endModule(this.name, startTime);
|
this.stats.endModule(this.name, startTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async preExecute(): Promise<void> {
|
protected async preExecute(): Promise<void> {
|
||||||
// Override in subclasses if needed
|
// Override in subclasses if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async postExecute(): Promise<void> {
|
protected async postExecute(): Promise<void> {
|
||||||
// Override in subclasses if needed
|
// Override in subclasses if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async modifyFile(filepath: string, content: string): Promise<void> {
|
protected async modifyFile(filepath: string, content: string): Promise<void> {
|
||||||
await this.context.trackFileChange(filepath);
|
|
||||||
await plugins.smartfile.memory.toFs(content, filepath);
|
await plugins.smartfile.memory.toFs(content, filepath);
|
||||||
await this.cache.updateFileCache(filepath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async createFile(filepath: string, content: string): Promise<void> {
|
protected async createFile(filepath: string, content: string): Promise<void> {
|
||||||
await plugins.smartfile.memory.toFs(content, filepath);
|
await plugins.smartfile.memory.toFs(content, filepath);
|
||||||
await this.cache.updateFileCache(filepath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async deleteFile(filepath: string): Promise<void> {
|
protected async deleteFile(filepath: string): Promise<void> {
|
||||||
await this.context.trackFileChange(filepath);
|
|
||||||
await plugins.smartfile.fs.remove(filepath);
|
await plugins.smartfile.fs.remove(filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
protected async shouldProcessFile(filepath: string): Promise<boolean> {
|
||||||
const config = new plugins.npmextra.Npmextra();
|
return true;
|
||||||
const useCache = config.dataFor('gitzone.format.cache.enabled', true);
|
|
||||||
|
|
||||||
if (!useCache) {
|
|
||||||
return true; // Process all files if cache is disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasChanged = await this.cache.hasFileChanged(filepath);
|
|
||||||
|
|
||||||
// Record cache statistics
|
|
||||||
if (hasChanged) {
|
|
||||||
this.stats.recordCacheMiss();
|
|
||||||
} else {
|
|
||||||
this.stats.recordCacheHit();
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,127 +18,206 @@ export class ChangeCache {
|
|||||||
private cacheDir: string;
|
private cacheDir: string;
|
||||||
private manifestPath: string;
|
private manifestPath: string;
|
||||||
private cacheVersion = '1.0.0';
|
private cacheVersion = '1.0.0';
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cacheDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-cache');
|
this.cacheDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-cache');
|
||||||
this.manifestPath = plugins.path.join(this.cacheDir, 'manifest.json');
|
this.manifestPath = plugins.path.join(this.cacheDir, 'manifest.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getManifest(): Promise<ICacheManifest> {
|
async getManifest(): Promise<ICacheManifest> {
|
||||||
|
const defaultManifest: ICacheManifest = {
|
||||||
|
version: this.cacheVersion,
|
||||||
|
lastFormat: 0,
|
||||||
|
files: [],
|
||||||
|
};
|
||||||
|
|
||||||
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return {
|
return defaultManifest;
|
||||||
version: this.cacheVersion,
|
}
|
||||||
lastFormat: 0,
|
|
||||||
files: []
|
try {
|
||||||
};
|
const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
|
||||||
|
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.smartfile.fs.remove(this.manifestPath);
|
||||||
|
} catch (removeError) {
|
||||||
|
// Ignore removal errors
|
||||||
|
}
|
||||||
|
return defaultManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
|
|
||||||
return JSON.parse(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveManifest(manifest: ICacheManifest): Promise<void> {
|
async saveManifest(manifest: ICacheManifest): Promise<void> {
|
||||||
await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
|
// Validate before saving
|
||||||
|
if (!this.isValidManifest(manifest)) {
|
||||||
|
throw new Error('Invalid manifest structure, cannot save');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
||||||
|
|
||||||
|
// Write directly with proper JSON stringification
|
||||||
|
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||||
|
await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasFileChanged(filePath: string): Promise<boolean> {
|
async hasFileChanged(filePath: string): Promise<boolean> {
|
||||||
const absolutePath = plugins.path.isAbsolute(filePath)
|
const absolutePath = plugins.path.isAbsolute(filePath)
|
||||||
? filePath
|
? filePath
|
||||||
: plugins.path.join(paths.cwd, filePath);
|
: plugins.path.join(paths.cwd, filePath);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return true; // File doesn't exist, so it's "changed" (will be created)
|
return true; // File doesn't exist, so it's "changed" (will be created)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current file stats
|
// Get current file stats
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
||||||
const content = await plugins.smartfile.fs.toStringSync(absolutePath);
|
|
||||||
|
// Skip directories
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
return false; // Directories are not processed
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
||||||
const currentChecksum = this.calculateChecksum(content);
|
const currentChecksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Get cached info
|
// Get cached info
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
const cachedFile = manifest.files.find(f => f.path === filePath);
|
const cachedFile = manifest.files.find((f) => f.path === filePath);
|
||||||
|
|
||||||
if (!cachedFile) {
|
if (!cachedFile) {
|
||||||
return true; // Not in cache, so it's changed
|
return true; // Not in cache, so it's changed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare checksums
|
// Compare checksums
|
||||||
return cachedFile.checksum !== currentChecksum ||
|
return (
|
||||||
cachedFile.size !== stats.size ||
|
cachedFile.checksum !== currentChecksum ||
|
||||||
cachedFile.modified !== stats.mtimeMs;
|
cachedFile.size !== stats.size ||
|
||||||
|
cachedFile.modified !== stats.mtimeMs
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFileCache(filePath: string): Promise<void> {
|
async updateFileCache(filePath: string): Promise<void> {
|
||||||
const absolutePath = plugins.path.isAbsolute(filePath)
|
const absolutePath = plugins.path.isAbsolute(filePath)
|
||||||
? filePath
|
? filePath
|
||||||
: plugins.path.join(paths.cwd, filePath);
|
: plugins.path.join(paths.cwd, filePath);
|
||||||
|
|
||||||
// Get current file stats
|
// Get current file stats
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
||||||
const content = await plugins.smartfile.fs.toStringSync(absolutePath);
|
|
||||||
|
// Skip directories
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
return; // Don't cache directories
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Update manifest
|
// Update manifest
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
const existingIndex = manifest.files.findIndex(f => f.path === filePath);
|
const existingIndex = manifest.files.findIndex((f) => f.path === filePath);
|
||||||
|
|
||||||
const cacheEntry: IFileCache = {
|
const cacheEntry: IFileCache = {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
checksum,
|
checksum,
|
||||||
modified: stats.mtimeMs,
|
modified: stats.mtimeMs,
|
||||||
size: stats.size
|
size: stats.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
manifest.files[existingIndex] = cacheEntry;
|
manifest.files[existingIndex] = cacheEntry;
|
||||||
} else {
|
} else {
|
||||||
manifest.files.push(cacheEntry);
|
manifest.files.push(cacheEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest.lastFormat = Date.now();
|
manifest.lastFormat = Date.now();
|
||||||
await this.saveManifest(manifest);
|
await this.saveManifest(manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChangedFiles(filePaths: string[]): Promise<string[]> {
|
async getChangedFiles(filePaths: string[]): Promise<string[]> {
|
||||||
const changedFiles: string[] = [];
|
const changedFiles: string[] = [];
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
for (const filePath of filePaths) {
|
||||||
if (await this.hasFileChanged(filePath)) {
|
if (await this.hasFileChanged(filePath)) {
|
||||||
changedFiles.push(filePath);
|
changedFiles.push(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changedFiles;
|
return changedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
async clean(): Promise<void> {
|
async clean(): Promise<void> {
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
const validFiles: IFileCache[] = [];
|
const validFiles: IFileCache[] = [];
|
||||||
|
|
||||||
// Remove entries for files that no longer exist
|
// Remove entries for files that no longer exist
|
||||||
for (const file of manifest.files) {
|
for (const file of manifest.files) {
|
||||||
const absolutePath = plugins.path.isAbsolute(file.path)
|
const absolutePath = plugins.path.isAbsolute(file.path)
|
||||||
? file.path
|
? file.path
|
||||||
: plugins.path.join(paths.cwd, file.path);
|
: plugins.path.join(paths.cwd, file.path);
|
||||||
|
|
||||||
if (await plugins.smartfile.fs.fileExists(absolutePath)) {
|
if (await plugins.smartfile.fs.fileExists(absolutePath)) {
|
||||||
validFiles.push(file);
|
validFiles.push(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest.files = validFiles;
|
manifest.files = validFiles;
|
||||||
await this.saveManifest(manifest);
|
await this.saveManifest(manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateChecksum(content: string | Buffer): string {
|
private calculateChecksum(content: string | Buffer): string {
|
||||||
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -9,35 +9,42 @@ export interface IModuleDependency {
|
|||||||
|
|
||||||
export class DependencyAnalyzer {
|
export class DependencyAnalyzer {
|
||||||
private moduleDependencies: Map<string, IModuleDependency> = new Map();
|
private moduleDependencies: Map<string, IModuleDependency> = new Map();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initializeDependencies();
|
this.initializeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeDependencies(): void {
|
private initializeDependencies(): void {
|
||||||
// Define dependencies between format modules
|
// Define dependencies between format modules
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
'cleanup': [], // No dependencies
|
cleanup: [], // No dependencies
|
||||||
'npmextra': [], // No dependencies
|
npmextra: [], // No dependencies
|
||||||
'license': ['npmextra'], // Depends on npmextra for config
|
license: ['npmextra'], // Depends on npmextra for config
|
||||||
'packagejson': ['npmextra'], // Depends on npmextra for config
|
packagejson: ['npmextra'], // Depends on npmextra for config
|
||||||
'templates': ['npmextra', 'packagejson'], // Depends on both
|
templates: ['npmextra', 'packagejson'], // Depends on both
|
||||||
'gitignore': ['templates'], // Depends on templates
|
gitignore: ['templates'], // Depends on templates
|
||||||
'tsconfig': ['packagejson'], // Depends on package.json
|
tsconfig: ['packagejson'], // Depends on package.json
|
||||||
'prettier': ['cleanup', 'npmextra', 'packagejson', 'templates', 'gitignore', 'tsconfig'], // Runs after most others
|
prettier: [
|
||||||
'readme': ['npmextra', 'packagejson'], // Depends on project metadata
|
'cleanup',
|
||||||
'copy': ['npmextra'], // Depends on config
|
'npmextra',
|
||||||
|
'packagejson',
|
||||||
|
'templates',
|
||||||
|
'gitignore',
|
||||||
|
'tsconfig',
|
||||||
|
], // Runs after most others
|
||||||
|
readme: ['npmextra', 'packagejson'], // Depends on project metadata
|
||||||
|
copy: ['npmextra'], // Depends on config
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize all modules
|
// Initialize all modules
|
||||||
for (const [module, deps] of Object.entries(dependencies)) {
|
for (const [module, deps] of Object.entries(dependencies)) {
|
||||||
this.moduleDependencies.set(module, {
|
this.moduleDependencies.set(module, {
|
||||||
module,
|
module,
|
||||||
dependencies: new Set(deps),
|
dependencies: new Set(deps),
|
||||||
dependents: new Set()
|
dependents: new Set(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build reverse dependencies (dependents)
|
// Build reverse dependencies (dependents)
|
||||||
for (const [module, deps] of Object.entries(dependencies)) {
|
for (const [module, deps] of Object.entries(dependencies)) {
|
||||||
for (const dep of deps) {
|
for (const dep of deps) {
|
||||||
@@ -48,34 +55,35 @@ export class DependencyAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
|
getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
|
||||||
const modulesMap = new Map(modules.map(m => [m.name, m]));
|
const modulesMap = new Map(modules.map((m) => [m.name, m]));
|
||||||
const executed = new Set<string>();
|
const executed = new Set<string>();
|
||||||
const groups: BaseFormatter[][] = [];
|
const groups: BaseFormatter[][] = [];
|
||||||
|
|
||||||
while (executed.size < modules.length) {
|
while (executed.size < modules.length) {
|
||||||
const currentGroup: BaseFormatter[] = [];
|
const currentGroup: BaseFormatter[] = [];
|
||||||
|
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
if (executed.has(module.name)) continue;
|
if (executed.has(module.name)) continue;
|
||||||
|
|
||||||
const dependency = this.moduleDependencies.get(module.name);
|
const dependency = this.moduleDependencies.get(module.name);
|
||||||
if (!dependency) {
|
if (!dependency) {
|
||||||
// Unknown module, execute in isolation
|
// Unknown module, execute in isolation
|
||||||
currentGroup.push(module);
|
currentGroup.push(module);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all dependencies have been executed
|
// Check if all dependencies have been executed
|
||||||
const allDepsExecuted = Array.from(dependency.dependencies)
|
const allDepsExecuted = Array.from(dependency.dependencies).every(
|
||||||
.every(dep => executed.has(dep) || !modulesMap.has(dep));
|
(dep) => executed.has(dep) || !modulesMap.has(dep),
|
||||||
|
);
|
||||||
|
|
||||||
if (allDepsExecuted) {
|
if (allDepsExecuted) {
|
||||||
currentGroup.push(module);
|
currentGroup.push(module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentGroup.length === 0) {
|
if (currentGroup.length === 0) {
|
||||||
// Circular dependency or error - execute remaining modules
|
// Circular dependency or error - execute remaining modules
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
@@ -84,24 +92,26 @@ export class DependencyAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentGroup.forEach(m => executed.add(m.name));
|
currentGroup.forEach((m) => executed.add(m.name));
|
||||||
groups.push(currentGroup);
|
groups.push(currentGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
canRunInParallel(module1: string, module2: string): boolean {
|
canRunInParallel(module1: string, module2: string): boolean {
|
||||||
const dep1 = this.moduleDependencies.get(module1);
|
const dep1 = this.moduleDependencies.get(module1);
|
||||||
const dep2 = this.moduleDependencies.get(module2);
|
const dep2 = this.moduleDependencies.get(module2);
|
||||||
|
|
||||||
if (!dep1 || !dep2) return false;
|
if (!dep1 || !dep2) return false;
|
||||||
|
|
||||||
// Check if module1 depends on module2 or vice versa
|
// Check if module1 depends on module2 or vice versa
|
||||||
return !dep1.dependencies.has(module2) &&
|
return (
|
||||||
!dep2.dependencies.has(module1) &&
|
!dep1.dependencies.has(module2) &&
|
||||||
!dep1.dependents.has(module2) &&
|
!dep2.dependencies.has(module1) &&
|
||||||
!dep2.dependents.has(module1);
|
!dep1.dependents.has(module2) &&
|
||||||
|
!dep2.dependents.has(module1)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,72 +4,85 @@ import { logger } from '../gitzone.logging.js';
|
|||||||
|
|
||||||
export class DiffReporter {
|
export class DiffReporter {
|
||||||
private diffs: Map<string, string> = new Map();
|
private diffs: Map<string, string> = new Map();
|
||||||
|
|
||||||
async generateDiff(filePath: string, oldContent: string, newContent: string): Promise<string> {
|
async generateDiff(
|
||||||
|
filePath: string,
|
||||||
|
oldContent: string,
|
||||||
|
newContent: string,
|
||||||
|
): Promise<string> {
|
||||||
const diff = plugins.smartdiff.createDiff(oldContent, newContent);
|
const diff = plugins.smartdiff.createDiff(oldContent, newContent);
|
||||||
this.diffs.set(filePath, diff);
|
this.diffs.set(filePath, diff);
|
||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateDiffForChange(change: IPlannedChange): Promise<string | null> {
|
async generateDiffForChange(change: IPlannedChange): Promise<string | null> {
|
||||||
if (change.type !== 'modify') {
|
if (change.type !== 'modify') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const exists = await plugins.smartfile.fs.fileExists(change.path);
|
const exists = await plugins.smartfile.fs.fileExists(change.path);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentContent = await plugins.smartfile.fs.toStringSync(change.path);
|
const currentContent = await plugins.smartfile.fs.toStringSync(
|
||||||
|
change.path,
|
||||||
|
);
|
||||||
|
|
||||||
// For planned changes, we need the new content
|
// For planned changes, we need the new content
|
||||||
if (!change.content) {
|
if (!change.content) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.generateDiff(change.path, currentContent, change.content);
|
return await this.generateDiff(
|
||||||
|
change.path,
|
||||||
|
currentContent,
|
||||||
|
change.content,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('error', `Failed to generate diff for ${change.path}: ${error.message}`);
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Failed to generate diff for ${change.path}: ${error.message}`,
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayDiff(filePath: string, diff?: string): void {
|
displayDiff(filePath: string, diff?: string): void {
|
||||||
const diffToShow = diff || this.diffs.get(filePath);
|
const diffToShow = diff || this.diffs.get(filePath);
|
||||||
|
|
||||||
if (!diffToShow) {
|
if (!diffToShow) {
|
||||||
logger.log('warn', `No diff available for ${filePath}`);
|
logger.log('warn', `No diff available for ${filePath}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n${this.formatDiffHeader(filePath)}`);
|
console.log(`\n${this.formatDiffHeader(filePath)}`);
|
||||||
console.log(this.colorDiff(diffToShow));
|
console.log(this.colorDiff(diffToShow));
|
||||||
console.log('━'.repeat(50));
|
console.log('━'.repeat(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAllDiffs(): void {
|
displayAllDiffs(): void {
|
||||||
if (this.diffs.size === 0) {
|
if (this.diffs.size === 0) {
|
||||||
logger.log('info', 'No diffs to display');
|
logger.log('info', 'No diffs to display');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\nFile Changes:');
|
console.log('\nFile Changes:');
|
||||||
console.log('═'.repeat(50));
|
console.log('═'.repeat(50));
|
||||||
|
|
||||||
for (const [filePath, diff] of this.diffs) {
|
for (const [filePath, diff] of this.diffs) {
|
||||||
this.displayDiff(filePath, diff);
|
this.displayDiff(filePath, diff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatDiffHeader(filePath: string): string {
|
private formatDiffHeader(filePath: string): string {
|
||||||
return `📄 ${filePath}`;
|
return `📄 ${filePath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private colorDiff(diff: string): string {
|
private colorDiff(diff: string): string {
|
||||||
const lines = diff.split('\n');
|
const lines = diff.split('\n');
|
||||||
const coloredLines = lines.map(line => {
|
const coloredLines = lines.map((line) => {
|
||||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
if (line.startsWith('+') && !line.startsWith('+++')) {
|
||||||
return `\x1b[32m${line}\x1b[0m`; // Green for additions
|
return `\x1b[32m${line}\x1b[0m`; // Green for additions
|
||||||
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
||||||
@@ -80,29 +93,32 @@ export class DiffReporter {
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return coloredLines.join('\n');
|
return coloredLines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveDiffReport(outputPath: string): Promise<void> {
|
async saveDiffReport(outputPath: string): Promise<void> {
|
||||||
const report = {
|
const report = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
totalFiles: this.diffs.size,
|
totalFiles: this.diffs.size,
|
||||||
diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
|
diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
|
||||||
path,
|
path,
|
||||||
diff
|
diff,
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath);
|
await plugins.smartfile.memory.toFs(
|
||||||
|
JSON.stringify(report, null, 2),
|
||||||
|
outputPath,
|
||||||
|
);
|
||||||
logger.log('info', `Diff report saved to ${outputPath}`);
|
logger.log('info', `Diff report saved to ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAnyDiffs(): boolean {
|
hasAnyDiffs(): boolean {
|
||||||
return this.diffs.size > 0;
|
return this.diffs.size > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDiffCount(): number {
|
getDiffCount(): number {
|
||||||
return this.diffs.size;
|
return this.diffs.size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,65 +1,14 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import { RollbackManager } from './classes.rollbackmanager.js';
|
|
||||||
import { ChangeCache } from './classes.changecache.js';
|
|
||||||
import { FormatStats } from './classes.formatstats.js';
|
import { FormatStats } from './classes.formatstats.js';
|
||||||
import type { IFormatOperation, IFormatPlan } from './interfaces.format.js';
|
|
||||||
|
|
||||||
export class FormatContext {
|
export class FormatContext {
|
||||||
private rollbackManager: RollbackManager;
|
|
||||||
private currentOperation: IFormatOperation | null = null;
|
|
||||||
private changeCache: ChangeCache;
|
|
||||||
private formatStats: FormatStats;
|
private formatStats: FormatStats;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.rollbackManager = new RollbackManager();
|
|
||||||
this.changeCache = new ChangeCache();
|
|
||||||
this.formatStats = new FormatStats();
|
this.formatStats = new FormatStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
async beginOperation(): Promise<void> {
|
|
||||||
this.currentOperation = await this.rollbackManager.createOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
async trackFileChange(filepath: string): Promise<void> {
|
|
||||||
if (!this.currentOperation) {
|
|
||||||
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
||||||
}
|
|
||||||
await this.rollbackManager.backupFile(filepath, this.currentOperation.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async commitOperation(): Promise<void> {
|
|
||||||
if (!this.currentOperation) {
|
|
||||||
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
||||||
}
|
|
||||||
await this.rollbackManager.markComplete(this.currentOperation.id);
|
|
||||||
this.currentOperation = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rollbackOperation(): Promise<void> {
|
|
||||||
if (!this.currentOperation) {
|
|
||||||
throw new Error('No operation in progress. Call beginOperation() first.');
|
|
||||||
}
|
|
||||||
await this.rollbackManager.rollback(this.currentOperation.id);
|
|
||||||
this.currentOperation = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rollbackTo(operationId: string): Promise<void> {
|
|
||||||
await this.rollbackManager.rollback(operationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRollbackManager(): RollbackManager {
|
|
||||||
return this.rollbackManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
getChangeCache(): ChangeCache {
|
|
||||||
return this.changeCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
async initializeCache(): Promise<void> {
|
|
||||||
await this.changeCache.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
getFormatStats(): FormatStats {
|
getFormatStats(): FormatStats {
|
||||||
return this.formatStats;
|
return this.formatStats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ export class FormatPlanner {
|
|||||||
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
private plannedChanges: Map<string, IPlannedChange[]> = new Map();
|
||||||
private dependencyAnalyzer = new DependencyAnalyzer();
|
private dependencyAnalyzer = new DependencyAnalyzer();
|
||||||
private diffReporter = new DiffReporter();
|
private diffReporter = new DiffReporter();
|
||||||
|
|
||||||
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
async planFormat(modules: BaseFormatter[]): Promise<IFormatPlan> {
|
||||||
const plan: IFormatPlan = {
|
const plan: IFormatPlan = {
|
||||||
summary: {
|
summary: {
|
||||||
@@ -18,20 +18,20 @@ export class FormatPlanner {
|
|||||||
filesAdded: 0,
|
filesAdded: 0,
|
||||||
filesModified: 0,
|
filesModified: 0,
|
||||||
filesRemoved: 0,
|
filesRemoved: 0,
|
||||||
estimatedTime: 0
|
estimatedTime: 0,
|
||||||
},
|
},
|
||||||
changes: [],
|
changes: [],
|
||||||
warnings: []
|
warnings: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
try {
|
try {
|
||||||
const changes = await module.analyze();
|
const changes = await module.analyze();
|
||||||
this.plannedChanges.set(module.name, changes);
|
this.plannedChanges.set(module.name, changes);
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
plan.changes.push(change);
|
plan.changes.push(change);
|
||||||
|
|
||||||
// Update summary
|
// Update summary
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
@@ -49,67 +49,51 @@ export class FormatPlanner {
|
|||||||
plan.warnings.push({
|
plan.warnings.push({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
message: `Failed to analyze module ${module.name}: ${error.message}`,
|
message: `Failed to analyze module ${module.name}: ${error.message}`,
|
||||||
module: module.name
|
module: module.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plan.summary.totalFiles = plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved;
|
plan.summary.totalFiles =
|
||||||
|
plan.summary.filesAdded +
|
||||||
|
plan.summary.filesModified +
|
||||||
|
plan.summary.filesRemoved;
|
||||||
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
|
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise<void> {
|
async executePlan(
|
||||||
await context.beginOperation();
|
plan: IFormatPlan,
|
||||||
|
modules: BaseFormatter[],
|
||||||
|
context: FormatContext,
|
||||||
|
parallel: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (parallel) {
|
// Always use sequential execution to avoid race conditions
|
||||||
// Get execution groups based on dependencies
|
for (const module of modules) {
|
||||||
const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules);
|
const changes = this.plannedChanges.get(module.name) || [];
|
||||||
|
|
||||||
logger.log('info', `Executing formatters in ${executionGroups.length} groups...`);
|
if (changes.length > 0) {
|
||||||
|
logger.log('info', `Executing ${module.name} formatter...`);
|
||||||
for (let i = 0; i < executionGroups.length; i++) {
|
await module.execute(changes);
|
||||||
const group = executionGroups[i];
|
|
||||||
logger.log('info', `Executing group ${i + 1}: ${group.map(m => m.name).join(', ')}`);
|
|
||||||
|
|
||||||
// Execute modules in this group in parallel
|
|
||||||
const promises = group.map(async (module) => {
|
|
||||||
const changes = this.plannedChanges.get(module.name) || [];
|
|
||||||
if (changes.length > 0) {
|
|
||||||
logger.log('info', `Executing ${module.name} formatter...`);
|
|
||||||
await module.execute(changes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Sequential execution (original implementation)
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const endTime = Date.now();
|
const endTime = Date.now();
|
||||||
const duration = endTime - startTime;
|
const duration = endTime - startTime;
|
||||||
logger.log('info', `Format operations completed in ${duration}ms`);
|
logger.log('info', `Format operations completed in ${duration}ms`);
|
||||||
|
|
||||||
await context.commitOperation();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await context.rollbackOperation();
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async displayPlan(plan: IFormatPlan, detailed: boolean = false): Promise<void> {
|
async displayPlan(
|
||||||
|
plan: IFormatPlan,
|
||||||
|
detailed: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
console.log('\nFormat Plan:');
|
console.log('\nFormat Plan:');
|
||||||
console.log('━'.repeat(50));
|
console.log('━'.repeat(50));
|
||||||
console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
|
console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
|
||||||
@@ -118,7 +102,7 @@ export class FormatPlanner {
|
|||||||
console.log(` • ${plan.summary.filesRemoved} deleted files`);
|
console.log(` • ${plan.summary.filesRemoved} deleted files`);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Changes by module:');
|
console.log('Changes by module:');
|
||||||
|
|
||||||
// Group changes by module
|
// Group changes by module
|
||||||
const changesByModule = new Map<string, IPlannedChange[]>();
|
const changesByModule = new Map<string, IPlannedChange[]>();
|
||||||
for (const change of plan.changes) {
|
for (const change of plan.changes) {
|
||||||
@@ -126,14 +110,16 @@ export class FormatPlanner {
|
|||||||
moduleChanges.push(change);
|
moduleChanges.push(change);
|
||||||
changesByModule.set(change.module, moduleChanges);
|
changesByModule.set(change.module, moduleChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [module, changes] of changesByModule) {
|
for (const [module, changes] of changesByModule) {
|
||||||
console.log(`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`);
|
console.log(
|
||||||
|
`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`,
|
||||||
|
);
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
const icon = this.getChangeIcon(change.type);
|
const icon = this.getChangeIcon(change.type);
|
||||||
console.log(` ${icon} ${change.path} - ${change.description}`);
|
console.log(` ${icon} ${change.path} - ${change.description}`);
|
||||||
|
|
||||||
// Show diff for modified files if detailed view is requested
|
// Show diff for modified files if detailed view is requested
|
||||||
if (detailed && change.type === 'modify') {
|
if (detailed && change.type === 'modify') {
|
||||||
const diff = await this.diffReporter.generateDiffForChange(change);
|
const diff = await this.diffReporter.generateDiffForChange(change);
|
||||||
@@ -143,7 +129,7 @@ export class FormatPlanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan.warnings.length > 0) {
|
if (plan.warnings.length > 0) {
|
||||||
console.log('\nWarnings:');
|
console.log('\nWarnings:');
|
||||||
for (const warning of plan.warnings) {
|
for (const warning of plan.warnings) {
|
||||||
@@ -151,26 +137,26 @@ export class FormatPlanner {
|
|||||||
console.log(` ${icon} ${warning.message}`);
|
console.log(` ${icon} ${warning.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n' + '━'.repeat(50));
|
console.log('\n' + '━'.repeat(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getModuleIcon(module: string): string {
|
private getModuleIcon(module: string): string {
|
||||||
const icons: Record<string, string> = {
|
const icons: Record<string, string> = {
|
||||||
'packagejson': '📦',
|
packagejson: '📦',
|
||||||
'license': '📝',
|
license: '📝',
|
||||||
'tsconfig': '🔧',
|
tsconfig: '🔧',
|
||||||
'cleanup': '🚮',
|
cleanup: '🚮',
|
||||||
'gitignore': '🔒',
|
gitignore: '🔒',
|
||||||
'prettier': '✨',
|
prettier: '✨',
|
||||||
'readme': '📖',
|
readme: '📖',
|
||||||
'templates': '📄',
|
templates: '📄',
|
||||||
'npmextra': '⚙️',
|
npmextra: '⚙️',
|
||||||
'copy': '📋'
|
copy: '📋',
|
||||||
};
|
};
|
||||||
return icons[module] || '📁';
|
return icons[module] || '📁';
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
private getChangeIcon(type: 'create' | 'modify' | 'delete'): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
@@ -181,4 +167,4 @@ export class FormatPlanner {
|
|||||||
return '❌';
|
return '❌';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ export interface IFormatStats {
|
|||||||
|
|
||||||
export class FormatStats {
|
export class FormatStats {
|
||||||
private stats: IFormatStats;
|
private stats: IFormatStats;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.stats = {
|
this.stats = {
|
||||||
totalExecutionTime: 0,
|
totalExecutionTime: 0,
|
||||||
@@ -44,11 +44,11 @@ export class FormatStats {
|
|||||||
totalDeleted: 0,
|
totalDeleted: 0,
|
||||||
totalErrors: 0,
|
totalErrors: 0,
|
||||||
cacheHits: 0,
|
cacheHits: 0,
|
||||||
cacheMisses: 0
|
cacheMisses: 0,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
startModule(moduleName: string): void {
|
startModule(moduleName: string): void {
|
||||||
this.stats.moduleStats.set(moduleName, {
|
this.stats.moduleStats.set(moduleName, {
|
||||||
name: moduleName,
|
name: moduleName,
|
||||||
@@ -58,31 +58,35 @@ export class FormatStats {
|
|||||||
successes: 0,
|
successes: 0,
|
||||||
filesCreated: 0,
|
filesCreated: 0,
|
||||||
filesModified: 0,
|
filesModified: 0,
|
||||||
filesDeleted: 0
|
filesDeleted: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleStartTime(moduleName: string): number {
|
moduleStartTime(moduleName: string): number {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
endModule(moduleName: string, startTime: number): void {
|
endModule(moduleName: string, startTime: number): void {
|
||||||
const moduleStats = this.stats.moduleStats.get(moduleName);
|
const moduleStats = this.stats.moduleStats.get(moduleName);
|
||||||
if (moduleStats) {
|
if (moduleStats) {
|
||||||
moduleStats.executionTime = Date.now() - startTime;
|
moduleStats.executionTime = Date.now() - startTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recordFileOperation(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void {
|
recordFileOperation(
|
||||||
|
moduleName: string,
|
||||||
|
operation: 'create' | 'modify' | 'delete',
|
||||||
|
success: boolean = true,
|
||||||
|
): void {
|
||||||
const moduleStats = this.stats.moduleStats.get(moduleName);
|
const moduleStats = this.stats.moduleStats.get(moduleName);
|
||||||
if (!moduleStats) return;
|
if (!moduleStats) return;
|
||||||
|
|
||||||
moduleStats.filesProcessed++;
|
moduleStats.filesProcessed++;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
moduleStats.successes++;
|
moduleStats.successes++;
|
||||||
this.stats.overallStats.totalFiles++;
|
this.stats.overallStats.totalFiles++;
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'create':
|
case 'create':
|
||||||
moduleStats.filesCreated++;
|
moduleStats.filesCreated++;
|
||||||
@@ -102,53 +106,66 @@ export class FormatStats {
|
|||||||
this.stats.overallStats.totalErrors++;
|
this.stats.overallStats.totalErrors++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recordCacheHit(): void {
|
recordCacheHit(): void {
|
||||||
this.stats.overallStats.cacheHits++;
|
this.stats.overallStats.cacheHits++;
|
||||||
}
|
}
|
||||||
|
|
||||||
recordCacheMiss(): void {
|
recordCacheMiss(): void {
|
||||||
this.stats.overallStats.cacheMisses++;
|
this.stats.overallStats.cacheMisses++;
|
||||||
}
|
}
|
||||||
|
|
||||||
finish(): void {
|
finish(): void {
|
||||||
this.stats.endTime = Date.now();
|
this.stats.endTime = Date.now();
|
||||||
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
this.stats.totalExecutionTime = this.stats.endTime - this.stats.startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayStats(): void {
|
displayStats(): void {
|
||||||
console.log('\n📊 Format Operation Statistics:');
|
console.log('\n📊 Format Operation Statistics:');
|
||||||
console.log('═'.repeat(50));
|
console.log('═'.repeat(50));
|
||||||
|
|
||||||
// Overall stats
|
// Overall stats
|
||||||
console.log('\nOverall Summary:');
|
console.log('\nOverall Summary:');
|
||||||
console.log(` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`);
|
console.log(
|
||||||
|
` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`,
|
||||||
|
);
|
||||||
console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`);
|
console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`);
|
||||||
console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
|
console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
|
||||||
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
|
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
|
||||||
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
|
||||||
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
|
||||||
|
|
||||||
if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) {
|
if (
|
||||||
const cacheHitRate = this.stats.overallStats.cacheHits /
|
this.stats.overallStats.cacheHits > 0 ||
|
||||||
(this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100;
|
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(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
|
||||||
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
|
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
|
||||||
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
|
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Module stats
|
// Module stats
|
||||||
console.log('\nModule Breakdown:');
|
console.log('\nModule Breakdown:');
|
||||||
console.log('─'.repeat(50));
|
console.log('─'.repeat(50));
|
||||||
|
|
||||||
const sortedModules = Array.from(this.stats.moduleStats.values())
|
const sortedModules = Array.from(this.stats.moduleStats.values()).sort(
|
||||||
.sort((a, b) => b.filesProcessed - a.filesProcessed);
|
(a, b) => b.filesProcessed - a.filesProcessed,
|
||||||
|
);
|
||||||
|
|
||||||
for (const moduleStats of sortedModules) {
|
for (const moduleStats of sortedModules) {
|
||||||
console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`);
|
console.log(
|
||||||
console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`);
|
`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
|
||||||
|
);
|
||||||
console.log(` Files Processed: ${moduleStats.filesProcessed}`);
|
console.log(` Files Processed: ${moduleStats.filesProcessed}`);
|
||||||
|
|
||||||
if (moduleStats.filesCreated > 0) {
|
if (moduleStats.filesCreated > 0) {
|
||||||
console.log(` • Created: ${moduleStats.filesCreated}`);
|
console.log(` • Created: ${moduleStats.filesCreated}`);
|
||||||
}
|
}
|
||||||
@@ -158,27 +175,30 @@ export class FormatStats {
|
|||||||
if (moduleStats.filesDeleted > 0) {
|
if (moduleStats.filesDeleted > 0) {
|
||||||
console.log(` • Deleted: ${moduleStats.filesDeleted}`);
|
console.log(` • Deleted: ${moduleStats.filesDeleted}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moduleStats.errors > 0) {
|
if (moduleStats.errors > 0) {
|
||||||
console.log(` ❌ Errors: ${moduleStats.errors}`);
|
console.log(` ❌ Errors: ${moduleStats.errors}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n' + '═'.repeat(50));
|
console.log('\n' + '═'.repeat(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveReport(outputPath: string): Promise<void> {
|
async saveReport(outputPath: string): Promise<void> {
|
||||||
const report = {
|
const report = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
executionTime: this.stats.totalExecutionTime,
|
executionTime: this.stats.totalExecutionTime,
|
||||||
overallStats: this.stats.overallStats,
|
overallStats: this.stats.overallStats,
|
||||||
moduleStats: Array.from(this.stats.moduleStats.values())
|
moduleStats: Array.from(this.stats.moduleStats.values()),
|
||||||
};
|
};
|
||||||
|
|
||||||
await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath);
|
await plugins.smartfile.memory.toFs(
|
||||||
|
JSON.stringify(report, null, 2),
|
||||||
|
outputPath,
|
||||||
|
);
|
||||||
logger.log('info', `Statistics report saved to ${outputPath}`);
|
logger.log('info', `Statistics report saved to ${outputPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatDuration(ms: number): string {
|
private formatDuration(ms: number): string {
|
||||||
if (ms < 1000) {
|
if (ms < 1000) {
|
||||||
return `${ms}ms`;
|
return `${ms}ms`;
|
||||||
@@ -190,20 +210,20 @@ export class FormatStats {
|
|||||||
return `${minutes}m ${seconds}s`;
|
return `${minutes}m ${seconds}s`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getModuleIcon(module: string): string {
|
private getModuleIcon(module: string): string {
|
||||||
const icons: Record<string, string> = {
|
const icons: Record<string, string> = {
|
||||||
'packagejson': '📦',
|
packagejson: '📦',
|
||||||
'license': '📝',
|
license: '📝',
|
||||||
'tsconfig': '🔧',
|
tsconfig: '🔧',
|
||||||
'cleanup': '🚮',
|
cleanup: '🚮',
|
||||||
'gitignore': '🔒',
|
gitignore: '🔒',
|
||||||
'prettier': '✨',
|
prettier: '✨',
|
||||||
'readme': '📖',
|
readme: '📖',
|
||||||
'templates': '📄',
|
templates: '📄',
|
||||||
'npmextra': '⚙️',
|
npmextra: '⚙️',
|
||||||
'copy': '📋'
|
copy: '📋',
|
||||||
};
|
};
|
||||||
return icons[module] || '📁';
|
return icons[module] || '📁';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,214 +5,314 @@ import type { IFormatOperation } from './interfaces.format.js';
|
|||||||
export class RollbackManager {
|
export class RollbackManager {
|
||||||
private backupDir: string;
|
private backupDir: string;
|
||||||
private manifestPath: string;
|
private manifestPath: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
|
this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
|
||||||
this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
|
this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOperation(): Promise<IFormatOperation> {
|
async createOperation(): Promise<IFormatOperation> {
|
||||||
await this.ensureBackupDir();
|
await this.ensureBackupDir();
|
||||||
|
|
||||||
const operation: IFormatOperation = {
|
const operation: IFormatOperation = {
|
||||||
id: this.generateOperationId(),
|
id: this.generateOperationId(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
files: [],
|
files: [],
|
||||||
status: 'pending'
|
status: 'pending',
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.updateManifest(operation);
|
await this.updateManifest(operation);
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
async backupFile(filepath: string, operationId: string): Promise<void> {
|
async backupFile(filepath: string, operationId: string): Promise<void> {
|
||||||
const operation = await this.getOperation(operationId);
|
const operation = await this.getOperation(operationId);
|
||||||
if (!operation) {
|
if (!operation) {
|
||||||
throw new Error(`Operation ${operationId} not found`);
|
throw new Error(`Operation ${operationId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const absolutePath = plugins.path.isAbsolute(filepath)
|
const absolutePath = plugins.path.isAbsolute(filepath)
|
||||||
? filepath
|
? filepath
|
||||||
: plugins.path.join(paths.cwd, filepath);
|
: plugins.path.join(paths.cwd, filepath);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
const exists = await plugins.smartfile.fs.fileExists(absolutePath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
// File doesn't exist yet (will be created), so we skip backup
|
// File doesn't exist yet (will be created), so we skip backup
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file content and metadata
|
// Read file content and metadata
|
||||||
const content = await plugins.smartfile.fs.toStringSync(absolutePath);
|
const content = plugins.smartfile.fs.toStringSync(absolutePath);
|
||||||
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
const stats = await plugins.smartfile.fs.stat(absolutePath);
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
// Create backup
|
// Create backup
|
||||||
const backupPath = this.getBackupPath(operationId, filepath);
|
const backupPath = this.getBackupPath(operationId, filepath);
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
|
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
|
||||||
await plugins.smartfile.memory.toFs(content, backupPath);
|
await plugins.smartfile.memory.toFs(content, backupPath);
|
||||||
|
|
||||||
// Update operation
|
// Update operation
|
||||||
operation.files.push({
|
operation.files.push({
|
||||||
path: filepath,
|
path: filepath,
|
||||||
originalContent: content,
|
originalContent: content,
|
||||||
checksum,
|
checksum,
|
||||||
permissions: stats.mode.toString(8)
|
permissions: stats.mode.toString(8),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateManifest(operation);
|
await this.updateManifest(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
async rollback(operationId: string): Promise<void> {
|
async rollback(operationId: string): Promise<void> {
|
||||||
const operation = await this.getOperation(operationId);
|
const operation = await this.getOperation(operationId);
|
||||||
if (!operation) {
|
if (!operation) {
|
||||||
throw new Error(`Operation ${operationId} not found`);
|
// 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') {
|
if (operation.status === 'rolled-back') {
|
||||||
throw new Error(`Operation ${operationId} has already been rolled back`);
|
throw new Error(`Operation ${operationId} has already been rolled back`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore files in reverse order
|
// Restore files in reverse order
|
||||||
for (let i = operation.files.length - 1; i >= 0; i--) {
|
for (let i = operation.files.length - 1; i >= 0; i--) {
|
||||||
const file = operation.files[i];
|
const file = operation.files[i];
|
||||||
const absolutePath = plugins.path.isAbsolute(file.path)
|
const absolutePath = plugins.path.isAbsolute(file.path)
|
||||||
? file.path
|
? file.path
|
||||||
: plugins.path.join(paths.cwd, file.path);
|
: plugins.path.join(paths.cwd, file.path);
|
||||||
|
|
||||||
// Verify backup integrity
|
// Verify backup integrity
|
||||||
const backupPath = this.getBackupPath(operationId, file.path);
|
const backupPath = this.getBackupPath(operationId, file.path);
|
||||||
const backupContent = await plugins.smartfile.fs.toStringSync(backupPath);
|
const backupContent = plugins.smartfile.fs.toStringSync(backupPath);
|
||||||
const backupChecksum = this.calculateChecksum(backupContent);
|
const backupChecksum = this.calculateChecksum(backupContent);
|
||||||
|
|
||||||
if (backupChecksum !== file.checksum) {
|
if (backupChecksum !== file.checksum) {
|
||||||
throw new Error(`Backup integrity check failed for ${file.path}`);
|
throw new Error(`Backup integrity check failed for ${file.path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore file
|
// Restore file
|
||||||
await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
|
await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
|
||||||
|
|
||||||
// Restore permissions
|
// Restore permissions
|
||||||
const mode = parseInt(file.permissions, 8);
|
const mode = parseInt(file.permissions, 8);
|
||||||
// Note: Permissions restoration may not work on all platforms
|
// Note: Permissions restoration may not work on all platforms
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update operation status
|
// Update operation status
|
||||||
operation.status = 'rolled-back';
|
operation.status = 'rolled-back';
|
||||||
await this.updateManifest(operation);
|
await this.updateManifest(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
async markComplete(operationId: string): Promise<void> {
|
async markComplete(operationId: string): Promise<void> {
|
||||||
const operation = await this.getOperation(operationId);
|
const operation = await this.getOperation(operationId);
|
||||||
if (!operation) {
|
if (!operation) {
|
||||||
throw new Error(`Operation ${operationId} not found`);
|
throw new Error(`Operation ${operationId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.status = 'completed';
|
operation.status = 'completed';
|
||||||
await this.updateManifest(operation);
|
await this.updateManifest(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanOldBackups(retentionDays: number): Promise<void> {
|
async cleanOldBackups(retentionDays: number): Promise<void> {
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000);
|
const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const operationsToDelete = manifest.operations.filter(op =>
|
const operationsToDelete = manifest.operations.filter(
|
||||||
op.timestamp < cutoffTime && op.status === 'completed'
|
(op) => op.timestamp < cutoffTime && op.status === 'completed',
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const operation of operationsToDelete) {
|
for (const operation of operationsToDelete) {
|
||||||
// Remove backup files
|
// Remove backup files
|
||||||
const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id);
|
const operationDir = plugins.path.join(
|
||||||
|
this.backupDir,
|
||||||
|
'operations',
|
||||||
|
operation.id,
|
||||||
|
);
|
||||||
await plugins.smartfile.fs.remove(operationDir);
|
await plugins.smartfile.fs.remove(operationDir);
|
||||||
|
|
||||||
// Remove from manifest
|
// Remove from manifest
|
||||||
manifest.operations = manifest.operations.filter(op => op.id !== operation.id);
|
manifest.operations = manifest.operations.filter(
|
||||||
|
(op) => op.id !== operation.id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveManifest(manifest);
|
await this.saveManifest(manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyBackup(operationId: string): Promise<boolean> {
|
async verifyBackup(operationId: string): Promise<boolean> {
|
||||||
const operation = await this.getOperation(operationId);
|
const operation = await this.getOperation(operationId);
|
||||||
if (!operation) {
|
if (!operation) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of operation.files) {
|
for (const file of operation.files) {
|
||||||
const backupPath = this.getBackupPath(operationId, file.path);
|
const backupPath = this.getBackupPath(operationId, file.path);
|
||||||
const exists = await plugins.smartfile.fs.fileExists(backupPath);
|
const exists = await plugins.smartfile.fs.fileExists(backupPath);
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await plugins.smartfile.fs.toStringSync(backupPath);
|
const content = plugins.smartfile.fs.toStringSync(backupPath);
|
||||||
const checksum = this.calculateChecksum(content);
|
const checksum = this.calculateChecksum(content);
|
||||||
|
|
||||||
if (checksum !== file.checksum) {
|
if (checksum !== file.checksum) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async listBackups(): Promise<IFormatOperation[]> {
|
async listBackups(): Promise<IFormatOperation[]> {
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
return manifest.operations;
|
return manifest.operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureBackupDir(): Promise<void> {
|
private async ensureBackupDir(): Promise<void> {
|
||||||
await plugins.smartfile.fs.ensureDir(this.backupDir);
|
await plugins.smartfile.fs.ensureDir(this.backupDir);
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations'));
|
await plugins.smartfile.fs.ensureDir(
|
||||||
|
plugins.path.join(this.backupDir, 'operations'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateOperationId(): string {
|
private generateOperationId(): string {
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const random = Math.random().toString(36).substring(2, 8);
|
const random = Math.random().toString(36).substring(2, 8);
|
||||||
return `${timestamp}-${random}`;
|
return `${timestamp}-${random}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBackupPath(operationId: string, filepath: string): string {
|
private getBackupPath(operationId: string, filepath: string): string {
|
||||||
const filename = plugins.path.basename(filepath);
|
const filename = plugins.path.basename(filepath);
|
||||||
const dir = plugins.path.dirname(filepath);
|
const dir = plugins.path.dirname(filepath);
|
||||||
const safeDir = dir.replace(/[/\\]/g, '__');
|
const safeDir = dir.replace(/[/\\]/g, '__');
|
||||||
return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
|
return plugins.path.join(
|
||||||
|
this.backupDir,
|
||||||
|
'operations',
|
||||||
|
operationId,
|
||||||
|
'files',
|
||||||
|
safeDir,
|
||||||
|
`${filename}.backup`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateChecksum(content: string | Buffer): string {
|
private calculateChecksum(content: string | Buffer): string {
|
||||||
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
return plugins.crypto.createHash('sha256').update(content).digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
|
||||||
|
const defaultManifest = { operations: [] };
|
||||||
|
|
||||||
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return { operations: [] };
|
return defaultManifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = plugins.smartfile.fs.toStringSync(this.manifestPath);
|
||||||
|
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.smartfile.fs.remove(this.manifestPath);
|
||||||
|
} catch (removeError) {
|
||||||
|
// Ignore removal errors
|
||||||
|
}
|
||||||
|
return defaultManifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
|
|
||||||
return JSON.parse(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> {
|
private async saveManifest(manifest: {
|
||||||
await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
|
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.smartfile.memory.toFs(jsonContent, this.manifestPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOperation(operationId: string): Promise<IFormatOperation | null> {
|
private async getOperation(
|
||||||
|
operationId: string,
|
||||||
|
): Promise<IFormatOperation | null> {
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
return manifest.operations.find(op => op.id === operationId) || null;
|
return manifest.operations.find((op) => op.id === operationId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
private async updateManifest(operation: IFormatOperation): Promise<void> {
|
||||||
const manifest = await this.getManifest();
|
const manifest = await this.getManifest();
|
||||||
const existingIndex = manifest.operations.findIndex(op => op.id === operation.id);
|
const existingIndex = manifest.operations.findIndex(
|
||||||
|
(op) => op.id === operation.id,
|
||||||
|
);
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
manifest.operations[existingIndex] = operation;
|
manifest.operations[existingIndex] = operation;
|
||||||
} else {
|
} else {
|
||||||
manifest.operations.push(operation);
|
manifest.operations.push(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveManifest(manifest);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -4,14 +4,21 @@ import * as paths from '../paths.js';
|
|||||||
import { logger } from '../gitzone.logging.js';
|
import { logger } from '../gitzone.logging.js';
|
||||||
import { Project } from '../classes.project.js';
|
import { Project } from '../classes.project.js';
|
||||||
|
|
||||||
const filesToDelete = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json'];
|
const filesToDelete = [
|
||||||
|
'defaults.yml',
|
||||||
|
'yarn.lock',
|
||||||
|
'package-lock.json',
|
||||||
|
'tslint.json',
|
||||||
|
];
|
||||||
|
|
||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
for (const relativeFilePath of filesToDelete) {
|
for (const relativeFilePath of filesToDelete) {
|
||||||
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
|
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
|
||||||
if (fileExists) {
|
if (fileExists) {
|
||||||
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
logger.log('info', `Found ${relativeFilePath}! Removing it!`);
|
||||||
plugins.smartfile.fs.removeSync(plugins.path.join(paths.cwd, relativeFilePath));
|
plugins.smartfile.fs.removeSync(
|
||||||
|
plugins.path.join(paths.cwd, relativeFilePath),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.log('info', `Project is free of ${relativeFilePath}`);
|
logger.log('info', `Project is free of ${relativeFilePath}`);
|
||||||
}
|
}
|
||||||
|
@@ -4,56 +4,59 @@ import { logger } from '../gitzone.logging.js';
|
|||||||
|
|
||||||
export const run = async (projectArg: Project) => {
|
export const run = async (projectArg: Project) => {
|
||||||
const gitzoneConfig = await projectArg.gitzoneConfig;
|
const gitzoneConfig = await projectArg.gitzoneConfig;
|
||||||
|
|
||||||
// Get copy configuration from npmextra.json
|
// Get copy configuration from npmextra.json
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||||
const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
|
const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
|
||||||
patterns: []
|
patterns: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
|
||||||
logger.log('info', 'No copy patterns configured in npmextra.json');
|
logger.log('info', 'No copy patterns configured in npmextra.json');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pattern of copyConfig.patterns) {
|
for (const pattern of copyConfig.patterns) {
|
||||||
if (!pattern.from || !pattern.to) {
|
if (!pattern.from || !pattern.to) {
|
||||||
logger.log('warn', 'Invalid copy pattern - missing "from" or "to" field');
|
logger.log('warn', 'Invalid copy pattern - missing "from" or "to" field');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle glob patterns
|
// Handle glob patterns
|
||||||
const files = await plugins.smartfile.fs.listFileTree('.', pattern.from);
|
const files = await plugins.smartfile.fs.listFileTree('.', pattern.from);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const sourcePath = file;
|
const sourcePath = file;
|
||||||
let destPath = pattern.to;
|
let destPath = pattern.to;
|
||||||
|
|
||||||
// If destination is a directory, preserve filename
|
// If destination is a directory, preserve filename
|
||||||
if (pattern.to.endsWith('/')) {
|
if (pattern.to.endsWith('/')) {
|
||||||
const filename = plugins.path.basename(file);
|
const filename = plugins.path.basename(file);
|
||||||
destPath = plugins.path.join(pattern.to, filename);
|
destPath = plugins.path.join(pattern.to, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle template variables in destination path
|
// Handle template variables in destination path
|
||||||
if (pattern.preservePath) {
|
if (pattern.preservePath) {
|
||||||
const relativePath = plugins.path.relative(
|
const relativePath = plugins.path.relative(
|
||||||
plugins.path.dirname(pattern.from.replace(/\*/g, '')),
|
plugins.path.dirname(pattern.from.replace(/\*/g, '')),
|
||||||
file
|
file,
|
||||||
);
|
);
|
||||||
destPath = plugins.path.join(pattern.to, relativePath);
|
destPath = plugins.path.join(pattern.to, relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure destination directory exists
|
// Ensure destination directory exists
|
||||||
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(destPath));
|
await plugins.smartfile.fs.ensureDir(plugins.path.dirname(destPath));
|
||||||
|
|
||||||
// Copy file
|
// Copy file
|
||||||
await plugins.smartfile.fs.copy(sourcePath, destPath);
|
await plugins.smartfile.fs.copy(sourcePath, destPath);
|
||||||
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
|
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('error', `Failed to copy pattern ${pattern.from}: ${error.message}`);
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Failed to copy pattern ${pattern.from}: ${error.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -79,4 +82,4 @@ export const run = async (projectArg: Project) => {
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
@@ -12,7 +12,8 @@ export const run = async (projectArg: Project) => {
|
|||||||
const ciTemplate = await templateModule.getTemplate('gitignore');
|
const ciTemplate = await templateModule.getTemplate('gitignore');
|
||||||
if (gitignoreExists) {
|
if (gitignoreExists) {
|
||||||
// lets get the existing gitignore file
|
// lets get the existing gitignore file
|
||||||
const existingGitIgnoreString = plugins.smartfile.fs.toStringSync(gitignorePath);
|
const existingGitIgnoreString =
|
||||||
|
plugins.smartfile.fs.toStringSync(gitignorePath);
|
||||||
let customPart = existingGitIgnoreString.split('# custom\n')[1];
|
let customPart = existingGitIgnoreString.split('# custom\n')[1];
|
||||||
customPart ? null : (customPart = '');
|
customPart ? null : (customPart = '');
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,9 @@ export const run = async (projectArg: Project) => {
|
|||||||
} else {
|
} else {
|
||||||
logger.log('error', 'Error -> licenses failed. Here is why:');
|
logger.log('error', 'Error -> licenses failed. Here is why:');
|
||||||
for (const failedModule of licenseCheckResult.failingModules) {
|
for (const failedModule of licenseCheckResult.failingModules) {
|
||||||
console.log(`${failedModule.name} fails with license ${failedModule.license}`);
|
console.log(
|
||||||
|
`${failedModule.name} fails with license ${failedModule.license}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -29,7 +29,12 @@ export const run = async (projectArg: Project) => {
|
|||||||
|
|
||||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||||
if (!plugins.smartobject.smartGet(npmextraJson.gitzone, expectedRepoInformationItem)) {
|
if (
|
||||||
|
!plugins.smartobject.smartGet(
|
||||||
|
npmextraJson.gitzone,
|
||||||
|
expectedRepoInformationItem,
|
||||||
|
)
|
||||||
|
) {
|
||||||
interactInstance.addQuestions([
|
interactInstance.addQuestions([
|
||||||
{
|
{
|
||||||
message: `What is the value of ${expectedRepoInformationItem}`,
|
message: `What is the value of ${expectedRepoInformationItem}`,
|
||||||
@@ -43,7 +48,9 @@ export const run = async (projectArg: Project) => {
|
|||||||
|
|
||||||
const answerbucket = await interactInstance.runQueue();
|
const answerbucket = await interactInstance.runQueue();
|
||||||
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
for (const expectedRepoInformationItem of expectedRepoInformation) {
|
||||||
const cliProvidedValue = answerbucket.getAnswerFor(expectedRepoInformationItem);
|
const cliProvidedValue = answerbucket.getAnswerFor(
|
||||||
|
expectedRepoInformationItem,
|
||||||
|
);
|
||||||
if (cliProvidedValue) {
|
if (cliProvidedValue) {
|
||||||
plugins.smartobject.smartAdd(
|
plugins.smartobject.smartAdd(
|
||||||
npmextraJson.gitzone,
|
npmextraJson.gitzone,
|
||||||
|
@@ -19,7 +19,7 @@ const ensureDependency = async (
|
|||||||
: [dependencyArg, 'latest'];
|
: [dependencyArg, 'latest'];
|
||||||
|
|
||||||
const targetSections: string[] = [];
|
const targetSections: string[] = [];
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 'dep':
|
case 'dep':
|
||||||
targetSections.push('dependencies');
|
targetSections.push('dependencies');
|
||||||
@@ -43,7 +43,8 @@ const ensureDependency = async (
|
|||||||
break;
|
break;
|
||||||
case 'include':
|
case 'include':
|
||||||
if (!packageJsonObjectArg[section][packageName]) {
|
if (!packageJsonObjectArg[section][packageName]) {
|
||||||
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
|
packageJsonObjectArg[section][packageName] =
|
||||||
|
version === 'latest' ? '^1.0.0' : version;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'latest':
|
case 'latest':
|
||||||
@@ -54,9 +55,13 @@ const ensureDependency = async (
|
|||||||
const latestVersion = packageInfo['dist-tags'].latest;
|
const latestVersion = packageInfo['dist-tags'].latest;
|
||||||
packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
|
packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('warn', `Could not fetch latest version for ${packageName}, using existing or default`);
|
logger.log(
|
||||||
|
'warn',
|
||||||
|
`Could not fetch latest version for ${packageName}, using existing or default`,
|
||||||
|
);
|
||||||
if (!packageJsonObjectArg[section][packageName]) {
|
if (!packageJsonObjectArg[section][packageName]) {
|
||||||
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
|
packageJsonObjectArg[section][packageName] =
|
||||||
|
version === 'latest' ? '^1.0.0' : version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -91,9 +96,15 @@ export const run = async (projectArg: Project) => {
|
|||||||
|
|
||||||
// Check for private or public
|
// Check for private or public
|
||||||
if (packageJson.private !== undefined) {
|
if (packageJson.private !== undefined) {
|
||||||
logger.log('info', 'Success -> found private/public info in package.json!');
|
logger.log(
|
||||||
|
'info',
|
||||||
|
'Success -> found private/public info in package.json!',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.log('error', 'found no private boolean! Setting it to private for now!');
|
logger.log(
|
||||||
|
'error',
|
||||||
|
'found no private boolean! Setting it to private for now!',
|
||||||
|
);
|
||||||
packageJson.private = true;
|
packageJson.private = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +112,10 @@ export const run = async (projectArg: Project) => {
|
|||||||
if (packageJson.license) {
|
if (packageJson.license) {
|
||||||
logger.log('info', 'Success -> found license in package.json!');
|
logger.log('info', 'Success -> found license in package.json!');
|
||||||
} else {
|
} else {
|
||||||
logger.log('error', 'found no license! Setting it to UNLICENSED for now!');
|
logger.log(
|
||||||
|
'error',
|
||||||
|
'found no license! Setting it to UNLICENSED for now!',
|
||||||
|
);
|
||||||
packageJson.license = 'UNLICENSED';
|
packageJson.license = 'UNLICENSED';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,13 +123,19 @@ export const run = async (projectArg: Project) => {
|
|||||||
if (packageJson.scripts.build) {
|
if (packageJson.scripts.build) {
|
||||||
logger.log('info', 'Success -> found build script in package.json!');
|
logger.log('info', 'Success -> found build script in package.json!');
|
||||||
} else {
|
} else {
|
||||||
logger.log('error', 'found no build script! Putting a placeholder there for now!');
|
logger.log(
|
||||||
|
'error',
|
||||||
|
'found no build script! Putting a placeholder there for now!',
|
||||||
|
);
|
||||||
packageJson.scripts.build = `echo "Not needed for now"`;
|
packageJson.scripts.build = `echo "Not needed for now"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for buildDocs script
|
// Check for buildDocs script
|
||||||
if (!packageJson.scripts.buildDocs) {
|
if (!packageJson.scripts.buildDocs) {
|
||||||
logger.log('info', 'found no buildDocs script! Putting tsdoc script there now.');
|
logger.log(
|
||||||
|
'info',
|
||||||
|
'found no buildDocs script! Putting tsdoc script there now.',
|
||||||
|
);
|
||||||
packageJson.scripts.buildDocs = `tsdoc`;
|
packageJson.scripts.buildDocs = `tsdoc`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,9 +154,24 @@ export const run = async (projectArg: Project) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// check for dependencies
|
// check for dependencies
|
||||||
await ensureDependency(packageJson, 'devDep', 'latest', '@push.rocks/tapbundle');
|
await ensureDependency(
|
||||||
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest');
|
packageJson,
|
||||||
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tsbuild');
|
'devDep',
|
||||||
|
'latest',
|
||||||
|
'@push.rocks/tapbundle',
|
||||||
|
);
|
||||||
|
await ensureDependency(
|
||||||
|
packageJson,
|
||||||
|
'devDep',
|
||||||
|
'latest',
|
||||||
|
'@git.zone/tstest',
|
||||||
|
);
|
||||||
|
await ensureDependency(
|
||||||
|
packageJson,
|
||||||
|
'devDep',
|
||||||
|
'latest',
|
||||||
|
'@git.zone/tsbuild',
|
||||||
|
);
|
||||||
|
|
||||||
// set overrides
|
// set overrides
|
||||||
const overrides = plugins.smartfile.fs.toObjectSync(
|
const overrides = plugins.smartfile.fs.toObjectSync(
|
||||||
|
@@ -16,7 +16,12 @@ const prettierDefaultMarkdownConfig: prettier.Options = {
|
|||||||
parser: 'markdown',
|
parser: 'markdown',
|
||||||
};
|
};
|
||||||
|
|
||||||
const filesToFormat = [`ts/**/*.ts`, `test/**/*.ts`, `readme.md`, `docs/**/*.md`];
|
const filesToFormat = [
|
||||||
|
`ts/**/*.ts`,
|
||||||
|
`test/**/*.ts`,
|
||||||
|
`readme.md`,
|
||||||
|
`docs/**/*.md`,
|
||||||
|
];
|
||||||
|
|
||||||
const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
|
const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
|
||||||
switch (fileArg.parsedPath.ext) {
|
switch (fileArg.parsedPath.ext) {
|
||||||
@@ -39,7 +44,10 @@ const prettierTypeScriptPipestop = plugins.through2.obj(
|
|||||||
cb(null);
|
cb(null);
|
||||||
} else {
|
} else {
|
||||||
logger.log('info', `${fileArg.path} is being reformated!`);
|
logger.log('info', `${fileArg.path} is being reformated!`);
|
||||||
const formatedFileString = await prettier.format(fileString, chosenConfig);
|
const formatedFileString = await prettier.format(
|
||||||
|
fileString,
|
||||||
|
chosenConfig,
|
||||||
|
);
|
||||||
fileArg.setContentsFromString(formatedFileString);
|
fileArg.setContentsFromString(formatedFileString);
|
||||||
cb(null, fileArg);
|
cb(null, fileArg);
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,8 @@ export const run = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check and initialize readme.hints.md if it doesn't exist
|
// Check and initialize readme.hints.md if it doesn't exist
|
||||||
const readmeHintsExists = await plugins.smartfile.fs.fileExists(readmeHintsPath);
|
const readmeHintsExists =
|
||||||
|
await plugins.smartfile.fs.fileExists(readmeHintsPath);
|
||||||
if (!readmeHintsExists) {
|
if (!readmeHintsExists) {
|
||||||
await plugins.smartfile.fs.toFs(
|
await plugins.smartfile.fs.toFs(
|
||||||
'# Project Readme Hints\n\nThis is the initial readme hints file.',
|
'# Project Readme Hints\n\nThis is the initial readme hints file.',
|
||||||
|
@@ -26,10 +26,12 @@ export const run = async (project: Project) => {
|
|||||||
case 'npm':
|
case 'npm':
|
||||||
case 'wcc':
|
case 'wcc':
|
||||||
if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
|
if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
|
||||||
const ciTemplateDefault = await templateModule.getTemplate('ci_default');
|
const ciTemplateDefault =
|
||||||
|
await templateModule.getTemplate('ci_default');
|
||||||
ciTemplateDefault.writeToDisk(paths.cwd);
|
ciTemplateDefault.writeToDisk(paths.cwd);
|
||||||
} else {
|
} else {
|
||||||
const ciTemplateDefault = await templateModule.getTemplate('ci_default_private');
|
const ciTemplateDefault =
|
||||||
|
await templateModule.getTemplate('ci_default_private');
|
||||||
ciTemplateDefault.writeToDisk(paths.cwd);
|
ciTemplateDefault.writeToDisk(paths.cwd);
|
||||||
}
|
}
|
||||||
logger.log('info', 'Updated .gitlabci.yml!');
|
logger.log('info', 'Updated .gitlabci.yml!');
|
||||||
@@ -41,7 +43,8 @@ export const run = async (project: Project) => {
|
|||||||
logger.log('info', 'Updated CI/CD config files!');
|
logger.log('info', 'Updated CI/CD config files!');
|
||||||
|
|
||||||
// lets care about docker
|
// lets care about docker
|
||||||
const dockerTemplate = await templateModule.getTemplate('dockerfile_service');
|
const dockerTemplate =
|
||||||
|
await templateModule.getTemplate('dockerfile_service');
|
||||||
dockerTemplate.writeToDisk(paths.cwd);
|
dockerTemplate.writeToDisk(paths.cwd);
|
||||||
logger.log('info', 'Updated Dockerfile!');
|
logger.log('info', 'Updated Dockerfile!');
|
||||||
|
|
||||||
@@ -56,17 +59,22 @@ export const run = async (project: Project) => {
|
|||||||
|
|
||||||
// update html
|
// update html
|
||||||
if (project.gitzoneConfig.data.projectType === 'website') {
|
if (project.gitzoneConfig.data.projectType === 'website') {
|
||||||
const websiteUpdateTemplate = await templateModule.getTemplate('website_update');
|
const websiteUpdateTemplate =
|
||||||
const variables ={
|
await templateModule.getTemplate('website_update');
|
||||||
|
const variables = {
|
||||||
assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
|
assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
|
||||||
legalUrl: project.gitzoneConfig.data.module.legalUrl,
|
legalUrl: project.gitzoneConfig.data.module.legalUrl,
|
||||||
};
|
};
|
||||||
console.log('updating website template with variables\n', JSON.stringify(variables, null, 2));
|
console.log(
|
||||||
|
'updating website template with variables\n',
|
||||||
|
JSON.stringify(variables, null, 2),
|
||||||
|
);
|
||||||
websiteUpdateTemplate.supplyVariables(variables);
|
websiteUpdateTemplate.supplyVariables(variables);
|
||||||
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
||||||
logger.log('info', `Updated html for website!`);
|
logger.log('info', `Updated html for website!`);
|
||||||
} else if (project.gitzoneConfig.data.projectType === 'service') {
|
} else if (project.gitzoneConfig.data.projectType === 'service') {
|
||||||
const websiteUpdateTemplate = await templateModule.getTemplate('service_update');
|
const websiteUpdateTemplate =
|
||||||
|
await templateModule.getTemplate('service_update');
|
||||||
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
await websiteUpdateTemplate.writeToDisk(paths.cwd);
|
||||||
logger.log('info', `Updated html for element template!`);
|
logger.log('info', `Updated html for element template!`);
|
||||||
} else if (project.gitzoneConfig.data.projectType === 'wcc') {
|
} else if (project.gitzoneConfig.data.projectType === 'wcc') {
|
||||||
|
@@ -19,8 +19,12 @@ export const run = async (projectArg: Project) => {
|
|||||||
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
|
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
|
||||||
for (const publishModule of Object.keys(publishModules)) {
|
for (const publishModule of Object.keys(publishModules)) {
|
||||||
const publishConfig = publishModules[publishModule];
|
const publishConfig = publishModules[publishModule];
|
||||||
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [`./${publishModule}/index.js`];
|
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
|
||||||
|
`./${publishModule}/index.js`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
tsconfigSmartfile.setContentsFromString(JSON.stringify(tsconfigObject, null, 2));
|
tsconfigSmartfile.setContentsFromString(
|
||||||
|
JSON.stringify(tsconfigObject, null, 2),
|
||||||
|
);
|
||||||
await tsconfigSmartfile.write();
|
await tsconfigSmartfile.write();
|
||||||
};
|
};
|
||||||
|
@@ -7,13 +7,18 @@ export class CleanupFormatter extends BaseFormatter {
|
|||||||
get name(): string {
|
get name(): string {
|
||||||
return 'cleanup';
|
return 'cleanup';
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
|
|
||||||
// List of files to remove
|
// List of files to remove
|
||||||
const filesToRemove = ['yarn.lock', 'package-lock.json', 'tslint.json', 'defaults.yml'];
|
const filesToRemove = [
|
||||||
|
'yarn.lock',
|
||||||
|
'package-lock.json',
|
||||||
|
'tslint.json',
|
||||||
|
'defaults.yml',
|
||||||
|
];
|
||||||
|
|
||||||
for (const file of filesToRemove) {
|
for (const file of filesToRemove) {
|
||||||
const exists = await plugins.smartfile.fs.fileExists(file);
|
const exists = await plugins.smartfile.fs.fileExists(file);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
@@ -21,14 +26,14 @@ export class CleanupFormatter extends BaseFormatter {
|
|||||||
type: 'delete',
|
type: 'delete',
|
||||||
path: file,
|
path: file,
|
||||||
module: this.name,
|
module: this.name,
|
||||||
description: `Remove obsolete file`
|
description: `Remove obsolete file`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
@@ -36,4 +41,4 @@ export class CleanupFormatter extends BaseFormatter {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class CopyFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'copy', formatCopy);
|
super(context, project, 'copy', formatCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class GitignoreFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'gitignore', formatGitignore);
|
super(context, project, 'gitignore', formatGitignore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,30 +7,37 @@ import * as plugins from '../mod.plugins.js';
|
|||||||
export class LegacyFormatter extends BaseFormatter {
|
export class LegacyFormatter extends BaseFormatter {
|
||||||
private moduleName: string;
|
private moduleName: string;
|
||||||
private formatModule: any;
|
private formatModule: any;
|
||||||
|
|
||||||
constructor(context: any, project: Project, moduleName: string, formatModule: any) {
|
constructor(
|
||||||
|
context: any,
|
||||||
|
project: Project,
|
||||||
|
moduleName: string,
|
||||||
|
formatModule: any,
|
||||||
|
) {
|
||||||
super(context, project);
|
super(context, project);
|
||||||
this.moduleName = moduleName;
|
this.moduleName = moduleName;
|
||||||
this.formatModule = formatModule;
|
this.formatModule = formatModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this.moduleName;
|
return this.moduleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
// For legacy modules, we can't easily predict changes
|
// For legacy modules, we can't easily predict changes
|
||||||
// So we'll return a generic change that indicates the module will run
|
// So we'll return a generic change that indicates the module will run
|
||||||
return [{
|
return [
|
||||||
type: 'modify',
|
{
|
||||||
path: '<various files>',
|
type: 'modify',
|
||||||
module: this.name,
|
path: '<various files>',
|
||||||
description: `Run ${this.name} formatter`
|
module: this.name,
|
||||||
}];
|
description: `Run ${this.name} formatter`,
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
// Run the legacy format module
|
// Run the legacy format module
|
||||||
await this.formatModule.run(this.project);
|
await this.formatModule.run(this.project);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class LicenseFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'license', formatLicense);
|
super(context, project, 'license', formatLicense);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class NpmextraFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'npmextra', formatNpmextra);
|
super(context, project, 'npmextra', formatNpmextra);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class PackageJsonFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'packagejson', formatPackageJson);
|
super(context, project, 'packagejson', formatPackageJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,55 +7,119 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
get name(): string {
|
get name(): string {
|
||||||
return 'prettier';
|
return 'prettier';
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
const globPattern = '**/*.{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
|
|
||||||
|
// Define directories to format (TypeScript directories by default)
|
||||||
|
const includeDirs = ['ts', 'ts_*', 'test', 'tests'];
|
||||||
|
|
||||||
|
// File extensions to format
|
||||||
|
const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
|
||||||
|
|
||||||
|
// Also format root-level config files
|
||||||
|
const rootConfigFiles = [
|
||||||
|
'package.json',
|
||||||
|
'tsconfig.json',
|
||||||
|
'npmextra.json',
|
||||||
|
'.prettierrc',
|
||||||
|
'.prettierrc.json',
|
||||||
|
'.prettierrc.js',
|
||||||
|
'readme.md',
|
||||||
|
'README.md',
|
||||||
|
'changelog.md',
|
||||||
|
'CHANGELOG.md',
|
||||||
|
'license',
|
||||||
|
'LICENSE',
|
||||||
|
'*.md',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Collect all files to format
|
||||||
|
const allFiles: string[] = [];
|
||||||
|
|
||||||
|
// Add files from TypeScript directories
|
||||||
|
for (const dir of includeDirs) {
|
||||||
|
const globPattern = `${dir}/**/*.${extensions}`;
|
||||||
|
const dirFiles = await plugins.smartfile.fs.listFileTree(
|
||||||
|
'.',
|
||||||
|
globPattern,
|
||||||
|
);
|
||||||
|
allFiles.push(...dirFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add root config files
|
||||||
|
for (const pattern of rootConfigFiles) {
|
||||||
|
const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern);
|
||||||
|
// Only include files at root level (no slashes in path)
|
||||||
|
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
|
||||||
|
allFiles.push(...rootLevelFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
const uniqueFiles = [...new Set(allFiles)];
|
||||||
|
|
||||||
// Get all files that match the pattern
|
// Get all files that match the pattern
|
||||||
const files = await plugins.smartfile.fs.listFileTree('.', globPattern);
|
const files = uniqueFiles;
|
||||||
|
|
||||||
// Check which files need formatting
|
// Ensure we only process actual files (not directories)
|
||||||
|
const validFiles: string[] = [];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const stats = await plugins.smartfile.fs.stat(file);
|
||||||
|
if (!stats.isDirectory()) {
|
||||||
|
validFiles.push(file);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Skip files that can't be accessed
|
||||||
|
logVerbose(`Skipping ${file} - cannot access: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check which files need formatting
|
||||||
|
for (const file of validFiles) {
|
||||||
// Skip files that haven't changed
|
// Skip files that haven't changed
|
||||||
if (!await this.shouldProcessFile(file)) {
|
if (!(await this.shouldProcessFile(file))) {
|
||||||
logVerbose(`Skipping ${file} - no changes detected`);
|
logVerbose(`Skipping ${file} - no changes detected`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.push({
|
changes.push({
|
||||||
type: 'modify',
|
type: 'modify',
|
||||||
path: file,
|
path: file,
|
||||||
module: this.name,
|
module: this.name,
|
||||||
description: 'Format with Prettier'
|
description: 'Format with Prettier',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('info', `Found ${changes.length} files to format with Prettier`);
|
logger.log('info', `Found ${changes.length} files to format with Prettier`);
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(changes: IPlannedChange[]): Promise<void> {
|
async execute(changes: IPlannedChange[]): Promise<void> {
|
||||||
const startTime = this.stats.moduleStartTime(this.name);
|
const startTime = this.stats.moduleStartTime(this.name);
|
||||||
this.stats.startModule(this.name);
|
this.stats.startModule(this.name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.preExecute();
|
await this.preExecute();
|
||||||
|
|
||||||
// Batch process files
|
// Batch process files
|
||||||
const batchSize = 10; // Process 10 files at a time
|
const batchSize = 10; // Process 10 files at a time
|
||||||
const batches: IPlannedChange[][] = [];
|
const batches: IPlannedChange[][] = [];
|
||||||
|
|
||||||
for (let i = 0; i < changes.length; i += batchSize) {
|
for (let i = 0; i < changes.length; i += batchSize) {
|
||||||
batches.push(changes.slice(i, i + batchSize));
|
batches.push(changes.slice(i, i + batchSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
logVerbose(`Processing ${changes.length} files in ${batches.length} batches`);
|
logVerbose(
|
||||||
|
`Processing ${changes.length} files in ${batches.length} batches`,
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < batches.length; i++) {
|
for (let i = 0; i < batches.length; i++) {
|
||||||
const batch = batches[i];
|
const batch = batches[i];
|
||||||
logVerbose(`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`);
|
logVerbose(
|
||||||
|
`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`,
|
||||||
|
);
|
||||||
|
|
||||||
// Process batch in parallel
|
// Process batch in parallel
|
||||||
const promises = batch.map(async (change) => {
|
const promises = batch.map(async (change) => {
|
||||||
try {
|
try {
|
||||||
@@ -63,44 +127,45 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
this.stats.recordFileOperation(this.name, change.type, true);
|
this.stats.recordFileOperation(this.name, change.type, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.stats.recordFileOperation(this.name, change.type, false);
|
this.stats.recordFileOperation(this.name, change.type, false);
|
||||||
logger.log('error', `Failed to format ${change.path}: ${error.message}`);
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Failed to format ${change.path}: ${error.message}`,
|
||||||
|
);
|
||||||
// Don't throw - continue with other files
|
// Don't throw - continue with other files
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.postExecute();
|
await this.postExecute();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.context.rollbackOperation();
|
// Rollback removed - no longer tracking operations
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
this.stats.endModule(this.name, startTime);
|
this.stats.endModule(this.name, startTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
if (change.type !== 'modify') return;
|
if (change.type !== 'modify') return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read current content
|
// Read current content
|
||||||
const content = await plugins.smartfile.fs.toStringSync(change.path);
|
const content = plugins.smartfile.fs.toStringSync(change.path);
|
||||||
|
|
||||||
// Format with prettier
|
// Format with prettier
|
||||||
const prettier = await import('prettier');
|
const prettier = await import('prettier');
|
||||||
const formatted = await prettier.format(content, {
|
const formatted = await prettier.format(content, {
|
||||||
filepath: change.path,
|
filepath: change.path,
|
||||||
...(await this.getPrettierConfig())
|
...(await this.getPrettierConfig()),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only write if content actually changed
|
// Only write if content actually changed
|
||||||
if (formatted !== content) {
|
if (formatted !== content) {
|
||||||
await this.modifyFile(change.path, formatted);
|
await this.modifyFile(change.path, formatted);
|
||||||
logVerbose(`Formatted ${change.path}`);
|
logVerbose(`Formatted ${change.path}`);
|
||||||
} else {
|
} else {
|
||||||
// Still update cache even if content didn't change
|
|
||||||
await this.cache.updateFileCache(change.path);
|
|
||||||
logVerbose(`No formatting changes for ${change.path}`);
|
logVerbose(`No formatting changes for ${change.path}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -108,7 +173,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getPrettierConfig(): Promise<any> {
|
private async getPrettierConfig(): Promise<any> {
|
||||||
// Try to load prettier config from the project
|
// Try to load prettier config from the project
|
||||||
const prettierConfig = new plugins.npmextra.Npmextra();
|
const prettierConfig = new plugins.npmextra.Npmextra();
|
||||||
@@ -119,7 +184,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
printWidth: 80,
|
printWidth: 80,
|
||||||
tabWidth: 2,
|
tabWidth: 2,
|
||||||
semi: true,
|
semi: true,
|
||||||
arrowParens: 'always'
|
arrowParens: 'always',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,17 +6,19 @@ export class ReadmeFormatter extends BaseFormatter {
|
|||||||
get name(): string {
|
get name(): string {
|
||||||
return 'readme';
|
return 'readme';
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
return [{
|
return [
|
||||||
type: 'modify',
|
{
|
||||||
path: 'readme.md',
|
type: 'modify',
|
||||||
module: this.name,
|
path: 'readme.md',
|
||||||
description: 'Ensure readme files exist'
|
module: this.name,
|
||||||
}];
|
description: 'Ensure readme files exist',
|
||||||
|
},
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
await formatReadme.run();
|
await formatReadme.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class TemplatesFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'templates', formatTemplates);
|
super(context, project, 'templates', formatTemplates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,4 +5,4 @@ export class TsconfigFormatter extends LegacyFormatter {
|
|||||||
constructor(context: any, project: any) {
|
constructor(context: any, project: any) {
|
||||||
super(context, project, 'tsconfig', formatTsconfig);
|
super(context, project, 'tsconfig', formatTsconfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,27 +16,29 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
|
|||||||
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
||||||
import { CopyFormatter } from './formatters/copy.formatter.js';
|
import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||||
|
|
||||||
export let run = async (options: {
|
export let run = async (
|
||||||
dryRun?: boolean;
|
options: {
|
||||||
yes?: boolean;
|
dryRun?: boolean;
|
||||||
planOnly?: boolean;
|
yes?: boolean;
|
||||||
savePlan?: string;
|
planOnly?: boolean;
|
||||||
fromPlan?: string;
|
savePlan?: string;
|
||||||
detailed?: boolean;
|
fromPlan?: string;
|
||||||
interactive?: boolean;
|
detailed?: boolean;
|
||||||
parallel?: boolean;
|
interactive?: boolean;
|
||||||
verbose?: boolean;
|
parallel?: boolean;
|
||||||
} = {}): Promise<any> => {
|
verbose?: boolean;
|
||||||
|
} = {},
|
||||||
|
): Promise<any> => {
|
||||||
// Set verbose mode if requested
|
// Set verbose mode if requested
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
setVerboseMode(true);
|
setVerboseMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const project = await Project.fromCwd();
|
const project = await Project.fromCwd();
|
||||||
const context = new FormatContext();
|
const context = new FormatContext();
|
||||||
await context.initializeCache(); // Initialize the cache system
|
// Cache system removed - no longer needed
|
||||||
const planner = new FormatPlanner();
|
const planner = new FormatPlanner();
|
||||||
|
|
||||||
// Get configuration from npmextra
|
// Get configuration from npmextra
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
const npmextraConfig = new plugins.npmextra.Npmextra();
|
||||||
const formatConfig = npmextraConfig.dataFor<any>('gitzone.format', {
|
const formatConfig = npmextraConfig.dataFor<any>('gitzone.format', {
|
||||||
@@ -49,30 +51,27 @@ export let run = async (options: {
|
|||||||
autoRollbackOnError: true,
|
autoRollbackOnError: true,
|
||||||
backupRetentionDays: 7,
|
backupRetentionDays: 7,
|
||||||
maxBackupSize: '100MB',
|
maxBackupSize: '100MB',
|
||||||
excludePatterns: ['node_modules/**', '.git/**']
|
excludePatterns: ['node_modules/**', '.git/**'],
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
skip: [],
|
skip: [],
|
||||||
only: [],
|
only: [],
|
||||||
order: []
|
order: [],
|
||||||
},
|
},
|
||||||
parallel: true,
|
parallel: true,
|
||||||
cache: {
|
cache: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
clean: true // Clean invalid entries from cache
|
clean: true, // Clean invalid entries from cache
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean cache if configured
|
// Cache cleaning removed - no longer using cache system
|
||||||
if (formatConfig.cache.clean) {
|
|
||||||
await context.getChangeCache().clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override config with command options
|
// Override config with command options
|
||||||
const interactive = options.interactive ?? formatConfig.interactive;
|
const interactive = options.interactive ?? formatConfig.interactive;
|
||||||
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
||||||
const parallel = options.parallel ?? formatConfig.parallel;
|
const parallel = options.parallel ?? formatConfig.parallel;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize formatters
|
// Initialize formatters
|
||||||
const formatters = [
|
const formatters = [
|
||||||
@@ -87,9 +86,9 @@ export let run = async (options: {
|
|||||||
new ReadmeFormatter(context, project),
|
new ReadmeFormatter(context, project),
|
||||||
new CopyFormatter(context, project),
|
new CopyFormatter(context, project),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter formatters based on configuration
|
// Filter formatters based on configuration
|
||||||
const activeFormatters = formatters.filter(formatter => {
|
const activeFormatters = formatters.filter((formatter) => {
|
||||||
if (formatConfig.modules.only.length > 0) {
|
if (formatConfig.modules.only.length > 0) {
|
||||||
return formatConfig.modules.only.includes(formatter.name);
|
return formatConfig.modules.only.includes(formatter.name);
|
||||||
}
|
}
|
||||||
@@ -98,33 +97,36 @@ export let run = async (options: {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Plan phase
|
// Plan phase
|
||||||
logger.log('info', 'Analyzing project for format operations...');
|
logger.log('info', 'Analyzing project for format operations...');
|
||||||
let plan = options.fromPlan
|
let plan = options.fromPlan
|
||||||
? JSON.parse(await plugins.smartfile.fs.toStringSync(options.fromPlan))
|
? JSON.parse(await plugins.smartfile.fs.toStringSync(options.fromPlan))
|
||||||
: await planner.planFormat(activeFormatters);
|
: await planner.planFormat(activeFormatters);
|
||||||
|
|
||||||
// Display plan
|
// Display plan
|
||||||
await planner.displayPlan(plan, options.detailed);
|
await planner.displayPlan(plan, options.detailed);
|
||||||
|
|
||||||
// Save plan if requested
|
// Save plan if requested
|
||||||
if (options.savePlan) {
|
if (options.savePlan) {
|
||||||
await plugins.smartfile.memory.toFs(JSON.stringify(plan, null, 2), options.savePlan);
|
await plugins.smartfile.memory.toFs(
|
||||||
|
JSON.stringify(plan, null, 2),
|
||||||
|
options.savePlan,
|
||||||
|
);
|
||||||
logger.log('info', `Plan saved to ${options.savePlan}`);
|
logger.log('info', `Plan saved to ${options.savePlan}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if plan-only mode
|
// Exit if plan-only mode
|
||||||
if (options.planOnly) {
|
if (options.planOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dry-run mode
|
// Dry-run mode
|
||||||
if (options.dryRun) {
|
if (options.dryRun) {
|
||||||
logger.log('info', 'Dry-run mode - no changes will be made');
|
logger.log('info', 'Dry-run mode - no changes will be made');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interactive confirmation
|
// Interactive confirmation
|
||||||
if (interactive && !autoApprove) {
|
if (interactive && !autoApprove) {
|
||||||
const interactInstance = new plugins.smartinteract.SmartInteract();
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
||||||
@@ -132,117 +134,56 @@ export let run = async (options: {
|
|||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
name: 'proceed',
|
name: 'proceed',
|
||||||
message: 'Proceed with formatting?',
|
message: 'Proceed with formatting?',
|
||||||
default: true
|
default: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(response as any).proceed) {
|
if (!(response as any).value) {
|
||||||
logger.log('info', 'Format operation cancelled by user');
|
logger.log('info', 'Format operation cancelled by user');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute phase
|
// Execute phase
|
||||||
logger.log('info', `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`);
|
logger.log(
|
||||||
|
'info',
|
||||||
|
`Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`,
|
||||||
|
);
|
||||||
await planner.executePlan(plan, activeFormatters, context, parallel);
|
await planner.executePlan(plan, activeFormatters, context, parallel);
|
||||||
|
|
||||||
// Finish statistics tracking
|
// Finish statistics tracking
|
||||||
context.getFormatStats().finish();
|
context.getFormatStats().finish();
|
||||||
|
|
||||||
// Display statistics
|
// Display statistics
|
||||||
const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true);
|
const showStats = npmextraConfig.dataFor('gitzone.format.showStats', true);
|
||||||
if (showStats) {
|
if (showStats) {
|
||||||
context.getFormatStats().displayStats();
|
context.getFormatStats().displayStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save stats if requested
|
// Save stats if requested
|
||||||
if (options.detailed) {
|
if (options.detailed) {
|
||||||
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
||||||
await context.getFormatStats().saveReport(statsPath);
|
await context.getFormatStats().saveReport(statsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('success', 'Format operations completed successfully!');
|
logger.log('success', 'Format operations completed successfully!');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('error', `Format operation failed: ${error.message}`);
|
logger.log('error', `Format operation failed: ${error.message}`);
|
||||||
|
|
||||||
// Automatic rollback if enabled
|
// Rollback system has been removed for stability
|
||||||
if (formatConfig.rollback.enabled && formatConfig.rollback.autoRollbackOnError) {
|
|
||||||
logger.log('info', 'Attempting automatic rollback...');
|
|
||||||
try {
|
|
||||||
await context.rollbackOperation();
|
|
||||||
logger.log('success', 'Rollback completed successfully');
|
|
||||||
} catch (rollbackError) {
|
|
||||||
logger.log('error', `Rollback failed: ${rollbackError.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Export CLI command handlers
|
// Export CLI command handlers
|
||||||
export const handleRollback = async (operationId?: string): Promise<void> => {
|
export const handleRollback = async (operationId?: string): Promise<void> => {
|
||||||
const context = new FormatContext();
|
logger.log('info', 'Rollback system has been disabled for stability');
|
||||||
const rollbackManager = context.getRollbackManager();
|
|
||||||
|
|
||||||
if (!operationId) {
|
|
||||||
// Rollback to last operation
|
|
||||||
const backups = await rollbackManager.listBackups();
|
|
||||||
const lastOperation = backups
|
|
||||||
.filter(op => op.status !== 'rolled-back')
|
|
||||||
.sort((a, b) => b.timestamp - a.timestamp)[0];
|
|
||||||
|
|
||||||
if (!lastOperation) {
|
|
||||||
logger.log('warn', 'No operations available for rollback');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
operationId = lastOperation.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await rollbackManager.rollback(operationId);
|
|
||||||
logger.log('success', `Successfully rolled back operation ${operationId}`);
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Rollback failed: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleListBackups = async (): Promise<void> => {
|
export const handleListBackups = async (): Promise<void> => {
|
||||||
const context = new FormatContext();
|
logger.log('info', 'Backup system has been disabled for stability');
|
||||||
const rollbackManager = context.getRollbackManager();
|
|
||||||
const backups = await rollbackManager.listBackups();
|
|
||||||
|
|
||||||
if (backups.length === 0) {
|
|
||||||
logger.log('info', 'No backup operations found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nAvailable backups:');
|
|
||||||
console.log('━'.repeat(50));
|
|
||||||
|
|
||||||
for (const backup of backups) {
|
|
||||||
const date = new Date(backup.timestamp).toLocaleString();
|
|
||||||
const status = backup.status;
|
|
||||||
const filesCount = backup.files.length;
|
|
||||||
|
|
||||||
console.log(`ID: ${backup.id}`);
|
|
||||||
console.log(`Date: ${date}`);
|
|
||||||
console.log(`Status: ${status}`);
|
|
||||||
console.log(`Files: ${filesCount}`);
|
|
||||||
console.log('─'.repeat(50));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleCleanBackups = async (): Promise<void> => {
|
export const handleCleanBackups = async (): Promise<void> => {
|
||||||
const context = new FormatContext();
|
logger.log('info', 'Backup cleaning has been disabled - backup system removed');
|
||||||
const rollbackManager = context.getRollbackManager();
|
};
|
||||||
|
|
||||||
// Get retention days from config
|
|
||||||
const npmextraConfig = new plugins.npmextra.Npmextra();
|
|
||||||
const retentionDays = npmextraConfig.dataFor<any>('gitzone.format.rollback.backupRetentionDays', 7);
|
|
||||||
|
|
||||||
await rollbackManager.cleanOldBackups(retentionDays);
|
|
||||||
logger.log('success', `Cleaned backups older than ${retentionDays} days`);
|
|
||||||
};
|
|
||||||
|
@@ -9,7 +9,7 @@ export type IFormatOperation = {
|
|||||||
}>;
|
}>;
|
||||||
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
|
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
|
||||||
error?: Error;
|
error?: Error;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type IFormatPlan = {
|
export type IFormatPlan = {
|
||||||
summary: {
|
summary: {
|
||||||
@@ -32,7 +32,7 @@ export type IFormatPlan = {
|
|||||||
message: string;
|
message: string;
|
||||||
module: string;
|
module: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type IPlannedChange = {
|
export type IPlannedChange = {
|
||||||
type: 'create' | 'modify' | 'delete';
|
type: 'create' | 'modify' | 'delete';
|
||||||
@@ -42,4 +42,4 @@ export type IPlannedChange = {
|
|||||||
content?: string; // For create/modify operations
|
content?: string; // For create/modify operations
|
||||||
diff?: string;
|
diff?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
};
|
||||||
|
@@ -35,7 +35,10 @@ export class Meta {
|
|||||||
* sorts the metaRepoData
|
* sorts the metaRepoData
|
||||||
*/
|
*/
|
||||||
public async sortMetaRepoData() {
|
public async sortMetaRepoData() {
|
||||||
const stringifiedMetadata = plugins.smartjson.stringify(this.metaRepoData, []);
|
const stringifiedMetadata = plugins.smartjson.stringify(
|
||||||
|
this.metaRepoData,
|
||||||
|
[],
|
||||||
|
);
|
||||||
this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
|
this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +48,15 @@ export class Meta {
|
|||||||
public async readDirectory() {
|
public async readDirectory() {
|
||||||
await this.syncToRemote(true);
|
await this.syncToRemote(true);
|
||||||
logger.log('info', `reading directory`);
|
logger.log('info', `reading directory`);
|
||||||
const metaFileExists = plugins.smartfile.fs.fileExistsSync(this.filePaths.metaJson);
|
const metaFileExists = plugins.smartfile.fs.fileExistsSync(
|
||||||
|
this.filePaths.metaJson,
|
||||||
|
);
|
||||||
if (!metaFileExists) {
|
if (!metaFileExists) {
|
||||||
throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
|
throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
|
||||||
}
|
}
|
||||||
this.metaRepoData = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson);
|
this.metaRepoData = plugins.smartfile.fs.toObjectSync(
|
||||||
|
this.filePaths.metaJson,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,7 +83,10 @@ export class Meta {
|
|||||||
this.filePaths.metaJson,
|
this.filePaths.metaJson,
|
||||||
);
|
);
|
||||||
// write .gitignore to disk
|
// write .gitignore to disk
|
||||||
plugins.smartfile.memory.toFsSync(await this.generateGitignore(), this.filePaths.gitIgnore);
|
plugins.smartfile.memory.toFsSync(
|
||||||
|
await this.generateGitignore(),
|
||||||
|
this.filePaths.gitIgnore,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,13 +94,17 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async syncToRemote(gitCleanArg = false) {
|
public async syncToRemote(gitCleanArg = false) {
|
||||||
logger.log('info', `syncing from origin master`);
|
logger.log('info', `syncing from origin master`);
|
||||||
await this.smartshellInstance.exec(`cd ${this.cwd} && git pull origin master`);
|
await this.smartshellInstance.exec(
|
||||||
|
`cd ${this.cwd} && git pull origin master`,
|
||||||
|
);
|
||||||
if (gitCleanArg) {
|
if (gitCleanArg) {
|
||||||
logger.log('info', `cleaning the repository from old directories`);
|
logger.log('info', `cleaning the repository from old directories`);
|
||||||
await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
|
await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
|
||||||
}
|
}
|
||||||
logger.log('info', `syncing to remote origin master`);
|
logger.log('info', `syncing to remote origin master`);
|
||||||
await this.smartshellInstance.exec(`cd ${this.cwd} && git push origin master`);
|
await this.smartshellInstance.exec(
|
||||||
|
`cd ${this.cwd} && git push origin master`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,7 +112,9 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async updateLocalRepos() {
|
public async updateLocalRepos() {
|
||||||
await this.syncToRemote();
|
await this.syncToRemote();
|
||||||
const projects = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson).projects;
|
const projects = plugins.smartfile.fs.toObjectSync(
|
||||||
|
this.filePaths.metaJson,
|
||||||
|
).projects;
|
||||||
const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
|
const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
|
||||||
for (const preExistingFolderArg of preExistingFolders) {
|
for (const preExistingFolderArg of preExistingFolders) {
|
||||||
if (
|
if (
|
||||||
@@ -107,14 +123,18 @@ export class Meta {
|
|||||||
projectFolder.startsWith(preExistingFolderArg),
|
projectFolder.startsWith(preExistingFolderArg),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation(
|
const response =
|
||||||
`Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
|
await plugins.smartinteraction.SmartInteract.getCliConfirmation(
|
||||||
true,
|
`Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
|
||||||
);
|
true,
|
||||||
|
);
|
||||||
if (response) {
|
if (response) {
|
||||||
logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
|
logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
|
||||||
} else {
|
} else {
|
||||||
logger.log('warn', `Not deleting ${preExistingFolderArg} by request!`);
|
logger.log(
|
||||||
|
'warn',
|
||||||
|
`Not deleting ${preExistingFolderArg} by request!`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +180,9 @@ export class Meta {
|
|||||||
*/
|
*/
|
||||||
public async initProject() {
|
public async initProject() {
|
||||||
await this.syncToRemote(true);
|
await this.syncToRemote(true);
|
||||||
const fileExists = await plugins.smartfile.fs.fileExists(this.filePaths.metaJson);
|
const fileExists = await plugins.smartfile.fs.fileExists(
|
||||||
|
this.filePaths.metaJson,
|
||||||
|
);
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfile.memory.toFs(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -168,7 +190,10 @@ export class Meta {
|
|||||||
}),
|
}),
|
||||||
this.filePaths.metaJson,
|
this.filePaths.metaJson,
|
||||||
);
|
);
|
||||||
logger.log(`success`, `created a new .meta.json in directory ${this.cwd}`);
|
logger.log(
|
||||||
|
`success`,
|
||||||
|
`created a new .meta.json in directory ${this.cwd}`,
|
||||||
|
);
|
||||||
await plugins.smartfile.memory.toFs(
|
await plugins.smartfile.memory.toFs(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
name: this.dirName,
|
name: this.dirName,
|
||||||
@@ -176,9 +201,15 @@ export class Meta {
|
|||||||
}),
|
}),
|
||||||
this.filePaths.packageJson,
|
this.filePaths.packageJson,
|
||||||
);
|
);
|
||||||
logger.log(`success`, `created a new package.json in directory ${this.cwd}`);
|
logger.log(
|
||||||
|
`success`,
|
||||||
|
`created a new package.json in directory ${this.cwd}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.log(`error`, `directory ${this.cwd} already has a .metaJson file. Doing nothing.`);
|
logger.log(
|
||||||
|
`error`,
|
||||||
|
`directory ${this.cwd} already has a .metaJson file. Doing nothing.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await this.smartshellInstance.exec(
|
await this.smartshellInstance.exec(
|
||||||
`cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`,
|
`cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`,
|
||||||
@@ -195,7 +226,9 @@ export class Meta {
|
|||||||
const existingProject = this.metaRepoData.projects[projectNameArg];
|
const existingProject = this.metaRepoData.projects[projectNameArg];
|
||||||
|
|
||||||
if (existingProject) {
|
if (existingProject) {
|
||||||
throw new Error('Project already exists! Please remove it first before adding it again.');
|
throw new Error(
|
||||||
|
'Project already exists! Please remove it first before adding it again.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.metaRepoData.projects[projectNameArg] = gitUrlArg;
|
this.metaRepoData.projects[projectNameArg] = gitUrlArg;
|
||||||
@@ -217,7 +250,10 @@ export class Meta {
|
|||||||
const existingProject = this.metaRepoData.projects[projectNameArg];
|
const existingProject = this.metaRepoData.projects[projectNameArg];
|
||||||
|
|
||||||
if (!existingProject) {
|
if (!existingProject) {
|
||||||
logger.log('error', `Project ${projectNameArg} does not exist! So it cannot be removed`);
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Project ${projectNameArg} does not exist! So it cannot be removed`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +264,9 @@ export class Meta {
|
|||||||
await this.writeToDisk();
|
await this.writeToDisk();
|
||||||
|
|
||||||
logger.log('info', 'removing directory from cwd');
|
logger.log('info', 'removing directory from cwd');
|
||||||
await plugins.smartfile.fs.remove(plugins.path.join(paths.cwd, projectNameArg));
|
await plugins.smartfile.fs.remove(
|
||||||
|
plugins.path.join(paths.cwd, projectNameArg),
|
||||||
|
);
|
||||||
await this.updateLocalRepos();
|
await this.updateLocalRepos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,9 @@ export let run = () => {
|
|||||||
* create a new project with 'gitzone template [template]'
|
* create a new project with 'gitzone template [template]'
|
||||||
the following templates exist: ${(() => {
|
the following templates exist: ${(() => {
|
||||||
let projects = `\n`;
|
let projects = `\n`;
|
||||||
for (const template of plugins.smartfile.fs.listFoldersSync(paths.templatesDir)) {
|
for (const template of plugins.smartfile.fs.listFoldersSync(
|
||||||
|
paths.templatesDir,
|
||||||
|
)) {
|
||||||
projects += ` - ${template}\n`;
|
projects += ` - ${template}\n`;
|
||||||
}
|
}
|
||||||
return projects;
|
return projects;
|
||||||
|
@@ -15,7 +15,9 @@ export const run = async (argvArg: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`);
|
await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`);
|
||||||
await smartshellInstance.execStrict(`cd ${paths.cwd} && git pull origin master`);
|
await smartshellInstance.execStrict(
|
||||||
|
`cd ${paths.cwd} && git pull origin master`,
|
||||||
|
);
|
||||||
await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
|
await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
|
||||||
|
|
||||||
await provideNoGitFiles();
|
await provideNoGitFiles();
|
||||||
|
@@ -16,7 +16,9 @@ export const isTemplate = async (templateNameArg: string) => {
|
|||||||
|
|
||||||
export const getTemplate = async (templateNameArg: string) => {
|
export const getTemplate = async (templateNameArg: string) => {
|
||||||
if (isTemplate(templateNameArg)) {
|
if (isTemplate(templateNameArg)) {
|
||||||
const localScafTemplate = new plugins.smartscaf.ScafTemplate(getTemplatePath(templateNameArg));
|
const localScafTemplate = new plugins.smartscaf.ScafTemplate(
|
||||||
|
getTemplatePath(templateNameArg),
|
||||||
|
);
|
||||||
await localScafTemplate.readTemplateFromDir();
|
await localScafTemplate.readTemplateFromDir();
|
||||||
return localScafTemplate;
|
return localScafTemplate;
|
||||||
} else {
|
} else {
|
||||||
@@ -32,7 +34,8 @@ export const run = async (argvArg: any) => {
|
|||||||
const answerBucket = await smartinteract.askQuestion({
|
const answerBucket = await smartinteract.askQuestion({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
default: 'npm',
|
default: 'npm',
|
||||||
message: 'What template do you want to scaffold? (Only showing mpost common options)',
|
message:
|
||||||
|
'What template do you want to scaffold? (Only showing mpost common options)',
|
||||||
name: 'templateName',
|
name: 'templateName',
|
||||||
choices: ['npm', 'service', 'wcc', 'website'],
|
choices: ['npm', 'service', 'wcc', 'website'],
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user