safelaunch 1.0.8 → 1.0.10
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 +30 -23
- package/package.json +5 -2
- package/src/init.js +11 -6
- package/src/telemetry.js +22 -0
- package/src/validate.js +52 -91
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
|
-
Automatically detects your project type and runs the right checks.
|
|
7
|
+
Works with Node.js, Next.js, Vite, and Create React App projects. Automatically detects your project type and runs the right checks.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -16,9 +16,7 @@ npm install -g safelaunch
|
|
|
16
16
|
|
|
17
17
|
safelaunch init
|
|
18
18
|
|
|
19
|
-
Scans your entire codebase and generates env.manifest.json automatically.
|
|
20
|
-
|
|
21
|
-
Works with process.env and import.meta.env.
|
|
19
|
+
Scans your entire codebase, finds every environment variable your app uses, and generates env.manifest.json automatically. Works with process.env and import.meta.env.
|
|
22
20
|
|
|
23
21
|
**Step 2: Validate before every push**
|
|
24
22
|
|
|
@@ -26,7 +24,7 @@ safelaunch validate
|
|
|
26
24
|
|
|
27
25
|
## What safelaunch checks
|
|
28
26
|
|
|
29
|
-
safelaunch validate runs
|
|
27
|
+
safelaunch validate runs 7 checks:
|
|
30
28
|
|
|
31
29
|
1. Missing required environment variables
|
|
32
30
|
2. Empty required environment variables
|
|
@@ -35,50 +33,59 @@ safelaunch validate runs 11 checks:
|
|
|
35
33
|
5. Dependencies not installed
|
|
36
34
|
6. Dependency drift (in package.json but not installed)
|
|
37
35
|
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
|
|
42
36
|
|
|
43
|
-
##
|
|
37
|
+
## Example output
|
|
44
38
|
|
|
45
|
-
safelaunch
|
|
39
|
+
Running safelaunch...
|
|
46
40
|
|
|
47
|
-
|
|
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
|
|
41
|
+
project type: Node.js
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
⚠️ RUNTIME MISMATCH
|
|
53
44
|
|
|
54
|
-
|
|
45
|
+
Node required: 18
|
|
46
|
+
Node actual: 20
|
|
47
|
+
|
|
48
|
+
⚠️ DEPENDENCY DRIFT (1 found)
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
express in package.json but not installed
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
❌ EMPTY VARIABLES (1 found)
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
DATABASE_URL required but empty in .env
|
|
61
55
|
|
|
62
56
|
❌ MISSING VARIABLES (1 found)
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
REDIS_URL required but missing from .env
|
|
65
59
|
|
|
66
60
|
✅ PASSING (1)
|
|
67
61
|
|
|
68
|
-
|
|
62
|
+
API_KEY present
|
|
69
63
|
|
|
70
64
|
Your environment is not ready for production.
|
|
71
65
|
|
|
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
|
+
|
|
72
75
|
## CI Integration
|
|
73
76
|
|
|
77
|
+
Add this to your GitHub Actions workflow:
|
|
78
|
+
|
|
74
79
|
- name: Install safelaunch
|
|
75
80
|
run: npm install -g safelaunch
|
|
76
81
|
|
|
77
82
|
- name: Validate environment
|
|
78
83
|
run: safelaunch validate
|
|
79
84
|
|
|
85
|
+
Blocks deployments automatically if any check fails.
|
|
86
|
+
|
|
80
87
|
## Built by Orches
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
Deployment Reliability Infrastructure.
|
|
83
90
|
GitHub: https://github.com/karthicedric7-cloud/safelaunch
|
|
84
91
|
VS Code: https://marketplace.visualstudio.com/items?itemName=Orches.deploycheck-vscode
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safelaunch",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Backend Reliability Infrastructure - catch what breaks production before it breaks",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,5 +16,8 @@
|
|
|
16
16
|
"devops"
|
|
17
17
|
],
|
|
18
18
|
"author": "",
|
|
19
|
-
"license": "ISC"
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"posthog-node": "^5.28.2"
|
|
22
|
+
}
|
|
20
23
|
}
|
package/src/init.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { track, shutdown } = require('./telemetry');
|
|
3
4
|
|
|
4
5
|
function detectProjectType(cwd) {
|
|
5
6
|
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
6
|
-
|
|
7
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts'))) {
|
|
7
8
|
return 'vite';
|
|
8
9
|
}
|
|
9
10
|
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
10
|
-
|
|
11
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) {
|
|
11
12
|
return 'next';
|
|
12
13
|
}
|
|
13
14
|
const packagePath = path.join(cwd, 'package.json');
|
|
@@ -38,20 +39,20 @@ function scanFiles(dir, extensions, pattern, found = new Set()) {
|
|
|
38
39
|
return found;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function init() {
|
|
42
|
+
async function init() {
|
|
42
43
|
console.log('\nscanning project for environment variables...\n');
|
|
43
|
-
|
|
44
44
|
const cwd = process.cwd();
|
|
45
45
|
const manifestPath = path.join(cwd, 'env.manifest.json');
|
|
46
46
|
|
|
47
47
|
if (fs.existsSync(manifestPath)) {
|
|
48
48
|
console.log('env.manifest.json already exists. delete it first.\n');
|
|
49
|
+
await track('safelaunch_init_already_exists');
|
|
50
|
+
await shutdown();
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
const projectType = detectProjectType(cwd);
|
|
53
55
|
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
54
|
-
|
|
55
56
|
let pattern;
|
|
56
57
|
let typeLabel;
|
|
57
58
|
|
|
@@ -70,11 +71,12 @@ function init() {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
console.log('detected project type: ' + typeLabel + '\n');
|
|
73
|
-
|
|
74
74
|
const found = scanFiles(cwd, extensions, pattern);
|
|
75
75
|
|
|
76
76
|
if (found.size === 0) {
|
|
77
77
|
console.log('no environment variables found in your project.\n');
|
|
78
|
+
await track('safelaunch_init_run', { project_type: projectType, vars_found: 0 });
|
|
79
|
+
await shutdown();
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -97,6 +99,9 @@ function init() {
|
|
|
97
99
|
console.log(' ' + key);
|
|
98
100
|
}
|
|
99
101
|
console.log('\ncreated env.manifest.json\n');
|
|
102
|
+
|
|
103
|
+
await track('safelaunch_init_run', { project_type: projectType, vars_found: found.size });
|
|
104
|
+
await shutdown();
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
init();
|
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { PostHog } = require('posthog-node');
|
|
2
|
+
|
|
3
|
+
const client = new PostHog('phc_WFm3O5H7wkZK2Ne3kyy5QgUXJR8y86SQbszSwk3BUn0', {
|
|
4
|
+
host: 'https://us.i.posthog.com'
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function track(event, properties = {}) {
|
|
8
|
+
client.capture({
|
|
9
|
+
distinctId: 'anonymous',
|
|
10
|
+
event,
|
|
11
|
+
properties: {
|
|
12
|
+
...properties,
|
|
13
|
+
cli_version: require('../../package.json').version,
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function shutdown() {
|
|
19
|
+
await client.shutdown();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { track, shutdown };
|
package/src/validate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { track, shutdown } = require('./telemetry');
|
|
3
4
|
|
|
4
5
|
function detectProjectType(cwd) {
|
|
5
6
|
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
@@ -28,42 +29,30 @@ function readManifest() {
|
|
|
28
29
|
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
function readEnv(
|
|
32
|
-
const
|
|
32
|
+
function readEnv() {
|
|
33
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
34
|
+
if (!fs.existsSync(envPath)) {
|
|
35
|
+
console.log('\nNo .env file found.\n');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
33
38
|
const envVars = {};
|
|
34
39
|
const duplicates = [];
|
|
35
40
|
const seen = new Set();
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
}
|
|
41
|
+
const lines = fs.readFileSync(envPath, 'utf8').split('\n');
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
44
|
+
if (match) {
|
|
45
|
+
const key = match[1].trim();
|
|
46
|
+
if (seen.has(key)) duplicates.push(key);
|
|
47
|
+
seen.add(key);
|
|
48
|
+
envVars[key] = match[2].trim();
|
|
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
|
+
return { envVars, duplicates };
|
|
63
52
|
}
|
|
64
53
|
|
|
65
|
-
function readEnvExample(
|
|
66
|
-
const envExamplePath = path.join(cwd, '.env.example');
|
|
54
|
+
function readEnvExample() {
|
|
55
|
+
const envExamplePath = path.join(process.cwd(), '.env.example');
|
|
67
56
|
if (!fs.existsSync(envExamplePath)) return null;
|
|
68
57
|
const envVars = {};
|
|
69
58
|
const lines = fs.readFileSync(envExamplePath, 'utf8').split('\n');
|
|
@@ -82,8 +71,8 @@ function checkRuntime(manifest) {
|
|
|
82
71
|
return null;
|
|
83
72
|
}
|
|
84
73
|
|
|
85
|
-
function checkEnvExample(envVars
|
|
86
|
-
const example = readEnvExample(
|
|
74
|
+
function checkEnvExample(envVars) {
|
|
75
|
+
const example = readEnvExample();
|
|
87
76
|
if (!example) return [];
|
|
88
77
|
const missing = [];
|
|
89
78
|
for (const key of Object.keys(example)) {
|
|
@@ -92,9 +81,9 @@ function checkEnvExample(envVars, cwd) {
|
|
|
92
81
|
return missing;
|
|
93
82
|
}
|
|
94
83
|
|
|
95
|
-
function checkDependencyDrift(
|
|
96
|
-
const packagePath = path.join(cwd, 'package.json');
|
|
97
|
-
const modulesPath = path.join(cwd, 'node_modules');
|
|
84
|
+
function checkDependencyDrift() {
|
|
85
|
+
const packagePath = path.join(process.cwd(), 'package.json');
|
|
86
|
+
const modulesPath = path.join(process.cwd(), 'node_modules');
|
|
98
87
|
if (!fs.existsSync(packagePath)) return null;
|
|
99
88
|
if (!fs.existsSync(modulesPath)) return { notInstalled: true, missing: [] };
|
|
100
89
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
@@ -107,57 +96,17 @@ function checkDependencyDrift(cwd) {
|
|
|
107
96
|
return { notInstalled: false, missing };
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
function
|
|
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
|
-
|
|
141
|
-
function validate() {
|
|
99
|
+
async function validate() {
|
|
142
100
|
console.log('\nRunning safelaunch...\n');
|
|
143
101
|
|
|
144
102
|
const cwd = process.cwd();
|
|
145
103
|
const manifest = readManifest();
|
|
146
|
-
const
|
|
104
|
+
const { envVars, duplicates } = readEnv();
|
|
147
105
|
|
|
106
|
+
const projectType = manifest.projectType || detectProjectType(cwd);
|
|
148
107
|
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
149
108
|
console.log('project type: ' + (typeLabels[projectType] || 'Node.js') + '\n');
|
|
150
109
|
|
|
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
|
-
|
|
161
110
|
const missing = [];
|
|
162
111
|
const empty = [];
|
|
163
112
|
const passing = [];
|
|
@@ -173,9 +122,8 @@ function validate() {
|
|
|
173
122
|
}
|
|
174
123
|
|
|
175
124
|
const runtimeMismatch = checkRuntime(manifest);
|
|
176
|
-
const exampleMissing = checkEnvExample(envVars
|
|
177
|
-
const drift = checkDependencyDrift(
|
|
178
|
-
const prefixWarnings = checkFrameworkPrefixes(envVars, manifest, projectType);
|
|
125
|
+
const exampleMissing = checkEnvExample(envVars);
|
|
126
|
+
const drift = checkDependencyDrift();
|
|
179
127
|
|
|
180
128
|
if (runtimeMismatch) {
|
|
181
129
|
console.log('⚠️ RUNTIME MISMATCH\n');
|
|
@@ -187,7 +135,7 @@ function validate() {
|
|
|
187
135
|
if (duplicates.length > 0) {
|
|
188
136
|
console.log('⚠️ DUPLICATE VARIABLES (' + duplicates.length + ' found)\n');
|
|
189
137
|
for (const key of duplicates) {
|
|
190
|
-
console.log(' ' + key);
|
|
138
|
+
console.log(' ' + key + ' defined more than once in .env');
|
|
191
139
|
}
|
|
192
140
|
console.log('');
|
|
193
141
|
}
|
|
@@ -205,14 +153,6 @@ function validate() {
|
|
|
205
153
|
}
|
|
206
154
|
}
|
|
207
155
|
|
|
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
|
-
|
|
216
156
|
if (exampleMissing.length > 0) {
|
|
217
157
|
console.log('⚠️ MISSING FROM .env.example (' + exampleMissing.length + ' found)\n');
|
|
218
158
|
for (const key of exampleMissing) {
|
|
@@ -246,15 +186,36 @@ function validate() {
|
|
|
246
186
|
}
|
|
247
187
|
|
|
248
188
|
const hasFailed = runtimeMismatch || missing.length > 0 || empty.length > 0 ||
|
|
249
|
-
duplicates.length > 0 || exampleMissing.length > 0 ||
|
|
189
|
+
duplicates.length > 0 || exampleMissing.length > 0 ||
|
|
250
190
|
(drift && (drift.notInstalled || drift.missing.length > 0));
|
|
251
191
|
|
|
252
192
|
if (hasFailed) {
|
|
253
193
|
console.log('Your environment is not ready for production.\n');
|
|
194
|
+
await track('safelaunch_validate_run', {
|
|
195
|
+
project_type: projectType,
|
|
196
|
+
passed: false,
|
|
197
|
+
missing: missing.length,
|
|
198
|
+
empty: empty.length,
|
|
199
|
+
duplicates: duplicates.length,
|
|
200
|
+
runtime_mismatch: !!runtimeMismatch,
|
|
201
|
+
dependency_drift: !!(drift && drift.missing.length > 0)
|
|
202
|
+
});
|
|
203
|
+
await shutdown();
|
|
254
204
|
process.exit(1);
|
|
255
205
|
} else {
|
|
256
206
|
console.log('Your environment is ready for production.\n');
|
|
207
|
+
await track('safelaunch_validate_run', {
|
|
208
|
+
project_type: projectType,
|
|
209
|
+
passed: true,
|
|
210
|
+
vars_passing: passing.length
|
|
211
|
+
});
|
|
212
|
+
await shutdown();
|
|
257
213
|
}
|
|
258
214
|
}
|
|
259
215
|
|
|
260
|
-
validate();
|
|
216
|
+
validate();
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Save with **Cmd + S**. Now we need to publish this to npm. Go to Terminal and run:
|
|
220
|
+
```
|
|
221
|
+
cd ~/safelaunch/safelaunch
|