envspot 0.1.0 → 0.1.2
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 +75 -14
- package/dist/config.js +8 -2
- package/dist/fly-deploy.js +2 -1
- package/dist/http.js +4 -3
- package/dist/whoami.js +3 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,26 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# envspot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Encrypted environment variables for your team, managed from the command line.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`envspot` is the command-line client for [EnvSpot](https://envspot.com). Link a
|
|
6
|
+
directory to a project, pull your secrets straight into a process, and keep
|
|
7
|
+
`.env` files off disk and out of git.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- **Token** — stored with **keytar** (OS keychain); on **401** / `token_invalid`, `run` clears the stored credential and exits **4** so you re-`login`.
|
|
9
|
+
This package is the CLI. EnvSpot is also a hosted platform — a web dashboard for
|
|
10
|
+
projects, environments, team access, audit history, and syncing secrets to your
|
|
11
|
+
deploy targets — at [envspot.com](https://envspot.com).
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Install
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g envspot
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or run it without installing:
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npx envspot <command>
|
|
23
|
+
```
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
Requires Node.js 18 or newer.
|
|
19
26
|
|
|
20
|
-
##
|
|
27
|
+
## Quick start
|
|
21
28
|
|
|
22
29
|
```bash
|
|
23
|
-
|
|
30
|
+
envspot init # create + link a project, import your .env, start your dev server
|
|
31
|
+
envspot login # sign in via browser device pairing
|
|
32
|
+
envspot link # link this directory to a project + environment
|
|
33
|
+
envspot run -- npm start # run a command with your secrets injected as env vars
|
|
24
34
|
```
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
| ----------------- | --------------------------------------------------------------------------------------- |
|
|
40
|
+
| `init` | Create a project from this directory, link it, import `.env`, and start your dev server |
|
|
41
|
+
| `login` | Sign in via browser device pairing, or store an API key for token login |
|
|
42
|
+
| `logout` | Remove the stored credential |
|
|
43
|
+
| `link` | Write `./.envspot.json` (project id + environment) |
|
|
44
|
+
| `run -- <cmd>` | Run a command with the linked project's secrets injected as environment variables |
|
|
45
|
+
| `dump` | Print the linked project's secrets as `KEY=value` to stdout (nothing written to disk) |
|
|
46
|
+
| `set <key> <val>` | Set one secret for the linked project |
|
|
47
|
+
| `unset <key>` | Delete one secret |
|
|
48
|
+
| `status` | Show the API/app URL and your credential + link state |
|
|
49
|
+
| `whoami` | Show the signed-in user, workspace, and active link or token scope |
|
|
50
|
+
| `fly deploy` | Stage the linked project's secrets to Fly.io and deploy |
|
|
51
|
+
|
|
52
|
+
Run `envspot help` or `envspot <command> --help` for full options.
|
|
53
|
+
|
|
54
|
+
## Integrations
|
|
55
|
+
|
|
56
|
+
EnvSpot syncs your secrets to your deployment targets so you set a value once and
|
|
57
|
+
it lands everywhere. Supported targets: **GitHub Actions, Vercel, Render, Railway,
|
|
58
|
+
and Fly.io** — connected and managed in the [dashboard](https://envspot.com).
|
|
59
|
+
|
|
60
|
+
`fly deploy` is the one target you can push to directly from the CLI; the rest
|
|
61
|
+
sync automatically once connected in the dashboard.
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
| Variable | Purpose |
|
|
66
|
+
| --------------- | ------------------------------------------------------------ |
|
|
67
|
+
| `ENVSPOT_TOKEN` | API key for non-interactive / CI use (skips the OS keychain) |
|
|
68
|
+
|
|
69
|
+
## How your secrets are handled
|
|
70
|
+
|
|
71
|
+
- **Decryption happens server-side.** No master key or data-encryption key ever
|
|
72
|
+
lives on your machine.
|
|
73
|
+
- **Plaintext secrets stay in memory.** `run` holds them only for the lifetime of
|
|
74
|
+
the child process; nothing is written to disk as a secret bundle.
|
|
75
|
+
- **Your credential lives in the OS keychain**, not a plaintext file. On an expired
|
|
76
|
+
or revoked token, the CLI clears it and asks you to sign in again.
|
|
77
|
+
- **`.envspot.json` is safe to commit** — it holds only the project id and
|
|
78
|
+
environment label, never secrets.
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- Website: https://envspot.com
|
|
83
|
+
- Questions or bug reports: contact@envspot.com
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,9 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
// Brand display name for CLI prose. The binary/package/config-file name stays
|
|
6
|
+
// lowercase `envspot`; this is only the human-facing company name.
|
|
7
|
+
export const BRAND_NAME = "envSpot";
|
|
5
8
|
export function packageVersion() {
|
|
6
9
|
const pjPath = join(__dirname, "..", "package.json");
|
|
7
10
|
const pj = JSON.parse(readFileSync(pjPath, "utf8"));
|
|
@@ -19,7 +22,9 @@ export function apiBase() {
|
|
|
19
22
|
return process.env.ENVV_API_URL.trim();
|
|
20
23
|
if (process.env.NODE_ENV === "development")
|
|
21
24
|
return "http://localhost:3000";
|
|
22
|
-
|
|
25
|
+
// API routes live under /api on the main app origin (single Next.js app);
|
|
26
|
+
// there is no api.* subdomain. Keep in sync with appOrigin() below.
|
|
27
|
+
return "https://envspot.com";
|
|
23
28
|
}
|
|
24
29
|
/** Browser/dashboard origin for CLI login approval (dashboard lives here, not on API origin). */
|
|
25
30
|
export function appOrigin() {
|
|
@@ -35,7 +40,8 @@ export function appOrigin() {
|
|
|
35
40
|
return "https://envspot.com";
|
|
36
41
|
}
|
|
37
42
|
export function statusPageHint() {
|
|
38
|
-
|
|
43
|
+
// No public status page yet; surface a link only if one is configured.
|
|
44
|
+
return process.env.ENVSPOT_STATUS_URL?.trim() || "";
|
|
39
45
|
}
|
|
40
46
|
export function upgradePageHint(jsonUpgradeUrl) {
|
|
41
47
|
const u = typeof jsonUpgradeUrl === "string" && jsonUpgradeUrl.trim().length > 0
|
package/dist/fly-deploy.js
CHANGED
|
@@ -7,6 +7,7 @@ import { authRequired, flyctlNotInstalled, flyTomlNotFound } from "./errors.js";
|
|
|
7
7
|
import * as out from "./output.js";
|
|
8
8
|
import { fetchProjectSecrets, resolveLinkScope } from "./secrets.js";
|
|
9
9
|
import { isHeadlessToken } from "./token-kind.js";
|
|
10
|
+
import { BRAND_NAME } from "./config.js";
|
|
10
11
|
/**
|
|
11
12
|
* Parse the tail of `envspot fly deploy …`. `--app`/`--no-secrets` are ours;
|
|
12
13
|
* every other token forwards verbatim to `flyctl deploy`. `--app` is also
|
|
@@ -124,7 +125,7 @@ export async function executeFlyDeploy(parsed, deps = {}) {
|
|
|
124
125
|
? await fetchProjectSecrets(token)
|
|
125
126
|
: await fetchProjectSecrets(token, resolveLinkScope());
|
|
126
127
|
const keys = Object.keys(secrets).sort();
|
|
127
|
-
out.step(`Fetched ${keys.length} secret${keys.length === 1 ? "" : "s"} from
|
|
128
|
+
out.step(`Fetched ${keys.length} secret${keys.length === 1 ? "" : "s"} from ${BRAND_NAME}`);
|
|
128
129
|
if (keys.length === 0) {
|
|
129
130
|
// Likely a wrong env/scope rather than a genuinely empty project — make
|
|
130
131
|
// the silent "deployed with no secrets" footgun visible.
|
package/dist/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { apiBase, cliUserAgent, statusPageHint } from "./config.js";
|
|
1
|
+
import { apiBase, BRAND_NAME, cliUserAgent, statusPageHint } from "./config.js";
|
|
2
2
|
import { CliError, ERR } from "./errors.js";
|
|
3
3
|
export function apiUrl(path) {
|
|
4
4
|
const base = apiBase().replace(/\/$/, "");
|
|
@@ -29,8 +29,9 @@ export function describeTransportFailure(err) {
|
|
|
29
29
|
const msg = err instanceof Error
|
|
30
30
|
? `${err.message}${err.cause instanceof Error ? ` (${err.cause.message})` : ""}`
|
|
31
31
|
: String(err);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const status = statusPageHint();
|
|
33
|
+
return (`Couldn't reach ${BRAND_NAME}. Check your connection.\n` +
|
|
34
|
+
(status ? `${status}\n` : "") +
|
|
34
35
|
` Detail: ${msg}`);
|
|
35
36
|
}
|
|
36
37
|
/** 3× backoff for transport faults (plus first attempt ⇒ 4 tries). */
|
package/dist/whoami.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CliError, ERR, notLoggedIn } from "./errors.js";
|
|
|
4
4
|
import { apiUrl, backoffTransport, fetchWithCliHeaders, parseJsonSafely, rethrowTransport, } from "./http.js";
|
|
5
5
|
import { findEnvspotLink } from "./paths.js";
|
|
6
6
|
import { isHeadlessToken } from "./token-kind.js";
|
|
7
|
+
import { BRAND_NAME } from "./config.js";
|
|
7
8
|
/** Bearer signing tolerates this much clock skew; past it, auth gets flaky. */
|
|
8
9
|
const CLOCK_SKEW_TOLERANCE_MS = 5 * 60 * 1000;
|
|
9
10
|
/** Mask a stored token for display: kind prefix + a few chars, rest hidden.
|
|
@@ -29,7 +30,7 @@ export function clockSkewWarning(serverDateHeader, nowMs) {
|
|
|
29
30
|
return (`warning[E0074]: System clock differs from envspot.com by ${minutes}m ` +
|
|
30
31
|
`(local: ${new Date(nowMs).toISOString()}, server: ${new Date(serverMs).toISOString()}).\n` +
|
|
31
32
|
` help: Bearer signing tolerates 5 minutes of skew; past that, expect intermittent auth failures. Ensure NTP is running.\n` +
|
|
32
|
-
` docs: https://
|
|
33
|
+
` docs: https://envspot.com/docs/errors/E0074`);
|
|
33
34
|
}
|
|
34
35
|
function titleCaseTier(tier) {
|
|
35
36
|
return tier.length ? tier[0].toUpperCase() + tier.slice(1) : tier;
|
|
@@ -74,7 +75,7 @@ export async function executeWhoami(opts, nowMs = Date.now()) {
|
|
|
74
75
|
// otherwise null-deref in formatWhoami or print "null" under --json.
|
|
75
76
|
const data = analyzed.json;
|
|
76
77
|
if (!data || typeof data.user !== "object" || data.user === null) {
|
|
77
|
-
throw new CliError(ERR.UNEXPECTED,
|
|
78
|
+
throw new CliError(ERR.UNEXPECTED, `Malformed response from ${BRAND_NAME} (expected whoami JSON).`);
|
|
78
79
|
}
|
|
79
80
|
// Emit the skew warning before the --json early return: CI uses --json, and
|
|
80
81
|
// that is exactly where clock skew silently breaks bearer auth. It goes to
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envspot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI for envspot — encrypted environment variables for your team",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "envSpot",
|
|
7
7
|
"homepage": "https://envspot.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"directory": "cli"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"
|
|
14
|
+
"email": "contact@envspot.com"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"envspot",
|