@adhix11/shipguard 0.1.1 โ 0.1.2
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 +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ๐ข ShipGuard
|
|
2
2
|
|
|
3
|
-
**Catch
|
|
3
|
+
**Catch costly production mistakes before you ship.**
|
|
4
4
|
|
|
5
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
6
|
|
|
@@ -11,7 +11,7 @@ A zero-config preflight scanner for JavaScript and TypeScript projects. Detect s
|
|
|
11
11
|
|
|
12
12
|
## Why?
|
|
13
13
|
|
|
14
|
-
Developers often forget small but
|
|
14
|
+
Developers often forget small but critical things before pushing code:
|
|
15
15
|
|
|
16
16
|
| Pain | Example |
|
|
17
17
|
|------|---------|
|
package/dist/index.js
CHANGED
|
@@ -631,10 +631,10 @@ async function fileExists(filePath) {
|
|
|
631
631
|
}
|
|
632
632
|
|
|
633
633
|
// src/index.ts
|
|
634
|
-
var VERSION = "0.1.
|
|
634
|
+
var VERSION = "0.1.2";
|
|
635
635
|
var HELP_TEXT = `
|
|
636
636
|
${pc2.bold(pc2.cyan("\u{1F6A2} ShipGuard"))} v${VERSION}
|
|
637
|
-
${pc2.dim("Catch
|
|
637
|
+
${pc2.dim("Catch costly production mistakes before you ship.")}
|
|
638
638
|
|
|
639
639
|
${pc2.bold("Usage:")}
|
|
640
640
|
npx @adhix11/shipguard [options]
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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.1\";\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"]}
|
|
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.2\";\n\nconst HELP_TEXT = `\n${pc.bold(pc.cyan(\"๐ข ShipGuard\"))} v${VERSION}\n${pc.dim(\"Catch costly 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":";;;AAAA,SAAS,WAAAA,gBAAe;AACxB,OAAOC,SAAQ;;;ACDf,OAAO,QAAQ;AACf,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAGxB,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAgBA,eAAsB,SACpB,KACA,eAAyB,CAAC,GACJ;AACtB,QAAM,iBAAiB,CAAC,GAAG,iBAAiB,GAAG,YAAY,EAAE;AAAA,IAC3D,CAAC,YAAY,MAAM,OAAO;AAAA,EAC5B;AAEA,QAAM,gBAAgB,SAAS,gBAAgB,KAAK,GAAG,CAAC;AAExD,QAAM,QAAQ,MAAM,GAAG,eAAe;AAAA,IACpC;AAAA,IACA,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,UAAU;AAAA,IACV,WAAW;AAAA,EACb,CAAC;AAED,QAAM,QAAqB,CAAC;AAE5B,aAAW,gBAAgB,OAAO;AAChC,QAAI;AACF,YAAM,eAAe,QAAQ,KAAK,YAAY;AAC9C,YAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,YAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,YAAM,KAAK,EAAE,cAAc,cAAc,SAAS,MAAM,CAAC;AAAA,IAC3D,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAKA,eAAsB,gBACpB,UACwB;AACxB,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC1GA,OAAO,QAAQ;AAMR,SAAS,YAAY,SAAuB,SAA2B;AAC5E,MAAI,QAAQ,MAAM;AAChB,oBAAgB,OAAO;AACvB;AAAA,EACF;AAEA,uBAAqB,SAAS,OAAO;AACvC;AAMO,SAAS,YACd,SACA,SACQ;AACR,QAAM,gBAAgB,gBAAgB,SAAS,UAAU;AACzD,QAAM,eAAe,gBAAgB,SAAS,SAAS;AAEvD,MAAI,gBAAgB,EAAG,QAAO;AAC9B,MAAI,QAAQ,UAAU,eAAe,EAAG,QAAO;AAC/C,SAAO;AACT;AAIA,SAAS,gBAAgB,SAA6B;AACpD,QAAM,SAAS;AAAA,IACb,MAAM;AAAA,IACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,SAAS,QAAQ,IAAI,CAAC,OAAO;AAAA,MAC3B,SAAS,EAAE;AAAA,MACX,QAAQ,EAAE,OAAO,IAAI,CAAC,OAAO;AAAA,QAC3B,MAAM,EAAE;AAAA,QACR,MAAM,EAAE,QAAQ;AAAA,QAChB,SAAS,EAAE;AAAA,QACX,UAAU,EAAE;AAAA,MACd,EAAE;AAAA,IACJ,EAAE;AAAA,IACF,SAAS;AAAA,MACP,UAAU,gBAAgB,SAAS,UAAU;AAAA,MAC7C,UAAU,gBAAgB,SAAS,SAAS;AAAA,MAC5C,MAAM,gBAAgB,SAAS,MAAM;AAAA,MACrC,aAAa,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,QAAQ,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC7C;AAEA,SAAS,qBACP,SACA,SACM;AACN,UAAQ,IAAI;AACZ,UAAQ;AAAA,IACN,GAAG,KAAK,GAAG,KAAK,4BAAqB,CAAC;AAAA,EACxC;AACA,UAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAClC,UAAQ,IAAI;AAEZ,MAAI,YAAY;AAEhB,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,OAAO,WAAW,EAAG;AAChC,gBAAY;AAGZ,UAAM,cAAc,OAAO,OAAO,KAAK,CAAC,MAAM,EAAE,aAAa,UAAU;AACvE,UAAM,cAAc,cAAc,GAAG,MAAM,GAAG;AAC9C,UAAM,OAAO,cAAc,WAAM;AAEjC,YAAQ,IAAI,YAAY,GAAG,KAAK,GAAG,IAAI,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC;AAG1D,UAAM,SAAS,YAAY,MAAM;AAEjC,eAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACnD,cAAQ,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,CAAC;AAC/B,iBAAW,SAAS,QAAQ;AAC1B,cAAM,WAAW,MAAM,OAAO,GAAG,IAAI,UAAU,MAAM,IAAI,GAAG,IAAI;AAChE,cAAM,SACJ,MAAM,aAAa,aACf,GAAG,IAAI,UAAK,IACZ,MAAM,aAAa,YACjB,GAAG,OAAO,UAAK,IACf,GAAG,KAAK,UAAK;AACrB,gBAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,OAAO,GAAG,QAAQ,EAAE;AAAA,MACrD;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,EACd;AAGA,QAAM,gBAAgB,gBAAgB,SAAS,UAAU;AACzD,QAAM,eAAe,gBAAgB,SAAS,SAAS;AACvD,QAAM,YAAY,gBAAgB,SAAS,MAAM;AAEjD,UAAQ,IAAI,GAAG,IAAI,SAAI,OAAO,EAAE,CAAC,CAAC;AAClC,UAAQ,IAAI,GAAG,KAAK,UAAU,CAAC;AAE/B,MAAI,gBAAgB,GAAG;AACrB,YAAQ,IAAI,GAAG,IAAI,KAAK,aAAa,kBAAkB,kBAAkB,IAAI,MAAM,EAAE,EAAE,CAAC;AAAA,EAC1F;AACA,MAAI,eAAe,GAAG;AACpB,YAAQ,IAAI,GAAG,OAAO,KAAK,YAAY,WAAW,iBAAiB,IAAI,MAAM,EAAE,EAAE,CAAC;AAAA,EACpF;AACA,MAAI,YAAY,GAAG;AACjB,YAAQ,IAAI,GAAG,KAAK,KAAK,SAAS,OAAO,CAAC;AAAA,EAC5C;AAEA,UAAQ,IAAI;AAEZ,QAAM,WAAW,YAAY,SAAS,OAAO;AAE7C,MAAI,CAAC,WAAW;AACd,YAAQ,IAAI,GAAG,MAAM,GAAG,KAAK,mDAAuC,CAAC,CAAC;AAAA,EACxE,WAAW,aAAa,GAAG;AACzB,YAAQ;AAAA,MACN,GAAG,OAAO,GAAG,KAAK,gDAA2C,CAAC;AAAA,IAChE;AAAA,EACF,OAAO;AACL,YAAQ;AAAA,MACN,GAAG,IAAI,GAAG,KAAK,yDAAoD,CAAC;AAAA,IACtE;AAAA,EACF;AAEA,UAAQ,IAAI;AACd;AAEA,SAAS,gBACP,SACA,UACQ;AACR,SAAO,QAAQ;AAAA,IACb,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE;AAAA,IAClE;AAAA,EACF;AACF;AAEA,SAAS,YACP,QACsC;AACtC,QAAM,UAAgD,CAAC;AACvD,aAAW,SAAS,OAAO,QAAQ;AACjC,UAAM,MAAM,MAAM,QAAQ;AAC1B,QAAI,CAAC,QAAQ,GAAG,EAAG,SAAQ,GAAG,IAAI,CAAC;AACnC,YAAQ,GAAG,EAAE,KAAK,KAAK;AAAA,EACzB;AACA,SAAO;AACT;;;ACvJA,IAAM,kBAAwD;AAAA,EAC5D;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAGA,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,SAAS,YAAY,OAAgC;AAC1D,QAAM,SAAsB,CAAC;AAE7B,aAAW,QAAQ,OAAO;AAExB,QAAI,cAAc,KAAK,CAAC,MAAM,EAAE,KAAK,KAAK,YAAY,CAAC,EAAG;AAE1D,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AAEzB,iBAAW,EAAE,SAAS,MAAM,KAAK,iBAAiB;AAChD,YAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,iBAAO,KAAK;AAAA,YACV,MAAM,KAAK;AAAA,YACX,MAAM,IAAI;AAAA,YACV,SAAS;AAAA,YACT,UAAU;AAAA,UACZ,CAAC;AACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF;AACF;;;ACpHA,SAAS,WAAAC,gBAAe;AAMxB,IAAM,oBAAoB;AAG1B,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKD,eAAsB,QACpB,OACA,KACqB;AACrB,QAAM,SAAsB,CAAC;AAG7B,QAAM,cAAc,oBAAI,IAA8C;AAEtE,aAAW,QAAQ,OAAO;AAExB,QAAI,QAAQ,KAAK,KAAK,YAAY,EAAG;AAErC,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AAEzB,iBAAW,WAAW,CAAC,mBAAmB,gBAAgB,GAAG;AAE3D,gBAAQ,YAAY;AACpB,YAAI;AAEJ,gBAAQ,QAAQ,QAAQ,KAAK,IAAI,OAAO,MAAM;AAC5C,gBAAM,UAAU,MAAM,CAAC;AACvB,cAAI,iBAAiB,IAAI,OAAO,EAAG;AAEnC,cAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAC7B,wBAAY,IAAI,SAAS,CAAC,CAAC;AAAA,UAC7B;AACA,sBAAY,IAAI,OAAO,EAAG,KAAK;AAAA,YAC7B,MAAM,KAAK;AAAA,YACX,MAAM,IAAI;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,WAAO,EAAE,MAAM,6BAA6B,MAAM,aAAM,OAAO;AAAA,EACjE;AAGA,QAAM,iBAAiBC,SAAQ,KAAK,cAAc;AAClD,QAAM,oBAAoB,MAAM,gBAAgB,cAAc;AAE9D,MAAI,sBAAsB,MAAM;AAE9B,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS,iCAAiC,YAAY,IAAI,gBAAgB,YAAY,SAAS,IAAI,MAAM,EAAE;AAAA,MAC3G,UAAU;AAAA,IACZ,CAAC;AAED,eAAW,CAAC,OAAO,KAAK,aAAa;AACnC,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,SAAS,YAAY,OAAO;AAAA,QAC5B,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,OAAO;AAEL,UAAM,iBAAiB,oBAAI,IAAY;AACvC,eAAW,QAAQ,kBAAkB,MAAM,IAAI,GAAG;AAChD,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,QAAQ,WAAW,GAAG,KAAK,YAAY,GAAI;AAC/C,YAAM,UAAU,QAAQ,QAAQ,GAAG;AACnC,YAAM,UAAU,YAAY,KAAK,QAAQ,MAAM,GAAG,OAAO,EAAE,KAAK,IAAI,QAAQ,KAAK;AACjF,UAAI,QAAS,gBAAe,IAAI,OAAO;AAAA,IACzC;AAGA,UAAM,cAAwB,CAAC;AAC/B,eAAW,CAAC,OAAO,KAAK,aAAa;AACnC,UAAI,CAAC,eAAe,IAAI,OAAO,GAAG;AAChC,oBAAY,KAAK,OAAO;AAAA,MAC1B;AAAA,IACF;AAEA,QAAI,YAAY,SAAS,GAAG;AAC1B,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,SAAS,2BAA2B,YAAY,MAAM,YAAY,YAAY,WAAW,IAAI,MAAM,EAAE;AAAA,QACrG,UAAU;AAAA,MACZ,CAAC;AAED,iBAAW,WAAW,aAAa;AACjC,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,SAAS,YAAY,OAAO;AAAA,UAC5B,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,6BAA6B,MAAM,aAAM,OAAO;AACjE;;;AC3HA,IAAM,iBAAuD;AAAA,EAC3D;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAGA,IAAMC,iBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKO,SAAS,UAAU,OAAgC;AACxD,QAAM,SAAsB,CAAC;AAE7B,aAAW,QAAQ,OAAO;AAExB,QAAIA,eAAc,KAAK,CAAC,MAAM,EAAE,KAAK,KAAK,YAAY,CAAC,EAAG;AAG1D,QAAI,CAAC,6BAA6B,KAAK,KAAK,YAAY,EAAG;AAE3D,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AAKzB,iBAAW,EAAE,SAAS,MAAM,KAAK,gBAAgB;AAC/C,YAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,iBAAO,KAAK;AAAA,YACV,MAAM,KAAK;AAAA,YACX,MAAM,IAAI;AAAA,YACV,SAAS;AAAA,YACT,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF;AACF;;;ACxFA,IAAM,qBAA2D;AAAA,EAC/D;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAGA,IAAM,oBAAoB;AAKnB,SAAS,UAAU,OAAgC;AACxD,QAAM,SAAsB,CAAC;AAE7B,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,kBAAkB,KAAK,EAAE,YAAY,CAAC;AAE5E,aAAW,QAAQ,WAAW;AAC5B,aAAS,IAAI,GAAG,IAAI,KAAK,MAAM,QAAQ,KAAK;AAC1C,YAAM,OAAO,KAAK,MAAM,CAAC;AAEzB,iBAAW,EAAE,SAAS,MAAM,KAAK,oBAAoB;AACnD,YAAI,QAAQ,KAAK,IAAI,GAAG;AACtB,iBAAO,KAAK;AAAA,YACV,MAAM,KAAK;AAAA,YACX,MAAM,IAAI;AAAA,YACV,SAAS;AAAA,YACT,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN;AAAA,EACF;AACF;;;AClFA,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAQ,iBAAiB;AAKlC,IAAM,gBAAgB,CAAC,eAAe,cAAc,SAAS;AAG7D,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAKA,eAAsB,kBAAkB,KAAkC;AACxE,QAAM,SAAsB,CAAC;AAG7B,MAAI,CAAE,MAAM,WAAWC,SAAQ,KAAK,WAAW,CAAC,GAAI;AAClD,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,CAAE,MAAM,WAAWA,SAAQ,KAAK,SAAS,CAAC,GAAI;AAChD,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,QAAM,aAAa,MAAM,gBAAgBA,SAAQ,KAAK,cAAc,CAAC;AAErE,MAAI,CAAC,YAAY;AACf,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC;AACD,WAAO,EAAE,MAAM,kBAAkB,MAAM,aAAM,OAAO;AAAA,EACtD;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,UAAU;AAAA,EAC7B,QAAQ;AACN,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC;AACD,WAAO,EAAE,MAAM,kBAAkB,MAAM,aAAM,OAAO;AAAA,EACtD;AAGA,QAAM,iBAAiB,CAAC,QAAQ,WAAW,aAAa;AACxD,aAAW,SAAS,gBAAgB;AAClC,QAAI,CAAC,IAAI,KAAK,GAAG;AACf,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,SAAS,4BAA4B,KAAK;AAAA,QAC1C,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,QAAQ,GAAG;AACpE,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,QAAM,UAAU,IAAI,SAAS;AAC7B,MAAI,SAAS;AACX,eAAW,cAAc,eAAe;AACtC,YAAM,cAAc,QAAQ,UAAU;AACtC,UAAI,CAAC,YAAa;AAGlB,YAAM,eAAe,oBAAoB;AAAA,QAAK,CAAC,MAC7C,EAAE,KAAK,WAAW;AAAA,MACpB;AAEA,UAAI,cAAc;AAChB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,SAAS,2BAA2B,UAAU,MAAM,WAAW;AAAA,UAC/D,UAAU;AAAA,QACZ,CAAC;AAAA,MACH,OAAO;AACL,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,SAAS,qBAAqB,UAAU,YAAY,WAAW;AAAA,UAC/D,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,kBAAkB,MAAM,aAAM,OAAO;AACtD;AAEA,eAAe,WAAW,UAAoC;AAC5D,MAAI;AACF,UAAM,OAAO,UAAU,UAAU,IAAI;AACrC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;APrHA,IAAM,UAAU;AAEhB,IAAM,YAAY;AAAA,EAChBC,IAAG,KAAKA,IAAG,KAAK,qBAAc,CAAC,CAAC,KAAK,OAAO;AAAA,EAC5CA,IAAG,IAAI,mDAAmD,CAAC;AAAA;AAAA,EAE3DA,IAAG,KAAK,QAAQ,CAAC;AAAA;AAAA;AAAA,EAGjBA,IAAG,KAAK,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnBA,IAAG,KAAK,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAUtB,SAAS,UAAU,MAA4B;AAC7C,QAAM,OAAO,KAAK,MAAM,CAAC;AACzB,QAAM,UAAsB;AAAA,IAC1B,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,CAAC;AAAA,IACT,MAAM;AAAA,EACR;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAElB,YAAQ,KAAK;AAAA,MACX,KAAK;AACH,gBAAQ,SAAS;AACjB;AAAA,MACF,KAAK;AACH,gBAAQ,OAAO;AACf;AAAA,MACF,KAAK,YAAY;AACf,cAAM,OAAO,KAAK,EAAE,CAAC;AACrB,YAAI,MAAM;AACR,kBAAQ,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,QACtE;AACA;AAAA,MACF;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,OAAO;AACf;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AACH,gBAAQ,IAAI,OAAO;AACnB,gBAAQ,KAAK,CAAC;AACd;AAAA,MACF;AACE,YAAI,IAAI,WAAW,WAAW,GAAG;AAC/B,gBAAM,QAAQ,IAAI,MAAM,YAAY,MAAM;AAC1C,kBAAQ,SAAS,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,QACvE;AACA;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;AAKA,eAAe,OAAsB;AACnC,QAAM,UAAU,UAAU,QAAQ,IAAI;AAEtC,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,SAAS;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,MAAMC,SAAQ,QAAQ,IAAI,CAAC;AAEjC,MAAI,CAAC,QAAQ,MAAM;AACjB,YAAQ,IAAI;AACZ,YAAQ;AAAA,MACND,IAAG,KAAKA,IAAG,KAAK,qBAAc,CAAC,IAC7BA,IAAG,IAAI,KAAK,OAAO,EAAE,IACrBA,IAAG,IAAI,6BAAwB;AAAA,IACnC;AACA,YAAQ,IAAI;AAAA,EACd;AAGA,QAAM,QAAQ,MAAM,SAAS,KAAK,QAAQ,MAAM;AAEhD,MAAI,CAAC,QAAQ,MAAM;AACjB,YAAQ,IAAIA,IAAG,IAAI,WAAW,MAAM,MAAM,gBAAgB,CAAC;AAC3D,YAAQ,IAAI;AAAA,EACd;AAGA,QAAM,UAAwB,CAAC;AAG/B,UAAQ,KAAK,YAAY,KAAK,CAAC;AAG/B,UAAQ,KAAK,MAAM,QAAQ,OAAO,GAAG,CAAC;AAGtC,UAAQ,KAAK,UAAU,KAAK,CAAC;AAG7B,UAAQ,KAAK,UAAU,KAAK,CAAC;AAG7B,UAAQ,KAAK,MAAM,kBAAkB,GAAG,CAAC;AAGzC,cAAY,SAAS,OAAO;AAG5B,QAAM,WAAW,YAAY,SAAS,OAAO;AAC7C,UAAQ,KAAK,QAAQ;AACvB;AAEA,KAAK,EAAE,MAAM,CAAC,QAAe;AAC3B,UAAQ,MAAMA,IAAG,IAAI;AAAA,yCAAuC,IAAI,OAAO,EAAE,CAAC;AAC1E,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["resolve","pc","resolve","resolve","SKIP_PATTERNS","resolve","resolve","pc","resolve"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhix11/shipguard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
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
5
|
"author": "adhix11",
|
|
6
6
|
"license": "MIT",
|