safelaunch 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +20 -42
  2. package/package.json +1 -1
  3. package/src/validate.js +122 -43
package/README.md CHANGED
@@ -5,46 +5,26 @@
5
5
  safelaunch validates your local environment against a required environment contract before you push to production. No more missing variables. No more runtime mismatches. No more production surprises.
6
6
 
7
7
  ## Install
8
- ```bash
8
+
9
9
  npm install -g safelaunch
10
- ```
11
10
 
12
11
  ## Quick Start
13
12
 
14
- **Step 1: Create your environment manifest**
15
-
16
- Add an `env.manifest.json` file to your project root:
17
- ```json
18
- {
19
- "version": "1",
20
- "runtime": {
21
- "node": "20"
22
- },
23
- "variables": {
24
- "DATABASE_URL": {
25
- "required": true,
26
- "description": "PostgreSQL connection string"
27
- },
28
- "REDIS_URL": {
29
- "required": true,
30
- "description": "Redis connection string"
31
- },
32
- "API_KEY": {
33
- "required": true,
34
- "description": "External API key"
35
- }
36
- }
37
- }
38
- ```
39
-
40
- **Step 2: Run the validator**
41
- ```bash
13
+ **Step 1: Generate your environment manifest automatically**
14
+
15
+ Run this in your project folder:
16
+
17
+ safelaunch init
18
+
19
+ safelaunch scans your entire codebase, finds every environment variable your app uses, and generates env.manifest.json automatically.
20
+
21
+ **Step 2: Validate before you push**
22
+
42
23
  safelaunch validate
43
- ```
44
24
 
45
- **Step 3: See exactly what is wrong**
46
- ```
47
- Running safelaunch...
25
+ **Step 3: See exactly what will break**
26
+
27
+ Running safelaunch validate gives you:
48
28
 
49
29
  ❌ MISSING VARIABLES (2 found)
50
30
 
@@ -57,23 +37,21 @@ Running safelaunch...
57
37
 
58
38
  Your environment is not ready for production.
59
39
  Fix the issues above and run safelaunch again.
60
- ```
40
+
41
+ ## Commands
42
+
43
+ safelaunch init scan project and generate env.manifest.json automatically
44
+ safelaunch validate validate your .env against the manifest
61
45
 
62
46
  ## CI Integration
63
47
 
64
48
  Add this to your GitHub Actions workflow to block deployments automatically:
65
- ```yaml
49
+
66
50
  - name: Install safelaunch
67
51
  run: npm install -g safelaunch
68
52
 
69
53
  - name: Validate environment
70
54
  run: safelaunch validate
71
- ```
72
-
73
- ## What it checks
74
-
75
- - Missing required environment variables
76
- - Runtime version mismatches between local and manifest
77
55
 
78
56
  ## Built by Orches
79
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safelaunch",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Backend Reliability Infrastructure - catch what breaks production before it breaks",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/validate.js CHANGED
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  function readManifest() {
5
5
  const manifestPath = path.join(process.cwd(), 'env.manifest.json');
6
6
  if (!fs.existsSync(manifestPath)) {
7
- console.log(' env.manifest.json not found');
7
+ console.log('\nNo env.manifest.json found. Run safelaunch init first.\n');
8
8
  process.exit(1);
9
9
  }
10
10
  return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -13,83 +13,162 @@ function readManifest() {
13
13
  function readEnv() {
14
14
  const envPath = path.join(process.cwd(), '.env');
15
15
  if (!fs.existsSync(envPath)) {
16
- return {};
16
+ console.log('\nNo .env file found.\n');
17
+ process.exit(1);
17
18
  }
19
+ const envVars = {};
20
+ const duplicates = [];
21
+ const seen = new Set();
18
22
  const lines = fs.readFileSync(envPath, 'utf8').split('\n');
19
- const env = {};
20
- lines.forEach((line) => {
21
- const [key, value] = line.split('=');
22
- if (key && key.trim()) {
23
- env[key.trim()] = value ? value.trim() : '';
23
+ for (const line of lines) {
24
+ const match = line.match(/^([^=]+)=(.*)$/);
25
+ if (match) {
26
+ const key = match[1].trim();
27
+ if (seen.has(key)) duplicates.push(key);
28
+ seen.add(key);
29
+ envVars[key] = match[2].trim();
24
30
  }
25
- });
26
- return env;
31
+ }
32
+ return { envVars, duplicates };
27
33
  }
28
34
 
29
- function checkRuntime(manifest) {
30
- if (!manifest.runtime || !manifest.runtime.node) {
31
- return null;
35
+ function readEnvExample() {
36
+ const envExamplePath = path.join(process.cwd(), '.env.example');
37
+ if (!fs.existsSync(envExamplePath)) return null;
38
+ const envVars = {};
39
+ const lines = fs.readFileSync(envExamplePath, 'utf8').split('\n');
40
+ for (const line of lines) {
41
+ const match = line.match(/^([^=]+)=(.*)$/);
42
+ if (match) envVars[match[1].trim()] = match[2].trim();
32
43
  }
44
+ return envVars;
45
+ }
33
46
 
34
- const requiredVersion = manifest.runtime.node;
35
- const actualVersion = process.versions.node.split('.')[0];
47
+ function checkRuntime(manifest) {
48
+ if (!manifest.runtime || !manifest.runtime.node) return null;
49
+ const required = String(manifest.runtime.node);
50
+ const actual = process.version.replace('v', '').split('.')[0];
51
+ if (required !== actual) return { required, actual };
52
+ return null;
53
+ }
36
54
 
37
- if (actualVersion !== requiredVersion) {
38
- return {
39
- required: requiredVersion,
40
- actual: actualVersion,
41
- };
55
+ function checkEnvExample(envVars) {
56
+ const example = readEnvExample();
57
+ if (!example) return [];
58
+ const missing = [];
59
+ for (const key of Object.keys(example)) {
60
+ if (!envVars[key]) missing.push(key);
42
61
  }
62
+ return missing;
63
+ }
43
64
 
44
- return null;
65
+ function checkDependencyDrift() {
66
+ const packagePath = path.join(process.cwd(), 'package.json');
67
+ const modulesPath = path.join(process.cwd(), 'node_modules');
68
+ if (!fs.existsSync(packagePath)) return null;
69
+ if (!fs.existsSync(modulesPath)) return { notInstalled: true, missing: [] };
70
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
71
+ const deps = Object.keys(pkg.dependencies || {});
72
+ const missing = [];
73
+ for (const dep of deps) {
74
+ const depPath = path.join(modulesPath, dep);
75
+ if (!fs.existsSync(depPath)) missing.push(dep);
76
+ }
77
+ return { notInstalled: false, missing };
45
78
  }
46
79
 
47
80
  function validate() {
48
- console.log('\nRunning deploycheck...\n');
81
+ console.log('\nRunning safelaunch...\n');
49
82
 
50
83
  const manifest = readManifest();
51
- const env = readEnv();
52
- const variables = manifest.variables;
84
+ const { envVars, duplicates } = readEnv();
53
85
 
54
86
  const missing = [];
87
+ const empty = [];
55
88
  const passing = [];
56
- const runtimeMismatch = checkRuntime(manifest);
57
89
 
58
- Object.keys(variables).forEach((key) => {
59
- if (variables[key].required && !env[key]) {
90
+ for (const [key, val] of Object.entries(manifest.variables || {})) {
91
+ if (val.required && key in envVars && envVars[key] === '') {
92
+ empty.push(key);
93
+ } else if (val.required && !(key in envVars)) {
60
94
  missing.push(key);
61
95
  } else {
62
96
  passing.push(key);
63
97
  }
64
- });
98
+ }
99
+
100
+ const runtimeMismatch = checkRuntime(manifest);
101
+ const exampleMissing = checkEnvExample(envVars);
102
+ const drift = checkDependencyDrift();
65
103
 
66
104
  if (runtimeMismatch) {
67
- console.log('RUNTIME MISMATCH\n');
68
- console.log(` Node.js required: v${runtimeMismatch.required}`);
69
- console.log(` Node.js found: v${runtimeMismatch.actual}\n`);
105
+ console.log('⚠️ RUNTIME MISMATCH\n');
106
+ console.log(' Node required: ' + runtimeMismatch.required);
107
+ console.log(' Node actual: ' + runtimeMismatch.actual);
108
+ console.log('');
109
+ }
110
+
111
+ if (duplicates.length > 0) {
112
+ console.log('⚠️ DUPLICATE VARIABLES (' + duplicates.length + ' found)\n');
113
+ for (const key of duplicates) {
114
+ console.log(' ' + key + ' defined more than once in .env');
115
+ }
116
+ console.log('');
117
+ }
118
+
119
+ if (drift) {
120
+ if (drift.notInstalled) {
121
+ console.log('⚠️ DEPENDENCIES NOT INSTALLED\n');
122
+ console.log(' node_modules not found. Run npm install.\n');
123
+ } else if (drift.missing.length > 0) {
124
+ console.log('⚠️ DEPENDENCY DRIFT (' + drift.missing.length + ' found)\n');
125
+ for (const dep of drift.missing) {
126
+ console.log(' ' + dep + ' in package.json but not installed');
127
+ }
128
+ console.log('');
129
+ }
130
+ }
131
+
132
+ if (exampleMissing.length > 0) {
133
+ console.log('⚠️ MISSING FROM .env.example (' + exampleMissing.length + ' found)\n');
134
+ for (const key of exampleMissing) {
135
+ console.log(' ' + key + ' in .env.example but missing from .env');
136
+ }
137
+ console.log('');
138
+ }
139
+
140
+ if (empty.length > 0) {
141
+ console.log('❌ EMPTY VARIABLES (' + empty.length + ' found)\n');
142
+ for (const key of empty) {
143
+ console.log(' ' + key + ' required but empty in .env');
144
+ }
145
+ console.log('');
70
146
  }
71
147
 
72
148
  if (missing.length > 0) {
73
- console.log(`❌ MISSING VARIABLES (${missing.length} found)\n`);
74
- missing.forEach((key) => {
75
- console.log(` ${key} required but missing from .env`);
76
- });
149
+ console.log('❌ MISSING VARIABLES (' + missing.length + ' found)\n');
150
+ for (const key of missing) {
151
+ console.log(' ' + key + ' required but missing from .env');
152
+ }
153
+ console.log('');
77
154
  }
78
155
 
79
156
  if (passing.length > 0) {
80
- console.log(`\n✅ PASSING (${passing.length})\n`);
81
- passing.forEach((key) => {
82
- console.log(` ${key} present`);
83
- });
157
+ console.log('✅ PASSING (' + passing.length + ')\n');
158
+ for (const key of passing) {
159
+ console.log(' ' + key + ' present');
160
+ }
161
+ console.log('');
84
162
  }
85
163
 
86
- if (runtimeMismatch || missing.length > 0) {
87
- console.log('\nYour environment is not ready for production.');
88
- console.log('Fix the issues above and run deploycheck again.\n');
164
+ const hasFailed = runtimeMismatch || missing.length > 0 || empty.length > 0 || duplicates.length > 0 || exampleMissing.length > 0 || (drift && (drift.notInstalled || drift.missing.length > 0));
165
+
166
+ if (hasFailed) {
167
+ console.log('Your environment is not ready for production.\n');
89
168
  process.exit(1);
90
169
  } else {
91
- console.log('\nAll clear. Your environment is ready for production.\n');
170
+ console.log('Your environment is ready for production.\n');
92
171
  }
93
172
  }
94
173
 
95
- validate();
174
+ validate();