@hyperdrive.bot/gut 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -779
- package/bin/run.js +5 -0
- package/package.json +10 -10
- package/bin/run +0 -5
- package/dist/base-command.d.ts +0 -21
- package/dist/base-command.js +0 -110
- package/dist/commands/add.d.ts +0 -13
- package/dist/commands/add.js +0 -73
- package/dist/commands/affected.d.ts +0 -23
- package/dist/commands/affected.js +0 -326
- package/dist/commands/audit.d.ts +0 -33
- package/dist/commands/audit.js +0 -593
- package/dist/commands/back.d.ts +0 -6
- package/dist/commands/back.js +0 -29
- package/dist/commands/commit.d.ts +0 -11
- package/dist/commands/commit.js +0 -113
- package/dist/commands/context.d.ts +0 -6
- package/dist/commands/context.js +0 -36
- package/dist/commands/contexts.d.ts +0 -7
- package/dist/commands/contexts.js +0 -92
- package/dist/commands/deps.d.ts +0 -10
- package/dist/commands/deps.js +0 -104
- package/dist/commands/entity/add.d.ts +0 -16
- package/dist/commands/entity/add.js +0 -105
- package/dist/commands/entity/clone-all.d.ts +0 -17
- package/dist/commands/entity/clone-all.js +0 -135
- package/dist/commands/entity/clone.d.ts +0 -15
- package/dist/commands/entity/clone.js +0 -109
- package/dist/commands/entity/list.d.ts +0 -11
- package/dist/commands/entity/list.js +0 -82
- package/dist/commands/entity/remove.d.ts +0 -12
- package/dist/commands/entity/remove.js +0 -58
- package/dist/commands/focus.d.ts +0 -19
- package/dist/commands/focus.js +0 -139
- package/dist/commands/graph.d.ts +0 -18
- package/dist/commands/graph.js +0 -238
- package/dist/commands/init.d.ts +0 -11
- package/dist/commands/init.js +0 -84
- package/dist/commands/insights.d.ts +0 -21
- package/dist/commands/insights.js +0 -434
- package/dist/commands/patterns.d.ts +0 -40
- package/dist/commands/patterns.js +0 -412
- package/dist/commands/pull.d.ts +0 -11
- package/dist/commands/pull.js +0 -121
- package/dist/commands/push.d.ts +0 -11
- package/dist/commands/push.js +0 -101
- package/dist/commands/quick-setup.d.ts +0 -20
- package/dist/commands/quick-setup.js +0 -422
- package/dist/commands/recent.d.ts +0 -9
- package/dist/commands/recent.js +0 -55
- package/dist/commands/related.d.ts +0 -23
- package/dist/commands/related.js +0 -257
- package/dist/commands/repos.d.ts +0 -14
- package/dist/commands/repos.js +0 -185
- package/dist/commands/stack.d.ts +0 -10
- package/dist/commands/stack.js +0 -83
- package/dist/commands/status.d.ts +0 -14
- package/dist/commands/status.js +0 -246
- package/dist/commands/sync.d.ts +0 -11
- package/dist/commands/sync.js +0 -142
- package/dist/commands/unfocus.d.ts +0 -6
- package/dist/commands/unfocus.js +0 -23
- package/dist/commands/used-by.d.ts +0 -10
- package/dist/commands/used-by.js +0 -111
- package/dist/commands/workspace.d.ts +0 -20
- package/dist/commands/workspace.js +0 -365
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -5
- package/dist/models/entity.model.d.ts +0 -81
- package/dist/models/entity.model.js +0 -2
- package/dist/services/config.service.d.ts +0 -34
- package/dist/services/config.service.js +0 -230
- package/dist/services/entity.service.d.ts +0 -19
- package/dist/services/entity.service.js +0 -130
- package/dist/services/focus.service.d.ts +0 -70
- package/dist/services/focus.service.js +0 -587
- package/dist/services/git.service.d.ts +0 -37
- package/dist/services/git.service.js +0 -180
- package/dist/utils/display.d.ts +0 -25
- package/dist/utils/display.js +0 -150
- package/dist/utils/filesystem.d.ts +0 -32
- package/dist/utils/filesystem.js +0 -220
- package/dist/utils/index.d.ts +0 -13
- package/dist/utils/index.js +0 -18
- package/dist/utils/validation.d.ts +0 -22
- package/dist/utils/validation.js +0 -196
- package/oclif.manifest.json +0 -1463
package/bin/run.js
ADDED
package/package.json
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperdrive.bot/gut",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Git Unified Tooling - Enhanced git with workspace intelligence for entity-based organization",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"gut": "./bin/run"
|
|
8
|
+
"gut": "./bin/run.js"
|
|
9
9
|
},
|
|
10
|
+
"type": "module",
|
|
10
11
|
"files": [
|
|
11
12
|
"/bin",
|
|
12
13
|
"/dist",
|
|
13
14
|
"/oclif.manifest.json"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
|
-
"build": "
|
|
17
|
+
"build": "rm -rf dist && tsc -b",
|
|
17
18
|
"dev": "node ./bin/dev",
|
|
18
|
-
"lint": "eslint .
|
|
19
|
-
"postpack": "
|
|
20
|
-
"prepack": "npm run build && oclif manifest",
|
|
21
|
-
"test": "
|
|
22
|
-
"version": "oclif readme && git add README.md"
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"postpack": "rm -f oclif.manifest.json",
|
|
21
|
+
"prepack": "npm run build && npx oclif manifest || true",
|
|
22
|
+
"test": "npm run build",
|
|
23
|
+
"version": "npx oclif readme && git add README.md || true"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"git",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"author": "DevSquad",
|
|
32
33
|
"license": "MIT",
|
|
33
34
|
"engines": {
|
|
34
|
-
"node": ">=14.
|
|
35
|
+
"node": ">=14.18.0"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@oclif/core": "^4.5.2",
|
|
@@ -51,7 +52,6 @@
|
|
|
51
52
|
"eslint-config-oclif": "^6.0.101",
|
|
52
53
|
"eslint-config-oclif-typescript": "^3.1.14",
|
|
53
54
|
"oclif": "^4.22.16",
|
|
54
|
-
"shx": "^0.4.0",
|
|
55
55
|
"ts-node": "^10.9.2",
|
|
56
56
|
"typescript": "^5.9.2"
|
|
57
57
|
},
|
package/bin/run
DELETED
package/dist/base-command.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
import { ConfigService } from './services/config.service';
|
|
3
|
-
import { FocusService } from './services/focus.service';
|
|
4
|
-
import { EntityService } from './services/entity.service';
|
|
5
|
-
import { GitService } from './services/git.service';
|
|
6
|
-
import { Entity } from './models/entity.model';
|
|
7
|
-
export declare abstract class BaseCommand extends Command {
|
|
8
|
-
protected configService: ConfigService;
|
|
9
|
-
protected focusService: FocusService;
|
|
10
|
-
protected entityService: EntityService;
|
|
11
|
-
protected gitService: GitService;
|
|
12
|
-
init(): Promise<void>;
|
|
13
|
-
protected get requiresInit(): boolean;
|
|
14
|
-
protected withFocus<T>(callback: (entities: Entity[]) => Promise<T>): Promise<T>;
|
|
15
|
-
protected withSpinner<T>(text: string, callback: () => Promise<T>): Promise<T>;
|
|
16
|
-
protected printEntityList(entities: Entity[]): void;
|
|
17
|
-
protected getTypeEmoji(type: string): string;
|
|
18
|
-
protected getStatusIcon(hasChanges: boolean): string;
|
|
19
|
-
protected formatPath(path: string): string;
|
|
20
|
-
}
|
|
21
|
-
export default BaseCommand;
|
package/dist/base-command.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BaseCommand = void 0;
|
|
4
|
-
const core_1 = require("@oclif/core");
|
|
5
|
-
const config_service_1 = require("./services/config.service");
|
|
6
|
-
const focus_service_1 = require("./services/focus.service");
|
|
7
|
-
const entity_service_1 = require("./services/entity.service");
|
|
8
|
-
const git_service_1 = require("./services/git.service");
|
|
9
|
-
class BaseCommand extends core_1.Command {
|
|
10
|
-
configService;
|
|
11
|
-
focusService;
|
|
12
|
-
entityService;
|
|
13
|
-
gitService;
|
|
14
|
-
async init() {
|
|
15
|
-
await super.init();
|
|
16
|
-
// Initialize services
|
|
17
|
-
this.configService = new config_service_1.ConfigService();
|
|
18
|
-
this.entityService = new entity_service_1.EntityService(this.configService);
|
|
19
|
-
this.focusService = new focus_service_1.FocusService(this.configService);
|
|
20
|
-
this.gitService = new git_service_1.GitService();
|
|
21
|
-
// Check workspace initialization for commands that require it
|
|
22
|
-
if (this.requiresInit && !this.configService.isInitialized()) {
|
|
23
|
-
this.error('Workspace not initialized. Run "gut init" first.');
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
get requiresInit() {
|
|
27
|
-
// Override in commands that don't require initialization
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
async withFocus(callback) {
|
|
31
|
-
const entities = await this.focusService.getFocusedEntities();
|
|
32
|
-
if (entities.length === 0) {
|
|
33
|
-
this.error('No entities focused. Use "gut focus <entity>" first.');
|
|
34
|
-
}
|
|
35
|
-
return callback(entities);
|
|
36
|
-
}
|
|
37
|
-
async withSpinner(text, callback) {
|
|
38
|
-
// Note: oclif doesn't have built-in spinner, we'll use ora later
|
|
39
|
-
this.log(text);
|
|
40
|
-
try {
|
|
41
|
-
const result = await callback();
|
|
42
|
-
this.log('✓ Done');
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
this.log('✗ Failed');
|
|
47
|
-
throw error;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
printEntityList(entities) {
|
|
51
|
-
if (entities.length === 0) {
|
|
52
|
-
this.log('No entities configured');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
// Group by type
|
|
56
|
-
const byType = entities.reduce((acc, entity) => {
|
|
57
|
-
if (!acc[entity.type]) {
|
|
58
|
-
acc[entity.type] = [];
|
|
59
|
-
}
|
|
60
|
-
acc[entity.type].push(entity);
|
|
61
|
-
return acc;
|
|
62
|
-
}, {});
|
|
63
|
-
// Display grouped
|
|
64
|
-
for (const [type, typeEntities] of Object.entries(byType)) {
|
|
65
|
-
this.log(`\n${this.getTypeEmoji(type)} ${type.charAt(0).toUpperCase() + type.slice(1)} entities:`);
|
|
66
|
-
for (const entity of typeEntities) {
|
|
67
|
-
this.log(` • ${entity.name} (${entity.path})`);
|
|
68
|
-
if (entity.description) {
|
|
69
|
-
this.log(` ${entity.description}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
getTypeEmoji(type) {
|
|
75
|
-
switch (type) {
|
|
76
|
-
case 'client':
|
|
77
|
-
return '🏢';
|
|
78
|
-
case 'prospect':
|
|
79
|
-
return '🎯';
|
|
80
|
-
case 'company':
|
|
81
|
-
return '🏛️';
|
|
82
|
-
case 'initiative':
|
|
83
|
-
return '🚀';
|
|
84
|
-
case 'system':
|
|
85
|
-
return '⚙️';
|
|
86
|
-
case 'delivery':
|
|
87
|
-
return '📦';
|
|
88
|
-
case 'module':
|
|
89
|
-
return '📚';
|
|
90
|
-
case 'service':
|
|
91
|
-
return '🔧';
|
|
92
|
-
case 'tool':
|
|
93
|
-
return '🛠️';
|
|
94
|
-
default:
|
|
95
|
-
return '📁';
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
getStatusIcon(hasChanges) {
|
|
99
|
-
return hasChanges ? '●' : '✓';
|
|
100
|
-
}
|
|
101
|
-
formatPath(path) {
|
|
102
|
-
const workspaceRoot = this.configService.getWorkspaceRoot();
|
|
103
|
-
if (path.startsWith(workspaceRoot)) {
|
|
104
|
-
return path.substring(workspaceRoot.length + 1);
|
|
105
|
-
}
|
|
106
|
-
return path;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
exports.BaseCommand = BaseCommand;
|
|
110
|
-
exports.default = BaseCommand;
|
package/dist/commands/add.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { BaseCommand } from '../base-command';
|
|
2
|
-
export default class Add extends BaseCommand {
|
|
3
|
-
static description: string;
|
|
4
|
-
static examples: string[];
|
|
5
|
-
static flags: {
|
|
6
|
-
all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
-
patch: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
};
|
|
9
|
-
static args: {
|
|
10
|
-
path: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
-
};
|
|
12
|
-
run(): Promise<void>;
|
|
13
|
-
}
|
package/dist/commands/add.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const core_1 = require("@oclif/core");
|
|
5
|
-
const base_command_1 = require("../base-command");
|
|
6
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
8
|
-
class Add extends base_command_1.BaseCommand {
|
|
9
|
-
static description = 'Stage changes in focused entities';
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %>',
|
|
12
|
-
'<%= config.bin %> <%= command.id %> .',
|
|
13
|
-
'<%= config.bin %> <%= command.id %> src/',
|
|
14
|
-
'<%= config.bin %> <%= command.id %> --all',
|
|
15
|
-
];
|
|
16
|
-
static flags = {
|
|
17
|
-
all: core_1.Flags.boolean({
|
|
18
|
-
char: 'A',
|
|
19
|
-
description: 'Stage all changes (equivalent to git add -A)',
|
|
20
|
-
default: false,
|
|
21
|
-
}),
|
|
22
|
-
patch: core_1.Flags.boolean({
|
|
23
|
-
char: 'p',
|
|
24
|
-
description: 'Interactive staging',
|
|
25
|
-
default: false,
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
28
|
-
static args = {
|
|
29
|
-
path: core_1.Args.string({
|
|
30
|
-
description: 'Path(s) to stage',
|
|
31
|
-
required: false,
|
|
32
|
-
default: '.',
|
|
33
|
-
}),
|
|
34
|
-
};
|
|
35
|
-
async run() {
|
|
36
|
-
const { args, flags } = await this.parse(Add);
|
|
37
|
-
const focusedEntities = await this.focusService.getFocusedEntities();
|
|
38
|
-
if (focusedEntities.length === 0) {
|
|
39
|
-
this.error('No entities are focused. Use "gut focus <entity>" first.');
|
|
40
|
-
}
|
|
41
|
-
this.log(chalk_1.default.bold('\n📝 Staging changes in focused entities\n'));
|
|
42
|
-
for (const entity of focusedEntities) {
|
|
43
|
-
const spinner = (0, ora_1.default)(`Staging in ${chalk_1.default.cyan(entity.name)}`).start();
|
|
44
|
-
try {
|
|
45
|
-
let files;
|
|
46
|
-
if (flags.all) {
|
|
47
|
-
files = ['-A'];
|
|
48
|
-
}
|
|
49
|
-
else if (flags.patch) {
|
|
50
|
-
files = ['-p', args.path];
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
files = [args.path];
|
|
54
|
-
}
|
|
55
|
-
await this.gitService.add(entity.path, files);
|
|
56
|
-
// Get status to check what was staged
|
|
57
|
-
const status = await this.gitService.getStatus(entity.path);
|
|
58
|
-
const staged = status.changes.length;
|
|
59
|
-
if (staged > 0) {
|
|
60
|
-
spinner.succeed(chalk_1.default.green(`✓ ${entity.name}: ${staged} file(s) staged`));
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
spinner.info(chalk_1.default.yellow(`${entity.name}: No changes to stage`));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
spinner.fail(chalk_1.default.red(`✗ ${entity.name}: ${error.message}`));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
this.log(chalk_1.default.dim('\nTip: Use "gut commit" to commit staged changes'));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
exports.default = Add;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import BaseCommand from '../base-command';
|
|
2
|
-
export default class Affected extends BaseCommand {
|
|
3
|
-
static description: string;
|
|
4
|
-
static examples: string[];
|
|
5
|
-
static flags: {
|
|
6
|
-
since: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
-
'include-tests': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
'include-docs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
-
verbose: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
};
|
|
11
|
-
static args: {
|
|
12
|
-
entity: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
13
|
-
};
|
|
14
|
-
run(): Promise<void>;
|
|
15
|
-
private getChangedFiles;
|
|
16
|
-
private analyzeAffected;
|
|
17
|
-
private checkDependencies;
|
|
18
|
-
private checkAPIChanges;
|
|
19
|
-
private checkConfigChanges;
|
|
20
|
-
private checkSchemaChanges;
|
|
21
|
-
private getEntityFiles;
|
|
22
|
-
private printAffected;
|
|
23
|
-
}
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const core_1 = require("@oclif/core");
|
|
5
|
-
const base_command_1 = tslib_1.__importDefault(require("../base-command"));
|
|
6
|
-
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
-
const path = tslib_1.__importStar(require("path"));
|
|
8
|
-
const child_process_1 = require("child_process");
|
|
9
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
10
|
-
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
11
|
-
class Affected extends base_command_1.default {
|
|
12
|
-
static description = 'Detect entities potentially affected by changes in current focus';
|
|
13
|
-
static examples = [
|
|
14
|
-
'<%= config.bin %> <%= command.id %>',
|
|
15
|
-
'<%= config.bin %> <%= command.id %> my-app',
|
|
16
|
-
'<%= config.bin %> <%= command.id %> --since HEAD~5',
|
|
17
|
-
'<%= config.bin %> <%= command.id %> --include-tests',
|
|
18
|
-
];
|
|
19
|
-
static flags = {
|
|
20
|
-
since: core_1.Flags.string({
|
|
21
|
-
char: 's',
|
|
22
|
-
description: 'Git reference to compare against',
|
|
23
|
-
default: 'HEAD~1',
|
|
24
|
-
}),
|
|
25
|
-
'include-tests': core_1.Flags.boolean({
|
|
26
|
-
description: 'Include test file changes in analysis',
|
|
27
|
-
default: false,
|
|
28
|
-
}),
|
|
29
|
-
'include-docs': core_1.Flags.boolean({
|
|
30
|
-
description: 'Include documentation changes in analysis',
|
|
31
|
-
default: false,
|
|
32
|
-
}),
|
|
33
|
-
verbose: core_1.Flags.boolean({
|
|
34
|
-
char: 'v',
|
|
35
|
-
description: 'Show detailed analysis',
|
|
36
|
-
default: false,
|
|
37
|
-
}),
|
|
38
|
-
};
|
|
39
|
-
static args = {
|
|
40
|
-
entity: core_1.Args.string({
|
|
41
|
-
name: 'entity',
|
|
42
|
-
required: false,
|
|
43
|
-
description: 'Entity to analyze (uses current focus if not provided)',
|
|
44
|
-
}),
|
|
45
|
-
};
|
|
46
|
-
async run() {
|
|
47
|
-
const { args, flags } = await this.parse(Affected);
|
|
48
|
-
const { since, 'include-tests': includeTests, 'include-docs': includeDocs, verbose } = flags;
|
|
49
|
-
const spinner = (0, ora_1.default)();
|
|
50
|
-
try {
|
|
51
|
-
let sourceEntities = [];
|
|
52
|
-
if (args.entity) {
|
|
53
|
-
const entity = await this.entityService.getEntity(args.entity);
|
|
54
|
-
if (!entity) {
|
|
55
|
-
this.error(`Entity '${args.entity}' not found`);
|
|
56
|
-
}
|
|
57
|
-
sourceEntities = [entity];
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
const focus = await this.focusService.getCurrentFocus();
|
|
61
|
-
if (!focus || !focus.entities || focus.entities.length === 0) {
|
|
62
|
-
this.error('No entity specified and no current focus');
|
|
63
|
-
}
|
|
64
|
-
const entities = await Promise.all(focus.entities.map((e) => this.entityService.getEntity(e.name)));
|
|
65
|
-
sourceEntities = entities.filter(e => e !== null);
|
|
66
|
-
}
|
|
67
|
-
spinner.start('Analyzing changes and dependencies');
|
|
68
|
-
const changedFiles = await this.getChangedFiles(sourceEntities, since, includeTests, includeDocs);
|
|
69
|
-
if (changedFiles.length === 0) {
|
|
70
|
-
spinner.succeed('No changes detected');
|
|
71
|
-
this.log(chalk_1.default.yellow('No file changes found since ' + since));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const allEntities = await this.entityService.listEntities();
|
|
75
|
-
const otherEntities = allEntities.filter(e => !sourceEntities.some(se => se.name === e.name));
|
|
76
|
-
const affected = await this.analyzeAffected(sourceEntities, otherEntities, changedFiles);
|
|
77
|
-
spinner.succeed(`Analysis complete: ${affected.length} entities potentially affected`);
|
|
78
|
-
if (affected.length === 0) {
|
|
79
|
-
this.log(chalk_1.default.green('No other entities appear to be affected by the changes'));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this.printAffected(affected, changedFiles, verbose);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
spinner.fail();
|
|
86
|
-
this.error(error.message);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
async getChangedFiles(entities, since, includeTests, includeDocs) {
|
|
90
|
-
const changedFiles = [];
|
|
91
|
-
for (const entity of entities) {
|
|
92
|
-
const entityPath = path.resolve(entity.path);
|
|
93
|
-
if (!fs.existsSync(path.join(entityPath, '.git'))) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const diff = (0, child_process_1.execSync)(`git diff ${since} --name-only`, {
|
|
98
|
-
cwd: entityPath,
|
|
99
|
-
encoding: 'utf-8',
|
|
100
|
-
stdio: 'pipe',
|
|
101
|
-
}).toString();
|
|
102
|
-
const files = diff.split('\n')
|
|
103
|
-
.filter(f => f.trim())
|
|
104
|
-
.filter(f => {
|
|
105
|
-
if (!includeTests && (f.includes('test') || f.includes('spec'))) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
if (!includeDocs && (f.endsWith('.md') || f.includes('docs/'))) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
112
|
-
})
|
|
113
|
-
.map(f => path.join(entityPath, f));
|
|
114
|
-
changedFiles.push(...files);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
// Ignore git errors
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return [...new Set(changedFiles)];
|
|
121
|
-
}
|
|
122
|
-
async analyzeAffected(sourceEntities, targetEntities, changedFiles) {
|
|
123
|
-
const affected = [];
|
|
124
|
-
for (const target of targetEntities) {
|
|
125
|
-
const reasons = [];
|
|
126
|
-
let confidence = 'low';
|
|
127
|
-
// Check direct dependencies
|
|
128
|
-
const depReason = await this.checkDependencies(sourceEntities, target);
|
|
129
|
-
if (depReason) {
|
|
130
|
-
reasons.push(depReason);
|
|
131
|
-
confidence = 'high';
|
|
132
|
-
}
|
|
133
|
-
// Check for API changes
|
|
134
|
-
const apiChanges = this.checkAPIChanges(changedFiles, target);
|
|
135
|
-
if (apiChanges) {
|
|
136
|
-
reasons.push(apiChanges);
|
|
137
|
-
if (confidence === 'low')
|
|
138
|
-
confidence = 'medium';
|
|
139
|
-
}
|
|
140
|
-
// Check for shared configuration
|
|
141
|
-
const configChanges = this.checkConfigChanges(changedFiles, target);
|
|
142
|
-
if (configChanges) {
|
|
143
|
-
reasons.push(configChanges);
|
|
144
|
-
if (confidence === 'low')
|
|
145
|
-
confidence = 'medium';
|
|
146
|
-
}
|
|
147
|
-
// Check for database/schema changes
|
|
148
|
-
const schemaChanges = this.checkSchemaChanges(changedFiles, target);
|
|
149
|
-
if (schemaChanges) {
|
|
150
|
-
reasons.push(schemaChanges);
|
|
151
|
-
confidence = 'high';
|
|
152
|
-
}
|
|
153
|
-
if (reasons.length > 0) {
|
|
154
|
-
affected.push({ entity: target, reason: reasons, confidence });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Sort by confidence
|
|
158
|
-
affected.sort((a, b) => {
|
|
159
|
-
const confidenceOrder = { high: 3, medium: 2, low: 1 };
|
|
160
|
-
return confidenceOrder[b.confidence] - confidenceOrder[a.confidence];
|
|
161
|
-
});
|
|
162
|
-
return affected;
|
|
163
|
-
}
|
|
164
|
-
async checkDependencies(sources, target) {
|
|
165
|
-
const targetPkgPath = path.join(target.path, 'package.json');
|
|
166
|
-
if (!fs.existsSync(targetPkgPath)) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
try {
|
|
170
|
-
const targetPkg = JSON.parse(fs.readFileSync(targetPkgPath, 'utf-8'));
|
|
171
|
-
const dependencies = {
|
|
172
|
-
...targetPkg.dependencies,
|
|
173
|
-
...targetPkg.devDependencies,
|
|
174
|
-
...targetPkg.peerDependencies,
|
|
175
|
-
};
|
|
176
|
-
for (const source of sources) {
|
|
177
|
-
const sourcePkgPath = path.join(source.path, 'package.json');
|
|
178
|
-
if (fs.existsSync(sourcePkgPath)) {
|
|
179
|
-
const sourcePkg = JSON.parse(fs.readFileSync(sourcePkgPath, 'utf-8'));
|
|
180
|
-
if (sourcePkg.name && dependencies[sourcePkg.name]) {
|
|
181
|
-
return `Depends on ${sourcePkg.name}`;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
// Ignore parsing errors
|
|
188
|
-
}
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
checkAPIChanges(changedFiles, target) {
|
|
192
|
-
const apiPatterns = [
|
|
193
|
-
/api\//i,
|
|
194
|
-
/routes\//i,
|
|
195
|
-
/controllers\//i,
|
|
196
|
-
/services\//i,
|
|
197
|
-
/endpoints\//i,
|
|
198
|
-
/graphql\//i,
|
|
199
|
-
];
|
|
200
|
-
const apiFiles = changedFiles.filter(f => apiPatterns.some(pattern => pattern.test(f)));
|
|
201
|
-
if (apiFiles.length > 0) {
|
|
202
|
-
// Check if target might consume these APIs
|
|
203
|
-
const targetFiles = this.getEntityFiles(target.path);
|
|
204
|
-
const hasApiConsumption = targetFiles.some(f => f.includes('api') || f.includes('client') || f.includes('service'));
|
|
205
|
-
if (hasApiConsumption) {
|
|
206
|
-
return `API changes detected (${apiFiles.length} files)`;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
checkConfigChanges(changedFiles, target) {
|
|
212
|
-
const configPatterns = [
|
|
213
|
-
/config\./i,
|
|
214
|
-
/\.env/,
|
|
215
|
-
/settings\./i,
|
|
216
|
-
/constants\./i,
|
|
217
|
-
];
|
|
218
|
-
const configFiles = changedFiles.filter(f => configPatterns.some(pattern => pattern.test(path.basename(f))));
|
|
219
|
-
if (configFiles.length > 0) {
|
|
220
|
-
return `Configuration changes detected (${configFiles.length} files)`;
|
|
221
|
-
}
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
checkSchemaChanges(changedFiles, target) {
|
|
225
|
-
const schemaPatterns = [
|
|
226
|
-
/schema\./i,
|
|
227
|
-
/migration/i,
|
|
228
|
-
/\.sql$/,
|
|
229
|
-
/models?\//i,
|
|
230
|
-
/entities\//i,
|
|
231
|
-
];
|
|
232
|
-
const schemaFiles = changedFiles.filter(f => schemaPatterns.some(pattern => pattern.test(f)));
|
|
233
|
-
if (schemaFiles.length > 0) {
|
|
234
|
-
// Check if target uses database
|
|
235
|
-
const targetFiles = this.getEntityFiles(target.path);
|
|
236
|
-
const hasDatabase = targetFiles.some(f => f.includes('model') || f.includes('entity') || f.includes('schema') || f.includes('database'));
|
|
237
|
-
if (hasDatabase) {
|
|
238
|
-
return `Database/Schema changes detected (${schemaFiles.length} files)`;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
getEntityFiles(entityPath) {
|
|
244
|
-
const files = [];
|
|
245
|
-
if (!fs.existsSync(entityPath))
|
|
246
|
-
return files;
|
|
247
|
-
const walkDir = (dir) => {
|
|
248
|
-
try {
|
|
249
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
250
|
-
for (const entry of entries) {
|
|
251
|
-
if (entry.name === 'node_modules' || entry.name === '.git')
|
|
252
|
-
continue;
|
|
253
|
-
const fullPath = path.join(dir, entry.name);
|
|
254
|
-
if (entry.isDirectory()) {
|
|
255
|
-
walkDir(fullPath);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
files.push(path.relative(entityPath, fullPath));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
// Ignore permission errors
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
walkDir(entityPath);
|
|
267
|
-
return files;
|
|
268
|
-
}
|
|
269
|
-
printAffected(affected, changedFiles, verbose) {
|
|
270
|
-
this.log('');
|
|
271
|
-
this.log(chalk_1.default.bold(`Changed files: ${changedFiles.length}`));
|
|
272
|
-
if (verbose) {
|
|
273
|
-
this.log(chalk_1.default.dim('Changed:'));
|
|
274
|
-
changedFiles.slice(0, 5).forEach(f => {
|
|
275
|
-
this.log(chalk_1.default.dim(` - ${path.basename(f)}`));
|
|
276
|
-
});
|
|
277
|
-
if (changedFiles.length > 5) {
|
|
278
|
-
this.log(chalk_1.default.dim(` ... and ${changedFiles.length - 5} more`));
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
this.log('');
|
|
282
|
-
this.log(chalk_1.default.bold('Potentially affected entities:'));
|
|
283
|
-
this.log('');
|
|
284
|
-
const groupedByConfidence = {
|
|
285
|
-
high: affected.filter(a => a.confidence === 'high'),
|
|
286
|
-
medium: affected.filter(a => a.confidence === 'medium'),
|
|
287
|
-
low: affected.filter(a => a.confidence === 'low'),
|
|
288
|
-
};
|
|
289
|
-
if (groupedByConfidence.high.length > 0) {
|
|
290
|
-
this.log(chalk_1.default.red.bold('High confidence:'));
|
|
291
|
-
groupedByConfidence.high.forEach(a => {
|
|
292
|
-
this.log(` ${chalk_1.default.red('●')} ${chalk_1.default.bold(a.entity.name)}`);
|
|
293
|
-
a.reason.forEach(r => {
|
|
294
|
-
this.log(chalk_1.default.dim(` - ${r}`));
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
this.log('');
|
|
298
|
-
}
|
|
299
|
-
if (groupedByConfidence.medium.length > 0) {
|
|
300
|
-
this.log(chalk_1.default.yellow.bold('Medium confidence:'));
|
|
301
|
-
groupedByConfidence.medium.forEach(a => {
|
|
302
|
-
this.log(` ${chalk_1.default.yellow('●')} ${chalk_1.default.bold(a.entity.name)}`);
|
|
303
|
-
if (verbose) {
|
|
304
|
-
a.reason.forEach(r => {
|
|
305
|
-
this.log(chalk_1.default.dim(` - ${r}`));
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
this.log('');
|
|
310
|
-
}
|
|
311
|
-
if (groupedByConfidence.low.length > 0) {
|
|
312
|
-
this.log(chalk_1.default.dim('Low confidence:'));
|
|
313
|
-
groupedByConfidence.low.forEach(a => {
|
|
314
|
-
this.log(` ${chalk_1.default.gray('●')} ${a.entity.name}`);
|
|
315
|
-
if (verbose) {
|
|
316
|
-
a.reason.forEach(r => {
|
|
317
|
-
this.log(chalk_1.default.dim(` - ${r}`));
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
this.log('');
|
|
323
|
-
this.log(chalk_1.default.dim('Tip: Use --verbose flag for detailed analysis'));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
exports.default = Affected;
|
package/dist/commands/audit.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { BaseCommand } from '../base-command';
|
|
2
|
-
export default class Audit extends BaseCommand {
|
|
3
|
-
static description: string;
|
|
4
|
-
static examples: string[];
|
|
5
|
-
static flags: {
|
|
6
|
-
entity: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
-
security: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
-
compliance: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
-
access: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
changes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
-
};
|
|
13
|
-
run(): Promise<void>;
|
|
14
|
-
private performAudit;
|
|
15
|
-
private auditEntity;
|
|
16
|
-
private auditMetadata;
|
|
17
|
-
private auditGitRepository;
|
|
18
|
-
private auditFileStructure;
|
|
19
|
-
private performSecurityAudit;
|
|
20
|
-
private performComplianceAudit;
|
|
21
|
-
private performAccessAudit;
|
|
22
|
-
private displayAuditResults;
|
|
23
|
-
private displayEntityAudit;
|
|
24
|
-
private displayRecommendations;
|
|
25
|
-
private getAuditTypes;
|
|
26
|
-
private hasSpecificAuditType;
|
|
27
|
-
private calculateMetadataCompleteness;
|
|
28
|
-
private getRequiredMetadataFields;
|
|
29
|
-
private hasNestedField;
|
|
30
|
-
private getExpectedFiles;
|
|
31
|
-
private calculateStructureScore;
|
|
32
|
-
private getAllFiles;
|
|
33
|
-
}
|