safelaunch 1.0.7 → 1.0.8
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 +23 -30
- package/package.json +1 -1
- package/src/validate.js +91 -28
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
safelaunch is a comprehensive environment validator for JavaScript projects. Run it before every push to catch everything that will break production.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Automatically detects your project type and runs the right checks.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -16,7 +16,9 @@ npm install -g safelaunch
|
|
|
16
16
|
|
|
17
17
|
safelaunch init
|
|
18
18
|
|
|
19
|
-
Scans your entire codebase
|
|
19
|
+
Scans your entire codebase and generates env.manifest.json automatically.
|
|
20
|
+
|
|
21
|
+
Works with process.env and import.meta.env.
|
|
20
22
|
|
|
21
23
|
**Step 2: Validate before every push**
|
|
22
24
|
|
|
@@ -24,7 +26,7 @@ safelaunch validate
|
|
|
24
26
|
|
|
25
27
|
## What safelaunch checks
|
|
26
28
|
|
|
27
|
-
safelaunch validate runs
|
|
29
|
+
safelaunch validate runs 11 checks:
|
|
28
30
|
|
|
29
31
|
1. Missing required environment variables
|
|
30
32
|
2. Empty required environment variables
|
|
@@ -33,59 +35,50 @@ safelaunch validate runs 7 checks:
|
|
|
33
35
|
5. Dependencies not installed
|
|
34
36
|
6. Dependency drift (in package.json but not installed)
|
|
35
37
|
7. Variables in .env.example but missing from .env
|
|
38
|
+
8. Missing VITE_ prefix for Vite projects
|
|
39
|
+
9. Missing NEXT_PUBLIC_ prefix for Next.js projects
|
|
40
|
+
10. Missing REACT_APP_ prefix for Create React App
|
|
41
|
+
11. .env file priority order for Next.js projects
|
|
36
42
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
Running safelaunch...
|
|
43
|
+
## Supported project types
|
|
40
44
|
|
|
41
|
-
project type
|
|
45
|
+
safelaunch automatically detects your project type.
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
- Node.js scans process.env
|
|
48
|
+
- Next.js scans process.env, checks NEXT_PUBLIC_ prefixes
|
|
49
|
+
- Vite scans import.meta.env, checks VITE_ prefixes
|
|
50
|
+
- React CRA scans REACT_APP_ variables
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
Node actual: 20
|
|
52
|
+
## Example output
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
Running safelaunch...
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
project type: Vite
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
⚠️ FRAMEWORK PREFIX WARNINGS (1 found)
|
|
53
59
|
|
|
54
|
-
|
|
60
|
+
API_URL missing VITE_ prefix - will be undefined in browser
|
|
55
61
|
|
|
56
62
|
❌ MISSING VARIABLES (1 found)
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
VITE_STRIPE_KEY required but missing from .env
|
|
59
65
|
|
|
60
66
|
✅ PASSING (1)
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
VITE_API_URL present
|
|
63
69
|
|
|
64
70
|
Your environment is not ready for production.
|
|
65
71
|
|
|
66
|
-
## Supported project types
|
|
67
|
-
|
|
68
|
-
safelaunch automatically detects your project type.
|
|
69
|
-
|
|
70
|
-
- Node.js scans process.env
|
|
71
|
-
- Next.js scans process.env
|
|
72
|
-
- Vite scans import.meta.env
|
|
73
|
-
- React CRA scans process.env REACT_APP_ variables
|
|
74
|
-
|
|
75
72
|
## CI Integration
|
|
76
73
|
|
|
77
|
-
Add this to your GitHub Actions workflow:
|
|
78
|
-
|
|
79
74
|
- name: Install safelaunch
|
|
80
75
|
run: npm install -g safelaunch
|
|
81
76
|
|
|
82
77
|
- name: Validate environment
|
|
83
78
|
run: safelaunch validate
|
|
84
79
|
|
|
85
|
-
Blocks deployments automatically if any check fails.
|
|
86
|
-
|
|
87
80
|
## Built by Orches
|
|
88
81
|
|
|
89
|
-
|
|
82
|
+
JavaScript Reliability Infrastructure.
|
|
90
83
|
GitHub: https://github.com/karthicedric7-cloud/safelaunch
|
|
91
84
|
VS Code: https://marketplace.visualstudio.com/items?itemName=Orches.deploycheck-vscode
|
package/package.json
CHANGED
package/src/validate.js
CHANGED
|
@@ -28,30 +28,42 @@ function readManifest() {
|
|
|
28
28
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function readEnv() {
|
|
32
|
-
const
|
|
33
|
-
if (!fs.existsSync(envPath)) {
|
|
34
|
-
console.log('\nNo .env file found.\n');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
31
|
+
function readEnv(cwd) {
|
|
32
|
+
const envFiles = ['.env.local', '.env.development.local', '.env.production.local', '.env.development', '.env.production', '.env'];
|
|
37
33
|
const envVars = {};
|
|
38
34
|
const duplicates = [];
|
|
39
35
|
const seen = new Set();
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
const loadedFiles = [];
|
|
37
|
+
|
|
38
|
+
for (const file of envFiles) {
|
|
39
|
+
const envPath = path.join(cwd, file);
|
|
40
|
+
if (!fs.existsSync(envPath)) continue;
|
|
41
|
+
loadedFiles.push(file);
|
|
42
|
+
const lines = fs.readFileSync(envPath, 'utf8').split('\n');
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
45
|
+
if (match) {
|
|
46
|
+
const key = match[1].trim();
|
|
47
|
+
if (seen.has(key)) {
|
|
48
|
+
duplicates.push(key + ' (in ' + file + ')');
|
|
49
|
+
} else {
|
|
50
|
+
seen.add(key);
|
|
51
|
+
envVars[key] = match[2].trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
|
-
|
|
56
|
+
|
|
57
|
+
if (loadedFiles.length === 0) {
|
|
58
|
+
console.log('\nNo .env file found.\n');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { envVars, duplicates, loadedFiles };
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
function readEnvExample() {
|
|
54
|
-
const envExamplePath = path.join(
|
|
65
|
+
function readEnvExample(cwd) {
|
|
66
|
+
const envExamplePath = path.join(cwd, '.env.example');
|
|
55
67
|
if (!fs.existsSync(envExamplePath)) return null;
|
|
56
68
|
const envVars = {};
|
|
57
69
|
const lines = fs.readFileSync(envExamplePath, 'utf8').split('\n');
|
|
@@ -70,8 +82,8 @@ function checkRuntime(manifest) {
|
|
|
70
82
|
return null;
|
|
71
83
|
}
|
|
72
84
|
|
|
73
|
-
function checkEnvExample(envVars) {
|
|
74
|
-
const example = readEnvExample();
|
|
85
|
+
function checkEnvExample(envVars, cwd) {
|
|
86
|
+
const example = readEnvExample(cwd);
|
|
75
87
|
if (!example) return [];
|
|
76
88
|
const missing = [];
|
|
77
89
|
for (const key of Object.keys(example)) {
|
|
@@ -80,9 +92,9 @@ function checkEnvExample(envVars) {
|
|
|
80
92
|
return missing;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
|
-
function checkDependencyDrift() {
|
|
84
|
-
const packagePath = path.join(
|
|
85
|
-
const modulesPath = path.join(
|
|
95
|
+
function checkDependencyDrift(cwd) {
|
|
96
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
97
|
+
const modulesPath = path.join(cwd, 'node_modules');
|
|
86
98
|
if (!fs.existsSync(packagePath)) return null;
|
|
87
99
|
if (!fs.existsSync(modulesPath)) return { notInstalled: true, missing: [] };
|
|
88
100
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
@@ -95,17 +107,57 @@ function checkDependencyDrift() {
|
|
|
95
107
|
return { notInstalled: false, missing };
|
|
96
108
|
}
|
|
97
109
|
|
|
110
|
+
function checkFrameworkPrefixes(envVars, manifest, projectType) {
|
|
111
|
+
const warnings = [];
|
|
112
|
+
const variables = Object.keys(manifest.variables || {});
|
|
113
|
+
|
|
114
|
+
if (projectType === 'vite') {
|
|
115
|
+
for (const key of variables) {
|
|
116
|
+
if (!key.startsWith('VITE_')) {
|
|
117
|
+
warnings.push(key + ' missing VITE_ prefix - will be undefined in browser');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (projectType === 'next') {
|
|
123
|
+
for (const key of variables) {
|
|
124
|
+
if (!key.startsWith('NEXT_PUBLIC_') && !key.startsWith('NEXT_') ) {
|
|
125
|
+
warnings.push(key + ' consider NEXT_PUBLIC_ prefix to expose to browser');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (projectType === 'cra') {
|
|
131
|
+
for (const key of variables) {
|
|
132
|
+
if (!key.startsWith('REACT_APP_')) {
|
|
133
|
+
warnings.push(key + ' missing REACT_APP_ prefix - will be undefined in browser');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return warnings;
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
function validate() {
|
|
99
142
|
console.log('\nRunning safelaunch...\n');
|
|
100
143
|
|
|
101
144
|
const cwd = process.cwd();
|
|
102
145
|
const manifest = readManifest();
|
|
103
|
-
const { envVars, duplicates } = readEnv();
|
|
104
|
-
|
|
105
146
|
const projectType = manifest.projectType || detectProjectType(cwd);
|
|
147
|
+
|
|
106
148
|
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
107
149
|
console.log('project type: ' + (typeLabels[projectType] || 'Node.js') + '\n');
|
|
108
150
|
|
|
151
|
+
const { envVars, duplicates, loadedFiles } = readEnv(cwd);
|
|
152
|
+
|
|
153
|
+
if (loadedFiles.length > 1) {
|
|
154
|
+
console.log('env files loaded (priority order):\n');
|
|
155
|
+
for (const file of loadedFiles) {
|
|
156
|
+
console.log(' ' + file);
|
|
157
|
+
}
|
|
158
|
+
console.log('');
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
const missing = [];
|
|
110
162
|
const empty = [];
|
|
111
163
|
const passing = [];
|
|
@@ -121,8 +173,9 @@ function validate() {
|
|
|
121
173
|
}
|
|
122
174
|
|
|
123
175
|
const runtimeMismatch = checkRuntime(manifest);
|
|
124
|
-
const exampleMissing = checkEnvExample(envVars);
|
|
125
|
-
const drift = checkDependencyDrift();
|
|
176
|
+
const exampleMissing = checkEnvExample(envVars, cwd);
|
|
177
|
+
const drift = checkDependencyDrift(cwd);
|
|
178
|
+
const prefixWarnings = checkFrameworkPrefixes(envVars, manifest, projectType);
|
|
126
179
|
|
|
127
180
|
if (runtimeMismatch) {
|
|
128
181
|
console.log('⚠️ RUNTIME MISMATCH\n');
|
|
@@ -134,7 +187,7 @@ function validate() {
|
|
|
134
187
|
if (duplicates.length > 0) {
|
|
135
188
|
console.log('⚠️ DUPLICATE VARIABLES (' + duplicates.length + ' found)\n');
|
|
136
189
|
for (const key of duplicates) {
|
|
137
|
-
console.log(' ' + key
|
|
190
|
+
console.log(' ' + key);
|
|
138
191
|
}
|
|
139
192
|
console.log('');
|
|
140
193
|
}
|
|
@@ -152,6 +205,14 @@ function validate() {
|
|
|
152
205
|
}
|
|
153
206
|
}
|
|
154
207
|
|
|
208
|
+
if (prefixWarnings.length > 0) {
|
|
209
|
+
console.log('⚠️ FRAMEWORK PREFIX WARNINGS (' + prefixWarnings.length + ' found)\n');
|
|
210
|
+
for (const warning of prefixWarnings) {
|
|
211
|
+
console.log(' ' + warning);
|
|
212
|
+
}
|
|
213
|
+
console.log('');
|
|
214
|
+
}
|
|
215
|
+
|
|
155
216
|
if (exampleMissing.length > 0) {
|
|
156
217
|
console.log('⚠️ MISSING FROM .env.example (' + exampleMissing.length + ' found)\n');
|
|
157
218
|
for (const key of exampleMissing) {
|
|
@@ -184,7 +245,9 @@ function validate() {
|
|
|
184
245
|
console.log('');
|
|
185
246
|
}
|
|
186
247
|
|
|
187
|
-
const hasFailed = runtimeMismatch || missing.length > 0 || empty.length > 0 ||
|
|
248
|
+
const hasFailed = runtimeMismatch || missing.length > 0 || empty.length > 0 ||
|
|
249
|
+
duplicates.length > 0 || exampleMissing.length > 0 || prefixWarnings.length > 0 ||
|
|
250
|
+
(drift && (drift.notInstalled || drift.missing.length > 0));
|
|
188
251
|
|
|
189
252
|
if (hasFailed) {
|
|
190
253
|
console.log('Your environment is not ready for production.\n');
|