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,203 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { fileExists, readFile } = require('./fileHelpers');
4
+
5
+ /**
6
+ * Generate auto-fix suggestions
7
+ */
8
+ async function generateFixes(issues, projectPath) {
9
+ const fixes = [];
10
+
11
+ issues.forEach(issue => {
12
+ const issueLower = issue.toLowerCase();
13
+
14
+ // .env.example fixes
15
+ if (issueLower.includes('.env.example')) {
16
+ fixes.push({
17
+ type: 'create_file',
18
+ file: '.env.example',
19
+ content: generateEnvExampleTemplate(),
20
+ description: 'Create .env.example file'
21
+ });
22
+ }
23
+
24
+ // README fixes
25
+ if (issueLower.includes('readme')) {
26
+ fixes.push({
27
+ type: 'create_file',
28
+ file: 'README.md',
29
+ content: generateReadmeTemplate(),
30
+ description: 'Create README.md file'
31
+ });
32
+ }
33
+
34
+ // Weak secret fixes
35
+ if (issueLower.includes('weak secret')) {
36
+ const match = issue.match(/WEAK SECRET: (\w+)/);
37
+ if (match) {
38
+ fixes.push({
39
+ type: 'suggestion',
40
+ description: `Generate a strong ${match[1]} using: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
41
+ });
42
+ }
43
+ }
44
+
45
+ // Missing env variable fixes
46
+ if (issueLower.includes('missing:')) {
47
+ const match = issue.match(/MISSING: (\w+)/);
48
+ if (match) {
49
+ fixes.push({
50
+ type: 'suggestion',
51
+ description: `Add ${match[1]} to your .env file`
52
+ });
53
+ }
54
+ }
55
+
56
+ // Security headers fixes
57
+ if (issueLower.includes('security headers')) {
58
+ fixes.push({
59
+ type: 'suggestion',
60
+ description: 'Install and configure Helmet.js: npm install helmet && app.use(require("helmet")())'
61
+ });
62
+ }
63
+
64
+ // CORS fixes
65
+ if (issueLower.includes('cors')) {
66
+ fixes.push({
67
+ type: 'suggestion',
68
+ description: 'Install and configure CORS: npm install cors && app.use(require("cors")({ origin: process.env.ALLOWED_ORIGINS?.split(",") }))'
69
+ });
70
+ }
71
+
72
+ // Rate limiting fixes
73
+ if (issueLower.includes('rate limit')) {
74
+ fixes.push({
75
+ type: 'suggestion',
76
+ description: 'Install express-rate-limit: npm install express-rate-limit && app.use(require("express-rate-limit")({ windowMs: 15 * 60 * 1000, max: 100 }))'
77
+ });
78
+ }
79
+
80
+ // Health endpoint fixes
81
+ if (issueLower.includes('health endpoint')) {
82
+ fixes.push({
83
+ type: 'suggestion',
84
+ description: 'Add health endpoint: app.get("/health", (req, res) => res.json({ status: "ok", timestamp: Date.now() }))'
85
+ });
86
+ }
87
+ });
88
+
89
+ return fixes;
90
+ }
91
+
92
+ /**
93
+ * Generate .env.example template
94
+ */
95
+ function generateEnvExampleTemplate() {
96
+ return `# Environment Variables
97
+ # Copy this file to .env and fill in your values
98
+
99
+ # Server
100
+ PORT=3000
101
+ NODE_ENV=development
102
+
103
+ # Database
104
+ DATABASE_URL=postgresql://user:password@localhost:5432/dbname
105
+
106
+ # JWT
107
+ JWT_SECRET=your-super-secret-jwt-key-min-32-chars
108
+ JWT_EXPIRY=7d
109
+
110
+ # CORS
111
+ ALLOWED_ORIGINS=http://localhost:3000
112
+
113
+ # API Keys (if needed)
114
+ # API_KEY=your-api-key
115
+ `;
116
+ }
117
+
118
+ /**
119
+ * Generate README template
120
+ */
121
+ function generateReadmeTemplate() {
122
+ return `# Project Name
123
+
124
+ Brief description of your project.
125
+
126
+ ## Installation
127
+
128
+ \`\`\`bash
129
+ npm install
130
+ \`\`\`
131
+
132
+ ## Configuration
133
+
134
+ Copy \`.env.example\` to \`.env\` and configure your environment variables.
135
+
136
+ \`\`\`bash
137
+ cp .env.example .env
138
+ \`\`\`
139
+
140
+ ## Usage
141
+
142
+ \`\`\`bash
143
+ npm start
144
+ \`\`\`
145
+
146
+ ## API Endpoints
147
+
148
+ - \`GET /health\` - Health check endpoint
149
+
150
+ ## License
151
+
152
+ MIT
153
+ `;
154
+ }
155
+
156
+ /**
157
+ * Apply fixes (with user confirmation in real usage)
158
+ */
159
+ async function applyFixes(fixes, projectPath, dryRun = true) {
160
+ const results = [];
161
+
162
+ for (const fix of fixes) {
163
+ if (fix.type === 'create_file') {
164
+ const filePath = path.join(projectPath, fix.file);
165
+ const exists = await fileExists(filePath);
166
+
167
+ if (exists && !dryRun) {
168
+ results.push({
169
+ fix,
170
+ status: 'skipped',
171
+ reason: 'File already exists'
172
+ });
173
+ } else if (dryRun) {
174
+ results.push({
175
+ fix,
176
+ status: 'would_create',
177
+ filePath
178
+ });
179
+ } else {
180
+ await fs.writeFile(filePath, fix.content);
181
+ results.push({
182
+ fix,
183
+ status: 'created',
184
+ filePath
185
+ });
186
+ }
187
+ } else if (fix.type === 'suggestion') {
188
+ results.push({
189
+ fix,
190
+ status: 'suggestion',
191
+ description: fix.description
192
+ });
193
+ }
194
+ }
195
+
196
+ return results;
197
+ }
198
+
199
+ module.exports = {
200
+ generateFixes,
201
+ applyFixes
202
+ };
203
+
@@ -0,0 +1,77 @@
1
+ const chalk = require('chalk');
2
+
3
+ /**
4
+ * Print success message
5
+ */
6
+ function success(message) {
7
+ console.log(chalk.green('✅'), message);
8
+ }
9
+
10
+ /**
11
+ * Print error message
12
+ */
13
+ function error(message) {
14
+ console.log(chalk.red('❌'), message);
15
+ }
16
+
17
+ /**
18
+ * Print warning message
19
+ */
20
+ function warning(message) {
21
+ console.log(chalk.yellow('⚠️'), message);
22
+ }
23
+
24
+ /**
25
+ * Print info message
26
+ */
27
+ function info(message) {
28
+ console.log(chalk.blue('ℹ️'), message);
29
+ }
30
+
31
+ /**
32
+ * Print section header
33
+ */
34
+ function header(message) {
35
+ console.log(chalk.bold.cyan('\n' + '='.repeat(50)));
36
+ console.log(chalk.bold.cyan(message));
37
+ console.log(chalk.bold.cyan('='.repeat(50) + '\n'));
38
+ }
39
+
40
+ /**
41
+ * Print verdict
42
+ */
43
+ function verdict(passed, message) {
44
+ if (passed) {
45
+ console.log(chalk.bold.green('\n✅ ' + message));
46
+ } else {
47
+ console.log(chalk.bold.red('\n❌ ' + message));
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Print list of issues
53
+ */
54
+ function printIssues(issues, type = 'error') {
55
+ if (issues.length === 0) return;
56
+
57
+ issues.forEach(issue => {
58
+ if (type === 'error') {
59
+ error(issue);
60
+ } else if (type === 'warning') {
61
+ warning(issue);
62
+ } else {
63
+ info(issue);
64
+ }
65
+ });
66
+ }
67
+
68
+ module.exports = {
69
+ success,
70
+ error,
71
+ warning,
72
+ info,
73
+ header,
74
+ verdict,
75
+ printIssues
76
+ };
77
+
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Check if string is a valid URL
3
+ */
4
+ function isValidUrl(str) {
5
+ try {
6
+ new URL(str);
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+
13
+ /**
14
+ * Check if string is a valid email
15
+ */
16
+ function isValidEmail(str) {
17
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
18
+ return emailRegex.test(str);
19
+ }
20
+
21
+ /**
22
+ * Check if string is a valid number
23
+ */
24
+ function isValidNumber(str) {
25
+ return !isNaN(str) && !isNaN(parseFloat(str));
26
+ }
27
+
28
+ /**
29
+ * Extract route definitions from code
30
+ */
31
+ function extractRoutes(code) {
32
+ const routes = [];
33
+
34
+ // Match common route patterns
35
+ const patterns = [
36
+ /(?:app|router)\.(get|post|put|delete|patch)\s*\(['"`]([^'"`]+)['"`]/g,
37
+ /(?:app|router)\.(get|post|put|delete|patch)\s*\(['"`]([^'"`]+)['"`]/g,
38
+ /Route\.(get|post|put|delete|patch)\s*\(['"`]([^'"`]+)['"`]/g,
39
+ ];
40
+
41
+ patterns.forEach(pattern => {
42
+ let match;
43
+ while ((match = pattern.exec(code)) !== null) {
44
+ routes.push({
45
+ method: match[1].toUpperCase(),
46
+ path: match[2],
47
+ line: code.substring(0, match.index).split('\n').length
48
+ });
49
+ }
50
+ });
51
+
52
+ return routes;
53
+ }
54
+
55
+ /**
56
+ * Check if route has auth middleware
57
+ */
58
+ function hasAuthMiddleware(code, routeIndex) {
59
+ const lines = code.split('\n');
60
+ const routeLine = lines[routeIndex - 1] || '';
61
+
62
+ // Check for common auth middleware patterns
63
+ const authPatterns = [
64
+ /authenticate|auth|jwt|verifyToken|requireAuth|isAuthenticated/i,
65
+ /passport\.authenticate/,
66
+ /middleware.*auth/i
67
+ ];
68
+
69
+ // Check the route line and a few lines before/after
70
+ const context = lines.slice(Math.max(0, routeIndex - 3), routeIndex + 3).join('\n');
71
+
72
+ return authPatterns.some(pattern => pattern.test(context));
73
+ }
74
+
75
+ /**
76
+ * Extract middleware usage
77
+ */
78
+ function extractMiddleware(code) {
79
+ const middleware = [];
80
+ const patterns = [
81
+ /\.use\s*\(['"`]([^'"`]+)['"`]/g,
82
+ /\.use\s*\(([a-zA-Z_$][a-zA-Z0-9_$]*)/g
83
+ ];
84
+
85
+ patterns.forEach(pattern => {
86
+ let match;
87
+ while ((match = pattern.exec(code)) !== null) {
88
+ middleware.push(match[1]);
89
+ }
90
+ });
91
+
92
+ return middleware;
93
+ }
94
+
95
+ /**
96
+ * Check if code has error handling
97
+ */
98
+ function hasErrorHandling(code) {
99
+ const errorPatterns = [
100
+ /catch\s*\(/,
101
+ /\.catch\s*\(/,
102
+ /errorHandler|error-handler|errorMiddleware/i,
103
+ /express\.errorHandler/,
104
+ /app\.use.*error/i
105
+ ];
106
+
107
+ return errorPatterns.some(pattern => pattern.test(code));
108
+ }
109
+
110
+ /**
111
+ * Check JWT expiry configuration
112
+ */
113
+ function extractJWTExpiry(code) {
114
+ const patterns = [
115
+ /expiresIn\s*[:=]\s*['"`]?(\d+)\s*([a-z]+)['"`]?/i,
116
+ /expiresIn\s*[:=]\s*(\d+)/i,
117
+ /JWT_EXPIRY\s*[:=]\s*['"`]?(\d+)\s*([a-z]+)['"`]?/i
118
+ ];
119
+
120
+ for (const pattern of patterns) {
121
+ const match = code.match(pattern);
122
+ if (match) {
123
+ const value = parseInt(match[1]);
124
+ const unit = (match[2] || 's').toLowerCase();
125
+
126
+ // Convert to seconds
127
+ let seconds = value;
128
+ if (unit.includes('year') || unit === 'y') seconds = value * 365 * 24 * 60 * 60;
129
+ else if (unit.includes('month') || unit === 'm') seconds = value * 30 * 24 * 60 * 60;
130
+ else if (unit.includes('day') || unit === 'd') seconds = value * 24 * 60 * 60;
131
+ else if (unit.includes('hour') || unit === 'h') seconds = value * 60 * 60;
132
+ else if (unit.includes('minute') || unit === 'min') seconds = value * 60;
133
+
134
+ return seconds;
135
+ }
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ module.exports = {
142
+ isValidUrl,
143
+ isValidEmail,
144
+ isValidNumber,
145
+ extractRoutes,
146
+ hasAuthMiddleware,
147
+ extractMiddleware,
148
+ hasErrorHandling,
149
+ extractJWTExpiry
150
+ };
151
+
@@ -0,0 +1,35 @@
1
+ name: Ready-to-Ship Validation
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master, develop ]
6
+ pull_request:
7
+ branches: [ main, master, develop ]
8
+
9
+ jobs:
10
+ validate:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v3
18
+ with:
19
+ node-version: '18'
20
+ cache: 'npm'
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Run Ready-to-Ship
26
+ run: npx ready-to-ship report --json
27
+ continue-on-error: true
28
+
29
+ - name: Upload report
30
+ uses: actions/upload-artifact@v3
31
+ if: always()
32
+ with:
33
+ name: ready-to-ship-report
34
+ path: ready-to-ship-report.json
35
+
@@ -0,0 +1,23 @@
1
+ # Templates
2
+
3
+ This directory contains ready-to-use templates for CI/CD integration.
4
+
5
+ ## GitHub Actions
6
+
7
+ Copy `github-actions.yml` to `.github/workflows/ready-to-ship.yml` in your project:
8
+
9
+ ```bash
10
+ mkdir -p .github/workflows
11
+ cp templates/github-actions.yml .github/workflows/ready-to-ship.yml
12
+ ```
13
+
14
+ This will automatically run Ready-to-Ship validation on every push and PR.
15
+
16
+ ## Customization
17
+
18
+ You can customize the workflow to:
19
+ - Run only on specific branches
20
+ - Add notifications (Slack, email)
21
+ - Upload reports as artifacts
22
+ - Block merges if validation fails
23
+
@@ -0,0 +1,35 @@
1
+ name: Ready-to-Ship Validation
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master, develop ]
6
+ pull_request:
7
+ branches: [ main, master, develop ]
8
+
9
+ jobs:
10
+ validate:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v3
18
+ with:
19
+ node-version: '18'
20
+ cache: 'npm'
21
+
22
+ - name: Install dependencies
23
+ run: npm ci
24
+
25
+ - name: Run Ready-to-Ship
26
+ run: npx ready-to-ship report --json
27
+ continue-on-error: true
28
+
29
+ - name: Upload report
30
+ uses: actions/upload-artifact@v3
31
+ if: always()
32
+ with:
33
+ name: ready-to-ship-report
34
+ path: ready-to-ship-report.json
35
+