safelaunch 1.0.5 → 1.0.7
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 +51 -18
- package/package.json +1 -1
- package/src/init.js +59 -11
- package/src/validate.js +23 -0
package/README.md
CHANGED
|
@@ -2,50 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
> Catch what breaks production before it breaks.
|
|
4
4
|
|
|
5
|
-
safelaunch
|
|
5
|
+
safelaunch is a comprehensive environment validator for JavaScript projects. Run it before every push to catch everything that will break production.
|
|
6
|
+
|
|
7
|
+
Works with Node.js, Next.js, Vite, and Create React App projects. Automatically detects your project type and runs the right checks.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
npm install -g safelaunch
|
|
10
12
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
**Step 1: Generate your environment manifest automatically**
|
|
13
|
+
## Two commands. That is it.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**Step 1: Generate your environment manifest**
|
|
16
16
|
|
|
17
17
|
safelaunch init
|
|
18
18
|
|
|
19
|
-
|
|
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.
|
|
20
20
|
|
|
21
|
-
**Step 2: Validate before
|
|
21
|
+
**Step 2: Validate before every push**
|
|
22
22
|
|
|
23
23
|
safelaunch validate
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
## What safelaunch checks
|
|
26
|
+
|
|
27
|
+
safelaunch validate runs 7 checks:
|
|
28
|
+
|
|
29
|
+
1. Missing required environment variables
|
|
30
|
+
2. Empty required environment variables
|
|
31
|
+
3. Runtime version mismatch (Node version)
|
|
32
|
+
4. Duplicate variables in .env
|
|
33
|
+
5. Dependencies not installed
|
|
34
|
+
6. Dependency drift (in package.json but not installed)
|
|
35
|
+
7. Variables in .env.example but missing from .env
|
|
36
|
+
|
|
37
|
+
## Example output
|
|
38
|
+
|
|
39
|
+
Running safelaunch...
|
|
40
|
+
|
|
41
|
+
project type: Node.js
|
|
26
42
|
|
|
27
|
-
|
|
43
|
+
⚠️ RUNTIME MISMATCH
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
Node required: 18
|
|
46
|
+
Node actual: 20
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
⚠️ DEPENDENCY DRIFT (1 found)
|
|
49
|
+
|
|
50
|
+
express in package.json but not installed
|
|
51
|
+
|
|
52
|
+
❌ EMPTY VARIABLES (1 found)
|
|
53
|
+
|
|
54
|
+
DATABASE_URL required but empty in .env
|
|
55
|
+
|
|
56
|
+
❌ MISSING VARIABLES (1 found)
|
|
57
|
+
|
|
58
|
+
REDIS_URL required but missing from .env
|
|
33
59
|
|
|
34
60
|
✅ PASSING (1)
|
|
35
61
|
|
|
36
62
|
API_KEY present
|
|
37
63
|
|
|
38
64
|
Your environment is not ready for production.
|
|
39
|
-
Fix the issues above and run safelaunch again.
|
|
40
65
|
|
|
41
|
-
##
|
|
66
|
+
## Supported project types
|
|
67
|
+
|
|
68
|
+
safelaunch automatically detects your project type.
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
74
|
|
|
46
75
|
## CI Integration
|
|
47
76
|
|
|
48
|
-
Add this to your GitHub Actions workflow
|
|
77
|
+
Add this to your GitHub Actions workflow:
|
|
49
78
|
|
|
50
79
|
- name: Install safelaunch
|
|
51
80
|
run: npm install -g safelaunch
|
|
@@ -53,6 +82,10 @@ Add this to your GitHub Actions workflow to block deployments automatically:
|
|
|
53
82
|
- name: Validate environment
|
|
54
83
|
run: safelaunch validate
|
|
55
84
|
|
|
85
|
+
Blocks deployments automatically if any check fails.
|
|
86
|
+
|
|
56
87
|
## Built by Orches
|
|
57
88
|
|
|
58
|
-
|
|
89
|
+
Deployment Reliability Infrastructure.
|
|
90
|
+
GitHub: https://github.com/karthicedric7-cloud/safelaunch
|
|
91
|
+
VS Code: https://marketplace.visualstudio.com/items?itemName=Orches.deploycheck-vscode
|
package/package.json
CHANGED
package/src/init.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
-
function
|
|
4
|
+
function detectProjectType(cwd) {
|
|
5
|
+
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
6
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts'))) {
|
|
7
|
+
return 'vite';
|
|
8
|
+
}
|
|
9
|
+
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
10
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) {
|
|
11
|
+
return 'next';
|
|
12
|
+
}
|
|
13
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
14
|
+
if (fs.existsSync(packagePath)) {
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
16
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
17
|
+
if (deps['react-scripts']) return 'cra';
|
|
18
|
+
}
|
|
19
|
+
return 'node';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scanFiles(dir, extensions, pattern, found = new Set()) {
|
|
5
23
|
const items = fs.readdirSync(dir);
|
|
6
24
|
for (const item of items) {
|
|
7
25
|
if (item === 'node_modules' || item === '.git') continue;
|
|
8
26
|
const full = path.join(dir, item);
|
|
9
27
|
const stat = fs.statSync(full);
|
|
10
28
|
if (stat.isDirectory()) {
|
|
11
|
-
scanFiles(full, extensions, found);
|
|
29
|
+
scanFiles(full, extensions, pattern, found);
|
|
12
30
|
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
13
31
|
const content = fs.readFileSync(full, 'utf8');
|
|
14
|
-
const matches = content.matchAll(
|
|
32
|
+
const matches = content.matchAll(pattern);
|
|
15
33
|
for (const match of matches) {
|
|
16
34
|
found.add(match[1]);
|
|
17
35
|
}
|
|
@@ -22,28 +40,58 @@ function scanFiles(dir, extensions, found = new Set()) {
|
|
|
22
40
|
|
|
23
41
|
function init() {
|
|
24
42
|
console.log('\nscanning project for environment variables...\n');
|
|
43
|
+
|
|
25
44
|
const cwd = process.cwd();
|
|
26
|
-
const
|
|
27
|
-
|
|
45
|
+
const manifestPath = path.join(cwd, 'env.manifest.json');
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync(manifestPath)) {
|
|
48
|
+
console.log('env.manifest.json already exists. delete it first.\n');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const projectType = detectProjectType(cwd);
|
|
53
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
54
|
+
|
|
55
|
+
let pattern;
|
|
56
|
+
let typeLabel;
|
|
57
|
+
|
|
58
|
+
if (projectType === 'vite') {
|
|
59
|
+
pattern = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
60
|
+
typeLabel = 'Vite';
|
|
61
|
+
} else if (projectType === 'cra') {
|
|
62
|
+
pattern = /process\.env\.(REACT_APP_[A-Z0-9_]*)/g;
|
|
63
|
+
typeLabel = 'Create React App';
|
|
64
|
+
} else if (projectType === 'next') {
|
|
65
|
+
pattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
66
|
+
typeLabel = 'Next.js';
|
|
67
|
+
} else {
|
|
68
|
+
pattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
69
|
+
typeLabel = 'Node.js';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('detected project type: ' + typeLabel + '\n');
|
|
73
|
+
|
|
74
|
+
const found = scanFiles(cwd, extensions, pattern);
|
|
75
|
+
|
|
28
76
|
if (found.size === 0) {
|
|
29
|
-
console.log('no
|
|
77
|
+
console.log('no environment variables found in your project.\n');
|
|
30
78
|
return;
|
|
31
79
|
}
|
|
80
|
+
|
|
32
81
|
const variables = {};
|
|
33
82
|
for (const key of [...found].sort()) {
|
|
34
83
|
variables[key] = { required: true, description: '' };
|
|
35
84
|
}
|
|
85
|
+
|
|
36
86
|
const manifest = {
|
|
37
87
|
version: '1',
|
|
88
|
+
projectType: projectType,
|
|
38
89
|
runtime: { node: process.version.replace('v', '').split('.')[0] },
|
|
39
90
|
variables
|
|
40
91
|
};
|
|
41
|
-
|
|
42
|
-
if (fs.existsSync(manifestPath)) {
|
|
43
|
-
console.log('env.manifest.json already exists. delete it first.\n');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
92
|
+
|
|
46
93
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
94
|
+
|
|
47
95
|
console.log('found ' + found.size + ' environment variables:\n');
|
|
48
96
|
for (const key of [...found].sort()) {
|
|
49
97
|
console.log(' ' + key);
|
package/src/validate.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
function detectProjectType(cwd) {
|
|
5
|
+
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
6
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts'))) {
|
|
7
|
+
return 'vite';
|
|
8
|
+
}
|
|
9
|
+
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
10
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) {
|
|
11
|
+
return 'next';
|
|
12
|
+
}
|
|
13
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
14
|
+
if (fs.existsSync(packagePath)) {
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
16
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
17
|
+
if (deps['react-scripts']) return 'cra';
|
|
18
|
+
}
|
|
19
|
+
return 'node';
|
|
20
|
+
}
|
|
21
|
+
|
|
4
22
|
function readManifest() {
|
|
5
23
|
const manifestPath = path.join(process.cwd(), 'env.manifest.json');
|
|
6
24
|
if (!fs.existsSync(manifestPath)) {
|
|
@@ -80,9 +98,14 @@ function checkDependencyDrift() {
|
|
|
80
98
|
function validate() {
|
|
81
99
|
console.log('\nRunning safelaunch...\n');
|
|
82
100
|
|
|
101
|
+
const cwd = process.cwd();
|
|
83
102
|
const manifest = readManifest();
|
|
84
103
|
const { envVars, duplicates } = readEnv();
|
|
85
104
|
|
|
105
|
+
const projectType = manifest.projectType || detectProjectType(cwd);
|
|
106
|
+
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
107
|
+
console.log('project type: ' + (typeLabels[projectType] || 'Node.js') + '\n');
|
|
108
|
+
|
|
86
109
|
const missing = [];
|
|
87
110
|
const empty = [];
|
|
88
111
|
const passing = [];
|