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 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 11 checks:
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
- ## Supported project types
37
+ ## Example output
44
38
 
45
- safelaunch automatically detects your project type.
39
+ Running safelaunch...
46
40
 
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
41
+ project type: Node.js
51
42
 
52
- ## Example output
43
+ ⚠️ RUNTIME MISMATCH
53
44
 
54
- Running safelaunch...
45
+ Node required: 18
46
+ Node actual: 20
47
+
48
+ ⚠️ DEPENDENCY DRIFT (1 found)
55
49
 
56
- project type: Vite
50
+ express in package.json but not installed
57
51
 
58
- ⚠️ FRAMEWORK PREFIX WARNINGS (1 found)
52
+ EMPTY VARIABLES (1 found)
59
53
 
60
- API_URL missing VITE_ prefix - will be undefined in browser
54
+ DATABASE_URL required but empty in .env
61
55
 
62
56
  ❌ MISSING VARIABLES (1 found)
63
57
 
64
- VITE_STRIPE_KEY required but missing from .env
58
+ REDIS_URL required but missing from .env
65
59
 
66
60
  ✅ PASSING (1)
67
61
 
68
- VITE_API_URL present
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
- JavaScript Reliability Infrastructure.
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.8",
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
- fs.existsSync(path.join(cwd, 'vite.config.ts'))) {
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
- fs.existsSync(path.join(cwd, 'next.config.ts'))) {
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();
@@ -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(cwd) {
32
- const envFiles = ['.env.local', '.env.development.local', '.env.production.local', '.env.development', '.env.production', '.env'];
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 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
- }
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(cwd) {
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, cwd) {
86
- const example = readEnvExample(cwd);
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(cwd) {
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 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
-
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 projectType = manifest.projectType || detectProjectType(cwd);
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, cwd);
177
- const drift = checkDependencyDrift(cwd);
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 || prefixWarnings.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