@benshawmean/envedit 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.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # envedit
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.14. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/index.ts ADDED
@@ -0,0 +1,565 @@
1
+ import fs from "node:fs";
2
+
3
+ import { input, password, select, checkbox, confirm } from "@inquirer/prompts";
4
+ import { parse } from "dotenv";
5
+ import yargs from "yargs";
6
+ import { hideBin } from "yargs/helpers";
7
+
8
+ import { NotFoundError } from "./lib/error";
9
+ import { Configuration, EnvironmentFile, VariableType } from "./lib/config";
10
+
11
+ await yargs(hideBin(process.argv))
12
+ .scriptName("envedit")
13
+ .command(
14
+ "environment",
15
+ "Manage environments",
16
+ (yargs) => yargs
17
+ .command(
18
+ "list",
19
+ "List all environments",
20
+ () => { },
21
+ async () => {
22
+ const config = new Configuration().loadConfigurationFile();
23
+
24
+ for (const env of config.configuration.environments) {
25
+ console.dir(env);
26
+ }
27
+ }
28
+ )
29
+ .command(
30
+ "add",
31
+ "Add a new environment",
32
+ () => { },
33
+ async () => {
34
+ const config = new Configuration().loadConfigurationFile();
35
+
36
+ const name = await input({
37
+ message: "Enter the environment name:",
38
+ validate: (input) => {
39
+ if (!input) {
40
+ return "Environment name cannot be empty";
41
+ }
42
+ if (config.configuration.environments.find(env => env.name === input)) {
43
+ return "Environment name already exists";
44
+ }
45
+ return true;
46
+ },
47
+ });
48
+
49
+ const prefix = await input({
50
+ message: "Enter the environment prefix:",
51
+ });
52
+
53
+ const file = await input({
54
+ message: "Enter the environment file path:",
55
+ default: `.env.${prefix}`,
56
+ });
57
+
58
+ const isActive = await confirm({
59
+ message: "Is this environment active?",
60
+ });
61
+
62
+ config
63
+ .addEnvironment({ name, prefix, file, isActive })
64
+ .saveConfigurationFile();
65
+
66
+ const envFile = new EnvironmentFile(file).load();
67
+
68
+ if (!fs.existsSync(file)) {
69
+ const shouldClone = await confirm({
70
+ message: "Do you want to clone a pre-existing environment into the new file?",
71
+ });
72
+
73
+ if (shouldClone) {
74
+ const clonedEnv = await select({
75
+ message: "Select the environment to clone from:",
76
+ choices: config.configuration.environments.map(env => ({
77
+ name: env.name,
78
+ value: env.name,
79
+ })),
80
+ });
81
+
82
+ const selectedEnv = config.getEnvironment(clonedEnv);
83
+
84
+ if (!selectedEnv) {
85
+ throw new NotFoundError(`Environment not found: ${clonedEnv}`);
86
+ }
87
+
88
+ const selectedEnvFile = new EnvironmentFile(selectedEnv.file);
89
+
90
+ selectedEnvFile.load();
91
+
92
+ for (const [key, value] of Object.entries(selectedEnvFile.content)) {
93
+ envFile.setVariable(key, value);
94
+ }
95
+ } else {
96
+ for (const variable of config.configuration.variables) {
97
+ const request = {
98
+ message: `Enter the value for ${variable.name} in environment ${name}:`,
99
+ mask: variable.type === VariableType.Private ? "*" : undefined,
100
+ };
101
+
102
+ const value = variable.type === VariableType.Private
103
+ ? await password(request)
104
+ : await input(request);
105
+
106
+ envFile.setVariable(variable.name, value);
107
+ }
108
+ }
109
+
110
+ envFile.save();
111
+ }
112
+ }
113
+ )
114
+ .command(
115
+ "remove",
116
+ "Remove an environment",
117
+ () => { },
118
+ async () => {
119
+ const config = new Configuration().loadConfigurationFile();
120
+
121
+ const name = await select({
122
+ message: "Select the environment to remove:",
123
+ choices: config.configuration.environments.map(env => ({
124
+ name: env.name,
125
+ value: env.name,
126
+ })),
127
+ });
128
+
129
+ const selectedEnv = config.getEnvironment(name);
130
+
131
+ if (!selectedEnv) {
132
+ throw new NotFoundError(`Environment not found: ${name}`);
133
+ }
134
+
135
+ config
136
+ .removeEnvironment(name)
137
+ .saveConfigurationFile();
138
+
139
+ if (fs.existsSync(selectedEnv.file)) {
140
+ const toDelete = await confirm({
141
+ message: `Environment file found: ${selectedEnv.file}. Do you want to delete it?`
142
+ });
143
+
144
+ if (toDelete) {
145
+ fs.unlinkSync(selectedEnv.file);
146
+ }
147
+ }
148
+ }
149
+ )
150
+ .command(
151
+ "edit",
152
+ "Edit an environment",
153
+ () => { },
154
+ async () => {
155
+ const config = new Configuration().loadConfigurationFile();
156
+
157
+ const name = await select({
158
+ message: "Select the environment to edit:",
159
+ choices: config.configuration.environments.map(env => ({
160
+ name: env.name,
161
+ value: env.name,
162
+ })),
163
+ });
164
+
165
+ const selectedEnv = config.getEnvironment(name);
166
+
167
+ if (!selectedEnv) {
168
+ throw new NotFoundError(`Environment not found: ${name}`);
169
+ }
170
+
171
+ const itemsToEdit = await checkbox({
172
+ message: "Select the items to edit:",
173
+ choices: [
174
+ { name: "Name", value: "name" },
175
+ { name: "Prefix", value: "prefix" },
176
+ { name: "File", value: "file" },
177
+ ],
178
+ });
179
+
180
+ const updatedEnv = { ...selectedEnv };
181
+
182
+ if (itemsToEdit.includes("name")) {
183
+ updatedEnv.name = await input({
184
+ message: "Enter the new environment name:",
185
+ default: selectedEnv.name,
186
+ });
187
+ }
188
+
189
+ if (itemsToEdit.includes("prefix")) {
190
+ updatedEnv.prefix = await input({
191
+ message: "Enter the new environment prefix:",
192
+ default: selectedEnv.prefix,
193
+ });
194
+ }
195
+
196
+ if (itemsToEdit.includes("file")) {
197
+ updatedEnv.file = await input({
198
+ message: "Enter the new environment file path:",
199
+ default: selectedEnv.file,
200
+ });
201
+ }
202
+
203
+ config
204
+ .editEnvironment(name, updatedEnv)
205
+ .saveConfigurationFile();
206
+
207
+ if (itemsToEdit.includes("file") && fs.existsSync(selectedEnv.file)) {
208
+ const toRename = await confirm({
209
+ message: `Old environment file found: ${selectedEnv.file}. Do you want to rename it to the new file path: ${updatedEnv.file}?`
210
+ });
211
+
212
+ if (toRename) {
213
+ fs.renameSync(selectedEnv.file, updatedEnv.file);
214
+ }
215
+ }
216
+ }
217
+ )
218
+ .demandCommand(),
219
+ )
220
+ .command(
221
+ "variable",
222
+ "Manage environment variables",
223
+ (yargs) => yargs
224
+ .command(
225
+ "list",
226
+ "List all environment variables",
227
+ () => { },
228
+ async () => {
229
+ const config = new Configuration().loadConfigurationFile();
230
+
231
+ const activeEnv = config.getActiveEnvironment();
232
+
233
+ if (!activeEnv) {
234
+ throw new NotFoundError(`No active environment found. Please set an active environment using the "use" command.`);
235
+ }
236
+
237
+ const envContent = fs.readFileSync(activeEnv.file, "utf-8");
238
+
239
+ const parsedEnv = parse(envContent);
240
+
241
+ for (const [key, value] of Object.entries(parsedEnv)) {
242
+ const variableConfiguration = config.getVariable(key);
243
+ if (variableConfiguration) {
244
+ const data = {
245
+ name: key,
246
+ value: variableConfiguration.type === VariableType.Private ? value.replaceAll(/./gm, '*') : value,
247
+ type: variableConfiguration.type,
248
+ };
249
+
250
+ console.dir(data);
251
+ }
252
+ }
253
+ }
254
+ )
255
+ .command(
256
+ "add",
257
+ "Add a new environment variable",
258
+ () => { },
259
+ async () => {
260
+ const config = new Configuration().loadConfigurationFile();
261
+
262
+ const name = await input({
263
+ message: "Enter the variable name:",
264
+ validate: (input) => {
265
+ if (!input) {
266
+ return "Variable name cannot be empty";
267
+ }
268
+ if (config.getVariable(input)) {
269
+ return "Variable name already exists";
270
+ }
271
+ return true;
272
+ },
273
+ });
274
+
275
+ const type = await select({
276
+ message: "Select the variable type:",
277
+ choices: [
278
+ { name: "Public", value: VariableType.Public },
279
+ { name: "Private", value: VariableType.Private },
280
+ ],
281
+ });
282
+
283
+ config
284
+ .addVariable({ name, type })
285
+ .saveConfigurationFile();
286
+
287
+ const shouldGloballySet = await confirm({
288
+ message: "Do you want to set this variable with a global value for all environments?",
289
+ });
290
+
291
+ const valuesToSet = config.configuration.environments.map(env => ({
292
+ envName: env.name,
293
+ value: '',
294
+ }));
295
+
296
+ if (shouldGloballySet) {
297
+ const request = {
298
+ message: `Enter the global value for ${name}:`,
299
+ mask: type === VariableType.Private ? "*" : undefined,
300
+ };
301
+
302
+ const globalValue = type === VariableType.Private
303
+ ? await password(request)
304
+ : await input(request);
305
+
306
+ valuesToSet.forEach((envValue) => { envValue.value = globalValue });
307
+ } else {
308
+ for (const envValue of valuesToSet) {
309
+ const request = {
310
+ message: `Enter the value for ${name} in environment ${envValue.envName}:`,
311
+ mask: type === VariableType.Private ? "*" : undefined,
312
+ };
313
+
314
+ envValue.value = type === VariableType.Private
315
+ ? await password(request)
316
+ : await input(request);
317
+ }
318
+ }
319
+
320
+ for (const envValue of valuesToSet) {
321
+ const env = config.getEnvironment(envValue.envName);
322
+
323
+ if (!env) {
324
+ throw new NotFoundError(`Environment not found: ${envValue.envName}`);
325
+ }
326
+
327
+ EnvironmentFile.quickUpdate(env.file, name, envValue.value);
328
+ }
329
+ }
330
+ )
331
+ .command(
332
+ "remove",
333
+ "Remove an environment variable",
334
+ () => { },
335
+ async () => {
336
+ const config = new Configuration().loadConfigurationFile();
337
+
338
+ const name = await select({
339
+ message: "Select the variable to remove:",
340
+ choices: config.configuration.variables.map(variable => ({
341
+ name: variable.name,
342
+ value: variable.name,
343
+ })),
344
+ });
345
+
346
+ if (!config.getVariable(name)) {
347
+ throw new NotFoundError(`Variable not found: ${name}`);
348
+ }
349
+
350
+ config
351
+ .removeVariable(name)
352
+ .saveConfigurationFile();
353
+
354
+ for (const env of config.configuration.environments) {
355
+ const envFile = new EnvironmentFile(env.file).load();
356
+
357
+ if (envFile.content[name] !== undefined) {
358
+ envFile.removeVariable(name).save();
359
+ }
360
+ }
361
+ }
362
+ )
363
+ .command(
364
+ "edit",
365
+ "Edit an environment variable",
366
+ () => { },
367
+ async () => {
368
+ const config = new Configuration().loadConfigurationFile();
369
+
370
+ const name = await select({
371
+ message: "Select the variable to edit:",
372
+ choices: config.configuration.variables.map(variable => ({
373
+ name: variable.name,
374
+ value: variable.name,
375
+ })),
376
+ });
377
+
378
+ const variableConfiguration = config.getVariable(name);
379
+
380
+ if (!variableConfiguration) {
381
+ throw new NotFoundError(`Variable not found: ${name}`);
382
+ }
383
+
384
+ const isEditingConfig = await select({
385
+ message: "Are you editing the variable configuration (name/type) or the variable value?",
386
+ choices: [
387
+ { name: "Configuration", value: true },
388
+ { name: "Value", value: false },
389
+ ],
390
+ });
391
+
392
+ if (isEditingConfig) {
393
+ const itemsToEdit = await checkbox({
394
+ message: "Select the items to edit:",
395
+ choices: [
396
+ { name: "Name", value: "name" },
397
+ { name: "Type", value: "type" },
398
+ ],
399
+ });
400
+
401
+ const updatedVariable = { ...variableConfiguration };
402
+
403
+ if (itemsToEdit.includes("name")) {
404
+ updatedVariable.name = await input({
405
+ message: "Enter the new variable name:",
406
+ default: variableConfiguration.name,
407
+ });
408
+
409
+ for (const env of config.configuration.environments) {
410
+ const envFile = new EnvironmentFile(env.file).load();
411
+
412
+ if (envFile.content[name] !== undefined) {
413
+ const value = envFile.content[name];
414
+ envFile.removeVariable(name);
415
+ envFile.setVariable(updatedVariable.name, value);
416
+ envFile.save();
417
+ }
418
+ }
419
+ }
420
+
421
+ if (itemsToEdit.includes("type")) {
422
+ updatedVariable.type = await select({
423
+ message: "Select the new variable type:",
424
+ choices: [
425
+ { name: "Public", value: VariableType.Public },
426
+ { name: "Private", value: VariableType.Private },
427
+ ],
428
+ default: variableConfiguration.type,
429
+ });
430
+ }
431
+
432
+ config
433
+ .editVariable(name, updatedVariable)
434
+ .saveConfigurationFile();
435
+ } else {
436
+ let valuesToSet = config.configuration.environments.map(env => ({
437
+ envName: env.name,
438
+ value: '',
439
+ }));
440
+
441
+ const shouldGloballySet = await confirm({
442
+ message: "Do you want to set this variable for all environments?",
443
+ });
444
+
445
+ if (shouldGloballySet) {
446
+ const request = {
447
+ message: `Enter the global value for ${variableConfiguration.name}:`,
448
+ mask: variableConfiguration.type === VariableType.Private ? "*" : undefined,
449
+ };
450
+
451
+ const globalValue = variableConfiguration.type === VariableType.Private
452
+ ? await password(request)
453
+ : await input(request);
454
+
455
+ valuesToSet.forEach((envValue) => { envValue.value = globalValue });
456
+ } else {
457
+ for (const envValue of valuesToSet) {
458
+ const request = {
459
+ message: `Enter the value for ${variableConfiguration.name} in environment ${envValue.envName}:`,
460
+ mask: variableConfiguration.type === VariableType.Private ? "*" : undefined,
461
+ };
462
+
463
+ envValue.value = variableConfiguration.type === VariableType.Private
464
+ ? await password(request)
465
+ : await input(request);
466
+ }
467
+ }
468
+
469
+ for (const envValue of valuesToSet) {
470
+ const env = config.getEnvironment(envValue.envName);
471
+
472
+ if (!env) {
473
+ throw new NotFoundError(`Environment not found: ${envValue.envName}`);
474
+ }
475
+
476
+ EnvironmentFile.quickUpdate(env.file, variableConfiguration.name, envValue.value);
477
+ }
478
+ }
479
+ }
480
+ )
481
+ .demandCommand(),
482
+ )
483
+ .command(
484
+ "setup",
485
+ "Setup the environment",
486
+ () => { },
487
+ async () => {
488
+ new Configuration().loadConfigurationFile();
489
+
490
+ if (fs.existsSync('.git')) {
491
+ const gitignorePath = '.gitignore';
492
+
493
+ if (!fs.existsSync(gitignorePath)) {
494
+ const toCreate = await confirm({
495
+ message: `Git Repository without a git ignore file not found: ${gitignorePath}. Do you want to create it?`
496
+ });
497
+
498
+ if (!toCreate) {
499
+ console.error(`Git Repository without a git ignore file not found: ${gitignorePath}`);
500
+ process.exit(1);
501
+ }
502
+
503
+ fs.writeFileSync(gitignorePath, "");
504
+ }
505
+
506
+ const expectedItems = ['.env', '.env.*', 'env.config.yaml'];
507
+
508
+ for (const item of expectedItems) {
509
+ if (!fs.readFileSync(gitignorePath, "utf-8").includes(item)) {
510
+ const toAdd = await confirm({
511
+ message: `The item ${item} is not in the git ignore file. Do you want to add it?`
512
+ });
513
+
514
+ if (!toAdd) {
515
+ console.error(`The item ${item} is not in the git ignore file.`);
516
+ process.exit(1);
517
+ }
518
+
519
+ fs.appendFileSync(gitignorePath, `\n${item}`);
520
+ }
521
+ }
522
+ }
523
+ }
524
+ )
525
+ .command(
526
+ "use",
527
+ "Set the current environment",
528
+ () => { },
529
+ async () => {
530
+ const config = new Configuration().loadConfigurationFile();
531
+
532
+ const name = await select({
533
+ message: "Select the environment to use",
534
+ choices: config.configuration.environments.map(env => ({
535
+ name: env.name + (env.isActive ? " (active)" : ""),
536
+ value: env.name,
537
+ })),
538
+ default: config.getActiveEnvironment()?.name,
539
+ });
540
+
541
+ const selectedEnv = config.getEnvironment(name);
542
+
543
+ if (!selectedEnv) {
544
+ throw new NotFoundError(`Environment not found: ${name}`);
545
+ }
546
+
547
+ if (!selectedEnv.isActive) {
548
+ for (const env of config.configuration.environments) {
549
+ env.isActive = env.name === name;
550
+ }
551
+
552
+ config.saveConfigurationFile();
553
+ }
554
+
555
+ const envFilePath = selectedEnv.file;
556
+
557
+ if (fs.existsSync('.env')) {
558
+ fs.unlinkSync('.env');
559
+ }
560
+
561
+ fs.symlinkSync(envFilePath, '.env', 'file');
562
+ }
563
+ )
564
+ .demandCommand()
565
+ .parseAsync();
package/lib/config.ts ADDED
@@ -0,0 +1,222 @@
1
+ import fs from 'fs';
2
+
3
+ import { parse } from 'dotenv';
4
+
5
+ import { NotFoundError } from './error';
6
+
7
+ export enum VariableType {
8
+ Public = 'public',
9
+ Private = 'private',
10
+ }
11
+
12
+ export interface VariableConfiguration {
13
+ name: string;
14
+ type: VariableType;
15
+ }
16
+
17
+ export interface EnviornmentConfiguration {
18
+ name: string;
19
+ prefix: string;
20
+ file: string;
21
+ isActive: boolean;
22
+ }
23
+
24
+ export interface GeneralConfiguration {
25
+ environments: EnviornmentConfiguration[];
26
+ variables: VariableConfiguration[];
27
+ }
28
+
29
+ export class Configuration {
30
+ private config: GeneralConfiguration = { environments: [], variables: [] };
31
+ private readonly configFilePath: string = 'env.config.yaml';
32
+ constructor(config: Partial<GeneralConfiguration> = {}) {
33
+ if (config.environments) {
34
+ this.config.environments = config.environments;
35
+ }
36
+
37
+ if (config.variables) {
38
+ this.config.variables = config.variables;
39
+ }
40
+ }
41
+ public get configuration(): GeneralConfiguration {
42
+ return {
43
+ environments: this.config.environments || [],
44
+ variables: this.config.variables || [],
45
+ };
46
+ }
47
+
48
+ public loadConfigurationFile() {
49
+ if (!fs.existsSync(this.configFilePath)) {
50
+ console.log(`Configuration file not found: ${this.configFilePath}`);
51
+ console.log(`Creating a new configuration file`);
52
+
53
+ const config = new Configuration();
54
+
55
+ if (fs.existsSync('.env')) {
56
+ config.importFromEnv(fs.readFileSync('.env', "utf-8"));
57
+ }
58
+
59
+ this.saveConfigurationFile();
60
+ }
61
+
62
+ const configContent = Bun.YAML.parse(fs.readFileSync(this.configFilePath, "utf-8")) as GeneralConfiguration;
63
+
64
+ this.config.environments = configContent.environments || [];
65
+ this.config.variables = configContent.variables || [];
66
+
67
+ return this;
68
+ }
69
+
70
+ public saveConfigurationFile() {
71
+ fs.writeFileSync(this.configFilePath, Bun.YAML.stringify(this.configuration, null, 2));
72
+
73
+ return this;
74
+ }
75
+
76
+ public setActiveEnvironment(name: string) {
77
+ const index = this.config.environments!.findIndex(e => e.name === name);
78
+ if (index === -1) {
79
+ throw new NotFoundError(`Environment not found: ${name}`);
80
+ }
81
+
82
+ this.config.environments.forEach((env, i) => {
83
+ env.isActive = i === index;
84
+ });
85
+
86
+ return this;
87
+ }
88
+
89
+ public getActiveEnvironment(): EnviornmentConfiguration | undefined {
90
+ return this.config.environments.find(env => env.isActive);
91
+ }
92
+
93
+ public addEnvironment(env: EnviornmentConfiguration) {
94
+ this.config.environments.push(env);
95
+ return this;
96
+ }
97
+
98
+ public editEnvironment(name: string, env: Partial<EnviornmentConfiguration>) {
99
+ const index = this.config.environments!.findIndex(e => e.name === name);
100
+ if (index === -1) {
101
+ throw new NotFoundError(`Environment not found: ${name}`);
102
+ }
103
+
104
+ this.config.environments[index] = {
105
+ name: env.name || this.config.environments[index]!.name,
106
+ prefix: env.prefix || this.config.environments[index]!.prefix,
107
+ file: env.file || this.config.environments[index]!.file,
108
+ isActive: env.isActive !== undefined ? env.isActive : this.config.environments[index]!.isActive,
109
+ };
110
+ return this;
111
+ }
112
+
113
+ public removeEnvironment(name: string) {
114
+ const index = this.config.environments!.findIndex(e => e.name === name);
115
+ if (index === -1) {
116
+ throw new NotFoundError(`Environment not found: ${name}`);
117
+ }
118
+
119
+ this.config.environments.splice(index, 1);
120
+ return this;
121
+ }
122
+
123
+ public getEnvironment(name: string): EnviornmentConfiguration | undefined {
124
+ return this.config.environments.find(env => env.name === name);
125
+ }
126
+
127
+ public addVariable(variable: VariableConfiguration) {
128
+ this.config.variables.push(variable);
129
+ return this;
130
+ }
131
+
132
+ public editVariable(name: string, variable: Partial<VariableConfiguration>) {
133
+ if (!this.config.variables.find(v => v.name === name)) {
134
+ throw new NotFoundError(`Variable not found: ${name}`);
135
+ }
136
+
137
+ this.config.variables = this.config.variables.map(v => v.name === name ? { ...v, ...variable } : v);
138
+ return this;
139
+ }
140
+
141
+ public removeVariable(name: string) {
142
+ if (!this.config.variables.find(v => v.name === name)) {
143
+ throw new NotFoundError(`Variable not found: ${name}`);
144
+ }
145
+
146
+ this.config.variables = this.config.variables.filter(v => v.name !== name);
147
+ return this;
148
+ }
149
+
150
+ public getVariable(name: string): VariableConfiguration | undefined {
151
+ return this.config.variables.find(v => v.name === name);
152
+ }
153
+
154
+ public importFromEnv(content: string): Configuration {
155
+ const config = new Configuration();
156
+
157
+ const env = parse(content);
158
+
159
+ for (const [key, _] of Object.entries(env)) {
160
+ if (key.includes('PRIVATE')) {
161
+ config.addVariable({
162
+ name: key,
163
+ type: VariableType.Private,
164
+ });
165
+ } else {
166
+ config.addVariable({
167
+ name: key,
168
+ type: VariableType.Public,
169
+ });
170
+ }
171
+ }
172
+
173
+ return config;
174
+ }
175
+ }
176
+
177
+ export class EnvironmentFile {
178
+ public content: Record<string, string> = {};
179
+ public rawContent: string = '';
180
+ constructor(public readonly path: string) { }
181
+
182
+ public load() {
183
+ if (!fs.existsSync(this.path)) {
184
+ fs.writeFileSync(this.path, '');
185
+ }
186
+
187
+ this.rawContent = fs.readFileSync(this.path, "utf-8");
188
+ this.content = parse(this.rawContent);
189
+
190
+ return this;
191
+ }
192
+
193
+ public save() {
194
+ const lines: string[] = [];
195
+ for (const [key, value] of Object.entries(this.content)) {
196
+ lines.push(`${key}='${value}'`);
197
+ }
198
+ this.rawContent = lines.join('\n');
199
+ fs.writeFileSync(this.path, this.rawContent);
200
+
201
+ return this;
202
+ }
203
+
204
+ public setVariable(name: string, value: string) {
205
+ this.content[name] = value;
206
+
207
+ return this;
208
+ }
209
+
210
+ public removeVariable(name: string) {
211
+ delete this.content[name];
212
+
213
+ return this;
214
+ }
215
+
216
+ public static quickUpdate(path: string, name: string, value: string) {
217
+ const envFile = new EnvironmentFile(path).load();
218
+ envFile.setVariable(name, value).save();
219
+
220
+ return envFile;
221
+ }
222
+ }
package/lib/error.ts ADDED
@@ -0,0 +1,6 @@
1
+ export class NotFoundError extends Error {
2
+ constructor(message?: string) {
3
+ super(message);
4
+ this.name = 'NotFoundError';
5
+ }
6
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@benshawmean/envedit",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "version": "1.0.0",
6
+ "bin": {
7
+ "envedit": "index.ts"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "provenance": true,
12
+ "tag": "latest"
13
+ },
14
+ "devDependencies": {
15
+ "@types/bun": "latest",
16
+ "@types/yargs": "^17.0.35"
17
+ },
18
+ "peerDependencies": {
19
+ "typescript": "^5"
20
+ },
21
+ "dependencies": {
22
+ "@inquirer/prompts": "^8.5.2",
23
+ "dotenv": "^17.4.2",
24
+ "yargs": "^18.1.0"
25
+ }
26
+ }
File without changes
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+ "types": ["bun"],
11
+
12
+ // Bundler mode
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "verbatimModuleSyntax": true,
16
+ "noEmit": true,
17
+
18
+ // Best practices
19
+ "strict": true,
20
+ "skipLibCheck": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "noUncheckedIndexedAccess": true,
23
+ "noImplicitOverride": true,
24
+
25
+ // Some stricter flags (disabled by default)
26
+ "noUnusedLocals": false,
27
+ "noUnusedParameters": false,
28
+ "noPropertyAccessFromIndexSignature": false
29
+ }
30
+ }