safelaunch 1.0.19 → 1.0.20
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/package.json +1 -1
- package/safelaunch/src/scan.js +222 -159
package/package.json
CHANGED
package/safelaunch/src/scan.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
3
4
|
const { track, shutdown } = require('./telemetry');
|
|
4
5
|
|
|
5
|
-
// ─── Impact messages ──────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
6
|
function impactBlock(lines) {
|
|
8
7
|
return ' Impact:\n' + lines.map(l => ' ' + l).join('\n');
|
|
9
8
|
}
|
|
@@ -11,51 +10,77 @@ function impactBlock(lines) {
|
|
|
11
10
|
const IMPACT = {
|
|
12
11
|
missing: (key) => impactBlock([
|
|
13
12
|
'Your app cannot use ' + key,
|
|
14
|
-
'
|
|
13
|
+
'\u2192 Will crash or behave incorrectly at runtime'
|
|
15
14
|
]),
|
|
16
15
|
empty: (key) => impactBlock([
|
|
17
16
|
key + ' is defined but has no value',
|
|
18
|
-
'
|
|
17
|
+
'\u2192 Your app will treat it as blank \u2014 this will break things'
|
|
19
18
|
]),
|
|
20
19
|
duplicate: (key) => impactBlock([
|
|
21
20
|
key + ' is defined more than once',
|
|
22
|
-
'
|
|
21
|
+
'\u2192 The last value silently wins \u2014 likely not what you want'
|
|
23
22
|
]),
|
|
24
23
|
depsNotInstalled: () => impactBlock([
|
|
25
24
|
'node_modules is missing entirely',
|
|
26
|
-
'
|
|
25
|
+
'\u2192 Your app will not start'
|
|
27
26
|
]),
|
|
28
27
|
depsDrift: (dep) => impactBlock([
|
|
29
28
|
dep + ' is in package.json but not installed',
|
|
30
|
-
'
|
|
29
|
+
'\u2192 Any code that imports it will fail'
|
|
30
|
+
]),
|
|
31
|
+
lockfileMissing: () => impactBlock([
|
|
32
|
+
'package-lock.json is missing',
|
|
33
|
+
'\u2192 npm install will resolve different versions each time \u2014 inconsistent deploys'
|
|
31
34
|
]),
|
|
32
35
|
prefixVite: (key) => impactBlock([
|
|
33
36
|
key + ' is missing the VITE_ prefix',
|
|
34
|
-
'
|
|
37
|
+
'\u2192 It will not be exposed to the browser \u2014 your frontend will not see it'
|
|
35
38
|
]),
|
|
36
39
|
prefixCra: (key) => impactBlock([
|
|
37
40
|
key + ' is missing the REACT_APP_ prefix',
|
|
38
|
-
'
|
|
41
|
+
'\u2192 It will not be exposed to the browser \u2014 your frontend will not see it'
|
|
39
42
|
]),
|
|
40
43
|
noEnvFile: () => impactBlock([
|
|
41
44
|
'No .env file found',
|
|
42
|
-
'
|
|
45
|
+
'\u2192 Every environment variable your app needs will be missing'
|
|
46
|
+
]),
|
|
47
|
+
envInGit: (file) => impactBlock([
|
|
48
|
+
file + ' is committed to your git history',
|
|
49
|
+
'\u2192 Your secrets are exposed to anyone with repo access'
|
|
50
|
+
]),
|
|
51
|
+
envNotIgnored: () => impactBlock([
|
|
52
|
+
'.env is not in your .gitignore',
|
|
53
|
+
'\u2192 You are one git add away from leaking your secrets'
|
|
54
|
+
]),
|
|
55
|
+
envExampleOutOfSync: (keys) => impactBlock([
|
|
56
|
+
keys.join(', ') + ' are in .env but missing from .env.example',
|
|
57
|
+
'\u2192 Teammates cloning this repo will not know these vars exist'
|
|
58
|
+
]),
|
|
59
|
+
hardcodedSecret: (file) => impactBlock([
|
|
60
|
+
'Possible secret found hardcoded in ' + file,
|
|
61
|
+
'\u2192 Secrets in source code get committed and exposed in version history'
|
|
62
|
+
]),
|
|
63
|
+
buildScriptMissing: () => impactBlock([
|
|
64
|
+
'No build script found in package.json',
|
|
65
|
+
'\u2192 Your deploy pipeline will not know how to build this project'
|
|
66
|
+
]),
|
|
67
|
+
uncommittedChanges: () => impactBlock([
|
|
68
|
+
'You have uncommitted changes',
|
|
69
|
+
'\u2192 These changes will not be included in your deploy'
|
|
70
|
+
]),
|
|
71
|
+
unpushedCommits: () => impactBlock([
|
|
72
|
+
'You have commits that have not been pushed',
|
|
73
|
+
'\u2192 Your deploy may not include your latest changes'
|
|
43
74
|
]),
|
|
44
75
|
};
|
|
45
76
|
|
|
46
|
-
const DIVIDER = '
|
|
47
|
-
|
|
48
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
77
|
+
const DIVIDER = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';
|
|
49
78
|
|
|
50
79
|
function detectProjectType(cwd) {
|
|
51
80
|
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
52
|
-
fs.existsSync(path.join(cwd, 'vite.config.ts')))
|
|
53
|
-
return 'vite';
|
|
54
|
-
}
|
|
81
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts'))) return 'vite';
|
|
55
82
|
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
56
|
-
fs.existsSync(path.join(cwd, 'next.config.ts')))
|
|
57
|
-
return 'next';
|
|
58
|
-
}
|
|
83
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) return 'next';
|
|
59
84
|
const packagePath = path.join(cwd, 'package.json');
|
|
60
85
|
if (fs.existsSync(packagePath)) {
|
|
61
86
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
@@ -76,9 +101,7 @@ function scanFiles(dir, extensions, pattern, found = new Set()) {
|
|
|
76
101
|
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
77
102
|
const content = fs.readFileSync(full, 'utf8');
|
|
78
103
|
const matches = content.matchAll(pattern);
|
|
79
|
-
for (const match of matches)
|
|
80
|
-
found.add(match[1]);
|
|
81
|
-
}
|
|
104
|
+
for (const match of matches) found.add(match[1]);
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
return found;
|
|
@@ -118,23 +141,106 @@ function checkDependencies(cwd) {
|
|
|
118
141
|
return { notInstalled: false, missing };
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
function
|
|
122
|
-
return (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
impact + '\n'
|
|
126
|
-
);
|
|
144
|
+
function checkLockfile(cwd) {
|
|
145
|
+
return !fs.existsSync(path.join(cwd, 'package-lock.json')) &&
|
|
146
|
+
!fs.existsSync(path.join(cwd, 'yarn.lock')) &&
|
|
147
|
+
!fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'));
|
|
127
148
|
}
|
|
128
149
|
|
|
129
|
-
|
|
150
|
+
function checkEnvInGit(cwd) {
|
|
151
|
+
try {
|
|
152
|
+
const tracked = execSync('git ls-files', { cwd, stdio: ['pipe', 'pipe', 'ignore'] }).toString();
|
|
153
|
+
const files = tracked.split('\n');
|
|
154
|
+
return files.filter(f => f === '.env' || /^\.env\.(production|staging|live)$/.test(f));
|
|
155
|
+
} catch (_) { return []; }
|
|
156
|
+
}
|
|
130
157
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
158
|
+
function checkEnvInGitignore(cwd) {
|
|
159
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
160
|
+
if (!fs.existsSync(gitignorePath)) return false;
|
|
161
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
162
|
+
return content.split('\n').some(line => line.trim() === '.env');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function checkEnvExampleSync(cwd, envVars) {
|
|
166
|
+
const examplePath = path.join(cwd, '.env.example');
|
|
167
|
+
if (!fs.existsSync(examplePath)) return [];
|
|
168
|
+
const exampleContent = fs.readFileSync(examplePath, 'utf8');
|
|
169
|
+
const exampleKeys = new Set();
|
|
170
|
+
for (const line of exampleContent.split('\n')) {
|
|
171
|
+
const match = line.match(/^([^=]+)=/);
|
|
172
|
+
if (match) exampleKeys.add(match[1].trim());
|
|
173
|
+
}
|
|
174
|
+
return Object.keys(envVars).filter(k => !exampleKeys.has(k));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function checkHardcodedSecrets(cwd) {
|
|
178
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
179
|
+
const secretPatterns = [
|
|
180
|
+
/sk_live_[a-zA-Z0-9]{20,}/,
|
|
181
|
+
/pk_live_[a-zA-Z0-9]{20,}/,
|
|
182
|
+
/AKIA[0-9A-Z]{16}/,
|
|
183
|
+
/-----BEGIN (RSA |EC )?PRIVATE KEY-----/,
|
|
184
|
+
/ghp_[a-zA-Z0-9]{36}/,
|
|
185
|
+
/xox[baprs]-[0-9a-zA-Z-]{10,}/,
|
|
186
|
+
];
|
|
187
|
+
const hits = [];
|
|
188
|
+
function walk(dir) {
|
|
189
|
+
try {
|
|
190
|
+
const items = fs.readdirSync(dir);
|
|
191
|
+
for (const item of items) {
|
|
192
|
+
if (item === 'node_modules' || item === '.git') continue;
|
|
193
|
+
const full = path.join(dir, item);
|
|
194
|
+
const stat = fs.statSync(full);
|
|
195
|
+
if (stat.isDirectory()) {
|
|
196
|
+
walk(full);
|
|
197
|
+
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
198
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
199
|
+
for (const pattern of secretPatterns) {
|
|
200
|
+
if (pattern.test(content)) {
|
|
201
|
+
hits.push(path.relative(cwd, full));
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch (_) {}
|
|
208
|
+
}
|
|
209
|
+
walk(cwd);
|
|
210
|
+
return hits;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function checkBuildScript(cwd) {
|
|
214
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
215
|
+
if (!fs.existsSync(packagePath)) return false;
|
|
216
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
217
|
+
return !!(pkg.scripts && pkg.scripts.build);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function checkGitStatus(cwd) {
|
|
221
|
+
try {
|
|
222
|
+
const status = execSync('git status --porcelain', { cwd, stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
|
|
223
|
+
const uncommitted = status.length > 0;
|
|
224
|
+
let unpushed = false;
|
|
225
|
+
try {
|
|
226
|
+
const ahead = execSync('git log @{u}.. --oneline', { cwd, stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
|
|
227
|
+
unpushed = ahead.length > 0;
|
|
228
|
+
} catch (_) {}
|
|
229
|
+
return { uncommitted, unpushed };
|
|
230
|
+
} catch (_) { return { uncommitted: false, unpushed: false }; }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function renderIssue(number, title, impact) {
|
|
234
|
+
return number + '. ' + title + '\n\n' + impact + '\n';
|
|
235
|
+
}
|
|
134
236
|
|
|
237
|
+
async function scan() {
|
|
135
238
|
const cwd = process.cwd();
|
|
136
239
|
const projectType = detectProjectType(cwd);
|
|
137
240
|
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
241
|
+
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log('Scanning your project...');
|
|
138
244
|
console.log('Detected: ' + typeLabels[projectType]);
|
|
139
245
|
console.log('');
|
|
140
246
|
|
|
@@ -149,158 +255,115 @@ async function scan() {
|
|
|
149
255
|
}
|
|
150
256
|
|
|
151
257
|
const found = scanFiles(cwd, extensions, pattern);
|
|
152
|
-
|
|
153
|
-
if (found.size === 0) {
|
|
154
|
-
console.log('🛡️ Safelaunch Scan Report');
|
|
155
|
-
console.log('');
|
|
156
|
-
console.log('No environment variables found in your codebase.');
|
|
157
|
-
console.log('');
|
|
158
|
-
console.log(DIVIDER);
|
|
159
|
-
console.log('');
|
|
160
|
-
console.log('✅ Nothing to check');
|
|
161
|
-
console.log('');
|
|
162
|
-
console.log(' Your project does not appear to use process.env');
|
|
163
|
-
console.log('');
|
|
164
|
-
await track('safelaunch_scan_run', { project_type: projectType, vars_found: 0 });
|
|
165
|
-
await shutdown();
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
258
|
const result = readEnvDetailed(cwd);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log(DIVIDER);
|
|
189
|
-
console.log('');
|
|
190
|
-
console.log('🚨 CRITICAL (will break your app)');
|
|
191
|
-
console.log('');
|
|
192
|
-
for (const block of criticals) {
|
|
193
|
-
console.log(block);
|
|
194
|
-
console.log('---');
|
|
195
|
-
console.log('');
|
|
196
|
-
}
|
|
197
|
-
console.log(DIVIDER);
|
|
198
|
-
console.log('');
|
|
199
|
-
console.log('💡 Next step');
|
|
200
|
-
console.log('');
|
|
201
|
-
console.log(' Create a .env file with your environment variables,');
|
|
202
|
-
console.log(' then run:');
|
|
203
|
-
console.log('');
|
|
204
|
-
console.log(' safelaunch init');
|
|
205
|
-
console.log('');
|
|
206
|
-
console.log(' Lock this configuration and prevent future breakage');
|
|
207
|
-
console.log('');
|
|
208
|
-
await track('safelaunch_scan_run', { project_type: projectType, vars_found: found.size, missing: found.size, no_env: true });
|
|
209
|
-
await shutdown();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const { envVars, duplicates } = result;
|
|
214
|
-
const missing = [];
|
|
215
|
-
const empty = [];
|
|
216
|
-
const present = [];
|
|
217
|
-
|
|
218
|
-
for (const key of [...found].sort()) {
|
|
219
|
-
if (!(key in envVars)) {
|
|
220
|
-
missing.push(key);
|
|
221
|
-
} else if (envVars[key] === '') {
|
|
222
|
-
empty.push(key);
|
|
223
|
-
} else {
|
|
224
|
-
present.push(key);
|
|
259
|
+
const drift = checkDependencies(cwd);
|
|
260
|
+
const lockfileMissing = checkLockfile(cwd);
|
|
261
|
+
const envInGit = checkEnvInGit(cwd);
|
|
262
|
+
const envIgnored = checkEnvInGitignore(cwd);
|
|
263
|
+
const hardcodedSecrets = checkHardcodedSecrets(cwd);
|
|
264
|
+
const hasBuildScript = checkBuildScript(cwd);
|
|
265
|
+
const gitStatus = checkGitStatus(cwd);
|
|
266
|
+
|
|
267
|
+
let missing = [], empty = [], present = [], duplicates = [], envExampleOutOfSync = [];
|
|
268
|
+
|
|
269
|
+
if (result) {
|
|
270
|
+
const { envVars, duplicates: dups } = result;
|
|
271
|
+
duplicates = dups;
|
|
272
|
+
envExampleOutOfSync = checkEnvExampleSync(cwd, envVars);
|
|
273
|
+
for (const key of [...found].sort()) {
|
|
274
|
+
if (!(key in envVars)) missing.push(key);
|
|
275
|
+
else if (envVars[key] === '') empty.push(key);
|
|
276
|
+
else present.push(key);
|
|
225
277
|
}
|
|
278
|
+
} else {
|
|
279
|
+
missing = [...found].sort();
|
|
226
280
|
}
|
|
227
281
|
|
|
228
|
-
const drift = checkDependencies(cwd);
|
|
229
|
-
|
|
230
282
|
const prefixWarnings = [];
|
|
231
283
|
for (const key of found) {
|
|
232
284
|
if (key === 'NODE_ENV') continue;
|
|
233
|
-
if (projectType === 'vite' && !key.startsWith('VITE_')) {
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
if (projectType === 'cra' && !key.startsWith('REACT_APP_')) {
|
|
237
|
-
prefixWarnings.push({ key, type: 'cra' });
|
|
238
|
-
}
|
|
285
|
+
if (projectType === 'vite' && !key.startsWith('VITE_')) prefixWarnings.push({ key, type: 'vite' });
|
|
286
|
+
if (projectType === 'cra' && !key.startsWith('REACT_APP_')) prefixWarnings.push({ key, type: 'cra' });
|
|
239
287
|
}
|
|
240
288
|
|
|
241
289
|
const criticalIssues = [];
|
|
242
290
|
const warningIssues = [];
|
|
243
291
|
const passingChecks = [];
|
|
244
292
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
criticalIssues.push({ title: 'Dependencies are not installed', impact: IMPACT.depsNotInstalled() });
|
|
253
|
-
}
|
|
293
|
+
if (!result) criticalIssues.push({ title: '.env file is missing', impact: IMPACT.noEnvFile() });
|
|
294
|
+
for (const key of missing) criticalIssues.push({ title: key + ' is missing', impact: IMPACT.missing(key) });
|
|
295
|
+
for (const key of empty) criticalIssues.push({ title: key + ' is empty', impact: IMPACT.empty(key) });
|
|
296
|
+
if (drift && drift.notInstalled) criticalIssues.push({ title: 'Dependencies are not installed', impact: IMPACT.depsNotInstalled() });
|
|
297
|
+
for (const file of envInGit) criticalIssues.push({ title: file + ' is tracked by git', impact: IMPACT.envInGit(file) });
|
|
298
|
+
for (const file of hardcodedSecrets) criticalIssues.push({ title: 'Hardcoded secret in ' + file, impact: IMPACT.hardcodedSecret(file) });
|
|
299
|
+
|
|
254
300
|
if (drift && !drift.notInstalled && drift.missing.length > 0) {
|
|
255
|
-
for (const dep of drift.missing) {
|
|
256
|
-
warningIssues.push({ title: dep + ' is not installed', impact: IMPACT.depsDrift(dep) });
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
for (const key of duplicates) {
|
|
260
|
-
warningIssues.push({ title: key + ' is defined more than once', impact: IMPACT.duplicate(key) });
|
|
301
|
+
for (const dep of drift.missing) warningIssues.push({ title: dep + ' is not installed', impact: IMPACT.depsDrift(dep) });
|
|
261
302
|
}
|
|
303
|
+
if (lockfileMissing) warningIssues.push({ title: 'No lockfile found', impact: IMPACT.lockfileMissing() });
|
|
304
|
+
for (const key of duplicates) warningIssues.push({ title: key + ' is defined more than once', impact: IMPACT.duplicate(key) });
|
|
262
305
|
for (const { key, type } of prefixWarnings) {
|
|
263
|
-
if (type === 'vite') {
|
|
264
|
-
|
|
265
|
-
} else {
|
|
266
|
-
warningIssues.push({ title: key + ' is missing REACT_APP_ prefix', impact: IMPACT.prefixCra(key) });
|
|
267
|
-
}
|
|
306
|
+
if (type === 'vite') warningIssues.push({ title: key + ' is missing VITE_ prefix', impact: IMPACT.prefixVite(key) });
|
|
307
|
+
else warningIssues.push({ title: key + ' is missing REACT_APP_ prefix', impact: IMPACT.prefixCra(key) });
|
|
268
308
|
}
|
|
309
|
+
if (!envIgnored && fs.existsSync(path.join(cwd, '.env'))) warningIssues.push({ title: '.env is not in .gitignore', impact: IMPACT.envNotIgnored() });
|
|
310
|
+
if (envExampleOutOfSync.length > 0) warningIssues.push({ title: '.env.example is out of sync', impact: IMPACT.envExampleOutOfSync(envExampleOutOfSync) });
|
|
311
|
+
if (!hasBuildScript && fs.existsSync(path.join(cwd, 'package.json'))) warningIssues.push({ title: 'No build script in package.json', impact: IMPACT.buildScriptMissing() });
|
|
312
|
+
if (gitStatus.uncommitted) warningIssues.push({ title: 'You have uncommitted changes', impact: IMPACT.uncommittedChanges() });
|
|
313
|
+
if (gitStatus.unpushed) warningIssues.push({ title: 'You have unpushed commits', impact: IMPACT.unpushedCommits() });
|
|
269
314
|
|
|
270
315
|
if (result) passingChecks.push('Environment file detected');
|
|
316
|
+
if (envIgnored) passingChecks.push('.env is in .gitignore');
|
|
317
|
+
if (envInGit.length === 0) passingChecks.push('No env files tracked by git');
|
|
318
|
+
if (hardcodedSecrets.length === 0) passingChecks.push('No hardcoded secrets found');
|
|
319
|
+
if (!lockfileMissing) passingChecks.push('Lockfile present');
|
|
271
320
|
if (!drift || (!drift.notInstalled && drift.missing.length === 0)) passingChecks.push('Dependencies installed');
|
|
272
321
|
if (duplicates.length === 0) passingChecks.push('No duplicate variables');
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
322
|
+
if (hasBuildScript) passingChecks.push('Build script found');
|
|
323
|
+
if (!gitStatus.uncommitted) passingChecks.push('No uncommitted changes');
|
|
324
|
+
if (!gitStatus.unpushed) passingChecks.push('No unpushed commits');
|
|
325
|
+
for (const key of present) passingChecks.push(key + ' configured');
|
|
276
326
|
|
|
277
327
|
const totalIssues = criticalIssues.length + warningIssues.length;
|
|
278
328
|
const hasFailed = totalIssues > 0;
|
|
279
329
|
|
|
330
|
+
if (found.size === 0 && totalIssues === 0) {
|
|
331
|
+
console.log('\uD83D\uDEE1\uFE0F Safelaunch Scan Report');
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log('No environment variables found in your codebase.');
|
|
334
|
+
console.log('');
|
|
335
|
+
console.log(DIVIDER);
|
|
336
|
+
console.log('');
|
|
337
|
+
console.log('\u2705 Nothing to check');
|
|
338
|
+
console.log('');
|
|
339
|
+
console.log(' Your project does not appear to use process.env');
|
|
340
|
+
console.log('');
|
|
341
|
+
await track('safelaunch_scan_run', { project_type: projectType, vars_found: 0 });
|
|
342
|
+
await shutdown();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
280
346
|
if (hasFailed) {
|
|
281
347
|
const parts = [];
|
|
282
348
|
if (criticalIssues.length > 0) parts.push(criticalIssues.length + ' critical');
|
|
283
349
|
if (warningIssues.length > 0) parts.push(warningIssues.length + ' warning' + (warningIssues.length !== 1 ? 's' : ''));
|
|
284
350
|
|
|
285
|
-
console.log('
|
|
351
|
+
console.log('\uD83D\uDEA8 Safelaunch Scan Report');
|
|
286
352
|
console.log('');
|
|
287
353
|
console.log('This project is NOT safe to deploy');
|
|
288
354
|
console.log('');
|
|
289
355
|
console.log(totalIssues + ' issue' + (totalIssues !== 1 ? 's' : '') + ' found');
|
|
290
|
-
console.log(parts.join('
|
|
356
|
+
console.log(parts.join(' \u00B7 '));
|
|
291
357
|
console.log('');
|
|
292
358
|
console.log(DIVIDER);
|
|
293
359
|
console.log('');
|
|
294
360
|
|
|
295
361
|
if (criticalIssues.length > 0) {
|
|
296
|
-
console.log('
|
|
362
|
+
console.log('\uD83D\uDEA8 CRITICAL (will break your app)');
|
|
297
363
|
console.log('');
|
|
298
364
|
criticalIssues.forEach((issue, i) => {
|
|
299
365
|
console.log(renderIssue(i + 1, issue.title, issue.impact));
|
|
300
|
-
if (i < criticalIssues.length - 1) {
|
|
301
|
-
console.log('---');
|
|
302
|
-
console.log('');
|
|
303
|
-
}
|
|
366
|
+
if (i < criticalIssues.length - 1) { console.log('---'); console.log(''); }
|
|
304
367
|
});
|
|
305
368
|
console.log('');
|
|
306
369
|
console.log(DIVIDER);
|
|
@@ -308,15 +371,12 @@ async function scan() {
|
|
|
308
371
|
}
|
|
309
372
|
|
|
310
373
|
if (warningIssues.length > 0) {
|
|
311
|
-
console.log('
|
|
374
|
+
console.log('\u26A0\uFE0F WARNINGS (may cause issues)');
|
|
312
375
|
console.log('');
|
|
313
376
|
const offset = criticalIssues.length;
|
|
314
377
|
warningIssues.forEach((issue, i) => {
|
|
315
378
|
console.log(renderIssue(offset + i + 1, issue.title, issue.impact));
|
|
316
|
-
if (i < warningIssues.length - 1) {
|
|
317
|
-
console.log('---');
|
|
318
|
-
console.log('');
|
|
319
|
-
}
|
|
379
|
+
if (i < warningIssues.length - 1) { console.log('---'); console.log(''); }
|
|
320
380
|
});
|
|
321
381
|
console.log('');
|
|
322
382
|
console.log(DIVIDER);
|
|
@@ -324,17 +384,15 @@ async function scan() {
|
|
|
324
384
|
}
|
|
325
385
|
|
|
326
386
|
if (passingChecks.length > 0) {
|
|
327
|
-
console.log(
|
|
387
|
+
console.log('\u2705 What\'s working (' + passingChecks.length + ' checks passed)');
|
|
328
388
|
console.log('');
|
|
329
|
-
for (const check of passingChecks)
|
|
330
|
-
console.log('- ' + check);
|
|
331
|
-
}
|
|
389
|
+
for (const check of passingChecks) console.log('- ' + check);
|
|
332
390
|
console.log('');
|
|
333
391
|
console.log(DIVIDER);
|
|
334
392
|
console.log('');
|
|
335
393
|
}
|
|
336
394
|
|
|
337
|
-
console.log('
|
|
395
|
+
console.log('\uD83D\uDCA1 Next step');
|
|
338
396
|
console.log('');
|
|
339
397
|
console.log(' Run:');
|
|
340
398
|
console.log(' safelaunch init');
|
|
@@ -343,7 +401,7 @@ async function scan() {
|
|
|
343
401
|
console.log('');
|
|
344
402
|
|
|
345
403
|
} else {
|
|
346
|
-
console.log('
|
|
404
|
+
console.log('\uD83D\uDEE1\uFE0F Safelaunch Scan Report');
|
|
347
405
|
console.log('');
|
|
348
406
|
console.log('Your project is safe to deploy');
|
|
349
407
|
console.log('');
|
|
@@ -351,15 +409,13 @@ async function scan() {
|
|
|
351
409
|
console.log('');
|
|
352
410
|
console.log(DIVIDER);
|
|
353
411
|
console.log('');
|
|
354
|
-
console.log('
|
|
412
|
+
console.log('\u2705 All checks passed');
|
|
355
413
|
console.log('');
|
|
356
|
-
for (const check of passingChecks)
|
|
357
|
-
console.log('- ' + check);
|
|
358
|
-
}
|
|
414
|
+
for (const check of passingChecks) console.log('- ' + check);
|
|
359
415
|
console.log('');
|
|
360
416
|
console.log(DIVIDER);
|
|
361
417
|
console.log('');
|
|
362
|
-
console.log(
|
|
418
|
+
console.log('\uD83C\uDF89 You\'re good to go');
|
|
363
419
|
console.log('');
|
|
364
420
|
console.log(' Deploy with confidence');
|
|
365
421
|
console.log('');
|
|
@@ -373,7 +429,14 @@ async function scan() {
|
|
|
373
429
|
empty: empty.length,
|
|
374
430
|
duplicates: duplicates.length,
|
|
375
431
|
prefix_warnings: prefixWarnings.length,
|
|
376
|
-
dependency_drift: !!(drift && drift.missing.length > 0)
|
|
432
|
+
dependency_drift: !!(drift && drift.missing.length > 0),
|
|
433
|
+
env_in_git: envInGit.length,
|
|
434
|
+
hardcoded_secrets: hardcodedSecrets.length,
|
|
435
|
+
lockfile_missing: lockfileMissing,
|
|
436
|
+
env_not_ignored: !envIgnored,
|
|
437
|
+
build_script_missing: !hasBuildScript,
|
|
438
|
+
uncommitted: gitStatus.uncommitted,
|
|
439
|
+
unpushed: gitStatus.unpushed
|
|
377
440
|
});
|
|
378
441
|
|
|
379
442
|
await shutdown();
|