envhub-cli 0.1.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.
- package/README.md +60 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +92 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/cat.d.ts +6 -0
- package/dist/commands/cat.d.ts.map +1 -0
- package/dist/commands/cat.js +55 -0
- package/dist/commands/cat.js.map +1 -0
- package/dist/commands/delete.d.ts +10 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +46 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/grant.d.ts +6 -0
- package/dist/commands/grant.d.ts.map +1 -0
- package/dist/commands/grant.js +25 -0
- package/dist/commands/grant.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +275 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +46 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/pull.d.ts +6 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +35 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +126 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/revoke.d.ts +6 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +25 -0
- package/dist/commands/revoke.js.map +1 -0
- package/dist/config/config.d.ts +52 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +136 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/config.schema.d.ts +66 -0
- package/dist/config/config.schema.d.ts.map +1 -0
- package/dist/config/config.schema.js +33 -0
- package/dist/config/config.schema.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/aws/aws-secrets.provider.d.ts +44 -0
- package/dist/providers/aws/aws-secrets.provider.d.ts.map +1 -0
- package/dist/providers/aws/aws-secrets.provider.js +306 -0
- package/dist/providers/aws/aws-secrets.provider.js.map +1 -0
- package/dist/providers/provider.factory.d.ts +24 -0
- package/dist/providers/provider.factory.d.ts.map +1 -0
- package/dist/providers/provider.factory.js +40 -0
- package/dist/providers/provider.factory.js.map +1 -0
- package/dist/providers/provider.interface.d.ts +96 -0
- package/dist/providers/provider.interface.d.ts.map +1 -0
- package/dist/providers/provider.interface.js +2 -0
- package/dist/providers/provider.interface.js.map +1 -0
- package/dist/utils/diff.d.ts +27 -0
- package/dist/utils/diff.d.ts.map +1 -0
- package/dist/utils/diff.js +105 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/env-parser.d.ts +48 -0
- package/dist/utils/env-parser.d.ts.map +1 -0
- package/dist/utils/env-parser.js +84 -0
- package/dist/utils/env-parser.js.map +1 -0
- package/dist/utils/logger.d.ts +57 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +82 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/versioning/version-control.d.ts +44 -0
- package/dist/versioning/version-control.d.ts.map +1 -0
- package/dist/versioning/version-control.js +81 -0
- package/dist/versioning/version-control.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# envhub Documentation
|
|
2
|
+
|
|
3
|
+
**envhub** is a CLI tool that makes sharing `.env` files between developers easy and secure. Instead of sending secrets over chat messages, envhub stores them in a cloud secrets manager and lets your team push and pull environment configurations safely.
|
|
4
|
+
|
|
5
|
+
## Why envhub?
|
|
6
|
+
|
|
7
|
+
- No more sending API keys over Teams or Slack
|
|
8
|
+
- Built-in version control prevents accidental overwrites
|
|
9
|
+
- Easy interactive setup — no manual config files needed
|
|
10
|
+
- Extensible provider architecture (AWS today, Azure & GCP coming soon)
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
### Getting Started
|
|
15
|
+
|
|
16
|
+
1. [Installation](docs/getting-started/installation.md)
|
|
17
|
+
2. [Setup (envhub init)](docs/getting-started/setup.md)
|
|
18
|
+
3. [Your First Secret](docs/getting-started/first-secret.md)
|
|
19
|
+
4. [Version Control](docs/getting-started/version-control.md)
|
|
20
|
+
|
|
21
|
+
### Commands
|
|
22
|
+
|
|
23
|
+
| Command | Description |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| [push](docs/commands/push.md) | Push a local .env file to the cloud |
|
|
26
|
+
| [pull](docs/commands/pull.md) | Pull the latest .env file from the cloud |
|
|
27
|
+
| [cat](docs/commands/cat.md) | Display the contents of a secret |
|
|
28
|
+
| [list](docs/commands/list.md) | List all managed secrets |
|
|
29
|
+
| [delete](docs/commands/delete.md) | Delete a secret |
|
|
30
|
+
| [grant](docs/commands/grant.md) | Grant a user access to a secret |
|
|
31
|
+
| [revoke](docs/commands/revoke.md) | Revoke a user's access to a secret |
|
|
32
|
+
|
|
33
|
+
### Architecture
|
|
34
|
+
|
|
35
|
+
- [Configuration (.envhubrc.json)](docs/architecture/configuration.md)
|
|
36
|
+
- [Provider Architecture](docs/architecture/providers.md)
|
|
37
|
+
|
|
38
|
+
## Quick Example
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 1. Set up your project
|
|
42
|
+
npx envhub init
|
|
43
|
+
|
|
44
|
+
# 2. Push your .env file
|
|
45
|
+
npx envhub push my-app-dev ./.env -m "Initial setup"
|
|
46
|
+
|
|
47
|
+
# 3. Your teammate pulls it
|
|
48
|
+
npx envhub pull my-app-dev ./.env
|
|
49
|
+
|
|
50
|
+
# 4. Grant access to another developer
|
|
51
|
+
npx envhub grant my-app-dev jane.doe
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Supported Providers
|
|
55
|
+
|
|
56
|
+
| Provider | Status |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| AWS Secrets Manager | Available |
|
|
59
|
+
| Azure Key Vault | Planned |
|
|
60
|
+
| GCP Secret Manager | Planned |
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAkGvC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { initCommand } from "./commands/init.js";
|
|
3
|
+
import { pushCommand } from "./commands/push.js";
|
|
4
|
+
import { pullCommand } from "./commands/pull.js";
|
|
5
|
+
import { catCommand } from "./commands/cat.js";
|
|
6
|
+
import { listCommand } from "./commands/list.js";
|
|
7
|
+
import { deleteCommand } from "./commands/delete.js";
|
|
8
|
+
import { grantCommand } from "./commands/grant.js";
|
|
9
|
+
import { revokeCommand } from "./commands/revoke.js";
|
|
10
|
+
/**
|
|
11
|
+
* Create and configure the CLI program with all commands.
|
|
12
|
+
*/
|
|
13
|
+
export function createProgram() {
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name("envhub")
|
|
17
|
+
.description("Securely share .env files between developers using cloud providers.")
|
|
18
|
+
.version("0.1.0");
|
|
19
|
+
// ── init ────────────────────────────────────────────────────────
|
|
20
|
+
program
|
|
21
|
+
.command("init")
|
|
22
|
+
.description("Set up envhub for your project (interactive wizard)")
|
|
23
|
+
.action(async () => {
|
|
24
|
+
await initCommand();
|
|
25
|
+
});
|
|
26
|
+
// ── push ────────────────────────────────────────────────────────
|
|
27
|
+
program
|
|
28
|
+
.command("push")
|
|
29
|
+
.description("Push a local .env file to the cloud provider")
|
|
30
|
+
.argument("<name>", "Name for the secret")
|
|
31
|
+
.argument("<file>", "Path to the .env file")
|
|
32
|
+
.option("-m, --message <message>", "A message describing this version")
|
|
33
|
+
.option("-f, --force", "Bypass version conflict checking", false)
|
|
34
|
+
.action(async (name, file, options) => {
|
|
35
|
+
await pushCommand(name, file, options);
|
|
36
|
+
});
|
|
37
|
+
// ── pull ────────────────────────────────────────────────────────
|
|
38
|
+
program
|
|
39
|
+
.command("pull")
|
|
40
|
+
.description("Pull the latest .env file from the cloud provider")
|
|
41
|
+
.argument("<name>", "Name of the secret to pull")
|
|
42
|
+
.argument("<file>", "Path to write the .env file to")
|
|
43
|
+
.action(async (name, file) => {
|
|
44
|
+
await pullCommand(name, file);
|
|
45
|
+
});
|
|
46
|
+
// ── cat ─────────────────────────────────────────────────────────
|
|
47
|
+
program
|
|
48
|
+
.command("cat")
|
|
49
|
+
.description("Display the contents of a secret")
|
|
50
|
+
.argument("<name>", "Name of the secret to display")
|
|
51
|
+
.action(async (name) => {
|
|
52
|
+
await catCommand(name);
|
|
53
|
+
});
|
|
54
|
+
// ── list ────────────────────────────────────────────────────────
|
|
55
|
+
program
|
|
56
|
+
.command("list")
|
|
57
|
+
.alias("ls")
|
|
58
|
+
.description("List all secrets managed by envhub")
|
|
59
|
+
.action(async () => {
|
|
60
|
+
await listCommand();
|
|
61
|
+
});
|
|
62
|
+
// ── delete ──────────────────────────────────────────────────────
|
|
63
|
+
program
|
|
64
|
+
.command("delete")
|
|
65
|
+
.alias("rm")
|
|
66
|
+
.description("Delete a secret from the cloud provider")
|
|
67
|
+
.argument("<name>", "Name of the secret to delete")
|
|
68
|
+
.option("-f, --force", "Force immediate deletion", false)
|
|
69
|
+
.action(async (name, options) => {
|
|
70
|
+
await deleteCommand(name, options);
|
|
71
|
+
});
|
|
72
|
+
// ── grant ───────────────────────────────────────────────────────
|
|
73
|
+
program
|
|
74
|
+
.command("grant")
|
|
75
|
+
.description("Grant another user access to a secret")
|
|
76
|
+
.argument("<name>", "Name of the secret")
|
|
77
|
+
.argument("<user>", "IAM username or ARN of the user to grant access")
|
|
78
|
+
.action(async (name, user) => {
|
|
79
|
+
await grantCommand(name, user);
|
|
80
|
+
});
|
|
81
|
+
// ── revoke ──────────────────────────────────────────────────────
|
|
82
|
+
program
|
|
83
|
+
.command("revoke")
|
|
84
|
+
.description("Revoke a user's access to a secret")
|
|
85
|
+
.argument("<name>", "Name of the secret")
|
|
86
|
+
.argument("<user>", "IAM username or ARN of the user to revoke access")
|
|
87
|
+
.action(async (name, user) => {
|
|
88
|
+
await revokeCommand(name, user);
|
|
89
|
+
});
|
|
90
|
+
return program;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cat.d.ts","sourceRoot":"","sources":["../../src/commands/cat.ts"],"names":[],"mappings":"AAqCA;;;GAGG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBlE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config/config.js";
|
|
3
|
+
import { ProviderFactory } from "../providers/provider.factory.js";
|
|
4
|
+
import { parseEnvContent } from "../utils/env-parser.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Format .env content into a styled, readable table.
|
|
8
|
+
*/
|
|
9
|
+
function formatEnvTable(content) {
|
|
10
|
+
const entries = parseEnvContent(content);
|
|
11
|
+
if (entries.size === 0) {
|
|
12
|
+
return chalk.dim(" (empty)");
|
|
13
|
+
}
|
|
14
|
+
// Find the longest key for alignment
|
|
15
|
+
let maxKeyLen = 0;
|
|
16
|
+
for (const key of entries.keys()) {
|
|
17
|
+
if (key.length > maxKeyLen)
|
|
18
|
+
maxKeyLen = key.length;
|
|
19
|
+
}
|
|
20
|
+
const lines = [];
|
|
21
|
+
const separator = chalk.dim("─".repeat(maxKeyLen + 40));
|
|
22
|
+
lines.push(separator);
|
|
23
|
+
for (const [key, value] of entries) {
|
|
24
|
+
const paddedKey = key.padEnd(maxKeyLen);
|
|
25
|
+
lines.push(` ${chalk.bold.cyan(paddedKey)} ${chalk.dim("=")} ${value}`);
|
|
26
|
+
}
|
|
27
|
+
lines.push(separator);
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The `envhub cat` command.
|
|
32
|
+
* Outputs the contents of a secret without writing to disk.
|
|
33
|
+
*/
|
|
34
|
+
export async function catCommand(secretName) {
|
|
35
|
+
// Load config and create provider
|
|
36
|
+
const config = await configManager.load();
|
|
37
|
+
const provider = ProviderFactory.createProvider(config);
|
|
38
|
+
const spinner = logger.spinner(`Reading '${secretName}'...`);
|
|
39
|
+
try {
|
|
40
|
+
const content = await provider.cat(secretName);
|
|
41
|
+
const entries = parseEnvContent(content);
|
|
42
|
+
spinner.succeed(`${chalk.bold(secretName)} ${chalk.dim(`(${entries.size} keys)`)}`);
|
|
43
|
+
logger.newline();
|
|
44
|
+
logger.log(formatEnvTable(content));
|
|
45
|
+
logger.newline();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
spinner.fail(`Failed to read '${secretName}'.`);
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
logger.error(error.message);
|
|
51
|
+
}
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=cat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cat.js","sourceRoot":"","sources":["../../src/commands/cat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAED,qCAAqC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACjC,IAAI,GAAG,CAAC,MAAM,GAAG,SAAS;YAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC;IAExD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;IAE7D,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACzC,OAAO,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QAEpF,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mBAAmB,UAAU,IAAI,CAAC,CAAC;QAChD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface DeleteCommandOptions {
|
|
2
|
+
force?: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* The `envhub delete` command.
|
|
6
|
+
* Deletes a secret from the cloud provider.
|
|
7
|
+
*/
|
|
8
|
+
export declare function deleteCommand(secretName: string, options: DeleteCommandOptions): Promise<void>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=delete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAKA,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
import { configManager } from "../config/config.js";
|
|
3
|
+
import { ProviderFactory } from "../providers/provider.factory.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* The `envhub delete` command.
|
|
7
|
+
* Deletes a secret from the cloud provider.
|
|
8
|
+
*/
|
|
9
|
+
export async function deleteCommand(secretName, options) {
|
|
10
|
+
// Load config and create provider
|
|
11
|
+
const config = await configManager.load();
|
|
12
|
+
const provider = ProviderFactory.createProvider(config);
|
|
13
|
+
// Confirm deletion
|
|
14
|
+
if (!options.force) {
|
|
15
|
+
const confirmed = await confirm({
|
|
16
|
+
message: `Are you sure you want to delete '${secretName}'? This action cannot be undone.`,
|
|
17
|
+
default: false,
|
|
18
|
+
});
|
|
19
|
+
if (!confirmed) {
|
|
20
|
+
logger.info("Deletion cancelled.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const spinner = logger.spinner(`Deleting '${secretName}'...`);
|
|
25
|
+
try {
|
|
26
|
+
await provider.delete(secretName, { force: options.force });
|
|
27
|
+
// Remove from local tracking
|
|
28
|
+
const cfg = configManager.getConfig();
|
|
29
|
+
if (cfg.secrets[secretName]) {
|
|
30
|
+
delete cfg.secrets[secretName];
|
|
31
|
+
await configManager.save(cfg);
|
|
32
|
+
}
|
|
33
|
+
spinner.succeed(`Deleted '${secretName}'.`);
|
|
34
|
+
if (!options.force) {
|
|
35
|
+
logger.dim(" Note: The secret is scheduled for deletion. Use --force for immediate deletion.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
spinner.fail(`Failed to delete '${secretName}'.`);
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
logger.error(error.message);
|
|
42
|
+
}
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.js","sourceRoot":"","sources":["../../src/commands/delete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAM5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAkB,EAClB,OAA6B;IAE7B,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,mBAAmB;IACnB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC;YAC9B,OAAO,EAAE,oCAAoC,UAAU,kCAAkC;YACzF,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,UAAU,MAAM,CAAC,CAAC;IAE9D,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,6BAA6B;QAC7B,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC/B,MAAM,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,YAAY,UAAU,IAAI,CAAC,CAAC;QAE5C,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,CACR,mFAAmF,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,qBAAqB,UAAU,IAAI,CAAC,CAAC;QAClD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant.d.ts","sourceRoot":"","sources":["../../src/commands/grant.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { configManager } from "../config/config.js";
|
|
2
|
+
import { ProviderFactory } from "../providers/provider.factory.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* The `envhub grant` command.
|
|
6
|
+
* Grants another user access to a secret.
|
|
7
|
+
*/
|
|
8
|
+
export async function grantCommand(secretName, userIdentifier) {
|
|
9
|
+
// Load config and create provider
|
|
10
|
+
const config = await configManager.load();
|
|
11
|
+
const provider = ProviderFactory.createProvider(config);
|
|
12
|
+
const spinner = logger.spinner(`Granting access to '${secretName}' for '${userIdentifier}'...`);
|
|
13
|
+
try {
|
|
14
|
+
await provider.grant(secretName, userIdentifier);
|
|
15
|
+
spinner.succeed(`Granted '${userIdentifier}' access to '${secretName}'.`);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
spinner.fail(`Failed to grant access.`);
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
logger.error(error.message);
|
|
21
|
+
}
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=grant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant.js","sourceRoot":"","sources":["../../src/commands/grant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAkB,EAClB,cAAsB;IAEtB,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAC5B,uBAAuB,UAAU,UAAU,cAAc,MAAM,CAChE,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACjD,OAAO,CAAC,OAAO,CACb,YAAY,cAAc,gBAAgB,UAAU,IAAI,CACzD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACxC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA6HA;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAsLjD"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { select, input, confirm } from "@inquirer/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
import { ConfigManager } from "../config/config.js";
|
|
7
|
+
import { ProviderFactory } from "../providers/provider.factory.js";
|
|
8
|
+
/**
|
|
9
|
+
* Read available AWS profiles from ~/.aws/credentials and ~/.aws/config.
|
|
10
|
+
* Also extracts the region for each profile from ~/.aws/config.
|
|
11
|
+
*/
|
|
12
|
+
async function getAWSProfiles() {
|
|
13
|
+
const profileNames = new Set();
|
|
14
|
+
const profileRegions = new Map();
|
|
15
|
+
const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
16
|
+
// Parse credentials file (only profile names)
|
|
17
|
+
const credentialsPath = path.join(homeDir, ".aws", "credentials");
|
|
18
|
+
try {
|
|
19
|
+
const content = await fs.readFile(credentialsPath, "utf-8");
|
|
20
|
+
const profileRegex = /\[([^\]]+)\]/g;
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = profileRegex.exec(content)) !== null) {
|
|
23
|
+
profileNames.add(match[1].trim());
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// File doesn't exist, skip
|
|
28
|
+
}
|
|
29
|
+
// Parse config file (profile names + regions)
|
|
30
|
+
const configPath = path.join(homeDir, ".aws", "config");
|
|
31
|
+
try {
|
|
32
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
let currentProfile = null;
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
// Match profile header: [profile name] or [default]
|
|
38
|
+
const headerMatch = trimmed.match(/^\[(?:profile\s+)?([^\]]+)\]$/);
|
|
39
|
+
if (headerMatch) {
|
|
40
|
+
currentProfile = headerMatch[1].trim();
|
|
41
|
+
profileNames.add(currentProfile);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Match region key under current profile
|
|
45
|
+
if (currentProfile) {
|
|
46
|
+
const regionMatch = trimmed.match(/^region\s*=\s*(.+)$/);
|
|
47
|
+
if (regionMatch) {
|
|
48
|
+
profileRegions.set(currentProfile, regionMatch[1].trim());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// File doesn't exist, skip
|
|
55
|
+
}
|
|
56
|
+
return Array.from(profileNames)
|
|
57
|
+
.sort()
|
|
58
|
+
.map((name) => ({
|
|
59
|
+
name,
|
|
60
|
+
region: profileRegions.get(name),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Common AWS regions for the selection prompt.
|
|
65
|
+
*/
|
|
66
|
+
const AWS_REGIONS = [
|
|
67
|
+
{ name: "EU (Frankfurt) - eu-central-1", value: "eu-central-1" },
|
|
68
|
+
{ name: "EU (Ireland) - eu-west-1", value: "eu-west-1" },
|
|
69
|
+
{ name: "EU (London) - eu-west-2", value: "eu-west-2" },
|
|
70
|
+
{ name: "EU (Paris) - eu-west-3", value: "eu-west-3" },
|
|
71
|
+
{ name: "EU (Stockholm) - eu-north-1", value: "eu-north-1" },
|
|
72
|
+
{ name: "US East (N. Virginia) - us-east-1", value: "us-east-1" },
|
|
73
|
+
{ name: "US East (Ohio) - us-east-2", value: "us-east-2" },
|
|
74
|
+
{ name: "US West (Oregon) - us-west-2", value: "us-west-2" },
|
|
75
|
+
{ name: "Asia Pacific (Tokyo) - ap-northeast-1", value: "ap-northeast-1" },
|
|
76
|
+
{ name: "Asia Pacific (Singapore) - ap-southeast-1", value: "ap-southeast-1" },
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* Update .gitignore to include .envhubrc.json if not already present.
|
|
80
|
+
*/
|
|
81
|
+
async function updateGitignore(dir) {
|
|
82
|
+
const gitignorePath = path.join(dir, ".gitignore");
|
|
83
|
+
const entry = ".envhubrc.json";
|
|
84
|
+
try {
|
|
85
|
+
let content = "";
|
|
86
|
+
try {
|
|
87
|
+
content = await fs.readFile(gitignorePath, "utf-8");
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// .gitignore doesn't exist, we'll create it
|
|
91
|
+
}
|
|
92
|
+
if (content.includes(entry)) {
|
|
93
|
+
return false; // Already in .gitignore
|
|
94
|
+
}
|
|
95
|
+
const newContent = content
|
|
96
|
+
? content.trimEnd() + "\n\n# envhub config (contains AWS profile info)\n" + entry + "\n"
|
|
97
|
+
: "# envhub config (contains AWS profile info)\n" + entry + "\n";
|
|
98
|
+
await fs.writeFile(gitignorePath, newContent, "utf-8");
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The `envhub init` command.
|
|
107
|
+
* Interactive wizard that creates the project configuration.
|
|
108
|
+
*/
|
|
109
|
+
export async function initCommand() {
|
|
110
|
+
logger.log("");
|
|
111
|
+
logger.log(chalk.cyan(" ███████╗███╗ ██╗██╗ ██╗██╗ ██╗██╗ ██╗██████╗ "));
|
|
112
|
+
logger.log(chalk.cyan(" ██╔════╝████╗ ██║██║ ██║██║ ██║██║ ██║██╔══██╗"));
|
|
113
|
+
logger.log(chalk.cyan(" █████╗ ██╔██╗ ██║██║ ██║███████║██║ ██║██████╔╝"));
|
|
114
|
+
logger.log(chalk.cyan(" ██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██╔══██║██║ ██║██╔══██╗"));
|
|
115
|
+
logger.log(chalk.cyan(" ███████╗██║ ╚████║ ╚████╔╝ ██║ ██║╚██████╔╝██████╔╝"));
|
|
116
|
+
logger.log(chalk.cyan(" ╚══════╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "));
|
|
117
|
+
logger.log("");
|
|
118
|
+
logger.log(chalk.dim(" Securely share .env files between developers."));
|
|
119
|
+
logger.log("");
|
|
120
|
+
// Check if config already exists
|
|
121
|
+
const exists = await ConfigManager.exists();
|
|
122
|
+
if (exists) {
|
|
123
|
+
const overwrite = await confirm({
|
|
124
|
+
message: "An .envhubrc.json already exists. Overwrite?",
|
|
125
|
+
default: false,
|
|
126
|
+
});
|
|
127
|
+
if (!overwrite) {
|
|
128
|
+
logger.info("Setup cancelled.");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Step 1: Select provider
|
|
133
|
+
const providers = ProviderFactory.getAvailableProviders();
|
|
134
|
+
const provider = await select({
|
|
135
|
+
message: "Which cloud provider would you like to use?",
|
|
136
|
+
choices: providers.map((p) => ({
|
|
137
|
+
name: p.available ? p.label : `${p.label} (coming soon)`,
|
|
138
|
+
value: p.type,
|
|
139
|
+
disabled: !p.available,
|
|
140
|
+
})),
|
|
141
|
+
});
|
|
142
|
+
// Step 2: Provider-specific configuration
|
|
143
|
+
const config = {
|
|
144
|
+
provider,
|
|
145
|
+
prefix: "envhub-",
|
|
146
|
+
secrets: {},
|
|
147
|
+
};
|
|
148
|
+
if (provider === "aws") {
|
|
149
|
+
// Detect available AWS profiles
|
|
150
|
+
const profiles = await getAWSProfiles();
|
|
151
|
+
let profileName;
|
|
152
|
+
let detectedRegion;
|
|
153
|
+
if (profiles.length > 0) {
|
|
154
|
+
profileName = await select({
|
|
155
|
+
message: "Select your AWS profile:",
|
|
156
|
+
choices: [
|
|
157
|
+
...profiles.map((p) => ({
|
|
158
|
+
name: p.region ? `${p.name} (${p.region})` : p.name,
|
|
159
|
+
value: p.name,
|
|
160
|
+
})),
|
|
161
|
+
{ name: "Enter a different profile name...", value: "__custom__" },
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
if (profileName === "__custom__") {
|
|
165
|
+
profileName = await input({
|
|
166
|
+
message: "Enter your AWS profile name:",
|
|
167
|
+
validate: (val) => (val.trim() ? true : "Profile name is required."),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Get the region from the selected profile
|
|
172
|
+
detectedRegion = profiles.find((p) => p.name === profileName)?.region;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
logger.warn("No AWS profiles found in ~/.aws/credentials or ~/.aws/config.");
|
|
177
|
+
logger.dim(" Create one first: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html");
|
|
178
|
+
logger.log("");
|
|
179
|
+
profileName = await input({
|
|
180
|
+
message: "Enter your AWS profile name:",
|
|
181
|
+
validate: (val) => (val.trim() ? true : "Profile name is required."),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Select region — use detected region from profile if available
|
|
185
|
+
let finalRegion;
|
|
186
|
+
if (detectedRegion) {
|
|
187
|
+
const useDetected = await confirm({
|
|
188
|
+
message: `Use region '${detectedRegion}' from your AWS profile?`,
|
|
189
|
+
default: true,
|
|
190
|
+
});
|
|
191
|
+
if (useDetected) {
|
|
192
|
+
finalRegion = detectedRegion;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
finalRegion = await select({
|
|
196
|
+
message: "Select a different AWS region:",
|
|
197
|
+
choices: [
|
|
198
|
+
...AWS_REGIONS,
|
|
199
|
+
{ name: "Enter a custom region...", value: "__custom__" },
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
if (finalRegion === "__custom__") {
|
|
203
|
+
finalRegion = await input({
|
|
204
|
+
message: "Enter the AWS region (e.g. eu-central-1):",
|
|
205
|
+
validate: (val) => (val.trim() ? true : "Region is required."),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
finalRegion = await select({
|
|
212
|
+
message: "Select your AWS region:",
|
|
213
|
+
choices: [
|
|
214
|
+
...AWS_REGIONS,
|
|
215
|
+
{ name: "Enter a custom region...", value: "__custom__" },
|
|
216
|
+
],
|
|
217
|
+
default: "eu-central-1",
|
|
218
|
+
});
|
|
219
|
+
if (finalRegion === "__custom__") {
|
|
220
|
+
finalRegion = await input({
|
|
221
|
+
message: "Enter the AWS region (e.g. eu-central-1):",
|
|
222
|
+
validate: (val) => (val.trim() ? true : "Region is required."),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
config.aws = {
|
|
227
|
+
profile: profileName,
|
|
228
|
+
region: finalRegion,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Step 3: Configure prefix
|
|
232
|
+
const customPrefix = await confirm({
|
|
233
|
+
message: `Use default secret prefix "${config.prefix}"?`,
|
|
234
|
+
default: true,
|
|
235
|
+
});
|
|
236
|
+
if (!customPrefix) {
|
|
237
|
+
config.prefix = await input({
|
|
238
|
+
message: "Enter a custom prefix for your secrets:",
|
|
239
|
+
default: "envhub-",
|
|
240
|
+
validate: (val) => (val.trim() ? true : "Prefix is required."),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// Step 4: Save configuration
|
|
244
|
+
const spinner = logger.spinner("Creating configuration...");
|
|
245
|
+
try {
|
|
246
|
+
const configManager = new ConfigManager();
|
|
247
|
+
const filePath = await configManager.create(config);
|
|
248
|
+
spinner.succeed("Configuration created.");
|
|
249
|
+
// Step 5: Update .gitignore
|
|
250
|
+
const gitignoreUpdated = await updateGitignore(process.cwd());
|
|
251
|
+
if (gitignoreUpdated) {
|
|
252
|
+
logger.success("Added .envhubrc.json to .gitignore");
|
|
253
|
+
}
|
|
254
|
+
logger.newline();
|
|
255
|
+
logger.success("envhub is ready to use!");
|
|
256
|
+
logger.newline();
|
|
257
|
+
logger.dim(` Config file: ${filePath}`);
|
|
258
|
+
logger.dim(` Provider: ${config.provider}`);
|
|
259
|
+
if (config.aws) {
|
|
260
|
+
logger.dim(` AWS Profile: ${config.aws.profile}`);
|
|
261
|
+
logger.dim(` AWS Region: ${config.aws.region}`);
|
|
262
|
+
}
|
|
263
|
+
logger.newline();
|
|
264
|
+
logger.log(" Next steps:");
|
|
265
|
+
logger.log(" Push a secret: envhub push <name> <file>");
|
|
266
|
+
logger.log(" Pull a secret: envhub pull <name> <file>");
|
|
267
|
+
logger.log(" List secrets: envhub list");
|
|
268
|
+
logger.newline();
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
spinner.fail("Failed to create configuration.");
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=init.js.map
|