envpass-cli 0.0.0-stage → 0.2.7
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 +46 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +231 -0
- package/dist/client.d.ts +86 -0
- package/dist/client.js +219 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +86 -0
- package/dist/device.d.ts +13 -0
- package/dist/device.js +56 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/integrations.d.ts +21 -0
- package/dist/integrations.js +181 -0
- package/dist/mask.d.ts +3 -0
- package/dist/mask.js +59 -0
- package/dist/schema.d.ts +7060 -0
- package/dist/schema.js +5 -0
- package/package.json +49 -4
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# envpass-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Client library and CLI for [envpass](https://envpass.dev): encrypted environment variables for every project and environment.
|
|
4
|
+
|
|
5
|
+
## CLI
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx envpass-cli login # sign in through your browser
|
|
9
|
+
bunx envpass-cli link -w acme -p shop -e dev # save defaults to .envpass.json (no secrets inside)
|
|
10
|
+
bunx envpass-cli pull # write .env
|
|
11
|
+
bunx envpass-cli pull -e prod -o - # print to stdout
|
|
12
|
+
bunx envpass-cli run -e dev -- bun dev # run with values injected, masked in output
|
|
13
|
+
bunx envpass-cli webhooks list # webhooks and syncs need `login`
|
|
14
|
+
bunx envpass-cli syncs run <id> # push a sync now
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`npx envpass-cli …` works the same way.
|
|
18
|
+
|
|
19
|
+
In CI, use a service token instead of `login`:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ENVPASS_TOKEN=ep_live_… npx envpass-cli pull -e prod
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Option | Environment variable | |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `-w, --workspace` | `ENVPASS_WORKSPACE` | Workspace slug (not needed with a service token) |
|
|
28
|
+
| `-p, --project` | `ENVPASS_PROJECT` | Project slug (not needed with a service token) |
|
|
29
|
+
| `-e, --env` | `ENVPASS_ENV` | Environment slug |
|
|
30
|
+
| `--url` | `ENVPASS_URL` | Server, default `https://envpass.dev` |
|
|
31
|
+
| | `ENVPASS_TOKEN` | Service token or CLI session token |
|
|
32
|
+
|
|
33
|
+
## Library
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createEnvpass, loadEnv } from "envpass-cli"
|
|
37
|
+
|
|
38
|
+
await loadEnv({ environment: "prod" })
|
|
39
|
+
|
|
40
|
+
const envpass = createEnvpass({ token: process.env.ENVPASS_TOKEN })
|
|
41
|
+
const entries = await envpass.resolve({ environment: "prod" })
|
|
42
|
+
const webhooks = await envpass.listWebhooks({ workspace: "acme" })
|
|
43
|
+
|
|
44
|
+
const { data } = await envpass.api.GET("/api/v1/workspaces")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`envpass.api` is a fully typed client for the whole REST API, generated from the OpenAPI spec.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { createEnvpass, EnvpassError } from "./client.js";
|
|
7
|
+
import { baseUrl, forgetToken, resolveScope, saveToken, storedToken, writeProjectLink, } from "./config.js";
|
|
8
|
+
import { deviceLogin, openBrowser } from "./device.js";
|
|
9
|
+
import { syncsCommand, webhooksCommand } from "./integrations.js";
|
|
10
|
+
import { maskingStream } from "./mask.js";
|
|
11
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
12
|
+
const HELP = `envpass ${version}: encrypted environment variables for every project and environment
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
envpass login Sign in through your browser
|
|
16
|
+
envpass logout Forget the saved login
|
|
17
|
+
envpass whoami Show who the current token belongs to
|
|
18
|
+
envpass link -w <ws> -p <project> Save defaults to .envpass.json in this directory
|
|
19
|
+
envpass pull [-e env] [-o .env] Write resolved values to a file ("-" for stdout)
|
|
20
|
+
envpass run [-e env] -- <command> Run a command with the values injected
|
|
21
|
+
|
|
22
|
+
Webhooks and syncs (need \`envpass login\`, not a service token)
|
|
23
|
+
envpass webhooks list List the workspace's webhooks
|
|
24
|
+
envpass webhooks add --url <url> Register a webhook and print its signing secret once
|
|
25
|
+
envpass webhooks remove <id> Delete a webhook
|
|
26
|
+
envpass webhooks test <id> Send a test event (exit code 1 if it fails)
|
|
27
|
+
envpass webhooks deliveries <id> Show a webhook's recent deliveries
|
|
28
|
+
envpass syncs list [-p project] List the project's syncs
|
|
29
|
+
envpass syncs run <id> Push a sync now
|
|
30
|
+
envpass syncs pause <id> Stop a sync from running
|
|
31
|
+
envpass syncs resume <id> Turn a paused sync back on
|
|
32
|
+
|
|
33
|
+
Options
|
|
34
|
+
-w, --workspace <slug> Workspace (not needed with a service token)
|
|
35
|
+
-p, --project <slug> Project (not needed with a service token)
|
|
36
|
+
-e, --env <slug> Environment, e.g. dev or prod
|
|
37
|
+
-o, --out <file> pull: output file (default .env)
|
|
38
|
+
-f, --format <format> pull: dotenv, shell or json (default dotenv)
|
|
39
|
+
--no-mask run: show secret values in the command's output
|
|
40
|
+
--no-browser login: print the link instead of opening a browser
|
|
41
|
+
--json webhooks list, webhooks deliveries, syncs list: print raw JSON
|
|
42
|
+
--limit <n> webhooks deliveries: how many to show, 1-100 (default 25)
|
|
43
|
+
--url <url> envpass server (default https://envpass.dev, or ENVPASS_URL)
|
|
44
|
+
|
|
45
|
+
Options for webhooks add
|
|
46
|
+
--url <url> Endpoint to POST to (point at your server with ENVPASS_URL)
|
|
47
|
+
--event <event> secrets.changed, sync.succeeded or sync.failed; repeat for more
|
|
48
|
+
(default secrets.changed and sync.failed)
|
|
49
|
+
-e, --env <slug> Only these environments; repeat for more (default all)
|
|
50
|
+
-p, --project <slug> Only these projects; repeat for more (default all)
|
|
51
|
+
-f, --format <format> json or slack (default slack for Slack URLs, json otherwise)
|
|
52
|
+
--description <text> A note to tell webhooks apart
|
|
53
|
+
|
|
54
|
+
Environment
|
|
55
|
+
ENVPASS_TOKEN A service token (ep_live_…) or CLI session token
|
|
56
|
+
ENVPASS_WORKSPACE, ENVPASS_PROJECT, ENVPASS_ENV
|
|
57
|
+
`;
|
|
58
|
+
function fail(message, code = 1) {
|
|
59
|
+
process.stderr.write(`envpass: ${message}\n`);
|
|
60
|
+
process.exit(code);
|
|
61
|
+
}
|
|
62
|
+
async function main(argv) {
|
|
63
|
+
const { values, positionals } = parseArgs({
|
|
64
|
+
args: argv,
|
|
65
|
+
allowPositionals: true,
|
|
66
|
+
options: {
|
|
67
|
+
workspace: { type: "string", short: "w" },
|
|
68
|
+
project: { type: "string", short: "p", multiple: true },
|
|
69
|
+
env: { type: "string", short: "e", multiple: true },
|
|
70
|
+
out: { type: "string", short: "o" },
|
|
71
|
+
format: { type: "string", short: "f" },
|
|
72
|
+
url: { type: "string" },
|
|
73
|
+
event: { type: "string", multiple: true },
|
|
74
|
+
description: { type: "string" },
|
|
75
|
+
limit: { type: "string" },
|
|
76
|
+
json: { type: "boolean" },
|
|
77
|
+
"no-mask": { type: "boolean" },
|
|
78
|
+
"no-browser": { type: "boolean" },
|
|
79
|
+
help: { type: "boolean", short: "h" },
|
|
80
|
+
version: { type: "boolean", short: "v" },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const [command, ...rest] = positionals;
|
|
84
|
+
if (values.version)
|
|
85
|
+
return void process.stdout.write(`${version}\n`);
|
|
86
|
+
if (values.help || !command || command === "help")
|
|
87
|
+
return void process.stdout.write(HELP);
|
|
88
|
+
const endpointUrl = command === "webhooks" && rest[0] === "add";
|
|
89
|
+
const url = baseUrl(endpointUrl ? undefined : values.url);
|
|
90
|
+
const flags = {
|
|
91
|
+
workspace: values.workspace,
|
|
92
|
+
project: values.project?.at(-1),
|
|
93
|
+
environment: values.env?.at(-1),
|
|
94
|
+
};
|
|
95
|
+
switch (command) {
|
|
96
|
+
case "login": {
|
|
97
|
+
const token = await deviceLogin(url, (code) => {
|
|
98
|
+
const link = code.verification_uri_complete ?? code.verification_uri;
|
|
99
|
+
process.stderr.write(`\nOpen ${link}\nand confirm the code: ${code.user_code}\n\nWaiting for approval…\n`);
|
|
100
|
+
if (!values["no-browser"])
|
|
101
|
+
openBrowser(link);
|
|
102
|
+
});
|
|
103
|
+
const path = saveToken(url, token);
|
|
104
|
+
const me = await createEnvpass({ baseUrl: url, token }).me();
|
|
105
|
+
process.stderr.write(`Signed in. Token saved to ${path}\n`);
|
|
106
|
+
if (me.workspaces.length) {
|
|
107
|
+
process.stderr.write(`Workspaces: ${me.workspaces.map((w) => w.slug).join(", ")}\n`);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
case "logout": {
|
|
112
|
+
const token = storedToken(url);
|
|
113
|
+
if (token) {
|
|
114
|
+
await fetch(`${url}/api/auth/sign-out`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
117
|
+
body: "{}",
|
|
118
|
+
}).catch(() => undefined);
|
|
119
|
+
}
|
|
120
|
+
const removed = forgetToken(url);
|
|
121
|
+
process.stderr.write(removed ? `Signed out of ${url}\n` : `No saved login for ${url}\n`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
case "whoami": {
|
|
125
|
+
const client = createEnvpass({ baseUrl: url });
|
|
126
|
+
if (!client.hasToken)
|
|
127
|
+
fail("Not signed in. Run `envpass login` or set ENVPASS_TOKEN.");
|
|
128
|
+
const me = await client.me();
|
|
129
|
+
if (me.token) {
|
|
130
|
+
process.stdout.write(`Service token ${me.token.id} (${me.token.access}) for project ${me.token.projectId}\n`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
process.stdout.write(`User ${me.userId}\n`);
|
|
134
|
+
for (const ws of me.workspaces)
|
|
135
|
+
process.stdout.write(` ${ws.slug}${ws.isOwner ? " (owner)" : ""}\n`);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
case "link": {
|
|
140
|
+
const scope = resolveScope(flags);
|
|
141
|
+
if (!scope.workspace || !scope.project)
|
|
142
|
+
fail("Pass --workspace and --project.");
|
|
143
|
+
const path = writeProjectLink(scope);
|
|
144
|
+
process.stderr.write(`Saved ${path}. It holds no secrets and is safe to commit.\n`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case "pull": {
|
|
148
|
+
const format = (values.format ?? "dotenv");
|
|
149
|
+
if (!["dotenv", "shell", "json"].includes(format))
|
|
150
|
+
fail("--format must be dotenv, shell or json");
|
|
151
|
+
const client = createEnvpass({ baseUrl: url });
|
|
152
|
+
if (!client.hasToken)
|
|
153
|
+
fail("Not signed in. Run `envpass login` or set ENVPASS_TOKEN.");
|
|
154
|
+
const text = format === "json"
|
|
155
|
+
? `${JSON.stringify(Object.fromEntries((await client.resolve(flags)).map((e) => [e.key, e.value])), null, 2)}\n`
|
|
156
|
+
: await client.resolveText(flags, format);
|
|
157
|
+
const out = values.out ?? ".env";
|
|
158
|
+
if (out === "-")
|
|
159
|
+
return void process.stdout.write(text);
|
|
160
|
+
writeFileSync(out, text, { mode: 0o600 });
|
|
161
|
+
const count = text
|
|
162
|
+
.split("\n")
|
|
163
|
+
.filter((line) => line.includes("=") || line.includes(":")).length;
|
|
164
|
+
process.stderr.write(`Wrote ${out} (${count} values). Keep it out of git.\n`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
case "run": {
|
|
168
|
+
const separator = argv.indexOf("--");
|
|
169
|
+
const commandArgs = separator === -1 ? rest : argv.slice(separator + 1);
|
|
170
|
+
const [program, ...programArgs] = commandArgs;
|
|
171
|
+
if (!program)
|
|
172
|
+
fail("Usage: envpass run [-e env] -- <command> [args…]");
|
|
173
|
+
const client = createEnvpass({ baseUrl: url });
|
|
174
|
+
if (!client.hasToken)
|
|
175
|
+
fail("Not signed in. Run `envpass login` or set ENVPASS_TOKEN.");
|
|
176
|
+
const entries = await client.resolve(flags);
|
|
177
|
+
const env = { ...process.env, ...Object.fromEntries(entries.map((e) => [e.key, e.value])) };
|
|
178
|
+
const mask = !values["no-mask"];
|
|
179
|
+
const child = spawn(program, programArgs, {
|
|
180
|
+
env,
|
|
181
|
+
stdio: mask ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
182
|
+
});
|
|
183
|
+
if (mask) {
|
|
184
|
+
const secrets = entries.map((e) => e.value);
|
|
185
|
+
child.stdout?.pipe(maskingStream(secrets)).pipe(process.stdout);
|
|
186
|
+
child.stderr?.pipe(maskingStream(secrets)).pipe(process.stderr);
|
|
187
|
+
}
|
|
188
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
189
|
+
process.on(signal, () => child.kill(signal));
|
|
190
|
+
}
|
|
191
|
+
child.on("error", (error) => fail(`Could not start ${program}: ${error.message}`, 127));
|
|
192
|
+
child.on("exit", (code, signal) => {
|
|
193
|
+
if (signal)
|
|
194
|
+
process.kill(process.pid, signal);
|
|
195
|
+
else
|
|
196
|
+
process.exit(code ?? 0);
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
case "webhooks":
|
|
201
|
+
case "syncs": {
|
|
202
|
+
const client = createEnvpass({ baseUrl: url });
|
|
203
|
+
if (!client.hasToken)
|
|
204
|
+
fail("Not signed in. Run `envpass login` or set ENVPASS_TOKEN.");
|
|
205
|
+
const run = command === "webhooks" ? webhooksCommand : syncsCommand;
|
|
206
|
+
process.exitCode = await run(client, {
|
|
207
|
+
scope: flags,
|
|
208
|
+
args: rest,
|
|
209
|
+
json: values.json,
|
|
210
|
+
url: endpointUrl ? values.url : undefined,
|
|
211
|
+
events: values.event,
|
|
212
|
+
environments: values.env,
|
|
213
|
+
projects: values.project,
|
|
214
|
+
format: values.format,
|
|
215
|
+
description: values.description,
|
|
216
|
+
limit: values.limit,
|
|
217
|
+
}, {
|
|
218
|
+
out: (text) => process.stdout.write(text),
|
|
219
|
+
err: (text) => process.stderr.write(text),
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
default:
|
|
224
|
+
fail(`Unknown command "${command}". Run \`envpass --help\`.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
228
|
+
if (error instanceof EnvpassError)
|
|
229
|
+
fail(`${error.message}${error.code ? ` (${error.code})` : ""}`);
|
|
230
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
231
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type ProjectLink } from "./config.js";
|
|
2
|
+
import type { components, paths } from "./schema.js";
|
|
3
|
+
type Schemas = components["schemas"];
|
|
4
|
+
export type Source = Schemas["SecretSource"];
|
|
5
|
+
export type ResolvedEntry = {
|
|
6
|
+
key: string;
|
|
7
|
+
value: string;
|
|
8
|
+
source: Source;
|
|
9
|
+
};
|
|
10
|
+
export type ExportFormat = "dotenv" | "json" | "shell";
|
|
11
|
+
export type Workspace = Schemas["Workspace"];
|
|
12
|
+
export type Environment = Schemas["Environment"];
|
|
13
|
+
export type Webhook = Schemas["Webhook"];
|
|
14
|
+
export type WebhookEvent = Schemas["WebhookEvent"];
|
|
15
|
+
export type WebhookFormat = Webhook["format"];
|
|
16
|
+
export type WebhookDelivery = Schemas["WebhookDelivery"];
|
|
17
|
+
export type WebhookTestResult = Schemas["WebhookTestResult"];
|
|
18
|
+
export type Connection = Schemas["Connection"];
|
|
19
|
+
export type Sync = Schemas["Sync"];
|
|
20
|
+
export type NewWebhook = {
|
|
21
|
+
url: string;
|
|
22
|
+
events?: WebhookEvent[];
|
|
23
|
+
environments?: string[];
|
|
24
|
+
projects?: string[];
|
|
25
|
+
format?: WebhookFormat;
|
|
26
|
+
description?: string | null;
|
|
27
|
+
};
|
|
28
|
+
export declare const WEBHOOK_EVENTS: readonly WebhookEvent[];
|
|
29
|
+
export type EnvpassOptions = {
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
token?: string;
|
|
32
|
+
userAgent?: string;
|
|
33
|
+
fetch?: typeof fetch;
|
|
34
|
+
};
|
|
35
|
+
export declare class EnvpassError extends Error {
|
|
36
|
+
readonly status: number;
|
|
37
|
+
readonly code?: string | undefined;
|
|
38
|
+
constructor(message: string, status: number, code?: string | undefined);
|
|
39
|
+
}
|
|
40
|
+
export declare function createEnvpass(options?: EnvpassOptions): {
|
|
41
|
+
url: string;
|
|
42
|
+
hasToken: boolean;
|
|
43
|
+
api: import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
44
|
+
me(): Promise<{
|
|
45
|
+
actor: "user" | "token";
|
|
46
|
+
userId: string;
|
|
47
|
+
token: {
|
|
48
|
+
id: string;
|
|
49
|
+
workspaceId: string;
|
|
50
|
+
projectId: string;
|
|
51
|
+
access: "read" | "write";
|
|
52
|
+
} | null;
|
|
53
|
+
workspaces: {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
slug: string;
|
|
57
|
+
isOwner: boolean;
|
|
58
|
+
}[];
|
|
59
|
+
}>;
|
|
60
|
+
resolve(scope?: ProjectLink): Promise<ResolvedEntry[]>;
|
|
61
|
+
resolveText(scope?: ProjectLink, format?: Exclude<ExportFormat, "json">): Promise<string>;
|
|
62
|
+
workspace(scope?: ProjectLink): Promise<Workspace>;
|
|
63
|
+
listEnvironments(scope?: ProjectLink): Promise<Environment[]>;
|
|
64
|
+
listWebhooks(scope?: ProjectLink): Promise<Webhook[]>;
|
|
65
|
+
createWebhook(input: NewWebhook, scope?: ProjectLink): Promise<{
|
|
66
|
+
webhook: Webhook;
|
|
67
|
+
secret: string;
|
|
68
|
+
}>;
|
|
69
|
+
deleteWebhook(id: string, scope?: ProjectLink): Promise<void>;
|
|
70
|
+
testWebhook(id: string, scope?: ProjectLink): Promise<WebhookTestResult>;
|
|
71
|
+
listWebhookDeliveries(id: string, options?: {
|
|
72
|
+
limit?: number;
|
|
73
|
+
before?: string;
|
|
74
|
+
}, scope?: ProjectLink): Promise<WebhookDelivery[]>;
|
|
75
|
+
listConnections(scope?: ProjectLink): Promise<Connection[]>;
|
|
76
|
+
listSyncs(scope?: ProjectLink): Promise<Sync[]>;
|
|
77
|
+
runSync(id: string, scope?: ProjectLink): Promise<void>;
|
|
78
|
+
setSyncEnabled(id: string, enabled: boolean, scope?: ProjectLink): Promise<void>;
|
|
79
|
+
};
|
|
80
|
+
export type Envpass = ReturnType<typeof createEnvpass>;
|
|
81
|
+
export declare function loadEnv(scope?: ProjectLink & EnvpassOptions & {
|
|
82
|
+
override?: boolean;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
[k: string]: string;
|
|
85
|
+
}>;
|
|
86
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import createFetchClient from "openapi-fetch";
|
|
2
|
+
import { baseUrl, resolveScope, resolveToken } from "./config.js";
|
|
3
|
+
export const WEBHOOK_EVENTS = [
|
|
4
|
+
"secrets.changed",
|
|
5
|
+
"sync.succeeded",
|
|
6
|
+
"sync.failed",
|
|
7
|
+
];
|
|
8
|
+
const DEFAULT_WEBHOOK_EVENTS = ["secrets.changed", "sync.failed"];
|
|
9
|
+
export class EnvpassError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
constructor(message, status, code) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.name = "EnvpassError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function errorFrom(status, body) {
|
|
20
|
+
const error = body?.error;
|
|
21
|
+
return new EnvpassError(error?.message ?? `Request failed with status ${status}`, status, error?.code);
|
|
22
|
+
}
|
|
23
|
+
function unwrap(result) {
|
|
24
|
+
if (result.data === undefined)
|
|
25
|
+
throw errorFrom(result.response.status, result.error);
|
|
26
|
+
return result.data;
|
|
27
|
+
}
|
|
28
|
+
function pickIds(items, refs, kind, workspace) {
|
|
29
|
+
return refs.map((ref) => {
|
|
30
|
+
const item = items.find((candidate) => candidate.slug === ref || candidate.id === ref);
|
|
31
|
+
if (!item)
|
|
32
|
+
throw new EnvpassError(`No ${kind} "${ref}" in ${workspace.slug}`, 404, "not_found");
|
|
33
|
+
return item.id;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function createEnvpass(options = {}) {
|
|
37
|
+
const url = baseUrl(options.baseUrl);
|
|
38
|
+
const token = resolveToken(url, options.token);
|
|
39
|
+
const headers = { "user-agent": options.userAgent ?? "envpass-node" };
|
|
40
|
+
if (token)
|
|
41
|
+
headers.authorization = `Bearer ${token}`;
|
|
42
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
43
|
+
const api = createFetchClient({ baseUrl: url, headers, fetch: fetcher });
|
|
44
|
+
function scopeQuery(scope) {
|
|
45
|
+
if (!scope.environment) {
|
|
46
|
+
throw new EnvpassError("No environment. Pass --env, set ENVPASS_ENV, or run `envpass link`.", 400, "invalid");
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
workspace: scope.workspace,
|
|
50
|
+
project: scope.project,
|
|
51
|
+
environment: scope.environment,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function requireUser() {
|
|
55
|
+
if (token?.startsWith("ep_")) {
|
|
56
|
+
throw new EnvpassError("Webhooks and syncs need a user login, not a service token. Run `envpass login` and unset ENVPASS_TOKEN.", 403, "forbidden");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function workspaceFor(scope) {
|
|
60
|
+
requireUser();
|
|
61
|
+
const ref = resolveScope(scope).workspace;
|
|
62
|
+
const { workspaces } = unwrap(await api.GET("/api/v1/workspaces"));
|
|
63
|
+
if (ref) {
|
|
64
|
+
const match = workspaces.find((w) => w.slug === ref || w.id === ref);
|
|
65
|
+
if (!match)
|
|
66
|
+
throw new EnvpassError(`No workspace "${ref}" for this login`, 404, "not_found");
|
|
67
|
+
return match;
|
|
68
|
+
}
|
|
69
|
+
const [only] = workspaces;
|
|
70
|
+
if (only && workspaces.length === 1)
|
|
71
|
+
return only;
|
|
72
|
+
throw new EnvpassError("No workspace. Pass --workspace, set ENVPASS_WORKSPACE, or run `envpass link`.", 400, "invalid");
|
|
73
|
+
}
|
|
74
|
+
async function projectsOf(workspaceId) {
|
|
75
|
+
const path = { workspaceId };
|
|
76
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/projects", { params: { path } }))
|
|
77
|
+
.projects;
|
|
78
|
+
}
|
|
79
|
+
async function environmentsOf(workspaceId) {
|
|
80
|
+
const path = { workspaceId };
|
|
81
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/environments", { params: { path } })).environments;
|
|
82
|
+
}
|
|
83
|
+
async function projectFor(scope) {
|
|
84
|
+
const workspace = await workspaceFor(scope);
|
|
85
|
+
const ref = resolveScope(scope).project;
|
|
86
|
+
if (!ref) {
|
|
87
|
+
throw new EnvpassError("No project. Pass --project, set ENVPASS_PROJECT, or run `envpass link`.", 400, "invalid");
|
|
88
|
+
}
|
|
89
|
+
const [projectId = ref] = pickIds(await projectsOf(workspace.id), [ref], "project", workspace);
|
|
90
|
+
return { workspaceId: workspace.id, projectId };
|
|
91
|
+
}
|
|
92
|
+
async function webhookPath(webhookId, scope) {
|
|
93
|
+
return { workspaceId: (await workspaceFor(scope)).id, webhookId };
|
|
94
|
+
}
|
|
95
|
+
async function syncPath(syncId, scope) {
|
|
96
|
+
return { ...(await projectFor(scope)), syncId };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
url,
|
|
100
|
+
hasToken: Boolean(token),
|
|
101
|
+
api,
|
|
102
|
+
async me() {
|
|
103
|
+
const { data, error, response } = await api.GET("/api/v1/me");
|
|
104
|
+
if (!data)
|
|
105
|
+
throw errorFrom(response.status, error);
|
|
106
|
+
return data;
|
|
107
|
+
},
|
|
108
|
+
async resolve(scope = {}) {
|
|
109
|
+
const query = scopeQuery(resolveScope(scope));
|
|
110
|
+
const { data, error, response } = await api.GET("/api/v1/resolve", {
|
|
111
|
+
params: { query: { ...query, format: "json" } },
|
|
112
|
+
});
|
|
113
|
+
if (!data || typeof data === "string")
|
|
114
|
+
throw errorFrom(response.status, error);
|
|
115
|
+
return data.entries;
|
|
116
|
+
},
|
|
117
|
+
async resolveText(scope = {}, format = "dotenv") {
|
|
118
|
+
const query = scopeQuery(resolveScope(scope));
|
|
119
|
+
const response = await fetcher(`${url}/api/v1/resolve?${new URLSearchParams(Object.entries({ ...query, format }).filter((entry) => Boolean(entry[1])))}`, { headers });
|
|
120
|
+
const text = await response.text();
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
let body;
|
|
123
|
+
try {
|
|
124
|
+
body = JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
body = undefined;
|
|
128
|
+
}
|
|
129
|
+
throw errorFrom(response.status, body);
|
|
130
|
+
}
|
|
131
|
+
return text;
|
|
132
|
+
},
|
|
133
|
+
async workspace(scope = {}) {
|
|
134
|
+
return workspaceFor(scope);
|
|
135
|
+
},
|
|
136
|
+
async listEnvironments(scope = {}) {
|
|
137
|
+
return environmentsOf((await workspaceFor(scope)).id);
|
|
138
|
+
},
|
|
139
|
+
async listWebhooks(scope = {}) {
|
|
140
|
+
const path = { workspaceId: (await workspaceFor(scope)).id };
|
|
141
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/webhooks", { params: { path } })).webhooks;
|
|
142
|
+
},
|
|
143
|
+
async createWebhook(input, scope = {}) {
|
|
144
|
+
const workspace = await workspaceFor(scope);
|
|
145
|
+
const environments = input.environments ?? [];
|
|
146
|
+
const projects = input.projects ?? [];
|
|
147
|
+
const [environmentIds, projectIds] = await Promise.all([
|
|
148
|
+
environments.length
|
|
149
|
+
? environmentsOf(workspace.id).then((all) => pickIds(all, environments, "environment", workspace))
|
|
150
|
+
: [],
|
|
151
|
+
projects.length
|
|
152
|
+
? projectsOf(workspace.id).then((all) => pickIds(all, projects, "project", workspace))
|
|
153
|
+
: [],
|
|
154
|
+
]);
|
|
155
|
+
return unwrap(await api.POST("/api/v1/workspaces/{workspaceId}/webhooks", {
|
|
156
|
+
params: { path: { workspaceId: workspace.id } },
|
|
157
|
+
body: {
|
|
158
|
+
url: input.url,
|
|
159
|
+
events: input.events?.length ? input.events : DEFAULT_WEBHOOK_EVENTS,
|
|
160
|
+
environmentIds,
|
|
161
|
+
projectIds,
|
|
162
|
+
format: input.format,
|
|
163
|
+
description: input.description,
|
|
164
|
+
},
|
|
165
|
+
}));
|
|
166
|
+
},
|
|
167
|
+
async deleteWebhook(id, scope = {}) {
|
|
168
|
+
const path = await webhookPath(id, scope);
|
|
169
|
+
unwrap(await api.DELETE("/api/v1/workspaces/{workspaceId}/webhooks/{webhookId}", {
|
|
170
|
+
params: { path },
|
|
171
|
+
}));
|
|
172
|
+
},
|
|
173
|
+
async testWebhook(id, scope = {}) {
|
|
174
|
+
const path = await webhookPath(id, scope);
|
|
175
|
+
return unwrap(await api.POST("/api/v1/workspaces/{workspaceId}/webhooks/{webhookId}/test", {
|
|
176
|
+
params: { path },
|
|
177
|
+
}));
|
|
178
|
+
},
|
|
179
|
+
async listWebhookDeliveries(id, options = {}, scope = {}) {
|
|
180
|
+
const path = await webhookPath(id, scope);
|
|
181
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/webhooks/{webhookId}/deliveries", {
|
|
182
|
+
params: { path, query: options },
|
|
183
|
+
})).deliveries;
|
|
184
|
+
},
|
|
185
|
+
async listConnections(scope = {}) {
|
|
186
|
+
const path = { workspaceId: (await workspaceFor(scope)).id };
|
|
187
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/connections", { params: { path } })).connections;
|
|
188
|
+
},
|
|
189
|
+
async listSyncs(scope = {}) {
|
|
190
|
+
const path = await projectFor(scope);
|
|
191
|
+
return unwrap(await api.GET("/api/v1/workspaces/{workspaceId}/projects/{projectId}/syncs", {
|
|
192
|
+
params: { path },
|
|
193
|
+
})).syncs;
|
|
194
|
+
},
|
|
195
|
+
async runSync(id, scope = {}) {
|
|
196
|
+
const path = await syncPath(id, scope);
|
|
197
|
+
unwrap(await api.POST("/api/v1/workspaces/{workspaceId}/projects/{projectId}/syncs/{syncId}/run", {
|
|
198
|
+
params: { path },
|
|
199
|
+
}));
|
|
200
|
+
},
|
|
201
|
+
async setSyncEnabled(id, enabled, scope = {}) {
|
|
202
|
+
const path = await syncPath(id, scope);
|
|
203
|
+
unwrap(await api.PATCH("/api/v1/workspaces/{workspaceId}/projects/{projectId}/syncs/{syncId}", {
|
|
204
|
+
params: { path },
|
|
205
|
+
body: { enabled },
|
|
206
|
+
}));
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export async function loadEnv(scope = {}) {
|
|
211
|
+
const { baseUrl: url, token, userAgent, override = false, ...link } = scope;
|
|
212
|
+
const client = createEnvpass({ baseUrl: url, token, userAgent });
|
|
213
|
+
const entries = await client.resolve(link);
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
if (override || process.env[entry.key] === undefined)
|
|
216
|
+
process.env[entry.key] = entry.value;
|
|
217
|
+
}
|
|
218
|
+
return Object.fromEntries(entries.map((entry) => [entry.key, entry.value]));
|
|
219
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const DEFAULT_URL = "https://envpass.dev";
|
|
2
|
+
export type ProjectLink = {
|
|
3
|
+
workspace?: string;
|
|
4
|
+
project?: string;
|
|
5
|
+
environment?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function baseUrl(explicit?: string): string;
|
|
8
|
+
export declare function saveToken(url: string, token: string): string;
|
|
9
|
+
export declare function forgetToken(url: string): boolean;
|
|
10
|
+
export declare function storedToken(url: string): string | undefined;
|
|
11
|
+
export declare function resolveToken(url: string, explicit?: string): string | undefined;
|
|
12
|
+
export declare function writeProjectLink(link: ProjectLink, dir?: string): string;
|
|
13
|
+
export declare function resolveScope(flags: ProjectLink, start?: string): ProjectLink;
|