flamerjs 0.1.6 → 1.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 +75 -19
- package/dist/cli.js +143 -7
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -51,13 +51,23 @@ can be used locally, in pull requests, or in CI.
|
|
|
51
51
|
- **Language-aware checks:** JavaScript/TypeScript, Python, Rust, Go, C/C++,
|
|
52
52
|
C#, Zig, SQL, notebooks, and additional language profiles.
|
|
53
53
|
- **Stable JSON output:** suitable for CI annotations and other automation.
|
|
54
|
-
- **Single-pass scanning:**
|
|
54
|
+
- **Single-pass TypeScript scanning:** the repository is walked once and
|
|
55
|
+
content is collected once before analyzers run.
|
|
56
|
+
- **Coordinated native workers:** Rust handles shared file metrics and
|
|
57
|
+
Rust-specific checks, while C++ handles C/C++ checks; TypeScript remains the
|
|
58
|
+
source of truth and fallback path.
|
|
55
59
|
|
|
56
60
|
## Requirements
|
|
57
61
|
|
|
58
62
|
- Node.js 18 or newer
|
|
59
63
|
- npm
|
|
60
64
|
|
|
65
|
+
The default TypeScript engine needs only Node.js and npm. The published package
|
|
66
|
+
also declares optional, platform-specific native packages containing both
|
|
67
|
+
workers, so a compatible npm install gets full mode automatically. Building
|
|
68
|
+
workers from this repository requires Rust/Cargo and a C++17 compiler with
|
|
69
|
+
CMake.
|
|
70
|
+
|
|
61
71
|
## Quick start
|
|
62
72
|
|
|
63
73
|
Run the published CLI without installing it permanently:
|
|
@@ -101,23 +111,24 @@ flamerjs /path/to/repository --format summary
|
|
|
101
111
|
flamerjs [path] [options]
|
|
102
112
|
```
|
|
103
113
|
|
|
104
|
-
| Option
|
|
105
|
-
|
|
|
106
|
-
| `--package, --scope <name>`
|
|
107
|
-
| `--path <dir>`
|
|
108
|
-
| `--format <json\|summary>`
|
|
109
|
-
| `--out <file>`
|
|
110
|
-
| `--fail-on <SEVERITY>`
|
|
111
|
-
| `--max-files <n>`
|
|
112
|
-
| `--commit-limit <n>`
|
|
113
|
-
| `--no-git`
|
|
114
|
-
| `--no-lockfile-warning`
|
|
115
|
-
| `--ignore-dir <name>`
|
|
116
|
-
| `--quiet`
|
|
117
|
-
| `--color`
|
|
118
|
-
| `--no-color`
|
|
119
|
-
| `--
|
|
120
|
-
| `--
|
|
114
|
+
| Option | Description |
|
|
115
|
+
| --------------------------------------- | ----------------------------------------------------------------------------- |
|
|
116
|
+
| `--package, --scope <name>` | Analyze only a named sub-project |
|
|
117
|
+
| `--path <dir>` | Set the repository root; defaults to the current directory |
|
|
118
|
+
| `--format <json\|summary>` | Select JSON or human-readable output; summary is the default |
|
|
119
|
+
| `--out <file>` | Also write the JSON report to a file |
|
|
120
|
+
| `--fail-on <SEVERITY>` | Exit with status 1 at or above `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, or `INFO` |
|
|
121
|
+
| `--max-files <n>` | Limit the number of files scanned |
|
|
122
|
+
| `--commit-limit <n>` | Limit commits inspected by Git checks |
|
|
123
|
+
| `--no-git` | Skip Git analysis |
|
|
124
|
+
| `--no-lockfile-warning` | Suppress the missing-lockfile suggestion |
|
|
125
|
+
| `--ignore-dir <name>` | Ignore an additional directory name; repeatable |
|
|
126
|
+
| `--quiet` | Suppress the human summary |
|
|
127
|
+
| `--color` | Force ANSI colors |
|
|
128
|
+
| `--no-color` | Disable ANSI colors |
|
|
129
|
+
| `--engine <all\|typescript\|rust\|cpp>` | Run all engines (default) or select one engine |
|
|
130
|
+
| `--list-languages` | List recognized language profiles |
|
|
131
|
+
| `--help` | Show command help |
|
|
121
132
|
|
|
122
133
|
Exit status `0` means the scan completed; findings may still exist. Status `1`
|
|
123
134
|
means `--fail-on` was reached, and status `2` indicates invalid usage or a
|
|
@@ -170,12 +181,57 @@ The project uses Node.js and npm as its supported development toolchain.
|
|
|
170
181
|
`package-lock.json` is the canonical dependency lockfile.
|
|
171
182
|
The current test suite contains **175 tests**.
|
|
172
183
|
|
|
184
|
+
### Native engine
|
|
185
|
+
|
|
186
|
+
Flamer runs all three components by default: TypeScript analyzers plus the Rust
|
|
187
|
+
and C++ native workers. They do different work and run concurrently:
|
|
188
|
+
|
|
189
|
+
- TypeScript walks the repository, runs the generic analyzers, and produces the
|
|
190
|
+
report.
|
|
191
|
+
- Rust computes shared metrics for all selected files and checks Rust files for
|
|
192
|
+
`.unwrap()` usage.
|
|
193
|
+
- C++ checks only C and C++ files for native-language issues such as
|
|
194
|
+
`#include <bits/stdc++.h>`.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
npm run native:build:all
|
|
198
|
+
node dist/cli.js .
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Native findings are merged into the TypeScript report only after all requested
|
|
202
|
+
native jobs succeed. If a native binary is unavailable or fails, the default
|
|
203
|
+
`all` mode falls back to a pure TypeScript report. Use `--engine typescript` for
|
|
204
|
+
a native-free scan, or `--engine rust` / `--engine cpp` to require one specific
|
|
205
|
+
worker; explicit native modes return an error when their worker fails.
|
|
206
|
+
The native protocol and worker details are documented in
|
|
207
|
+
[`native/core/README.md`](native/core/README.md).
|
|
208
|
+
|
|
209
|
+
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the runtime layers,
|
|
210
|
+
worker protocol, engine selection, and native development workflow.
|
|
211
|
+
|
|
212
|
+
For the source installation path:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
curl -fsSL https://raw.githubusercontent.com/dummy3ye/flamer/master/scripts/install.sh | sh
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Platform-specific native release binaries are not bundled in the current npm
|
|
219
|
+
package yet. The npm package uses optional native packages when they are
|
|
220
|
+
published for the current platform; if npm skips them, TypeScript fallback is
|
|
221
|
+
automatic. The installer's `native` path is reserved for standalone releases
|
|
222
|
+
that attach those assets.
|
|
223
|
+
|
|
173
224
|
## How it works
|
|
174
225
|
|
|
175
226
|
1. Detect the repository layout, languages, and ecosystems.
|
|
176
227
|
2. Walk the tree once and collect file metadata and content-derived signals.
|
|
177
228
|
3. Run isolated analyzers against the collected records.
|
|
178
|
-
4.
|
|
229
|
+
4. Run the requested native jobs through the versioned JSONL protocol. Native
|
|
230
|
+
jobs receive non-overlapping responsibilities and return metrics or
|
|
231
|
+
language-specific findings.
|
|
232
|
+
5. Merge successful native findings, sort everything, and emit JSON or a
|
|
233
|
+
concise summary. In default `all` mode, any native failure returns to the
|
|
234
|
+
TypeScript-only result.
|
|
179
235
|
|
|
180
236
|
Analyzers are intentionally factual and conservative. A finding should identify
|
|
181
237
|
something actionable rather than merely express a preference.
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { writeFileSync, existsSync as
|
|
4
|
+
import { writeFileSync, existsSync as existsSync4 } from "fs";
|
|
5
5
|
import { resolve, basename } from "path";
|
|
6
6
|
|
|
7
7
|
// src/util/fs.ts
|
|
@@ -353,7 +353,8 @@ var CATEGORIES = [
|
|
|
353
353
|
"structure",
|
|
354
354
|
"git",
|
|
355
355
|
"error",
|
|
356
|
-
"unfinished"
|
|
356
|
+
"unfinished",
|
|
357
|
+
"native"
|
|
357
358
|
];
|
|
358
359
|
function rank(severity) {
|
|
359
360
|
const r = RANK[String(severity).toUpperCase()];
|
|
@@ -2901,6 +2902,82 @@ var ANALYZERS = [
|
|
|
2901
2902
|
{ id: "systems", label: "Rust / Go / C / C++ / C# / Zig", fn: analyze6 }
|
|
2902
2903
|
];
|
|
2903
2904
|
|
|
2905
|
+
// src/native.ts
|
|
2906
|
+
import { spawn } from "child_process";
|
|
2907
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2908
|
+
import { join as join6 } from "path";
|
|
2909
|
+
import { createRequire } from "module";
|
|
2910
|
+
var require2 = createRequire(import.meta.url);
|
|
2911
|
+
function nativePackageName() {
|
|
2912
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
2913
|
+
const supported = /* @__PURE__ */ new Set(["linux-x64", "darwin-x64", "darwin-arm64", "win32-x64"]);
|
|
2914
|
+
return supported.has(platform) ? `@dummy3ye/flamer-native-${platform}` : null;
|
|
2915
|
+
}
|
|
2916
|
+
function packagedWorkerPath(worker) {
|
|
2917
|
+
const packageName = nativePackageName();
|
|
2918
|
+
if (!packageName) return null;
|
|
2919
|
+
try {
|
|
2920
|
+
return require2.resolve(`${packageName}/${worker}`);
|
|
2921
|
+
} catch {
|
|
2922
|
+
return null;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
function nativeWorkerPath(root, worker) {
|
|
2926
|
+
const configured = worker === "rust" ? process.env.FLAMER_RUST_CORE : process.env.FLAMER_CPP_CORE;
|
|
2927
|
+
if (configured) return configured;
|
|
2928
|
+
if (process.env.FLAMER_CORE && worker === "rust") return process.env.FLAMER_CORE;
|
|
2929
|
+
const packaged = packagedWorkerPath(worker);
|
|
2930
|
+
if (packaged) return packaged;
|
|
2931
|
+
return worker === "rust" ? join6(root, "native", "core", "rust", "target", "release", "flamer-core") : join6(root, "native", "core", "cpp", "build", "flamer-core-cpp");
|
|
2932
|
+
}
|
|
2933
|
+
function runNativeWorker(root, files, worker, operation = "file_metrics") {
|
|
2934
|
+
const executable = nativeWorkerPath(root, worker);
|
|
2935
|
+
if (!existsSync3(executable)) {
|
|
2936
|
+
return Promise.reject(
|
|
2937
|
+
new Error(`${worker} worker not found at ${executable}; build the native workers first`)
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
const request = JSON.stringify({ protocol: 1, operation, root, files });
|
|
2941
|
+
return new Promise((resolve2, reject) => {
|
|
2942
|
+
const child = spawn(executable, { stdio: ["pipe", "pipe", "pipe"] });
|
|
2943
|
+
let stdout = "";
|
|
2944
|
+
let stderr = "";
|
|
2945
|
+
const timeout = setTimeout(() => {
|
|
2946
|
+
child.kill();
|
|
2947
|
+
reject(new Error(`${worker} worker timed out`));
|
|
2948
|
+
}, 3e4);
|
|
2949
|
+
child.stdout.on("data", (chunk) => stdout += chunk);
|
|
2950
|
+
child.stderr.on("data", (chunk) => stderr += chunk);
|
|
2951
|
+
child.on("error", (error) => {
|
|
2952
|
+
clearTimeout(timeout);
|
|
2953
|
+
reject(new Error(`${worker} worker failed: ${error.message}`));
|
|
2954
|
+
});
|
|
2955
|
+
child.on("close", (status) => {
|
|
2956
|
+
clearTimeout(timeout);
|
|
2957
|
+
if (status !== 0) {
|
|
2958
|
+
reject(new Error(`${worker} worker exited with status ${status}: ${stderr.trim()}`));
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
try {
|
|
2962
|
+
const response = JSON.parse(stdout);
|
|
2963
|
+
if (response.protocol !== 1 || response.operation !== operation) {
|
|
2964
|
+
throw new Error(`${worker} worker returned an incompatible response`);
|
|
2965
|
+
}
|
|
2966
|
+
if (!response.ok) {
|
|
2967
|
+
throw new Error(
|
|
2968
|
+
response.errors.map((error) => `${error.path}: ${error.message}`).join("; ")
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
resolve2(response);
|
|
2972
|
+
} catch (error) {
|
|
2973
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
child.stdin.end(`${request}
|
|
2977
|
+
`);
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2904
2981
|
// src/cli.ts
|
|
2905
2982
|
var SPEC_VERSION = 1;
|
|
2906
2983
|
function parseArgs(argv) {
|
|
@@ -2918,6 +2995,7 @@ function parseArgs(argv) {
|
|
|
2918
2995
|
listLanguages: false,
|
|
2919
2996
|
help: false,
|
|
2920
2997
|
color: null,
|
|
2998
|
+
engine: "all",
|
|
2921
2999
|
ignoreDirs: []
|
|
2922
3000
|
};
|
|
2923
3001
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -2963,6 +3041,14 @@ function parseArgs(argv) {
|
|
|
2963
3041
|
case "--no-color":
|
|
2964
3042
|
opts.color = false;
|
|
2965
3043
|
break;
|
|
3044
|
+
case "--engine":
|
|
3045
|
+
opts.engine = argv[++i] || "all";
|
|
3046
|
+
if (!["all", "typescript", "rust", "cpp"].includes(opts.engine)) {
|
|
3047
|
+
process.stderr.write(`invalid engine: ${opts.engine}
|
|
3048
|
+
`);
|
|
3049
|
+
process.exit(2);
|
|
3050
|
+
}
|
|
3051
|
+
break;
|
|
2966
3052
|
case "--list-languages":
|
|
2967
3053
|
opts.listLanguages = true;
|
|
2968
3054
|
break;
|
|
@@ -3004,6 +3090,8 @@ Options:
|
|
|
3004
3090
|
--quiet suppress the human summary
|
|
3005
3091
|
--color force ANSI colors
|
|
3006
3092
|
--no-color disable ANSI colors
|
|
3093
|
+
--engine <all|typescript|rust|cpp>
|
|
3094
|
+
all runs TypeScript, Rust, and C++ (default: all)
|
|
3007
3095
|
--list-languages print the supported language table
|
|
3008
3096
|
-h, --help show this help
|
|
3009
3097
|
`);
|
|
@@ -3020,10 +3108,10 @@ function printLanguages() {
|
|
|
3020
3108
|
${allExtensions().length} file extensions recognized.
|
|
3021
3109
|
`);
|
|
3022
3110
|
}
|
|
3023
|
-
function run2(opts) {
|
|
3111
|
+
async function run2(opts) {
|
|
3024
3112
|
const started = Date.now();
|
|
3025
3113
|
const root = opts.root;
|
|
3026
|
-
if (!
|
|
3114
|
+
if (!existsSync4(root) || !isDir(root)) {
|
|
3027
3115
|
const err = {
|
|
3028
3116
|
specVersion: SPEC_VERSION,
|
|
3029
3117
|
tool: "flamer",
|
|
@@ -3161,6 +3249,54 @@ function run2(opts) {
|
|
|
3161
3249
|
analyzers: analyzerResults
|
|
3162
3250
|
}
|
|
3163
3251
|
};
|
|
3252
|
+
if (opts.engine !== "typescript") {
|
|
3253
|
+
const allFiles = scopedFiles.map((file) => file.rel);
|
|
3254
|
+
const cppFiles = scopedFiles.filter((file) => ["c", "cpp"].includes(file.lang)).map((file) => file.rel);
|
|
3255
|
+
const rustFiles = scopedFiles.filter((file) => file.lang === "rust").map((file) => file.rel);
|
|
3256
|
+
const jobs = opts.engine === "all" ? [
|
|
3257
|
+
["rust", "file_metrics", allFiles],
|
|
3258
|
+
["rust", "rust_analysis", rustFiles],
|
|
3259
|
+
["cpp", "cpp_analysis", cppFiles]
|
|
3260
|
+
] : opts.engine === "rust" ? [
|
|
3261
|
+
["rust", "file_metrics", allFiles],
|
|
3262
|
+
["rust", "rust_analysis", rustFiles]
|
|
3263
|
+
] : [["cpp", "cpp_analysis", cppFiles]];
|
|
3264
|
+
const nativeResults = {};
|
|
3265
|
+
const failures = [];
|
|
3266
|
+
const nativeFindings = [];
|
|
3267
|
+
await Promise.all(
|
|
3268
|
+
jobs.map(async ([worker, operation, files2]) => {
|
|
3269
|
+
if (!files2.length && operation !== "file_metrics") return;
|
|
3270
|
+
try {
|
|
3271
|
+
const native = await runNativeWorker(root, files2, worker, operation);
|
|
3272
|
+
nativeResults[`${worker}:${operation}`] = {
|
|
3273
|
+
files: native.metrics.files,
|
|
3274
|
+
bytes: native.metrics.bytes,
|
|
3275
|
+
lines: native.metrics.lines,
|
|
3276
|
+
durationMs: native.duration_ms
|
|
3277
|
+
};
|
|
3278
|
+
nativeFindings.push(...native.findings || []);
|
|
3279
|
+
} catch (error) {
|
|
3280
|
+
failures.push(error?.message || String(error));
|
|
3281
|
+
}
|
|
3282
|
+
})
|
|
3283
|
+
);
|
|
3284
|
+
if (failures.length && opts.engine !== "all") {
|
|
3285
|
+
findings.push(
|
|
3286
|
+
finding({
|
|
3287
|
+
category: "error",
|
|
3288
|
+
severity: "CRITICAL",
|
|
3289
|
+
title: "Native engine failed",
|
|
3290
|
+
detail: failures.join("; ")
|
|
3291
|
+
})
|
|
3292
|
+
);
|
|
3293
|
+
report.findings = sortFindings(findings);
|
|
3294
|
+
report.metadata.native = { engine: opts.engine, errors: failures };
|
|
3295
|
+
return { report, exitCode: 2 };
|
|
3296
|
+
}
|
|
3297
|
+
if (!failures.length) findings.push(...nativeFindings);
|
|
3298
|
+
report.metadata.native = failures.length ? { engine: "typescript", fallback: failures, workers: nativeResults } : { engine: opts.engine, workers: nativeResults };
|
|
3299
|
+
}
|
|
3164
3300
|
let exitCode = 0;
|
|
3165
3301
|
if (opts.failOn && report.metadata.worst && rank(report.metadata.worst) <= rank(opts.failOn)) {
|
|
3166
3302
|
exitCode = 1;
|
|
@@ -3284,11 +3420,11 @@ for (const stream of [process.stdout, process.stderr]) {
|
|
|
3284
3420
|
throw err;
|
|
3285
3421
|
});
|
|
3286
3422
|
}
|
|
3287
|
-
function main() {
|
|
3423
|
+
async function main() {
|
|
3288
3424
|
const opts = parseArgs(process.argv.slice(2));
|
|
3289
3425
|
if (opts.help) return printHelp();
|
|
3290
3426
|
if (opts.listLanguages) return printLanguages();
|
|
3291
|
-
const { report, exitCode } = run2(opts);
|
|
3427
|
+
const { report, exitCode } = await run2(opts);
|
|
3292
3428
|
const json = JSON.stringify(report, null, 2);
|
|
3293
3429
|
if (opts.out) {
|
|
3294
3430
|
try {
|
|
@@ -3305,4 +3441,4 @@ function main() {
|
|
|
3305
3441
|
}
|
|
3306
3442
|
process.exitCode = exitCode;
|
|
3307
3443
|
}
|
|
3308
|
-
main();
|
|
3444
|
+
void main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flamerjs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Language-agnostic code health auditor that walks a repository once and reports what is wrong with it",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,9 +22,20 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
|
+
"optionalDependencies": {
|
|
26
|
+
"@dummy3ye/flamer-native-darwin-arm64": "1.1.1",
|
|
27
|
+
"@dummy3ye/flamer-native-darwin-x64": "1.1.1",
|
|
28
|
+
"@dummy3ye/flamer-native-linux-x64": "1.1.1",
|
|
29
|
+
"@dummy3ye/flamer-native-win32-x64": "1.1.1"
|
|
30
|
+
},
|
|
25
31
|
"scripts": {
|
|
26
32
|
"build": "tsup",
|
|
27
33
|
"dev": "tsup --watch",
|
|
34
|
+
"native:build": "sh scripts/native-build.sh",
|
|
35
|
+
"native:test": "sh scripts/native-test.sh",
|
|
36
|
+
"native:build:cpp": "sh scripts/native-build-cpp.sh",
|
|
37
|
+
"native:build:all": "sh scripts/native-build.sh",
|
|
38
|
+
"native:package": "node scripts/package-native.mjs",
|
|
28
39
|
"prepublishOnly": "npm run lint && npm run build && npm test",
|
|
29
40
|
"lint": "eslint .",
|
|
30
41
|
"lint:fix": "eslint . --fix",
|