ready-to-ship 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.
@@ -0,0 +1,117 @@
1
+ const path = require('path');
2
+ const { fileExists, parseEnvFile, readFile } = require('../utils/fileHelpers');
3
+ const { error, success, verdict, printIssues } = require('../utils/logHelpers');
4
+ const { isValidUrl, isValidEmail, isValidNumber } = require('../utils/parseHelpers');
5
+
6
+ /**
7
+ * Validate environment variables
8
+ */
9
+ async function validate(projectPath = process.cwd()) {
10
+ const issues = [];
11
+ const warnings = [];
12
+
13
+ const envPath = path.join(projectPath, '.env');
14
+ const envExamplePath = path.join(projectPath, '.env.example');
15
+
16
+ // Check if .env exists
17
+ const envExists = await fileExists(envPath);
18
+ const envExampleExists = await fileExists(envExamplePath);
19
+
20
+ if (!envExists) {
21
+ issues.push('MISSING: .env file not found');
22
+ }
23
+
24
+ if (!envExampleExists) {
25
+ warnings.push('WARNING: .env.example file not found (recommended for team collaboration)');
26
+ }
27
+
28
+ // Parse .env.example to get expected variables
29
+ let expectedVars = {};
30
+ if (envExampleExists) {
31
+ expectedVars = await parseEnvFile(envExamplePath);
32
+ }
33
+
34
+ // Parse .env file
35
+ let actualVars = {};
36
+ if (envExists) {
37
+ actualVars = await parseEnvFile(envPath);
38
+ }
39
+
40
+ // Check for missing variables
41
+ const missingVars = Object.keys(expectedVars).filter(key => !(key in actualVars));
42
+ missingVars.forEach(key => {
43
+ issues.push(`MISSING: ${key}`);
44
+ });
45
+
46
+ // Check for weak secrets
47
+ const secretKeys = Object.keys(actualVars).filter(key =>
48
+ /SECRET|KEY|PASSWORD|TOKEN/i.test(key)
49
+ );
50
+
51
+ secretKeys.forEach(key => {
52
+ const value = actualVars[key];
53
+ if (!value) {
54
+ issues.push(`EMPTY: ${key} is empty`);
55
+ } else if (value.length < 32 && key.toUpperCase().includes('SECRET')) {
56
+ issues.push(`WEAK SECRET: ${key} (length < 32)`);
57
+ } else if (value.length < 16 && /PASSWORD|KEY/i.test(key)) {
58
+ warnings.push(`WEAK: ${key} might be too short (length < 16)`);
59
+ }
60
+ });
61
+
62
+ // Check for unused variables (in .env but not in .env.example)
63
+ if (envExampleExists) {
64
+ const unusedVars = Object.keys(actualVars).filter(key =>
65
+ !(key in expectedVars) && !key.startsWith('#')
66
+ );
67
+ if (unusedVars.length > 0) {
68
+ warnings.push(`UNUSED: Variables in .env but not in .env.example: ${unusedVars.join(', ')}`);
69
+ }
70
+ }
71
+
72
+ // Type validation (basic checks)
73
+ Object.keys(actualVars).forEach(key => {
74
+ const value = actualVars[key];
75
+
76
+ if (key.includes('URL') && value && !isValidUrl(value) && !value.startsWith('${')) {
77
+ warnings.push(`INVALID URL: ${key} does not appear to be a valid URL`);
78
+ }
79
+
80
+ if (key.includes('EMAIL') && value && !isValidEmail(value) && !value.startsWith('${')) {
81
+ warnings.push(`INVALID EMAIL: ${key} does not appear to be a valid email`);
82
+ }
83
+
84
+ if (key.includes('PORT') && value && !isValidNumber(value)) {
85
+ warnings.push(`INVALID PORT: ${key} should be a number`);
86
+ }
87
+ });
88
+
89
+ // Print results
90
+ console.log('\n🔹 ENV VALIDATION\n');
91
+
92
+ if (issues.length === 0 && warnings.length === 0) {
93
+ success('All environment variables are properly configured');
94
+ verdict(true, 'ENV: READY');
95
+ return { passed: true, issues: [], warnings: [] };
96
+ }
97
+
98
+ printIssues(issues, 'error');
99
+ printIssues(warnings, 'warning');
100
+
101
+ if (issues.length === 0) {
102
+ verdict(true, 'ENV: READY (with warnings)');
103
+ } else {
104
+ verdict(false, 'ENV: NOT READY');
105
+ }
106
+
107
+ return {
108
+ passed: issues.length === 0,
109
+ issues,
110
+ warnings
111
+ };
112
+ }
113
+
114
+ module.exports = {
115
+ validate
116
+ };
117
+
@@ -0,0 +1,175 @@
1
+ const path = require('path');
2
+ const { fileExists, readFile, getAllFiles } = require('../utils/fileHelpers');
3
+ const { error, success, verdict, printIssues } = require('../utils/logHelpers');
4
+ const { hasErrorHandling } = require('../utils/parseHelpers');
5
+
6
+ /**
7
+ * Validate project structure and best practices
8
+ */
9
+ async function validate(projectPath = process.cwd()) {
10
+ const issues = [];
11
+ const warnings = [];
12
+
13
+ // Check for .env.example
14
+ const envExamplePath = path.join(projectPath, '.env.example');
15
+ const envExampleExists = await fileExists(envExamplePath);
16
+
17
+ if (!envExampleExists) {
18
+ issues.push('.env.example missing (required for team collaboration)');
19
+ }
20
+
21
+ // Check for README
22
+ const readmePaths = [
23
+ path.join(projectPath, 'README.md'),
24
+ path.join(projectPath, 'README.txt'),
25
+ path.join(projectPath, 'readme.md')
26
+ ];
27
+
28
+ let readmeExists = false;
29
+ let readmePath = null;
30
+ let readmeContent = '';
31
+
32
+ for (const readmePathOption of readmePaths) {
33
+ if (await fileExists(readmePathOption)) {
34
+ readmeExists = true;
35
+ readmePath = readmePathOption;
36
+ readmeContent = await readFile(readmePathOption) || '';
37
+ break;
38
+ }
39
+ }
40
+
41
+ if (!readmeExists) {
42
+ issues.push('README missing');
43
+ } else {
44
+ // Check if README has meaningful content
45
+ const meaningfulContent = readmeContent.length > 100;
46
+ const hasInstallation = /install|setup|getting started/i.test(readmeContent);
47
+ const hasUsage = /usage|how to|example/i.test(readmeContent);
48
+
49
+ if (!meaningfulContent) {
50
+ warnings.push('README exists but seems too short or empty');
51
+ } else if (!hasInstallation && !hasUsage) {
52
+ warnings.push('README missing installation or usage instructions');
53
+ }
54
+ }
55
+
56
+ // Check folder structure
57
+ const expectedFolders = ['src', 'routes', 'config', 'middleware', 'controllers', 'models'];
58
+ const existingFolders = [];
59
+
60
+ for (const folder of expectedFolders) {
61
+ const folderPath = path.join(projectPath, folder);
62
+ if (await fileExists(folderPath)) {
63
+ existingFolders.push(folder);
64
+ }
65
+ }
66
+
67
+ // Check for src/ or similar structure
68
+ const srcPath = path.join(projectPath, 'src');
69
+ const hasSrc = await fileExists(srcPath);
70
+
71
+ if (!hasSrc && existingFolders.length === 0) {
72
+ warnings.push('No standard project structure detected (src/, routes/, etc.)');
73
+ }
74
+
75
+ // Check for error handling
76
+ let hasGlobalErrorHandler = false;
77
+ const errorHandlerPatterns = [
78
+ '**/middleware/**/*.{js,ts}',
79
+ '**/middleware.{js,ts}',
80
+ '**/error*.{js,ts}',
81
+ '**/src/**/*.{js,ts}'
82
+ ];
83
+
84
+ for (const pattern of errorHandlerPatterns) {
85
+ const files = await require('../utils/fileHelpers').findFiles(pattern, projectPath);
86
+ for (const filePath of files.slice(0, 10)) { // Limit to first 10 files
87
+ const code = await readFile(filePath);
88
+ if (code && hasErrorHandling(code)) {
89
+ hasGlobalErrorHandler = true;
90
+ break;
91
+ }
92
+ }
93
+ if (hasGlobalErrorHandler) break;
94
+ }
95
+
96
+ // Also check main app file
97
+ if (!hasGlobalErrorHandler) {
98
+ const mainFiles = await require('../utils/fileHelpers').findFiles('**/{app,server,index,main}.{js,ts}', projectPath);
99
+ for (const filePath of mainFiles.slice(0, 3)) {
100
+ const code = await readFile(filePath);
101
+ if (code && hasErrorHandling(code)) {
102
+ hasGlobalErrorHandler = true;
103
+ break;
104
+ }
105
+ }
106
+ }
107
+
108
+ if (!hasGlobalErrorHandler) {
109
+ issues.push('Error handling middleware not found (recommended: global error handler)');
110
+ }
111
+
112
+ // Check for package.json
113
+ const packageJsonPath = path.join(projectPath, 'package.json');
114
+ const packageJsonExists = await fileExists(packageJsonPath);
115
+
116
+ if (!packageJsonExists) {
117
+ issues.push('package.json missing');
118
+ } else {
119
+ const packageJsonContent = await readFile(packageJsonPath);
120
+ if (packageJsonContent) {
121
+ try {
122
+ const pkg = JSON.parse(packageJsonContent);
123
+
124
+ // Check for start script
125
+ if (!pkg.scripts || !pkg.scripts.start) {
126
+ warnings.push('package.json missing "start" script');
127
+ }
128
+
129
+ // Check for description
130
+ if (!pkg.description) {
131
+ warnings.push('package.json missing "description" field');
132
+ }
133
+ } catch (e) {
134
+ warnings.push('package.json might be malformed');
135
+ }
136
+ }
137
+ }
138
+
139
+ // Print results
140
+ console.log('\n🔹 PROJECT VALIDATION\n');
141
+
142
+ if (readmeExists) {
143
+ success('README found');
144
+ }
145
+
146
+ if (envExampleExists) {
147
+ success('.env.example found');
148
+ }
149
+
150
+ if (hasGlobalErrorHandler) {
151
+ success('Error handling found');
152
+ }
153
+
154
+ if (issues.length === 0 && warnings.length === 0) {
155
+ success('Project structure looks good');
156
+ verdict(true, 'PROJECT: READY');
157
+ return { passed: true, issues: [], warnings: [] };
158
+ }
159
+
160
+ printIssues(issues, 'error');
161
+ printIssues(warnings, 'warning');
162
+
163
+ verdict(false, 'PROJECT: NOT READY');
164
+
165
+ return {
166
+ passed: issues.length === 0,
167
+ issues,
168
+ warnings
169
+ };
170
+ }
171
+
172
+ module.exports = {
173
+ validate
174
+ };
175
+
@@ -0,0 +1,118 @@
1
+ const envModule = require('./env');
2
+ const authModule = require('./auth');
3
+ const apiModule = require('./api');
4
+ const projectModule = require('./project');
5
+ const securityModule = require('./security');
6
+ const dependenciesModule = require('./dependencies');
7
+ const databaseModule = require('./database');
8
+ const { header, verdict, error, success } = require('../utils/logHelpers');
9
+
10
+ /**
11
+ * Generate comprehensive report combining all checks
12
+ */
13
+ async function generate(projectPath = process.cwd(), options = {}) {
14
+ const { json = false, verbose = false, skip = [] } = options;
15
+
16
+ header('READY-TO-SHIP REPORT');
17
+
18
+ // Run all validations (skip specified modules)
19
+ const allModules = {
20
+ env: envModule,
21
+ auth: authModule,
22
+ api: apiModule,
23
+ project: projectModule,
24
+ security: securityModule,
25
+ dependencies: dependenciesModule,
26
+ database: databaseModule
27
+ };
28
+
29
+ const results = {};
30
+ for (const [name, module] of Object.entries(allModules)) {
31
+ if (!skip.includes(name)) {
32
+ results[name] = await module.validate(projectPath);
33
+ }
34
+ }
35
+
36
+ // Calculate overall status
37
+ const allPassed = Object.values(results).every(result => result.passed);
38
+
39
+ // Print summary
40
+ console.log('\n' + '='.repeat(50));
41
+ console.log('SUMMARY');
42
+ console.log('='.repeat(50) + '\n');
43
+
44
+ const statusIcon = (passed) => passed ? '✅' : '❌';
45
+ const statusText = (passed) => passed ? 'PASS' : 'FAIL';
46
+
47
+ // Print module statuses
48
+ Object.entries(results).forEach(([name, result]) => {
49
+ const nameUpper = name.toUpperCase().padEnd(12);
50
+ console.log(`${nameUpper} ${statusIcon(result.passed)} ${statusText(result.passed)}`);
51
+ });
52
+
53
+ // Count issues
54
+ const totalIssues = Object.values(results).reduce((sum, result) => sum + (result.issues?.length || 0), 0);
55
+ const totalWarnings = Object.values(results).reduce((sum, result) => sum + (result.warnings?.length || 0), 0);
56
+
57
+ console.log('\n' + '='.repeat(50));
58
+ console.log(`Total Issues: ${totalIssues}`);
59
+ console.log(`Total Warnings: ${totalWarnings}`);
60
+ console.log('='.repeat(50) + '\n');
61
+
62
+ // Final verdict
63
+ if (allPassed) {
64
+ verdict(true, 'FINAL VERDICT: ✅ READY TO SHIP');
65
+ success('Your backend project looks ready for deployment!');
66
+ } else {
67
+ verdict(false, 'FINAL VERDICT: ❌ NOT READY');
68
+ error('Please fix the issues above before deploying.');
69
+
70
+ if (verbose) {
71
+ console.log('\n📋 Detailed Issues:\n');
72
+ Object.entries(results).forEach(([module, result]) => {
73
+ if (result.issues && result.issues.length > 0) {
74
+ console.log(`\n${module.toUpperCase()}:`);
75
+ result.issues.forEach(issue => error(` - ${issue}`));
76
+ }
77
+ if (result.warnings && result.warnings.length > 0) {
78
+ result.warnings.forEach(warning => console.log(` ⚠️ ${warning}`));
79
+ }
80
+ });
81
+ }
82
+ }
83
+
84
+ // Export to JSON if requested
85
+ if (json) {
86
+ const jsonReport = {
87
+ timestamp: new Date().toISOString(),
88
+ projectPath,
89
+ verdict: allPassed ? 'READY' : 'NOT_READY',
90
+ summary: Object.fromEntries(
91
+ Object.entries(results).map(([name, result]) => [
92
+ name,
93
+ {
94
+ passed: result.passed,
95
+ issues: result.issues?.length || 0,
96
+ warnings: result.warnings?.length || 0
97
+ }
98
+ ])
99
+ ),
100
+ details: results
101
+ };
102
+
103
+ const fs = require('fs-extra');
104
+ const reportPath = require('path').join(projectPath, 'ready-to-ship-report.json');
105
+ await fs.writeJson(reportPath, jsonReport, { spaces: 2 });
106
+ console.log(`\n📄 JSON report saved to: ${reportPath}`);
107
+ }
108
+
109
+ return {
110
+ passed: allPassed,
111
+ results
112
+ };
113
+ }
114
+
115
+ module.exports = {
116
+ generate
117
+ };
118
+
@@ -0,0 +1,149 @@
1
+ const path = require('path');
2
+ const { findFiles, readFile } = require('../utils/fileHelpers');
3
+ const { error, success, verdict, printIssues } = require('../utils/logHelpers');
4
+
5
+ /**
6
+ * Validate security configurations
7
+ */
8
+ async function validate(projectPath = process.cwd()) {
9
+ const issues = [];
10
+ const warnings = [];
11
+
12
+ // Find main app files
13
+ const appFiles = await findFiles('**/{app,server,index,main}.{js,ts}', projectPath);
14
+ const configFiles = await findFiles('**/config/**/*.{js,ts}', projectPath);
15
+ const middlewareFiles = await findFiles('**/middleware/**/*.{js,ts}', projectPath);
16
+
17
+ const allFiles = [...appFiles, ...configFiles, ...middlewareFiles];
18
+
19
+ let hasCORS = false;
20
+ let hasHelmet = false;
21
+ let hasRateLimit = false;
22
+ let hasSecurityHeaders = false;
23
+ let corsConfig = null;
24
+
25
+ // Analyze files for security features
26
+ for (const filePath of allFiles.slice(0, 20)) { // Limit to first 20 files
27
+ const code = await readFile(filePath);
28
+ if (!code) continue;
29
+
30
+ // Check for CORS
31
+ if (/cors|CORS/i.test(code)) {
32
+ hasCORS = true;
33
+ const corsMatch = code.match(/cors\s*\([^)]*\)/i);
34
+ if (corsMatch) {
35
+ corsConfig = corsMatch[0];
36
+ // Check for overly permissive CORS
37
+ if (/origin\s*:\s*['"]\*['"]/i.test(corsConfig)) {
38
+ issues.push('CORS configured with wildcard origin (*) - security risk');
39
+ }
40
+ }
41
+ }
42
+
43
+ // Check for Helmet.js (security headers)
44
+ if (/helmet|helmet\(\)/i.test(code)) {
45
+ hasHelmet = true;
46
+ hasSecurityHeaders = true;
47
+ }
48
+
49
+ // Check for security headers manually
50
+ if (/x-frame-options|x-content-type-options|x-xss-protection|strict-transport-security/i.test(code)) {
51
+ hasSecurityHeaders = true;
52
+ }
53
+
54
+ // Check for rate limiting
55
+ if (/rateLimit|rate-limit|express-rate-limit|limiter/i.test(code)) {
56
+ hasRateLimit = true;
57
+ }
58
+ }
59
+
60
+ // Check package.json for security dependencies
61
+ const packageJsonPath = path.join(projectPath, 'package.json');
62
+ const packageJsonContent = await readFile(packageJsonPath);
63
+ let packageJson = {};
64
+
65
+ if (packageJsonContent) {
66
+ try {
67
+ packageJson = JSON.parse(packageJsonContent);
68
+ } catch (e) {
69
+ // Invalid JSON
70
+ }
71
+ }
72
+
73
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
74
+ const securityPackages = {
75
+ 'helmet': 'helmet',
76
+ 'cors': 'cors',
77
+ 'express-rate-limit': 'express-rate-limit',
78
+ 'express-slow-down': 'express-slow-down',
79
+ 'hpp': 'hpp',
80
+ 'xss': 'xss'
81
+ };
82
+
83
+ Object.keys(securityPackages).forEach(pkg => {
84
+ if (!deps[pkg] && !hasHelmet && pkg === 'helmet') {
85
+ warnings.push(`Security package "${pkg}" not found (recommended: npm install ${pkg})`);
86
+ }
87
+ });
88
+
89
+ // Security checks
90
+ if (!hasCORS) {
91
+ warnings.push('CORS middleware not detected (recommended for API security)');
92
+ }
93
+
94
+ if (!hasSecurityHeaders) {
95
+ issues.push('Security headers not configured (use Helmet.js or configure manually)');
96
+ }
97
+
98
+ if (!hasRateLimit) {
99
+ warnings.push('Rate limiting not detected (recommended to prevent abuse)');
100
+ }
101
+
102
+ // Check for common security anti-patterns (exclude node_modules)
103
+ for (const filePath of allFiles.slice(0, 20)) {
104
+ // Skip node_modules
105
+ if (filePath.includes('node_modules')) continue;
106
+
107
+ const code = await readFile(filePath);
108
+ if (!code) continue;
109
+
110
+ // Check for eval usage
111
+ if (/\beval\s*\(/i.test(code)) {
112
+ issues.push(`eval() usage detected in ${path.relative(projectPath, filePath)} - security risk`);
113
+ }
114
+
115
+ // Check for dangerous regex
116
+ if (/new RegExp\([^)]*\+/i.test(code)) {
117
+ warnings.push(`Dynamic regex construction in ${path.relative(projectPath, filePath)} - potential ReDoS risk`);
118
+ }
119
+ }
120
+
121
+ // Print results
122
+ console.log('\n🔹 SECURITY VALIDATION\n');
123
+
124
+ if (hasCORS) success('CORS configured');
125
+ if (hasSecurityHeaders) success('Security headers configured');
126
+ if (hasRateLimit) success('Rate limiting detected');
127
+
128
+ if (issues.length === 0 && warnings.length === 0) {
129
+ success('Security configuration looks good');
130
+ verdict(true, 'SECURITY: READY');
131
+ return { passed: true, issues: [], warnings: [] };
132
+ }
133
+
134
+ printIssues(issues, 'error');
135
+ printIssues(warnings, 'warning');
136
+
137
+ verdict(false, 'SECURITY: NOT READY');
138
+
139
+ return {
140
+ passed: issues.length === 0,
141
+ issues,
142
+ warnings
143
+ };
144
+ }
145
+
146
+ module.exports = {
147
+ validate
148
+ };
149
+
@@ -0,0 +1,95 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { glob } = require('glob');
4
+
5
+ /**
6
+ * Read file content safely
7
+ */
8
+ async function readFile(filePath) {
9
+ try {
10
+ return await fs.readFile(filePath, 'utf-8');
11
+ } catch (error) {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Check if file exists
18
+ */
19
+ async function fileExists(filePath) {
20
+ try {
21
+ return await fs.pathExists(filePath);
22
+ } catch (error) {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Find files matching pattern
29
+ */
30
+ async function findFiles(pattern, cwd = process.cwd()) {
31
+ try {
32
+ const files = await glob(pattern, { cwd, absolute: true });
33
+ return files;
34
+ } catch (error) {
35
+ return [];
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get all files in directory recursively
41
+ */
42
+ async function getAllFiles(dir, extensions = ['.js', '.ts', '.jsx', '.tsx']) {
43
+ const files = [];
44
+ try {
45
+ const entries = await fs.readdir(dir, { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ const fullPath = path.join(dir, entry.name);
48
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
49
+ files.push(...await getAllFiles(fullPath, extensions));
50
+ } else if (entry.isFile()) {
51
+ const ext = path.extname(entry.name);
52
+ if (extensions.length === 0 || extensions.includes(ext)) {
53
+ files.push(fullPath);
54
+ }
55
+ }
56
+ }
57
+ } catch (error) {
58
+ // Directory doesn't exist or permission denied
59
+ }
60
+ return files;
61
+ }
62
+
63
+ /**
64
+ * Parse .env file
65
+ */
66
+ async function parseEnvFile(filePath) {
67
+ const content = await readFile(filePath);
68
+ if (!content) return {};
69
+
70
+ const env = {};
71
+ const lines = content.split('\n');
72
+
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed || trimmed.startsWith('#')) continue;
76
+
77
+ const match = trimmed.match(/^([^=]+)=(.*)$/);
78
+ if (match) {
79
+ const key = match[1].trim();
80
+ const value = match[2].trim().replace(/^["']|["']$/g, '');
81
+ env[key] = value;
82
+ }
83
+ }
84
+
85
+ return env;
86
+ }
87
+
88
+ module.exports = {
89
+ readFile,
90
+ fileExists,
91
+ findFiles,
92
+ getAllFiles,
93
+ parseEnvFile
94
+ };
95
+