getmyenv 0.12.2 → 0.14.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.
Files changed (64) hide show
  1. package/README.md +25 -12
  2. package/dist/bin.js +8 -0
  3. package/dist/cli.js +13 -3
  4. package/dist/commands/backup.js +1 -1
  5. package/dist/commands/claim.js +3 -2
  6. package/dist/commands/context.js +4 -4
  7. package/dist/commands/export.js +19 -22
  8. package/dist/commands/guest.d.ts +1 -1
  9. package/dist/commands/guest.js +17 -6
  10. package/dist/commands/import.js +42 -10
  11. package/dist/commands/lock.js +1 -1
  12. package/dist/commands/ls.js +2 -2
  13. package/dist/commands/push.d.ts +25 -0
  14. package/dist/commands/push.js +189 -0
  15. package/dist/commands/restore.js +3 -3
  16. package/dist/commands/run.d.ts +1 -0
  17. package/dist/commands/run.js +63 -24
  18. package/dist/commands/set.d.ts +16 -0
  19. package/dist/commands/set.js +98 -14
  20. package/dist/commands/start.js +7 -7
  21. package/dist/commands/status.js +3 -3
  22. package/dist/commands/template.js +9 -6
  23. package/dist/commands/unlink.d.ts +14 -5
  24. package/dist/commands/unlink.js +79 -17
  25. package/dist/lib/api-url.d.ts +2 -0
  26. package/dist/lib/api-url.js +10 -0
  27. package/dist/lib/api.d.ts +22 -2
  28. package/dist/lib/api.js +108 -34
  29. package/dist/lib/authorize.js +2 -2
  30. package/dist/lib/bootstrap.d.ts +5 -0
  31. package/dist/lib/bootstrap.js +32 -8
  32. package/dist/lib/colors.d.ts +14 -0
  33. package/dist/lib/colors.js +17 -0
  34. package/dist/lib/confirm.d.ts +7 -3
  35. package/dist/lib/confirm.js +21 -12
  36. package/dist/lib/context.js +1 -1
  37. package/dist/lib/credentials.d.ts +4 -0
  38. package/dist/lib/credentials.js +13 -11
  39. package/dist/lib/fs.d.ts +36 -1
  40. package/dist/lib/fs.js +102 -23
  41. package/dist/lib/known-keys-cache.d.ts +15 -0
  42. package/dist/lib/known-keys-cache.js +102 -0
  43. package/dist/lib/known-keys.d.ts +2 -0
  44. package/dist/lib/known-keys.js +12 -3
  45. package/dist/lib/read-context.d.ts +26 -0
  46. package/dist/lib/read-context.js +22 -0
  47. package/dist/lib/recovery-code.js +1 -1
  48. package/dist/lib/resolve.d.ts +2 -1
  49. package/dist/lib/resolve.js +1 -1
  50. package/dist/lib/run-env.d.ts +19 -3
  51. package/dist/lib/run-env.js +66 -13
  52. package/dist/lib/template-file.js +1 -1
  53. package/dist/lib/update-check.js +1 -1
  54. package/dist/lib/upload.js +2 -2
  55. package/dist/lib/vault.js +1 -1
  56. package/dist/providers/http.d.ts +16 -0
  57. package/dist/providers/http.js +69 -0
  58. package/dist/providers/netlify.d.ts +11 -0
  59. package/dist/providers/netlify.js +184 -0
  60. package/dist/providers/types.d.ts +56 -0
  61. package/dist/providers/types.js +1 -0
  62. package/dist/providers/vercel.d.ts +17 -0
  63. package/dist/providers/vercel.js +205 -0
  64. package/package.json +19 -12
@@ -0,0 +1,205 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isPublicEnvName, isReservedPushName } from "@getmyenv/shared";
4
+ import { pushApiBase } from "../lib/api-url.js";
5
+ import { PlatformError, platformRequest } from "./http.js";
6
+ export const VERCEL_API = "https://api.vercel.com";
7
+ const MAX_TOTAL_BYTES = 64 * 1024;
8
+ export const isVercelReserved = (name) => isReservedPushName("vercel", name);
9
+ /** `.vercel/project.json`, written by `vercel link`. */
10
+ export function readVercelProjectFile(cwd = process.cwd()) {
11
+ const file = path.join(cwd, ".vercel", "project.json");
12
+ if (!fs.existsSync(file))
13
+ return null;
14
+ try {
15
+ const raw = JSON.parse(fs.readFileSync(file, "utf8"));
16
+ if (typeof raw.projectId !== "string" || !raw.projectId)
17
+ return null;
18
+ const teamId = typeof raw.orgId === "string" && raw.orgId.startsWith("team_") ? raw.orgId : undefined;
19
+ return { projectId: raw.projectId, teamId };
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function targetsOf(env) {
26
+ if (Array.isArray(env.target))
27
+ return env.target;
28
+ return env.target ? [env.target] : [];
29
+ }
30
+ function vercelType(type) {
31
+ return type === "sensitive" ? "sensitive" : "encrypted";
32
+ }
33
+ export function createVercelAdapter(opts) {
34
+ const target = opts.target;
35
+ const base = pushApiBase(VERCEL_API);
36
+ let projectId = "";
37
+ let teamId;
38
+ let envs = null;
39
+ const request = (method, p, body) => {
40
+ const sep = p.includes("?") ? "&" : "?";
41
+ const full = teamId ? `${p}${sep}teamId=${encodeURIComponent(teamId)}` : p;
42
+ return platformRequest({ title: "Vercel", base, token: opts.token, method, path: full, body });
43
+ };
44
+ /** Entries that set a value for the pushed target on every branch. Branch-only entries are not ours. */
45
+ const forTarget = (key) => (envs ?? []).filter((e) => !e.gitBranch && targetsOf(e).includes(target) && (key === undefined || e.key === key));
46
+ async function loadEnvs() {
47
+ if (!envs) {
48
+ const res = await request("GET", `/v9/projects/${encodeURIComponent(projectId)}/env`);
49
+ envs = res.envs ?? [];
50
+ }
51
+ return envs;
52
+ }
53
+ async function create(name, value, type) {
54
+ await request("POST", `/v10/projects/${encodeURIComponent(projectId)}/env`, {
55
+ key: name,
56
+ value,
57
+ type: vercelType(type),
58
+ target: [target],
59
+ });
60
+ }
61
+ async function remove(id) {
62
+ await request("DELETE", `/v9/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(id)}`);
63
+ }
64
+ async function setTargets(id, targets) {
65
+ await request("PATCH", `/v9/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(id)}`, {
66
+ target: targets,
67
+ });
68
+ }
69
+ async function write(v) {
70
+ const [entry] = forTarget(v.name);
71
+ const want = vercelType(v.type);
72
+ if (entry && targetsOf(entry).length === 1 && entry.type === want) {
73
+ await request("PATCH", `/v9/projects/${encodeURIComponent(projectId)}/env/${encodeURIComponent(entry.id)}`, {
74
+ value: v.value,
75
+ });
76
+ return v.type;
77
+ }
78
+ if (entry && targetsOf(entry).length === 1) {
79
+ // Vercel does not change a variable's type in place. Remove and add it again, for this target only.
80
+ await remove(entry.id);
81
+ }
82
+ else if (entry) {
83
+ // The entry also serves other targets. They keep it. This target gets its own entry.
84
+ await setTargets(entry.id, targetsOf(entry).filter((t) => t !== target));
85
+ }
86
+ try {
87
+ await create(v.name, v.value, v.type);
88
+ return v.type;
89
+ }
90
+ catch (err) {
91
+ if (v.type === "sensitive" && target === "development" && err instanceof PlatformError && err.status === 400) {
92
+ await create(v.name, v.value, "readable");
93
+ return "readable";
94
+ }
95
+ if (entry) {
96
+ throw new Error(`${v.name} was taken off Vercel ${target} to write it again, then adding it failed: ${err.message} Run push again.`);
97
+ }
98
+ throw err;
99
+ }
100
+ }
101
+ return {
102
+ id: "vercel",
103
+ title: "Vercel",
104
+ target,
105
+ isReserved: isVercelReserved,
106
+ typeFor: (name) => (isPublicEnvName(name) ? "readable" : "sensitive"),
107
+ check(values) {
108
+ const skip = [];
109
+ const warnings = [];
110
+ let total = 0;
111
+ for (const [name, value] of Object.entries(values)) {
112
+ const bytes = Buffer.byteLength(name) + Buffer.byteLength(value);
113
+ if (bytes > MAX_TOTAL_BYTES) {
114
+ skip.push({ name, reason: "larger than 64 KB, the Vercel limit" });
115
+ continue;
116
+ }
117
+ total += bytes;
118
+ }
119
+ if (total > MAX_TOTAL_BYTES) {
120
+ warnings.push(`These values add up to ${Math.ceil(total / 1024)} KB. Vercel allows 64 KB of variables per deployment.`);
121
+ }
122
+ return { skip, warnings };
123
+ },
124
+ planNotes(plan) {
125
+ if (target !== "development" || !plan.some((p) => p.type === "sensitive"))
126
+ return [];
127
+ return [
128
+ "If Vercel refuses Sensitive for Development, those variables are pushed as readable and listed after the push.",
129
+ ];
130
+ },
131
+ async destination() {
132
+ const file = opts.project ? null : readVercelProjectFile(opts.cwd);
133
+ const ref = opts.project ? { projectId: opts.project, teamId: opts.team } : file;
134
+ if (!ref) {
135
+ throw new Error("No Vercel project. Run `vercel link` in this folder, or pass --project <id> (and --team <id> for a team project).");
136
+ }
137
+ projectId = ref.projectId;
138
+ teamId = opts.team ?? ref.teamId;
139
+ const project = await request("GET", `/v9/projects/${encodeURIComponent(projectId)}`);
140
+ projectId = project.id;
141
+ if (!teamId && project.accountId?.startsWith("team_"))
142
+ teamId = project.accountId;
143
+ let ownerSlug;
144
+ if (teamId) {
145
+ const team = await request("GET", `/v2/teams/${encodeURIComponent(teamId)}`);
146
+ ownerSlug = team.slug;
147
+ }
148
+ else {
149
+ const me = await request("GET", "/v2/user");
150
+ ownerSlug = me.user.username;
151
+ }
152
+ return {
153
+ provider: "vercel",
154
+ title: "Vercel",
155
+ externalId: project.id,
156
+ externalName: project.name,
157
+ ownerSlug,
158
+ label: `${ownerSlug}/${project.name}`,
159
+ target,
160
+ };
161
+ },
162
+ async remoteNames() {
163
+ await loadEnvs();
164
+ return [
165
+ ...new Set(forTarget()
166
+ .filter((e) => e.type !== "system" && !isVercelReserved(e.key))
167
+ .map((e) => e.key)),
168
+ ];
169
+ },
170
+ async apply(upserts, prune) {
171
+ await loadEnvs();
172
+ const result = { written: [], pruned: [], notPruned: [], notWritten: [], notes: [] };
173
+ for (const v of upserts) {
174
+ try {
175
+ const type = await write(v);
176
+ result.written.push({ name: v.name, type });
177
+ }
178
+ catch (err) {
179
+ result.notWritten.push({ name: v.name, reason: err.message });
180
+ }
181
+ }
182
+ const fellBack = result.written.filter((w) => w.type === "readable" && upserts.find((u) => u.name === w.name)?.type === "sensitive");
183
+ if (fellBack.length > 0) {
184
+ result.notes.push(`Vercel refused Sensitive for Development. Pushed as readable: ${fellBack.map((w) => w.name).join(", ")}.`);
185
+ }
186
+ for (const name of prune) {
187
+ for (const entry of forTarget(name)) {
188
+ const targets = targetsOf(entry);
189
+ try {
190
+ if (targets.length === 1)
191
+ await remove(entry.id);
192
+ else
193
+ await setTargets(entry.id, targets.filter((t) => t !== target));
194
+ if (!result.pruned.includes(name))
195
+ result.pruned.push(name);
196
+ }
197
+ catch (err) {
198
+ result.notPruned.push({ name, reason: err.message });
199
+ }
200
+ }
201
+ }
202
+ return result;
203
+ },
204
+ };
205
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getmyenv",
3
- "version": "0.12.2",
3
+ "version": "0.14.0",
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": {
@@ -13,35 +13,42 @@
13
13
  "README.md",
14
14
  "LICENSE"
15
15
  ],
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/yobotn/getmyenv.git"
19
- },
20
16
  "homepage": "https://getmyenv.com",
21
17
  "bugs": {
22
- "url": "https://github.com/yobotn/getmyenv/issues"
18
+ "url": "https://getmyenv.com/contact",
19
+ "email": "hello@getmyenv.com"
23
20
  },
24
21
  "keywords": [
25
22
  "env",
26
- "environment",
27
- "secrets",
28
23
  "dotenv",
29
- "cli"
24
+ "environment-variables",
25
+ "secrets",
26
+ "secrets-management",
27
+ "encryption",
28
+ "e2ee",
29
+ "end-to-end-encryption",
30
+ "cli",
31
+ "config",
32
+ "configuration",
33
+ "devops",
34
+ "vault",
35
+ ".env",
36
+ "api-keys"
30
37
  ],
31
38
  "license": "MIT",
32
39
  "publishConfig": {
33
40
  "access": "public"
34
41
  },
35
42
  "dependencies": {
36
- "@getmyenv/crypto": "0.5.0",
37
- "@getmyenv/shared": "0.11.0",
43
+ "@getmyenv/crypto": "0.6.1",
44
+ "@getmyenv/shared": "0.13.0",
38
45
  "@napi-rs/keyring": "^2.1.0",
39
46
  "commander": "^15.0.0",
40
47
  "dotenv": "^16.4.7",
41
48
  "picocolors": "^1.1.1"
42
49
  },
43
50
  "devDependencies": {
44
- "@types/node": "^22.13.10",
51
+ "@types/node": "^24.19.0",
45
52
  "typescript": "^7.0.2",
46
53
  "vitest": "^4.1.11"
47
54
  },