@grafema/cli 0.2.0-beta → 0.2.2-beta

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/dist/cli.js CHANGED
@@ -3,6 +3,9 @@
3
3
  * @grafema/cli - CLI for Grafema code analysis toolkit
4
4
  */
5
5
  import { Command } from 'commander';
6
+ import { readFileSync } from 'fs';
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
6
9
  import { initCommand } from './commands/init.js';
7
10
  import { analyzeCommand } from './commands/analyze.js';
8
11
  import { overviewCommand } from './commands/overview.js';
@@ -20,11 +23,14 @@ import { coverageCommand } from './commands/coverage.js';
20
23
  import { doctorCommand } from './commands/doctor.js';
21
24
  import { schemaCommand } from './commands/schema.js';
22
25
  import { explainCommand } from './commands/explain.js';
26
+ // Read version from package.json
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
23
29
  const program = new Command();
24
30
  program
25
31
  .name('grafema')
26
32
  .description('Grafema code analysis CLI')
27
- .version('0.1.0-alpha.1');
33
+ .version(pkg.version);
28
34
  // Commands in logical order
29
35
  program.addCommand(initCommand);
30
36
  program.addCommand(analyzeCommand);
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiIpC,eAAO,MAAM,cAAc,SAmKvB,CAAC"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgLpC,eAAO,MAAM,cAAc,SAqKvB,CAAC"}
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { Command } from 'commander';
5
5
  import { resolve, join } from 'path';
6
- import { existsSync, mkdirSync } from 'fs';
6
+ import { existsSync, mkdirSync, readdirSync } from 'fs';
7
+ import { pathToFileURL } from 'url';
7
8
  import { Orchestrator, RFDBServerBackend, DiagnosticReporter, DiagnosticWriter, createLogger, loadConfig,
8
9
  // Discovery
9
10
  SimpleProjectDiscovery, MonorepoServiceDiscovery, WorkspaceDiscovery,
@@ -55,13 +56,47 @@ const BUILTIN_PLUGINS = {
55
56
  TypeScriptDeadCodeValidator: () => new TypeScriptDeadCodeValidator(),
56
57
  BrokenImportValidator: () => new BrokenImportValidator(),
57
58
  };
58
- function createPlugins(config) {
59
+ /**
60
+ * Load custom plugins from .grafema/plugins/ directory
61
+ */
62
+ async function loadCustomPlugins(projectPath, log) {
63
+ const pluginsDir = join(projectPath, '.grafema', 'plugins');
64
+ if (!existsSync(pluginsDir)) {
65
+ return {};
66
+ }
67
+ const customPlugins = {};
68
+ try {
69
+ const files = readdirSync(pluginsDir).filter((f) => f.endsWith('.js') || f.endsWith('.mjs'));
70
+ for (const file of files) {
71
+ try {
72
+ const pluginPath = join(pluginsDir, file);
73
+ const pluginUrl = pathToFileURL(pluginPath).href;
74
+ const module = await import(pluginUrl);
75
+ const PluginClass = module.default || module[file.replace(/\.(m?js)$/, '')];
76
+ if (PluginClass && typeof PluginClass === 'function') {
77
+ const pluginName = PluginClass.name || file.replace(/\.(m?js)$/, '');
78
+ customPlugins[pluginName] = () => new PluginClass();
79
+ log(`Loaded custom plugin: ${pluginName}`);
80
+ }
81
+ }
82
+ catch (err) {
83
+ console.warn(`Failed to load plugin ${file}: ${err.message}`);
84
+ }
85
+ }
86
+ }
87
+ catch (err) {
88
+ console.warn(`Error loading custom plugins: ${err.message}`);
89
+ }
90
+ return customPlugins;
91
+ }
92
+ function createPlugins(config, customPlugins = {}) {
59
93
  const plugins = [];
60
94
  const phases = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
61
95
  for (const phase of phases) {
62
96
  const names = config[phase] || [];
63
97
  for (const name of names) {
64
- const factory = BUILTIN_PLUGINS[name];
98
+ // Check built-in first, then custom
99
+ const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
65
100
  if (factory) {
66
101
  plugins.push(factory());
67
102
  }
@@ -137,7 +172,9 @@ Examples:
137
172
  log(` - ${svc.name}: ${svc.path}${entry}`);
138
173
  }
139
174
  }
140
- const plugins = createPlugins(config.plugins);
175
+ // Load custom plugins from .grafema/plugins/
176
+ const customPlugins = await loadCustomPlugins(projectPath, log);
177
+ const plugins = createPlugins(config.plugins, customPlugins);
141
178
  log(`Loaded ${plugins.length} plugins`);
142
179
  // Resolve strict mode: CLI flag overrides config
143
180
  const strictMode = options.strict ?? config.strict ?? false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafema/cli",
3
- "version": "0.2.0-beta",
3
+ "version": "0.2.2-beta",
4
4
  "description": "CLI for Grafema code analysis toolkit",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -37,8 +37,8 @@
37
37
  "ink-text-input": "^6.0.0",
38
38
  "react": "^19.2.3",
39
39
  "yaml": "^2.8.2",
40
- "@grafema/core": "0.2.0-beta",
41
- "@grafema/types": "0.2.0-beta"
40
+ "@grafema/core": "0.2.1-beta",
41
+ "@grafema/types": "0.2.1-beta"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^25.0.8",
package/src/cli.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  */
5
5
 
6
6
  import { Command } from 'commander';
7
+ import { readFileSync } from 'fs';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
7
10
  import { initCommand } from './commands/init.js';
8
11
  import { analyzeCommand } from './commands/analyze.js';
9
12
  import { overviewCommand } from './commands/overview.js';
@@ -22,12 +25,16 @@ import { doctorCommand } from './commands/doctor.js';
22
25
  import { schemaCommand } from './commands/schema.js';
23
26
  import { explainCommand } from './commands/explain.js';
24
27
 
28
+ // Read version from package.json
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
31
+
25
32
  const program = new Command();
26
33
 
27
34
  program
28
35
  .name('grafema')
29
36
  .description('Grafema code analysis CLI')
30
- .version('0.1.0-alpha.1');
37
+ .version(pkg.version);
31
38
 
32
39
  // Commands in logical order
33
40
  program.addCommand(initCommand);
@@ -4,7 +4,8 @@
4
4
 
5
5
  import { Command } from 'commander';
6
6
  import { resolve, join } from 'path';
7
- import { existsSync, mkdirSync } from 'fs';
7
+ import { existsSync, mkdirSync, readdirSync } from 'fs';
8
+ import { pathToFileURL } from 'url';
8
9
  import {
9
10
  Orchestrator,
10
11
  RFDBServerBackend,
@@ -96,14 +97,60 @@ const BUILTIN_PLUGINS: Record<string, () => Plugin> = {
96
97
  BrokenImportValidator: () => new BrokenImportValidator() as Plugin,
97
98
  };
98
99
 
99
- function createPlugins(config: GrafemaConfig['plugins']): Plugin[] {
100
+ /**
101
+ * Load custom plugins from .grafema/plugins/ directory
102
+ */
103
+ async function loadCustomPlugins(
104
+ projectPath: string,
105
+ log: (msg: string) => void
106
+ ): Promise<Record<string, () => Plugin>> {
107
+ const pluginsDir = join(projectPath, '.grafema', 'plugins');
108
+ if (!existsSync(pluginsDir)) {
109
+ return {};
110
+ }
111
+
112
+ const customPlugins: Record<string, () => Plugin> = {};
113
+
114
+ try {
115
+ const files = readdirSync(pluginsDir).filter(
116
+ (f) => f.endsWith('.js') || f.endsWith('.mjs')
117
+ );
118
+
119
+ for (const file of files) {
120
+ try {
121
+ const pluginPath = join(pluginsDir, file);
122
+ const pluginUrl = pathToFileURL(pluginPath).href;
123
+ const module = await import(pluginUrl);
124
+
125
+ const PluginClass = module.default || module[file.replace(/\.(m?js)$/, '')];
126
+ if (PluginClass && typeof PluginClass === 'function') {
127
+ const pluginName = PluginClass.name || file.replace(/\.(m?js)$/, '');
128
+ customPlugins[pluginName] = () => new PluginClass() as Plugin;
129
+ log(`Loaded custom plugin: ${pluginName}`);
130
+ }
131
+ } catch (err) {
132
+ console.warn(`Failed to load plugin ${file}: ${(err as Error).message}`);
133
+ }
134
+ }
135
+ } catch (err) {
136
+ console.warn(`Error loading custom plugins: ${(err as Error).message}`);
137
+ }
138
+
139
+ return customPlugins;
140
+ }
141
+
142
+ function createPlugins(
143
+ config: GrafemaConfig['plugins'],
144
+ customPlugins: Record<string, () => Plugin> = {}
145
+ ): Plugin[] {
100
146
  const plugins: Plugin[] = [];
101
147
  const phases: (keyof GrafemaConfig['plugins'])[] = ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'];
102
148
 
103
149
  for (const phase of phases) {
104
150
  const names = config[phase] || [];
105
151
  for (const name of names) {
106
- const factory = BUILTIN_PLUGINS[name];
152
+ // Check built-in first, then custom
153
+ const factory = BUILTIN_PLUGINS[name] || customPlugins[name];
107
154
  if (factory) {
108
155
  plugins.push(factory());
109
156
  } else {
@@ -188,7 +235,9 @@ Examples:
188
235
  }
189
236
  }
190
237
 
191
- const plugins = createPlugins(config.plugins);
238
+ // Load custom plugins from .grafema/plugins/
239
+ const customPlugins = await loadCustomPlugins(projectPath, log);
240
+ const plugins = createPlugins(config.plugins, customPlugins);
192
241
 
193
242
  log(`Loaded ${plugins.length} plugins`);
194
243