@adhix11/shipguard 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 adhix11
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # ๐Ÿšข ShipGuard
2
+
3
+ **Catch stupid production mistakes before you ship.**
4
+
5
+ A zero-config preflight scanner for JavaScript and TypeScript projects. Detect secrets, missing env docs, debug code, risky test flags, and basic package readiness before you ship.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@adhix11/shipguard.svg)](https://www.npmjs.com/package/@adhix11/shipguard)
8
+ [![license](https://img.shields.io/npm/l/@adhix11/shipguard.svg)](https://github.com/adhix11/shipguard/blob/main/LICENSE)
9
+
10
+ ---
11
+
12
+ ## Why?
13
+
14
+ Developers often forget small but dangerous things before pushing code:
15
+
16
+ | Pain | Example |
17
+ |------|---------|
18
+ | **Secret leakage** | AWS key, JWT secret, DB password inside code |
19
+ | **Missing env docs** | Code uses `process.env.DB_URL`, but `.env.example` doesn't mention it |
20
+ | **Debug code** | `console.log`, `debugger`, temporary test code left in |
21
+ | **Test mistakes** | `describe.only`, `it.only`, skipped tests |
22
+ | **Risky package scripts** | Suspicious `postinstall`, `preinstall`, shell commands |
23
+ | **Poor release readiness** | No README, no LICENSE, missing package.json fields |
24
+
25
+ With AI-generated code accelerating development, the bottleneck has shifted from **writing code** to **reviewing and validating it**. ShipGuard helps you catch the things that slip through.
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ Run with zero setup:
32
+
33
+ ```bash
34
+ npx @adhix11/shipguard
35
+ ```
36
+
37
+ Or install globally:
38
+
39
+ ```bash
40
+ npm install -g @adhix11/shipguard
41
+ shipguard
42
+ ```
43
+
44
+ ---
45
+
46
+ ## What It Scans
47
+
48
+ ### ๐Ÿ” Secrets Risk
49
+ - AWS Access Keys & Secret Keys
50
+ - Private key blocks (`-----BEGIN PRIVATE KEY-----`)
51
+ - MongoDB connection URIs (`mongodb+srv://...`)
52
+ - Hardcoded passwords (`password = "..."`)
53
+ - JWT secrets
54
+ - API keys & tokens (GitHub, OpenAI, Stripe, Slack)
55
+
56
+ ### ๐Ÿ“‹ Missing Env Documentation
57
+ - Finds all `process.env.XYZ` and `import.meta.env.XYZ` in your code
58
+ - Cross-references with `.env.example`
59
+ - Reports any environment variables missing from documentation
60
+
61
+ ### ๐Ÿ› Debug Code
62
+ - `console.log`, `console.debug`, `console.warn`
63
+ - `debugger` statements
64
+ - `TODO`, `FIXME`, `HACK`, `XXX` comments
65
+ - `alert()` calls
66
+
67
+ ### ๐Ÿงช Test Risk
68
+ - `describe.only()`, `it.only()`, `test.only()` โ€” focused tests
69
+ - `describe.skip()`, `it.skip()`, `test.skip()` โ€” skipped tests
70
+ - `fdescribe`, `fit`, `xit`, `xdescribe` โ€” Jasmine equivalents
71
+
72
+ ### ๐Ÿ“ฆ Package Health
73
+ - README.md exists
74
+ - LICENSE exists
75
+ - `package.json` has `name`, `version`, `description`
76
+ - Entry point (`main`, `bin`, `module`, or `exports`) is defined
77
+ - Risky lifecycle scripts (`postinstall`, `preinstall`) with suspicious commands
78
+
79
+ ---
80
+
81
+ ## Example Output
82
+
83
+ ```
84
+ ๐Ÿšข ShipGuard Report
85
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
86
+
87
+ โŒ Secrets Risk
88
+ src/config/db.ts
89
+ โœ— Possible MongoDB URI found (line 8)
90
+
91
+ โš ๏ธ Missing Env Documentation
92
+ .env.example
93
+ โ— .env.example is missing 3 variables:
94
+ โ— Missing: DB_URL
95
+ โ— Missing: JWT_SECRET
96
+ โ— Missing: AWS_REGION
97
+
98
+ โš ๏ธ Debug Code Found
99
+ src/app.ts
100
+ โ— console.log found (line 42)
101
+
102
+ โŒ Test Risk
103
+ src/__tests__/user.test.ts
104
+ โœ— it.only found โ€” other tests will be skipped (line 18)
105
+
106
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
107
+ Summary:
108
+ 2 critical issues
109
+ 4 warnings
110
+
111
+ โœ— Run failed. Fix critical issues before shipping.
112
+ ```
113
+
114
+ ---
115
+
116
+ ## CLI Options
117
+
118
+ | Option | Description |
119
+ |--------|-------------|
120
+ | `--strict` | Treat warnings as errors (exit code 1 for any issue) |
121
+ | `--json` | Output results as JSON for CI/CD integration |
122
+ | `--ignore <dirs>` | Comma-separated directories to ignore |
123
+ | `--help`, `-h` | Show help message |
124
+ | `--version`, `-v` | Show version number |
125
+
126
+ ### Examples
127
+
128
+ ```bash
129
+ # Basic scan
130
+ npx @adhix11/shipguard
131
+
132
+ # Strict mode โ€” fail on any warning
133
+ npx @adhix11/shipguard --strict
134
+
135
+ # JSON output for CI pipelines
136
+ npx @adhix11/shipguard --json
137
+
138
+ # Ignore specific directories
139
+ npx @adhix11/shipguard --ignore "dist,build,coverage"
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Default Ignores
145
+
146
+ ShipGuard automatically skips these directories:
147
+
148
+ `node_modules`, `dist`, `build`, `.git`, `.next`, `.nuxt`, `coverage`, `.cache`, `.turbo`, `.output`, `out`
149
+
150
+ And these files: `*.min.js`, `*.min.css`, `*.map`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
151
+
152
+ ---
153
+
154
+ ## CI/CD Integration
155
+
156
+ ### GitHub Actions
157
+
158
+ ```yaml
159
+ - name: ShipGuard Preflight Check
160
+ run: npx @adhix11/shipguard --strict
161
+ ```
162
+
163
+ ### Pre-commit Hook (with Husky)
164
+
165
+ ```bash
166
+ npx husky add .husky/pre-commit "npx @adhix11/shipguard"
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Exit Codes
172
+
173
+ | Code | Meaning |
174
+ |------|---------|
175
+ | `0` | All clear (or warnings only without `--strict`) |
176
+ | `1` | Critical issues found (or warnings in `--strict` mode) |
177
+ | `2` | ShipGuard internal error |
178
+
179
+ ---
180
+
181
+ ## License
182
+
183
+ MIT ยฉ [adhix11](https://github.com/adhix11)
package/dist/index.js ADDED
@@ -0,0 +1,733 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { resolve as resolve4 } from "path";
5
+ import pc2 from "picocolors";
6
+
7
+ // src/utils/readFiles.ts
8
+ import fg from "fast-glob";
9
+ import { readFile } from "fs/promises";
10
+ import { resolve } from "path";
11
+ var DEFAULT_IGNORES = [
12
+ "node_modules",
13
+ "dist",
14
+ "build",
15
+ ".git",
16
+ ".next",
17
+ ".nuxt",
18
+ "coverage",
19
+ ".cache",
20
+ ".turbo",
21
+ ".output",
22
+ "out",
23
+ "*.min.js",
24
+ "*.min.css",
25
+ "*.map",
26
+ "package-lock.json",
27
+ "yarn.lock",
28
+ "pnpm-lock.yaml"
29
+ ];
30
+ var SCAN_EXTENSIONS = [
31
+ "ts",
32
+ "tsx",
33
+ "js",
34
+ "jsx",
35
+ "mjs",
36
+ "cjs",
37
+ "json",
38
+ "yaml",
39
+ "yml",
40
+ "env",
41
+ "env.local",
42
+ "env.development",
43
+ "env.production",
44
+ "env.test",
45
+ "toml",
46
+ "cfg",
47
+ "conf",
48
+ "ini"
49
+ ];
50
+ async function getFiles(cwd, extraIgnores = []) {
51
+ const ignorePatterns = [...DEFAULT_IGNORES, ...extraIgnores].map(
52
+ (pattern) => `**/${pattern}/**`
53
+ );
54
+ const extensionGlob = `**/*.{${SCAN_EXTENSIONS.join(",")}}`;
55
+ const paths = await fg(extensionGlob, {
56
+ cwd,
57
+ ignore: ignorePatterns,
58
+ dot: true,
59
+ absolute: false,
60
+ onlyFiles: true
61
+ });
62
+ const files = [];
63
+ for (const relativePath of paths) {
64
+ try {
65
+ const absolutePath = resolve(cwd, relativePath);
66
+ const content = await readFile(absolutePath, "utf-8");
67
+ const lines = content.split("\n");
68
+ files.push({ relativePath, absolutePath, content, lines });
69
+ } catch {
70
+ }
71
+ }
72
+ return files;
73
+ }
74
+ async function readFileContent(filePath) {
75
+ try {
76
+ return await readFile(filePath, "utf-8");
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ // src/utils/report.ts
83
+ import pc from "picocolors";
84
+ function printReport(results, options) {
85
+ if (options.json) {
86
+ printJsonReport(results);
87
+ return;
88
+ }
89
+ printFormattedReport(results, options);
90
+ }
91
+ function getExitCode(results, options) {
92
+ const criticalCount = countBySeverity(results, "critical");
93
+ const warningCount = countBySeverity(results, "warning");
94
+ if (criticalCount > 0) return 1;
95
+ if (options.strict && warningCount > 0) return 1;
96
+ return 0;
97
+ }
98
+ function printJsonReport(results) {
99
+ const output = {
100
+ tool: "@adhix11/shipguard",
101
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
102
+ results: results.map((r) => ({
103
+ scanner: r.name,
104
+ issues: r.issues.map((i) => ({
105
+ file: i.file,
106
+ line: i.line ?? null,
107
+ message: i.message,
108
+ severity: i.severity
109
+ }))
110
+ })),
111
+ summary: {
112
+ critical: countBySeverity(results, "critical"),
113
+ warnings: countBySeverity(results, "warning"),
114
+ info: countBySeverity(results, "info"),
115
+ totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0)
116
+ }
117
+ };
118
+ console.log(JSON.stringify(output, null, 2));
119
+ }
120
+ function printFormattedReport(results, options) {
121
+ console.log();
122
+ console.log(
123
+ pc.bold(pc.cyan("\u{1F6A2} ShipGuard Report"))
124
+ );
125
+ console.log(pc.dim("\u2500".repeat(50)));
126
+ console.log();
127
+ let hasIssues = false;
128
+ for (const result of results) {
129
+ if (result.issues.length === 0) continue;
130
+ hasIssues = true;
131
+ const hasCritical = result.issues.some((i) => i.severity === "critical");
132
+ const headerColor = hasCritical ? pc.red : pc.yellow;
133
+ const icon = hasCritical ? "\u274C" : "\u26A0\uFE0F";
134
+ console.log(headerColor(pc.bold(`${icon} ${result.name}`)));
135
+ const byFile = groupByFile(result);
136
+ for (const [file, issues] of Object.entries(byFile)) {
137
+ console.log(pc.dim(` ${file}`));
138
+ for (const issue of issues) {
139
+ const lineInfo = issue.line ? pc.dim(` (line ${issue.line})`) : "";
140
+ const bullet = issue.severity === "critical" ? pc.red(" \u2717") : issue.severity === "warning" ? pc.yellow(" \u25CF") : pc.blue(" \u25CB");
141
+ console.log(`${bullet} ${issue.message}${lineInfo}`);
142
+ }
143
+ }
144
+ console.log();
145
+ }
146
+ const criticalCount = countBySeverity(results, "critical");
147
+ const warningCount = countBySeverity(results, "warning");
148
+ const infoCount = countBySeverity(results, "info");
149
+ console.log(pc.dim("\u2500".repeat(50)));
150
+ console.log(pc.bold("Summary:"));
151
+ if (criticalCount > 0) {
152
+ console.log(pc.red(` ${criticalCount} critical issue${criticalCount !== 1 ? "s" : ""}`));
153
+ }
154
+ if (warningCount > 0) {
155
+ console.log(pc.yellow(` ${warningCount} warning${warningCount !== 1 ? "s" : ""}`));
156
+ }
157
+ if (infoCount > 0) {
158
+ console.log(pc.blue(` ${infoCount} info`));
159
+ }
160
+ console.log();
161
+ const exitCode = getExitCode(results, options);
162
+ if (!hasIssues) {
163
+ console.log(pc.green(pc.bold("\u2714 All clear! Ship with confidence. \u{1F680}")));
164
+ } else if (exitCode === 0) {
165
+ console.log(
166
+ pc.yellow(pc.bold("\u26A0 Warnings found. Review before shipping."))
167
+ );
168
+ } else {
169
+ console.log(
170
+ pc.red(pc.bold("\u2717 Run failed. Fix critical issues before shipping."))
171
+ );
172
+ }
173
+ console.log();
174
+ }
175
+ function countBySeverity(results, severity) {
176
+ return results.reduce(
177
+ (sum, r) => sum + r.issues.filter((i) => i.severity === severity).length,
178
+ 0
179
+ );
180
+ }
181
+ function groupByFile(result) {
182
+ const grouped = {};
183
+ for (const issue of result.issues) {
184
+ const key = issue.file || "(project)";
185
+ if (!grouped[key]) grouped[key] = [];
186
+ grouped[key].push(issue);
187
+ }
188
+ return grouped;
189
+ }
190
+
191
+ // src/scanners/secrets.ts
192
+ var SECRET_PATTERNS = [
193
+ {
194
+ pattern: /AWS_ACCESS_KEY_ID\s*[=:]\s*['"]?[A-Z0-9]{16,}/i,
195
+ label: "Possible AWS Access Key found"
196
+ },
197
+ {
198
+ pattern: /AWS_SECRET_ACCESS_KEY\s*[=:]\s*['"]?[A-Za-z0-9/+=]{30,}/i,
199
+ label: "Possible AWS Secret Key found"
200
+ },
201
+ {
202
+ pattern: /AKIA[0-9A-Z]{16}/,
203
+ label: "AWS Access Key ID pattern detected"
204
+ },
205
+ {
206
+ pattern: /SECRET_KEY\s*[=:]\s*['"]?[^\s'"]{8,}/i,
207
+ label: "Possible SECRET_KEY assignment found"
208
+ },
209
+ {
210
+ pattern: /PRIVATE_KEY\s*[=:]\s*['"]?[^\s'"]{8,}/i,
211
+ label: "Possible PRIVATE_KEY assignment found"
212
+ },
213
+ {
214
+ pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/,
215
+ label: "Private key block detected"
216
+ },
217
+ {
218
+ pattern: /mongodb\+srv:\/\/[^\s'"]+/i,
219
+ label: "Possible MongoDB connection URI found"
220
+ },
221
+ {
222
+ pattern: /mongodb:\/\/[^\s'"]+/i,
223
+ label: "Possible MongoDB connection URI found"
224
+ },
225
+ {
226
+ pattern: /password\s*[=:]\s*['"][^'"]{4,}['"]/i,
227
+ label: "Possible hardcoded password found"
228
+ },
229
+ {
230
+ pattern: /jwtSecret\s*[=:]\s*['"][^'"]+['"]/i,
231
+ label: "Possible JWT secret found"
232
+ },
233
+ {
234
+ pattern: /jwt_secret\s*[=:]\s*['"][^'"]+['"]/i,
235
+ label: "Possible JWT secret found"
236
+ },
237
+ {
238
+ pattern: /api[_-]?key\s*[=:]\s*['"][^'"]{8,}['"]/i,
239
+ label: "Possible API key found"
240
+ },
241
+ {
242
+ pattern: /api[_-]?secret\s*[=:]\s*['"][^'"]{8,}['"]/i,
243
+ label: "Possible API secret found"
244
+ },
245
+ {
246
+ pattern: /ghp_[A-Za-z0-9_]{36,}/,
247
+ label: "GitHub personal access token detected"
248
+ },
249
+ {
250
+ pattern: /sk-[A-Za-z0-9]{32,}/,
251
+ label: "Possible OpenAI/Stripe secret key detected"
252
+ },
253
+ {
254
+ pattern: /xox[bpras]-[A-Za-z0-9-]+/,
255
+ label: "Possible Slack token detected"
256
+ }
257
+ ];
258
+ var SKIP_PATTERNS = [
259
+ /\.env\.example$/,
260
+ /\.env\.sample$/,
261
+ /\.env\.template$/,
262
+ /package-lock\.json$/,
263
+ /yarn\.lock$/,
264
+ /pnpm-lock\.yaml$/
265
+ ];
266
+ function scanSecrets(files) {
267
+ const issues = [];
268
+ for (const file of files) {
269
+ if (SKIP_PATTERNS.some((p) => p.test(file.relativePath))) continue;
270
+ for (let i = 0; i < file.lines.length; i++) {
271
+ const line = file.lines[i];
272
+ for (const { pattern, label } of SECRET_PATTERNS) {
273
+ if (pattern.test(line)) {
274
+ issues.push({
275
+ file: file.relativePath,
276
+ line: i + 1,
277
+ message: label,
278
+ severity: "critical"
279
+ });
280
+ break;
281
+ }
282
+ }
283
+ }
284
+ }
285
+ return {
286
+ name: "Secrets Risk",
287
+ icon: "\u{1F510}",
288
+ issues
289
+ };
290
+ }
291
+
292
+ // src/scanners/env.ts
293
+ import { resolve as resolve2 } from "path";
294
+ var ENV_USAGE_PATTERN = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
295
+ var VITE_ENV_PATTERN = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
296
+ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
297
+ "NODE_ENV",
298
+ "PORT",
299
+ "HOME",
300
+ "PATH",
301
+ "USER",
302
+ "SHELL",
303
+ "TERM",
304
+ "LANG",
305
+ "PWD",
306
+ "HOSTNAME",
307
+ "CI",
308
+ "TZ",
309
+ "npm_package_version",
310
+ "npm_package_name"
311
+ ]);
312
+ async function scanEnv(files, cwd) {
313
+ const issues = [];
314
+ const usedEnvVars = /* @__PURE__ */ new Map();
315
+ for (const file of files) {
316
+ if (/\.env/.test(file.relativePath)) continue;
317
+ for (let i = 0; i < file.lines.length; i++) {
318
+ const line = file.lines[i];
319
+ for (const pattern of [ENV_USAGE_PATTERN, VITE_ENV_PATTERN]) {
320
+ pattern.lastIndex = 0;
321
+ let match;
322
+ while ((match = pattern.exec(line)) !== null) {
323
+ const varName = match[1];
324
+ if (BUILTIN_ENV_VARS.has(varName)) continue;
325
+ if (!usedEnvVars.has(varName)) {
326
+ usedEnvVars.set(varName, []);
327
+ }
328
+ usedEnvVars.get(varName).push({
329
+ file: file.relativePath,
330
+ line: i + 1
331
+ });
332
+ }
333
+ }
334
+ }
335
+ }
336
+ if (usedEnvVars.size === 0) {
337
+ return { name: "Missing Env Documentation", icon: "\u{1F4CB}", issues };
338
+ }
339
+ const envExamplePath = resolve2(cwd, ".env.example");
340
+ const envExampleContent = await readFileContent(envExamplePath);
341
+ if (envExampleContent === null) {
342
+ issues.push({
343
+ file: ".env.example",
344
+ message: `.env.example file is missing. ${usedEnvVars.size} env variable${usedEnvVars.size !== 1 ? "s" : ""} found in code.`,
345
+ severity: "warning"
346
+ });
347
+ for (const [varName] of usedEnvVars) {
348
+ issues.push({
349
+ file: ".env.example",
350
+ message: `Missing: ${varName}`,
351
+ severity: "warning"
352
+ });
353
+ }
354
+ } else {
355
+ const documentedVars = /* @__PURE__ */ new Set();
356
+ for (const line of envExampleContent.split("\n")) {
357
+ const trimmed = line.trim();
358
+ if (trimmed.startsWith("#") || trimmed === "") continue;
359
+ const eqIndex = trimmed.indexOf("=");
360
+ const varName = eqIndex !== -1 ? trimmed.slice(0, eqIndex).trim() : trimmed.trim();
361
+ if (varName) documentedVars.add(varName);
362
+ }
363
+ const missingVars = [];
364
+ for (const [varName] of usedEnvVars) {
365
+ if (!documentedVars.has(varName)) {
366
+ missingVars.push(varName);
367
+ }
368
+ }
369
+ if (missingVars.length > 0) {
370
+ issues.push({
371
+ file: ".env.example",
372
+ message: `.env.example is missing ${missingVars.length} variable${missingVars.length !== 1 ? "s" : ""}:`,
373
+ severity: "warning"
374
+ });
375
+ for (const varName of missingVars) {
376
+ issues.push({
377
+ file: ".env.example",
378
+ message: `Missing: ${varName}`,
379
+ severity: "warning"
380
+ });
381
+ }
382
+ }
383
+ }
384
+ return { name: "Missing Env Documentation", icon: "\u{1F4CB}", issues };
385
+ }
386
+
387
+ // src/scanners/debug.ts
388
+ var DEBUG_PATTERNS = [
389
+ {
390
+ pattern: /\bconsole\.log\s*\(/,
391
+ label: "console.log found"
392
+ },
393
+ {
394
+ pattern: /\bconsole\.debug\s*\(/,
395
+ label: "console.debug found"
396
+ },
397
+ {
398
+ pattern: /\bconsole\.warn\s*\(/,
399
+ label: "console.warn found"
400
+ },
401
+ {
402
+ pattern: /\bdebugger\b/,
403
+ label: "debugger statement found"
404
+ },
405
+ {
406
+ pattern: /\/\/\s*TODO\b/i,
407
+ label: "TODO comment found"
408
+ },
409
+ {
410
+ pattern: /\/\/\s*FIXME\b/i,
411
+ label: "FIXME comment found"
412
+ },
413
+ {
414
+ pattern: /\/\/\s*HACK\b/i,
415
+ label: "HACK comment found"
416
+ },
417
+ {
418
+ pattern: /\/\/\s*XXX\b/i,
419
+ label: "XXX marker found"
420
+ },
421
+ {
422
+ pattern: /\balert\s*\(/,
423
+ label: "alert() call found"
424
+ }
425
+ ];
426
+ var SKIP_PATTERNS2 = [
427
+ /\.config\.(ts|js|mjs|cjs)$/,
428
+ /tsup\.config/,
429
+ /vite\.config/,
430
+ /next\.config/,
431
+ /webpack\.config/,
432
+ /jest\.config/,
433
+ /eslint/,
434
+ /prettier/
435
+ ];
436
+ function scanDebug(files) {
437
+ const issues = [];
438
+ for (const file of files) {
439
+ if (SKIP_PATTERNS2.some((p) => p.test(file.relativePath))) continue;
440
+ if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file.relativePath)) continue;
441
+ for (let i = 0; i < file.lines.length; i++) {
442
+ const line = file.lines[i];
443
+ for (const { pattern, label } of DEBUG_PATTERNS) {
444
+ if (pattern.test(line)) {
445
+ issues.push({
446
+ file: file.relativePath,
447
+ line: i + 1,
448
+ message: label,
449
+ severity: "warning"
450
+ });
451
+ }
452
+ }
453
+ }
454
+ }
455
+ return {
456
+ name: "Debug Code Found",
457
+ icon: "\u{1F41B}",
458
+ issues
459
+ };
460
+ }
461
+
462
+ // src/scanners/tests.ts
463
+ var TEST_RISK_PATTERNS = [
464
+ {
465
+ pattern: /\bdescribe\.only\s*\(/,
466
+ label: "describe.only found \u2014 other tests will be skipped"
467
+ },
468
+ {
469
+ pattern: /\bit\.only\s*\(/,
470
+ label: "it.only found \u2014 other tests will be skipped"
471
+ },
472
+ {
473
+ pattern: /\btest\.only\s*\(/,
474
+ label: "test.only found \u2014 other tests will be skipped"
475
+ },
476
+ {
477
+ pattern: /\bdescribe\.skip\s*\(/,
478
+ label: "describe.skip found \u2014 tests are being skipped"
479
+ },
480
+ {
481
+ pattern: /\bit\.skip\s*\(/,
482
+ label: "it.skip found \u2014 test is being skipped"
483
+ },
484
+ {
485
+ pattern: /\btest\.skip\s*\(/,
486
+ label: "test.skip found \u2014 test is being skipped"
487
+ },
488
+ {
489
+ pattern: /\bfdescribe\s*\(/,
490
+ label: "fdescribe found \u2014 focused test suite (Jasmine)"
491
+ },
492
+ {
493
+ pattern: /\bfit\s*\(/,
494
+ label: "fit found \u2014 focused test (Jasmine)"
495
+ },
496
+ {
497
+ pattern: /\bxit\s*\(/,
498
+ label: "xit found \u2014 excluded test (Jasmine)"
499
+ },
500
+ {
501
+ pattern: /\bxdescribe\s*\(/,
502
+ label: "xdescribe found \u2014 excluded test suite (Jasmine)"
503
+ }
504
+ ];
505
+ var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/;
506
+ function scanTests(files) {
507
+ const issues = [];
508
+ const testFiles = files.filter((f) => TEST_FILE_PATTERN.test(f.relativePath));
509
+ for (const file of testFiles) {
510
+ for (let i = 0; i < file.lines.length; i++) {
511
+ const line = file.lines[i];
512
+ for (const { pattern, label } of TEST_RISK_PATTERNS) {
513
+ if (pattern.test(line)) {
514
+ issues.push({
515
+ file: file.relativePath,
516
+ line: i + 1,
517
+ message: label,
518
+ severity: "critical"
519
+ });
520
+ }
521
+ }
522
+ }
523
+ }
524
+ return {
525
+ name: "Test Risk",
526
+ icon: "\u{1F9EA}",
527
+ issues
528
+ };
529
+ }
530
+
531
+ // src/scanners/packageHealth.ts
532
+ import { resolve as resolve3 } from "path";
533
+ import { access, constants } from "fs/promises";
534
+ var RISKY_SCRIPTS = ["postinstall", "preinstall", "install"];
535
+ var SUSPICIOUS_PATTERNS = [
536
+ /curl\s/,
537
+ /wget\s/,
538
+ /sh\s+-c/,
539
+ /bash\s+-c/,
540
+ /powershell/i,
541
+ /eval\s/,
542
+ /\brm\s+-rf/,
543
+ />&\s*\/dev\/null/,
544
+ /\.sh\b/
545
+ ];
546
+ async function scanPackageHealth(cwd) {
547
+ const issues = [];
548
+ if (!await fileExists(resolve3(cwd, "README.md"))) {
549
+ issues.push({
550
+ file: "README.md",
551
+ message: "README.md is missing",
552
+ severity: "warning"
553
+ });
554
+ }
555
+ if (!await fileExists(resolve3(cwd, "LICENSE"))) {
556
+ issues.push({
557
+ file: "LICENSE",
558
+ message: "LICENSE file is missing",
559
+ severity: "warning"
560
+ });
561
+ }
562
+ const pkgContent = await readFileContent(resolve3(cwd, "package.json"));
563
+ if (!pkgContent) {
564
+ issues.push({
565
+ file: "package.json",
566
+ message: "package.json not found",
567
+ severity: "critical"
568
+ });
569
+ return { name: "Package Health", icon: "\u{1F4E6}", issues };
570
+ }
571
+ let pkg;
572
+ try {
573
+ pkg = JSON.parse(pkgContent);
574
+ } catch {
575
+ issues.push({
576
+ file: "package.json",
577
+ message: "package.json contains invalid JSON",
578
+ severity: "critical"
579
+ });
580
+ return { name: "Package Health", icon: "\u{1F4E6}", issues };
581
+ }
582
+ const requiredFields = ["name", "version", "description"];
583
+ for (const field of requiredFields) {
584
+ if (!pkg[field]) {
585
+ issues.push({
586
+ file: "package.json",
587
+ message: `Missing required field: "${field}"`,
588
+ severity: "warning"
589
+ });
590
+ }
591
+ }
592
+ if (!pkg["main"] && !pkg["bin"] && !pkg["exports"] && !pkg["module"]) {
593
+ issues.push({
594
+ file: "package.json",
595
+ message: 'Missing entry point: needs "main", "bin", "module", or "exports"',
596
+ severity: "warning"
597
+ });
598
+ }
599
+ const scripts = pkg["scripts"];
600
+ if (scripts) {
601
+ for (const scriptName of RISKY_SCRIPTS) {
602
+ const scriptValue = scripts[scriptName];
603
+ if (!scriptValue) continue;
604
+ const isSuspicious = SUSPICIOUS_PATTERNS.some(
605
+ (p) => p.test(scriptValue)
606
+ );
607
+ if (isSuspicious) {
608
+ issues.push({
609
+ file: "package.json",
610
+ message: `Risky lifecycle script "${scriptName}": ${scriptValue}`,
611
+ severity: "critical"
612
+ });
613
+ } else {
614
+ issues.push({
615
+ file: "package.json",
616
+ message: `Lifecycle script "${scriptName}" found: ${scriptValue}`,
617
+ severity: "info"
618
+ });
619
+ }
620
+ }
621
+ }
622
+ return { name: "Package Health", icon: "\u{1F4E6}", issues };
623
+ }
624
+ async function fileExists(filePath) {
625
+ try {
626
+ await access(filePath, constants.F_OK);
627
+ return true;
628
+ } catch {
629
+ return false;
630
+ }
631
+ }
632
+
633
+ // src/index.ts
634
+ var VERSION = "0.1.0";
635
+ var HELP_TEXT = `
636
+ ${pc2.bold(pc2.cyan("\u{1F6A2} ShipGuard"))} v${VERSION}
637
+ ${pc2.dim("Catch stupid production mistakes before you ship.")}
638
+
639
+ ${pc2.bold("Usage:")}
640
+ npx @adhix11/shipguard [options]
641
+
642
+ ${pc2.bold("Options:")}
643
+ --strict Treat warnings as errors (exit code 1)
644
+ --json Output results as JSON
645
+ --ignore <dirs> Comma-separated directories to ignore
646
+ (e.g., --ignore "dist,build,coverage")
647
+ --help, -h Show this help message
648
+ --version, -v Show version number
649
+
650
+ ${pc2.bold("Examples:")}
651
+ npx @adhix11/shipguard
652
+ npx @adhix11/shipguard --strict
653
+ npx @adhix11/shipguard --json
654
+ npx @adhix11/shipguard --ignore "dist,node_modules,build"
655
+ `;
656
+ function parseArgs(argv) {
657
+ const args = argv.slice(2);
658
+ const options = {
659
+ strict: false,
660
+ json: false,
661
+ ignore: [],
662
+ help: false
663
+ };
664
+ for (let i = 0; i < args.length; i++) {
665
+ const arg = args[i];
666
+ switch (arg) {
667
+ case "--strict":
668
+ options.strict = true;
669
+ break;
670
+ case "--json":
671
+ options.json = true;
672
+ break;
673
+ case "--ignore": {
674
+ const next = args[++i];
675
+ if (next) {
676
+ options.ignore = next.split(",").map((s) => s.trim()).filter(Boolean);
677
+ }
678
+ break;
679
+ }
680
+ case "--help":
681
+ case "-h":
682
+ options.help = true;
683
+ break;
684
+ case "--version":
685
+ case "-v":
686
+ console.log(VERSION);
687
+ process.exit(0);
688
+ break;
689
+ default:
690
+ if (arg.startsWith("--ignore=")) {
691
+ const value = arg.slice("--ignore=".length);
692
+ options.ignore = value.split(",").map((s) => s.trim()).filter(Boolean);
693
+ }
694
+ break;
695
+ }
696
+ }
697
+ return options;
698
+ }
699
+ async function main() {
700
+ const options = parseArgs(process.argv);
701
+ if (options.help) {
702
+ console.log(HELP_TEXT);
703
+ process.exit(0);
704
+ }
705
+ const cwd = resolve4(process.cwd());
706
+ if (!options.json) {
707
+ console.log();
708
+ console.log(
709
+ pc2.bold(pc2.cyan("\u{1F6A2} ShipGuard")) + pc2.dim(` v${VERSION}`) + pc2.dim(" \u2014 scanning project...")
710
+ );
711
+ console.log();
712
+ }
713
+ const files = await getFiles(cwd, options.ignore);
714
+ if (!options.json) {
715
+ console.log(pc2.dim(` Found ${files.length} files to scan`));
716
+ console.log();
717
+ }
718
+ const results = [];
719
+ results.push(scanSecrets(files));
720
+ results.push(await scanEnv(files, cwd));
721
+ results.push(scanDebug(files));
722
+ results.push(scanTests(files));
723
+ results.push(await scanPackageHealth(cwd));
724
+ printReport(results, options);
725
+ const exitCode = getExitCode(results, options);
726
+ process.exit(exitCode);
727
+ }
728
+ main().catch((err) => {
729
+ console.error(pc2.red(`
730
+ \u2717 ShipGuard encountered an error: ${err.message}`));
731
+ process.exit(2);
732
+ });
733
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/readFiles.ts","../src/utils/report.ts","../src/scanners/secrets.ts","../src/scanners/env.ts","../src/scanners/debug.ts","../src/scanners/tests.ts","../src/scanners/packageHealth.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport pc from \"picocolors\";\nimport type { CLIOptions, ScanResult } from \"./types.js\";\nimport { getFiles } from \"./utils/readFiles.js\";\nimport { printReport, getExitCode } from \"./utils/report.js\";\nimport { scanSecrets } from \"./scanners/secrets.js\";\nimport { scanEnv } from \"./scanners/env.js\";\nimport { scanDebug } from \"./scanners/debug.js\";\nimport { scanTests } from \"./scanners/tests.js\";\nimport { scanPackageHealth } from \"./scanners/packageHealth.js\";\n\nconst VERSION = \"0.1.0\";\n\nconst HELP_TEXT = `\n${pc.bold(pc.cyan(\"๐Ÿšข ShipGuard\"))} v${VERSION}\n${pc.dim(\"Catch stupid production mistakes before you ship.\")}\n\n${pc.bold(\"Usage:\")}\n npx @adhix11/shipguard [options]\n\n${pc.bold(\"Options:\")}\n --strict Treat warnings as errors (exit code 1)\n --json Output results as JSON\n --ignore <dirs> Comma-separated directories to ignore\n (e.g., --ignore \"dist,build,coverage\")\n --help, -h Show this help message\n --version, -v Show version number\n\n${pc.bold(\"Examples:\")}\n npx @adhix11/shipguard\n npx @adhix11/shipguard --strict\n npx @adhix11/shipguard --json\n npx @adhix11/shipguard --ignore \"dist,node_modules,build\"\n`;\n\n/**\n * Parse CLI arguments into CLIOptions.\n */\nfunction parseArgs(argv: string[]): CLIOptions {\n const args = argv.slice(2);\n const options: CLIOptions = {\n strict: false,\n json: false,\n ignore: [],\n help: false,\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n switch (arg) {\n case \"--strict\":\n options.strict = true;\n break;\n case \"--json\":\n options.json = true;\n break;\n case \"--ignore\": {\n const next = args[++i];\n if (next) {\n options.ignore = next.split(\",\").map((s) => s.trim()).filter(Boolean);\n }\n break;\n }\n case \"--help\":\n case \"-h\":\n options.help = true;\n break;\n case \"--version\":\n case \"-v\":\n console.log(VERSION);\n process.exit(0);\n break;\n default:\n if (arg.startsWith(\"--ignore=\")) {\n const value = arg.slice(\"--ignore=\".length);\n options.ignore = value.split(\",\").map((s) => s.trim()).filter(Boolean);\n }\n break;\n }\n }\n\n return options;\n}\n\n/**\n * Main entry point.\n */\nasync function main(): Promise<void> {\n const options = parseArgs(process.argv);\n\n if (options.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n }\n\n const cwd = resolve(process.cwd());\n\n if (!options.json) {\n console.log();\n console.log(\n pc.bold(pc.cyan(\"๐Ÿšข ShipGuard\")) +\n pc.dim(` v${VERSION}`) +\n pc.dim(\" โ€” scanning project...\")\n );\n console.log();\n }\n\n // Load all scannable files\n const files = await getFiles(cwd, options.ignore);\n\n if (!options.json) {\n console.log(pc.dim(` Found ${files.length} files to scan`));\n console.log();\n }\n\n // Run all scanners\n const results: ScanResult[] = [];\n\n // 1. Secrets scan\n results.push(scanSecrets(files));\n\n // 2. Env scan\n results.push(await scanEnv(files, cwd));\n\n // 3. Debug code scan\n results.push(scanDebug(files));\n\n // 4. Test safety scan\n results.push(scanTests(files));\n\n // 5. Package health scan\n results.push(await scanPackageHealth(cwd));\n\n // Print report\n printReport(results, options);\n\n // Exit with appropriate code\n const exitCode = getExitCode(results, options);\n process.exit(exitCode);\n}\n\nmain().catch((err: Error) => {\n console.error(pc.red(`\\nโœ— ShipGuard encountered an error: ${err.message}`));\n process.exit(2);\n});\n","import fg from \"fast-glob\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\n/** Default directories/patterns to always ignore */\nconst DEFAULT_IGNORES = [\n \"node_modules\",\n \"dist\",\n \"build\",\n \".git\",\n \".next\",\n \".nuxt\",\n \"coverage\",\n \".cache\",\n \".turbo\",\n \".output\",\n \"out\",\n \"*.min.js\",\n \"*.min.css\",\n \"*.map\",\n \"package-lock.json\",\n \"yarn.lock\",\n \"pnpm-lock.yaml\",\n];\n\n/** File extensions we scan */\nconst SCAN_EXTENSIONS = [\n \"ts\",\n \"tsx\",\n \"js\",\n \"jsx\",\n \"mjs\",\n \"cjs\",\n \"json\",\n \"yaml\",\n \"yml\",\n \"env\",\n \"env.local\",\n \"env.development\",\n \"env.production\",\n \"env.test\",\n \"toml\",\n \"cfg\",\n \"conf\",\n \"ini\",\n];\n\nexport interface FileEntry {\n /** Relative path from project root */\n relativePath: string;\n /** Absolute path */\n absolutePath: string;\n /** File content */\n content: string;\n /** Lines of the file (split by newline) */\n lines: string[];\n}\n\n/**\n * Get all scannable files in the project directory.\n */\nexport async function getFiles(\n cwd: string,\n extraIgnores: string[] = []\n): Promise<FileEntry[]> {\n const ignorePatterns = [...DEFAULT_IGNORES, ...extraIgnores].map(\n (pattern) => `**/${pattern}/**`\n );\n\n const extensionGlob = `**/*.{${SCAN_EXTENSIONS.join(\",\")}}`;\n\n const paths = await fg(extensionGlob, {\n cwd,\n ignore: ignorePatterns,\n dot: true,\n absolute: false,\n onlyFiles: true,\n });\n\n const files: FileEntry[] = [];\n\n for (const relativePath of paths) {\n try {\n const absolutePath = resolve(cwd, relativePath);\n const content = await readFile(absolutePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n files.push({ relativePath, absolutePath, content, lines });\n } catch {\n // Skip files that can't be read\n }\n }\n\n return files;\n}\n\n/**\n * Read a single file and return its content, or null if it doesn't exist.\n */\nexport async function readFileContent(\n filePath: string\n): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n","import pc from \"picocolors\";\nimport type { ScanResult, CLIOptions } from \"../types.js\";\n\n/**\n * Format and print the ShipGuard report to the terminal.\n */\nexport function printReport(results: ScanResult[], options: CLIOptions): void {\n if (options.json) {\n printJsonReport(results);\n return;\n }\n\n printFormattedReport(results, options);\n}\n\n/**\n * Returns the exit code based on scan results.\n * 0 = pass, 1 = fail\n */\nexport function getExitCode(\n results: ScanResult[],\n options: CLIOptions\n): number {\n const criticalCount = countBySeverity(results, \"critical\");\n const warningCount = countBySeverity(results, \"warning\");\n\n if (criticalCount > 0) return 1;\n if (options.strict && warningCount > 0) return 1;\n return 0;\n}\n\n// โ”€โ”€ Internal helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n\nfunction printJsonReport(results: ScanResult[]): void {\n const output = {\n tool: \"@adhix11/shipguard\",\n timestamp: new Date().toISOString(),\n results: results.map((r) => ({\n scanner: r.name,\n issues: r.issues.map((i) => ({\n file: i.file,\n line: i.line ?? null,\n message: i.message,\n severity: i.severity,\n })),\n })),\n summary: {\n critical: countBySeverity(results, \"critical\"),\n warnings: countBySeverity(results, \"warning\"),\n info: countBySeverity(results, \"info\"),\n totalIssues: results.reduce((sum, r) => sum + r.issues.length, 0),\n },\n };\n\n console.log(JSON.stringify(output, null, 2));\n}\n\nfunction printFormattedReport(\n results: ScanResult[],\n options: CLIOptions\n): void {\n console.log();\n console.log(\n pc.bold(pc.cyan(\"๐Ÿšข ShipGuard Report\"))\n );\n console.log(pc.dim(\"โ”€\".repeat(50)));\n console.log();\n\n let hasIssues = false;\n\n for (const result of results) {\n if (result.issues.length === 0) continue;\n hasIssues = true;\n\n // Group issues by severity to pick the header color\n const hasCritical = result.issues.some((i) => i.severity === \"critical\");\n const headerColor = hasCritical ? pc.red : pc.yellow;\n const icon = hasCritical ? \"โŒ\" : \"โš ๏ธ\";\n\n console.log(headerColor(pc.bold(`${icon} ${result.name}`)));\n\n // Group issues by file\n const byFile = groupByFile(result);\n\n for (const [file, issues] of Object.entries(byFile)) {\n console.log(pc.dim(` ${file}`));\n for (const issue of issues) {\n const lineInfo = issue.line ? pc.dim(` (line ${issue.line})`) : \"\";\n const bullet =\n issue.severity === \"critical\"\n ? pc.red(\" โœ—\")\n : issue.severity === \"warning\"\n ? pc.yellow(\" โ—\")\n : pc.blue(\" โ—‹\");\n console.log(`${bullet} ${issue.message}${lineInfo}`);\n }\n }\n\n console.log();\n }\n\n // Summary\n const criticalCount = countBySeverity(results, \"critical\");\n const warningCount = countBySeverity(results, \"warning\");\n const infoCount = countBySeverity(results, \"info\");\n\n console.log(pc.dim(\"โ”€\".repeat(50)));\n console.log(pc.bold(\"Summary:\"));\n\n if (criticalCount > 0) {\n console.log(pc.red(` ${criticalCount} critical issue${criticalCount !== 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n console.log(pc.yellow(` ${warningCount} warning${warningCount !== 1 ? \"s\" : \"\"}`));\n }\n if (infoCount > 0) {\n console.log(pc.blue(` ${infoCount} info`));\n }\n\n console.log();\n\n const exitCode = getExitCode(results, options);\n\n if (!hasIssues) {\n console.log(pc.green(pc.bold(\"โœ” All clear! Ship with confidence. ๐Ÿš€\")));\n } else if (exitCode === 0) {\n console.log(\n pc.yellow(pc.bold(\"โš  Warnings found. Review before shipping.\"))\n );\n } else {\n console.log(\n pc.red(pc.bold(\"โœ— Run failed. Fix critical issues before shipping.\"))\n );\n }\n\n console.log();\n}\n\nfunction countBySeverity(\n results: ScanResult[],\n severity: string\n): number {\n return results.reduce(\n (sum, r) => sum + r.issues.filter((i) => i.severity === severity).length,\n 0\n );\n}\n\nfunction groupByFile(\n result: ScanResult\n): Record<string, ScanResult[\"issues\"]> {\n const grouped: Record<string, ScanResult[\"issues\"]> = {};\n for (const issue of result.issues) {\n const key = issue.file || \"(project)\";\n if (!grouped[key]) grouped[key] = [];\n grouped[key].push(issue);\n }\n return grouped;\n}\n","import type { ScanResult, ScanIssue } from \"../types.js\";\nimport type { FileEntry } from \"../utils/readFiles.js\";\n\n/**\n * Patterns to detect hardcoded secrets.\n * Each pattern has a regex and a human-readable label.\n */\nconst SECRET_PATTERNS: { pattern: RegExp; label: string }[] = [\n {\n pattern: /AWS_ACCESS_KEY_ID\\s*[=:]\\s*['\"]?[A-Z0-9]{16,}/i,\n label: \"Possible AWS Access Key found\",\n },\n {\n pattern: /AWS_SECRET_ACCESS_KEY\\s*[=:]\\s*['\"]?[A-Za-z0-9/+=]{30,}/i,\n label: \"Possible AWS Secret Key found\",\n },\n {\n pattern: /AKIA[0-9A-Z]{16}/,\n label: \"AWS Access Key ID pattern detected\",\n },\n {\n pattern: /SECRET_KEY\\s*[=:]\\s*['\"]?[^\\s'\"]{8,}/i,\n label: \"Possible SECRET_KEY assignment found\",\n },\n {\n pattern: /PRIVATE_KEY\\s*[=:]\\s*['\"]?[^\\s'\"]{8,}/i,\n label: \"Possible PRIVATE_KEY assignment found\",\n },\n {\n pattern: /-----BEGIN\\s+(RSA\\s+)?PRIVATE\\s+KEY-----/,\n label: \"Private key block detected\",\n },\n {\n pattern: /mongodb\\+srv:\\/\\/[^\\s'\"]+/i,\n label: \"Possible MongoDB connection URI found\",\n },\n {\n pattern: /mongodb:\\/\\/[^\\s'\"]+/i,\n label: \"Possible MongoDB connection URI found\",\n },\n {\n pattern: /password\\s*[=:]\\s*['\"][^'\"]{4,}['\"]/i,\n label: \"Possible hardcoded password found\",\n },\n {\n pattern: /jwtSecret\\s*[=:]\\s*['\"][^'\"]+['\"]/i,\n label: \"Possible JWT secret found\",\n },\n {\n pattern: /jwt_secret\\s*[=:]\\s*['\"][^'\"]+['\"]/i,\n label: \"Possible JWT secret found\",\n },\n {\n pattern: /api[_-]?key\\s*[=:]\\s*['\"][^'\"]{8,}['\"]/i,\n label: \"Possible API key found\",\n },\n {\n pattern: /api[_-]?secret\\s*[=:]\\s*['\"][^'\"]{8,}['\"]/i,\n label: \"Possible API secret found\",\n },\n {\n pattern: /ghp_[A-Za-z0-9_]{36,}/,\n label: \"GitHub personal access token detected\",\n },\n {\n pattern: /sk-[A-Za-z0-9]{32,}/,\n label: \"Possible OpenAI/Stripe secret key detected\",\n },\n {\n pattern: /xox[bpras]-[A-Za-z0-9-]+/,\n label: \"Possible Slack token detected\",\n },\n];\n\n/** Files that are expected to have secrets-like patterns (skip them) */\nconst SKIP_PATTERNS = [\n /\\.env\\.example$/,\n /\\.env\\.sample$/,\n /\\.env\\.template$/,\n /package-lock\\.json$/,\n /yarn\\.lock$/,\n /pnpm-lock\\.yaml$/,\n];\n\n/**\n * Scan files for hardcoded secrets.\n */\nexport function scanSecrets(files: FileEntry[]): ScanResult {\n const issues: ScanIssue[] = [];\n\n for (const file of files) {\n // Skip files that are expected to contain template patterns\n if (SKIP_PATTERNS.some((p) => p.test(file.relativePath))) continue;\n\n for (let i = 0; i < file.lines.length; i++) {\n const line = file.lines[i];\n\n for (const { pattern, label } of SECRET_PATTERNS) {\n if (pattern.test(line)) {\n issues.push({\n file: file.relativePath,\n line: i + 1,\n message: label,\n severity: \"critical\",\n });\n break; // One match per line is enough\n }\n }\n }\n }\n\n return {\n name: \"Secrets Risk\",\n icon: \"๐Ÿ”\",\n issues,\n };\n}\n","import { resolve } from \"node:path\";\nimport type { ScanResult, ScanIssue } from \"../types.js\";\nimport type { FileEntry } from \"../utils/readFiles.js\";\nimport { readFileContent } from \"../utils/readFiles.js\";\n\n/** Regex to find process.env.VARIABLE_NAME usage */\nconst ENV_USAGE_PATTERN = /process\\.env\\.([A-Z_][A-Z0-9_]*)/g;\n\n/** Regex to find import.meta.env.VARIABLE_NAME usage (Vite) */\nconst VITE_ENV_PATTERN = /import\\.meta\\.env\\.([A-Z_][A-Z0-9_]*)/g;\n\n/** Standard env vars that don't need to be in .env.example */\nconst BUILTIN_ENV_VARS = new Set([\n \"NODE_ENV\",\n \"PORT\",\n \"HOME\",\n \"PATH\",\n \"USER\",\n \"SHELL\",\n \"TERM\",\n \"LANG\",\n \"PWD\",\n \"HOSTNAME\",\n \"CI\",\n \"TZ\",\n \"npm_package_version\",\n \"npm_package_name\",\n]);\n\n/**\n * Scan for environment variable usage and check against .env.example\n */\nexport async function scanEnv(\n files: FileEntry[],\n cwd: string\n): Promise<ScanResult> {\n const issues: ScanIssue[] = [];\n\n // Collect all env vars used in source code\n const usedEnvVars = new Map<string, { file: string; line: number }[]>();\n\n for (const file of files) {\n // Only scan source code files, not .env files themselves\n if (/\\.env/.test(file.relativePath)) continue;\n\n for (let i = 0; i < file.lines.length; i++) {\n const line = file.lines[i];\n\n for (const pattern of [ENV_USAGE_PATTERN, VITE_ENV_PATTERN]) {\n // Reset lastIndex because we reuse the regex\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(line)) !== null) {\n const varName = match[1];\n if (BUILTIN_ENV_VARS.has(varName)) continue;\n\n if (!usedEnvVars.has(varName)) {\n usedEnvVars.set(varName, []);\n }\n usedEnvVars.get(varName)!.push({\n file: file.relativePath,\n line: i + 1,\n });\n }\n }\n }\n }\n\n if (usedEnvVars.size === 0) {\n return { name: \"Missing Env Documentation\", icon: \"๐Ÿ“‹\", issues };\n }\n\n // Read .env.example\n const envExamplePath = resolve(cwd, \".env.example\");\n const envExampleContent = await readFileContent(envExamplePath);\n\n if (envExampleContent === null) {\n // No .env.example exists at all\n issues.push({\n file: \".env.example\",\n message: `.env.example file is missing. ${usedEnvVars.size} env variable${usedEnvVars.size !== 1 ? \"s\" : \"\"} found in code.`,\n severity: \"warning\",\n });\n\n for (const [varName] of usedEnvVars) {\n issues.push({\n file: \".env.example\",\n message: `Missing: ${varName}`,\n severity: \"warning\",\n });\n }\n } else {\n // Parse .env.example to get documented vars\n const documentedVars = new Set<string>();\n for (const line of envExampleContent.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.startsWith(\"#\") || trimmed === \"\") continue;\n const eqIndex = trimmed.indexOf(\"=\");\n const varName = eqIndex !== -1 ? trimmed.slice(0, eqIndex).trim() : trimmed.trim();\n if (varName) documentedVars.add(varName);\n }\n\n // Find missing vars\n const missingVars: string[] = [];\n for (const [varName] of usedEnvVars) {\n if (!documentedVars.has(varName)) {\n missingVars.push(varName);\n }\n }\n\n if (missingVars.length > 0) {\n issues.push({\n file: \".env.example\",\n message: `.env.example is missing ${missingVars.length} variable${missingVars.length !== 1 ? \"s\" : \"\"}:`,\n severity: \"warning\",\n });\n\n for (const varName of missingVars) {\n issues.push({\n file: \".env.example\",\n message: `Missing: ${varName}`,\n severity: \"warning\",\n });\n }\n }\n }\n\n return { name: \"Missing Env Documentation\", icon: \"๐Ÿ“‹\", issues };\n}\n","import type { ScanResult, ScanIssue } from \"../types.js\";\nimport type { FileEntry } from \"../utils/readFiles.js\";\n\n/**\n * Debug/leftover patterns to detect.\n */\nconst DEBUG_PATTERNS: { pattern: RegExp; label: string }[] = [\n {\n pattern: /\\bconsole\\.log\\s*\\(/,\n label: \"console.log found\",\n },\n {\n pattern: /\\bconsole\\.debug\\s*\\(/,\n label: \"console.debug found\",\n },\n {\n pattern: /\\bconsole\\.warn\\s*\\(/,\n label: \"console.warn found\",\n },\n {\n pattern: /\\bdebugger\\b/,\n label: \"debugger statement found\",\n },\n {\n pattern: /\\/\\/\\s*TODO\\b/i,\n label: \"TODO comment found\",\n },\n {\n pattern: /\\/\\/\\s*FIXME\\b/i,\n label: \"FIXME comment found\",\n },\n {\n pattern: /\\/\\/\\s*HACK\\b/i,\n label: \"HACK comment found\",\n },\n {\n pattern: /\\/\\/\\s*XXX\\b/i,\n label: \"XXX marker found\",\n },\n {\n pattern: /\\balert\\s*\\(/,\n label: \"alert() call found\",\n },\n];\n\n/** Skip files that typically have console/debug usage by design */\nconst SKIP_PATTERNS = [\n /\\.config\\.(ts|js|mjs|cjs)$/,\n /tsup\\.config/,\n /vite\\.config/,\n /next\\.config/,\n /webpack\\.config/,\n /jest\\.config/,\n /eslint/,\n /prettier/,\n];\n\n/**\n * Scan for debug/leftover code that shouldn't ship to production.\n */\nexport function scanDebug(files: FileEntry[]): ScanResult {\n const issues: ScanIssue[] = [];\n\n for (const file of files) {\n // Skip config files\n if (SKIP_PATTERNS.some((p) => p.test(file.relativePath))) continue;\n\n // Skip non-source files\n if (!/\\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file.relativePath)) continue;\n\n for (let i = 0; i < file.lines.length; i++) {\n const line = file.lines[i];\n\n // Skip commented-out lines that are just comments about these patterns\n // (i.e., \"// We removed console.log\" shouldn't trigger)\n\n for (const { pattern, label } of DEBUG_PATTERNS) {\n if (pattern.test(line)) {\n issues.push({\n file: file.relativePath,\n line: i + 1,\n message: label,\n severity: \"warning\",\n });\n }\n }\n }\n }\n\n return {\n name: \"Debug Code Found\",\n icon: \"๐Ÿ›\",\n issues,\n };\n}\n","import type { ScanResult, ScanIssue } from \"../types.js\";\nimport type { FileEntry } from \"../utils/readFiles.js\";\n\n/**\n * Risky test patterns that prevent proper test execution in CI.\n */\nconst TEST_RISK_PATTERNS: { pattern: RegExp; label: string }[] = [\n {\n pattern: /\\bdescribe\\.only\\s*\\(/,\n label: \"describe.only found โ€” other tests will be skipped\",\n },\n {\n pattern: /\\bit\\.only\\s*\\(/,\n label: \"it.only found โ€” other tests will be skipped\",\n },\n {\n pattern: /\\btest\\.only\\s*\\(/,\n label: \"test.only found โ€” other tests will be skipped\",\n },\n {\n pattern: /\\bdescribe\\.skip\\s*\\(/,\n label: \"describe.skip found โ€” tests are being skipped\",\n },\n {\n pattern: /\\bit\\.skip\\s*\\(/,\n label: \"it.skip found โ€” test is being skipped\",\n },\n {\n pattern: /\\btest\\.skip\\s*\\(/,\n label: \"test.skip found โ€” test is being skipped\",\n },\n {\n pattern: /\\bfdescribe\\s*\\(/,\n label: \"fdescribe found โ€” focused test suite (Jasmine)\",\n },\n {\n pattern: /\\bfit\\s*\\(/,\n label: \"fit found โ€” focused test (Jasmine)\",\n },\n {\n pattern: /\\bxit\\s*\\(/,\n label: \"xit found โ€” excluded test (Jasmine)\",\n },\n {\n pattern: /\\bxdescribe\\s*\\(/,\n label: \"xdescribe found โ€” excluded test suite (Jasmine)\",\n },\n];\n\n/** Only scan test files */\nconst TEST_FILE_PATTERN = /\\.(test|spec)\\.(ts|tsx|js|jsx|mjs|cjs)$/;\n\n/**\n * Scan test files for risky patterns (.only, .skip, focused tests).\n */\nexport function scanTests(files: FileEntry[]): ScanResult {\n const issues: ScanIssue[] = [];\n\n const testFiles = files.filter((f) => TEST_FILE_PATTERN.test(f.relativePath));\n\n for (const file of testFiles) {\n for (let i = 0; i < file.lines.length; i++) {\n const line = file.lines[i];\n\n for (const { pattern, label } of TEST_RISK_PATTERNS) {\n if (pattern.test(line)) {\n issues.push({\n file: file.relativePath,\n line: i + 1,\n message: label,\n severity: \"critical\",\n });\n }\n }\n }\n }\n\n return {\n name: \"Test Risk\",\n icon: \"๐Ÿงช\",\n issues,\n };\n}\n","import { resolve } from \"node:path\";\nimport { access, constants } from \"node:fs/promises\";\nimport type { ScanResult, ScanIssue } from \"../types.js\";\nimport { readFileContent } from \"../utils/readFiles.js\";\n\n/** Risky npm lifecycle scripts to flag */\nconst RISKY_SCRIPTS = [\"postinstall\", \"preinstall\", \"install\"];\n\n/** Shell command patterns that look suspicious in scripts */\nconst SUSPICIOUS_PATTERNS = [\n /curl\\s/,\n /wget\\s/,\n /sh\\s+-c/,\n /bash\\s+-c/,\n /powershell/i,\n /eval\\s/,\n /\\brm\\s+-rf/,\n />&\\s*\\/dev\\/null/,\n /\\.sh\\b/,\n];\n\n/**\n * Check package-level health: README, LICENSE, package.json fields, risky scripts.\n */\nexport async function scanPackageHealth(cwd: string): Promise<ScanResult> {\n const issues: ScanIssue[] = [];\n\n // Check README.md\n if (!(await fileExists(resolve(cwd, \"README.md\")))) {\n issues.push({\n file: \"README.md\",\n message: \"README.md is missing\",\n severity: \"warning\",\n });\n }\n\n // Check LICENSE\n if (!(await fileExists(resolve(cwd, \"LICENSE\")))) {\n issues.push({\n file: \"LICENSE\",\n message: \"LICENSE file is missing\",\n severity: \"warning\",\n });\n }\n\n // Check package.json\n const pkgContent = await readFileContent(resolve(cwd, \"package.json\"));\n\n if (!pkgContent) {\n issues.push({\n file: \"package.json\",\n message: \"package.json not found\",\n severity: \"critical\",\n });\n return { name: \"Package Health\", icon: \"๐Ÿ“ฆ\", issues };\n }\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(pkgContent);\n } catch {\n issues.push({\n file: \"package.json\",\n message: \"package.json contains invalid JSON\",\n severity: \"critical\",\n });\n return { name: \"Package Health\", icon: \"๐Ÿ“ฆ\", issues };\n }\n\n // Required fields\n const requiredFields = [\"name\", \"version\", \"description\"];\n for (const field of requiredFields) {\n if (!pkg[field]) {\n issues.push({\n file: \"package.json\",\n message: `Missing required field: \"${field}\"`,\n severity: \"warning\",\n });\n }\n }\n\n // Should have main or bin or exports\n if (!pkg[\"main\"] && !pkg[\"bin\"] && !pkg[\"exports\"] && !pkg[\"module\"]) {\n issues.push({\n file: \"package.json\",\n message: 'Missing entry point: needs \"main\", \"bin\", \"module\", or \"exports\"',\n severity: \"warning\",\n });\n }\n\n // Check for risky scripts\n const scripts = pkg[\"scripts\"] as Record<string, string> | undefined;\n if (scripts) {\n for (const scriptName of RISKY_SCRIPTS) {\n const scriptValue = scripts[scriptName];\n if (!scriptValue) continue;\n\n // Check if the script has suspicious patterns\n const isSuspicious = SUSPICIOUS_PATTERNS.some((p) =>\n p.test(scriptValue)\n );\n\n if (isSuspicious) {\n issues.push({\n file: \"package.json\",\n message: `Risky lifecycle script \"${scriptName}\": ${scriptValue}`,\n severity: \"critical\",\n });\n } else {\n issues.push({\n file: \"package.json\",\n message: `Lifecycle script \"${scriptName}\" found: ${scriptValue}`,\n severity: \"info\",\n });\n }\n }\n }\n\n return { name: \"Package Health\", icon: \"๐Ÿ“ฆ\", issues };\n}\n\nasync function fileExists(filePath: string): Promise<boolean> {\n try {\n await access(filePath, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["resolve","pc","resolve","resolve","SKIP_PATTERNS","resolve","resolve","pc","resolve"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@adhix11/shipguard",
3
+ "version": "0.1.0",
4
+ "description": "A zero-config preflight scanner for JavaScript and TypeScript projects. Detect secrets, missing env docs, debug code, risky test flags, and basic package readiness before you ship.",
5
+ "author": "adhix11",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "shipguard": "./dist/index.js"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "preflight",
24
+ "scanner",
25
+ "secrets",
26
+ "security",
27
+ "lint",
28
+ "cli",
29
+ "devtools",
30
+ "shipguard",
31
+ "env",
32
+ "debug",
33
+ "test-safety",
34
+ "code-review",
35
+ "ai-code",
36
+ "release",
37
+ "pre-commit"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/adhix11/shipguard"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/adhix11/shipguard/issues"
45
+ },
46
+ "homepage": "https://github.com/adhix11/shipguard#readme",
47
+ "engines": {
48
+ "node": ">=16.0.0"
49
+ },
50
+ "dependencies": {
51
+ "fast-glob": "^3.3.3",
52
+ "picocolors": "^1.1.1"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.15.0",
56
+ "tsup": "^8.4.0",
57
+ "typescript": "^5.8.0"
58
+ }
59
+ }