itworksbut 0.1.1 → 0.3.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 +60 -61
- package/bin/itworksbut.js +65 -56
- package/package.json +1 -1
- package/src/checks/node/express-json-limit-missing.js +2 -2
- package/src/cli/output.js +12 -3
- package/src/cli/parseArgs.js +9 -11
- package/src/cli/terminal.js +10 -27
- package/src/core/packageInfo.js +11 -0
- package/src/core/scanner.js +2 -1
- package/src/reporters/consoleReporter.js +91 -86
- package/src/reporters/consoleStyle.js +226 -121
- package/src/reporters/todoReporter.js +133 -0
package/README.md
CHANGED
|
@@ -4,17 +4,20 @@ ItWorksBut is a Node.js CI tool for static checks in JavaScript, Node.js, web, T
|
|
|
4
4
|
|
|
5
5
|
It focuses on common "it works, but..." risks often found in AI-generated or rushed prototypes: committed env files, missing lockfiles, weak CI, unsafe web APIs, broad desktop permissions, and similar issues.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
For every finding, ItWorksBut gives you a copy-ready fix prompt you can paste into your coding agent. It does not just say what is wrong; it tells your AI exactly what to inspect, what to change, and what not to leak.
|
|
8
|
+
|
|
9
|
+
It only reads files and reports findings. It does not call external APIs, does not send telemetry, and does not modify the scanned project unless you explicitly ask it to write `todo.md` with `--todo`.
|
|
8
10
|
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
11
13
|
```sh
|
|
12
|
-
|
|
14
|
+
npm install --global itworksbut
|
|
15
|
+
itworksbut scan
|
|
13
16
|
```
|
|
14
17
|
|
|
15
18
|
### Homebrew
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
With the Homebrew tap:
|
|
18
21
|
|
|
19
22
|
```sh
|
|
20
23
|
brew tap oliverjessner/tap
|
|
@@ -22,84 +25,77 @@ brew install itworksbut
|
|
|
22
25
|
itworksbut scan
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
brew install oliverjessner/tap/itworksbut
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
The `itworksbut` formula belongs in the Homebrew tap repo, not in this app repo:
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
https://github.com/oliverjessner/homebrew-tap
|
|
35
|
-
└── Formula/
|
|
36
|
-
└── itworksbut.rb
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
This repository contains a one-command release script. It runs checks, publishes the npm package, generates the Homebrew formula, commits it to the tap, and pushes the tap:
|
|
28
|
+
## Quick Start
|
|
40
29
|
|
|
41
30
|
```sh
|
|
42
|
-
|
|
43
|
-
npm run publish
|
|
31
|
+
itworksbut scan
|
|
44
32
|
```
|
|
45
33
|
|
|
46
|
-
|
|
34
|
+
`scan` is intentionally the strict/default path: all checks are enabled, only heavy generated folders are skipped, and the default `fail-on` threshold is `low` so more issues fail early. Use `--config` only when you deliberately want to tune or suppress checks.
|
|
47
35
|
|
|
48
|
-
|
|
36
|
+
Common commands:
|
|
49
37
|
|
|
50
38
|
```sh
|
|
51
|
-
|
|
39
|
+
itworksbut scan --path .
|
|
40
|
+
itworksbut scan --fail-on high
|
|
41
|
+
itworksbut scan --json
|
|
42
|
+
itworksbut scan --sarif > itworksbut.sarif
|
|
43
|
+
itworksbut scan --todo
|
|
44
|
+
itworksbut scan --config itworksbut.config.json
|
|
45
|
+
itworksbut scan --verbose
|
|
46
|
+
itworksbut --version
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
|
|
49
|
+
## Options
|
|
55
50
|
|
|
56
|
-
```
|
|
57
|
-
|
|
51
|
+
```text
|
|
52
|
+
itworksbut scan [options]
|
|
58
53
|
```
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
- `--path <path>`: Scan a specific project directory. Defaults to the current directory.
|
|
56
|
+
- `--config <path>`: Use a custom config file. Defaults to `itworksbut.config.json` when present.
|
|
57
|
+
- `--fail-on <severity>`: Exit with code `1` when a finding at or above the severity exists. Levels: `critical`, `high`, `medium`, `low`, `info`. Default: `low`.
|
|
58
|
+
- `--json`: Print machine-readable JSON only. No banner, colors, spinner, table, or extra text.
|
|
59
|
+
- `--sarif`: Print SARIF JSON for GitHub Code Scanning. No banner, colors, spinner, table, or extra text.
|
|
60
|
+
- `--todo`: Write an AI-ready `todo.md` into the scanned project with prioritized findings, fix prompts, and acceptance criteria.
|
|
61
|
+
- `--verbose`: Include scanner warnings and extra metadata in console output.
|
|
62
|
+
- `--quiet`: Print only the summary.
|
|
63
|
+
- `--no-color`: Disable colored output.
|
|
64
|
+
- `--no-banner`: Disable the ASCII intro banner.
|
|
65
|
+
- `--version`, `-v`: Print the installed ItWorksBut version.
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
Exit codes:
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
node ./bin/itworksbut.js scan --sarif
|
|
68
|
-
node ./bin/itworksbut.js scan --fail-on high
|
|
69
|
-
node ./bin/itworksbut.js scan --config itworksbut.config.json
|
|
70
|
-
node ./bin/itworksbut.js scan --path .
|
|
71
|
-
node ./bin/itworksbut.js scan --verbose
|
|
72
|
-
```
|
|
69
|
+
- `0`: no findings at or above the configured `fail-on` severity
|
|
70
|
+
- `1`: at least one finding at or above the configured `fail-on` severity
|
|
71
|
+
- `2`: tool/runtime error
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
Severity levels are `critical`, `high`, `medium`, `low`, and `info`.
|
|
75
74
|
|
|
76
75
|
## Terminal Experience
|
|
77
76
|
|
|
78
77
|
Normal console output is intentionally more opinionated than the machine-readable reporters:
|
|
79
78
|
|
|
80
79
|
```sh
|
|
81
|
-
|
|
80
|
+
itworksbut scan
|
|
82
81
|
```
|
|
83
82
|
|
|
84
83
|
Console-only flags:
|
|
85
84
|
|
|
86
85
|
- `--no-color`
|
|
87
86
|
- `--no-banner`
|
|
88
|
-
- `--no-spinner`
|
|
89
|
-
- `--compact`
|
|
90
87
|
- `--quiet`
|
|
91
88
|
- `--verbose`
|
|
92
|
-
- `--theme default|toxic|mono`
|
|
93
89
|
|
|
94
90
|
In CI, spinners and banners are automatically disabled. With `--json` and `--sarif`, stdout contains only valid machine-readable output. The edgy tone applies only to the Console Reporter.
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
To create a fix list for a coding agent:
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
```sh
|
|
95
|
+
itworksbut scan --todo
|
|
96
|
+
```
|
|
101
97
|
|
|
102
|
-
|
|
98
|
+
This writes `todo.md` to the scanned project. The file is ordered by severity and includes agent rules, exact fix prompts, locations, recommendations, and final verification checkboxes.
|
|
103
99
|
|
|
104
100
|
## GitHub Actions
|
|
105
101
|
|
|
@@ -121,13 +117,13 @@ jobs:
|
|
|
121
117
|
node-version: 20
|
|
122
118
|
cache: npm
|
|
123
119
|
- run: npm ci
|
|
124
|
-
- run:
|
|
120
|
+
- run: npx itworksbut scan --fail-on high
|
|
125
121
|
```
|
|
126
122
|
|
|
127
123
|
For GitHub Code Scanning-style output:
|
|
128
124
|
|
|
129
125
|
```sh
|
|
130
|
-
|
|
126
|
+
itworksbut scan --sarif > itworksbut.sarif
|
|
131
127
|
```
|
|
132
128
|
|
|
133
129
|
## Configuration
|
|
@@ -169,20 +165,21 @@ release/**
|
|
|
169
165
|
|
|
170
166
|
## Example Output
|
|
171
167
|
|
|
172
|
-
|
|
173
|
-
✖
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
▲
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
168
|
+

|
|
169
|
+
✖ CRITICAL It works, but your .env is tracked.
|
|
170
|
+
✔ Check: env.env-file-tracked
|
|
171
|
+
📁 File: .env
|
|
172
|
+
🤔 Why: .env appears to be tracked by git. Secrets may be exposed.
|
|
173
|
+
🤖 prompt: You are a senior security engineer working in this repository. Fix the ItWorksBut finding env.env-file-tracked at .env. Treat this as a concrete finding. Problem: .env appears to be tracked by git. Secrets may be exposed. Required change: Remove tracked env files from git, add safe examples such as .env.example, and make sure any exposed credentials are treated as compromised. Do not print, log, or preserve raw secret values; use placeholders only. Keep existing behavior intact where possible, add or update focused tests when useful, and do not silence the check unless the underlying risk is actually fixed.
|
|
174
|
+
|
|
175
|
+
▲ HIGH It works, but your SQL query is one template string away from pain.
|
|
176
|
+
✔ Check: database.raw-sql-interpolation
|
|
177
|
+
📁 File: src/db.js:12
|
|
178
|
+
🤔 Why: Possible SQL injection risk: raw SQL appears to be built with template string interpolation.
|
|
179
|
+
🤖 prompt: You are a senior security engineer working in this repository. Fix the ItWorksBut finding database.raw-sql-interpolation at src/db.js:12. This finding is heuristic, so inspect the code first and only change behavior when the risk is real. Problem: Possible SQL injection risk: raw SQL appears to be built with template string interpolation. Required change: Replace SQL string interpolation or concatenation with parameterized queries, prepared statements, or a safe ORM query builder. Keep existing behavior intact where possible, add or update focused tests when useful, and do not silence the check unless the underlying risk is actually fixed.
|
|
184
180
|
|
|
185
181
|
SUMMARY
|
|
182
|
+
|
|
186
183
|
- ship status: DO NOT SHIP
|
|
187
184
|
- Fix the red stuff before production.
|
|
188
185
|
- total findings: 2
|
|
@@ -193,6 +190,7 @@ SUMMARY
|
|
|
193
190
|
- info: 0
|
|
194
191
|
- fail-on: high
|
|
195
192
|
- exit decision: 1
|
|
193
|
+
|
|
196
194
|
```
|
|
197
195
|
|
|
198
196
|
Secret-like findings never print the full secret value. Findings report the file, line, and secret type where possible.
|
|
@@ -239,3 +237,4 @@ Each check is a plain ESM module with an `id`, metadata, and async `run(context)
|
|
|
239
237
|
ItWorksBut is a static heuristic scanner, not a pentest, SAST replacement, dependency vulnerability database, or runtime security monitor. Findings intentionally use wording such as "possible", "potential", and "appears to" when a check is heuristic.
|
|
240
238
|
|
|
241
239
|
Use it as a CI guardrail for common project hygiene and security mistakes. For production systems, combine it with code review, tests, dependency scanning, secrets scanning, threat modeling, and focused security assessment.
|
|
240
|
+
```
|
package/bin/itworksbut.js
CHANGED
|
@@ -1,63 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { parseArgs } from
|
|
4
|
-
import { printUsage, printRuntimeError } from
|
|
5
|
-
import { createScanSpinner,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { parseArgs } from '../src/cli/parseArgs.js';
|
|
4
|
+
import { printUsage, printRuntimeError, printVersion } from '../src/cli/output.js';
|
|
5
|
+
import { createScanSpinner, printIntro } from '../src/cli/terminal.js';
|
|
6
|
+
import { packageInfo } from '../src/core/packageInfo.js';
|
|
7
|
+
import { scanProject } from '../src/core/scanner.js';
|
|
8
|
+
import { getExitCode } from '../src/core/findings.js';
|
|
9
|
+
import { reportConsole } from '../src/reporters/consoleReporter.js';
|
|
10
|
+
import { reportJson } from '../src/reporters/jsonReporter.js';
|
|
11
|
+
import { reportSarif } from '../src/reporters/sarifReporter.js';
|
|
12
|
+
import { writeTodoReport } from '../src/reporters/todoReporter.js';
|
|
11
13
|
|
|
12
14
|
async function main() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
15
|
+
const args = parseArgs(process.argv.slice(2));
|
|
16
|
+
|
|
17
|
+
if (args.help) {
|
|
18
|
+
printUsage();
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (args.version) {
|
|
23
|
+
printVersion(`It Works But… version ${packageInfo.version}`);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (args.command !== 'scan') {
|
|
28
|
+
printUsage();
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
printIntro(args);
|
|
33
|
+
|
|
34
|
+
const spinner = createScanSpinner(args);
|
|
35
|
+
if (spinner) spinner.start();
|
|
36
|
+
|
|
37
|
+
let result;
|
|
38
|
+
try {
|
|
39
|
+
result = await scanProject({
|
|
40
|
+
rootPath: args.path,
|
|
41
|
+
configPath: args.config,
|
|
42
|
+
failOn: args.failOn,
|
|
43
|
+
verbose: args.verbose,
|
|
44
|
+
});
|
|
45
|
+
if (spinner) spinner.succeed('Scan complete. Now the receipts.');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (spinner) spinner.fail('Scan stopped before the receipts were printed.');
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (args.sarif) {
|
|
52
|
+
process.stdout.write(`${JSON.stringify(reportSarif(result), null, 2)}\n`);
|
|
53
|
+
} else if (args.json) {
|
|
54
|
+
process.stdout.write(`${JSON.stringify(reportJson(result), null, 2)}\n`);
|
|
55
|
+
} else if (args.todo) {
|
|
56
|
+
const filePath = await writeTodoReport(result);
|
|
57
|
+
if (!args.quiet) process.stdout.write(`Wrote AI todo file: ${filePath}\n`);
|
|
58
|
+
} else {
|
|
59
|
+
reportConsole(result, args);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return getExitCode(result.findings, result.config.failOn);
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
main()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
.then(code => {
|
|
67
|
+
process.exitCode = code;
|
|
68
|
+
})
|
|
69
|
+
.catch(error => {
|
|
70
|
+
printRuntimeError(error);
|
|
71
|
+
process.exitCode = 2;
|
|
72
|
+
});
|
package/package.json
CHANGED
|
@@ -18,11 +18,11 @@ export default {
|
|
|
18
18
|
const args = match.match[1] || "";
|
|
19
19
|
if (/\blimit\s*:/.test(args)) continue;
|
|
20
20
|
findings.push({
|
|
21
|
-
message: "
|
|
21
|
+
message: "Express JSON body parsing appears to be used without an explicit request body size limit.",
|
|
22
22
|
file: match.file,
|
|
23
23
|
line: match.line,
|
|
24
24
|
column: match.column,
|
|
25
|
-
recommendation: "Set a conservative
|
|
25
|
+
recommendation: "Set a conservative JSON body parser limit such as 100kb, and tune it per route when needed."
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
return findings;
|
package/src/cli/output.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import gradient from 'gradient-string';
|
|
2
|
+
|
|
1
3
|
export function printUsage() {
|
|
2
4
|
process.stdout.write(`ItWorksBut
|
|
3
5
|
|
|
@@ -12,17 +14,24 @@ Options:
|
|
|
12
14
|
Levels: critical, high, medium, low, info. Default: low.
|
|
13
15
|
--json Print machine-readable JSON.
|
|
14
16
|
--sarif Print SARIF for GitHub Code Scanning.
|
|
17
|
+
--todo Write an AI-ready todo.md to the scanned project.
|
|
15
18
|
--no-color Disable color styling.
|
|
16
19
|
--no-banner Disable the intro banner.
|
|
17
|
-
--no-spinner Disable scan spinner.
|
|
18
|
-
--compact Print one-line findings.
|
|
19
20
|
--quiet Print only the summary.
|
|
20
|
-
--theme <theme> Console theme: default, toxic, mono.
|
|
21
21
|
--verbose Include scanner warnings and extra metadata.
|
|
22
|
+
--version, -v Print the ItWorksBut version.
|
|
22
23
|
--help Show this help.
|
|
23
24
|
`);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
export function printVersion(version) {
|
|
28
|
+
try {
|
|
29
|
+
process.stdout.write(`${gradient.rainbow(version)}\n`);
|
|
30
|
+
} catch {
|
|
31
|
+
process.stdout.write(`${version}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
export function printRuntimeError(error) {
|
|
27
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
28
37
|
process.stderr.write(`ItWorksBut runtime error: ${message}\n`);
|
package/src/cli/parseArgs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const FLAG_WITH_VALUE = new Set(["--fail-on", "--config", "--path"
|
|
2
|
-
const BOOLEAN_FLAGS = new Set(["--json", "--sarif", "--verbose", "--help", "-h", "--
|
|
1
|
+
const FLAG_WITH_VALUE = new Set(["--fail-on", "--config", "--path"]);
|
|
2
|
+
const BOOLEAN_FLAGS = new Set(["--json", "--sarif", "--todo", "--verbose", "--help", "-h", "--version", "-v", "--no-color", "--no-banner", "--quiet"]);
|
|
3
3
|
|
|
4
4
|
export function parseArgs(argv) {
|
|
5
5
|
const args = {
|
|
@@ -9,14 +9,13 @@ export function parseArgs(argv) {
|
|
|
9
9
|
failOn: undefined,
|
|
10
10
|
json: false,
|
|
11
11
|
sarif: false,
|
|
12
|
+
todo: false,
|
|
12
13
|
verbose: false,
|
|
13
14
|
noColor: false,
|
|
14
15
|
noBanner: false,
|
|
15
|
-
noSpinner: false,
|
|
16
|
-
compact: false,
|
|
17
16
|
quiet: false,
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
help: false,
|
|
18
|
+
version: false
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
const tokens = [...argv];
|
|
@@ -45,13 +44,13 @@ export function parseArgs(argv) {
|
|
|
45
44
|
|
|
46
45
|
if (BOOLEAN_FLAGS.has(token)) {
|
|
47
46
|
if (token === "--help" || token === "-h") args.help = true;
|
|
47
|
+
if (token === "--version" || token === "-v") args.version = true;
|
|
48
48
|
if (token === "--json") args.json = true;
|
|
49
49
|
if (token === "--sarif") args.sarif = true;
|
|
50
|
+
if (token === "--todo") args.todo = true;
|
|
50
51
|
if (token === "--verbose") args.verbose = true;
|
|
51
52
|
if (token === "--no-color") args.noColor = true;
|
|
52
53
|
if (token === "--no-banner") args.noBanner = true;
|
|
53
|
-
if (token === "--no-spinner") args.noSpinner = true;
|
|
54
|
-
if (token === "--compact") args.compact = true;
|
|
55
54
|
if (token === "--quiet") args.quiet = true;
|
|
56
55
|
continue;
|
|
57
56
|
}
|
|
@@ -59,8 +58,8 @@ export function parseArgs(argv) {
|
|
|
59
58
|
throw new Error(`Unknown argument: ${token}`);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
if (args.json
|
|
63
|
-
throw new Error("Use only one output format: --json or --
|
|
61
|
+
if ([args.json, args.sarif, args.todo].filter(Boolean).length > 1) {
|
|
62
|
+
throw new Error("Use only one output format: --json, --sarif, or --todo");
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
return args;
|
|
@@ -70,6 +69,5 @@ function assignValue(args, flag, value) {
|
|
|
70
69
|
if (flag === "--fail-on") args.failOn = value;
|
|
71
70
|
else if (flag === "--config") args.config = value;
|
|
72
71
|
else if (flag === "--path") args.path = value;
|
|
73
|
-
else if (flag === "--theme") args.theme = value;
|
|
74
72
|
else throw new Error(`Unknown argument: ${flag}`);
|
|
75
73
|
}
|
package/src/cli/terminal.js
CHANGED
|
@@ -4,8 +4,6 @@ import figlet from "figlet";
|
|
|
4
4
|
import gradient from "gradient-string";
|
|
5
5
|
import ora from "ora";
|
|
6
6
|
|
|
7
|
-
const THEMES = new Set(["default", "toxic", "mono"]);
|
|
8
|
-
|
|
9
7
|
const SPINNER_TEXT = {
|
|
10
8
|
git: "Checking git hygiene",
|
|
11
9
|
env: "Sniffing for secrets",
|
|
@@ -20,21 +18,12 @@ const SPINNER_TEXT = {
|
|
|
20
18
|
default: "Looking for things that work but should not ship"
|
|
21
19
|
};
|
|
22
20
|
|
|
23
|
-
export function normalizeTheme(theme) {
|
|
24
|
-
const normalized = String(theme || "default").toLowerCase();
|
|
25
|
-
if (!THEMES.has(normalized)) {
|
|
26
|
-
throw new Error(`Invalid theme "${theme}". Expected one of: default, toxic, mono`);
|
|
27
|
-
}
|
|
28
|
-
return normalized;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
export function isFancyOutputEnabled(options = {}, env = process.env, stdout = process.stdout) {
|
|
32
|
-
return Boolean(stdout.isTTY) && !env.CI && !options.json && !options.sarif && !options.
|
|
22
|
+
return Boolean(stdout.isTTY) && !env.CI && !options.json && !options.sarif && !options.todo && !options.noColor;
|
|
33
23
|
}
|
|
34
24
|
|
|
35
25
|
export function isColorEnabled(options = {}, env = process.env, stdout = process.stdout) {
|
|
36
|
-
if (options.noColor || options.json || options.sarif) return false;
|
|
37
|
-
if (normalizeTheme(options.theme) === "mono") return false;
|
|
26
|
+
if (options.noColor || options.json || options.sarif || options.todo) return false;
|
|
38
27
|
if (env.FORCE_COLOR && env.FORCE_COLOR !== "0") return true;
|
|
39
28
|
if (env.CI) return false;
|
|
40
29
|
return Boolean(stdout.isTTY);
|
|
@@ -51,7 +40,7 @@ export function shouldUseSpinner(options = {}, env = process.env, stdout = proce
|
|
|
51
40
|
!env.CI &&
|
|
52
41
|
!options.json &&
|
|
53
42
|
!options.sarif &&
|
|
54
|
-
!options.
|
|
43
|
+
!options.todo &&
|
|
55
44
|
!options.quiet
|
|
56
45
|
);
|
|
57
46
|
}
|
|
@@ -61,23 +50,18 @@ export function createScanSpinner(options = {}) {
|
|
|
61
50
|
return ora({
|
|
62
51
|
text: SPINNER_TEXT.default,
|
|
63
52
|
stream: process.stderr,
|
|
64
|
-
color:
|
|
53
|
+
color: "cyan"
|
|
65
54
|
});
|
|
66
55
|
}
|
|
67
56
|
|
|
68
57
|
export function printIntro(options = {}) {
|
|
69
|
-
if (options.json || options.sarif || options.noBanner || options.quiet || process.env.CI || !process.stdout.isTTY) {
|
|
58
|
+
if (options.json || options.sarif || options.todo || options.noBanner || options.quiet || process.env.CI || !process.stdout.isTTY) {
|
|
70
59
|
return;
|
|
71
60
|
}
|
|
72
61
|
|
|
73
|
-
const theme = normalizeTheme(options.theme);
|
|
74
62
|
const colors = getChalk(options);
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const claim =
|
|
78
|
-
theme === "toxic"
|
|
79
|
-
? `${colors.bold("Green builds. Red flags.")}\n${colors.green("Let's see what breaks before production.")}`
|
|
80
|
-
: `${colors.bold("AI-built? Nice.")}\n${colors.yellow("Now let's see what breaks before production.")}`;
|
|
63
|
+
const title = renderTitle(options.noColor);
|
|
64
|
+
const claim = `${colors.bold("AI-built? Nice.")}\n${colors.yellow("Now let's see what breaks before production.")}`;
|
|
81
65
|
|
|
82
66
|
process.stdout.write(`${title}\n`);
|
|
83
67
|
process.stdout.write(
|
|
@@ -85,12 +69,12 @@ export function printIntro(options = {}) {
|
|
|
85
69
|
padding: 1,
|
|
86
70
|
margin: 1,
|
|
87
71
|
borderStyle: "round",
|
|
88
|
-
borderColor:
|
|
72
|
+
borderColor: options.noColor ? undefined : "cyan"
|
|
89
73
|
})}\n`
|
|
90
74
|
);
|
|
91
75
|
}
|
|
92
76
|
|
|
93
|
-
function renderTitle(
|
|
77
|
+
function renderTitle(noColor) {
|
|
94
78
|
let title = "ItWorksBut";
|
|
95
79
|
try {
|
|
96
80
|
title = figlet.textSync("ItWorksBut", {
|
|
@@ -103,8 +87,7 @@ function renderTitle(theme) {
|
|
|
103
87
|
}
|
|
104
88
|
|
|
105
89
|
try {
|
|
106
|
-
if (
|
|
107
|
-
if (theme === "toxic") return gradient(["#faff00", "#39ff14", "#00f5ff"])(title);
|
|
90
|
+
if (noColor) return title;
|
|
108
91
|
return gradient.rainbow(title);
|
|
109
92
|
} catch {
|
|
110
93
|
return title;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
6
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
|
|
7
|
+
|
|
8
|
+
export const packageInfo = {
|
|
9
|
+
name: packageJson.name,
|
|
10
|
+
version: packageJson.version
|
|
11
|
+
};
|
package/src/core/scanner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import checks from "../checks/index.js";
|
|
2
2
|
import { createContext } from "./context.js";
|
|
3
3
|
import { normalizeFinding, severityRank } from "./findings.js";
|
|
4
|
+
import { packageInfo } from "./packageInfo.js";
|
|
4
5
|
|
|
5
6
|
export async function scanProject(options = {}) {
|
|
6
7
|
const startedAt = new Date();
|
|
@@ -43,7 +44,7 @@ export async function scanProject(options = {}) {
|
|
|
43
44
|
config: context.config,
|
|
44
45
|
meta: {
|
|
45
46
|
tool: "ItWorksBut",
|
|
46
|
-
version:
|
|
47
|
+
version: packageInfo.version,
|
|
47
48
|
rootPath: context.rootPath,
|
|
48
49
|
packageManager: context.packageManager,
|
|
49
50
|
gitAvailable: context.gitAvailable,
|