getmyenv 0.9.4 → 0.9.6

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 CHANGED
@@ -22,7 +22,7 @@ npx getmyenv run staging -- npm start # or pick one
22
22
  - `import [file] [--prune] [--dry-run] [--yes]`: `.env.<slug>` goes to that context.
23
23
  - `export [-o file | --stdout] [--yes]`: for tools that need a file. The file is added to `.gitignore` and gets owner-only permissions on macOS and Linux.
24
24
  - `guest [--name <name>]`: a project without an account. It works for 30 days. Claim it within 60.
25
- - `claim`: claim a guest project. Reads `.getmyenv/claim.json`, or asks for the claim code and guest passphrase that `guest` printed once.
25
+ - `claim`: claim a guest project. Reads `.getmyenv/claim.json`, or asks for the claim code and guest passphrase that `guest` printed once at a terminal.
26
26
  - `backup [-o file]`: encrypted backup of open contexts (default `<project>.getmyenv-backup`). It opens with the Vault password in effect when it was made.
27
27
  - `restore <file> [--dry-run] [--yes]`: restore a backup into this folder's project. Values go into open contexts with the same slug and replace values with the same name. Read-only and missing contexts are skipped.
28
28
  - `start`: same as bare `getmyenv`. `start --template <id or link>` adds a template's variable names and asks for each value.
@@ -35,7 +35,7 @@ Prompts and status lines go to stderr, so `export --stdout > .env` stays clean.
35
35
  Commit `.getmyenv/project.json`. `token.json` and `claim.json` stay local and are gitignored.
36
36
 
37
37
  <!-- cli:help -->
38
- Output of `npx getmyenv --help` (0.9.4):
38
+ Output of `npx getmyenv --help` (0.9.6):
39
39
 
40
40
  ```text
41
41
  Usage: getmyenv [options] [command]
@@ -9,7 +9,7 @@ export async function backupCommand(opts) {
9
9
  const { client } = await ensureTokenAndLink({ apiUrl: opts.apiUrl });
10
10
  const data = await client.request("GET", "/api/cli/backup");
11
11
  if (!data.publicKey || !data.wrappedPrivateKey) {
12
- throw new Error("No Vault password yet, so there is nothing to back up. Set a value first: npx getmyenv set KEY");
12
+ throw new Error("No Vault password yet, so there is nothing to back up. Set it in the dashboard, or set a value: npx getmyenv set KEY");
13
13
  }
14
14
  const envelope = createBackupEnvelope({
15
15
  projectName: data.projectName,
@@ -50,9 +50,14 @@ export async function createGuestProject(opts = {}) {
50
50
  const daysLeft = Math.max(0, Math.ceil((new Date(created.guestExpiresAt).getTime() - Date.now()) / 86_400_000));
51
51
  console.error(`${pc.green("✓")} Guest project ${pc.bold(created.projectName)}. Context: development.`);
52
52
  console.error(pc.dim(`Expires in ${daysLeft} days unless claimed. Commit .getmyenv/project.json. token.json and claim.json stay local.`));
53
- console.error(pc.dim("claim.json holds the claim code and guest passphrase. Shown once, save them to claim later:"));
54
- console.error(` ${pc.dim("Claim code:")} ${created.claimCode}`);
55
- console.error(` ${pc.dim("Guest passphrase:")} ${passphrase}\n`);
53
+ if (process.stdin.isTTY) {
54
+ console.error(pc.dim("claim.json holds the claim code and guest passphrase. Shown once, save them to claim later:"));
55
+ console.error(` ${pc.dim("Claim code:")} ${created.claimCode}`);
56
+ console.error(` ${pc.dim("Guest passphrase:")} ${passphrase}\n`);
57
+ }
58
+ else {
59
+ console.error(pc.dim("claim.json holds the claim code and guest passphrase. It is the only copy. Keep it private.\n"));
60
+ }
56
61
  return created;
57
62
  }
58
63
  export async function guestCommand(opts = {}) {
@@ -22,7 +22,10 @@ export async function unlinkCommand(opts) {
22
22
  }
23
23
  if (!opts.yes) {
24
24
  const label = meta?.projectName ?? meta?.projectId ?? "this folder";
25
- const ok = await promptConfirm(`Unlink ${label}? Revokes this folder's token and removes .getmyenv files. The project and its values stay.`, { defaultYes: false });
25
+ const tokenLine = readClaimFile()
26
+ ? "Removes .getmyenv files except claim.json. The guest token stays, so claim still works."
27
+ : "Revokes this folder's token and removes .getmyenv files.";
28
+ const ok = await promptConfirm(`Unlink ${label}? ${tokenLine} The project and its values stay.`, { defaultYes: false });
26
29
  if (!ok) {
27
30
  console.log(pc.dim("Kept."));
28
31
  return;
@@ -30,8 +33,13 @@ export async function unlinkCommand(opts) {
30
33
  }
31
34
  if (folder) {
32
35
  try {
33
- await new ApiClient(resolveApiUrl(opts.apiUrl), folder.token).request("DELETE", "/api/cli/token");
34
- console.log(`${pc.green("✓")} Token revoked`);
36
+ const res = await new ApiClient(resolveApiUrl(opts.apiUrl), folder.token).request("DELETE", "/api/cli/token");
37
+ if (res?.revoked === false) {
38
+ console.log(pc.dim("Guest token kept on the server, so claim still works with claim.json."));
39
+ }
40
+ else {
41
+ console.log(`${pc.green("✓")} Token revoked`);
42
+ }
35
43
  }
36
44
  catch (err) {
37
45
  // 401/404: the token is already gone on the server. Anything else keeps
package/dist/lib/api.js CHANGED
@@ -55,15 +55,17 @@ export class ApiClient {
55
55
  throw new ApiError(`Could not reach ${this.baseUrl}`, 0);
56
56
  }
57
57
  if (!res.ok) {
58
- let message = `HTTP ${res.status}`;
58
+ let serverMessage;
59
59
  try {
60
60
  const data = (await res.json());
61
- if (data.error)
62
- message = data.error;
61
+ serverMessage = data.error;
63
62
  }
64
63
  catch {
65
64
  /* ignore */
66
65
  }
66
+ const message = res.status >= 500
67
+ ? `The getmyenv server had an error (HTTP ${res.status}). Try again in a moment.`
68
+ : (serverMessage ?? `HTTP ${res.status}`);
67
69
  throw new ApiError(message, res.status);
68
70
  }
69
71
  if (res.status === 204)
package/dist/lib/fs.js CHANGED
@@ -142,7 +142,7 @@ export function readLine(question) {
142
142
  export async function promptHidden(question) {
143
143
  const stdin = process.stdin;
144
144
  if (!stdin.isTTY) {
145
- throw new Error(`No terminal to ask for the Vault password. Unlock once in a terminal: open contexts stay unlocked for ${VAULT_UNLOCK_DURATION}. On servers and in CI, use a server token.`);
145
+ throw new Error(`No terminal to ask for the Vault password. Run this same command once in a terminal to unlock. Open contexts then stay unlocked for ${VAULT_UNLOCK_DURATION}. On servers and in CI, use a server token.`);
146
146
  }
147
147
  // Prompts go to stderr so stdout stays clean for `export --stdout > .env` and pipes.
148
148
  const out = process.stderr;
@@ -1,4 +1,4 @@
1
- import type { ContextInfo } from "./api.js";
1
+ import { type ContextInfo } from "./api.js";
2
2
  import type { Linked } from "./bootstrap.js";
3
3
  /** Fail fast before prompting or encrypting. Mirrors the server's `checkWrite`, which still enforces it. */
4
4
  export declare function assertWritable(linked: Linked, ctx: ContextInfo): void;
@@ -1,5 +1,6 @@
1
1
  import { encryptSecret, serializePayload } from "@getmyenv/crypto";
2
2
  import { FACTS } from "@getmyenv/shared";
3
+ import { ApiError } from "./api.js";
3
4
  import { contextKey } from "./vault.js";
4
5
  /** Fail fast before prompting or encrypting. Mirrors the server's `checkWrite`, which still enforces it. */
5
6
  export function assertWritable(linked, ctx) {
@@ -39,7 +40,15 @@ export async function uploadValues(input) {
39
40
  if (Buffer.byteLength(JSON.stringify(body)) > UPLOAD_MAX_BYTES) {
40
41
  throw new Error("Too much to upload at once (max 1 MB encrypted). Split the file and import each part.");
41
42
  }
42
- return linked.client.request("POST", "/api/cli/values", body);
43
+ try {
44
+ return await linked.client.request("POST", "/api/cli/values", body);
45
+ }
46
+ catch (err) {
47
+ if (err instanceof ApiError && err.status >= 500) {
48
+ throw new ApiError(`${err.message} Some values may already be saved. npx getmyenv ls shows what is set in ${context.slug}.`, err.status);
49
+ }
50
+ throw err;
51
+ }
43
52
  }
44
53
  /** Matches the server limit on `/api/cli/values`. */
45
54
  const UPLOAD_MAX_BYTES = 1024 * 1024;
package/dist/lib/vault.js CHANGED
@@ -129,7 +129,7 @@ async function holderPrivateKey(linked, vault, opts) {
129
129
  const isGuest = vault.holder === "guest";
130
130
  if (!vault.publicKey || !vault.wrappedPrivateKey) {
131
131
  if (!opts.create) {
132
- throw new Error("No Vault password yet. Set a value first: npx getmyenv set KEY");
132
+ throw new Error("No Vault password yet. Set it in the dashboard, or set a value: npx getmyenv set KEY");
133
133
  }
134
134
  if (!isGuest) {
135
135
  console.error(pc.dim("Choose a Vault password. It stays on this machine. We store an encrypted key blob only.\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getmyenv",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "CLI for getmyenv, an encrypted store for environment variables and secrets. We store ciphertext only.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@getmyenv/crypto": "0.4.1",
37
- "@getmyenv/shared": "0.8.0",
37
+ "@getmyenv/shared": "0.8.2",
38
38
  "@napi-rs/keyring": "^2.1.0",
39
39
  "commander": "^15.0.0",
40
40
  "dotenv": "^16.4.7",