@benshawmean/envedit 1.3.0 → 1.3.1

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.
@@ -47,8 +47,6 @@ export default {
47
47
  .addEnvironment({ name, prefix, file, isActive })
48
48
  .save();
49
49
 
50
- const envFile = new EnvironmentFile(file).load();
51
-
52
50
  const valuesToSet: Array<[string, string]> = []
53
51
 
54
52
  if (!fs.existsSync(file)) {
@@ -75,9 +73,9 @@ export default {
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)) {
78
+ for (const [key, value] of Object.entries(envSpecificFile.data)) {
81
79
  valuesToSet.push([key, value]);
82
80
  }
83
81
  } else {
@@ -98,13 +96,15 @@ export default {
98
96
  }
99
97
  }
100
98
 
99
+ const envFile = new EnvironmentFile(file);
100
+
101
+
101
102
  for (const [key, value] of valuesToSet) {
102
103
  logger
103
104
  .debug(`Setting variable "${key}" with value "${value}" in environment file: ${file}`);
105
+
104
106
  envFile.setVariable(key, value);
105
107
  }
106
-
107
- envFile.save();
108
108
  }
109
109
  }
110
110
  } satisfies CommandModule;
package/cmd/setup.ts CHANGED
@@ -35,7 +35,7 @@ export default {
35
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
 
package/cmd/use.ts CHANGED
@@ -22,7 +22,7 @@ export default {
22
22
  name: env.name + (env.isActive ? " (active)" : ""),
23
23
  value: env.name,
24
24
  })),
25
- default: config.getActiveEnvironment()?.name,
25
+ default: config.active,
26
26
  });
27
27
 
28
28
  const selectedEnv = config.getEnvironment(name);
@@ -36,9 +36,7 @@ export default {
36
36
  if (!selectedEnv.isActive) {
37
37
  logger.debug(`Updating active environment in configuration file to "${name}"`);
38
38
 
39
- for (const env of config.configuration.environments) {
40
- env.isActive = env.name === name;
41
- }
39
+ config.active = selectedEnv.name;
42
40
 
43
41
  config.save();
44
42
  }
@@ -54,15 +54,14 @@ export default {
54
54
  });
55
55
 
56
56
  for (const env of config.configuration.environments) {
57
- const envFile = new EnvironmentFile(env.file).load();
57
+ const envFile = new EnvironmentFile(env.file);
58
58
 
59
59
  logger.debug(`Editing variable "${name}" in environment file: ${env.file}`);
60
60
 
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();
61
+ if (envFile.data[name] !== undefined) {
62
+ envFile
63
+ .removeVariable(name)
64
+ .setVariable(updatedVariable.name, envFile.data[name]);
66
65
  }
67
66
  }
68
67
  }
@@ -10,15 +10,15 @@ export default {
10
10
  const config = new Configuration().load();
11
11
  const logger = new Logger(argv.level);
12
12
 
13
- const activeEnv = config.getActiveEnvironment();
13
+ const activeEnv = config.active ? config.getEnvironment(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)) {
21
+ for (const [key, value] of Object.entries(envFile.data)) {
22
22
  const variableConfiguration = config.getVariable(key);
23
23
  if (variableConfiguration) {
24
24
  const type = Configuration.getTypeDetails(variableConfiguration.type);
@@ -31,11 +31,11 @@ export default {
31
31
  .save();
32
32
 
33
33
  for (const env of config.configuration.environments) {
34
- const envFile = new EnvironmentFile(env.file).load();
34
+ const envFile = new EnvironmentFile(env.file);
35
35
 
36
- if (envFile.content[name] !== undefined) {
36
+ if (envFile.data[name] !== undefined) {
37
37
  logger.debug(`Removing variable "${name}" from environment file: ${env.file}`);
38
- envFile.removeVariable(name).save();
38
+ envFile.removeVariable(name);
39
39
  }
40
40
  }
41
41
  }
package/lib/config.ts CHANGED
@@ -1,11 +1,8 @@
1
- import fs from 'fs';
1
+ import fs, { existsSync as exists, readFileSync as read, writeFileSync as write } from 'fs';
2
2
 
3
3
  import { parse } from 'dotenv';
4
4
 
5
5
  import { NotFoundError } from '#lib/error';
6
- import { Logger } from '#lib/logger';
7
-
8
- const logger = new Logger(process.env.LOG_LEVEL || 'info');
9
6
 
10
7
  export enum VariableType {
11
8
  Public,
@@ -31,52 +28,40 @@ export interface GeneralConfiguration {
31
28
  }
32
29
 
33
30
  export class Configuration {
34
- private config: GeneralConfiguration = { version: 1, environments: [], variables: [] };
31
+ public readonly vars: Map<string, VariableConfiguration> = new Map();
32
+ public readonly envs: Map<string, EnviornmentConfiguration> = new Map();
33
+ public readonly version: number = 1;
35
34
  private readonly configFilePath: string = 'env.config.yaml';
36
- constructor(config: Partial<GeneralConfiguration> = {}) {
37
- if (config.environments) {
38
- this.config.environments = config.environments;
39
- }
40
35
 
41
- if (config.variables) {
42
- this.config.variables = config.variables;
43
- }
44
- if (config.version) {
45
- this.config.version = config.version;
46
- }
47
- }
48
36
  public get configuration(): GeneralConfiguration {
49
37
  return {
50
- version: this.config.version,
51
- environments: this.config.environments || [],
52
- variables: this.config.variables || [],
38
+ version: this.version,
39
+ environments: Array.from(this.envs.values()) || [],
40
+ variables: Array.from(this.vars.values()) || [],
53
41
  };
54
42
  }
55
43
 
56
44
  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
-
61
- const config = new Configuration();
62
-
63
- if (fs.existsSync('.env')) {
64
- config.importFromEnv(fs.readFileSync('.env', "utf-8"));
65
- }
66
-
67
- this.save();
45
+ if (!exists(this.configFilePath)) {
46
+ this
47
+ .importFromEnv(exists('.env') ? read('.env', "utf-8") : '')
48
+ .save();
68
49
  }
69
50
 
70
- const configContent = Bun.YAML.parse(fs.readFileSync(this.configFilePath, "utf-8")) as GeneralConfiguration;
51
+ const configContent = Bun.YAML.parse(read(this.configFilePath, "utf-8")) as GeneralConfiguration;
71
52
 
72
- this.config.environments = configContent.environments || [];
73
- this.config.variables = configContent.variables || [];
53
+ const setWrapper = <
54
+ K extends EnviornmentConfiguration | VariableConfiguration
55
+ >(map: Map<string, K>): ((item: K) => void) => (item: K) => void map.set(item.name, item);
56
+
57
+ configContent.environments.forEach(setWrapper(this.envs))
58
+ configContent.variables.forEach(setWrapper(this.vars))
74
59
 
75
60
  return this;
76
61
  }
77
62
 
78
63
  public save() {
79
- fs.writeFileSync(
64
+ write(
80
65
  this.configFilePath,
81
66
  Bun.YAML.stringify(this.configuration, null, 2),
82
67
  { mode: 0o600 },
@@ -85,164 +70,134 @@ export class Configuration {
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);
79
+ public set active(env: string | undefined) {
80
+ if (env && !this.envs.has(env)) {
81
+ throw new NotFoundError(`Environment not found: ${env}`)
82
+ }
83
+
84
+ for (const [name, environment] of this.envs.entries()) {
85
+ this.envs.set(name, { ...environment, isActive: name === env });
86
+ }
87
+
88
+ this.save();
103
89
  }
104
90
 
105
91
  public addEnvironment(env: EnviornmentConfiguration) {
106
- this.config.environments.push(env);
92
+ this.envs.set(env.name, env);
93
+
107
94
  return this;
108
95
  }
109
96
 
110
97
  public editEnvironment(name: string, env: Partial<EnviornmentConfiguration>) {
111
- const index = this.config.environments!.findIndex(e => e.name === name);
112
- if (index === -1) {
98
+ if (!this.envs.has(name)) {
113
99
  throw new NotFoundError(`Environment not found: ${name}`);
114
100
  }
115
101
 
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
- };
102
+ this.envs.set(name, { ...this.envs.get(name)!, ...env });
103
+
122
104
  return this;
123
105
  }
124
106
 
125
107
  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
- }
108
+ this.envs.delete(name);
130
109
 
131
- this.config.environments.splice(index, 1);
132
110
  return this;
133
111
  }
134
112
 
135
113
  public getEnvironment(name: string): EnviornmentConfiguration | undefined {
136
- return this.config.environments.find(env => env.name === name);
114
+ return this.envs.get(name);
137
115
  }
138
116
 
139
117
  public addVariable(variable: VariableConfiguration) {
140
- this.config.variables.push(variable);
118
+ this.vars.set(variable.name, variable);
119
+
141
120
  return this;
142
121
  }
143
122
 
144
123
  public editVariable(name: string, variable: Partial<VariableConfiguration>) {
145
- if (!this.config.variables.find(v => v.name === name)) {
124
+ if (!this.vars.has(name)) {
146
125
  throw new NotFoundError(`Variable not found: ${name}`);
147
126
  }
148
127
 
149
- this.config.variables = this.config.variables.map(v => v.name === name ? { ...v, ...variable } : v);
128
+ this.vars.set(name, { ...this.vars.get(name)!, ...variable });
129
+
150
130
  return this;
151
131
  }
152
132
 
153
133
  public removeVariable(name: string) {
154
- if (!this.config.variables.find(v => v.name === name)) {
155
- throw new NotFoundError(`Variable not found: ${name}`);
156
- }
134
+ this.vars.delete(name);
157
135
 
158
- this.config.variables = this.config.variables.filter(v => v.name !== name);
159
136
  return this;
160
137
  }
161
138
 
162
139
  public getVariable(name: string): VariableConfiguration | undefined {
163
- return this.config.variables.find(v => v.name === name);
140
+ return this.vars.get(name);
164
141
  }
165
142
 
166
143
  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
- }
144
+ for (const [key, _] of Object.entries(parse(content))) {
145
+ this.addVariable({
146
+ name: key,
147
+ type: key.includes('PRIVATE') ? VariableType.Private : VariableType.Public,
148
+ });
183
149
  }
184
150
 
185
- return config;
151
+ return this;
186
152
  }
187
153
 
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>);
154
+ public static getTypeDetails(type: VariableType) {
155
+ const VariableTypeMap = new Map<number, keyof typeof VariableType>();
156
+
157
+ for (const [key, value] of Object.entries(VariableType)) {
158
+ if (typeof value !== "number") continue;
159
+ VariableTypeMap.set(value, key as keyof typeof VariableType);
160
+ }
195
161
 
196
162
  return {
197
- name: VariableTypeMap[type]!,
163
+ name: VariableTypeMap.get(type)!,
198
164
  value: type
199
165
  };
200
166
  }
201
167
  }
202
168
 
203
169
  export class EnvironmentFile {
204
- public content: Record<string, string> = {};
205
- public rawContent: string = '';
206
170
  constructor(public readonly path: string) { }
207
-
208
- public load() {
171
+ public get data() {
209
172
  if (!fs.existsSync(this.path)) {
210
173
  fs.writeFileSync(this.path, '', { mode: 0o600 });
211
174
  }
212
-
213
- this.rawContent = fs.readFileSync(this.path, "utf-8");
214
- this.content = parse(this.rawContent);
215
-
216
- return this;
175
+ return parse(fs.readFileSync(this.path, "utf-8"));
217
176
  }
218
177
 
219
- public save() {
178
+ public set data(content: Record<string, string>) {
220
179
  const lines: string[] = [];
221
- for (const [key, value] of Object.entries(this.content)) {
180
+ for (const [key, value] of Object.entries(content)) {
222
181
  lines.push(`${key}='${value}'`);
223
182
  }
224
- this.rawContent = lines.join('\n');
225
- fs.writeFileSync(this.path, this.rawContent);
226
-
227
- return this;
183
+ fs.writeFileSync(this.path, lines.join('\n'));
228
184
  }
229
185
 
230
186
  public setVariable(name: string, value: string) {
231
- this.content[name] = value;
232
-
187
+ this.data = { ...this.data, [name]: value };
233
188
  return this;
234
189
  }
235
190
 
236
191
  public removeVariable(name: string) {
237
- delete this.content[name];
238
-
192
+ const tmpData = Object.assign({}, this.data);
193
+ delete tmpData[name];
194
+ this.data = tmpData;
239
195
  return this;
240
196
  }
241
197
 
242
198
  public static quickUpdate(path: string, name: string, value: string) {
243
- const envFile = new EnvironmentFile(path).load();
244
- envFile.setVariable(name, value).save();
245
-
199
+ const envFile = new EnvironmentFile(path);
200
+ envFile.setVariable(name, value);
246
201
  return envFile;
247
202
  }
248
203
  }
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
@@ -2,7 +2,7 @@
2
2
  "name": "@benshawmean/envedit",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "1.3.0",
5
+ "version": "1.3.1",
6
6
  "license": "GPL-3.0-only",
7
7
  "bin": {
8
8
  "envedit": "index.ts"