safelaunch 1.0.9 → 1.0.11
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 +14 -7
- package/deploycheck-vscode/.vscode/launch.json +15 -0
- package/deploycheck-vscode/README.md +64 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.1.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.10.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.2.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.3.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.4.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.6.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.7.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.8.vsix +0 -0
- package/deploycheck-vscode/deploycheck-vscode-0.0.9.vsix +0 -0
- package/deploycheck-vscode/init.js +54 -0
- package/deploycheck-vscode/package.json +35 -0
- package/deploycheck-vscode/src/extension.js +199 -0
- package/package.json +1 -1
- package/src/init.js +51 -1
- package/src/telemetry.js +1 -1
- package/src/validate.js +66 -7
package/README.md
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# safelaunch
|
|
2
|
-
|
|
3
2
|
> Catch what breaks production before it breaks.
|
|
4
3
|
|
|
5
4
|
safelaunch is a comprehensive environment validator for JavaScript projects. Run it before every push to catch everything that will break production.
|
|
@@ -24,7 +23,7 @@ safelaunch validate
|
|
|
24
23
|
|
|
25
24
|
## What safelaunch checks
|
|
26
25
|
|
|
27
|
-
safelaunch validate runs
|
|
26
|
+
safelaunch validate runs 11 checks:
|
|
28
27
|
|
|
29
28
|
1. Missing required environment variables
|
|
30
29
|
2. Empty required environment variables
|
|
@@ -33,12 +32,16 @@ safelaunch validate runs 7 checks:
|
|
|
33
32
|
5. Dependencies not installed
|
|
34
33
|
6. Dependency drift (in package.json but not installed)
|
|
35
34
|
7. Variables in .env.example but missing from .env
|
|
35
|
+
8. VITE_ prefix warning (Vite projects — variables without VITE_ won't be exposed to the client)
|
|
36
|
+
9. REACT_APP_ prefix warning (CRA projects — variables without REACT_APP_ won't be exposed to the client)
|
|
37
|
+
10. NEXT_PUBLIC_ prefix awareness (Next.js — flags variables intended for the client)
|
|
38
|
+
11. .env file priority conflicts (Next.js — warns when .env.local overrides .env silently)
|
|
36
39
|
|
|
37
40
|
## Example output
|
|
38
41
|
|
|
39
42
|
Running safelaunch...
|
|
40
43
|
|
|
41
|
-
project type:
|
|
44
|
+
project type: Next.js
|
|
42
45
|
|
|
43
46
|
⚠️ RUNTIME MISMATCH
|
|
44
47
|
|
|
@@ -49,6 +52,10 @@ project type: Node.js
|
|
|
49
52
|
|
|
50
53
|
express in package.json but not installed
|
|
51
54
|
|
|
55
|
+
⚠️ ENV FILE PRIORITY (1 found)
|
|
56
|
+
|
|
57
|
+
DATABASE_URL in both .env and .env.local (.env.local takes priority)
|
|
58
|
+
|
|
52
59
|
❌ EMPTY VARIABLES (1 found)
|
|
53
60
|
|
|
54
61
|
DATABASE_URL required but empty in .env
|
|
@@ -68,9 +75,9 @@ Your environment is not ready for production.
|
|
|
68
75
|
safelaunch automatically detects your project type.
|
|
69
76
|
|
|
70
77
|
- 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_
|
|
78
|
+
- Next.js scans process.env, checks NEXT_PUBLIC_ and .env priority
|
|
79
|
+
- Vite scans import.meta.env, checks VITE_ prefix
|
|
80
|
+
- React CRA scans process.env, checks REACT_APP_ prefix
|
|
74
81
|
|
|
75
82
|
## CI Integration
|
|
76
83
|
|
|
@@ -78,7 +85,6 @@ Add this to your GitHub Actions workflow:
|
|
|
78
85
|
|
|
79
86
|
- name: Install safelaunch
|
|
80
87
|
run: npm install -g safelaunch
|
|
81
|
-
|
|
82
88
|
- name: Validate environment
|
|
83
89
|
run: safelaunch validate
|
|
84
90
|
|
|
@@ -87,5 +93,6 @@ Blocks deployments automatically if any check fails.
|
|
|
87
93
|
## Built by Orches
|
|
88
94
|
|
|
89
95
|
Deployment Reliability Infrastructure.
|
|
96
|
+
|
|
90
97
|
GitHub: https://github.com/karthicedric7-cloud/safelaunch
|
|
91
98
|
VS Code: https://marketplace.visualstudio.com/items?itemName=Orches.deploycheck-vscode
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "chrome",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Launch Chrome against localhost",
|
|
11
|
+
"url": "http://localhost:8080",
|
|
12
|
+
"webRoot": "${workspaceFolder}"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# deploycheck
|
|
2
|
+
|
|
3
|
+
> Catch what breaks production before it breaks.
|
|
4
|
+
|
|
5
|
+
Real time environment validation inside VS Code. deploycheck runs 7 checks on your environment before you push to production.
|
|
6
|
+
|
|
7
|
+
Works with Node.js, Next.js, Vite, and Create React App. Automatically detects your project type.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
Open the Command Palette with Ctrl+Shift+P and run:
|
|
12
|
+
|
|
13
|
+
**deploycheck: Generate env.manifest.json**
|
|
14
|
+
Scans your entire project for environment variables and automatically generates env.manifest.json.
|
|
15
|
+
|
|
16
|
+
Works with:
|
|
17
|
+
- process.env for Node.js and Next.js projects
|
|
18
|
+
- import.meta.env for Vite projects
|
|
19
|
+
- REACT_APP_ variables for Create React App
|
|
20
|
+
|
|
21
|
+
**deploycheck: Validate Environment**
|
|
22
|
+
Runs 7 checks on your environment and tells you exactly what will break before you push.
|
|
23
|
+
|
|
24
|
+
## What deploycheck checks
|
|
25
|
+
|
|
26
|
+
1. Missing required environment variables
|
|
27
|
+
2. Empty required environment variables
|
|
28
|
+
3. Runtime version mismatch (Node version)
|
|
29
|
+
4. Duplicate variables in .env
|
|
30
|
+
5. Dependencies not installed
|
|
31
|
+
6. Dependency drift (in package.json but not installed)
|
|
32
|
+
7. Variables in .env.example but missing from .env
|
|
33
|
+
|
|
34
|
+
## Supported project types
|
|
35
|
+
|
|
36
|
+
deploycheck automatically detects your project type.
|
|
37
|
+
|
|
38
|
+
- Node.js scans process.env
|
|
39
|
+
- Next.js scans process.env
|
|
40
|
+
- Vite scans import.meta.env
|
|
41
|
+
- React CRA scans REACT_APP_ variables
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
Step 1: Open your project in VS Code
|
|
46
|
+
Step 2: Press Ctrl+Shift+P
|
|
47
|
+
Step 3: Type deploycheck
|
|
48
|
+
Step 4: Run Generate to create your manifest
|
|
49
|
+
Step 5: Run Validate before every push
|
|
50
|
+
|
|
51
|
+
## CLI Version
|
|
52
|
+
|
|
53
|
+
For terminal and CI use install the CLI:
|
|
54
|
+
|
|
55
|
+
npm install -g safelaunch
|
|
56
|
+
|
|
57
|
+
safelaunch init
|
|
58
|
+
safelaunch validate
|
|
59
|
+
|
|
60
|
+
## Built by Orches
|
|
61
|
+
|
|
62
|
+
JavaScript Reliability Infrastructure.
|
|
63
|
+
GitHub: https://github.com/karthicedric7-cloud/safelaunch
|
|
64
|
+
npm: https://www.npmjs.com/package/safelaunch
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function scanFiles(dir, extensions, found = new Set()) {
|
|
5
|
+
const items = fs.readdirSync(dir);
|
|
6
|
+
for (const item of items) {
|
|
7
|
+
if (item === 'node_modules' || item === '.git') continue;
|
|
8
|
+
const full = path.join(dir, item);
|
|
9
|
+
const stat = fs.statSync(full);
|
|
10
|
+
if (stat.isDirectory()) {
|
|
11
|
+
scanFiles(full, extensions, found);
|
|
12
|
+
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
13
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
14
|
+
const matches = content.matchAll(/process\.env\.([A-Z_][A-Z0-9_]*)/g);
|
|
15
|
+
for (const match of matches) {
|
|
16
|
+
found.add(match[1]);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return found;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function init() {
|
|
24
|
+
console.log('\nscanning project for environment variables...\n');
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
27
|
+
const found = scanFiles(cwd, extensions);
|
|
28
|
+
if (found.size === 0) {
|
|
29
|
+
console.log('no process.env references found.\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const variables = {};
|
|
33
|
+
for (const key of [...found].sort()) {
|
|
34
|
+
variables[key] = { required: true, description: '' };
|
|
35
|
+
}
|
|
36
|
+
const manifest = {
|
|
37
|
+
version: '1',
|
|
38
|
+
runtime: { node: process.version.replace('v', '').split('.')[0] },
|
|
39
|
+
variables
|
|
40
|
+
};
|
|
41
|
+
const manifestPath = path.join(cwd, 'env.manifest.json');
|
|
42
|
+
if (fs.existsSync(manifestPath)) {
|
|
43
|
+
console.log('env.manifest.json already exists. delete it first.\n');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
47
|
+
console.log('found ' + found.size + ' environment variables:\n');
|
|
48
|
+
for (const key of [...found].sort()) {
|
|
49
|
+
console.log(' ' + key);
|
|
50
|
+
}
|
|
51
|
+
console.log('\ncreated env.manifest.json\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
init();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deploycheck-vscode",
|
|
3
|
+
"displayName": "deploycheck",
|
|
4
|
+
"description": "Catch what breaks production before it breaks. Real time environment validation inside VS Code.",
|
|
5
|
+
"version": "0.0.10",
|
|
6
|
+
"publisher": "Orches",
|
|
7
|
+
"engines": {
|
|
8
|
+
"vscode": "^1.74.0"
|
|
9
|
+
},
|
|
10
|
+
"categories": [
|
|
11
|
+
"Linters"
|
|
12
|
+
],
|
|
13
|
+
"activationEvents": [
|
|
14
|
+
"*"
|
|
15
|
+
],
|
|
16
|
+
"main": "./src/extension.js",
|
|
17
|
+
"contributes": {
|
|
18
|
+
"commands": [
|
|
19
|
+
{
|
|
20
|
+
"command": "deploycheck.init",
|
|
21
|
+
"title": "deploycheck: Generate env.manifest.json"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"command": "deploycheck.validate",
|
|
25
|
+
"title": "deploycheck: Validate Environment"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/vscode": "^1.74.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const vscode = require('vscode');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function detectProjectType(cwd) {
|
|
6
|
+
if (fs.existsSync(path.join(cwd, 'vite.config.js')) ||
|
|
7
|
+
fs.existsSync(path.join(cwd, 'vite.config.ts'))) {
|
|
8
|
+
return 'vite';
|
|
9
|
+
}
|
|
10
|
+
if (fs.existsSync(path.join(cwd, 'next.config.js')) ||
|
|
11
|
+
fs.existsSync(path.join(cwd, 'next.config.ts'))) {
|
|
12
|
+
return 'next';
|
|
13
|
+
}
|
|
14
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
15
|
+
if (fs.existsSync(packagePath)) {
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
17
|
+
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
|
18
|
+
if (deps['react-scripts']) return 'cra';
|
|
19
|
+
}
|
|
20
|
+
return 'node';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function scanFiles(dir, extensions, pattern, found = new Set()) {
|
|
24
|
+
const items = fs.readdirSync(dir);
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (item === 'node_modules' || item === '.git') continue;
|
|
27
|
+
const full = path.join(dir, item);
|
|
28
|
+
const stat = fs.statSync(full);
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
scanFiles(full, extensions, pattern, found);
|
|
31
|
+
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
32
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
33
|
+
const matches = content.matchAll(pattern);
|
|
34
|
+
for (const match of matches) found.add(match[1]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function activate(context) {
|
|
41
|
+
const initCommand = vscode.commands.registerCommand('deploycheck.init', function () {
|
|
42
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
43
|
+
if (!workspaceFolders) {
|
|
44
|
+
vscode.window.showErrorMessage('deploycheck: No workspace folder open.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const cwd = workspaceFolders[0].uri.fsPath;
|
|
48
|
+
const manifestPath = path.join(cwd, 'env.manifest.json');
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(manifestPath)) {
|
|
51
|
+
vscode.window.showWarningMessage('deploycheck: env.manifest.json already exists. Delete it first to regenerate.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const projectType = detectProjectType(cwd);
|
|
56
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
57
|
+
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
58
|
+
|
|
59
|
+
let pattern;
|
|
60
|
+
if (projectType === 'vite') {
|
|
61
|
+
pattern = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
62
|
+
} else if (projectType === 'cra') {
|
|
63
|
+
pattern = /process\.env\.(REACT_APP_[A-Z0-9_]*)/g;
|
|
64
|
+
} else {
|
|
65
|
+
pattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const found = scanFiles(cwd, extensions, pattern);
|
|
69
|
+
|
|
70
|
+
if (found.size === 0) {
|
|
71
|
+
vscode.window.showInformationMessage('deploycheck: No environment variables found in your ' + typeLabels[projectType] + ' project.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const variables = {};
|
|
76
|
+
for (const key of [...found].sort()) {
|
|
77
|
+
variables[key] = { required: true, description: '' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const manifest = {
|
|
81
|
+
version: '1',
|
|
82
|
+
projectType: projectType,
|
|
83
|
+
runtime: { node: process.version.replace('v', '').split('.')[0] },
|
|
84
|
+
variables
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
88
|
+
vscode.window.showInformationMessage('deploycheck: Found ' + found.size + ' variables in ' + typeLabels[projectType] + ' project. Created env.manifest.json');
|
|
89
|
+
|
|
90
|
+
vscode.workspace.openTextDocument(manifestPath).then(doc => {
|
|
91
|
+
vscode.window.showTextDocument(doc);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const validateCommand = vscode.commands.registerCommand('deploycheck.validate', function () {
|
|
96
|
+
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
97
|
+
if (!workspaceFolders) {
|
|
98
|
+
vscode.window.showErrorMessage('deploycheck: No workspace folder open.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const cwd = workspaceFolders[0].uri.fsPath;
|
|
102
|
+
const manifestPath = path.join(cwd, 'env.manifest.json');
|
|
103
|
+
|
|
104
|
+
if (!fs.existsSync(manifestPath)) {
|
|
105
|
+
vscode.window.showWarningMessage('deploycheck: No env.manifest.json found. Run deploycheck.init first.');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const envPath = path.join(cwd, '.env');
|
|
110
|
+
if (!fs.existsSync(envPath)) {
|
|
111
|
+
vscode.window.showErrorMessage('deploycheck: No .env file found.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const envVars = {};
|
|
116
|
+
const duplicates = [];
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
const lines = fs.readFileSync(envPath, 'utf8').split('\n');
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
121
|
+
if (match) {
|
|
122
|
+
const key = match[1].trim();
|
|
123
|
+
if (seen.has(key)) duplicates.push(key);
|
|
124
|
+
seen.add(key);
|
|
125
|
+
envVars[key] = match[2].trim();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
130
|
+
const projectType = manifest.projectType || detectProjectType(cwd);
|
|
131
|
+
const typeLabels = { vite: 'Vite', next: 'Next.js', cra: 'Create React App', node: 'Node.js' };
|
|
132
|
+
|
|
133
|
+
const missing = [];
|
|
134
|
+
const empty = [];
|
|
135
|
+
|
|
136
|
+
for (const [key, val] of Object.entries(manifest.variables || {})) {
|
|
137
|
+
if (val.required && key in envVars && envVars[key] === '') {
|
|
138
|
+
empty.push(key);
|
|
139
|
+
} else if (val.required && !(key in envVars)) {
|
|
140
|
+
missing.push(key);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const examplePath = path.join(cwd, '.env.example');
|
|
145
|
+
const exampleMissing = [];
|
|
146
|
+
if (fs.existsSync(examplePath)) {
|
|
147
|
+
const exLines = fs.readFileSync(examplePath, 'utf8').split('\n');
|
|
148
|
+
for (const line of exLines) {
|
|
149
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
150
|
+
if (match && !envVars[match[1].trim()]) exampleMissing.push(match[1].trim());
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const nodeRequired = manifest.runtime && manifest.runtime.node ? String(manifest.runtime.node) : null;
|
|
155
|
+
const nodeActual = process.version.replace('v', '').split('.')[0];
|
|
156
|
+
const runtimeMismatch = nodeRequired && nodeRequired !== nodeActual;
|
|
157
|
+
|
|
158
|
+
const modulesPath = path.join(cwd, 'node_modules');
|
|
159
|
+
const packagePath = path.join(cwd, 'package.json');
|
|
160
|
+
let driftMissing = [];
|
|
161
|
+
let notInstalled = false;
|
|
162
|
+
if (fs.existsSync(packagePath)) {
|
|
163
|
+
if (!fs.existsSync(modulesPath)) {
|
|
164
|
+
notInstalled = true;
|
|
165
|
+
} else {
|
|
166
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
167
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
168
|
+
driftMissing = deps.filter(dep => !fs.existsSync(path.join(modulesPath, dep)));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const problems = [];
|
|
173
|
+
if (runtimeMismatch) problems.push('Runtime mismatch: need Node ' + nodeRequired + ' got ' + nodeActual);
|
|
174
|
+
if (duplicates.length > 0) problems.push('Duplicates: ' + duplicates.join(', '));
|
|
175
|
+
if (notInstalled) problems.push('node_modules missing. Run npm install.');
|
|
176
|
+
if (driftMissing.length > 0) problems.push('Drift: ' + driftMissing.join(', '));
|
|
177
|
+
if (exampleMissing.length > 0) problems.push('Missing from .env.example: ' + exampleMissing.join(', '));
|
|
178
|
+
if (empty.length > 0) problems.push('Empty: ' + empty.join(', '));
|
|
179
|
+
if (missing.length > 0) problems.push('Missing: ' + missing.join(', '));
|
|
180
|
+
|
|
181
|
+
const label = typeLabels[projectType] || 'Node.js';
|
|
182
|
+
|
|
183
|
+
if (problems.length === 0) {
|
|
184
|
+
vscode.window.showInformationMessage('deploycheck (' + label + '): ✅ Environment is ready for production.');
|
|
185
|
+
} else {
|
|
186
|
+
vscode.window.showErrorMessage('deploycheck (' + label + '): ❌ ' + problems[0]);
|
|
187
|
+
if (problems.length > 1) {
|
|
188
|
+
vscode.window.showWarningMessage('deploycheck: ' + (problems.length - 1) + ' more issue(s): ' + problems.slice(1).join(' | '));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
context.subscriptions.push(initCommand);
|
|
194
|
+
context.subscriptions.push(validateCommand);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function deactivate() {}
|
|
198
|
+
|
|
199
|
+
module.exports = { activate, deactivate };
|
package/package.json
CHANGED
package/src/init.js
CHANGED
|
@@ -54,4 +54,54 @@ async function init() {
|
|
|
54
54
|
const projectType = detectProjectType(cwd);
|
|
55
55
|
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
56
56
|
let pattern;
|
|
57
|
-
let typeLabel;
|
|
57
|
+
let typeLabel;
|
|
58
|
+
|
|
59
|
+
if (projectType === 'vite') {
|
|
60
|
+
pattern = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
61
|
+
typeLabel = 'Vite';
|
|
62
|
+
} else if (projectType === 'cra') {
|
|
63
|
+
pattern = /process\.env\.(REACT_APP_[A-Z0-9_]*)/g;
|
|
64
|
+
typeLabel = 'Create React App';
|
|
65
|
+
} else if (projectType === 'next') {
|
|
66
|
+
pattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
67
|
+
typeLabel = 'Next.js';
|
|
68
|
+
} else {
|
|
69
|
+
pattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
70
|
+
typeLabel = 'Node.js';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('detected project type: ' + typeLabel + '\n');
|
|
74
|
+
const found = scanFiles(cwd, extensions, pattern);
|
|
75
|
+
|
|
76
|
+
if (found.size === 0) {
|
|
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();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const variables = {};
|
|
84
|
+
for (const key of [...found].sort()) {
|
|
85
|
+
variables[key] = { required: true, description: '' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const manifest = {
|
|
89
|
+
version: '1',
|
|
90
|
+
projectType: projectType,
|
|
91
|
+
runtime: { node: process.version.replace('v', '').split('.')[0] },
|
|
92
|
+
variables
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
96
|
+
|
|
97
|
+
console.log('found ' + found.size + ' environment variables:\n');
|
|
98
|
+
for (const key of [...found].sort()) {
|
|
99
|
+
console.log(' ' + key);
|
|
100
|
+
}
|
|
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();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
init();
|
package/src/telemetry.js
CHANGED
package/src/validate.js
CHANGED
|
@@ -96,6 +96,49 @@ function checkDependencyDrift() {
|
|
|
96
96
|
return { notInstalled: false, missing };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function checkPrefixes(envVars, projectType) {
|
|
100
|
+
const warnings = [];
|
|
101
|
+
for (const key of Object.keys(envVars)) {
|
|
102
|
+
if (key === 'NODE_ENV') continue;
|
|
103
|
+
if (projectType === 'vite' && !key.startsWith('VITE_')) {
|
|
104
|
+
warnings.push(key + ' missing VITE_ prefix (won\'t be exposed to client)');
|
|
105
|
+
}
|
|
106
|
+
if (projectType === 'cra' && !key.startsWith('REACT_APP_')) {
|
|
107
|
+
warnings.push(key + ' missing REACT_APP_ prefix (won\'t be exposed to client)');
|
|
108
|
+
}
|
|
109
|
+
if (projectType === 'next' && key.startsWith('NEXT_PUBLIC_') === false) {
|
|
110
|
+
// server-side only — no warning needed
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return warnings;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function checkEnvPriority(cwd, projectType) {
|
|
117
|
+
if (projectType !== 'next') return [];
|
|
118
|
+
const warnings = [];
|
|
119
|
+
const fileVars = {};
|
|
120
|
+
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
|
|
121
|
+
for (const file of envFiles) {
|
|
122
|
+
const filePath = path.join(cwd, file);
|
|
123
|
+
if (!fs.existsSync(filePath)) continue;
|
|
124
|
+
const vars = {};
|
|
125
|
+
const lines = fs.readFileSync(filePath, 'utf8').split('\n');
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
128
|
+
if (match) vars[match[1].trim()] = match[2].trim();
|
|
129
|
+
}
|
|
130
|
+
fileVars[file] = vars;
|
|
131
|
+
}
|
|
132
|
+
if (fileVars['.env'] && fileVars['.env.local']) {
|
|
133
|
+
for (const key of Object.keys(fileVars['.env'])) {
|
|
134
|
+
if (fileVars['.env.local'][key]) {
|
|
135
|
+
warnings.push(key + ' in both .env and .env.local (.env.local takes priority)');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return warnings;
|
|
140
|
+
}
|
|
141
|
+
|
|
99
142
|
async function validate() {
|
|
100
143
|
console.log('\nRunning safelaunch...\n');
|
|
101
144
|
|
|
@@ -124,6 +167,8 @@ async function validate() {
|
|
|
124
167
|
const runtimeMismatch = checkRuntime(manifest);
|
|
125
168
|
const exampleMissing = checkEnvExample(envVars);
|
|
126
169
|
const drift = checkDependencyDrift();
|
|
170
|
+
const prefixWarnings = checkPrefixes(envVars, projectType);
|
|
171
|
+
const priorityWarnings = checkEnvPriority(cwd, projectType);
|
|
127
172
|
|
|
128
173
|
if (runtimeMismatch) {
|
|
129
174
|
console.log('⚠️ RUNTIME MISMATCH\n');
|
|
@@ -161,6 +206,22 @@ async function validate() {
|
|
|
161
206
|
console.log('');
|
|
162
207
|
}
|
|
163
208
|
|
|
209
|
+
if (prefixWarnings.length > 0) {
|
|
210
|
+
console.log('⚠️ PREFIX WARNINGS (' + prefixWarnings.length + ' found)\n');
|
|
211
|
+
for (const w of prefixWarnings) {
|
|
212
|
+
console.log(' ' + w);
|
|
213
|
+
}
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (priorityWarnings.length > 0) {
|
|
218
|
+
console.log('⚠️ ENV FILE PRIORITY (' + priorityWarnings.length + ' found)\n');
|
|
219
|
+
for (const w of priorityWarnings) {
|
|
220
|
+
console.log(' ' + w);
|
|
221
|
+
}
|
|
222
|
+
console.log('');
|
|
223
|
+
}
|
|
224
|
+
|
|
164
225
|
if (empty.length > 0) {
|
|
165
226
|
console.log('❌ EMPTY VARIABLES (' + empty.length + ' found)\n');
|
|
166
227
|
for (const key of empty) {
|
|
@@ -187,6 +248,7 @@ async function validate() {
|
|
|
187
248
|
|
|
188
249
|
const hasFailed = runtimeMismatch || missing.length > 0 || empty.length > 0 ||
|
|
189
250
|
duplicates.length > 0 || exampleMissing.length > 0 ||
|
|
251
|
+
prefixWarnings.length > 0 || priorityWarnings.length > 0 ||
|
|
190
252
|
(drift && (drift.notInstalled || drift.missing.length > 0));
|
|
191
253
|
|
|
192
254
|
if (hasFailed) {
|
|
@@ -198,7 +260,9 @@ async function validate() {
|
|
|
198
260
|
empty: empty.length,
|
|
199
261
|
duplicates: duplicates.length,
|
|
200
262
|
runtime_mismatch: !!runtimeMismatch,
|
|
201
|
-
dependency_drift: !!(drift && drift.missing.length > 0)
|
|
263
|
+
dependency_drift: !!(drift && drift.missing.length > 0),
|
|
264
|
+
prefix_warnings: prefixWarnings.length,
|
|
265
|
+
priority_warnings: priorityWarnings.length
|
|
202
266
|
});
|
|
203
267
|
await shutdown();
|
|
204
268
|
process.exit(1);
|
|
@@ -213,9 +277,4 @@ async function validate() {
|
|
|
213
277
|
}
|
|
214
278
|
}
|
|
215
279
|
|
|
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
|
|
280
|
+
validate();
|