easybuild-nox 1.0.0

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.
@@ -0,0 +1,302 @@
1
+ const { Command } = require('commander');
2
+ const inquirer = require('inquirer');
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const crypto = require('crypto');
6
+ const logger = require('../utils/logger');
7
+
8
+ const envCommand = new Command('env')
9
+ .description('Manage environment variables')
10
+ .option('-a, --action <action>', 'Action (list, get, set, generate, validate, encrypt, decrypt)')
11
+ .option('-e, --env <environment>', 'Environment file (.env, .env.local, .env.production)')
12
+ .action(async (options) => {
13
+ logger.header('🔐 Environment Manager');
14
+
15
+ try {
16
+ const action = options.action || 'list';
17
+
18
+ const actions = {
19
+ list: envList,
20
+ get: envGet,
21
+ set: envSet,
22
+ generate: envGenerate,
23
+ validate: envValidate,
24
+ encrypt: envEncrypt,
25
+ decrypt: envDecrypt,
26
+ };
27
+
28
+ const actionFn = actions[action];
29
+ if (!actionFn) {
30
+ logger.error(`Unknown action: ${action}`);
31
+ process.exit(1);
32
+ }
33
+
34
+ await actionFn(options);
35
+ } catch (error) {
36
+ logger.error(`Environment operation failed: ${error.message}`);
37
+ process.exit(1);
38
+ }
39
+ });
40
+
41
+ async function envList(options) {
42
+ const envFile = options.env || '.env';
43
+ const envPath = path.join(process.cwd(), envFile);
44
+
45
+ if (!(await fs.pathExists(envPath))) {
46
+ logger.warning(`No ${envFile} found`);
47
+ return;
48
+ }
49
+
50
+ const content = await fs.readFile(envPath, 'utf8');
51
+ const lines = content.split('\n').filter(line => line.trim() && !line.startsWith('#'));
52
+
53
+ logger.section(`Environment Variables (${envFile})`);
54
+
55
+ lines.forEach(line => {
56
+ const [key, ...valueParts] = line.split('=');
57
+ const value = valueParts.join('=');
58
+ const masked = value.length > 20 ? value.substring(0, 10) + '...' + value.substring(value.length - 5) : value;
59
+ logger.dim(` ${key} = ${masked}`);
60
+ });
61
+
62
+ logger.info('');
63
+ logger.info(`Total: ${lines.length} variables`);
64
+ }
65
+
66
+ async function envGet(options) {
67
+ const key = options.key;
68
+ if (!key) {
69
+ const keyAnswer = await inquirer.prompt([
70
+ { type: 'input', name: 'key', message: 'Variable name:' },
71
+ ]);
72
+ key = keyAnswer.key;
73
+ }
74
+
75
+ const envFile = options.env || '.env';
76
+ const envPath = path.join(process.cwd(), envFile);
77
+
78
+ if (!(await fs.pathExists(envPath))) {
79
+ logger.warning(`No ${envFile} found`);
80
+ return;
81
+ }
82
+
83
+ const content = await fs.readFile(envPath, 'utf8');
84
+ const lines = content.split('\n');
85
+
86
+ for (const line of lines) {
87
+ if (line.startsWith(`${key}=`)) {
88
+ const value = line.split('=').slice(1).join('=');
89
+ console.log(value);
90
+ return;
91
+ }
92
+ }
93
+
94
+ logger.warning(`Variable "${key}" not found`);
95
+ }
96
+
97
+ async function envSet(options) {
98
+ let key = options.key;
99
+ let value = options.value;
100
+
101
+ if (!key) {
102
+ const answers = await inquirer.prompt([
103
+ { type: 'input', name: 'key', message: 'Variable name:' },
104
+ { type: 'input', name: 'value', message: 'Value:' },
105
+ ]);
106
+ key = answers.key;
107
+ value = answers.value;
108
+ }
109
+
110
+ const envFile = options.env || '.env';
111
+ const envPath = path.join(process.cwd(), envFile);
112
+
113
+ let content = '';
114
+ if (await fs.pathExists(envPath)) {
115
+ content = await fs.readFile(envPath, 'utf8');
116
+ }
117
+
118
+ // Check if key already exists
119
+ const lines = content.split('\n');
120
+ const keyIndex = lines.findIndex(line => line.startsWith(`${key}=`));
121
+
122
+ if (keyIndex >= 0) {
123
+ lines[keyIndex] = `${key}=${value}`;
124
+ logger.success(`Updated ${key}`);
125
+ } else {
126
+ lines.push(`${key}=${value}`);
127
+ logger.success(`Added ${key}`);
128
+ }
129
+
130
+ await fs.writeFile(envPath, lines.join('\n'));
131
+ }
132
+
133
+ async function envGenerate(options) {
134
+ logger.info('Generating secure environment variables...');
135
+
136
+ const templates = {
137
+ database: {
138
+ DB_HOST: 'localhost',
139
+ DB_PORT: '5432',
140
+ DB_NAME: 'myapp',
141
+ DB_USER: 'postgres',
142
+ DB_PASSWORD: generateSecurePassword(32),
143
+ },
144
+ jwt: {
145
+ JWT_SECRET: generateSecurePassword(64),
146
+ JWT_EXPIRES_IN: '7d',
147
+ },
148
+ api: {
149
+ API_KEY: generateApiKey(),
150
+ API_SECRET: generateSecurePassword(48),
151
+ },
152
+ redis: {
153
+ REDIS_URL: 'redis://localhost:6379',
154
+ },
155
+ mail: {
156
+ SMTP_HOST: 'smtp.gmail.com',
157
+ SMTP_PORT: '587',
158
+ SMTP_USER: 'your@email.com',
159
+ SMTP_PASS: generateSecurePassword(24),
160
+ },
161
+ aws: {
162
+ AWS_ACCESS_KEY_ID: 'YOUR_ACCESS_KEY',
163
+ AWS_SECRET_ACCESS_KEY: generateSecurePassword(40),
164
+ AWS_REGION: 'us-east-1',
165
+ },
166
+ };
167
+
168
+ const templateAnswer = await inquirer.prompt([
169
+ {
170
+ type: 'checkbox',
171
+ name: 'templates',
172
+ message: 'Select templates to generate:',
173
+ choices: Object.keys(templates).map(t => ({
174
+ name: t.charAt(0).toUpperCase() + t.slice(1),
175
+ value: t,
176
+ })),
177
+ },
178
+ ]);
179
+
180
+ let content = '# Generated by easy-build\n';
181
+ content += `# Generated at: ${new Date().toISOString()}\n\n`;
182
+
183
+ for (const template of templateAnswer.templates) {
184
+ content += `# ${template.charAt(0).toUpperCase() + template.slice(1)} Configuration\n`;
185
+ for (const [key, value] of Object.entries(templates[template])) {
186
+ content += `${key}=${value}\n`;
187
+ }
188
+ content += '\n';
189
+ }
190
+
191
+ const envPath = path.join(process.cwd(), '.env.generated');
192
+ await fs.writeFile(envPath, content);
193
+ logger.success(`Generated ${envPath}`);
194
+ logger.warning('Review and rename to .env before using!');
195
+ }
196
+
197
+ async function envValidate(options) {
198
+ const envFile = options.env || '.env';
199
+ const envPath = path.join(process.cwd(), envFile);
200
+
201
+ if (!(await fs.pathExists(envPath))) {
202
+ logger.warning(`No ${envFile} found`);
203
+ return;
204
+ }
205
+
206
+ const content = await fs.readFile(envPath, 'utf8');
207
+ const lines = content.split('\n');
208
+ const issues = [];
209
+
210
+ lines.forEach((line, index) => {
211
+ if (!line.trim() || line.startsWith('#')) return;
212
+
213
+ if (!line.includes('=')) {
214
+ issues.push(`Line ${index + 1}: Missing equals sign`);
215
+ }
216
+
217
+ const [key] = line.split('=');
218
+ if (key.includes(' ')) {
219
+ issues.push(`Line ${index + 1}: Key contains spaces`);
220
+ }
221
+
222
+ if (key !== key.toUpperCase()) {
223
+ issues.push(`Line ${index + 1}: Key should be uppercase`);
224
+ }
225
+ });
226
+
227
+ if (issues.length === 0) {
228
+ logger.success('Environment file is valid!');
229
+ } else {
230
+ logger.warning(`Found ${issues.length} issues:`);
231
+ issues.forEach(issue => logger.dim(` ${issue}`));
232
+ }
233
+ }
234
+
235
+ async function envEncrypt(options) {
236
+ const envFile = options.env || '.env';
237
+ const envPath = path.join(process.cwd(), envFile);
238
+
239
+ if (!(await fs.pathExists(envPath))) {
240
+ logger.warning(`No ${envFile} found`);
241
+ return;
242
+ }
243
+
244
+ const keyAnswer = await inquirer.prompt([
245
+ { type: 'password', name: 'key', message: 'Encryption key:', mask: '*' },
246
+ ]);
247
+
248
+ const content = await fs.readFile(envPath, 'utf8');
249
+ const encrypted = encrypt(content, keyAnswer.key);
250
+
251
+ const encryptedPath = `${envPath}.encrypted`;
252
+ await fs.writeFile(encryptedPath, encrypted);
253
+ logger.success(`Encrypted to ${encryptedPath}`);
254
+ }
255
+
256
+ async function envDecrypt(options) {
257
+ const envFile = options.env || '.env';
258
+ const encryptedPath = `${path.join(process.cwd(), envFile)}.encrypted`;
259
+
260
+ if (!(await fs.pathExists(encryptedPath))) {
261
+ logger.warning(`No ${encryptedPath} found`);
262
+ return;
263
+ }
264
+
265
+ const keyAnswer = await inquirer.prompt([
266
+ { type: 'password', name: 'key', message: 'Decryption key:', mask: '*' },
267
+ ]);
268
+
269
+ const encrypted = await fs.readFile(encryptedPath, 'utf8');
270
+ try {
271
+ const decrypted = decrypt(encrypted, keyAnswer.key);
272
+ const decryptedPath = `${envPath}.decrypted`;
273
+ await fs.writeFile(decryptedPath, decrypted);
274
+ logger.success(`Decrypted to ${decryptedPath}`);
275
+ } catch (error) {
276
+ logger.error('Decryption failed - incorrect key?');
277
+ }
278
+ }
279
+
280
+ function generateSecurePassword(length = 32) {
281
+ return crypto.randomBytes(length).toString('base64').substring(0, length);
282
+ }
283
+
284
+ function generateApiKey() {
285
+ return `ak_${crypto.randomBytes(32).toString('hex')}`;
286
+ }
287
+
288
+ function encrypt(text, key) {
289
+ const cipher = crypto.createCipher('aes-256-cbc', key);
290
+ let encrypted = cipher.update(text, 'utf8', 'hex');
291
+ encrypted += cipher.final('hex');
292
+ return encrypted;
293
+ }
294
+
295
+ function decrypt(text, key) {
296
+ const decipher = crypto.createDecipher('aes-256-cbc', key);
297
+ let decrypted = decipher.update(text, 'hex', 'utf8');
298
+ decrypted += decipher.final('utf8');
299
+ return decrypted;
300
+ }
301
+
302
+ module.exports = envCommand;