@odoograph/cli 0.1.0
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 +118 -0
- package/dist/cache.d.ts +35 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +108 -0
- package/dist/cache.js.map +1 -0
- package/dist/cache.test.d.ts +5 -0
- package/dist/cache.test.d.ts.map +1 -0
- package/dist/cache.test.js +54 -0
- package/dist/cache.test.js.map +1 -0
- package/dist/commands/gaps.d.ts +6 -0
- package/dist/commands/gaps.d.ts.map +1 -0
- package/dist/commands/gaps.js +87 -0
- package/dist/commands/gaps.js.map +1 -0
- package/dist/commands/graph.d.ts +6 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +36 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/lint.d.ts +6 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +56 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/parse.d.ts +6 -0
- package/dist/commands/parse.d.ts.map +1 -0
- package/dist/commands/parse.js +60 -0
- package/dist/commands/parse.js.map +1 -0
- package/dist/commands/workspace.d.ts +6 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +113 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +104 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +5 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +76 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +27 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +114 -0
- package/dist/output.js.map +1 -0
- package/dist/progress.d.ts +29 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +65 -0
- package/dist/progress.js.map +1 -0
- package/dist/utils.d.ts +24 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +104 -0
- package/dist/utils.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @odoograph/cli
|
|
2
|
+
|
|
3
|
+
Cross-file semantic analysis CLI for Odoo modules.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @odoograph/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @odoograph/cli --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
### `ograph parse <file>`
|
|
20
|
+
|
|
21
|
+
Parse a single file into structured JSON.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ograph parse models/sale.py
|
|
25
|
+
ograph parse views/sale_views.xml --format human
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### `ograph lint <path>`
|
|
29
|
+
|
|
30
|
+
Run linting rules on a file or module.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
ograph lint models/sale.py
|
|
34
|
+
ograph lint ./my_module
|
|
35
|
+
ograph lint ./my_module --format json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `ograph gaps <path>`
|
|
39
|
+
|
|
40
|
+
Run gap detection rules on a module.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ograph gaps ./my_module
|
|
44
|
+
ograph gaps ./my_module --category G1xx # Filter by category
|
|
45
|
+
ograph gaps ./my_module --severity error # Filter by severity
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `ograph graph <module>`
|
|
49
|
+
|
|
50
|
+
Build and display module graph statistics.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ograph graph ./my_module
|
|
54
|
+
ograph graph ./my_module --format json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `ograph workspace`
|
|
58
|
+
|
|
59
|
+
Build cross-module workspace graph with full analysis.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Basic usage with explicit paths
|
|
63
|
+
ograph workspace -w ./addons -e /path/to/odoo/addons
|
|
64
|
+
|
|
65
|
+
# Using Odoo config file
|
|
66
|
+
ograph workspace -w ./addons --config ~/.odoorc
|
|
67
|
+
|
|
68
|
+
# Run gap detection
|
|
69
|
+
ograph workspace -w ./addons --config ~/.odoorc --gaps
|
|
70
|
+
|
|
71
|
+
# Cache the graph for reuse
|
|
72
|
+
ograph workspace -w ./addons --save-cache ./graph.bin
|
|
73
|
+
ograph workspace --load-cache ./graph.bin --gaps
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Path Resolution & Precedence:**
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Priority: -w > -e > --config
|
|
80
|
+
|
|
81
|
+
1. Paths from -w → workspace (highest priority)
|
|
82
|
+
2. Paths from -e → external
|
|
83
|
+
3. Paths from --config → external (lowest priority)
|
|
84
|
+
|
|
85
|
+
Deduplication: If a path appears in multiple sources,
|
|
86
|
+
higher priority wins. No duplicates in final graph.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Options
|
|
90
|
+
|
|
91
|
+
| Option | Description |
|
|
92
|
+
|--------|-------------|
|
|
93
|
+
| `-w, --workspace <paths...>` | Workspace addon paths (analyzed for issues) |
|
|
94
|
+
| `-e, --external <paths...>` | External addon paths (for dependency resolution) |
|
|
95
|
+
| `-c, --config <file>` | Odoo config file to read `addons_path` |
|
|
96
|
+
| `-f, --format <format>` | Output format: `json` or `human` |
|
|
97
|
+
| `--gaps` | Run gap detection after building graph |
|
|
98
|
+
| `--save-cache <file>` | Save graph to cache file |
|
|
99
|
+
| `--load-cache <file>` | Load graph from cache file |
|
|
100
|
+
| `--no-cache` | Disable automatic caching |
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
- Node.js >= 20.0.0
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
Copyright © 2025 Solutions Unity Co. See [LICENSE](./LICENSE) for details.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
**OdooGraph** is developed and maintained by [Hussain Hammad](https://github.com/hussain) and [Solutions Unity Co.](https://solutionsunity.com)
|
|
113
|
+
|
|
114
|
+
This repository distributes compiled artifacts only.
|
|
115
|
+
The core analysis engine is proprietary.
|
|
116
|
+
|
|
117
|
+
For more information, visit [odoograph.com](https://odoograph.com)
|
|
118
|
+
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk cache for graph persistence
|
|
3
|
+
*
|
|
4
|
+
* Cache location: ~/.odoo-graph/cache/
|
|
5
|
+
* Key format: hash of workspace paths + file mtimes
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get cache key from paths (hash of sorted paths)
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCacheKey(workspacePaths: string[], externalPaths: string[]): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Load cached graph from disk
|
|
13
|
+
*
|
|
14
|
+
* @param keyOrPath - Cache key (for automatic cache) or absolute path (for --load-cache)
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadCache(keyOrPath: string): Promise<Uint8Array | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Save graph to cache
|
|
19
|
+
*
|
|
20
|
+
* @param keyOrPath - Cache key (for automatic cache) or absolute path (for --save-cache)
|
|
21
|
+
* @param data - Serialized graph data
|
|
22
|
+
*/
|
|
23
|
+
export declare function saveCache(keyOrPath: string, data: Uint8Array): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Clear cache directory
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearCache(): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Get cache statistics
|
|
30
|
+
*/
|
|
31
|
+
export declare function getCacheStats(): Promise<{
|
|
32
|
+
entries: number;
|
|
33
|
+
totalSize: number;
|
|
34
|
+
}>;
|
|
35
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;GAEG;AACH,wBAAsB,WAAW,CAC/B,cAAc,EAAE,MAAM,EAAE,EACxB,aAAa,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAgB7E;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAiBlD;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CAkBD"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk cache for graph persistence
|
|
3
|
+
*
|
|
4
|
+
* Cache location: ~/.odoo-graph/cache/
|
|
5
|
+
* Key format: hash of workspace paths + file mtimes
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, stat, readdir } from "fs/promises";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { createHash } from "crypto";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
const CACHE_VERSION = "1";
|
|
13
|
+
const CACHE_DIR = join(homedir(), ".odoo-graph", "cache");
|
|
14
|
+
/**
|
|
15
|
+
* Get cache key from paths (hash of sorted paths)
|
|
16
|
+
*/
|
|
17
|
+
export async function getCacheKey(workspacePaths, externalPaths) {
|
|
18
|
+
const allPaths = [...workspacePaths, ...externalPaths].sort();
|
|
19
|
+
const hash = createHash("sha256");
|
|
20
|
+
hash.update(CACHE_VERSION);
|
|
21
|
+
for (const p of allPaths) {
|
|
22
|
+
hash.update(p);
|
|
23
|
+
// Include mtime of path for invalidation
|
|
24
|
+
try {
|
|
25
|
+
const s = await stat(p);
|
|
26
|
+
hash.update(s.mtimeMs.toString());
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Path doesn't exist or can't be accessed
|
|
30
|
+
hash.update("missing");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return hash.digest("hex").substring(0, 16);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Load cached graph from disk
|
|
37
|
+
*
|
|
38
|
+
* @param keyOrPath - Cache key (for automatic cache) or absolute path (for --load-cache)
|
|
39
|
+
*/
|
|
40
|
+
export async function loadCache(keyOrPath) {
|
|
41
|
+
try {
|
|
42
|
+
// If it's an absolute path, load directly
|
|
43
|
+
const cachePath = keyOrPath.startsWith("/")
|
|
44
|
+
? keyOrPath
|
|
45
|
+
: join(CACHE_DIR, `${keyOrPath}.bin`);
|
|
46
|
+
if (!existsSync(cachePath)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const buffer = await readFile(cachePath);
|
|
50
|
+
return new Uint8Array(buffer);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Save graph to cache
|
|
58
|
+
*
|
|
59
|
+
* @param keyOrPath - Cache key (for automatic cache) or absolute path (for --save-cache)
|
|
60
|
+
* @param data - Serialized graph data
|
|
61
|
+
*/
|
|
62
|
+
export async function saveCache(keyOrPath, data) {
|
|
63
|
+
// Determine target path
|
|
64
|
+
const isAbsolute = keyOrPath.startsWith("/");
|
|
65
|
+
const cachePath = isAbsolute ? keyOrPath : join(CACHE_DIR, `${keyOrPath}.bin`);
|
|
66
|
+
// Ensure directory exists
|
|
67
|
+
const dir = isAbsolute ? join(cachePath, "..") : CACHE_DIR;
|
|
68
|
+
await mkdir(dir, { recursive: true });
|
|
69
|
+
await writeFile(cachePath, data);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clear cache directory
|
|
73
|
+
*/
|
|
74
|
+
export async function clearCache() {
|
|
75
|
+
if (!existsSync(CACHE_DIR)) {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
const files = await readdir(CACHE_DIR);
|
|
79
|
+
let count = 0;
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
if (file.endsWith(".bin")) {
|
|
82
|
+
const { unlink } = await import("fs/promises");
|
|
83
|
+
await unlink(join(CACHE_DIR, file));
|
|
84
|
+
count++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get cache statistics
|
|
91
|
+
*/
|
|
92
|
+
export async function getCacheStats() {
|
|
93
|
+
if (!existsSync(CACHE_DIR)) {
|
|
94
|
+
return { entries: 0, totalSize: 0 };
|
|
95
|
+
}
|
|
96
|
+
const files = await readdir(CACHE_DIR);
|
|
97
|
+
let entries = 0;
|
|
98
|
+
let totalSize = 0;
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
if (file.endsWith(".bin")) {
|
|
101
|
+
const s = await stat(join(CACHE_DIR, file));
|
|
102
|
+
entries++;
|
|
103
|
+
totalSize += s.size;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { entries, totalSize };
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../src/cache.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for cache functionality
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
5
|
+
import { mkdir, rm } from "fs/promises";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { tmpdir } from "os";
|
|
8
|
+
import { getCacheKey, loadCache, saveCache } from "./cache.js";
|
|
9
|
+
const TEST_DIR = join(tmpdir(), "ograph-cache-test-" + Date.now());
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
await mkdir(TEST_DIR, { recursive: true });
|
|
12
|
+
});
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
describe("getCacheKey", () => {
|
|
17
|
+
it("should generate consistent keys for same paths", async () => {
|
|
18
|
+
const paths = ["/path/a", "/path/b"];
|
|
19
|
+
const key1 = await getCacheKey(paths, []);
|
|
20
|
+
const key2 = await getCacheKey(paths, []);
|
|
21
|
+
expect(key1).toBe(key2);
|
|
22
|
+
});
|
|
23
|
+
it("should generate different keys for different paths", async () => {
|
|
24
|
+
const key1 = await getCacheKey(["/path/a"], []);
|
|
25
|
+
const key2 = await getCacheKey(["/path/b"], []);
|
|
26
|
+
expect(key1).not.toBe(key2);
|
|
27
|
+
});
|
|
28
|
+
it("should include external paths in key", async () => {
|
|
29
|
+
const key1 = await getCacheKey(["/path/a"], []);
|
|
30
|
+
const key2 = await getCacheKey(["/path/a"], ["/path/ext"]);
|
|
31
|
+
expect(key1).not.toBe(key2);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("saveCache and loadCache", () => {
|
|
35
|
+
it("should save and load cache from absolute path", async () => {
|
|
36
|
+
const cachePath = join(TEST_DIR, "test.bin");
|
|
37
|
+
const data = new Uint8Array([1, 2, 3, 4, 5]);
|
|
38
|
+
await saveCache(cachePath, data);
|
|
39
|
+
const loaded = await loadCache(cachePath);
|
|
40
|
+
expect(loaded).toEqual(data);
|
|
41
|
+
});
|
|
42
|
+
it("should return null for non-existent cache", async () => {
|
|
43
|
+
const loaded = await loadCache(join(TEST_DIR, "nonexistent.bin"));
|
|
44
|
+
expect(loaded).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
it("should handle empty data", async () => {
|
|
47
|
+
const cachePath = join(TEST_DIR, "empty.bin");
|
|
48
|
+
const data = new Uint8Array([]);
|
|
49
|
+
await saveCache(cachePath, data);
|
|
50
|
+
const loaded = await loadCache(cachePath);
|
|
51
|
+
expect(loaded).toEqual(data);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../src/cache.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gaps.d.ts","sourceRoot":"","sources":["../../src/commands/gaps.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,WAAW,SAgGrB,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gaps command - run gap detection rules on a module or workspace
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { OdooGraph } from "@odoograph/wasm";
|
|
7
|
+
import { formatOutput, formatGapReport } from "../output.js";
|
|
8
|
+
import { loadCache, saveCache, getCacheKey } from "../cache.js";
|
|
9
|
+
import { createProgress } from "../progress.js";
|
|
10
|
+
export const gapsCommand = new Command("gaps")
|
|
11
|
+
.description("Run gap detection rules on a module")
|
|
12
|
+
.argument("<path>", "Module path to analyze")
|
|
13
|
+
.option("-f, --format <format>", "Output format: json, human", "human")
|
|
14
|
+
.option("-c, --category <cat>", "Filter by category (e.g., G1xx, G2xx)")
|
|
15
|
+
.option("-s, --severity <sev>", "Filter by severity (critical, error, warning, info)")
|
|
16
|
+
.option("--no-cache", "Disable cache")
|
|
17
|
+
.action(async (targetPath, options) => {
|
|
18
|
+
const modulePath = resolve(targetPath);
|
|
19
|
+
const progress = createProgress("Analyzing gaps...");
|
|
20
|
+
progress.start();
|
|
21
|
+
try {
|
|
22
|
+
// Try to load from cache first
|
|
23
|
+
let graph = null;
|
|
24
|
+
if (options.cache) {
|
|
25
|
+
const cacheKey = await getCacheKey([modulePath], []);
|
|
26
|
+
const cached = await loadCache(cacheKey);
|
|
27
|
+
if (cached) {
|
|
28
|
+
graph = OdooGraph.fromBytes(cached);
|
|
29
|
+
progress.text = "Loaded from cache";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Build graph if not cached
|
|
33
|
+
if (!graph) {
|
|
34
|
+
progress.text = "Building graph...";
|
|
35
|
+
graph = OdooGraph.build({
|
|
36
|
+
workspace_paths: [modulePath],
|
|
37
|
+
external_paths: [],
|
|
38
|
+
});
|
|
39
|
+
// Save to cache
|
|
40
|
+
if (options.cache) {
|
|
41
|
+
const cacheKey = await getCacheKey([modulePath], []);
|
|
42
|
+
await saveCache(cacheKey, graph.serialize());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
progress.text = "Running gap rules...";
|
|
46
|
+
let reports = graph.runGapRules();
|
|
47
|
+
// Apply filters
|
|
48
|
+
if (options.category || options.severity) {
|
|
49
|
+
reports = reports.map((report) => ({
|
|
50
|
+
...report,
|
|
51
|
+
diagnostics: report.diagnostics.filter((d) => {
|
|
52
|
+
if (options.category && !d.rule_id.startsWith(options.category)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (options.severity && d.severity !== options.severity) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}),
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
progress.stop();
|
|
63
|
+
// Output
|
|
64
|
+
if (options.format === "json") {
|
|
65
|
+
console.log(formatOutput(reports, "json"));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
for (const report of reports) {
|
|
69
|
+
console.log(formatGapReport(report));
|
|
70
|
+
}
|
|
71
|
+
// Summary
|
|
72
|
+
const total = reports.reduce((sum, r) => sum + r.diagnostics.length, 0);
|
|
73
|
+
if (total > 0) {
|
|
74
|
+
console.log(`\n${total} gap(s) found`);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log("No gaps found");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
progress.stop();
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=gaps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gaps.js","sourceRoot":"","sources":["../../src/commands/gaps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/commands/graph.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,YAAY,SA6BrB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph command - build and display module graph
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { OdooGraph } from "@odoograph/wasm";
|
|
7
|
+
import { formatOutput, formatStats } from "../output.js";
|
|
8
|
+
import { createProgress } from "../progress.js";
|
|
9
|
+
export const graphCommand = new Command("graph")
|
|
10
|
+
.description("Build and display module graph")
|
|
11
|
+
.argument("<module>", "Module path to analyze")
|
|
12
|
+
.option("-f, --format <format>", "Output format: json, human", "human")
|
|
13
|
+
.action(async (modulePath, options) => {
|
|
14
|
+
const fullPath = resolve(modulePath);
|
|
15
|
+
const progress = createProgress("Building graph...");
|
|
16
|
+
progress.start();
|
|
17
|
+
try {
|
|
18
|
+
const graph = OdooGraph.build({
|
|
19
|
+
workspace_paths: [fullPath],
|
|
20
|
+
external_paths: [],
|
|
21
|
+
});
|
|
22
|
+
progress.stop();
|
|
23
|
+
const stats = graph.stats();
|
|
24
|
+
if (options.format === "json") {
|
|
25
|
+
console.log(formatOutput(stats, "json"));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log(formatStats(stats));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
progress.stop();
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/commands/graph.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,gCAAgC,CAAC;KAC7C,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAC;KAC9C,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,EAAE,OAAO,CAAC;KACtE,MAAM,CAAC,KAAK,EAAE,UAAkB,EAAE,OAA2B,EAAE,EAAE;IAChE,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErC,MAAM,QAAQ,GAAG,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,CAAC;IAEjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAC5B,eAAe,EAAE,CAAC,QAAQ,CAAC;YAC3B,cAAc,EAAE,EAAE;SACnB,CAAC,CAAC;QAEH,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEhB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAE5B,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,WAAW,SAwDrB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lint command - run linting rules on files or modules
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFile, stat } from "fs/promises";
|
|
6
|
+
import { resolve, relative } from "path";
|
|
7
|
+
import { lintFile } from "@odoograph/wasm";
|
|
8
|
+
import { formatOutput, formatDiagnostics } from "../output.js";
|
|
9
|
+
import { walkOdooFiles } from "../utils.js";
|
|
10
|
+
export const lintCommand = new Command("lint")
|
|
11
|
+
.description("Run linting rules on a file or module")
|
|
12
|
+
.argument("<path>", "File or module directory to lint")
|
|
13
|
+
.option("-f, --format <format>", "Output format: json, human", "human")
|
|
14
|
+
.option("--fix", "Apply automatic fixes (where available)", false)
|
|
15
|
+
.action(async (targetPath, options) => {
|
|
16
|
+
const fullPath = resolve(targetPath);
|
|
17
|
+
const pathStat = await stat(fullPath);
|
|
18
|
+
let allDiagnostics = [];
|
|
19
|
+
if (pathStat.isFile()) {
|
|
20
|
+
// Lint single file
|
|
21
|
+
const content = await readFile(fullPath, "utf-8");
|
|
22
|
+
const diagnostics = lintFile(content, fullPath);
|
|
23
|
+
allDiagnostics.push({ file: fullPath, diagnostics });
|
|
24
|
+
}
|
|
25
|
+
else if (pathStat.isDirectory()) {
|
|
26
|
+
// Lint all files in module
|
|
27
|
+
const files = await walkOdooFiles(fullPath);
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const content = await readFile(file, "utf-8");
|
|
30
|
+
const diagnostics = lintFile(content, file);
|
|
31
|
+
if (diagnostics.length > 0) {
|
|
32
|
+
allDiagnostics.push({ file, diagnostics });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (options.format === "json") {
|
|
37
|
+
console.log(formatOutput(allDiagnostics, "json"));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
for (const { file, diagnostics } of allDiagnostics) {
|
|
42
|
+
const relPath = relative(cwd, file);
|
|
43
|
+
console.log(formatDiagnostics(relPath, diagnostics));
|
|
44
|
+
}
|
|
45
|
+
// Summary
|
|
46
|
+
const total = allDiagnostics.reduce((sum, f) => sum + f.diagnostics.length, 0);
|
|
47
|
+
if (total > 0) {
|
|
48
|
+
console.log(`\n${total} issue(s) found`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log("No issues found");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/commands/parse.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,YAAY,SA2DrB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse command - parse a single file and output structured data
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
import { formatOutput } from "../output.js";
|
|
8
|
+
export const parseCommand = new Command("parse")
|
|
9
|
+
.description("Parse a single file into structured JSON")
|
|
10
|
+
.argument("<file>", "File to parse")
|
|
11
|
+
.option("-f, --format <format>", "Output format: json, human", "json")
|
|
12
|
+
.action(async (file, options) => {
|
|
13
|
+
const filePath = resolve(file);
|
|
14
|
+
const content = await readFile(filePath, "utf-8");
|
|
15
|
+
// Determine file type and parse
|
|
16
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
17
|
+
let result;
|
|
18
|
+
switch (ext) {
|
|
19
|
+
case "py":
|
|
20
|
+
// For now, we'll use a simple parse result
|
|
21
|
+
// In Phase 2, this would use web-tree-sitter
|
|
22
|
+
result = {
|
|
23
|
+
type: "python",
|
|
24
|
+
path: filePath,
|
|
25
|
+
size: content.length,
|
|
26
|
+
lines: content.split("\n").length,
|
|
27
|
+
// TODO: Add actual parsing with WASM
|
|
28
|
+
};
|
|
29
|
+
break;
|
|
30
|
+
case "xml":
|
|
31
|
+
result = {
|
|
32
|
+
type: "xml",
|
|
33
|
+
path: filePath,
|
|
34
|
+
size: content.length,
|
|
35
|
+
lines: content.split("\n").length,
|
|
36
|
+
// TODO: Add actual parsing with WASM
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
case "csv":
|
|
40
|
+
result = {
|
|
41
|
+
type: "csv",
|
|
42
|
+
path: filePath,
|
|
43
|
+
size: content.length,
|
|
44
|
+
lines: content.split("\n").length,
|
|
45
|
+
};
|
|
46
|
+
break;
|
|
47
|
+
case "js":
|
|
48
|
+
result = {
|
|
49
|
+
type: "javascript",
|
|
50
|
+
path: filePath,
|
|
51
|
+
size: content.length,
|
|
52
|
+
lines: content.split("\n").length,
|
|
53
|
+
};
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Unsupported file type: ${ext}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(formatOutput(result, options.format));
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/commands/parse.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,0CAA0C,CAAC;KACvD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;KACnC,MAAM,CAAC,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,CAAC;KACrE,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,OAA2B,EAAE,EAAE;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElD,gCAAgC;IAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,CAAC;IAErD,IAAI,MAAc,CAAC;IAEnB,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,IAAI;YACP,2CAA2C;YAC3C,6CAA6C;YAC7C,MAAM,GAAG;gBACP,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO,CAAC,MAAM;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;gBACjC,qCAAqC;aACtC,CAAC;YACF,MAAM;QAER,KAAK,KAAK;YACR,MAAM,GAAG;gBACP,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO,CAAC,MAAM;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;gBACjC,qCAAqC;aACtC,CAAC;YACF,MAAM;QAER,KAAK,KAAK;YACR,MAAM,GAAG;gBACP,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO,CAAC,MAAM;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;aAClC,CAAC;YACF,MAAM;QAER,KAAK,IAAI;YACP,MAAM,GAAG;gBACP,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,OAAO,CAAC,MAAM;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;aAClC,CAAC;YACF,MAAM;QAER;YACE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC"}
|