dotswitch 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/dist/index.cjs ADDED
@@ -0,0 +1,246 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+
28
+ //#endregion
29
+ let node_fs = require("node:fs");
30
+ node_fs = __toESM(node_fs);
31
+ let node_path = require("node:path");
32
+ node_path = __toESM(node_path);
33
+ let picocolors = require("picocolors");
34
+ picocolors = __toESM(picocolors);
35
+
36
+ //#region src/lib/constants.ts
37
+ const TRACKER_PREFIX = "# dotswitch:";
38
+ const EXCLUDED_ENV_FILES = new Set([
39
+ ".env",
40
+ ".env.local",
41
+ ".env.local.backup",
42
+ ".env.example"
43
+ ]);
44
+
45
+ //#endregion
46
+ //#region src/lib/tracker.ts
47
+ function createTrackerHeader(env) {
48
+ return `${TRACKER_PREFIX}${env}`;
49
+ }
50
+ function parseTrackerHeader(content) {
51
+ const firstLine = content.split("\n")[0];
52
+ if (firstLine?.startsWith(TRACKER_PREFIX)) return firstLine.slice(TRACKER_PREFIX.length).trim();
53
+ return null;
54
+ }
55
+ function addTrackerHeader(content, env) {
56
+ const header = createTrackerHeader(env);
57
+ if (parseTrackerHeader(content) !== null) {
58
+ const lines = content.split("\n");
59
+ lines[0] = header;
60
+ return lines.join("\n");
61
+ }
62
+ return `${header}\n${content}`;
63
+ }
64
+ function removeTrackerHeader(content) {
65
+ if (parseTrackerHeader(content) !== null) {
66
+ const lines = content.split("\n");
67
+ lines.shift();
68
+ return lines.join("\n");
69
+ }
70
+ return content;
71
+ }
72
+
73
+ //#endregion
74
+ //#region src/lib/logger.ts
75
+ const logger = {
76
+ success(message) {
77
+ console.log(picocolors.default.green(`✓ ${message}`));
78
+ },
79
+ info(message) {
80
+ console.log(picocolors.default.cyan(message));
81
+ },
82
+ warn(message) {
83
+ console.log(picocolors.default.yellow(`⚠ ${message}`));
84
+ },
85
+ error(message) {
86
+ console.error(picocolors.default.red(`✗ ${message}`));
87
+ }
88
+ };
89
+
90
+ //#endregion
91
+ //#region src/lib/config.ts
92
+ const CONFIG_FILENAME = ".dotswitchrc.json";
93
+ const DEFAULT_CONFIG = {
94
+ target: ".env.local",
95
+ exclude: [],
96
+ hooks: {}
97
+ };
98
+ function loadConfig(dir, fsModule = node_fs.default) {
99
+ const configPath = node_path.default.join(dir, CONFIG_FILENAME);
100
+ try {
101
+ if (fsModule.existsSync(configPath)) {
102
+ const raw = JSON.parse(fsModule.readFileSync(configPath, "utf-8"));
103
+ return {
104
+ target: raw.target ?? DEFAULT_CONFIG.target,
105
+ exclude: raw.exclude ?? DEFAULT_CONFIG.exclude,
106
+ hooks: raw.hooks ?? DEFAULT_CONFIG.hooks
107
+ };
108
+ }
109
+ } catch {}
110
+ return { ...DEFAULT_CONFIG };
111
+ }
112
+ function getTargetFile(config) {
113
+ return config.target;
114
+ }
115
+ function getBackupFile(config) {
116
+ return `${config.target}.backup`;
117
+ }
118
+
119
+ //#endregion
120
+ //#region src/lib/env.ts
121
+ function resolveConfig(dir, config, fsModule) {
122
+ return config ?? loadConfig(dir, fsModule);
123
+ }
124
+ function listEnvFiles(dir, fsModule = node_fs.default, config) {
125
+ const cfg = resolveConfig(dir, config, fsModule);
126
+ const entries = fsModule.readdirSync(dir);
127
+ const activeEnv = getActiveEnv(dir, fsModule, cfg);
128
+ const target = getTargetFile(cfg);
129
+ const backup = getBackupFile(cfg);
130
+ const excluded = new Set([
131
+ ...EXCLUDED_ENV_FILES,
132
+ ...cfg.exclude,
133
+ target,
134
+ backup
135
+ ]);
136
+ return entries.filter((name) => name.startsWith(".env.") && !excluded.has(name)).sort().map((name) => {
137
+ const env = name.replace(/^\.env\./, "");
138
+ return {
139
+ name,
140
+ env,
141
+ path: node_path.default.join(dir, name),
142
+ active: env === activeEnv
143
+ };
144
+ });
145
+ }
146
+ function getActiveEnv(dir, fsModule = node_fs.default, config) {
147
+ const cfg = resolveConfig(dir, config, fsModule);
148
+ const targetPath = node_path.default.join(dir, getTargetFile(cfg));
149
+ try {
150
+ return parseTrackerHeader(fsModule.readFileSync(targetPath, "utf-8"));
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ function backupEnvLocal(dir, fsModule = node_fs.default, config) {
156
+ const cfg = resolveConfig(dir, config, fsModule);
157
+ const target = getTargetFile(cfg);
158
+ const targetPath = node_path.default.join(dir, target);
159
+ const backupPath = node_path.default.join(dir, getBackupFile(cfg));
160
+ try {
161
+ if (fsModule.existsSync(targetPath)) {
162
+ fsModule.copyFileSync(targetPath, backupPath);
163
+ return true;
164
+ }
165
+ return false;
166
+ } catch (error) {
167
+ logger.warn(`Failed to back up ${target}: ${error instanceof Error ? error.message : String(error)}`);
168
+ return false;
169
+ }
170
+ }
171
+ function restoreEnvLocal(dir, fsModule = node_fs.default, config) {
172
+ const cfg = resolveConfig(dir, config, fsModule);
173
+ const target = getTargetFile(cfg);
174
+ const backup = getBackupFile(cfg);
175
+ const backupPath = node_path.default.join(dir, backup);
176
+ const targetPath = node_path.default.join(dir, target);
177
+ if (!fsModule.existsSync(backupPath)) throw new Error(`No backup file found (${backup})`);
178
+ fsModule.copyFileSync(backupPath, targetPath);
179
+ }
180
+ function switchEnv(dir, env, options = { backup: true }, fsModule = node_fs.default, config) {
181
+ const cfg = resolveConfig(dir, config, fsModule);
182
+ const sourcePath = node_path.default.join(dir, `.env.${env}`);
183
+ const targetPath = node_path.default.join(dir, getTargetFile(cfg));
184
+ if (!fsModule.existsSync(sourcePath)) throw new Error(`Environment file .env.${env} does not exist`);
185
+ if (options.backup) backupEnvLocal(dir, fsModule, cfg);
186
+ const tracked = addTrackerHeader(fsModule.readFileSync(sourcePath, "utf-8"), env);
187
+ fsModule.writeFileSync(targetPath, tracked, "utf-8");
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/lib/parser.ts
192
+ /**
193
+ * Parse a .env file into a key-value map.
194
+ * Strips comments (lines starting with #) and empty lines.
195
+ */
196
+ function parseEnvContent(content) {
197
+ const result = /* @__PURE__ */ new Map();
198
+ for (const line of content.split("\n")) {
199
+ const trimmed = line.trim();
200
+ if (!trimmed || trimmed.startsWith("#")) continue;
201
+ const eqIndex = trimmed.indexOf("=");
202
+ if (eqIndex === -1) continue;
203
+ const key = trimmed.slice(0, eqIndex).trim();
204
+ const value = trimmed.slice(eqIndex + 1).trim();
205
+ if (key) result.set(key, value);
206
+ }
207
+ return result;
208
+ }
209
+ /**
210
+ * Compute the diff between two parsed env maps.
211
+ * "added" = keys in `to` but not in `from`.
212
+ * "removed" = keys in `from` but not in `to`.
213
+ * "changed" = keys in both with different values.
214
+ */
215
+ function diffEnvMaps(from, to) {
216
+ const added = [];
217
+ const removed = [];
218
+ const changed = [];
219
+ const unchanged = [];
220
+ for (const key of from.keys()) if (!to.has(key)) removed.push(key);
221
+ else if (from.get(key) !== to.get(key)) changed.push(key);
222
+ else unchanged.push(key);
223
+ for (const key of to.keys()) if (!from.has(key)) added.push(key);
224
+ return {
225
+ added: added.sort(),
226
+ removed: removed.sort(),
227
+ changed: changed.sort(),
228
+ unchanged: unchanged.sort()
229
+ };
230
+ }
231
+
232
+ //#endregion
233
+ exports.addTrackerHeader = addTrackerHeader;
234
+ exports.backupEnvLocal = backupEnvLocal;
235
+ exports.createTrackerHeader = createTrackerHeader;
236
+ exports.diffEnvMaps = diffEnvMaps;
237
+ exports.getActiveEnv = getActiveEnv;
238
+ exports.getBackupFile = getBackupFile;
239
+ exports.getTargetFile = getTargetFile;
240
+ exports.listEnvFiles = listEnvFiles;
241
+ exports.loadConfig = loadConfig;
242
+ exports.parseEnvContent = parseEnvContent;
243
+ exports.parseTrackerHeader = parseTrackerHeader;
244
+ exports.removeTrackerHeader = removeTrackerHeader;
245
+ exports.restoreEnvLocal = restoreEnvLocal;
246
+ exports.switchEnv = switchEnv;
@@ -0,0 +1,69 @@
1
+ import fs from "node:fs";
2
+
3
+ //#region src/lib/config.d.ts
4
+ interface DotswitchConfig {
5
+ /** Target file to write to (default: ".env.local") */
6
+ target: string;
7
+ /** File patterns to exclude from env listing */
8
+ exclude: string[];
9
+ /** Branch-to-env mappings for git hook auto-switching */
10
+ hooks: Record<string, string>;
11
+ }
12
+ declare function loadConfig(dir: string, fsModule?: typeof fs): DotswitchConfig;
13
+ declare function getTargetFile(config: DotswitchConfig): string;
14
+ declare function getBackupFile(config: DotswitchConfig): string;
15
+ //#endregion
16
+ //#region src/types.d.ts
17
+ interface EnvFile {
18
+ name: string;
19
+ env: string;
20
+ path: string;
21
+ active: boolean;
22
+ }
23
+ interface UseOptions {
24
+ force: boolean;
25
+ backup: boolean;
26
+ dryRun: boolean;
27
+ path: string;
28
+ }
29
+ interface CommonOptions {
30
+ path: string;
31
+ json: boolean;
32
+ }
33
+ //#endregion
34
+ //#region src/lib/env.d.ts
35
+ declare function listEnvFiles(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): EnvFile[];
36
+ declare function getActiveEnv(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): string | null;
37
+ declare function backupEnvLocal(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): boolean;
38
+ declare function restoreEnvLocal(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): void;
39
+ declare function switchEnv(dir: string, env: string, options?: {
40
+ backup: boolean;
41
+ }, fsModule?: typeof fs, config?: DotswitchConfig): void;
42
+ //#endregion
43
+ //#region src/lib/tracker.d.ts
44
+ declare function createTrackerHeader(env: string): string;
45
+ declare function parseTrackerHeader(content: string): string | null;
46
+ declare function addTrackerHeader(content: string, env: string): string;
47
+ declare function removeTrackerHeader(content: string): string;
48
+ //#endregion
49
+ //#region src/lib/parser.d.ts
50
+ /**
51
+ * Parse a .env file into a key-value map.
52
+ * Strips comments (lines starting with #) and empty lines.
53
+ */
54
+ declare function parseEnvContent(content: string): Map<string, string>;
55
+ interface EnvDiff {
56
+ added: string[];
57
+ removed: string[];
58
+ changed: string[];
59
+ unchanged: string[];
60
+ }
61
+ /**
62
+ * Compute the diff between two parsed env maps.
63
+ * "added" = keys in `to` but not in `from`.
64
+ * "removed" = keys in `from` but not in `to`.
65
+ * "changed" = keys in both with different values.
66
+ */
67
+ declare function diffEnvMaps(from: Map<string, string>, to: Map<string, string>): EnvDiff;
68
+ //#endregion
69
+ export { type CommonOptions, type DotswitchConfig, type EnvDiff, type EnvFile, type UseOptions, addTrackerHeader, backupEnvLocal, createTrackerHeader, diffEnvMaps, getActiveEnv, getBackupFile, getTargetFile, listEnvFiles, loadConfig, parseEnvContent, parseTrackerHeader, removeTrackerHeader, restoreEnvLocal, switchEnv };
@@ -0,0 +1,69 @@
1
+ import fs from "node:fs";
2
+
3
+ //#region src/lib/config.d.ts
4
+ interface DotswitchConfig {
5
+ /** Target file to write to (default: ".env.local") */
6
+ target: string;
7
+ /** File patterns to exclude from env listing */
8
+ exclude: string[];
9
+ /** Branch-to-env mappings for git hook auto-switching */
10
+ hooks: Record<string, string>;
11
+ }
12
+ declare function loadConfig(dir: string, fsModule?: typeof fs): DotswitchConfig;
13
+ declare function getTargetFile(config: DotswitchConfig): string;
14
+ declare function getBackupFile(config: DotswitchConfig): string;
15
+ //#endregion
16
+ //#region src/types.d.ts
17
+ interface EnvFile {
18
+ name: string;
19
+ env: string;
20
+ path: string;
21
+ active: boolean;
22
+ }
23
+ interface UseOptions {
24
+ force: boolean;
25
+ backup: boolean;
26
+ dryRun: boolean;
27
+ path: string;
28
+ }
29
+ interface CommonOptions {
30
+ path: string;
31
+ json: boolean;
32
+ }
33
+ //#endregion
34
+ //#region src/lib/env.d.ts
35
+ declare function listEnvFiles(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): EnvFile[];
36
+ declare function getActiveEnv(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): string | null;
37
+ declare function backupEnvLocal(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): boolean;
38
+ declare function restoreEnvLocal(dir: string, fsModule?: typeof fs, config?: DotswitchConfig): void;
39
+ declare function switchEnv(dir: string, env: string, options?: {
40
+ backup: boolean;
41
+ }, fsModule?: typeof fs, config?: DotswitchConfig): void;
42
+ //#endregion
43
+ //#region src/lib/tracker.d.ts
44
+ declare function createTrackerHeader(env: string): string;
45
+ declare function parseTrackerHeader(content: string): string | null;
46
+ declare function addTrackerHeader(content: string, env: string): string;
47
+ declare function removeTrackerHeader(content: string): string;
48
+ //#endregion
49
+ //#region src/lib/parser.d.ts
50
+ /**
51
+ * Parse a .env file into a key-value map.
52
+ * Strips comments (lines starting with #) and empty lines.
53
+ */
54
+ declare function parseEnvContent(content: string): Map<string, string>;
55
+ interface EnvDiff {
56
+ added: string[];
57
+ removed: string[];
58
+ changed: string[];
59
+ unchanged: string[];
60
+ }
61
+ /**
62
+ * Compute the diff between two parsed env maps.
63
+ * "added" = keys in `to` but not in `from`.
64
+ * "removed" = keys in `from` but not in `to`.
65
+ * "changed" = keys in both with different values.
66
+ */
67
+ declare function diffEnvMaps(from: Map<string, string>, to: Map<string, string>): EnvDiff;
68
+ //#endregion
69
+ export { type CommonOptions, type DotswitchConfig, type EnvDiff, type EnvFile, type UseOptions, addTrackerHeader, backupEnvLocal, createTrackerHeader, diffEnvMaps, getActiveEnv, getBackupFile, getTargetFile, listEnvFiles, loadConfig, parseEnvContent, parseTrackerHeader, removeTrackerHeader, restoreEnvLocal, switchEnv };
package/dist/index.mjs ADDED
@@ -0,0 +1,202 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import pc from "picocolors";
4
+
5
+ //#region src/lib/constants.ts
6
+ const TRACKER_PREFIX = "# dotswitch:";
7
+ const EXCLUDED_ENV_FILES = new Set([
8
+ ".env",
9
+ ".env.local",
10
+ ".env.local.backup",
11
+ ".env.example"
12
+ ]);
13
+
14
+ //#endregion
15
+ //#region src/lib/tracker.ts
16
+ function createTrackerHeader(env) {
17
+ return `${TRACKER_PREFIX}${env}`;
18
+ }
19
+ function parseTrackerHeader(content) {
20
+ const firstLine = content.split("\n")[0];
21
+ if (firstLine?.startsWith(TRACKER_PREFIX)) return firstLine.slice(TRACKER_PREFIX.length).trim();
22
+ return null;
23
+ }
24
+ function addTrackerHeader(content, env) {
25
+ const header = createTrackerHeader(env);
26
+ if (parseTrackerHeader(content) !== null) {
27
+ const lines = content.split("\n");
28
+ lines[0] = header;
29
+ return lines.join("\n");
30
+ }
31
+ return `${header}\n${content}`;
32
+ }
33
+ function removeTrackerHeader(content) {
34
+ if (parseTrackerHeader(content) !== null) {
35
+ const lines = content.split("\n");
36
+ lines.shift();
37
+ return lines.join("\n");
38
+ }
39
+ return content;
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/lib/logger.ts
44
+ const logger = {
45
+ success(message) {
46
+ console.log(pc.green(`✓ ${message}`));
47
+ },
48
+ info(message) {
49
+ console.log(pc.cyan(message));
50
+ },
51
+ warn(message) {
52
+ console.log(pc.yellow(`⚠ ${message}`));
53
+ },
54
+ error(message) {
55
+ console.error(pc.red(`✗ ${message}`));
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ //#region src/lib/config.ts
61
+ const CONFIG_FILENAME = ".dotswitchrc.json";
62
+ const DEFAULT_CONFIG = {
63
+ target: ".env.local",
64
+ exclude: [],
65
+ hooks: {}
66
+ };
67
+ function loadConfig(dir, fsModule = fs) {
68
+ const configPath = path.join(dir, CONFIG_FILENAME);
69
+ try {
70
+ if (fsModule.existsSync(configPath)) {
71
+ const raw = JSON.parse(fsModule.readFileSync(configPath, "utf-8"));
72
+ return {
73
+ target: raw.target ?? DEFAULT_CONFIG.target,
74
+ exclude: raw.exclude ?? DEFAULT_CONFIG.exclude,
75
+ hooks: raw.hooks ?? DEFAULT_CONFIG.hooks
76
+ };
77
+ }
78
+ } catch {}
79
+ return { ...DEFAULT_CONFIG };
80
+ }
81
+ function getTargetFile(config) {
82
+ return config.target;
83
+ }
84
+ function getBackupFile(config) {
85
+ return `${config.target}.backup`;
86
+ }
87
+
88
+ //#endregion
89
+ //#region src/lib/env.ts
90
+ function resolveConfig(dir, config, fsModule) {
91
+ return config ?? loadConfig(dir, fsModule);
92
+ }
93
+ function listEnvFiles(dir, fsModule = fs, config) {
94
+ const cfg = resolveConfig(dir, config, fsModule);
95
+ const entries = fsModule.readdirSync(dir);
96
+ const activeEnv = getActiveEnv(dir, fsModule, cfg);
97
+ const target = getTargetFile(cfg);
98
+ const backup = getBackupFile(cfg);
99
+ const excluded = new Set([
100
+ ...EXCLUDED_ENV_FILES,
101
+ ...cfg.exclude,
102
+ target,
103
+ backup
104
+ ]);
105
+ return entries.filter((name) => name.startsWith(".env.") && !excluded.has(name)).sort().map((name) => {
106
+ const env = name.replace(/^\.env\./, "");
107
+ return {
108
+ name,
109
+ env,
110
+ path: path.join(dir, name),
111
+ active: env === activeEnv
112
+ };
113
+ });
114
+ }
115
+ function getActiveEnv(dir, fsModule = fs, config) {
116
+ const cfg = resolveConfig(dir, config, fsModule);
117
+ const targetPath = path.join(dir, getTargetFile(cfg));
118
+ try {
119
+ return parseTrackerHeader(fsModule.readFileSync(targetPath, "utf-8"));
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+ function backupEnvLocal(dir, fsModule = fs, config) {
125
+ const cfg = resolveConfig(dir, config, fsModule);
126
+ const target = getTargetFile(cfg);
127
+ const targetPath = path.join(dir, target);
128
+ const backupPath = path.join(dir, getBackupFile(cfg));
129
+ try {
130
+ if (fsModule.existsSync(targetPath)) {
131
+ fsModule.copyFileSync(targetPath, backupPath);
132
+ return true;
133
+ }
134
+ return false;
135
+ } catch (error) {
136
+ logger.warn(`Failed to back up ${target}: ${error instanceof Error ? error.message : String(error)}`);
137
+ return false;
138
+ }
139
+ }
140
+ function restoreEnvLocal(dir, fsModule = fs, config) {
141
+ const cfg = resolveConfig(dir, config, fsModule);
142
+ const target = getTargetFile(cfg);
143
+ const backup = getBackupFile(cfg);
144
+ const backupPath = path.join(dir, backup);
145
+ const targetPath = path.join(dir, target);
146
+ if (!fsModule.existsSync(backupPath)) throw new Error(`No backup file found (${backup})`);
147
+ fsModule.copyFileSync(backupPath, targetPath);
148
+ }
149
+ function switchEnv(dir, env, options = { backup: true }, fsModule = fs, config) {
150
+ const cfg = resolveConfig(dir, config, fsModule);
151
+ const sourcePath = path.join(dir, `.env.${env}`);
152
+ const targetPath = path.join(dir, getTargetFile(cfg));
153
+ if (!fsModule.existsSync(sourcePath)) throw new Error(`Environment file .env.${env} does not exist`);
154
+ if (options.backup) backupEnvLocal(dir, fsModule, cfg);
155
+ const tracked = addTrackerHeader(fsModule.readFileSync(sourcePath, "utf-8"), env);
156
+ fsModule.writeFileSync(targetPath, tracked, "utf-8");
157
+ }
158
+
159
+ //#endregion
160
+ //#region src/lib/parser.ts
161
+ /**
162
+ * Parse a .env file into a key-value map.
163
+ * Strips comments (lines starting with #) and empty lines.
164
+ */
165
+ function parseEnvContent(content) {
166
+ const result = /* @__PURE__ */ new Map();
167
+ for (const line of content.split("\n")) {
168
+ const trimmed = line.trim();
169
+ if (!trimmed || trimmed.startsWith("#")) continue;
170
+ const eqIndex = trimmed.indexOf("=");
171
+ if (eqIndex === -1) continue;
172
+ const key = trimmed.slice(0, eqIndex).trim();
173
+ const value = trimmed.slice(eqIndex + 1).trim();
174
+ if (key) result.set(key, value);
175
+ }
176
+ return result;
177
+ }
178
+ /**
179
+ * Compute the diff between two parsed env maps.
180
+ * "added" = keys in `to` but not in `from`.
181
+ * "removed" = keys in `from` but not in `to`.
182
+ * "changed" = keys in both with different values.
183
+ */
184
+ function diffEnvMaps(from, to) {
185
+ const added = [];
186
+ const removed = [];
187
+ const changed = [];
188
+ const unchanged = [];
189
+ for (const key of from.keys()) if (!to.has(key)) removed.push(key);
190
+ else if (from.get(key) !== to.get(key)) changed.push(key);
191
+ else unchanged.push(key);
192
+ for (const key of to.keys()) if (!from.has(key)) added.push(key);
193
+ return {
194
+ added: added.sort(),
195
+ removed: removed.sort(),
196
+ changed: changed.sort(),
197
+ unchanged: unchanged.sort()
198
+ };
199
+ }
200
+
201
+ //#endregion
202
+ export { addTrackerHeader, backupEnvLocal, createTrackerHeader, diffEnvMaps, getActiveEnv, getBackupFile, getTargetFile, listEnvFiles, loadConfig, parseEnvContent, parseTrackerHeader, removeTrackerHeader, restoreEnvLocal, switchEnv };
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "dotswitch",
3
+ "version": "1.0.0",
4
+ "description": "Quickly switch between .env files",
5
+ "type": "module",
6
+ "bin": {
7
+ "dotswitch": "dist/cli.js"
8
+ },
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.mts",
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.mts",
16
+ "default": "./dist/index.mjs"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsdown && mv dist/cli.mjs dist/cli.js && mv dist/cli.d.mts dist/cli.d.ts",
29
+ "dev": "tsx src/cli.ts",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "keywords": [
35
+ "dotswitch",
36
+ "env",
37
+ "dotenv",
38
+ "env-switch",
39
+ "env-files",
40
+ "env-management",
41
+ "environment",
42
+ "environment-variables",
43
+ "configuration",
44
+ "cli",
45
+ "nextjs",
46
+ "vite",
47
+ "remix",
48
+ "monorepo",
49
+ "devtools",
50
+ "developer-tools",
51
+ "git-hooks"
52
+ ],
53
+ "author": "Stefan Natter <https://natterstefan.me>",
54
+ "license": "MIT",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/natterstefan/dotswitch.git"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/natterstefan/dotswitch/issues"
61
+ },
62
+ "homepage": "https://github.com/natterstefan/dotswitch#readme",
63
+ "engines": {
64
+ "node": ">=20"
65
+ },
66
+ "dependencies": {
67
+ "@inquirer/select": "^4.1.0",
68
+ "commander": "^13.1.0",
69
+ "picocolors": "^1.1.1"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^22.13.4",
73
+ "memfs": "^4.17.0",
74
+ "tsdown": "^0.20.3",
75
+ "tsx": "^4.19.3",
76
+ "typescript": "^5.7.3",
77
+ "vitest": "^3.0.7"
78
+ }
79
+ }