@dotenc/cli 0.2.3 → 0.3.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/commands/edit.js +10 -4
- package/dist/commands/init.js +22 -7
- package/dist/commands/key/export.js +11 -2
- package/dist/commands/key/import.js +13 -3
- package/dist/commands/key/rotate.js +35 -0
- package/dist/commands/run.js +2 -2
- package/dist/helpers/environmentExists.js +6 -0
- package/dist/helpers/getEnvironmentNameSuggestion.js +5 -0
- package/dist/helpers/key.js +5 -5
- package/dist/helpers/parseEnv.js +2 -0
- package/dist/program.js +23 -8
- package/dist/prompts/inputKey.js +13 -0
- package/dist/tests/e2e.test.js +64 -0
- package/dist/tests/helpers/cleanupProjectKeys.js +13 -0
- package/dist/tests/helpers/waitForFile.js +15 -0
- package/dist/{helpers → tests}/parseEnv.test.js +3 -1
- package/package.json +26 -18
package/dist/commands/edit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import { execSync } from "node:child_process";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import fs from "node:fs/promises";
|
|
@@ -13,12 +14,17 @@ export const editCommand = async (environmentArg) => {
|
|
|
13
14
|
if (!environment) {
|
|
14
15
|
environment = await chooseEnvironmentPrompt("What environment do you want to edit?");
|
|
15
16
|
}
|
|
16
|
-
const
|
|
17
|
+
const environmentFile = `.env.${environment}.enc`;
|
|
18
|
+
const environmentFilePath = path.join(process.cwd(), environmentFile);
|
|
17
19
|
if (!existsSync(environmentFilePath)) {
|
|
18
20
|
console.error(`Environment file not found: ${environmentFilePath}`);
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
23
|
const key = await getKey(environment);
|
|
24
|
+
if (!key) {
|
|
25
|
+
console.error(`\n${chalk.red("Error:")} no key found for the ${chalk.cyan(environment)} environment.`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
22
28
|
const tempFilePath = path.join(os.tmpdir(), `.env.${environment}`);
|
|
23
29
|
const content = await decrypt(key, environmentFilePath);
|
|
24
30
|
await fs.writeFile(tempFilePath, content);
|
|
@@ -29,17 +35,17 @@ export const editCommand = async (environmentArg) => {
|
|
|
29
35
|
execSync(`${editor} ${tempFilePath}`, { stdio: "inherit" });
|
|
30
36
|
}
|
|
31
37
|
catch (error) {
|
|
32
|
-
console.error(
|
|
38
|
+
console.error(`\nFailed to open editor: ${editor}`);
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
41
|
const newContent = await fs.readFile(tempFilePath, "utf-8");
|
|
36
42
|
const finalHash = createHash(newContent);
|
|
37
43
|
if (initialHash === finalHash) {
|
|
38
|
-
console.log(
|
|
44
|
+
console.log(`\nNo changes were made to the ${chalk.cyan(environment)} environment.`);
|
|
39
45
|
}
|
|
40
46
|
else {
|
|
41
47
|
await encrypt(key, newContent, environmentFilePath);
|
|
42
|
-
console.log(
|
|
48
|
+
console.log(`\nEncrypted ${chalk.cyan(environment)} environment and saved it to ${chalk.gray(environmentFile)}.`);
|
|
43
49
|
}
|
|
44
50
|
await fs.unlink(tempFilePath);
|
|
45
51
|
};
|
package/dist/commands/init.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { createEnvironment } from "../helpers/createEnvironment.js";
|
|
3
4
|
import { createLocalEnvironment } from "../helpers/createLocalEnvironment.js";
|
|
4
5
|
import { createProject } from "../helpers/createProject.js";
|
|
6
|
+
import { environmentExists } from "../helpers/environmentExists.js";
|
|
7
|
+
import { getEnvironmentNameSuggestion } from "../helpers/getEnvironmentNameSuggestion.js";
|
|
5
8
|
import { addKey } from "../helpers/key.js";
|
|
6
9
|
import { createEnvironmentPrompt } from "../prompts/createEnvironment.js";
|
|
7
10
|
export const initCommand = async (environmentArg) => {
|
|
@@ -14,16 +17,28 @@ export const initCommand = async (environmentArg) => {
|
|
|
14
17
|
// Prompt for the environment name
|
|
15
18
|
let environment = environmentArg;
|
|
16
19
|
if (!environment) {
|
|
17
|
-
environment = await createEnvironmentPrompt("What should the environment be named?",
|
|
20
|
+
environment = await createEnvironmentPrompt("What should the environment be named?", getEnvironmentNameSuggestion());
|
|
21
|
+
}
|
|
22
|
+
if (!environment) {
|
|
23
|
+
console.log(`${chalk.red("Error:")} no environment name provided`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (environmentExists(environment)) {
|
|
27
|
+
console.log(`${chalk.red("Error:")} environment ${environment} already exists. To edit it, use ${chalk.gray(`dotenc edit ${environment}`)}`);
|
|
28
|
+
return;
|
|
18
29
|
}
|
|
19
30
|
await createEnvironment(environment, key);
|
|
20
31
|
// Store the key
|
|
21
32
|
await addKey(projectId, environment, key);
|
|
22
33
|
// Output success message
|
|
23
|
-
console.log("Initialization complete
|
|
24
|
-
console.log("
|
|
25
|
-
|
|
26
|
-
console.log(
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
console.log(`${chalk.green("✔")} Initialization complete!`);
|
|
35
|
+
console.log("\nSome useful tips:");
|
|
36
|
+
const editCommand = chalk.gray(`dotenc edit ${environment}`);
|
|
37
|
+
console.log(`\n- To securely edit your environment:\t${editCommand}`);
|
|
38
|
+
const runCommand = chalk.gray(`dotenc run -e ${environment} <command> [args...]`);
|
|
39
|
+
const runCommandWithEnv = chalk.gray(`DOTENC_ENV=${environment} dotenc run <command> [args...]`);
|
|
40
|
+
console.log(`- To run your application:\t\t${runCommand} or ${runCommandWithEnv}`);
|
|
41
|
+
const initCommand = chalk.gray("dotenc init [environment]");
|
|
42
|
+
console.log(`- To initialize a new environment:\t${initCommand}`);
|
|
43
|
+
console.log(`- Use the git-ignored ${chalk.gray(".env")} file for local development. It will have priority over any encrypted environments.`);
|
|
29
44
|
};
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import { getKey } from "../../helpers/key.js";
|
|
2
3
|
import { getProjectConfig } from "../../helpers/projectConfig.js";
|
|
4
|
+
import { chooseEnvironmentPrompt } from "../../prompts/chooseEnvironment.js";
|
|
3
5
|
export const keyExportCommand = async (environmentArg) => {
|
|
4
|
-
const environment = environmentArg;
|
|
5
6
|
const { projectId } = await getProjectConfig();
|
|
6
7
|
if (!projectId) {
|
|
7
8
|
console.error('No project found. Run "dotenc init" to create one.');
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
11
|
+
let environment = environmentArg;
|
|
12
|
+
if (!environment) {
|
|
13
|
+
environment = await chooseEnvironmentPrompt("What environment do you want to export the key from?");
|
|
14
|
+
}
|
|
10
15
|
const key = await getKey(environment);
|
|
11
|
-
|
|
16
|
+
if (!key) {
|
|
17
|
+
console.error(`\nNo key found for the ${chalk.cyan(environment)} environment.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(`\nKey for the ${chalk.cyan(environment)} environment: ${chalk.gray(key)}`);
|
|
12
21
|
};
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
1
2
|
import { addKey } from "../../helpers/key.js";
|
|
2
3
|
import { getProjectConfig } from "../../helpers/projectConfig.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import { chooseEnvironmentPrompt } from "../../prompts/chooseEnvironment.js";
|
|
5
|
+
import { inputKeyPrompt } from "../../prompts/inputKey.js";
|
|
6
|
+
export const keyImportCommand = async (environmentArg, keyArg) => {
|
|
5
7
|
const { projectId } = await getProjectConfig();
|
|
6
8
|
if (!projectId) {
|
|
7
9
|
console.error('No project found. Run "dotenc init" to create one.');
|
|
8
10
|
return;
|
|
9
11
|
}
|
|
12
|
+
let environment = environmentArg;
|
|
13
|
+
if (!environment) {
|
|
14
|
+
environment = await chooseEnvironmentPrompt("What environment do you want to import the key to?");
|
|
15
|
+
}
|
|
16
|
+
let key = keyArg;
|
|
17
|
+
if (!key) {
|
|
18
|
+
key = await inputKeyPrompt("Paste the key here:");
|
|
19
|
+
}
|
|
10
20
|
await addKey(projectId, environment, key);
|
|
11
|
-
console.log(
|
|
21
|
+
console.log(`\nKey imported to the ${chalk.cyan(environment)} environment.`);
|
|
12
22
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { decrypt, encrypt } from "../../helpers/crypto.js";
|
|
6
|
+
import { addKey, getKey } from "../../helpers/key.js";
|
|
7
|
+
import { getProjectConfig } from "../../helpers/projectConfig.js";
|
|
8
|
+
import { chooseEnvironmentPrompt } from "../../prompts/chooseEnvironment.js";
|
|
9
|
+
export const keyRotateCommand = async (environmentArg) => {
|
|
10
|
+
const { projectId } = await getProjectConfig();
|
|
11
|
+
if (!projectId) {
|
|
12
|
+
console.error('No project found. Run "dotenc init" to create one.');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let environment = environmentArg;
|
|
16
|
+
if (!environment) {
|
|
17
|
+
environment = await chooseEnvironmentPrompt("What environment do you want to rotate the key for?");
|
|
18
|
+
}
|
|
19
|
+
const environmentFile = `.env.${environment}.enc`;
|
|
20
|
+
const environmentFilePath = path.join(process.cwd(), environmentFile);
|
|
21
|
+
if (!existsSync(environmentFilePath)) {
|
|
22
|
+
console.error(`Environment file not found: ${environmentFilePath}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const key = await getKey(environment);
|
|
26
|
+
if (!key) {
|
|
27
|
+
console.error(`\nNo key found for the ${chalk.cyan(environment)} environment.`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const content = await decrypt(key, environmentFilePath);
|
|
31
|
+
const newKey = crypto.randomBytes(32).toString("base64");
|
|
32
|
+
await encrypt(newKey, content, environmentFilePath);
|
|
33
|
+
await addKey(projectId, environment, newKey);
|
|
34
|
+
console.log(`\nKey rotated for the ${chalk.cyan(environment)} environment.`);
|
|
35
|
+
};
|
package/dist/commands/run.js
CHANGED
|
@@ -13,13 +13,13 @@ export const runCommand = async (command, args, options) => {
|
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
const environments = environmentArg.split(",");
|
|
16
|
-
const decryptedEnvs = await Promise.all(environments.map(async (environment) => {
|
|
16
|
+
const decryptedEnvs = await Promise.all(environments.map(async (environment, index) => {
|
|
17
17
|
const environmentFilePath = path.join(process.cwd(), `.env.${environment}.enc`);
|
|
18
18
|
if (!existsSync(environmentFilePath)) {
|
|
19
19
|
console.error(`Environment file not found: ${environmentFilePath}`);
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
const key = await getKey(environment);
|
|
22
|
+
const key = await getKey(environment, index);
|
|
23
23
|
const content = await decrypt(key, environmentFilePath);
|
|
24
24
|
const decryptedEnv = parseEnv(content);
|
|
25
25
|
return decryptedEnv;
|
package/dist/helpers/key.js
CHANGED
|
@@ -3,23 +3,23 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { getProjectConfig } from "./projectConfig.js";
|
|
6
|
-
|
|
6
|
+
const keysFile = path.join(os.homedir(), ".dotenc", "keys.json");
|
|
7
|
+
export const getKey = async (environment, index = 0) => {
|
|
7
8
|
if (process.env.DOTENC_KEY) {
|
|
8
|
-
|
|
9
|
+
const keys = process.env.DOTENC_KEY.split(",");
|
|
10
|
+
return keys[index];
|
|
9
11
|
}
|
|
10
12
|
const { projectId } = await getProjectConfig();
|
|
11
|
-
const keysFile = path.join(os.homedir(), ".dotenc", "keys.json");
|
|
12
13
|
if (existsSync(keysFile)) {
|
|
13
14
|
const keys = JSON.parse(await fs.readFile(keysFile, "utf-8"));
|
|
14
15
|
return keys[projectId][environment];
|
|
15
16
|
}
|
|
16
|
-
throw new Error("No key found. Please set the DOTENC_KEY environment variable or import the key using `dotenc
|
|
17
|
+
throw new Error("No key found. Please set the DOTENC_KEY environment variable or import the key using `dotenc key import <environment> <key>`.");
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
19
20
|
* Adds or updates a key for a specific project and environment.
|
|
20
21
|
*/
|
|
21
22
|
export const addKey = async (projectId, environment, key) => {
|
|
22
|
-
const keysFile = path.join(os.homedir(), ".dotenc", "keys.json");
|
|
23
23
|
// Ensure the keys file exists
|
|
24
24
|
if (!existsSync(keysFile)) {
|
|
25
25
|
await fs.mkdir(path.dirname(keysFile), { recursive: true });
|
package/dist/helpers/parseEnv.js
CHANGED
|
@@ -70,11 +70,13 @@ export const parseEnv = (content) => {
|
|
|
70
70
|
}
|
|
71
71
|
// Handle single quote opening
|
|
72
72
|
if (char === "'") {
|
|
73
|
+
currentValue = "";
|
|
73
74
|
isInSingleQuotes = true;
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
77
|
// Handle double quote opening
|
|
77
78
|
if (char === '"') {
|
|
79
|
+
currentValue = "";
|
|
78
80
|
isInDoubleQuotes = true;
|
|
79
81
|
continue;
|
|
80
82
|
}
|
package/dist/program.js
CHANGED
|
@@ -8,6 +8,7 @@ import { editCommand } from "./commands/edit.js";
|
|
|
8
8
|
import { initCommand } from "./commands/init.js";
|
|
9
9
|
import { keyExportCommand } from "./commands/key/export.js";
|
|
10
10
|
import { keyImportCommand } from "./commands/key/import.js";
|
|
11
|
+
import { keyRotateCommand } from "./commands/key/rotate.js";
|
|
11
12
|
import { runCommand } from "./commands/run.js";
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -18,29 +19,43 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
18
19
|
program.command("debug").description("debug the CLI").action(debugCommand);
|
|
19
20
|
}
|
|
20
21
|
program
|
|
21
|
-
.command("init
|
|
22
|
+
.command("init")
|
|
23
|
+
.argument("[environment]", "the environment to initialize")
|
|
22
24
|
.description("initialize a new environment")
|
|
23
25
|
.action(initCommand);
|
|
24
26
|
program
|
|
25
|
-
.command("edit
|
|
27
|
+
.command("edit")
|
|
28
|
+
.argument("[environment]", "the environment to edit")
|
|
26
29
|
.description("edit an environment")
|
|
27
30
|
.action(editCommand);
|
|
28
31
|
program
|
|
29
|
-
.command("run
|
|
30
|
-
.
|
|
32
|
+
.command("run")
|
|
33
|
+
.argument("<command>", "the command to run")
|
|
34
|
+
.argument("[args...]", "the arguments to pass to the command")
|
|
35
|
+
.addOption(new Option("-e, --env <env1>[,env2[,...]]", "the environments to run the command in"))
|
|
31
36
|
.description("run a command in an environment")
|
|
32
37
|
.action(runCommand);
|
|
33
|
-
const key = program.command("key").description("
|
|
38
|
+
const key = program.command("key").description("manage stored keys");
|
|
34
39
|
key
|
|
35
|
-
.command("import
|
|
40
|
+
.command("import")
|
|
41
|
+
.argument("[environment]", "the environment to import the key to")
|
|
42
|
+
.argument("[key]", "the key to import")
|
|
36
43
|
.description("import a key for an environment")
|
|
37
44
|
.action(keyImportCommand);
|
|
38
45
|
key
|
|
39
|
-
.command("export
|
|
46
|
+
.command("export")
|
|
47
|
+
.argument("[environment]", "the environment to export the key from")
|
|
40
48
|
.description("export a key from an environment")
|
|
41
49
|
.action(keyExportCommand);
|
|
50
|
+
key
|
|
51
|
+
.command("rotate")
|
|
52
|
+
.argument("[environment]", "the environment to rotate the key for")
|
|
53
|
+
.description("rotate a key for an environment")
|
|
54
|
+
.action(keyRotateCommand);
|
|
42
55
|
program
|
|
43
|
-
.command("config
|
|
56
|
+
.command("config")
|
|
57
|
+
.argument("<key>", "the key to get or set")
|
|
58
|
+
.argument("[value]", "the value to set the key to")
|
|
44
59
|
.addOption(new Option("-r, --remove", "remove a configuration key"))
|
|
45
60
|
.description("manage global configuration")
|
|
46
61
|
.action(configCommand);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
export const inputKeyPrompt = async (message, defaultValue) => {
|
|
3
|
+
const result = await inquirer.prompt([
|
|
4
|
+
{
|
|
5
|
+
type: "password",
|
|
6
|
+
name: "key",
|
|
7
|
+
mask: "*",
|
|
8
|
+
message,
|
|
9
|
+
default: defaultValue,
|
|
10
|
+
},
|
|
11
|
+
]);
|
|
12
|
+
return result.key;
|
|
13
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
|
5
|
+
import { editCommand } from "../commands/edit.js";
|
|
6
|
+
import { initCommand } from "../commands/init.js";
|
|
7
|
+
import { keyRotateCommand } from "../commands/key/rotate.js";
|
|
8
|
+
import { runCommand } from "../commands/run.js";
|
|
9
|
+
import { getKey } from "../helpers/key.js";
|
|
10
|
+
import { cleanupProjectKeys } from "./helpers/cleanupProjectKeys.js";
|
|
11
|
+
import { waitForFile } from "./helpers/waitForFile.js";
|
|
12
|
+
const localEnvFilePath = path.join(process.cwd(), ".env");
|
|
13
|
+
const encryptedEnvFilePath = path.join(process.cwd(), ".env.test.enc");
|
|
14
|
+
const projectFilePath = path.join(process.cwd(), "dotenc.json");
|
|
15
|
+
const outputFilePath = path.join(process.cwd(), "e2e.txt");
|
|
16
|
+
vi.mock("node:child_process", async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal();
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
// Mock for the edit command
|
|
21
|
+
execSync: () => {
|
|
22
|
+
const tempFilePath = path.join(os.tmpdir(), ".env.test");
|
|
23
|
+
writeFileSync(tempFilePath, "DOTENC_HELLO=Hello, world!");
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
describe("e2e", () => {
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
vi.spyOn(console, "log").mockImplementation(() => { });
|
|
30
|
+
vi.spyOn(process, "exit").mockImplementation(() => ({}));
|
|
31
|
+
});
|
|
32
|
+
test("should initialize an environment", async () => {
|
|
33
|
+
await initCommand("test");
|
|
34
|
+
expect(existsSync(localEnvFilePath)).toBe(true);
|
|
35
|
+
expect(existsSync(encryptedEnvFilePath)).toBe(true);
|
|
36
|
+
expect(existsSync(projectFilePath)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
test("should edit an environment", async () => {
|
|
39
|
+
const initialContent = readFileSync(encryptedEnvFilePath, "utf-8");
|
|
40
|
+
await editCommand("test");
|
|
41
|
+
const editedContent = readFileSync(encryptedEnvFilePath, "utf-8");
|
|
42
|
+
expect(editedContent).not.toBe(initialContent);
|
|
43
|
+
});
|
|
44
|
+
test("should rotate a key", async () => {
|
|
45
|
+
const currentKey = await getKey("test");
|
|
46
|
+
await keyRotateCommand("test");
|
|
47
|
+
const rotatedKey = await getKey("test");
|
|
48
|
+
expect(rotatedKey).not.toBe(currentKey);
|
|
49
|
+
});
|
|
50
|
+
test("should run a command in an environment", async () => {
|
|
51
|
+
await runCommand("sh", [path.join(__dirname, "helpers", "e2e.sh")], {
|
|
52
|
+
env: "test",
|
|
53
|
+
});
|
|
54
|
+
const output = await waitForFile(outputFilePath);
|
|
55
|
+
expect(output).toBe("Hello, world!\n");
|
|
56
|
+
});
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
await cleanupProjectKeys();
|
|
59
|
+
unlinkSync(localEnvFilePath);
|
|
60
|
+
unlinkSync(encryptedEnvFilePath);
|
|
61
|
+
unlinkSync(projectFilePath);
|
|
62
|
+
unlinkSync(outputFilePath);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getProjectConfig } from "../../helpers/projectConfig.js";
|
|
5
|
+
export const cleanupProjectKeys = async () => {
|
|
6
|
+
const { projectId } = await getProjectConfig();
|
|
7
|
+
const keysFile = path.join(os.homedir(), ".dotenc", "keys.json");
|
|
8
|
+
if (existsSync(keysFile)) {
|
|
9
|
+
const keys = JSON.parse(readFileSync(keysFile, "utf-8"));
|
|
10
|
+
delete keys[projectId];
|
|
11
|
+
writeFileSync(keysFile, JSON.stringify(keys, null, 2));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
export const waitForFile = (filePath, timeout = 5000) => new Promise((resolve, reject) => {
|
|
3
|
+
const startTime = Date.now();
|
|
4
|
+
const interval = setInterval(() => {
|
|
5
|
+
if (existsSync(filePath)) {
|
|
6
|
+
clearInterval(interval);
|
|
7
|
+
resolve(readFileSync(filePath, "utf-8"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (Date.now() - startTime > timeout) {
|
|
11
|
+
clearInterval(interval);
|
|
12
|
+
reject(new Error(`Timeout waiting for file ${filePath}`));
|
|
13
|
+
}
|
|
14
|
+
}, 100);
|
|
15
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { parseEnv } from "
|
|
2
|
+
import { parseEnv } from "../helpers/parseEnv.js";
|
|
3
3
|
describe("parseEnv", () => {
|
|
4
4
|
test("should parse a simple key-value pair", () => {
|
|
5
5
|
const env = parseEnv("FOO=bar");
|
|
@@ -19,10 +19,12 @@ foo
|
|
|
19
19
|
# Comment here "!#' foo
|
|
20
20
|
|
|
21
21
|
HELLO = WORLD
|
|
22
|
+
DOTENC_HELLO = "Hello, world!"
|
|
22
23
|
`);
|
|
23
24
|
expect(env.FOO).toBe("\nbar");
|
|
24
25
|
expect(env.BAR).toBe("baz\nfoo\n");
|
|
25
26
|
expect(env.BAZ).toBe("123");
|
|
26
27
|
expect(env.HELLO).toBe("WORLD");
|
|
28
|
+
expect(env.DOTENC_HELLO).toBe("Hello, world!");
|
|
27
29
|
});
|
|
28
30
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotenc/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "🔐 Secure, encrypted environment variables that live in your codebase",
|
|
5
|
+
"author": "Ivan Filho <i@ivanfilho.com>",
|
|
6
|
+
"license": "MIT",
|
|
5
7
|
"type": "module",
|
|
6
8
|
"bin": {
|
|
7
9
|
"dotenc": "./dist/cli.js"
|
|
@@ -9,6 +11,29 @@
|
|
|
9
11
|
"files": [
|
|
10
12
|
"dist"
|
|
11
13
|
],
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@biomejs/biome": "^1.9.4",
|
|
16
|
+
"@types/node": "^22.13.7",
|
|
17
|
+
"tsc-alias": "^1.8.11",
|
|
18
|
+
"tsx": "^4.19.3",
|
|
19
|
+
"typescript": "^5.8.2",
|
|
20
|
+
"vitest": "^3.0.9"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@paralleldrive/cuid2": "^2.2.2",
|
|
24
|
+
"chalk": "^5.4.1",
|
|
25
|
+
"commander": "^13.1.0",
|
|
26
|
+
"inquirer": "^12.4.2",
|
|
27
|
+
"zod": "^3.24.2"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ivanfilhoz/dotenc.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/ivanfilhoz/dotenc/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/ivanfilhoz/dotenc#readme",
|
|
12
37
|
"keywords": [
|
|
13
38
|
"environment",
|
|
14
39
|
"variables",
|
|
@@ -26,23 +51,6 @@
|
|
|
26
51
|
"encrypted",
|
|
27
52
|
"codebase"
|
|
28
53
|
],
|
|
29
|
-
"author": "Ivan Filho <i@ivanfilho.com>",
|
|
30
|
-
"license": "MIT",
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"@biomejs/biome": "^1.9.4",
|
|
33
|
-
"@types/node": "^22.13.7",
|
|
34
|
-
"tsc-alias": "^1.8.11",
|
|
35
|
-
"tsx": "^4.19.3",
|
|
36
|
-
"typescript": "^5.8.2",
|
|
37
|
-
"vitest": "^3.0.9"
|
|
38
|
-
},
|
|
39
|
-
"dependencies": {
|
|
40
|
-
"@paralleldrive/cuid2": "^2.2.2",
|
|
41
|
-
"chalk": "^5.4.1",
|
|
42
|
-
"commander": "^13.1.0",
|
|
43
|
-
"inquirer": "^12.4.2",
|
|
44
|
-
"zod": "^3.24.2"
|
|
45
|
-
},
|
|
46
54
|
"scripts": {
|
|
47
55
|
"dev": "tsx src/cli.ts",
|
|
48
56
|
"start": "node dist/cli.js",
|