Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
d32d47b706 | |||
fd90cfe895 | |||
c48f48fc8b | |||
e21e7f0850 | |||
5f561527f9 | |||
9f5f568c3f | |||
39a31a4304 | |||
b629a7d70b | |||
4003944139 | |||
83d374dffd |
@@ -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
|
||||||
|
|
38
changelog.md
38
changelog.md
@@ -1,5 +1,43 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## 2025-08-07 - 1.16.2 - fix(format)
|
||||||
Fix format command confirmation prompt to correctly check user response
|
Fix format command confirmation prompt to correctly check user response
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.16.2",
|
"version": "1.16.7",
|
||||||
"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",
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '1.16.1',
|
version: '1.16.7',
|
||||||
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',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -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',
|
||||||
});
|
});
|
||||||
|
@@ -29,21 +29,67 @@ export class ChangeCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getManifest(): Promise<ICacheManifest> {
|
async getManifest(): Promise<ICacheManifest> {
|
||||||
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
|
const defaultManifest: ICacheManifest = {
|
||||||
if (!exists) {
|
|
||||||
return {
|
|
||||||
version: this.cacheVersion,
|
version: this.cacheVersion,
|
||||||
lastFormat: 0,
|
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);
|
try {
|
||||||
return JSON.parse(content);
|
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> {
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use atomic write: write to temp file, then move it
|
||||||
|
const tempPath = `${this.manifestPath}.tmp`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write to temporary file
|
||||||
|
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||||
|
await plugins.smartfile.memory.toFs(jsonContent, tempPath);
|
||||||
|
|
||||||
|
// Move temp file to actual manifest (atomic-like operation)
|
||||||
|
// Since smartfile doesn't have rename, we copy and delete
|
||||||
|
await plugins.smartfile.fs.copy(tempPath, this.manifestPath);
|
||||||
|
await plugins.smartfile.fs.remove(tempPath);
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up temp file if it exists
|
||||||
|
try {
|
||||||
|
await plugins.smartfile.fs.remove(tempPath);
|
||||||
|
} catch (removeError) {
|
||||||
|
// Ignore removal errors
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasFileChanged(filePath: string): Promise<boolean> {
|
async hasFileChanged(filePath: string): Promise<boolean> {
|
||||||
@@ -59,7 +105,13 @@ export class ChangeCache {
|
|||||||
|
|
||||||
// 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
|
||||||
@@ -83,7 +135,13 @@ export class ChangeCache {
|
|||||||
|
|
||||||
// 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
|
||||||
@@ -141,4 +199,31 @@ export class ChangeCache {
|
|||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -43,7 +43,7 @@ export class RollbackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ export class RollbackManager {
|
|||||||
|
|
||||||
// Verify backup integrity
|
// Verify backup integrity
|
||||||
const backupPath = this.getBackupPath(operationId, file.path);
|
const backupPath = this.getBackupPath(operationId, file.path);
|
||||||
const backupContent = 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) {
|
||||||
@@ -146,7 +146,7 @@ export class RollbackManager {
|
|||||||
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) {
|
||||||
@@ -185,17 +185,63 @@ export class RollbackManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
|
try {
|
||||||
return JSON.parse(content);
|
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> {
|
private async saveManifest(manifest: { operations: IFormatOperation[] }): 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 rollback manifest structure, cannot save');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use atomic write: write to temp file, then move it
|
||||||
|
const tempPath = `${this.manifestPath}.tmp`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write to temporary file
|
||||||
|
const jsonContent = JSON.stringify(manifest, null, 2);
|
||||||
|
await plugins.smartfile.memory.toFs(jsonContent, tempPath);
|
||||||
|
|
||||||
|
// Move temp file to actual manifest (atomic-like operation)
|
||||||
|
// Since smartfile doesn't have rename, we copy and delete
|
||||||
|
await plugins.smartfile.fs.copy(tempPath, this.manifestPath);
|
||||||
|
await plugins.smartfile.fs.remove(tempPath);
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up temp file if it exists
|
||||||
|
try {
|
||||||
|
await plugins.smartfile.fs.remove(tempPath);
|
||||||
|
} catch (removeError) {
|
||||||
|
// Ignore removal errors
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOperation(operationId: string): Promise<IFormatOperation | null> {
|
private async getOperation(operationId: string): Promise<IFormatOperation | null> {
|
||||||
@@ -215,4 +261,38 @@ export class RollbackManager {
|
|||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -10,13 +10,75 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
// 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
|
// Check which files need formatting
|
||||||
for (const file of files) {
|
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`);
|
||||||
@@ -85,7 +147,7 @@ export class PrettierFormatter extends BaseFormatter {
|
|||||||
|
|
||||||
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');
|
||||||
|
Reference in New Issue
Block a user