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.
Files changed (4) hide show
  1. package/README.md +51 -3
  2. package/bin/cli.js +30 -2
  3. package/index.js +106 -44
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # predeploy-check
2
2
 
3
- > Catch deployment failures **before** you push. Scans your project for known Render & Vercel pitfalls.
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
- Clean, colored terminal output with:
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
- Exit code `1` if any failures, `0` otherwise.
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
- console.log(pkg.version);
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
- * Returns exit code: 1 if any ❌, 0 otherwise.
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 runAllChecks(projectRoot, options = {}) {
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
- let hasFail = false;
80
- let passCount = 0;
81
- let warnCount = 0;
82
- let failCount = 0;
83
- let skipCount = 0;
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 (passCount > 0) parts.push(chalk.green(`${passCount} passed`));
120
- if (warnCount > 0) parts.push(chalk.yellow(`${warnCount} warnings`));
121
- if (failCount > 0) parts.push(chalk.red(`${failCount} failed`));
122
- if (skipCount > 0) parts.push(chalk.gray(`${skipCount} skipped`));
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 (warnCount > 0) {
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 hasFail ? 1 : 0;
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.1.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"