Compare commits

...

15 Commits

Author SHA1 Message Date
74a8229e43 1.16.8
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 06:25:40 +00:00
859cbc733d fix(format): Improve concurrency control in cache and rollback management with mutex locking and refine formatting details 2025-08-08 06:25:40 +00:00
d32d47b706 1.16.7
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 05:48:41 +00:00
fd90cfe895 fix(core): Improve formatting, logging, and rollback integrity in core modules 2025-08-08 05:48:41 +00:00
c48f48fc8b 1.16.6
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 05:43:34 +00:00
e21e7f0850 fix(changecache): Improve cache manifest validation and atomic file writes; add local settings and overrides 2025-08-08 05:43:34 +00:00
5f561527f9 1.16.5
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 05:34:54 +00:00
9f5f568c3f fix(prettier): Improve file selection in Prettier formatter, remove legacy package overrides, and update CI template indentation 2025-08-08 05:34:54 +00:00
39a31a4304 1.16.4
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 05:28:02 +00:00
b629a7d70b fix(prettier): Improve file exclusion in the Prettier formatter to skip unnecessary files and directories. 2025-08-08 05:28:01 +00:00
4003944139 1.16.3
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 05:18:19 +00:00
83d374dffd fix(changecache/prettier): Skip directories during file processing to prevent errors in changecache and prettier formatting 2025-08-08 05:18:19 +00:00
49a5a66440 fix(format): Fix format command confirmation prompt to correctly check user response
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-07 21:13:12 +00:00
20a53d4d92 1.16.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-04 13:16:56 +00:00
fe02b990b3 fix(package/config): Move smartdiff dependency to runtime and add local bash permissions settings 2025-08-04 13:16:56 +00:00
48 changed files with 1370 additions and 772 deletions

View File

@@ -2,4 +2,3 @@ runafter:
- git add -A && git commit -m initial
- git push origin master
- gitzone meta update

View File

@@ -1,5 +1,62 @@
# 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)
Enhance format module with rollback, diff reporting, and improved parallel execution

View File

@@ -1,7 +1,7 @@
{
"name": "@git.zone/cli",
"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.",
"main": "dist_ts/index.ts",
"typings": "dist_ts/index.d.ts",
@@ -60,7 +60,6 @@
"@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.96",
"@push.rocks/smartdiff": "^1.0.3",
"@types/node": "^22.15.18"
},
"dependencies": {
@@ -75,6 +74,7 @@
"@push.rocks/smartchok": "^1.0.34",
"@push.rocks/smartcli": "^4.0.11",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartdiff": "^1.0.3",
"@push.rocks/smartfile": "^11.2.0",
"@push.rocks/smartgulp": "^3.0.4",
"@push.rocks/smartinteract": "^2.0.15",
@@ -113,9 +113,7 @@
"last 1 chrome versions"
],
"pnpm": {
"overrides": {
"peek-readable": "5.3.1"
}
"overrides": {}
},
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}

419
readme.md
View File

@@ -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
[![npm version](https://img.shields.io/npm/v/@git.zone/cli.svg)](https://www.npmjs.com/package/@git.zone/cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
# 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:
```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
# Create a semantic 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
gitzone deprecate
```bash
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 projects CI/CD settings instantly:
### 🎨 Intelligent Code Formatting
```shell
gitzone open ci
The most powerful feature of gitzone - automatically format and standardize your entire codebase:
```bash
# Preview changes without applying them
gitzone format --dry-run
# Format with automatic approval
gitzone format --yes
# Save formatting plan for later execution
gitzone format --save-plan format-plan.json
# Execute a saved plan
gitzone format --from-plan format-plan.json
# Enable verbose output for debugging
gitzone format --verbose
```
This command opens the settings page of the repositorys 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
gitzone format
# Rollback to a specific operation
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:
```shell
gitzone clean
```bash
gitzone commit
```
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
# 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:
```shell
```bash
# Clean up all Docker resources
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**
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 npms `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
# Open pipelines view
gitzone open pipelines
```
- **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:
- **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.
Smoothly transition users from old to new packages:
#### **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

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
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.'
}

View File

@@ -40,7 +40,9 @@ export class GitzoneConfig {
public async readConfigFromCwd() {
const npmextraInstance = new plugins.npmextra.Npmextra(paths.cwd);
this.data = npmextraInstance.dataFor<IGitzoneConfigData>('gitzone', {});
this.data.npmciOptions = npmextraInstance.dataFor<IGitzoneConfigData['npmciOptions']>('npmci', {
this.data.npmciOptions = npmextraInstance.dataFor<
IGitzoneConfigData['npmciOptions']
>('npmci', {
npmAccessLevel: 'public',
});
}

View File

@@ -89,7 +89,7 @@ export let run = async () => {
detailed: argvArg.detailed,
interactive: argvArg.interactive !== false,
parallel: argvArg.parallel !== false,
verbose: argvArg.verbose
verbose: argvArg.verbose,
});
});

View File

@@ -5,7 +5,8 @@ import * as plugins from './plugins.js';
export const logger = plugins.smartlog.Smartlog.createForCommitinfo(commitinfo);
// Add console destination
const consoleDestination = new plugins.smartlogDestinationLocal.DestinationLocal();
const consoleDestination =
new plugins.smartlogDestinationLocal.DestinationLocal();
logger.addLogDestination(consoleDestination);
// Verbose logging helper

View File

@@ -10,20 +10,22 @@ export const run = async (argvArg: any) => {
await formatMod.run();
}
logger.log('info', `gathering facts...`);
const aidoc = new plugins.tsdoc.AiDoc();
await aidoc.start();
const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd);
logger.log('info', `---------
logger.log(
'info',
`---------
Next recommended commit would be:
===========
-> ${nextCommitObject.recommendedNextVersion}:
-> ${nextCommitObject.recommendedNextVersionLevel}(${nextCommitObject.recommendedNextVersionScope}): ${nextCommitObject.recommendedNextVersionMessage}
===========
`);
`,
);
const commitInteract = new plugins.smartinteract.SmartInteract();
commitInteract.addQuestions([
{
@@ -72,32 +74,55 @@ export const run = async (argvArg: any) => {
});
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();
logger.log('info', `Writing changelog.md ...`);
let changelog = nextCommitObject.changelog;
changelog = changelog.replaceAll('{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString);
changelog = changelog.replaceAll('{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`);
changelog = changelog.replaceAll('{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage);
changelog = changelog.replaceAll(
'{{nextVersion}}',
(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) {
changelog = changelog.replaceAll('{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '));
changelog = changelog.replaceAll(
'{{nextVersionDetails}}',
'- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '),
);
} else {
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:`);
await smartshellInstance.exec(`git add -A`);
await smartshellInstance.exec(`git commit -m "${commitString}"`);
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`);
}
};
const createCommitStringFromAnswerBucket = (answerBucket: plugins.smartinteract.AnswerBucket) => {
const createCommitStringFromAnswerBucket = (
answerBucket: plugins.smartinteract.AnswerBucket,
) => {
const commitType = answerBucket.getAnswerFor('commitType');
const commitScope = answerBucket.getAnswerFor('commitScope');
const commitDescription = answerBucket.getAnswerFor('commitDescription');

View File

@@ -36,7 +36,10 @@ export const run = async () => {
const registryUrls = answerBucket.getAnswerFor(`registryUrls`).split(',');
const oldPackageName = answerBucket.getAnswerFor(`oldPackageName`);
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({
executor: 'bash',
});

View File

@@ -2,18 +2,15 @@ import * as plugins from './mod.plugins.js';
import { FormatContext } from './classes.formatcontext.js';
import type { IPlannedChange } from './interfaces.format.js';
import { Project } from '../classes.project.js';
import { ChangeCache } from './classes.changecache.js';
export abstract class BaseFormatter {
protected context: FormatContext;
protected project: Project;
protected cache: ChangeCache;
protected stats: any; // Will be FormatStats from context
constructor(context: FormatContext, project: Project) {
this.context = context;
this.project = project;
this.cache = context.getChangeCache();
this.stats = context.getFormatStats();
}
@@ -40,7 +37,7 @@ export abstract class BaseFormatter {
await this.postExecute();
} catch (error) {
await this.context.rollbackOperation();
// Don't rollback here - let the FormatPlanner handle it
throw error;
} finally {
this.stats.endModule(this.name, startTime);
@@ -56,38 +53,18 @@ export abstract class BaseFormatter {
}
protected async modifyFile(filepath: string, content: string): Promise<void> {
await this.context.trackFileChange(filepath);
await plugins.smartfile.memory.toFs(content, filepath);
await this.cache.updateFileCache(filepath);
}
protected async createFile(filepath: string, content: string): Promise<void> {
await plugins.smartfile.memory.toFs(content, filepath);
await this.cache.updateFileCache(filepath);
}
protected async deleteFile(filepath: string): Promise<void> {
await this.context.trackFileChange(filepath);
await plugins.smartfile.fs.remove(filepath);
}
protected async shouldProcessFile(filepath: string): Promise<boolean> {
const config = new plugins.npmextra.Npmextra();
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;
return true;
}
}

View File

@@ -29,21 +29,54 @@ export class ChangeCache {
}
async getManifest(): Promise<ICacheManifest> {
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
if (!exists) {
return {
const defaultManifest: ICacheManifest = {
version: this.cacheVersion,
lastFormat: 0,
files: []
files: [],
};
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
if (!exists) {
return defaultManifest;
}
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
return JSON.parse(content);
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;
}
}
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> {
@@ -59,21 +92,29 @@ export class ChangeCache {
// Get current file stats
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);
// Get cached info
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) {
return true; // Not in cache, so it's changed
}
// Compare checksums
return cachedFile.checksum !== currentChecksum ||
return (
cachedFile.checksum !== currentChecksum ||
cachedFile.size !== stats.size ||
cachedFile.modified !== stats.mtimeMs;
cachedFile.modified !== stats.mtimeMs
);
}
async updateFileCache(filePath: string): Promise<void> {
@@ -83,18 +124,24 @@ export class ChangeCache {
// Get current file stats
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);
// Update manifest
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 = {
path: filePath,
checksum,
modified: stats.mtimeMs,
size: stats.size
size: stats.size,
};
if (existingIndex !== -1) {
@@ -141,4 +188,36 @@ export class ChangeCache {
private calculateChecksum(content: string | Buffer): string {
return plugins.crypto.createHash('sha256').update(content).digest('hex');
}
private isValidManifest(manifest: any): manifest is ICacheManifest {
// Check if manifest has the required structure
if (!manifest || typeof manifest !== 'object') {
return false;
}
// Check required fields
if (
typeof manifest.version !== 'string' ||
typeof manifest.lastFormat !== 'number' ||
!Array.isArray(manifest.files)
) {
return false;
}
// Check each file entry
for (const file of manifest.files) {
if (
!file ||
typeof file !== 'object' ||
typeof file.path !== 'string' ||
typeof file.checksum !== 'string' ||
typeof file.modified !== 'number' ||
typeof file.size !== 'number'
) {
return false;
}
}
return true;
}
}

View File

@@ -17,16 +17,23 @@ export class DependencyAnalyzer {
private initializeDependencies(): void {
// Define dependencies between format modules
const dependencies = {
'cleanup': [], // No dependencies
'npmextra': [], // No dependencies
'license': ['npmextra'], // Depends on npmextra for config
'packagejson': ['npmextra'], // Depends on npmextra for config
'templates': ['npmextra', 'packagejson'], // Depends on both
'gitignore': ['templates'], // Depends on templates
'tsconfig': ['packagejson'], // Depends on package.json
'prettier': ['cleanup', 'npmextra', 'packagejson', 'templates', 'gitignore', 'tsconfig'], // Runs after most others
'readme': ['npmextra', 'packagejson'], // Depends on project metadata
'copy': ['npmextra'], // Depends on config
cleanup: [], // No dependencies
npmextra: [], // No dependencies
license: ['npmextra'], // Depends on npmextra for config
packagejson: ['npmextra'], // Depends on npmextra for config
templates: ['npmextra', 'packagejson'], // Depends on both
gitignore: ['templates'], // Depends on templates
tsconfig: ['packagejson'], // Depends on package.json
prettier: [
'cleanup',
'npmextra',
'packagejson',
'templates',
'gitignore',
'tsconfig',
], // Runs after most others
readme: ['npmextra', 'packagejson'], // Depends on project metadata
copy: ['npmextra'], // Depends on config
};
// Initialize all modules
@@ -34,7 +41,7 @@ export class DependencyAnalyzer {
this.moduleDependencies.set(module, {
module,
dependencies: new Set(deps),
dependents: new Set()
dependents: new Set(),
});
}
@@ -50,7 +57,7 @@ export class DependencyAnalyzer {
}
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 groups: BaseFormatter[][] = [];
@@ -68,8 +75,9 @@ export class DependencyAnalyzer {
}
// Check if all dependencies have been executed
const allDepsExecuted = Array.from(dependency.dependencies)
.every(dep => executed.has(dep) || !modulesMap.has(dep));
const allDepsExecuted = Array.from(dependency.dependencies).every(
(dep) => executed.has(dep) || !modulesMap.has(dep),
);
if (allDepsExecuted) {
currentGroup.push(module);
@@ -85,7 +93,7 @@ export class DependencyAnalyzer {
}
}
currentGroup.forEach(m => executed.add(m.name));
currentGroup.forEach((m) => executed.add(m.name));
groups.push(currentGroup);
}
@@ -99,9 +107,11 @@ export class DependencyAnalyzer {
if (!dep1 || !dep2) return false;
// Check if module1 depends on module2 or vice versa
return !dep1.dependencies.has(module2) &&
return (
!dep1.dependencies.has(module2) &&
!dep2.dependencies.has(module1) &&
!dep1.dependents.has(module2) &&
!dep2.dependents.has(module1);
!dep2.dependents.has(module1)
);
}
}

View File

@@ -5,7 +5,11 @@ import { logger } from '../gitzone.logging.js';
export class DiffReporter {
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);
this.diffs.set(filePath, diff);
return diff;
@@ -22,16 +26,25 @@ export class DiffReporter {
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
if (!change.content) {
return null;
}
return await this.generateDiff(change.path, currentContent, change.content);
return await this.generateDiff(
change.path,
currentContent,
change.content,
);
} 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;
}
}
@@ -69,7 +82,7 @@ export class DiffReporter {
private colorDiff(diff: string): string {
const lines = diff.split('\n');
const coloredLines = lines.map(line => {
const coloredLines = lines.map((line) => {
if (line.startsWith('+') && !line.startsWith('+++')) {
return `\x1b[32m${line}\x1b[0m`; // Green for additions
} else if (line.startsWith('-') && !line.startsWith('---')) {
@@ -90,11 +103,14 @@ export class DiffReporter {
totalFiles: this.diffs.size,
diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
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}`);
}

View File

@@ -1,64 +1,13 @@
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 type { IFormatOperation, IFormatPlan } from './interfaces.format.js';
export class FormatContext {
private rollbackManager: RollbackManager;
private currentOperation: IFormatOperation | null = null;
private changeCache: ChangeCache;
private formatStats: FormatStats;
constructor() {
this.rollbackManager = new RollbackManager();
this.changeCache = new ChangeCache();
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 {
return this.formatStats;
}

View File

@@ -18,10 +18,10 @@ export class FormatPlanner {
filesAdded: 0,
filesModified: 0,
filesRemoved: 0,
estimatedTime: 0
estimatedTime: 0,
},
changes: [],
warnings: []
warnings: [],
};
for (const module of modules) {
@@ -49,45 +49,30 @@ export class FormatPlanner {
plan.warnings.push({
level: 'error',
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
return plan;
}
async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise<void> {
await context.beginOperation();
async executePlan(
plan: IFormatPlan,
modules: BaseFormatter[],
context: FormatContext,
parallel: boolean = false,
): Promise<void> {
const startTime = Date.now();
try {
if (parallel) {
// Get execution groups based on dependencies
const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules);
logger.log('info', `Executing formatters in ${executionGroups.length} groups...`);
for (let i = 0; i < executionGroups.length; i++) {
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)
// Always use sequential execution to avoid race conditions
for (const module of modules) {
const changes = this.plannedChanges.get(module.name) || [];
@@ -96,20 +81,19 @@ export class FormatPlanner {
await module.execute(changes);
}
}
}
const endTime = Date.now();
const duration = endTime - startTime;
logger.log('info', `Format operations completed in ${duration}ms`);
await context.commitOperation();
} catch (error) {
await context.rollbackOperation();
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('━'.repeat(50));
console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
@@ -128,7 +112,9 @@ export class FormatPlanner {
}
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) {
const icon = this.getChangeIcon(change.type);
@@ -157,16 +143,16 @@ export class FormatPlanner {
private getModuleIcon(module: string): string {
const icons: Record<string, string> = {
'packagejson': '📦',
'license': '📝',
'tsconfig': '🔧',
'cleanup': '🚮',
'gitignore': '🔒',
'prettier': '✨',
'readme': '📖',
'templates': '📄',
'npmextra': '⚙️',
'copy': '📋'
packagejson: '📦',
license: '📝',
tsconfig: '🔧',
cleanup: '🚮',
gitignore: '🔒',
prettier: '✨',
readme: '📖',
templates: '📄',
npmextra: '⚙️',
copy: '📋',
};
return icons[module] || '📁';
}

View File

@@ -44,8 +44,8 @@ export class FormatStats {
totalDeleted: 0,
totalErrors: 0,
cacheHits: 0,
cacheMisses: 0
}
cacheMisses: 0,
},
};
}
@@ -58,7 +58,7 @@ export class FormatStats {
successes: 0,
filesCreated: 0,
filesModified: 0,
filesDeleted: 0
filesDeleted: 0,
});
}
@@ -73,7 +73,11 @@ export class FormatStats {
}
}
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);
if (!moduleStats) return;
@@ -122,16 +126,24 @@ export class FormatStats {
// Overall stats
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(` • Created: ${this.stats.overallStats.totalCreated}`);
console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) {
const cacheHitRate = this.stats.overallStats.cacheHits /
(this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100;
if (
this.stats.overallStats.cacheHits > 0 ||
this.stats.overallStats.cacheMisses > 0
) {
const cacheHitRate =
(this.stats.overallStats.cacheHits /
(this.stats.overallStats.cacheHits +
this.stats.overallStats.cacheMisses)) *
100;
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
@@ -141,12 +153,17 @@ export class FormatStats {
console.log('\nModule Breakdown:');
console.log('─'.repeat(50));
const sortedModules = Array.from(this.stats.moduleStats.values())
.sort((a, b) => b.filesProcessed - a.filesProcessed);
const sortedModules = Array.from(this.stats.moduleStats.values()).sort(
(a, b) => b.filesProcessed - a.filesProcessed,
);
for (const moduleStats of sortedModules) {
console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`);
console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`);
console.log(
`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
);
console.log(
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
);
console.log(` Files Processed: ${moduleStats.filesProcessed}`);
if (moduleStats.filesCreated > 0) {
@@ -172,10 +189,13 @@ export class FormatStats {
timestamp: new Date().toISOString(),
executionTime: this.stats.totalExecutionTime,
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}`);
}
@@ -193,16 +213,16 @@ export class FormatStats {
private getModuleIcon(module: string): string {
const icons: Record<string, string> = {
'packagejson': '📦',
'license': '📝',
'tsconfig': '🔧',
'cleanup': '🚮',
'gitignore': '🔒',
'prettier': '✨',
'readme': '📖',
'templates': '📄',
'npmextra': '⚙️',
'copy': '📋'
packagejson: '📦',
license: '📝',
tsconfig: '🔧',
cleanup: '🚮',
gitignore: '🔒',
prettier: '✨',
readme: '📖',
templates: '📄',
npmextra: '⚙️',
copy: '📋',
};
return icons[module] || '📁';
}

View File

@@ -18,7 +18,7 @@ export class RollbackManager {
id: this.generateOperationId(),
timestamp: Date.now(),
files: [],
status: 'pending'
status: 'pending',
};
await this.updateManifest(operation);
@@ -43,7 +43,7 @@ export class RollbackManager {
}
// 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 checksum = this.calculateChecksum(content);
@@ -57,7 +57,7 @@ export class RollbackManager {
path: filepath,
originalContent: content,
checksum,
permissions: stats.mode.toString(8)
permissions: stats.mode.toString(8),
});
await this.updateManifest(operation);
@@ -66,7 +66,9 @@ export class RollbackManager {
async rollback(operationId: string): Promise<void> {
const operation = await this.getOperation(operationId);
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') {
@@ -82,7 +84,7 @@ export class RollbackManager {
// Verify backup integrity
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);
if (backupChecksum !== file.checksum) {
@@ -114,19 +116,25 @@ export class RollbackManager {
async cleanOldBackups(retentionDays: number): Promise<void> {
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 =>
op.timestamp < cutoffTime && op.status === 'completed'
const operationsToDelete = manifest.operations.filter(
(op) => op.timestamp < cutoffTime && op.status === 'completed',
);
for (const operation of operationsToDelete) {
// Remove backup files
const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id);
const operationDir = plugins.path.join(
this.backupDir,
'operations',
operation.id,
);
await plugins.smartfile.fs.remove(operationDir);
// 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);
@@ -146,7 +154,7 @@ export class RollbackManager {
return false;
}
const content = await plugins.smartfile.fs.toStringSync(backupPath);
const content = plugins.smartfile.fs.toStringSync(backupPath);
const checksum = this.calculateChecksum(content);
if (checksum !== file.checksum) {
@@ -164,7 +172,9 @@ export class RollbackManager {
private async ensureBackupDir(): Promise<void> {
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 {
@@ -177,7 +187,14 @@ export class RollbackManager {
const filename = plugins.path.basename(filepath);
const dir = plugins.path.dirname(filepath);
const safeDir = dir.replace(/[/\\]/g, '__');
return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
return plugins.path.join(
this.backupDir,
'operations',
operationId,
'files',
safeDir,
`${filename}.backup`,
);
}
private calculateChecksum(content: string | Buffer): string {
@@ -185,27 +202,68 @@ export class RollbackManager {
}
private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
const defaultManifest = { operations: [] };
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
if (!exists) {
return { operations: [] };
return defaultManifest;
}
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
return JSON.parse(content);
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;
}
}
private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> {
await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
private async saveManifest(manifest: {
operations: IFormatOperation[];
}): Promise<void> {
// Validate before saving
if (!this.isValidManifest(manifest)) {
throw new Error('Invalid rollback manifest structure, cannot save');
}
private async getOperation(operationId: string): Promise<IFormatOperation | null> {
// 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> {
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> {
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) {
manifest.operations[existingIndex] = operation;
@@ -215,4 +273,46 @@ export class RollbackManager {
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;
}
}

View File

@@ -4,14 +4,21 @@ import * as paths from '../paths.js';
import { logger } from '../gitzone.logging.js';
import { Project } from '../classes.project.js';
const filesToDelete = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json'];
const filesToDelete = [
'defaults.yml',
'yarn.lock',
'package-lock.json',
'tslint.json',
];
export const run = async (projectArg: Project) => {
for (const relativeFilePath of filesToDelete) {
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
if (fileExists) {
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 {
logger.log('info', `Project is free of ${relativeFilePath}`);
}

View File

@@ -8,7 +8,7 @@ export const run = async (projectArg: Project) => {
// Get copy configuration from npmextra.json
const npmextraConfig = new plugins.npmextra.Npmextra();
const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
patterns: []
patterns: [],
});
if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
@@ -40,7 +40,7 @@ export const run = async (projectArg: Project) => {
if (pattern.preservePath) {
const relativePath = plugins.path.relative(
plugins.path.dirname(pattern.from.replace(/\*/g, '')),
file
file,
);
destPath = plugins.path.join(pattern.to, relativePath);
}
@@ -53,7 +53,10 @@ export const run = async (projectArg: Project) => {
logger.log('info', `Copied ${sourcePath} to ${destPath}`);
}
} catch (error) {
logger.log('error', `Failed to copy pattern ${pattern.from}: ${error.message}`);
logger.log(
'error',
`Failed to copy pattern ${pattern.from}: ${error.message}`,
);
}
}
};

View File

@@ -12,7 +12,8 @@ export const run = async (projectArg: Project) => {
const ciTemplate = await templateModule.getTemplate('gitignore');
if (gitignoreExists) {
// 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];
customPart ? null : (customPart = '');
}

View File

@@ -24,7 +24,9 @@ export const run = async (projectArg: Project) => {
} else {
logger.log('error', 'Error -> licenses failed. Here is why:');
for (const failedModule of licenseCheckResult.failingModules) {
console.log(`${failedModule.name} fails with license ${failedModule.license}`);
console.log(
`${failedModule.name} fails with license ${failedModule.license}`,
);
}
}
};

View File

@@ -29,7 +29,12 @@ export const run = async (projectArg: Project) => {
const interactInstance = new plugins.smartinteract.SmartInteract();
for (const expectedRepoInformationItem of expectedRepoInformation) {
if (!plugins.smartobject.smartGet(npmextraJson.gitzone, expectedRepoInformationItem)) {
if (
!plugins.smartobject.smartGet(
npmextraJson.gitzone,
expectedRepoInformationItem,
)
) {
interactInstance.addQuestions([
{
message: `What is the value of ${expectedRepoInformationItem}`,
@@ -43,7 +48,9 @@ export const run = async (projectArg: Project) => {
const answerbucket = await interactInstance.runQueue();
for (const expectedRepoInformationItem of expectedRepoInformation) {
const cliProvidedValue = answerbucket.getAnswerFor(expectedRepoInformationItem);
const cliProvidedValue = answerbucket.getAnswerFor(
expectedRepoInformationItem,
);
if (cliProvidedValue) {
plugins.smartobject.smartAdd(
npmextraJson.gitzone,

View File

@@ -43,7 +43,8 @@ const ensureDependency = async (
break;
case 'include':
if (!packageJsonObjectArg[section][packageName]) {
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
packageJsonObjectArg[section][packageName] =
version === 'latest' ? '^1.0.0' : version;
}
break;
case 'latest':
@@ -54,9 +55,13 @@ const ensureDependency = async (
const latestVersion = packageInfo['dist-tags'].latest;
packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
} catch (error) {
logger.log('warn', `Could not fetch latest version for ${packageName}, using existing or default`);
logger.log(
'warn',
`Could not fetch latest version for ${packageName}, using existing or default`,
);
if (!packageJsonObjectArg[section][packageName]) {
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version;
packageJsonObjectArg[section][packageName] =
version === 'latest' ? '^1.0.0' : version;
}
}
break;
@@ -91,9 +96,15 @@ export const run = async (projectArg: Project) => {
// Check for private or public
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 {
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;
}
@@ -101,7 +112,10 @@ export const run = async (projectArg: Project) => {
if (packageJson.license) {
logger.log('info', 'Success -> found license in package.json!');
} else {
logger.log('error', 'found no license! Setting it to UNLICENSED for now!');
logger.log(
'error',
'found no license! Setting it to UNLICENSED for now!',
);
packageJson.license = 'UNLICENSED';
}
@@ -109,13 +123,19 @@ export const run = async (projectArg: Project) => {
if (packageJson.scripts.build) {
logger.log('info', 'Success -> found build script in package.json!');
} else {
logger.log('error', 'found no build script! Putting a placeholder there for now!');
logger.log(
'error',
'found no build script! Putting a placeholder there for now!',
);
packageJson.scripts.build = `echo "Not needed for now"`;
}
// Check for buildDocs script
if (!packageJson.scripts.buildDocs) {
logger.log('info', 'found no buildDocs script! Putting tsdoc script there now.');
logger.log(
'info',
'found no buildDocs script! Putting tsdoc script there now.',
);
packageJson.scripts.buildDocs = `tsdoc`;
}
@@ -134,9 +154,24 @@ export const run = async (projectArg: Project) => {
];
// check for dependencies
await ensureDependency(packageJson, 'devDep', 'latest', '@push.rocks/tapbundle');
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest');
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tsbuild');
await ensureDependency(
packageJson,
'devDep',
'latest',
'@push.rocks/tapbundle',
);
await ensureDependency(
packageJson,
'devDep',
'latest',
'@git.zone/tstest',
);
await ensureDependency(
packageJson,
'devDep',
'latest',
'@git.zone/tsbuild',
);
// set overrides
const overrides = plugins.smartfile.fs.toObjectSync(

View File

@@ -16,7 +16,12 @@ const prettierDefaultMarkdownConfig: prettier.Options = {
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) => {
switch (fileArg.parsedPath.ext) {
@@ -39,7 +44,10 @@ const prettierTypeScriptPipestop = plugins.through2.obj(
cb(null);
} else {
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);
cb(null, fileArg);
}

View File

@@ -18,7 +18,8 @@ export const run = async () => {
}
// 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) {
await plugins.smartfile.fs.toFs(
'# Project Readme Hints\n\nThis is the initial readme hints file.',

View File

@@ -26,10 +26,12 @@ export const run = async (project: Project) => {
case 'npm':
case 'wcc':
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);
} else {
const ciTemplateDefault = await templateModule.getTemplate('ci_default_private');
const ciTemplateDefault =
await templateModule.getTemplate('ci_default_private');
ciTemplateDefault.writeToDisk(paths.cwd);
}
logger.log('info', 'Updated .gitlabci.yml!');
@@ -41,7 +43,8 @@ export const run = async (project: Project) => {
logger.log('info', 'Updated CI/CD config files!');
// lets care about docker
const dockerTemplate = await templateModule.getTemplate('dockerfile_service');
const dockerTemplate =
await templateModule.getTemplate('dockerfile_service');
dockerTemplate.writeToDisk(paths.cwd);
logger.log('info', 'Updated Dockerfile!');
@@ -56,17 +59,22 @@ export const run = async (project: Project) => {
// update html
if (project.gitzoneConfig.data.projectType === 'website') {
const websiteUpdateTemplate = await templateModule.getTemplate('website_update');
const variables ={
const websiteUpdateTemplate =
await templateModule.getTemplate('website_update');
const variables = {
assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
legalUrl: project.gitzoneConfig.data.module.legalUrl,
};
console.log('updating website template with variables\n', JSON.stringify(variables, null, 2));
console.log(
'updating website template with variables\n',
JSON.stringify(variables, null, 2),
);
websiteUpdateTemplate.supplyVariables(variables);
await websiteUpdateTemplate.writeToDisk(paths.cwd);
logger.log('info', `Updated html for website!`);
} else if (project.gitzoneConfig.data.projectType === 'service') {
const websiteUpdateTemplate = await templateModule.getTemplate('service_update');
const websiteUpdateTemplate =
await templateModule.getTemplate('service_update');
await websiteUpdateTemplate.writeToDisk(paths.cwd);
logger.log('info', `Updated html for element template!`);
} else if (project.gitzoneConfig.data.projectType === 'wcc') {

View File

@@ -19,8 +19,12 @@ export const run = async (projectArg: Project) => {
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
for (const publishModule of Object.keys(publishModules)) {
const publishConfig = publishModules[publishModule];
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [`./${publishModule}/index.js`];
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();
};

View File

@@ -12,7 +12,12 @@ export class CleanupFormatter extends BaseFormatter {
const changes: IPlannedChange[] = [];
// 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) {
const exists = await plugins.smartfile.fs.fileExists(file);
@@ -21,7 +26,7 @@ export class CleanupFormatter extends BaseFormatter {
type: 'delete',
path: file,
module: this.name,
description: `Remove obsolete file`
description: `Remove obsolete file`,
});
}
}

View File

@@ -8,7 +8,12 @@ export class LegacyFormatter extends BaseFormatter {
private moduleName: string;
private formatModule: any;
constructor(context: any, project: Project, moduleName: string, formatModule: any) {
constructor(
context: any,
project: Project,
moduleName: string,
formatModule: any,
) {
super(context, project);
this.moduleName = moduleName;
this.formatModule = formatModule;
@@ -21,12 +26,14 @@ export class LegacyFormatter extends BaseFormatter {
async analyze(): Promise<IPlannedChange[]> {
// For legacy modules, we can't easily predict changes
// So we'll return a generic change that indicates the module will run
return [{
return [
{
type: 'modify',
path: '<various files>',
module: this.name,
description: `Run ${this.name} formatter`
}];
description: `Run ${this.name} formatter`,
},
];
}
async applyChange(change: IPlannedChange): Promise<void> {

View File

@@ -10,15 +10,75 @@ export class PrettierFormatter extends BaseFormatter {
async analyze(): Promise<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
const files = await plugins.smartfile.fs.listFileTree('.', globPattern);
const files = uniqueFiles;
// Ensure we only process actual files (not directories)
const validFiles: string[] = [];
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 files) {
for (const file of validFiles) {
// Skip files that haven't changed
if (!await this.shouldProcessFile(file)) {
if (!(await this.shouldProcessFile(file))) {
logVerbose(`Skipping ${file} - no changes detected`);
continue;
}
@@ -27,7 +87,7 @@ export class PrettierFormatter extends BaseFormatter {
type: 'modify',
path: file,
module: this.name,
description: 'Format with Prettier'
description: 'Format with Prettier',
});
}
@@ -50,11 +110,15 @@ export class PrettierFormatter extends BaseFormatter {
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++) {
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
const promises = batch.map(async (change) => {
@@ -63,7 +127,10 @@ export class PrettierFormatter extends BaseFormatter {
this.stats.recordFileOperation(this.name, change.type, true);
} catch (error) {
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
}
});
@@ -73,7 +140,7 @@ export class PrettierFormatter extends BaseFormatter {
await this.postExecute();
} catch (error) {
await this.context.rollbackOperation();
// Rollback removed - no longer tracking operations
throw error;
} finally {
this.stats.endModule(this.name, startTime);
@@ -85,13 +152,13 @@ export class PrettierFormatter extends BaseFormatter {
try {
// Read current content
const content = await plugins.smartfile.fs.toStringSync(change.path);
const content = plugins.smartfile.fs.toStringSync(change.path);
// Format with prettier
const prettier = await import('prettier');
const formatted = await prettier.format(content, {
filepath: change.path,
...(await this.getPrettierConfig())
...(await this.getPrettierConfig()),
});
// Only write if content actually changed
@@ -99,8 +166,6 @@ export class PrettierFormatter extends BaseFormatter {
await this.modifyFile(change.path, formatted);
logVerbose(`Formatted ${change.path}`);
} else {
// Still update cache even if content didn't change
await this.cache.updateFileCache(change.path);
logVerbose(`No formatting changes for ${change.path}`);
}
} catch (error) {
@@ -119,7 +184,7 @@ export class PrettierFormatter extends BaseFormatter {
printWidth: 80,
tabWidth: 2,
semi: true,
arrowParens: 'always'
arrowParens: 'always',
});
}
}

View File

@@ -8,12 +8,14 @@ export class ReadmeFormatter extends BaseFormatter {
}
async analyze(): Promise<IPlannedChange[]> {
return [{
return [
{
type: 'modify',
path: 'readme.md',
module: this.name,
description: 'Ensure readme files exist'
}];
description: 'Ensure readme files exist',
},
];
}
async applyChange(change: IPlannedChange): Promise<void> {

View File

@@ -16,7 +16,8 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
import { ReadmeFormatter } from './formatters/readme.formatter.js';
import { CopyFormatter } from './formatters/copy.formatter.js';
export let run = async (options: {
export let run = async (
options: {
dryRun?: boolean;
yes?: boolean;
planOnly?: boolean;
@@ -26,7 +27,8 @@ export let run = async (options: {
interactive?: boolean;
parallel?: boolean;
verbose?: boolean;
} = {}): Promise<any> => {
} = {},
): Promise<any> => {
// Set verbose mode if requested
if (options.verbose) {
setVerboseMode(true);
@@ -34,7 +36,7 @@ export let run = async (options: {
const project = await Project.fromCwd();
const context = new FormatContext();
await context.initializeCache(); // Initialize the cache system
// Cache system removed - no longer needed
const planner = new FormatPlanner();
// Get configuration from npmextra
@@ -49,24 +51,21 @@ export let run = async (options: {
autoRollbackOnError: true,
backupRetentionDays: 7,
maxBackupSize: '100MB',
excludePatterns: ['node_modules/**', '.git/**']
excludePatterns: ['node_modules/**', '.git/**'],
},
modules: {
skip: [],
only: [],
order: []
order: [],
},
parallel: true,
cache: {
enabled: true,
clean: true // Clean invalid entries from cache
}
clean: true, // Clean invalid entries from cache
},
});
// Clean cache if configured
if (formatConfig.cache.clean) {
await context.getChangeCache().clean();
}
// Cache cleaning removed - no longer using cache system
// Override config with command options
const interactive = options.interactive ?? formatConfig.interactive;
@@ -89,7 +88,7 @@ export let run = async (options: {
];
// Filter formatters based on configuration
const activeFormatters = formatters.filter(formatter => {
const activeFormatters = formatters.filter((formatter) => {
if (formatConfig.modules.only.length > 0) {
return formatConfig.modules.only.includes(formatter.name);
}
@@ -110,7 +109,10 @@ export let run = async (options: {
// Save plan if requested
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}`);
}
@@ -132,17 +134,20 @@ export let run = async (options: {
type: 'confirm',
name: 'proceed',
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');
return;
}
}
// 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);
// Finish statistics tracking
@@ -161,20 +166,10 @@ export let run = async (options: {
}
logger.log('success', 'Format operations completed successfully!');
} catch (error) {
logger.log('error', `Format operation failed: ${error.message}`);
// Automatic rollback if enabled
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}`);
}
}
// Rollback system has been removed for stability
throw error;
}
@@ -182,67 +177,13 @@ export let run = async (options: {
// Export CLI command handlers
export const handleRollback = async (operationId?: string): Promise<void> => {
const context = new FormatContext();
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;
}
logger.log('info', 'Rollback system has been disabled for stability');
};
export const handleListBackups = async (): Promise<void> => {
const context = new FormatContext();
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));
}
logger.log('info', 'Backup system has been disabled for stability');
};
export const handleCleanBackups = async (): Promise<void> => {
const context = new FormatContext();
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`);
logger.log('info', 'Backup cleaning has been disabled - backup system removed');
};

View File

@@ -9,7 +9,7 @@ export type IFormatOperation = {
}>;
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
error?: Error;
}
};
export type IFormatPlan = {
summary: {
@@ -32,7 +32,7 @@ export type IFormatPlan = {
message: string;
module: string;
}>;
}
};
export type IPlannedChange = {
type: 'create' | 'modify' | 'delete';
@@ -42,4 +42,4 @@ export type IPlannedChange = {
content?: string; // For create/modify operations
diff?: string;
size?: number;
}
};

View File

@@ -35,7 +35,10 @@ export class Meta {
* sorts the metaRepoData
*/
public async sortMetaRepoData() {
const stringifiedMetadata = plugins.smartjson.stringify(this.metaRepoData, []);
const stringifiedMetadata = plugins.smartjson.stringify(
this.metaRepoData,
[],
);
this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
}
@@ -45,11 +48,15 @@ export class Meta {
public async readDirectory() {
await this.syncToRemote(true);
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) {
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,
);
// 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) {
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) {
logger.log('info', `cleaning the repository from old directories`);
await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
}
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() {
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);
for (const preExistingFolderArg of preExistingFolders) {
if (
@@ -107,14 +123,18 @@ export class Meta {
projectFolder.startsWith(preExistingFolderArg),
)
) {
const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation(
const response =
await plugins.smartinteraction.SmartInteract.getCliConfirmation(
`Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
true,
);
if (response) {
logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
} 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() {
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) {
await plugins.smartfile.memory.toFs(
JSON.stringify({
@@ -168,7 +190,10 @@ export class Meta {
}),
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(
JSON.stringify({
name: this.dirName,
@@ -176,9 +201,15 @@ export class Meta {
}),
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 {
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(
`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];
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;
@@ -217,7 +250,10 @@ export class Meta {
const existingProject = this.metaRepoData.projects[projectNameArg];
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;
}
@@ -228,7 +264,9 @@ export class Meta {
await this.writeToDisk();
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();
}
}

View File

@@ -16,7 +16,9 @@ export let run = () => {
* create a new project with 'gitzone template [template]'
the following templates exist: ${(() => {
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`;
}
return projects;

View File

@@ -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 pull origin master`);
await smartshellInstance.execStrict(
`cd ${paths.cwd} && git pull origin master`,
);
await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
await provideNoGitFiles();

View File

@@ -16,7 +16,9 @@ export const isTemplate = async (templateNameArg: string) => {
export const getTemplate = async (templateNameArg: string) => {
if (isTemplate(templateNameArg)) {
const localScafTemplate = new plugins.smartscaf.ScafTemplate(getTemplatePath(templateNameArg));
const localScafTemplate = new plugins.smartscaf.ScafTemplate(
getTemplatePath(templateNameArg),
);
await localScafTemplate.readTemplateFromDir();
return localScafTemplate;
} else {
@@ -32,7 +34,8 @@ export const run = async (argvArg: any) => {
const answerBucket = await smartinteract.askQuestion({
type: 'list',
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',
choices: ['npm', 'service', 'wcc', 'website'],
});