prodlint 0.7.1 → 0.8.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/action.yml CHANGED
@@ -1,152 +1,152 @@
1
- name: 'Prodlint'
2
- description: 'The linter for vibe-coded apps — catch what AI coding tools miss'
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' + ' Prodlint Score: **' + 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@v2
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@v2
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