@benshawmean/envedit 1.3.0 → 1.3.2-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.
@@ -1,4 +1,8 @@
1
- import fs from "node:fs";
1
+ import {
2
+ existsSync as exists,
3
+ unlinkSync as unlink,
4
+ symlinkSync as symlink,
5
+ } from "node:fs";
2
6
 
3
7
  import { confirm, input, password, select } from "@inquirer/prompts";
4
8
 
@@ -11,7 +15,7 @@ export default {
11
15
  describe: 'Add a new environment',
12
16
  builder: (yargs) => yargs,
13
17
  handler: async (argv) => {
14
- const config = new Configuration().load();
18
+ const config = new Configuration();
15
19
  const logger = new Logger(argv.level);
16
20
 
17
21
  const name = await input({
@@ -20,7 +24,7 @@ export default {
20
24
  if (!input) {
21
25
  return "Environment name cannot be empty";
22
26
  }
23
- if (config.getEnvironment(input)) {
27
+ if (config.envs.has(input)) {
24
28
  return "Environment name already exists";
25
29
  }
26
30
  return true;
@@ -38,20 +42,17 @@ export default {
38
42
 
39
43
  const isActive = await confirm({
40
44
  message: "Is this environment active?",
45
+ default: !config.active,
41
46
  });
42
47
 
43
- logger
44
- .debug(`Adding new environment "${name}" with prefix "${prefix}" and file: "${file}"`);
48
+ logger.debug(`Adding new environment "${name}" with prefix "${prefix}" and file: "${file}"`);
45
49
 
46
- config
47
- .addEnvironment({ name, prefix, file, isActive })
48
- .save();
50
+ config.envs.set(name, { name, prefix, file, isActive });
51
+ config.save();
52
+
53
+ const envFile = new EnvironmentFile(file);
49
54
 
50
- const envFile = new EnvironmentFile(file).load();
51
-
52
- const valuesToSet: Array<[string, string]> = []
53
-
54
- if (!fs.existsSync(file)) {
55
+ if (!exists(file)) {
55
56
  logger.debug(`Environment file not found: ${file}`);
56
57
 
57
58
  const shouldClone = await confirm({
@@ -63,25 +64,22 @@ export default {
63
64
 
64
65
  const clonedEnv = await select({
65
66
  message: "Select the environment to clone from:",
66
- choices: config.configuration.environments.map(env => ({
67
- name: env.name,
68
- value: env.name,
69
- })),
67
+ choices: Array.from(config.envs.keys()),
70
68
  });
71
69
 
72
- const selectedEnv = config.getEnvironment(clonedEnv);
70
+ const selectedEnv = config.envs.get(clonedEnv);
73
71
 
74
72
  if (!selectedEnv) {
75
73
  throw new NotFoundError(`Environment not found: ${clonedEnv}`);
76
74
  }
77
75
 
78
- const selectedEnvFile = new EnvironmentFile(selectedEnv.file).load();
76
+ const envSpecificFile = new EnvironmentFile(selectedEnv.file);
79
77
 
80
- for (const [key, value] of Object.entries(selectedEnvFile.content)) {
81
- valuesToSet.push([key, value]);
78
+ for (const [key, value] of Object.entries(envSpecificFile.vars)) {
79
+ envFile.vars.set(key, value);
82
80
  }
83
81
  } else {
84
- for (const variable of config.configuration.variables) {
82
+ for (const [_, variable] of config.vars) {
85
83
  const request = {
86
84
  message: `Enter the value for ${variable.name} in environment ${name}:`,
87
85
  mask: variable.type === VariableType.Private ? "*" : undefined,
@@ -94,17 +92,22 @@ export default {
94
92
  logger
95
93
  .debug(`Setting variable "${variable.name}" with value "${value}" in environment "${name}"`);
96
94
 
97
- valuesToSet.push([variable.name, value]);
95
+ envFile.vars.set(variable.name, value);
98
96
  }
99
97
  }
100
98
 
101
- for (const [key, value] of valuesToSet) {
102
- logger
103
- .debug(`Setting variable "${key}" with value "${value}" in environment file: ${file}`);
104
- envFile.setVariable(key, value);
99
+ envFile.save();
100
+ }
101
+
102
+ if (isActive) {
103
+ config.active = name;
104
+
105
+ if (exists('.env')) {
106
+ unlink('.env');
105
107
  }
106
108
 
107
- envFile.save();
109
+ const properEnv = config.envs.get(name)!;
110
+ symlink(properEnv.file, '.env', 'file');
108
111
  }
109
112
  }
110
113
  } satisfies CommandModule;
@@ -1,6 +1,9 @@
1
- import fs from "node:fs";
1
+ import {
2
+ renameSync as rename,
3
+ existsSync as exists,
4
+ } from "node:fs";
2
5
 
3
- import { confirm, input, select, checkbox } from "@inquirer/prompts";
6
+ import { input, select, checkbox } from "@inquirer/prompts";
4
7
 
5
8
  import { Configuration } from "#lib/config";
6
9
  import { NotFoundError } from "#lib/error";
@@ -11,18 +14,15 @@ export default {
11
14
  describe: 'Edit an environment',
12
15
  builder: (yargs) => yargs,
13
16
  handler: async (argv) => {
14
- const config = new Configuration().load();
17
+ const config = new Configuration();
15
18
  const logger = new Logger(argv.level);
16
19
 
17
20
  const name = await select({
18
21
  message: "Select the environment to edit:",
19
- choices: config.configuration.environments.map(env => ({
20
- name: env.name,
21
- value: env.name,
22
- })),
22
+ choices: Array.from(config.envs.keys()),
23
23
  });
24
24
 
25
- const selectedEnv = config.getEnvironment(name);
25
+ const selectedEnv = config.envs.get(name);
26
26
 
27
27
  if (!selectedEnv) {
28
28
  throw new NotFoundError(`Environment not found: ${name}`);
@@ -63,20 +63,12 @@ export default {
63
63
  });
64
64
  }
65
65
 
66
- config
67
- .editEnvironment(name, updatedEnv)
68
- .save();
66
+ config.envs.set(name, updatedEnv);
67
+ config.save();
69
68
 
70
- if (itemsToEdit.includes("file") && fs.existsSync(selectedEnv.file)) {
71
- logger.debug(`Old environment file found: ${selectedEnv.file}`);
72
- const toRename = await confirm({
73
- message: `Old environment file found: ${selectedEnv.file}. Do you want to rename it to the new file path: ${updatedEnv.file}?`
74
- });
75
-
76
- if (toRename) {
77
- logger.debug(`Renaming environment file from "${selectedEnv.file}" to "${updatedEnv.file}"`);
78
- fs.renameSync(selectedEnv.file, updatedEnv.file);
79
- }
69
+ if (itemsToEdit.includes("file") && exists(selectedEnv.file)) {
70
+ logger.debug(`Renaming environment file from "${selectedEnv.file}" to "${updatedEnv.file}"`);
71
+ rename(selectedEnv.file, updatedEnv.file);
80
72
  }
81
73
  }
82
74
  } satisfies CommandModule;
@@ -6,13 +6,11 @@ export default {
6
6
  describe: 'List all environments',
7
7
  builder: (yargs) => yargs,
8
8
  handler: async (argv) => {
9
- const config = new Configuration().load();
9
+ const config = new Configuration();
10
10
  const logger = new Logger(argv.level);
11
11
 
12
12
  logger.debug(`Listing all environments`);
13
13
 
14
- console.log(
15
- JSON.stringify(config.configuration.environments, null, 2)
16
- )
14
+ console.log(JSON.stringify(Array.from(config.envs.values()), null, 2));
17
15
  }
18
16
  } satisfies CommandModule;
@@ -1,4 +1,7 @@
1
- import fs from "node:fs";
1
+ import {
2
+ existsSync as exists,
3
+ unlinkSync as unlink,
4
+ } from "node:fs";
2
5
 
3
6
  import { confirm, select } from "@inquirer/prompts";
4
7
 
@@ -11,18 +14,15 @@ export default {
11
14
  describe: 'Remove an environment',
12
15
  builder: (yargs) => yargs,
13
16
  handler: async (argv) => {
14
- const config = new Configuration().load();
17
+ const config = new Configuration();
15
18
  const logger = new Logger(argv.level);
16
19
 
17
20
  const name = await select({
18
21
  message: "Select the environment to remove:",
19
- choices: config.configuration.environments.map(env => ({
20
- name: env.name,
21
- value: env.name,
22
- })),
22
+ choices: Array.from(config.envs.keys()),
23
23
  });
24
24
 
25
- const selectedEnv = config.getEnvironment(name);
25
+ const selectedEnv = config.envs.get(name);
26
26
 
27
27
  if (!selectedEnv) {
28
28
  throw new NotFoundError(`Environment not found: ${name}`);
@@ -30,18 +30,17 @@ export default {
30
30
 
31
31
  logger.debug(`Removing environment "${name}" with file: "${selectedEnv.file}"`);
32
32
 
33
- config
34
- .removeEnvironment(name)
35
- .save();
33
+ config.envs.delete(name);
34
+ config.save();
36
35
 
37
- if (fs.existsSync(selectedEnv.file)) {
36
+ if (exists(selectedEnv.file)) {
38
37
  const toDelete = await confirm({
39
38
  message: `Environment file found: ${selectedEnv.file}. Do you want to delete it?`
40
39
  });
41
40
 
42
41
  if (toDelete) {
43
42
  logger.debug(`Deleting environment file: ${selectedEnv.file}`);
44
- fs.unlinkSync(selectedEnv.file);
43
+ unlink(selectedEnv.file);
45
44
  }
46
45
  }
47
46
  }
@@ -1,6 +1,7 @@
1
1
  import Environment from './environment';
2
2
  import Variable from './variable';
3
3
  import Use from './use';
4
+ import Init from './init';
4
5
  import Setup from './setup';
5
6
 
6
- export default [Environment, Variable, Use, Setup];
7
+ export default [Environment, Variable, Use, Init, Setup];
@@ -1,4 +1,9 @@
1
- import fs from "node:fs";
1
+ import {
2
+ existsSync as exists,
3
+ readFileSync as read,
4
+ writeFileSync as write,
5
+ appendFileSync as append,
6
+ } from "node:fs";
2
7
 
3
8
  import { confirm } from "@inquirer/prompts";
4
9
 
@@ -6,41 +11,36 @@ import { Configuration } from "#lib/config";
6
11
  import { Logger } from "#lib/logger";
7
12
 
8
13
  export default {
9
- command: 'setup',
10
- describe: 'Setup the project for use with envedit',
14
+ command: 'init',
15
+ describe: 'Initialize the project for use with envedit',
11
16
  builder: (yargs) => yargs
12
- .usage('Usage: envedit setup'),
17
+ .usage('Usage: envedit init')
18
+ .epilogue("Note that this command is meant to be used when the project is FIRST setup, not to setup based on a pre-existing configuration. for that, please run the 'setup' command"),
13
19
  handler: async (argv) => {
14
20
  const logger = new Logger(argv.level);
21
+ new Configuration();
15
22
 
16
- new Configuration().load();
17
-
18
- if (fs.existsSync('.git')) {
23
+ if (exists('.git')) {
19
24
  logger.debug(`Git repository found. Checking for git ignore file.`);
20
25
 
21
26
  const gitignorePath = '.gitignore';
22
27
 
23
- if (!fs.existsSync(gitignorePath)) {
24
- logger.debug(`Git ignore file not found: ${gitignorePath}`);
25
-
28
+ if (!exists('.gitignore')) {
26
29
  const toCreate = await confirm({
27
- message: `Git Repository without a git ignore file not found: ${gitignorePath}. Do you want to create it?`
30
+ message: `Git Repository without a git ignore file not found. Do you want to create it?`
28
31
  });
29
32
 
30
33
  if (toCreate) {
31
- console.error(`Git Repository without a git ignore file not found: ${gitignorePath}`);
32
- process.exit(1);
34
+ write('.gitignore', "");
33
35
  }
34
-
35
- fs.writeFileSync(gitignorePath, "");
36
36
  }
37
37
 
38
- const expectedItems = ['.env', '.env.*', 'env.config.yaml'];
38
+ const expectedItems = ['.env', '.env.*'];
39
39
 
40
40
  logger.debug(`Checking for expected items in git ignore file: ${expectedItems.join(", ")}`);
41
41
 
42
42
  for (const item of expectedItems) {
43
- if (!fs.readFileSync(gitignorePath, "utf-8").includes(item)) {
43
+ if (read(gitignorePath, "utf-8").includes(item)) {
44
44
  logger.debug(`Item not found in git ignore file: ${item}`);
45
45
 
46
46
  const toAdd = await confirm({
@@ -52,7 +52,7 @@ export default {
52
52
  process.exit(1);
53
53
  }
54
54
 
55
- fs.appendFileSync(gitignorePath, `\n${item}`);
55
+ append(gitignorePath, `\n${item}`);
56
56
  }
57
57
  }
58
58
  }
@@ -0,0 +1,46 @@
1
+ import {
2
+ lstatSync as lstat,
3
+ existsSync as exists,
4
+ copyFileSync as copy,
5
+ unlinkSync as unlink,
6
+ symlinkSync as symlink,
7
+ } from 'node:fs';
8
+
9
+ import { Configuration, EnvironmentFile } from "#lib/config";
10
+ import { NotFoundError } from "#lib/error";
11
+
12
+ export default {
13
+ command: 'setup',
14
+ describe: 'Setup the project for use with envedit',
15
+ builder: (yargs) => yargs
16
+ .usage('Usage: envedit setup')
17
+ .epilogue("Note that this command is meant to be used after the project has been setup. To make a clean start, please run the 'init' command instead."),
18
+ handler: async (_argv) => {
19
+ if (!exists('env.config.yaml')) {
20
+ throw new NotFoundError("Could not find configuration file. Did you run the init command?")
21
+ }
22
+
23
+ const config = new Configuration();
24
+
25
+ for (const environment of config.envs.values()) {
26
+ const envFile = new EnvironmentFile(environment.file);
27
+
28
+ if (environment.isActive) {
29
+ if (exists('.env') && !lstat('.env').isSymbolicLink()) {
30
+ copy('.env', environment.file);
31
+ unlink('.env');
32
+ }
33
+
34
+ symlink(environment.file, '.env', 'file');
35
+ }
36
+
37
+ for (const variable of config.vars.values()) {
38
+ if (!envFile.vars.has(variable.name)) {
39
+ envFile.vars.set(variable.name, '');
40
+ }
41
+ }
42
+
43
+ envFile.save();
44
+ }
45
+ }
46
+ } as CommandModule;
@@ -1,4 +1,9 @@
1
- import fs from "node:fs";
1
+ import {
2
+ lstatSync as lstat,
3
+ existsSync as exists,
4
+ unlinkSync as unlink,
5
+ symlinkSync as symlink,
6
+ } from 'node:fs';
2
7
 
3
8
  import { select } from "@inquirer/prompts";
4
9
 
@@ -13,19 +18,18 @@ export default {
13
18
  .usage('Usage: envedit use'),
14
19
  handler: async (argv) => {
15
20
  const logger = new Logger(argv.level);
16
-
17
- const config = new Configuration().load();
21
+ const config = new Configuration();
18
22
 
19
23
  const name = await select({
20
24
  message: "Select the environment to use",
21
- choices: config.configuration.environments.map(env => ({
22
- name: env.name + (env.isActive ? " (active)" : ""),
23
- value: env.name,
25
+ choices: Array.from(config.envs.keys()).map(env => ({
26
+ name: env + (config.envs.get(env)?.isActive ? " (active)" : ""),
27
+ value: env,
24
28
  })),
25
- default: config.getActiveEnvironment()?.name,
29
+ default: config.active,
26
30
  });
27
31
 
28
- const selectedEnv = config.getEnvironment(name);
32
+ const selectedEnv = config.envs.get(name);
29
33
 
30
34
  if (!selectedEnv) {
31
35
  throw new NotFoundError(`Environment not found: ${name}`);
@@ -36,9 +40,7 @@ export default {
36
40
  if (!selectedEnv.isActive) {
37
41
  logger.debug(`Updating active environment in configuration file to "${name}"`);
38
42
 
39
- for (const env of config.configuration.environments) {
40
- env.isActive = env.name === name;
41
- }
43
+ config.active = selectedEnv.name;
42
44
 
43
45
  config.save();
44
46
  }
@@ -47,12 +49,12 @@ export default {
47
49
 
48
50
  const envFilePath = selectedEnv.file;
49
51
 
50
- if (fs.existsSync('.env')) {
52
+ if (exists('.env')) {
51
53
  logger.debug(`Removing existing symlink or file: .env`);
52
54
 
53
- fs.unlinkSync('.env');
55
+ unlink('.env');
54
56
  }
55
57
 
56
- fs.symlinkSync(envFilePath, '.env', 'file');
58
+ symlink(envFilePath, '.env', 'file');
57
59
  }
58
60
  } as CommandModule;
@@ -9,7 +9,7 @@ export default {
9
9
  describe: 'Add a new environment variable',
10
10
  builder: (yargs) => yargs,
11
11
  handler: async (argv) => {
12
- const config = new Configuration().load();
12
+ const config = new Configuration();
13
13
  const logger = new Logger(argv.level);
14
14
 
15
15
  const name = await input({
@@ -18,7 +18,7 @@ export default {
18
18
  if (!input) {
19
19
  return "Variable name cannot be empty";
20
20
  }
21
- if (config.getVariable(input)) {
21
+ if (config.vars.has(input)) {
22
22
  return "Variable name already exists";
23
23
  }
24
24
  return true;
@@ -35,15 +35,14 @@ export default {
35
35
 
36
36
  logger.debug(`Adding new variable "${name}" with type "${type}"`);
37
37
 
38
- config
39
- .addVariable({ name, type })
40
- .save();
38
+ config.vars.set(name, { name, type });
39
+ config.save();
41
40
 
42
41
  const shouldGloballySet = await confirm({
43
42
  message: "Do you want to set this variable with a global value for all environments?",
44
43
  });
45
44
 
46
- const valuesToSet = config.configuration.environments.map(env => ({
45
+ const valuesToSet = Array.from(config.envs.values()).map(env => ({
47
46
  envName: env.name,
48
47
  value: '',
49
48
  }));
@@ -77,14 +76,17 @@ export default {
77
76
  }
78
77
 
79
78
  for (const envValue of valuesToSet) {
80
- const env = config.getEnvironment(envValue.envName);
79
+ const env = config.envs.get(envValue.envName);
81
80
  logger.debug(`Retrieving environment: ${envValue.envName}`);
82
81
 
83
82
  if (!env) {
84
83
  throw new NotFoundError(`Environment not found: ${envValue.envName}`);
85
84
  }
86
85
 
87
- EnvironmentFile.quickUpdate(env.file, name, envValue.value);
86
+ const envFile = new EnvironmentFile(env.file);
87
+
88
+ envFile.vars.set(name, envValue.value);
89
+ envFile.save();
88
90
  }
89
91
  }
90
92
  } satisfies CommandModule;
@@ -9,18 +9,15 @@ export default {
9
9
  describe: 'Edit an environment variable',
10
10
  builder: (yargs) => yargs,
11
11
  handler: async (argv) => {
12
- const config = new Configuration().load();
12
+ const config = new Configuration();
13
13
  const logger = new Logger(argv.level);
14
14
 
15
15
  const name = await select({
16
16
  message: "Select the variable to edit:",
17
- choices: config.configuration.variables.map(variable => ({
18
- name: variable.name,
19
- value: variable.name,
20
- })),
17
+ choices: Array.from(config.vars.keys()),
21
18
  });
22
19
 
23
- const variableConfiguration = config.getVariable(name);
20
+ const variableConfiguration = config.vars.get(name);
24
21
 
25
22
  if (!variableConfiguration) {
26
23
  throw new NotFoundError(`Variable not found: ${name}`);
@@ -53,18 +50,22 @@ export default {
53
50
  default: variableConfiguration.name,
54
51
  });
55
52
 
56
- for (const env of config.configuration.environments) {
57
- const envFile = new EnvironmentFile(env.file).load();
53
+ for (const [_, env] of config.envs) {
54
+ const envFile = new EnvironmentFile(env.file);
58
55
 
59
56
  logger.debug(`Editing variable "${name}" in environment file: ${env.file}`);
60
57
 
61
- if (envFile.content[name] !== undefined) {
62
- const value = envFile.content[name];
63
- envFile.removeVariable(name);
64
- envFile.setVariable(updatedVariable.name, value);
65
- envFile.save();
66
- }
58
+ if (envFile.vars.has(name)) {
59
+ const value = envFile.vars.get(name) || '';
60
+
61
+ envFile.vars.delete(name);
62
+ envFile.vars.set(updatedVariable.name, value);
63
+ };
64
+
65
+ envFile.save();
67
66
  }
67
+
68
+ config.vars.delete(name);
68
69
  }
69
70
 
70
71
  if (itemsToEdit.includes("type")) {
@@ -80,13 +81,12 @@ export default {
80
81
  logger.debug(`Editing variable type for "${name}" to "${updatedVariable.type}"`);
81
82
  }
82
83
 
83
- config
84
- .editVariable(name, updatedVariable)
85
- .save();
84
+ config.vars.set(updatedVariable.name, updatedVariable);
85
+ config.save();
86
86
  } else {
87
- logger.debug(`Adding editing variable value for: ${name}`);
87
+ logger.debug(`Adding edited variable value for: ${name}`);
88
88
 
89
- let valuesToSet = config.configuration.environments.map(env => ({
89
+ let valuesToSet = Array.from(config.envs.values()).map(env => ({
90
90
  envName: env.name,
91
91
  value: '',
92
92
  }));
@@ -107,47 +107,43 @@ export default {
107
107
 
108
108
  logger.debug(`Setting variable "${variableConfiguration.name}" with global value "${globalValue}" for all environments`);
109
109
 
110
- valuesToSet.forEach((envValue) => { envValue.value = globalValue });
110
+ for (const [_, env] of config.envs) {
111
+ const envFile = new EnvironmentFile(env.file);
112
+
113
+ logger.debug(`Setting variable "${variableConfiguration.name}" with value "${globalValue}" in environment file: ${env.file}`);
114
+
115
+ envFile.vars.set(variableConfiguration.name, globalValue);
116
+ envFile.save();
117
+ }
111
118
  } else {
112
119
  const envsToEdit = await checkbox({
113
120
  message: "Select the environments to edit the variable value for:",
114
- choices: config.configuration.environments.map(env => ({
115
- name: env.name,
116
- value: env.name,
117
- })),
121
+ choices: Array.from(config.envs.keys()),
118
122
  });
119
123
 
120
124
  logger.debug(`Selected environments for editing: ${envsToEdit.join(", ")}`);
121
125
 
122
- valuesToSet = valuesToSet.filter(envValue => envsToEdit.includes(envValue.envName));
126
+ for (const selectedEnv of envsToEdit) {
127
+ const env = config.envs.get(selectedEnv);
123
128
 
124
- if (valuesToSet.length === 0) {
125
- console.log("No environments selected. Exiting.");
126
- return;
127
- }
129
+ if (!env) {
130
+ throw new NotFoundError(`Environment not found: ${selectedEnv}`);
131
+ }
132
+
133
+ const envFile = new EnvironmentFile(env.file);
128
134
 
129
- for (const envValue of valuesToSet) {
130
135
  const request = {
131
- message: `Enter the value for ${variableConfiguration.name} in environment ${envValue.envName}:`,
136
+ message: `Enter the value for ${variableConfiguration.name} in environment ${selectedEnv}:`,
132
137
  mask: variableConfiguration.type === VariableType.Private ? "*" : undefined,
133
138
  };
134
139
 
135
- envValue.value = variableConfiguration.type === VariableType.Private
140
+ const newValue = variableConfiguration.type === VariableType.Private
136
141
  ? await password(request)
137
142
  : await input(request);
138
- }
139
- }
140
143
 
141
- for (const envValue of valuesToSet) {
142
- const env = config.getEnvironment(envValue.envName);
143
-
144
- if (!env) {
145
- throw new NotFoundError(`Environment not found: ${envValue.envName}`);
144
+ envFile.vars.set(variableConfiguration.name, newValue);
145
+ envFile.save();
146
146
  }
147
-
148
- logger.debug(`Setting variable "${variableConfiguration.name}" with value "${envValue.value}" in environment file: ${env.file}`);
149
-
150
- EnvironmentFile.quickUpdate(env.file, variableConfiguration.name, envValue.value);
151
147
  }
152
148
  }
153
149
  }
@@ -7,19 +7,19 @@ export default {
7
7
  describe: 'List all environment variables',
8
8
  builder: (yargs) => yargs,
9
9
  handler: async (argv) => {
10
- const config = new Configuration().load();
10
+ const config = new Configuration();
11
11
  const logger = new Logger(argv.level);
12
12
 
13
- const activeEnv = config.getActiveEnvironment();
13
+ const activeEnv = config.active ? config.envs.get(config.active)! : undefined;
14
14
 
15
15
  if (!activeEnv) {
16
16
  throw new NotFoundError(`No active environment found. Please set an active environment using the "use" command.`);
17
17
  }
18
18
 
19
- const envFile = new EnvironmentFile(activeEnv.file).load();
19
+ const envFile = new EnvironmentFile(activeEnv.file);
20
20
 
21
- for (const [key, value] of Object.entries(envFile.content)) {
22
- const variableConfiguration = config.getVariable(key);
21
+ for (const [key, value] of envFile.vars) {
22
+ const variableConfiguration = config.vars.get(key);
23
23
  if (variableConfiguration) {
24
24
  const type = Configuration.getTypeDetails(variableConfiguration.type);
25
25
 
@@ -9,33 +9,30 @@ export default {
9
9
  describe: 'Remove an environment variable',
10
10
  builder: (yargs) => yargs,
11
11
  handler: async (argv) => {
12
- const config = new Configuration().load();
12
+ const config = new Configuration();
13
13
  const logger = new Logger(argv.level);
14
14
 
15
15
  const name = await select({
16
16
  message: "Select the variable to remove:",
17
- choices: config.configuration.variables.map(variable => ({
18
- name: variable.name,
19
- value: variable.name,
20
- })),
17
+ choices: Array.from(config.vars.keys()),
21
18
  });
22
19
 
23
- if (!config.getVariable(name)) {
20
+ if (!config.vars.has(name)) {
24
21
  throw new NotFoundError(`Variable not found: ${name}`);
25
22
  }
26
23
 
27
24
  logger.debug(`Removing variable "${name}" from configuration and all environment files`);
28
25
 
29
- config
30
- .removeVariable(name)
31
- .save();
26
+ config.vars.delete(name);
27
+ config.save();
32
28
 
33
- for (const env of config.configuration.environments) {
34
- const envFile = new EnvironmentFile(env.file).load();
29
+ for (const [_, env] of config.envs) {
30
+ const envFile = new EnvironmentFile(env.file);
35
31
 
36
- if (envFile.content[name] !== undefined) {
32
+ if (envFile.vars.has(name)) {
37
33
  logger.debug(`Removing variable "${name}" from environment file: ${env.file}`);
38
- envFile.removeVariable(name).save();
34
+ envFile.vars.delete(name);
35
+ envFile.save();
39
36
  }
40
37
  }
41
38
  }
@@ -9,13 +9,13 @@ await yargs(hideBin(process.argv))
9
9
  alias: "l",
10
10
  type: "string",
11
11
  description: "Set the log level",
12
- choices: ['debug', 'info', 'warn', 'error', 'fatal'],
13
12
  default: process.env.LOG_LEVEL || "info",
14
13
  })
15
14
  .env('ENVEDIT')
16
15
  .scriptName("envedit")
17
16
  .usage("Usage: $0 <command>")
18
17
  .command(Commands)
18
+ .showHelpOnFail(false)
19
19
  .demandCommand()
20
20
  .strict()
21
21
  .parseAsync();
package/lib/config.ts CHANGED
@@ -1,11 +1,13 @@
1
- import fs from 'fs';
1
+ import {
2
+ existsSync as exists,
3
+ readFileSync as read,
4
+ writeFileSync as write,
5
+ appendFileSync as append,
6
+ } from 'fs';
2
7
 
3
8
  import { parse } from 'dotenv';
4
9
 
5
10
  import { NotFoundError } from '#lib/error';
6
- import { Logger } from '#lib/logger';
7
-
8
- const logger = new Logger(process.env.LOG_LEVEL || 'info');
9
11
 
10
12
  export enum VariableType {
11
13
  Public,
@@ -31,218 +33,101 @@ export interface GeneralConfiguration {
31
33
  }
32
34
 
33
35
  export class Configuration {
34
- private config: GeneralConfiguration = { version: 1, environments: [], variables: [] };
36
+ public readonly vars: Map<string, VariableConfiguration> = new Map();
37
+ public readonly envs: Map<string, EnviornmentConfiguration> = new Map();
38
+ public readonly version: number = 1;
35
39
  private readonly configFilePath: string = 'env.config.yaml';
36
- constructor(config: Partial<GeneralConfiguration> = {}) {
37
- if (config.environments) {
38
- this.config.environments = config.environments;
39
- }
40
40
 
41
- if (config.variables) {
42
- this.config.variables = config.variables;
41
+ constructor() {
42
+ if (!exists(this.configFilePath)) {
43
+ this
44
+ .importFromEnv(exists('.env') ? read('.env', "utf-8") : '')
45
+ .save();
43
46
  }
44
- if (config.version) {
45
- this.config.version = config.version;
46
- }
47
- }
48
- public get configuration(): GeneralConfiguration {
49
- return {
50
- version: this.config.version,
51
- environments: this.config.environments || [],
52
- variables: this.config.variables || [],
53
- };
54
- }
55
-
56
- public load() {
57
- if (!fs.existsSync(this.configFilePath)) {
58
- logger.info(`Configuration file not found: ${this.configFilePath}`);
59
- logger.info(`Creating a new configuration file`);
60
47
 
61
- const config = new Configuration();
48
+ const configContent = Bun.YAML.parse(read(this.configFilePath, "utf-8")) as GeneralConfiguration;
62
49
 
63
- if (fs.existsSync('.env')) {
64
- config.importFromEnv(fs.readFileSync('.env', "utf-8"));
65
- }
66
-
67
- this.save();
68
- }
69
-
70
- const configContent = Bun.YAML.parse(fs.readFileSync(this.configFilePath, "utf-8")) as GeneralConfiguration;
50
+ const setWrapper = <
51
+ K extends EnviornmentConfiguration | VariableConfiguration
52
+ >(map: Map<string, K>): ((item: K) => void) => (item: K) => void map.set(item.name, item);
71
53
 
72
- this.config.environments = configContent.environments || [];
73
- this.config.variables = configContent.variables || [];
54
+ configContent.environments.forEach(setWrapper(this.envs))
55
+ configContent.variables.forEach(setWrapper(this.vars))
74
56
 
75
- return this;
76
57
  }
77
58
 
78
59
  public save() {
79
- fs.writeFileSync(
60
+ write(
80
61
  this.configFilePath,
81
- Bun.YAML.stringify(this.configuration, null, 2),
62
+ Bun.YAML.stringify({
63
+ version: this.version,
64
+ environments: Array.from(this.envs.values()),
65
+ variables: Array.from(this.vars.values()),
66
+ }, null, 2),
82
67
  { mode: 0o600 },
83
68
  );
84
69
 
85
70
  return this;
86
71
  }
87
72
 
88
- public setActiveEnvironment(name: string) {
89
- const index = this.config.environments!.findIndex(e => e.name === name);
90
- if (index === -1) {
91
- throw new NotFoundError(`Environment not found: ${name}`);
92
- }
93
-
94
- this.config.environments.forEach((env, i) => {
95
- env.isActive = i === index;
96
- });
73
+ public get active() {
74
+ const activeEnv = Array.from(this.envs.values()).find(env => env.isActive);
97
75
 
98
- return this;
76
+ return activeEnv ? activeEnv.name : undefined;
99
77
  }
100
78
 
101
- public getActiveEnvironment(): EnviornmentConfiguration | undefined {
102
- return this.config.environments.find(env => env.isActive);
103
- }
104
-
105
- public addEnvironment(env: EnviornmentConfiguration) {
106
- this.config.environments.push(env);
107
- return this;
108
- }
109
-
110
- public editEnvironment(name: string, env: Partial<EnviornmentConfiguration>) {
111
- const index = this.config.environments!.findIndex(e => e.name === name);
112
- if (index === -1) {
113
- throw new NotFoundError(`Environment not found: ${name}`);
79
+ public set active(env: string | undefined) {
80
+ if (env && !this.envs.has(env)) {
81
+ throw new NotFoundError(`Environment not found: ${env}`)
114
82
  }
115
83
 
116
- this.config.environments[index] = {
117
- name: env.name || this.config.environments[index]!.name,
118
- prefix: env.prefix || this.config.environments[index]!.prefix,
119
- file: env.file || this.config.environments[index]!.file,
120
- isActive: env.isActive !== undefined ? env.isActive : this.config.environments[index]!.isActive,
121
- };
122
- return this;
123
- }
124
-
125
- public removeEnvironment(name: string) {
126
- const index = this.config.environments!.findIndex(e => e.name === name);
127
- if (index === -1) {
128
- throw new NotFoundError(`Environment not found: ${name}`);
129
- }
130
-
131
- this.config.environments.splice(index, 1);
132
- return this;
133
- }
134
-
135
- public getEnvironment(name: string): EnviornmentConfiguration | undefined {
136
- return this.config.environments.find(env => env.name === name);
137
- }
138
-
139
- public addVariable(variable: VariableConfiguration) {
140
- this.config.variables.push(variable);
141
- return this;
142
- }
143
-
144
- public editVariable(name: string, variable: Partial<VariableConfiguration>) {
145
- if (!this.config.variables.find(v => v.name === name)) {
146
- throw new NotFoundError(`Variable not found: ${name}`);
84
+ for (const [name, environment] of this.envs.entries()) {
85
+ this.envs.set(name, { ...environment, isActive: name === env });
147
86
  }
148
87
 
149
- this.config.variables = this.config.variables.map(v => v.name === name ? { ...v, ...variable } : v);
150
- return this;
88
+ this.save();
151
89
  }
152
90
 
153
- public removeVariable(name: string) {
154
- if (!this.config.variables.find(v => v.name === name)) {
155
- throw new NotFoundError(`Variable not found: ${name}`);
91
+ public importFromEnv(content: string): Configuration {
92
+ for (const [key, _] of Object.entries(parse(content))) {
93
+ this.vars.set(key, { name: key, type: VariableType.Private });
156
94
  }
157
95
 
158
- this.config.variables = this.config.variables.filter(v => v.name !== name);
159
96
  return this;
160
97
  }
161
98
 
162
- public getVariable(name: string): VariableConfiguration | undefined {
163
- return this.config.variables.find(v => v.name === name);
164
- }
99
+ public static getTypeDetails(type: VariableType) {
100
+ const VariableTypeMap = new Map<number, keyof typeof VariableType>();
165
101
 
166
- public importFromEnv(content: string): Configuration {
167
- const config = new Configuration();
168
-
169
- const env = parse(content);
170
-
171
- for (const [key, _] of Object.entries(env)) {
172
- if (key.includes('PRIVATE')) {
173
- config.addVariable({
174
- name: key,
175
- type: VariableType.Private,
176
- });
177
- } else {
178
- config.addVariable({
179
- name: key,
180
- type: VariableType.Public,
181
- });
182
- }
102
+ for (const [key, value] of Object.entries(VariableType)) {
103
+ if (typeof value !== "number") continue;
104
+ VariableTypeMap.set(value, key as keyof typeof VariableType);
183
105
  }
184
106
 
185
- return config;
186
- }
187
-
188
- public static getTypeDetails(type: VariableType): { name: keyof typeof VariableType, value: VariableType } {
189
- const VariableTypeMap = Object.entries(VariableType).reduce((acc, [key, value]) => {
190
- if (typeof value === "number") {
191
- acc[value] = key as keyof typeof VariableType;
192
- }
193
- return acc;
194
- }, {} as Record<number, keyof typeof VariableType>);
195
-
196
107
  return {
197
- name: VariableTypeMap[type]!,
108
+ name: VariableTypeMap.get(type)!,
198
109
  value: type
199
110
  };
200
111
  }
201
112
  }
202
113
 
203
114
  export class EnvironmentFile {
204
- public content: Record<string, string> = {};
205
- public rawContent: string = '';
206
- constructor(public readonly path: string) { }
207
-
208
- public load() {
209
- if (!fs.existsSync(this.path)) {
210
- fs.writeFileSync(this.path, '', { mode: 0o600 });
115
+ public readonly vars: Map<string, string> = new Map();
116
+ constructor(public readonly path: string) {
117
+ if (exists(path)) {
118
+ const content = read(path, "utf-8");
119
+ const parsed = parse(content);
120
+
121
+ for (const [key, value] of Object.entries(parsed)) {
122
+ this.vars.set(key, value);
123
+ }
211
124
  }
212
-
213
- this.rawContent = fs.readFileSync(this.path, "utf-8");
214
- this.content = parse(this.rawContent);
215
-
216
- return this;
217
125
  }
218
126
 
219
127
  public save() {
220
- const lines: string[] = [];
221
- for (const [key, value] of Object.entries(this.content)) {
222
- lines.push(`${key}='${value}'`);
128
+ write(this.path, '')
129
+ for (const [key, value] of this.vars.entries()) {
130
+ append(this.path, `${key}='${value}'\n`);
223
131
  }
224
- this.rawContent = lines.join('\n');
225
- fs.writeFileSync(this.path, this.rawContent);
226
-
227
- return this;
228
- }
229
-
230
- public setVariable(name: string, value: string) {
231
- this.content[name] = value;
232
-
233
- return this;
234
- }
235
-
236
- public removeVariable(name: string) {
237
- delete this.content[name];
238
-
239
- return this;
240
- }
241
-
242
- public static quickUpdate(path: string, name: string, value: string) {
243
- const envFile = new EnvironmentFile(path).load();
244
- envFile.setVariable(name, value).save();
245
-
246
- return envFile;
247
132
  }
248
133
  }
package/lib/logger.ts CHANGED
@@ -2,39 +2,39 @@ import pino from 'pino';
2
2
  import type { LevelWithSilentOrString, Logger as PinoLogger } from 'pino';
3
3
 
4
4
  export class Logger {
5
- private readonly logger: PinoLogger<never, boolean>;
6
- constructor(public readonly level: LevelWithSilentOrString) {
7
- this.logger = pino({
8
- level,
9
- transport: {
10
- target: 'pino-pretty',
11
- options: {
12
- colorize: true,
13
- translateTime: 'SYS:standard',
14
- ignore: 'pid,hostname',
15
- },
5
+ private readonly logger: PinoLogger<never, boolean> = pino({
6
+ transport: {
7
+ target: 'pino-pretty',
8
+ options: {
9
+ colorize: true,
10
+ translateTime: 'SYS:standard',
11
+ ignore: 'pid,hostname',
16
12
  },
17
- });
13
+ },
14
+ });
15
+
16
+ constructor(public readonly level: LevelWithSilentOrString, private testMode: boolean = false) {
17
+ this.logger.level = level;
18
18
  }
19
19
 
20
20
  public readonly debug = (msg: string) => {
21
- this.logger.debug(msg);
21
+ this.testMode ? '' : this.logger.debug(msg);
22
22
  return this;
23
23
  }
24
24
  public readonly info = (msg: string) => {
25
- this.logger.info(msg);
25
+ this.testMode ? '' : this.logger.info(msg);
26
26
  return this;
27
27
  }
28
28
  public readonly warn = (msg: string) => {
29
- this.logger.warn(msg);
29
+ this.testMode ? '' : this.logger.warn(msg);
30
30
  return this;
31
31
  }
32
32
  public readonly error = (msg: string) => {
33
- this.logger.error(msg);
33
+ this.testMode ? '' : this.logger.error(msg);
34
34
  return this;
35
35
  }
36
36
  public readonly fatal = (msg: string) => {
37
- this.logger.fatal(msg);
37
+ this.testMode ? '' : this.logger.fatal(msg);
38
38
  return this;
39
39
  }
40
40
  }
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@benshawmean/envedit",
3
- "module": "index.ts",
4
3
  "type": "module",
5
- "version": "1.3.0",
4
+ "version": "1.3.2-0",
6
5
  "license": "GPL-3.0-only",
7
6
  "bin": {
8
- "envedit": "index.ts"
7
+ "envedit": "cli/index.ts"
9
8
  },
10
9
  "repository": {
11
10
  "url": "https://gitlab.com/benshawmean/envedit.git"
File without changes
File without changes
File without changes
File without changes