-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
123 lines (101 loc) · 4 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
'use strict';
const fs = require('fs');
const path = require('path');
const isWsl = require('is-wsl');
const pathType = require('path-type');
const filterObj = require('filter-obj');
const camelcase = require('camelcase');
const camelcaseKeys = require('camelcase-keys');
const loadEnvFile = require('./lib/load-env-file');
const num = fs.constants;
const permissionMask = 0o777;
const windowsPermission = 0o555;
const ownerReadWrite = num.S_IRUSR | num.S_IWUSR;
const checkMode = (filepath, mask) => {
const status = fs.statSync(filepath);
if (!status.isFile()) {
throw new Error(`Filepath must be a file: ${filepath}`);
}
return status.mode & mask;
};
const assertHidden = (filepath) => {
const filename = path.basename(filepath);
if (!filename.startsWith('.')) {
throw new Error(`Filepath must be hidden. Fix: mv '${filename}' '.${filename}'`);
}
};
const assertIgnored = (filepath) => {
const failMessage = `File must be ignored by git. Fix: echo '${path.basename(filepath)}' >> .gitignore`;
let ignores;
try {
ignores = fs.readFileSync(path.join(filepath, '..', '.gitignore'), 'utf8');
}
catch (error) {
if (error.code === 'ENOENT') {
if (!pathType.dirSync(path.join(filepath, '..', '.git'))) {
return;
}
throw new Error(failMessage);
}
throw error;
}
if (!ignores.split(/\r?\n/u).includes(path.basename(filepath))) {
throw new Error(failMessage);
}
};
const isWindows = () => {
return isWsl || process.platform === 'win32';
};
const envy = (input) => {
const envPath = input || '.env';
const examplePath = envPath + '.example';
assertHidden(envPath);
if (checkMode(examplePath, num.S_IWOTH) === num.S_IWOTH) {
throw new Error(`File must not be writable by others. Fix: chmod o-w '${examplePath}'`);
}
const exampleEnv = loadEnvFile(examplePath);
const exampleEnvKeys = Object.keys(exampleEnv);
const camelizedExampleEnvKeys = Object.keys(camelcaseKeys(exampleEnv));
const exampleHasValues = Object.values(exampleEnv).some((val) => {
return val !== '';
});
if (exampleHasValues) {
throw new Error(`No values are allowed in ${examplePath}, put them in ${envPath} instead`);
}
const camelizedGlobalEnv = camelcaseKeys(process.env);
const camelizedGlobalEnvKeys = Object.keys(camelizedGlobalEnv);
// We treat env vars as case insensitive, like Windows does.
const needsEnvFile = camelizedExampleEnvKeys.some((key) => {
return !camelizedGlobalEnvKeys.includes(key);
});
if (!needsEnvFile) {
return filterObj(camelizedGlobalEnv, camelizedExampleEnvKeys);
}
if (isWindows() && checkMode(envPath, permissionMask) !== windowsPermission) {
throw new Error(`File permissions are unsafe. Make them 555 '${envPath}'`);
}
else if (!isWindows() && checkMode(envPath, permissionMask) !== ownerReadWrite) {
throw new Error(`File permissions are unsafe. Fix: chmod 600 '${envPath}'`);
}
assertIgnored(envPath);
const camelizedLocalEnv = camelcaseKeys(loadEnvFile(envPath));
const camelizedMergedEnv = {
...camelizedLocalEnv,
...camelizedGlobalEnv
};
const camelizedMergedEnvKeys = Object.keys(camelizedMergedEnv);
const camelizedMissingKeys = camelizedExampleEnvKeys.filter((key) => {
return !camelizedMergedEnv[key] || !camelizedMergedEnvKeys.includes(key);
});
if (camelizedMissingKeys.length > 0) {
const missingKeys = camelizedMissingKeys.map((camelizedMissingKey) => {
return exampleEnvKeys.find((exampleKey) => {
return camelcase(exampleKey) === camelizedMissingKey;
});
});
throw new Error(`Environment variables are missing: ${missingKeys.join(', ')}`);
}
const keepKeys = [...new Set([...Object.keys(camelizedLocalEnv), ...camelizedExampleEnvKeys])];
return filterObj(camelizedMergedEnv, keepKeys);
};
module.exports = envy;