predeploy-check 1.1.1 → 1.2.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 +51 -3
- package/bin/cli.js +30 -2
- package/index.js +106 -44
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# predeploy-check
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Stop deployment failures before they happen. `predeploy-check` scans your project for the most common deployment mistakes across Vercel and Render, including missing environment variables, case-sensitive imports, Python wheel compatibility, start command issues, and more — before you push your code.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx predeploy-check
|
|
@@ -21,12 +21,19 @@ No install required — just run it in your project directory.
|
|
|
21
21
|
|
|
22
22
|
## Output
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Two output modes, same underlying checks:
|
|
25
|
+
|
|
26
|
+
**Terminal (default)** — clean, colored output with:
|
|
25
27
|
- ✅ / ⚠️ / ❌ per check
|
|
26
28
|
- File and line context
|
|
27
29
|
- One-line suggested fix
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
**JSON** (`--json`) — a single structured JSON object on stdout, with no
|
|
32
|
+
colors or decoration, designed for CI dashboards, GitHub bots, editor
|
|
33
|
+
extensions, or any tool that needs to parse the results programmatically
|
|
34
|
+
rather than read them. See [JSON output](#json-output) below for the shape.
|
|
35
|
+
|
|
36
|
+
Exit code `1` if any ❌ failures, `0` otherwise — in both output modes.
|
|
30
37
|
|
|
31
38
|
## Usage
|
|
32
39
|
|
|
@@ -41,6 +48,12 @@ npx predeploy-check ./my-project
|
|
|
41
48
|
# relying on the built-in known-package list (slower, needs internet)
|
|
42
49
|
npx predeploy-check --live
|
|
43
50
|
|
|
51
|
+
# Output machine-readable JSON instead of colored terminal text
|
|
52
|
+
npx predeploy-check --json
|
|
53
|
+
|
|
54
|
+
# Combine flags freely
|
|
55
|
+
npx predeploy-check --live --json ./my-project
|
|
56
|
+
|
|
44
57
|
# Show help
|
|
45
58
|
npx predeploy-check --help
|
|
46
59
|
```
|
|
@@ -52,6 +65,41 @@ Pass `--live` to query PyPI directly for the exact pinned version in your
|
|
|
52
65
|
`requirements.txt`, which turns a "might be missing" warning into a
|
|
53
66
|
confirmed pass or fail.
|
|
54
67
|
|
|
68
|
+
## JSON output
|
|
69
|
+
|
|
70
|
+
`--json` prints a single JSON object to stdout, with nothing else mixed
|
|
71
|
+
in — safe to pipe directly into `JSON.parse()`, `jq`, or any tool that
|
|
72
|
+
expects clean machine-readable output:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx predeploy-check --json | jq '.summary'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Shape:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"tool": "predeploy-check",
|
|
83
|
+
"version": "1.2.0",
|
|
84
|
+
"projectRoot": "/path/to/project",
|
|
85
|
+
"live": false,
|
|
86
|
+
"summary": { "passed": 4, "warnings": 1, "failed": 1, "skipped": 0 },
|
|
87
|
+
"willLikelyFail": true,
|
|
88
|
+
"checks": [
|
|
89
|
+
{
|
|
90
|
+
"check": "Render Start Command: missing start configuration",
|
|
91
|
+
"status": "fail",
|
|
92
|
+
"message": "...",
|
|
93
|
+
"fix": "Add a \"start\" script to package.json...",
|
|
94
|
+
"details": [ { "file": "package.json", "message": "..." } ]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--help --json` and `--version --json` also return structured JSON
|
|
101
|
+
instead of plain text, for tools that want to introspect the CLI itself.
|
|
102
|
+
|
|
55
103
|
## Adding custom checks
|
|
56
104
|
|
|
57
105
|
Create a new file in the `checks/` folder:
|
package/bin/cli.js
CHANGED
|
@@ -6,8 +6,31 @@ const path = require('path');
|
|
|
6
6
|
const { runAllChecks } = require('../index');
|
|
7
7
|
|
|
8
8
|
const args = process.argv.slice(2);
|
|
9
|
+
const wantsJson = args.includes('--json');
|
|
9
10
|
|
|
10
11
|
if (args.includes('--help') || args.includes('-h')) {
|
|
12
|
+
if (wantsJson) {
|
|
13
|
+
console.log(JSON.stringify({
|
|
14
|
+
tool: 'predeploy-check',
|
|
15
|
+
usage: 'npx predeploy-check [directory] [options]',
|
|
16
|
+
options: {
|
|
17
|
+
'-h, --help': 'Show this help message',
|
|
18
|
+
'-v, --version': 'Show version number',
|
|
19
|
+
'--live': 'Verify Python wheel availability against PyPI (slower, needs internet)',
|
|
20
|
+
'--json': 'Output machine-readable JSON instead of colored terminal output',
|
|
21
|
+
},
|
|
22
|
+
checks: [
|
|
23
|
+
{ id: 1, name: 'Python + Render', description: 'Rust-compiled deps on unsupported Python versions' },
|
|
24
|
+
{ id: 2, name: 'ESLint + Vercel', description: 'Peer-dependency mismatches in Next.js projects' },
|
|
25
|
+
{ id: 3, name: 'Case Sensitivity', description: 'Import paths that differ from actual filenames' },
|
|
26
|
+
{ id: 4, name: 'Missing Engines', description: 'No "engines" field in package.json' },
|
|
27
|
+
{ id: 5, name: 'Env Var Check', description: 'process.env references missing from .env files' },
|
|
28
|
+
{ id: 6, name: 'Render Start Cmd', description: 'Missing start command for Render deployments' },
|
|
29
|
+
],
|
|
30
|
+
}, null, 2));
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
console.log(`
|
|
12
35
|
predeploy-check — catch deployment failures before they happen
|
|
13
36
|
|
|
@@ -21,6 +44,7 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
21
44
|
-h, --help Show this help message
|
|
22
45
|
-v, --version Show version number
|
|
23
46
|
--live Verify Python wheel availability against PyPI (slower, needs internet)
|
|
47
|
+
--json Output machine-readable JSON instead of colored terminal output
|
|
24
48
|
|
|
25
49
|
Checks:
|
|
26
50
|
1. Python + Render: Rust-compiled deps on unsupported Python versions
|
|
@@ -35,12 +59,16 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
35
59
|
|
|
36
60
|
if (args.includes('--version') || args.includes('-v')) {
|
|
37
61
|
const pkg = require('../package.json');
|
|
38
|
-
|
|
62
|
+
if (wantsJson) {
|
|
63
|
+
console.log(JSON.stringify({ tool: 'predeploy-check', version: pkg.version }, null, 2));
|
|
64
|
+
} else {
|
|
65
|
+
console.log(pkg.version);
|
|
66
|
+
}
|
|
39
67
|
process.exit(0);
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
const projectRoot = path.resolve(args.find((a) => !a.startsWith('-')) || process.cwd());
|
|
43
|
-
const options = { live: args.includes('--live') };
|
|
71
|
+
const options = { live: args.includes('--live'), json: wantsJson };
|
|
44
72
|
|
|
45
73
|
runAllChecks(projectRoot, options).then((exitCode) => {
|
|
46
74
|
process.exit(exitCode);
|
package/index.js
CHANGED
|
@@ -60,10 +60,53 @@ function formatResult(result) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Run all checks against the given project root
|
|
64
|
-
*
|
|
63
|
+
* Run all checks against the given project root and collect their raw
|
|
64
|
+
* results, without printing anything. This is the single source of truth
|
|
65
|
+
* that both the terminal formatter and the JSON formatter consume, so the
|
|
66
|
+
* two output modes can never drift out of sync with each other.
|
|
65
67
|
*/
|
|
66
|
-
async function
|
|
68
|
+
async function collectResults(projectRoot, options = {}) {
|
|
69
|
+
const allResults = [];
|
|
70
|
+
|
|
71
|
+
for (const check of checks) {
|
|
72
|
+
try {
|
|
73
|
+
const results = await check.run(projectRoot, options);
|
|
74
|
+
const resultArray = Array.isArray(results) ? results : [results];
|
|
75
|
+
|
|
76
|
+
for (const result of resultArray) {
|
|
77
|
+
allResults.push({ checkName: check.name, ...result });
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
allResults.push({
|
|
81
|
+
checkName: check.name,
|
|
82
|
+
status: 'error',
|
|
83
|
+
message: `${check.name}: internal error — ${err.message}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return allResults;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Compute summary counts and the overall exit code from a results array.
|
|
93
|
+
* Both output modes (terminal and JSON) use this so the numbers always
|
|
94
|
+
* match regardless of how they're displayed.
|
|
95
|
+
*/
|
|
96
|
+
function summarize(allResults) {
|
|
97
|
+
const counts = { pass: 0, warn: 0, fail: 0, skip: 0, error: 0 };
|
|
98
|
+
for (const result of allResults) {
|
|
99
|
+
counts[result.status] = (counts[result.status] || 0) + 1;
|
|
100
|
+
}
|
|
101
|
+
const hasFail = counts.fail > 0 || counts.error > 0;
|
|
102
|
+
return { counts, hasFail, exitCode: hasFail ? 1 : 0 };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Print results to the terminal with colors, icons, and a summary —
|
|
107
|
+
* the original human-readable output format.
|
|
108
|
+
*/
|
|
109
|
+
function printTerminalOutput(projectRoot, options, allResults, summary) {
|
|
67
110
|
console.log('');
|
|
68
111
|
console.log(chalk.bold.underline(` predeploy-check`) + chalk.dim(` scanning ${projectRoot}`));
|
|
69
112
|
if (options.live) {
|
|
@@ -76,62 +119,81 @@ async function runAllChecks(projectRoot, options = {}) {
|
|
|
76
119
|
console.log(chalk.dim(' ─'.repeat(30)));
|
|
77
120
|
console.log('');
|
|
78
121
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (const check of checks) {
|
|
86
|
-
try {
|
|
87
|
-
const results = await check.run(projectRoot, options);
|
|
88
|
-
|
|
89
|
-
// A check can return a single result or an array of results
|
|
90
|
-
const resultArray = Array.isArray(results) ? results : [results];
|
|
91
|
-
|
|
92
|
-
for (const result of resultArray) {
|
|
93
|
-
console.log(formatResult(result));
|
|
94
|
-
console.log('');
|
|
95
|
-
|
|
96
|
-
if (result.status === 'fail') {
|
|
97
|
-
hasFail = true;
|
|
98
|
-
failCount++;
|
|
99
|
-
} else if (result.status === 'warn') {
|
|
100
|
-
warnCount++;
|
|
101
|
-
} else if (result.status === 'skip') {
|
|
102
|
-
skipCount++;
|
|
103
|
-
} else {
|
|
104
|
-
passCount++;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
} catch (err) {
|
|
108
|
-
console.log(chalk.red(`❌ ${check.name}: internal error — ${err.message}`));
|
|
109
|
-
console.log('');
|
|
110
|
-
hasFail = true;
|
|
111
|
-
failCount++;
|
|
122
|
+
for (const result of allResults) {
|
|
123
|
+
if (result.status === 'error') {
|
|
124
|
+
console.log(chalk.red(`❌ ${result.message}`));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(formatResult(result));
|
|
112
127
|
}
|
|
128
|
+
console.log('');
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
// Summary bar
|
|
116
131
|
console.log(chalk.dim(' ─'.repeat(30)));
|
|
117
132
|
|
|
133
|
+
const { counts, hasFail } = summary;
|
|
118
134
|
const parts = [];
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
if (
|
|
122
|
-
if (
|
|
135
|
+
if (counts.pass > 0) parts.push(chalk.green(`${counts.pass} passed`));
|
|
136
|
+
if (counts.warn > 0) parts.push(chalk.yellow(`${counts.warn} warnings`));
|
|
137
|
+
if (counts.fail + counts.error > 0) parts.push(chalk.red(`${counts.fail + counts.error} failed`));
|
|
138
|
+
if (counts.skip > 0) parts.push(chalk.gray(`${counts.skip} skipped`));
|
|
123
139
|
|
|
124
140
|
console.log(`\n ${chalk.bold('Summary:')} ${parts.join(chalk.dim(' · '))}`);
|
|
125
141
|
|
|
126
142
|
if (hasFail) {
|
|
127
143
|
console.log(chalk.red.bold('\n Deploy will likely fail. Fix ❌ issues above.\n'));
|
|
128
|
-
} else if (
|
|
144
|
+
} else if (counts.warn > 0) {
|
|
129
145
|
console.log(chalk.yellow('\n Some warnings detected. Review ⚠️ items above.\n'));
|
|
130
146
|
} else {
|
|
131
147
|
console.log(chalk.green.bold('\n All checks passed! You\'re good to deploy. 🚀\n'));
|
|
132
148
|
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Print results as a single machine-readable JSON object to stdout.
|
|
153
|
+
* No colors, no decoration — designed for CI dashboards, GitHub bots,
|
|
154
|
+
* editor extensions, or anything else that needs to parse the output
|
|
155
|
+
* programmatically rather than read it.
|
|
156
|
+
*/
|
|
157
|
+
function printJsonOutput(projectRoot, options, allResults, summary) {
|
|
158
|
+
const output = {
|
|
159
|
+
tool: 'predeploy-check',
|
|
160
|
+
version: require('./package.json').version,
|
|
161
|
+
projectRoot,
|
|
162
|
+
live: Boolean(options.live),
|
|
163
|
+
summary: {
|
|
164
|
+
passed: summary.counts.pass,
|
|
165
|
+
warnings: summary.counts.warn,
|
|
166
|
+
failed: summary.counts.fail + summary.counts.error,
|
|
167
|
+
skipped: summary.counts.skip,
|
|
168
|
+
},
|
|
169
|
+
willLikelyFail: summary.hasFail,
|
|
170
|
+
checks: allResults.map((r) => ({
|
|
171
|
+
check: r.checkName,
|
|
172
|
+
status: r.status,
|
|
173
|
+
message: r.message,
|
|
174
|
+
fix: r.fix || null,
|
|
175
|
+
details: r.details || [],
|
|
176
|
+
})),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
console.log(JSON.stringify(output, null, 2));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run all checks against the given project root.
|
|
184
|
+
* Returns exit code: 1 if any ❌, 0 otherwise.
|
|
185
|
+
*/
|
|
186
|
+
async function runAllChecks(projectRoot, options = {}) {
|
|
187
|
+
const allResults = await collectResults(projectRoot, options);
|
|
188
|
+
const summary = summarize(allResults);
|
|
189
|
+
|
|
190
|
+
if (options.json) {
|
|
191
|
+
printJsonOutput(projectRoot, options, allResults, summary);
|
|
192
|
+
} else {
|
|
193
|
+
printTerminalOutput(projectRoot, options, allResults, summary);
|
|
194
|
+
}
|
|
133
195
|
|
|
134
|
-
return
|
|
196
|
+
return summary.exitCode;
|
|
135
197
|
}
|
|
136
198
|
|
|
137
|
-
module.exports = { runAllChecks };
|
|
199
|
+
module.exports = { runAllChecks, collectResults, summarize };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "predeploy-check",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Scan a project folder and flag known deployment-failure patterns for Render and Vercel before you push.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"url": "https://github.com/Alok-Fusion/predeploy_check/issues"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"chalk": "^4.1.2"
|
|
34
|
+
"chalk": "^4.1.2",
|
|
35
|
+
"predeploy-check": "^1.1.1"
|
|
35
36
|
},
|
|
36
37
|
"engines": {
|
|
37
38
|
"node": ">=14.0.0"
|