prodlint 0.9.4 → 0.10.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/LICENSE +21 -21
- package/README.md +332 -329
- package/action.yml +152 -152
- package/dist/cli.js +31 -0
- package/dist/index.js +31 -0
- package/dist/mcp.js +31 -0
- package/package.json +97 -97
package/action.yml
CHANGED
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
name: 'Prodlint'
|
|
2
|
-
description: 'Production readiness for vibe-coded apps — check your AI code before you ship'
|
|
3
|
-
branding:
|
|
4
|
-
icon: 'shield'
|
|
5
|
-
color: 'green'
|
|
6
|
-
|
|
7
|
-
inputs:
|
|
8
|
-
path:
|
|
9
|
-
description: 'Path to scan (default: current directory)'
|
|
10
|
-
required: false
|
|
11
|
-
default: '.'
|
|
12
|
-
threshold:
|
|
13
|
-
description: 'Minimum score to pass (0-100). Fails the check if score is below this.'
|
|
14
|
-
required: false
|
|
15
|
-
default: '0'
|
|
16
|
-
ignore:
|
|
17
|
-
description: 'Glob patterns to ignore (comma-separated)'
|
|
18
|
-
required: false
|
|
19
|
-
default: ''
|
|
20
|
-
comment:
|
|
21
|
-
description: 'Post a PR comment with results (true/false)'
|
|
22
|
-
required: false
|
|
23
|
-
default: 'true'
|
|
24
|
-
|
|
25
|
-
outputs:
|
|
26
|
-
score:
|
|
27
|
-
description: 'Overall prodlint score (0-100)'
|
|
28
|
-
value: ${{ steps.scan.outputs.score }}
|
|
29
|
-
critical:
|
|
30
|
-
description: 'Number of critical findings'
|
|
31
|
-
value: ${{ steps.scan.outputs.critical }}
|
|
32
|
-
|
|
33
|
-
runs:
|
|
34
|
-
using: 'composite'
|
|
35
|
-
steps:
|
|
36
|
-
- name: Setup Node.js
|
|
37
|
-
uses: actions/setup-node@v4
|
|
38
|
-
with:
|
|
39
|
-
node-version: '20'
|
|
40
|
-
|
|
41
|
-
- name: Run prodlint
|
|
42
|
-
id: scan
|
|
43
|
-
shell: bash
|
|
44
|
-
env:
|
|
45
|
-
INPUT_PATH: ${{ inputs.path }}
|
|
46
|
-
INPUT_IGNORE: ${{ inputs.ignore }}
|
|
47
|
-
run: |
|
|
48
|
-
CMD_ARGS=("$INPUT_PATH" "--json")
|
|
49
|
-
if [ -n "$INPUT_IGNORE" ]; then
|
|
50
|
-
IFS=',' read -ra PATTERNS <<< "$INPUT_IGNORE"
|
|
51
|
-
for p in "${PATTERNS[@]}"; do
|
|
52
|
-
trimmed=$(echo "$p" | xargs)
|
|
53
|
-
CMD_ARGS+=("--ignore" "$trimmed")
|
|
54
|
-
done
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
OUTPUT=$(npx -y prodlint@latest "${CMD_ARGS[@]}" 2>&1) || true
|
|
58
|
-
|
|
59
|
-
SCORE=$(echo "$OUTPUT" | node -e "
|
|
60
|
-
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
61
|
-
console.log(Number(d.overallScore) || 0);
|
|
62
|
-
" 2>/dev/null || echo "0")
|
|
63
|
-
SCORE=$(echo "$SCORE" | tr -cd '0-9')
|
|
64
|
-
|
|
65
|
-
CRITICAL=$(echo "$OUTPUT" | node -e "
|
|
66
|
-
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
67
|
-
console.log(Number(d.summary.critical) || 0);
|
|
68
|
-
" 2>/dev/null || echo "0")
|
|
69
|
-
CRITICAL=$(echo "$CRITICAL" | tr -cd '0-9')
|
|
70
|
-
|
|
71
|
-
echo "score=${SCORE:-0}" >> "$GITHUB_OUTPUT"
|
|
72
|
-
echo "critical=${CRITICAL:-0}" >> "$GITHUB_OUTPUT"
|
|
73
|
-
echo "$OUTPUT" > /tmp/prodlint-result.json
|
|
74
|
-
|
|
75
|
-
- name: Generate comment body
|
|
76
|
-
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
77
|
-
id: comment
|
|
78
|
-
shell: bash
|
|
79
|
-
env:
|
|
80
|
-
SCAN_SCORE: ${{ steps.scan.outputs.score }}
|
|
81
|
-
INPUT_THRESHOLD: ${{ inputs.threshold }}
|
|
82
|
-
run: |
|
|
83
|
-
SCORE="$SCAN_SCORE"
|
|
84
|
-
THRESHOLD="$INPUT_THRESHOLD"
|
|
85
|
-
|
|
86
|
-
if [ "$SCORE" -ge 80 ]; then
|
|
87
|
-
EMOJI="✅"
|
|
88
|
-
COLOR="brightgreen"
|
|
89
|
-
elif [ "$SCORE" -ge 60 ]; then
|
|
90
|
-
EMOJI="⚠️"
|
|
91
|
-
COLOR="yellow"
|
|
92
|
-
else
|
|
93
|
-
EMOJI="🚨"
|
|
94
|
-
COLOR="red"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Build comment from JSON with markdown sanitization
|
|
98
|
-
TMPFILE=$(mktemp "${RUNNER_TEMP:-/tmp}/prodlint-comment-XXXXXX.md")
|
|
99
|
-
node -e "
|
|
100
|
-
const fs = require('fs');
|
|
101
|
-
const d = JSON.parse(fs.readFileSync('/tmp/prodlint-result.json', 'utf8'));
|
|
102
|
-
const esc = s => String(s).replace(/[[\]()\\*_\`<>]/g, c => '\\\\' + c);
|
|
103
|
-
const lines = [];
|
|
104
|
-
lines.push('## ' + '$EMOJI' + ' Production Readiness: **' + d.overallScore + '/100**');
|
|
105
|
-
lines.push('');
|
|
106
|
-
lines.push('| Category | Score | Issues |');
|
|
107
|
-
lines.push('|----------|-------|--------|');
|
|
108
|
-
for (const c of d.categoryScores) {
|
|
109
|
-
const icon = c.score >= 80 ? '🟢' : c.score >= 60 ? '🟡' : '🔴';
|
|
110
|
-
lines.push('| ' + icon + ' ' + esc(c.category) + ' | ' + c.score + '/100 | ' + c.findingCount + ' |');
|
|
111
|
-
}
|
|
112
|
-
if (d.summary.critical > 0) {
|
|
113
|
-
lines.push('');
|
|
114
|
-
lines.push('### Critical Issues');
|
|
115
|
-
lines.push('');
|
|
116
|
-
const crits = d.findings.filter(f => f.severity === 'critical');
|
|
117
|
-
for (const f of crits.slice(0, 10)) {
|
|
118
|
-
lines.push('- \`' + esc(f.ruleId) + '\` \`' + esc(f.file) + ':' + f.line + '\` — ' + esc(f.message));
|
|
119
|
-
}
|
|
120
|
-
if (crits.length > 10) {
|
|
121
|
-
lines.push('- ...and ' + (crits.length - 10) + ' more');
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
lines.push('');
|
|
125
|
-
lines.push('---');
|
|
126
|
-
lines.push('*Scanned ' + d.filesScanned + ' files in ' + d.scanDurationMs + 'ms · [prodlint](https://prodlint.com)*');
|
|
127
|
-
fs.writeFileSync('$TMPFILE', lines.join('\n'));
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
# Use the generated file for the comment step
|
|
131
|
-
cp "$TMPFILE" /tmp/prodlint-comment.md
|
|
132
|
-
|
|
133
|
-
- name: Post PR comment
|
|
134
|
-
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
135
|
-
uses: marocchino/sticky-pull-request-comment@v3
|
|
136
|
-
with:
|
|
137
|
-
header: prodlint
|
|
138
|
-
path: /tmp/prodlint-comment.md
|
|
139
|
-
|
|
140
|
-
- name: Check threshold
|
|
141
|
-
if: inputs.threshold != '0'
|
|
142
|
-
shell: bash
|
|
143
|
-
env:
|
|
144
|
-
SCAN_SCORE: ${{ steps.scan.outputs.score }}
|
|
145
|
-
INPUT_THRESHOLD: ${{ inputs.threshold }}
|
|
146
|
-
run: |
|
|
147
|
-
SCORE="$SCAN_SCORE"
|
|
148
|
-
THRESHOLD="$INPUT_THRESHOLD"
|
|
149
|
-
if [ "$SCORE" -lt "$THRESHOLD" ]; then
|
|
150
|
-
echo "::error::Prodlint score ($SCORE) is below threshold ($THRESHOLD)"
|
|
151
|
-
exit 1
|
|
152
|
-
fi
|
|
1
|
+
name: 'Prodlint'
|
|
2
|
+
description: 'Production readiness for vibe-coded apps — check your AI code before you ship'
|
|
3
|
+
branding:
|
|
4
|
+
icon: 'shield'
|
|
5
|
+
color: 'green'
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
path:
|
|
9
|
+
description: 'Path to scan (default: current directory)'
|
|
10
|
+
required: false
|
|
11
|
+
default: '.'
|
|
12
|
+
threshold:
|
|
13
|
+
description: 'Minimum score to pass (0-100). Fails the check if score is below this.'
|
|
14
|
+
required: false
|
|
15
|
+
default: '0'
|
|
16
|
+
ignore:
|
|
17
|
+
description: 'Glob patterns to ignore (comma-separated)'
|
|
18
|
+
required: false
|
|
19
|
+
default: ''
|
|
20
|
+
comment:
|
|
21
|
+
description: 'Post a PR comment with results (true/false)'
|
|
22
|
+
required: false
|
|
23
|
+
default: 'true'
|
|
24
|
+
|
|
25
|
+
outputs:
|
|
26
|
+
score:
|
|
27
|
+
description: 'Overall prodlint score (0-100)'
|
|
28
|
+
value: ${{ steps.scan.outputs.score }}
|
|
29
|
+
critical:
|
|
30
|
+
description: 'Number of critical findings'
|
|
31
|
+
value: ${{ steps.scan.outputs.critical }}
|
|
32
|
+
|
|
33
|
+
runs:
|
|
34
|
+
using: 'composite'
|
|
35
|
+
steps:
|
|
36
|
+
- name: Setup Node.js
|
|
37
|
+
uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version: '20'
|
|
40
|
+
|
|
41
|
+
- name: Run prodlint
|
|
42
|
+
id: scan
|
|
43
|
+
shell: bash
|
|
44
|
+
env:
|
|
45
|
+
INPUT_PATH: ${{ inputs.path }}
|
|
46
|
+
INPUT_IGNORE: ${{ inputs.ignore }}
|
|
47
|
+
run: |
|
|
48
|
+
CMD_ARGS=("$INPUT_PATH" "--json")
|
|
49
|
+
if [ -n "$INPUT_IGNORE" ]; then
|
|
50
|
+
IFS=',' read -ra PATTERNS <<< "$INPUT_IGNORE"
|
|
51
|
+
for p in "${PATTERNS[@]}"; do
|
|
52
|
+
trimmed=$(echo "$p" | xargs)
|
|
53
|
+
CMD_ARGS+=("--ignore" "$trimmed")
|
|
54
|
+
done
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
OUTPUT=$(npx -y prodlint@latest "${CMD_ARGS[@]}" 2>&1) || true
|
|
58
|
+
|
|
59
|
+
SCORE=$(echo "$OUTPUT" | node -e "
|
|
60
|
+
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
61
|
+
console.log(Number(d.overallScore) || 0);
|
|
62
|
+
" 2>/dev/null || echo "0")
|
|
63
|
+
SCORE=$(echo "$SCORE" | tr -cd '0-9')
|
|
64
|
+
|
|
65
|
+
CRITICAL=$(echo "$OUTPUT" | node -e "
|
|
66
|
+
const d = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
67
|
+
console.log(Number(d.summary.critical) || 0);
|
|
68
|
+
" 2>/dev/null || echo "0")
|
|
69
|
+
CRITICAL=$(echo "$CRITICAL" | tr -cd '0-9')
|
|
70
|
+
|
|
71
|
+
echo "score=${SCORE:-0}" >> "$GITHUB_OUTPUT"
|
|
72
|
+
echo "critical=${CRITICAL:-0}" >> "$GITHUB_OUTPUT"
|
|
73
|
+
echo "$OUTPUT" > /tmp/prodlint-result.json
|
|
74
|
+
|
|
75
|
+
- name: Generate comment body
|
|
76
|
+
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
77
|
+
id: comment
|
|
78
|
+
shell: bash
|
|
79
|
+
env:
|
|
80
|
+
SCAN_SCORE: ${{ steps.scan.outputs.score }}
|
|
81
|
+
INPUT_THRESHOLD: ${{ inputs.threshold }}
|
|
82
|
+
run: |
|
|
83
|
+
SCORE="$SCAN_SCORE"
|
|
84
|
+
THRESHOLD="$INPUT_THRESHOLD"
|
|
85
|
+
|
|
86
|
+
if [ "$SCORE" -ge 80 ]; then
|
|
87
|
+
EMOJI="✅"
|
|
88
|
+
COLOR="brightgreen"
|
|
89
|
+
elif [ "$SCORE" -ge 60 ]; then
|
|
90
|
+
EMOJI="⚠️"
|
|
91
|
+
COLOR="yellow"
|
|
92
|
+
else
|
|
93
|
+
EMOJI="🚨"
|
|
94
|
+
COLOR="red"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Build comment from JSON with markdown sanitization
|
|
98
|
+
TMPFILE=$(mktemp "${RUNNER_TEMP:-/tmp}/prodlint-comment-XXXXXX.md")
|
|
99
|
+
node -e "
|
|
100
|
+
const fs = require('fs');
|
|
101
|
+
const d = JSON.parse(fs.readFileSync('/tmp/prodlint-result.json', 'utf8'));
|
|
102
|
+
const esc = s => String(s).replace(/[[\]()\\*_\`<>]/g, c => '\\\\' + c);
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push('## ' + '$EMOJI' + ' Production Readiness: **' + d.overallScore + '/100**');
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push('| Category | Score | Issues |');
|
|
107
|
+
lines.push('|----------|-------|--------|');
|
|
108
|
+
for (const c of d.categoryScores) {
|
|
109
|
+
const icon = c.score >= 80 ? '🟢' : c.score >= 60 ? '🟡' : '🔴';
|
|
110
|
+
lines.push('| ' + icon + ' ' + esc(c.category) + ' | ' + c.score + '/100 | ' + c.findingCount + ' |');
|
|
111
|
+
}
|
|
112
|
+
if (d.summary.critical > 0) {
|
|
113
|
+
lines.push('');
|
|
114
|
+
lines.push('### Critical Issues');
|
|
115
|
+
lines.push('');
|
|
116
|
+
const crits = d.findings.filter(f => f.severity === 'critical');
|
|
117
|
+
for (const f of crits.slice(0, 10)) {
|
|
118
|
+
lines.push('- \`' + esc(f.ruleId) + '\` \`' + esc(f.file) + ':' + f.line + '\` — ' + esc(f.message));
|
|
119
|
+
}
|
|
120
|
+
if (crits.length > 10) {
|
|
121
|
+
lines.push('- ...and ' + (crits.length - 10) + ' more');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
lines.push('');
|
|
125
|
+
lines.push('---');
|
|
126
|
+
lines.push('*Scanned ' + d.filesScanned + ' files in ' + d.scanDurationMs + 'ms · [prodlint](https://prodlint.com)*');
|
|
127
|
+
fs.writeFileSync('$TMPFILE', lines.join('\n'));
|
|
128
|
+
"
|
|
129
|
+
|
|
130
|
+
# Use the generated file for the comment step
|
|
131
|
+
cp "$TMPFILE" /tmp/prodlint-comment.md
|
|
132
|
+
|
|
133
|
+
- name: Post PR comment
|
|
134
|
+
if: inputs.comment == 'true' && github.event_name == 'pull_request'
|
|
135
|
+
uses: marocchino/sticky-pull-request-comment@v3
|
|
136
|
+
with:
|
|
137
|
+
header: prodlint
|
|
138
|
+
path: /tmp/prodlint-comment.md
|
|
139
|
+
|
|
140
|
+
- name: Check threshold
|
|
141
|
+
if: inputs.threshold != '0'
|
|
142
|
+
shell: bash
|
|
143
|
+
env:
|
|
144
|
+
SCAN_SCORE: ${{ steps.scan.outputs.score }}
|
|
145
|
+
INPUT_THRESHOLD: ${{ inputs.threshold }}
|
|
146
|
+
run: |
|
|
147
|
+
SCORE="$SCAN_SCORE"
|
|
148
|
+
THRESHOLD="$INPUT_THRESHOLD"
|
|
149
|
+
if [ "$SCORE" -lt "$THRESHOLD" ]; then
|
|
150
|
+
echo "::error::Prodlint score ($SCORE) is below threshold ($THRESHOLD)"
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
package/dist/cli.js
CHANGED
|
@@ -522,6 +522,33 @@ async function collectWorkspaceDependencies(root, patterns) {
|
|
|
522
522
|
}
|
|
523
523
|
return deps;
|
|
524
524
|
}
|
|
525
|
+
async function collectNestedPackageDependencies(root) {
|
|
526
|
+
const deps = /* @__PURE__ */ new Set();
|
|
527
|
+
try {
|
|
528
|
+
const pkgFiles = await fg(["**/package.json"], {
|
|
529
|
+
cwd: root,
|
|
530
|
+
absolute: false,
|
|
531
|
+
ignore: ["**/node_modules/**", "package.json"]
|
|
532
|
+
});
|
|
533
|
+
for (const pkgFile of pkgFiles) {
|
|
534
|
+
try {
|
|
535
|
+
const raw = await readFile(resolve(root, pkgFile), "utf-8");
|
|
536
|
+
const pkg = JSON.parse(raw);
|
|
537
|
+
if (pkg.name) deps.add(pkg.name);
|
|
538
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
539
|
+
if (pkg[key] && typeof pkg[key] === "object") {
|
|
540
|
+
for (const dep of Object.keys(pkg[key])) {
|
|
541
|
+
deps.add(dep);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
return deps;
|
|
551
|
+
}
|
|
525
552
|
async function buildProjectContext(root, files) {
|
|
526
553
|
let packageJson = null;
|
|
527
554
|
let declaredDependencies = /* @__PURE__ */ new Set();
|
|
@@ -549,6 +576,10 @@ async function buildProjectContext(root, files) {
|
|
|
549
576
|
declaredDependencies.add(dep);
|
|
550
577
|
}
|
|
551
578
|
}
|
|
579
|
+
const nestedDeps = await collectNestedPackageDependencies(root);
|
|
580
|
+
for (const dep of nestedDeps) {
|
|
581
|
+
declaredDependencies.add(dep);
|
|
582
|
+
}
|
|
552
583
|
for (const dep of declaredDependencies) {
|
|
553
584
|
const framework = DEPENDENCY_TO_FRAMEWORK[dep];
|
|
554
585
|
if (framework) {
|
package/dist/index.js
CHANGED
|
@@ -516,6 +516,33 @@ async function collectWorkspaceDependencies(root, patterns) {
|
|
|
516
516
|
}
|
|
517
517
|
return deps;
|
|
518
518
|
}
|
|
519
|
+
async function collectNestedPackageDependencies(root) {
|
|
520
|
+
const deps = /* @__PURE__ */ new Set();
|
|
521
|
+
try {
|
|
522
|
+
const pkgFiles = await fg(["**/package.json"], {
|
|
523
|
+
cwd: root,
|
|
524
|
+
absolute: false,
|
|
525
|
+
ignore: ["**/node_modules/**", "package.json"]
|
|
526
|
+
});
|
|
527
|
+
for (const pkgFile of pkgFiles) {
|
|
528
|
+
try {
|
|
529
|
+
const raw = await readFile(resolve(root, pkgFile), "utf-8");
|
|
530
|
+
const pkg = JSON.parse(raw);
|
|
531
|
+
if (pkg.name) deps.add(pkg.name);
|
|
532
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
533
|
+
if (pkg[key] && typeof pkg[key] === "object") {
|
|
534
|
+
for (const dep of Object.keys(pkg[key])) {
|
|
535
|
+
deps.add(dep);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
return deps;
|
|
545
|
+
}
|
|
519
546
|
async function buildProjectContext(root, files) {
|
|
520
547
|
let packageJson = null;
|
|
521
548
|
let declaredDependencies = /* @__PURE__ */ new Set();
|
|
@@ -543,6 +570,10 @@ async function buildProjectContext(root, files) {
|
|
|
543
570
|
declaredDependencies.add(dep);
|
|
544
571
|
}
|
|
545
572
|
}
|
|
573
|
+
const nestedDeps = await collectNestedPackageDependencies(root);
|
|
574
|
+
for (const dep of nestedDeps) {
|
|
575
|
+
declaredDependencies.add(dep);
|
|
576
|
+
}
|
|
546
577
|
for (const dep of declaredDependencies) {
|
|
547
578
|
const framework = DEPENDENCY_TO_FRAMEWORK[dep];
|
|
548
579
|
if (framework) {
|
package/dist/mcp.js
CHANGED
|
@@ -525,6 +525,33 @@ async function collectWorkspaceDependencies(root, patterns) {
|
|
|
525
525
|
}
|
|
526
526
|
return deps;
|
|
527
527
|
}
|
|
528
|
+
async function collectNestedPackageDependencies(root) {
|
|
529
|
+
const deps = /* @__PURE__ */ new Set();
|
|
530
|
+
try {
|
|
531
|
+
const pkgFiles = await fg(["**/package.json"], {
|
|
532
|
+
cwd: root,
|
|
533
|
+
absolute: false,
|
|
534
|
+
ignore: ["**/node_modules/**", "package.json"]
|
|
535
|
+
});
|
|
536
|
+
for (const pkgFile of pkgFiles) {
|
|
537
|
+
try {
|
|
538
|
+
const raw = await readFile(resolve(root, pkgFile), "utf-8");
|
|
539
|
+
const pkg = JSON.parse(raw);
|
|
540
|
+
if (pkg.name) deps.add(pkg.name);
|
|
541
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
542
|
+
if (pkg[key] && typeof pkg[key] === "object") {
|
|
543
|
+
for (const dep of Object.keys(pkg[key])) {
|
|
544
|
+
deps.add(dep);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
return deps;
|
|
554
|
+
}
|
|
528
555
|
async function buildProjectContext(root, files) {
|
|
529
556
|
let packageJson = null;
|
|
530
557
|
let declaredDependencies = /* @__PURE__ */ new Set();
|
|
@@ -552,6 +579,10 @@ async function buildProjectContext(root, files) {
|
|
|
552
579
|
declaredDependencies.add(dep);
|
|
553
580
|
}
|
|
554
581
|
}
|
|
582
|
+
const nestedDeps = await collectNestedPackageDependencies(root);
|
|
583
|
+
for (const dep of nestedDeps) {
|
|
584
|
+
declaredDependencies.add(dep);
|
|
585
|
+
}
|
|
555
586
|
for (const dep of declaredDependencies) {
|
|
556
587
|
const framework = DEPENDENCY_TO_FRAMEWORK[dep];
|
|
557
588
|
if (framework) {
|
package/package.json
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "prodlint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Production readiness for vibe-coded apps — know your AI code is ready to ship",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "prodlint contributors",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://github.com/prodlint/prodlint"
|
|
11
|
-
},
|
|
12
|
-
"bugs": {
|
|
13
|
-
"url": "https://github.com/prodlint/prodlint/issues"
|
|
14
|
-
},
|
|
15
|
-
"homepage": "https://prodlint.com",
|
|
16
|
-
"mcpName": "io.github.
|
|
17
|
-
"bin": {
|
|
18
|
-
"prodlint": "./dist/cli.js",
|
|
19
|
-
"prodlint-mcp": "./dist/mcp.js"
|
|
20
|
-
},
|
|
21
|
-
"main": "./dist/index.js",
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"exports": {
|
|
24
|
-
".": {
|
|
25
|
-
"import": "./dist/index.js",
|
|
26
|
-
"types": "./dist/index.d.ts"
|
|
27
|
-
},
|
|
28
|
-
"./mcp": {
|
|
29
|
-
"import": "./dist/mcp.js"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist/**/*.js",
|
|
34
|
-
"dist/**/*.d.ts",
|
|
35
|
-
"action.yml"
|
|
36
|
-
],
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"dev": "tsup --watch",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"test:watch": "vitest",
|
|
42
|
-
"lint": "node dist/cli.js .",
|
|
43
|
-
"audit:supply-chain": "node scripts/supply-chain-audit.cjs",
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
},
|
|
63
|
-
"overrides": {
|
|
64
|
-
"esbuild": "^0.28.1"
|
|
65
|
-
},
|
|
66
|
-
"engines": {
|
|
67
|
-
"node": ">=18"
|
|
68
|
-
},
|
|
69
|
-
"keywords": [
|
|
70
|
-
"lint",
|
|
71
|
-
"linter",
|
|
72
|
-
"security",
|
|
73
|
-
"ai",
|
|
74
|
-
"ai-code",
|
|
75
|
-
"vibe-coding",
|
|
76
|
-
"production-readiness",
|
|
77
|
-
"code-quality",
|
|
78
|
-
"code-scanner",
|
|
79
|
-
"next.js",
|
|
80
|
-
"react",
|
|
81
|
-
"typescript",
|
|
82
|
-
"static-analysis",
|
|
83
|
-
"cli",
|
|
84
|
-
"scanner",
|
|
85
|
-
"mcp",
|
|
86
|
-
"mcp-server",
|
|
87
|
-
"cursor",
|
|
88
|
-
"copilot",
|
|
89
|
-
"v0",
|
|
90
|
-
"bolt",
|
|
91
|
-
"windsurf",
|
|
92
|
-
"claude",
|
|
93
|
-
"github-action",
|
|
94
|
-
"eslint-alternative",
|
|
95
|
-
"devtools"
|
|
96
|
-
]
|
|
97
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "prodlint",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Production readiness for vibe-coded apps — know your AI code is ready to ship",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "prodlint contributors",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/prodlint/prodlint"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/prodlint/prodlint/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://prodlint.com",
|
|
16
|
+
"mcpName": "io.github.prodlint/prodlint",
|
|
17
|
+
"bin": {
|
|
18
|
+
"prodlint": "./dist/cli.js",
|
|
19
|
+
"prodlint-mcp": "./dist/mcp.js"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./mcp": {
|
|
29
|
+
"import": "./dist/mcp.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/**/*.js",
|
|
34
|
+
"dist/**/*.d.ts",
|
|
35
|
+
"action.yml"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:watch": "vitest",
|
|
42
|
+
"lint": "node dist/cli.js .",
|
|
43
|
+
"audit:supply-chain": "node scripts/supply-chain-audit.cjs",
|
|
44
|
+
"verify:release": "node scripts/verify-release.cjs",
|
|
45
|
+
"sync:versions": "node scripts/sync-versions.cjs",
|
|
46
|
+
"version": "node scripts/sync-versions.cjs",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@babel/parser": "^7.29.7",
|
|
51
|
+
"@babel/types": "^7.29.7",
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
53
|
+
"fast-glob": "^3.3.3",
|
|
54
|
+
"picocolors": "^1.1.1",
|
|
55
|
+
"zod": "^4.4.3"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^26.1.2",
|
|
59
|
+
"tsup": "^8.4.0",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vitest": "^3.2.7"
|
|
62
|
+
},
|
|
63
|
+
"overrides": {
|
|
64
|
+
"esbuild": "^0.28.1"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=18"
|
|
68
|
+
},
|
|
69
|
+
"keywords": [
|
|
70
|
+
"lint",
|
|
71
|
+
"linter",
|
|
72
|
+
"security",
|
|
73
|
+
"ai",
|
|
74
|
+
"ai-code",
|
|
75
|
+
"vibe-coding",
|
|
76
|
+
"production-readiness",
|
|
77
|
+
"code-quality",
|
|
78
|
+
"code-scanner",
|
|
79
|
+
"next.js",
|
|
80
|
+
"react",
|
|
81
|
+
"typescript",
|
|
82
|
+
"static-analysis",
|
|
83
|
+
"cli",
|
|
84
|
+
"scanner",
|
|
85
|
+
"mcp",
|
|
86
|
+
"mcp-server",
|
|
87
|
+
"cursor",
|
|
88
|
+
"copilot",
|
|
89
|
+
"v0",
|
|
90
|
+
"bolt",
|
|
91
|
+
"windsurf",
|
|
92
|
+
"claude",
|
|
93
|
+
"github-action",
|
|
94
|
+
"eslint-alternative",
|
|
95
|
+
"devtools"
|
|
96
|
+
]
|
|
97
|
+
}
|