bridgepreflight 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # BridgePreflight
2
+
3
+ ![BridgePreflight](https://github.com/GodsfavourJesse/bridgepreflight/actions/workflows/bridgepreflight.yml/badge.svg)
4
+
5
+
6
+ <!------------------------------ ------------------------------------>
7
+ BridgePreflight
8
+
9
+ AI-native infrastructure readiness scanner for Node & TypeScript projects.
10
+
11
+ BridgePreflight analyzes your repository before code merges and assigns a Preflight Readiness Score (0–100) — automatically blocking pull requests that introduce risk.
12
+ <!------------------------------ ------------------------------------>
13
+
14
+
15
+ <!------------------------------ ------------------------------------>
16
+ The Problem
17
+ Teams merge code that:
18
+ • Has no tests
19
+ • Breaks the build
20
+ • Lacks documentation
21
+ • Has weak repository hygiene
22
+ Issues are discovered too late — during staging or production.
23
+
24
+ BridgePreflight moves detection earlier — directly into CI.
25
+ <!------------------------------ ------------------------------------>
26
+
27
+
28
+
29
+ <!------------------------------ ------------------------------------>
30
+ What BridgePreflight Does
31
+ BridgePreflight scans your repository and:
32
+ • Generates an Infrastructure Readiness Score
33
+ • Classifies readiness (Ready / Caution / Critical)
34
+ • Identifies top risk factors
35
+ • Automatically fails PRs if score < 70
36
+ • Posts a structured risk summary comment on pull requests
37
+ It integrates directly into GitHub Actions for seamless enforcement.
38
+ <!------------------------------ ------------------------------------>
39
+
40
+
41
+
42
+ <!------------------------------ ------------------------------------>
43
+ How It Works
44
+ BridgePreflight evaluates:
45
+ • Test presence and structure
46
+ • Build configuration
47
+ • CI workflow setup
48
+ • Documentation presence
49
+ • Repository hygiene
50
+ Each category contributes weighted points to the final score.
51
+
52
+ If the score falls below the threshold:
53
+ • The CI pipeline fails
54
+ • The merge button is blocked
55
+ • A risk summary is posted on the PR
56
+ <!------------------------------ ------------------------------------>
57
+
58
+
59
+ <!------------------------------ ------------------------------------>
60
+ Installation (Local Usage)
61
+ npm install
62
+ npm run build
63
+ node dist/cli.js scan
64
+
65
+
66
+ For machine-readable output:
67
+ node dist/cli.js scan --json
68
+ <!------------------------------ ------------------------------------>
69
+
70
+
71
+ <!------------------------------ ------------------------------------>
72
+ GitHub Actions Integration
73
+ Place this file in:
74
+ .github/workflows/bridgepreflight.yml
75
+
76
+
77
+ BridgePreflight will:
78
+ • Run on every push to main
79
+ • Run on every pull request
80
+ • Automatically fail if readiness < 70
81
+ • Comment with risk breakdown
82
+ <!------------------------------ ------------------------------------>
83
+
84
+
85
+ <!------------------------------ ------------------------------------>
86
+ Example Output
87
+ {
88
+ "totalScore": 82,
89
+ "readiness": "Ready",
90
+ "results": [
91
+ { "name": "Test Check", "severity": "healthy" },
92
+ { "name": "Build Check", "severity": "warning" }
93
+ ]
94
+ }
95
+ <!------------------------------ ------------------------------------>
96
+
97
+
98
+ <!------------------------------ ------------------------------------>
99
+ Why BridgePreflight Matters
100
+ BridgePreflight transforms infrastructure quality into a measurable metric.
101
+
102
+ It acts as:
103
+ • A DevOps gatekeeper
104
+ • A pre-merge risk detection layer
105
+ • A credibility signal for repositories
106
+ Instead of hoping code is safe — you measure it.
107
+ <!------------------------------ ------------------------------------>
108
+
109
+
110
+ <!------------------------------ ------------------------------------>
111
+ Roadmap
112
+ • Configurable scoring weights
113
+ • Plugin-based analyzer system
114
+ • Historical score tracking
115
+ • SaaS dashboard
116
+ • AI-based remediation suggestions
117
+ <!------------------------------ ------------------------------------>
118
+
119
+
120
+ <!------------------------------ ------------------------------------>
121
+ 📄 License
122
+ MIT
123
+ <!------------------------------ ------------------------------------>
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BuildAnalyzer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const execa_1 = require("execa");
10
+ const severity_1 = require("../utils/severity");
11
+ class BuildAnalyzer {
12
+ constructor() {
13
+ this.name = "Build Check";
14
+ this.weight = 1.5;
15
+ this.supports = ["node"];
16
+ this.MAX_SCORE = 50;
17
+ this.BUILD_TIMEOUT_MS = 60000; // 60s safety timeout
18
+ }
19
+ async run() {
20
+ const projectRoot = process.cwd();
21
+ const packageJsonPath = path_1.default.join(projectRoot, "package.json");
22
+ const tsconfigPath = path_1.default.join(projectRoot, "tsconfig.json");
23
+ let score = this.MAX_SCORE;
24
+ const findings = [];
25
+ // 1. Ensure package.json exists
26
+ if (!fs_1.default.existsSync(packageJsonPath)) {
27
+ return {
28
+ name: this.name,
29
+ success: false,
30
+ findings: [
31
+ {
32
+ type: "error",
33
+ message: "package.json file is missing.",
34
+ suggestion: "Ensure this is a valid Node.js project with a package.json file.",
35
+ },
36
+ ],
37
+ scoreImpact: 0,
38
+ maxScore: this.MAX_SCORE,
39
+ severity: "critical",
40
+ };
41
+ }
42
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf-8"));
43
+ const hasBuildScript = Boolean(pkg.scripts?.build);
44
+ const usesTypeScript = fs_1.default.existsSync(tsconfigPath);
45
+ // 2. TypeScript but no build script
46
+ if (usesTypeScript && !hasBuildScript) {
47
+ score = 10;
48
+ findings.push({
49
+ type: "error",
50
+ message: "TypeScript project detected but no build script found in package.json.",
51
+ evidence: "tsconfig.json exists but scripts.build is undefined",
52
+ suggestion: 'Add a build script (e.g., `"build": "tsc"`) to compile TypeScript before deployment.',
53
+ });
54
+ return {
55
+ name: this.name,
56
+ success: false,
57
+ findings,
58
+ scoreImpact: score,
59
+ maxScore: this.MAX_SCORE,
60
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE),
61
+ };
62
+ }
63
+ // 3. No build script at all
64
+ if (!hasBuildScript) {
65
+ findings.push({
66
+ type: "warning",
67
+ message: "No build script found in package.json.",
68
+ suggestion: "Consider defining a build step to ensure consistent production builds.",
69
+ });
70
+ return {
71
+ name: this.name,
72
+ success: true,
73
+ findings,
74
+ scoreImpact: score,
75
+ maxScore: this.MAX_SCORE,
76
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE),
77
+ };
78
+ }
79
+ // Execute build safely
80
+ try {
81
+ const { stdout, stderr } = await (0, execa_1.execa)("npm", ["run", "build"], {
82
+ cwd: projectRoot,
83
+ timeout: this.BUILD_TIMEOUT_MS,
84
+ });
85
+ const combineOutput = `${stdout}\n${stderr}`;
86
+ const warningCount = (combineOutput.match(/warning/gi) || []).length;
87
+ if (warningCount > 0) {
88
+ const deduction = Math.min(warningCount * 2, 20);
89
+ score -= deduction;
90
+ findings.push({
91
+ type: "warning",
92
+ message: `${warningCount} build warning(s) detected.`,
93
+ suggestion: "Resolve build warnings to improve production stability and reduce hidden runtime issues."
94
+ });
95
+ }
96
+ if (score < 0)
97
+ score = 0;
98
+ return {
99
+ name: this.name,
100
+ success: score === this.MAX_SCORE,
101
+ findings,
102
+ scoreImpact: score,
103
+ maxScore: this.MAX_SCORE,
104
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE),
105
+ };
106
+ }
107
+ catch (err) {
108
+ score = 0;
109
+ findings.push({
110
+ type: "error",
111
+ message: "Build execution failed.",
112
+ evidence: err?.stderr || err?.message,
113
+ suggestion: "Fix build errors before deployment. Ensure the project builds successfully in a clean environment."
114
+ });
115
+ return {
116
+ name: this.name,
117
+ success: false,
118
+ findings,
119
+ scoreImpact: score,
120
+ maxScore: this.MAX_SCORE,
121
+ severity: "critical"
122
+ };
123
+ }
124
+ }
125
+ }
126
+ exports.BuildAnalyzer = BuildAnalyzer;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EnvAnalyzer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const glob_1 = require("glob");
10
+ const severity_1 = require("../utils/severity");
11
+ class EnvAnalyzer {
12
+ constructor() {
13
+ this.name = "Environment Variables Check";
14
+ this.weight = 1;
15
+ this.supports = ["all"];
16
+ this.MAX_SCORE = 30;
17
+ }
18
+ async run() {
19
+ const projectRoot = process.cwd();
20
+ const baseDir = fs_1.default.existsSync(path_1.default.join(projectRoot, "src"))
21
+ ? path_1.default.join(projectRoot, "src")
22
+ : projectRoot;
23
+ const files = (0, glob_1.globSync)("**/*.{ts,js,tsx,jsx,html,css}", {
24
+ cwd: baseDir,
25
+ ignore: ["node_modules/**", "dist/**"],
26
+ nodir: true
27
+ });
28
+ let score = this.MAX_SCORE;
29
+ const findings = [];
30
+ const envUsageFiles = [];
31
+ // Scan up to 300 files for performance safety
32
+ for (const file of files.slice(0, 300)) {
33
+ const content = fs_1.default.readFileSync(path_1.default.join(baseDir, file), "utf-8");
34
+ if (content.includes("process.env"))
35
+ envUsageFiles.push(file);
36
+ }
37
+ const usesEnv = envUsageFiles.length > 0;
38
+ // Check for any environment file variants
39
+ const envFiles = [
40
+ ".env",
41
+ ".env.local",
42
+ ".env.development",
43
+ ".env.production"
44
+ ];
45
+ const existingEnvFile = envFiles.find((f) => fs_1.default.existsSync(path_1.default.join(projectRoot, f)));
46
+ // Detect static HTML/CSS/JS projects
47
+ const isStaticProject = files.every((f) => /\.(html|css|js)$/.test(f));
48
+ // ----------- 1. Static projects with no env usage -----------
49
+ if (isStaticProject && !usesEnv) {
50
+ findings.push({
51
+ type: "info",
52
+ message: "No .env file and no process.env usage detected.",
53
+ suggestion: "If your project will need API calls or secrets in the future, consider adding a .env file."
54
+ });
55
+ }
56
+ else {
57
+ // ----------- 2. Process.env used but no env file -----------
58
+ if (usesEnv && !existingEnvFile) {
59
+ score -= 20;
60
+ findings.push({
61
+ type: "error",
62
+ message: ".env file is missing but process.env usage was detected.",
63
+ evidence: `process.env found in ${envUsageFiles.length} file(s). Example: ${envUsageFiles.slice(0, 3).join(", ")}`,
64
+ suggestion: "Create a .env file (or .env.local/.env.development) for local development or configure environment variables in your deployment platform."
65
+ });
66
+ }
67
+ }
68
+ // ----------- 3. Recommend .env.example even if env file exists -----------
69
+ if (!fs_1.default.existsSync(path_1.default.join(projectRoot, ".env.example"))) {
70
+ findings.push({
71
+ type: "info",
72
+ message: ".env.example file is missing.",
73
+ suggestion: "Add a .env.example file to document required environment variables for your team and CI/CD environments."
74
+ });
75
+ }
76
+ if (score < 0)
77
+ score = 0;
78
+ return {
79
+ name: this.name,
80
+ success: score === this.MAX_SCORE,
81
+ findings,
82
+ scoreImpact: score,
83
+ maxScore: this.MAX_SCORE,
84
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE)
85
+ };
86
+ }
87
+ }
88
+ exports.EnvAnalyzer = EnvAnalyzer;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NetworkAnalyzer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const glob_1 = require("glob");
10
+ const severity_1 = require("../utils/severity");
11
+ class NetworkAnalyzer {
12
+ constructor() {
13
+ this.name = "Localhost Leak Check";
14
+ this.weight = 1.2;
15
+ this.supports = ["all"];
16
+ this.MAX_SCORE = 20;
17
+ this.MAX_FILES_TO_SCAN = 500;
18
+ }
19
+ async run() {
20
+ const projectRoot = process.cwd();
21
+ const baseDir = fs_1.default.existsSync(path_1.default.join(projectRoot, "src"))
22
+ ? path_1.default.join(projectRoot, "src")
23
+ : projectRoot;
24
+ const files = (0, glob_1.globSync)("**/*.{ts,js,tsx,jsx}", {
25
+ cwd: baseDir,
26
+ ignore: ["node_modules/**", "dist/**"],
27
+ nodir: true
28
+ });
29
+ let score = this.MAX_SCORE;
30
+ const findings = [];
31
+ const leakFiles = [];
32
+ for (const file of files.slice(0, this.MAX_FILES_TO_SCAN)) {
33
+ const fullPath = path_1.default.join(baseDir, file);
34
+ const content = fs_1.default.readFileSync(fullPath, "utf-8");
35
+ // Basic but effective detection
36
+ if (content.includes("localhost") ||
37
+ content.includes("127.0.0.1")) {
38
+ leakFiles.push(file);
39
+ }
40
+ }
41
+ const leakCount = leakFiles.length;
42
+ if (leakCount > 0) {
43
+ // Deduct up to a max of 12 points (prevent total collapse)
44
+ const deduction = Math.min(leakCount * 2, 12);
45
+ score -= deduction;
46
+ findings.push({
47
+ type: leakCount > 5 ? "error" : "warning",
48
+ message: `${leakCount} hardcoded localhost reference(s) detected.`,
49
+ evidence: `Examples: ${leakFiles.slice(0, 5).join(", ")}`,
50
+ suggestion: "Replace hardcoded localhost or 127.0.0.1 URLs with environment-based configuration (e.g., process.env.API_BASE_URL).",
51
+ });
52
+ }
53
+ if (score < 0)
54
+ score = 0;
55
+ return {
56
+ name: this.name,
57
+ success: leakCount === 0,
58
+ findings,
59
+ scoreImpact: score,
60
+ maxScore: this.MAX_SCORE,
61
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE)
62
+ };
63
+ }
64
+ }
65
+ exports.NetworkAnalyzer = NetworkAnalyzer;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RuntimeAnalyzer = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const severity_1 = require("../utils/severity");
10
+ class RuntimeAnalyzer {
11
+ constructor() {
12
+ this.name = "Runtime Compatibility Check";
13
+ this.weight = 2;
14
+ this.supports = ["node"];
15
+ this.MAX_SCORE = 20;
16
+ }
17
+ async run() {
18
+ const projectRoot = process.cwd();
19
+ const packageJsonPath = path_1.default.join(projectRoot, "package.json");
20
+ let score = 0;
21
+ const findings = [];
22
+ // Check Node engine declaration
23
+ if (fs_1.default.existsSync(packageJsonPath)) {
24
+ const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf-8"));
25
+ if (!pkg.engines?.node) {
26
+ score -= 8;
27
+ findings.push({
28
+ type: "error",
29
+ message: "Node.js version is not specified in package.json (engines.node missing).",
30
+ evidence: "package.json does not define engines.node",
31
+ suggestion: 'Add `"engines": { "node": ">=18.x" }` to package.json to enforce runtime consistency.',
32
+ });
33
+ }
34
+ }
35
+ else {
36
+ score = 0;
37
+ findings.push({
38
+ type: "error",
39
+ message: "package.json file is missing.",
40
+ suggestion: "Ensure this project is a valid Node.js project with a package.json file.",
41
+ });
42
+ }
43
+ // Check version manager config (.nvmrc / .node-version)
44
+ const hasNodeVersionFile = fs_1.default.existsSync(path_1.default.join(projectRoot, ".nvmrc")) ||
45
+ fs_1.default.existsSync(path_1.default.join(projectRoot, ".node-version"));
46
+ if (!hasNodeVersionFile) {
47
+ score -= 6;
48
+ findings.push({
49
+ type: "warning",
50
+ message: "No Node version manager file found (.nvmrc or .node-version).",
51
+ suggestion: "Add a .nvmrc or .node-version file to standardize Node.js versions across environments.",
52
+ });
53
+ }
54
+ // Check dependency lockfile
55
+ const lockfiles = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"];
56
+ const detectedLockfile = lockfiles.find(file => fs_1.default.existsSync(path_1.default.join(projectRoot, file)));
57
+ if (!detectedLockfile) {
58
+ score -= 6;
59
+ findings.push({
60
+ type: "error",
61
+ message: "No dependency lockfile detected.",
62
+ evidence: "Missing package-lock.json, yarn.lock, or pnpm-lock.yaml",
63
+ suggestion: "Commit a lockfile to ensure deterministic dependency installation across environments.",
64
+ });
65
+ }
66
+ if (score < 0)
67
+ score = 0;
68
+ return {
69
+ name: this.name,
70
+ success: score === this.MAX_SCORE,
71
+ findings,
72
+ scoreImpact: score,
73
+ maxScore: this.MAX_SCORE,
74
+ severity: (0, severity_1.calculateSeverity)(score, this.MAX_SCORE),
75
+ };
76
+ }
77
+ }
78
+ exports.RuntimeAnalyzer = RuntimeAnalyzer;
package/dist/cli.js ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const scan_1 = require("./core/scan");
43
+ const build_1 = require("./analyzers/build");
44
+ const env_1 = require("./analyzers/env");
45
+ const network_1 = require("./analyzers/network");
46
+ const runtime_1 = require("./analyzers/runtime");
47
+ const projectUtils_1 = require("./utils/projectUtils");
48
+ const ci_1 = require("./utils/ci");
49
+ const program = new commander_1.Command();
50
+ function renderProgressBar(percentage, length = 20) {
51
+ const safe = Math.max(0, Math.min(percentage, 100));
52
+ const filled = Math.round((safe / 100) * length);
53
+ const bar = "█".repeat(filled) + "░".repeat(length - filled);
54
+ return `[${bar}] ${safe.toFixed(1)}%`;
55
+ }
56
+ program
57
+ .name("bridgepreflight")
58
+ .description("AI-native production safety scanner")
59
+ .version("0.0.1");
60
+ program
61
+ .command("scan")
62
+ .option("--json", "Output results as JSON (for CI pipelines)")
63
+ .description("Run production safety checks")
64
+ .action(async (options) => {
65
+ console.log(chalk_1.default.blue("\nWelcome to BridgePreflight — AI-native infrastructure readiness scanner\n"));
66
+ const ciMode = (0, ci_1.isCI)();
67
+ if (!ciMode) {
68
+ const accessGranted = await (0, projectUtils_1.askProjectAccess)();
69
+ if (!accessGranted) {
70
+ console.log(chalk_1.default.blue("\nThank you for using BridgePreflight"));
71
+ process.exit(0);
72
+ }
73
+ }
74
+ else {
75
+ console.log(chalk_1.default.gray("CI environment detected — interactive prompts disabled."));
76
+ }
77
+ const projectType = (0, projectUtils_1.detectProjectType)();
78
+ if (projectType === "static") {
79
+ console.log(chalk_1.default.yellow("Detected static HTML/CSS/JS project. Node-specific checks will be skipped."));
80
+ }
81
+ let spinner;
82
+ try {
83
+ const ora = (await Promise.resolve().then(() => __importStar(require("ora")))).default;
84
+ spinner = ora("Running analyzers...").start();
85
+ const allAnalyzers = [
86
+ new build_1.BuildAnalyzer(),
87
+ new env_1.EnvAnalyzer(),
88
+ new network_1.NetworkAnalyzer(),
89
+ new runtime_1.RuntimeAnalyzer(),
90
+ ];
91
+ const analyzers = allAnalyzers.filter((a) => a.supports.includes(projectType) ||
92
+ a.supports.includes("all"));
93
+ const skippedAnalyzers = allAnalyzers.filter((a) => !analyzers.includes(a));
94
+ const engine = new scan_1.ScanEngine(analyzers);
95
+ const results = await engine.runAll();
96
+ spinner.stop();
97
+ // =============================
98
+ // 1. Scoring System
99
+ // =============================
100
+ let totalScore = 0;
101
+ let totalMaxScore = 0;
102
+ let weightedScore = 0;
103
+ let weightedMaxScore = 0;
104
+ results.forEach(({ analyzer, result }) => {
105
+ const weight = analyzer.weight ?? 1;
106
+ totalScore += result.scoreImpact;
107
+ totalMaxScore += result.maxScore;
108
+ weightedScore += result.scoreImpact * weight;
109
+ weightedMaxScore += result.maxScore * weight;
110
+ });
111
+ const percentage = weightedMaxScore === 0
112
+ ? 100
113
+ : (weightedScore / weightedMaxScore) * 100;
114
+ let readinessLabel;
115
+ let readinessColor;
116
+ if (percentage >= 90) {
117
+ readinessLabel = "Safe to Deploy";
118
+ readinessColor = chalk_1.default.green;
119
+ }
120
+ else if (percentage >= 70) {
121
+ readinessLabel = "Deploy with Caution";
122
+ readinessColor = chalk_1.default.yellow;
123
+ }
124
+ else {
125
+ readinessLabel = "High Risk";
126
+ readinessColor = chalk_1.default.red;
127
+ }
128
+ // =============================
129
+ // 2. Top Risk Detection
130
+ // =============================
131
+ let topRisk;
132
+ let lowestHealth = 101;
133
+ for (const { result } of results) {
134
+ if (result.maxScore === 0)
135
+ continue;
136
+ const health = (result.scoreImpact / result.maxScore) * 100;
137
+ if (health < lowestHealth) {
138
+ lowestHealth = health;
139
+ topRisk = {
140
+ name: result.name,
141
+ health,
142
+ };
143
+ }
144
+ }
145
+ // =============================
146
+ // JSON Output Mode
147
+ // =============================
148
+ if (options.json) {
149
+ console.log(JSON.stringify({
150
+ totalScore,
151
+ maxScore: totalMaxScore,
152
+ percentage,
153
+ readiness: readinessLabel,
154
+ results,
155
+ }, null, 2));
156
+ process.exit(percentage < 70 ? 1 : 0);
157
+ }
158
+ // =============================
159
+ // 3. Analyzer Overview
160
+ // =============================
161
+ console.log(chalk_1.default.blue("\n--- BridgePreflight Scan ---\n"));
162
+ results.forEach(({ result, duration }) => {
163
+ const health = result.maxScore === 0
164
+ ? 100
165
+ : (result.scoreImpact / result.maxScore) * 100;
166
+ const icon = health >= 90 ? "✅" :
167
+ health >= 70 ? "⚠" : "❌";
168
+ const color = health >= 90
169
+ ? chalk_1.default.green
170
+ : health >= 70
171
+ ? chalk_1.default.yellow
172
+ : chalk_1.default.red;
173
+ console.log(color(`${icon} ${result.name} (${result.scoreImpact}/${result.maxScore}) - ${duration}ms`));
174
+ console.log(color(` ${renderProgressBar(health)}`));
175
+ });
176
+ // Show skipped analyzers clearly
177
+ skippedAnalyzers.forEach((a) => {
178
+ console.log(chalk_1.default.gray(`➖ ${a.name} skipped for ${projectType} project.`));
179
+ });
180
+ // =============================
181
+ // 4. Total Summary
182
+ // =============================
183
+ console.log(chalk_1.default.blue("\n-----------------------------"));
184
+ console.log(chalk_1.default.magenta.bold(`Total Score: ${totalScore}/${totalMaxScore} (${percentage.toFixed(1)}%)`));
185
+ console.log(chalk_1.default.gray("Weighted scoring applied based on risk category"));
186
+ console.log(readinessColor.bold(`Readiness: ${readinessLabel}`));
187
+ if (topRisk && topRisk.health < 100) {
188
+ const riskColor = topRisk.health >= 70
189
+ ? chalk_1.default.yellow
190
+ : chalk_1.default.red;
191
+ console.log(riskColor.bold(`Top Risk Area: ${topRisk.name} (${topRisk.health.toFixed(1)}% health)`));
192
+ }
193
+ else {
194
+ console.log(chalk_1.default.gray("Top Risk Area: None (all checks passed)"));
195
+ }
196
+ console.log(chalk_1.default.blue("-----------------------------\n"));
197
+ // =============================
198
+ // 5. Detailed Findings
199
+ // =============================
200
+ console.log(chalk_1.default.cyan.bold("--- Detailed Findings ---\n"));
201
+ results.forEach(({ analyzer, result }) => {
202
+ if (result.findings.length === 0)
203
+ return;
204
+ console.log(chalk_1.default.yellow.bold(`${analyzer.name} Findings:`));
205
+ result.findings.forEach((f) => {
206
+ const typeColor = f.type === "error"
207
+ ? chalk_1.default.red.bold
208
+ : f.type === "warning"
209
+ ? chalk_1.default.yellow.bold
210
+ : chalk_1.default.blue;
211
+ console.log(typeColor(` • ${f.type.toUpperCase()}: ${f.message}`));
212
+ if (f.evidence) {
213
+ console.log(chalk_1.default.gray(` • Evidence: ${f.evidence}`));
214
+ }
215
+ if (f.suggestion) {
216
+ console.log(chalk_1.default.cyan(` → Suggested action: ${f.suggestion}`));
217
+ }
218
+ });
219
+ console.log("");
220
+ });
221
+ if (percentage < 70)
222
+ process.exit(1);
223
+ }
224
+ catch (error) {
225
+ if (spinner)
226
+ spinner.stop();
227
+ console.error(chalk_1.default.red("Error running scan:"), error);
228
+ process.exit(1);
229
+ }
230
+ });
231
+ program.parse(process.argv);
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScanEngine = void 0;
4
+ class ScanEngine {
5
+ constructor(analyzers) {
6
+ this.analyzers = analyzers;
7
+ }
8
+ async runAll() {
9
+ const results = [];
10
+ for (const analyzer of this.analyzers) {
11
+ const start = Date.now();
12
+ const result = await analyzer.run();
13
+ const duration = Date.now() - start;
14
+ results.push({
15
+ analyzer,
16
+ result,
17
+ duration,
18
+ });
19
+ }
20
+ return results;
21
+ }
22
+ }
23
+ exports.ScanEngine = ScanEngine;
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROJECT_TYPES = void 0;
4
+ exports.PROJECT_TYPES = {
5
+ NODE: "node",
6
+ STATIC: "static",
7
+ FRONTEND: "frontend",
8
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ // Detects whether BridgePreflight is running in a CI environment.
3
+ // Supports major CI providers and generic CI flag.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.isCI = isCI;
6
+ function isCI() {
7
+ const env = process.env;
8
+ return Boolean(env.CI ||
9
+ env.GITHUB_ACTIONS ||
10
+ env.GITLAB_CI ||
11
+ env.BITBUCKET_BUILD_NUMBER ||
12
+ env.CIRCLECI ||
13
+ env.BUILDKITE ||
14
+ env.TRAVIS ||
15
+ env.TEAMCITY_VERSION ||
16
+ env.AZURE_HTTP_USER_AGENT ||
17
+ env.JENKINS_URL);
18
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.prompt = prompt;
7
+ exports.askProjectAccess = askProjectAccess;
8
+ exports.detectProjectType = detectProjectType;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const readline_1 = __importDefault(require("readline"));
12
+ const ci_1 = require("./ci");
13
+ // Prompt user with a question in CLI
14
+ async function prompt(question) {
15
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
16
+ return new Promise(resolve => {
17
+ rl.question(question, answer => {
18
+ rl.close();
19
+ resolve(answer.trim().toLowerCase());
20
+ });
21
+ });
22
+ }
23
+ // Ask the developer for permission to scan the project
24
+ async function askProjectAccess() {
25
+ if ((0, ci_1.isCI)())
26
+ return true;
27
+ const answer = await prompt(chalk_1.default.blackBright("BridgePreflight needs access to scan your entire project. Allow? (y/n): "));
28
+ return answer === "y";
29
+ }
30
+ // Detect project type: Node, Frontend, or Static HTML/CSS/JS
31
+ function detectProjectType() {
32
+ const files = fs_1.default.readdirSync(process.cwd());
33
+ if (files.includes("package.json"))
34
+ return "node";
35
+ if (files.some(f => f.endsWith(".html")))
36
+ return "static";
37
+ return "frontend";
38
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateSeverity = calculateSeverity;
4
+ function calculateSeverity(score, max) {
5
+ const percent = max === 0 ? 100 : (score / max) * 100;
6
+ if (percent === 100)
7
+ return "healthy";
8
+ if (percent >= 75)
9
+ return "low";
10
+ if (percent >= 50)
11
+ return "medium";
12
+ if (percent >= 25)
13
+ return "high";
14
+ return "critical";
15
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "bridgepreflight",
3
+ "version": "1.0.0",
4
+ "description": "AI-native production readiness scanner for developers",
5
+ "bin": {
6
+ "bridgepreflight": "./dist/cli.js"
7
+ },
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "ts-node src/cli.ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "devtools",
18
+ "ci",
19
+ "production",
20
+ "deployment",
21
+ "node",
22
+ "scanner"
23
+ ],
24
+ "author": "Godsfavour Jesse",
25
+ "license": "MIT",
26
+ "type": "commonjs",
27
+ "dependencies": {
28
+ "chalk": "^5.6.2",
29
+ "commander": "^14.0.3",
30
+ "execa": "^9.6.1",
31
+ "glob": "^13.0.2",
32
+ "ora": "^9.3.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/glob": "^8.1.0",
36
+ "@types/node": "^25.2.3",
37
+ "ts-node": "^10.9.2",
38
+ "typescript": "^5.9.3"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ }
43
+ }