getmyenv 0.13.0 → 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.
- package/README.md +14 -12
- package/dist/bin.js +8 -0
- package/dist/cli.js +11 -3
- package/dist/commands/backup.js +1 -1
- package/dist/commands/claim.js +3 -2
- package/dist/commands/context.js +4 -4
- package/dist/commands/export.js +17 -11
- package/dist/commands/guest.d.ts +1 -1
- package/dist/commands/guest.js +17 -6
- package/dist/commands/import.js +38 -9
- package/dist/commands/lock.js +1 -1
- package/dist/commands/ls.js +2 -2
- package/dist/commands/push.js +6 -5
- package/dist/commands/restore.js +3 -3
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +61 -12
- package/dist/commands/set.d.ts +16 -0
- package/dist/commands/set.js +91 -12
- package/dist/commands/start.js +7 -7
- package/dist/commands/status.js +3 -3
- package/dist/commands/template.js +3 -3
- package/dist/commands/unlink.d.ts +14 -5
- package/dist/commands/unlink.js +79 -17
- package/dist/lib/api-url.d.ts +2 -0
- package/dist/lib/api-url.js +10 -0
- package/dist/lib/api.d.ts +22 -2
- package/dist/lib/api.js +108 -34
- package/dist/lib/authorize.js +2 -2
- package/dist/lib/bootstrap.d.ts +5 -0
- package/dist/lib/bootstrap.js +32 -8
- package/dist/lib/colors.d.ts +14 -0
- package/dist/lib/colors.js +17 -0
- package/dist/lib/confirm.d.ts +7 -3
- package/dist/lib/confirm.js +21 -12
- package/dist/lib/context.js +1 -1
- package/dist/lib/credentials.d.ts +4 -0
- package/dist/lib/credentials.js +13 -11
- package/dist/lib/fs.d.ts +36 -1
- package/dist/lib/fs.js +102 -23
- package/dist/lib/known-keys-cache.d.ts +3 -2
- package/dist/lib/known-keys-cache.js +16 -4
- package/dist/lib/known-keys.js +1 -1
- package/dist/lib/recovery-code.js +1 -1
- package/dist/lib/resolve.js +1 -1
- package/dist/lib/run-env.d.ts +19 -3
- package/dist/lib/run-env.js +66 -13
- package/dist/lib/template-file.js +1 -1
- package/dist/lib/update-check.js +1 -1
- package/dist/lib/upload.js +2 -2
- package/dist/lib/vault.js +1 -1
- package/dist/providers/http.js +15 -12
- package/dist/providers/netlify.js +3 -1
- package/dist/providers/vercel.js +3 -1
- package/package.json +19 -12
package/dist/lib/fs.js
CHANGED
|
@@ -27,17 +27,29 @@ export function getProjectRoot(cwd = process.cwd()) {
|
|
|
27
27
|
export function getLocalDir(cwd = process.cwd()) {
|
|
28
28
|
return path.join(getProjectRoot(cwd), LOCAL_DIR);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/** Text of a file as UTF-8, without a byte order mark (Notepad and PowerShell 5.1 add one). */
|
|
31
|
+
export function readTextFile(file) {
|
|
32
|
+
return fs.readFileSync(file, "utf8").replace(/^/, "");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse a JSON file. Null when it does not exist. Invalid JSON throws
|
|
36
|
+
* `invalid`: a broken file is never read as missing, so it is never
|
|
37
|
+
* silently replaced.
|
|
38
|
+
*/
|
|
39
|
+
export function readJsonFile(file, invalid) {
|
|
32
40
|
if (!fs.existsSync(file))
|
|
33
41
|
return null;
|
|
34
|
-
let raw;
|
|
35
42
|
try {
|
|
36
|
-
|
|
43
|
+
return JSON.parse(readTextFile(file));
|
|
37
44
|
}
|
|
38
45
|
catch {
|
|
39
|
-
|
|
46
|
+
throw new Error(invalid);
|
|
40
47
|
}
|
|
48
|
+
}
|
|
49
|
+
export const PROJECT_JSON_INVALID = ".getmyenv/project.json is not valid JSON. Fix it, or restore it: git checkout -- .getmyenv/project.json";
|
|
50
|
+
export function readProjectMeta(cwd = process.cwd()) {
|
|
51
|
+
const file = path.join(getLocalDir(cwd), "project.json");
|
|
52
|
+
const raw = readJsonFile(file, PROJECT_JSON_INVALID);
|
|
41
53
|
if (!raw || typeof raw !== "object")
|
|
42
54
|
return null;
|
|
43
55
|
const d = raw;
|
|
@@ -69,21 +81,32 @@ export function writeProjectMeta(meta, cwd = process.cwd()) {
|
|
|
69
81
|
fs.writeFileSync(path.join(dir, "project.json"), JSON.stringify(payload, null, 2) + "\n", { mode: 0o600 });
|
|
70
82
|
}
|
|
71
83
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
84
|
+
* Check that `file` can be written as a plain file: not a symlink, not a
|
|
85
|
+
* folder, and its folder exists. Returns its stats, or null when it is new.
|
|
74
86
|
*/
|
|
75
|
-
export function
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
stat = fs.lstatSync(file);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
/* does not exist */
|
|
82
|
-
}
|
|
87
|
+
export function assertFileTarget(file) {
|
|
88
|
+
const stat = fs.lstatSync(file, { throwIfNoEntry: false }) ?? null;
|
|
83
89
|
if (stat?.isSymbolicLink())
|
|
84
90
|
throw new Error(`${file} is a symlink. Refusing to write through it.`);
|
|
91
|
+
if (stat?.isDirectory()) {
|
|
92
|
+
throw new Error(`${file} is a folder. Pass a file name with -o.`);
|
|
93
|
+
}
|
|
85
94
|
if (stat && !stat.isFile())
|
|
86
95
|
throw new Error(`${file} is not a regular file.`);
|
|
96
|
+
if (!stat) {
|
|
97
|
+
const dir = path.dirname(path.resolve(file));
|
|
98
|
+
if (!fs.statSync(dir, { throwIfNoEntry: false })?.isDirectory()) {
|
|
99
|
+
throw new Error(`The folder ${path.dirname(file)} does not exist. Create it first, or pick another path with -o.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return stat;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write a file only this user can read. Never through a symlink. A new file
|
|
106
|
+
* is created with `wx`; an existing one is replaced only when `overwrite`.
|
|
107
|
+
*/
|
|
108
|
+
export function writePrivateFile(file, content, opts) {
|
|
109
|
+
const stat = assertFileTarget(file);
|
|
87
110
|
if (stat && !opts.overwrite)
|
|
88
111
|
throw new Error(`${file} already exists. Pick another name with -o.`);
|
|
89
112
|
fs.writeFileSync(file, content, { mode: 0o600, flag: stat ? "w" : "wx" });
|
|
@@ -265,7 +288,7 @@ export function readClaimFile(cwd = process.cwd()) {
|
|
|
265
288
|
if (!fs.existsSync(file))
|
|
266
289
|
return null;
|
|
267
290
|
try {
|
|
268
|
-
const raw = JSON.parse(
|
|
291
|
+
const raw = JSON.parse(readTextFile(file));
|
|
269
292
|
if (typeof raw.claimCode !== "string" ||
|
|
270
293
|
typeof raw.passphrase !== "string" ||
|
|
271
294
|
typeof raw.projectId !== "string") {
|
|
@@ -303,20 +326,76 @@ export function clearClaimFile(cwd = process.cwd()) {
|
|
|
303
326
|
fs.unlinkSync(file);
|
|
304
327
|
return true;
|
|
305
328
|
}
|
|
306
|
-
/** dotenv rules (multiline values, inline comments, BOM)
|
|
307
|
-
export function
|
|
308
|
-
const
|
|
329
|
+
/** dotenv rules (multiline values, inline comments, BOM). Invalid names land in `skipped`. */
|
|
330
|
+
export function readEnvText(text) {
|
|
331
|
+
const values = {};
|
|
332
|
+
const skipped = [];
|
|
309
333
|
for (const [key, value] of Object.entries(dotenv.parse(text))) {
|
|
310
334
|
if (isValidVariableName(key))
|
|
311
|
-
|
|
335
|
+
values[key] = value;
|
|
336
|
+
else
|
|
337
|
+
skipped.push(key);
|
|
338
|
+
}
|
|
339
|
+
return { values, skipped };
|
|
340
|
+
}
|
|
341
|
+
/** `readEnvText`, valid names only. */
|
|
342
|
+
export function parseEnvText(text) {
|
|
343
|
+
return readEnvText(text).values;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Text from bytes, by byte order mark: UTF-16 LE or BE (PowerShell 5.1 `>`
|
|
347
|
+
* writes UTF-16 LE), else UTF-8 without its BOM.
|
|
348
|
+
*/
|
|
349
|
+
export function decodeText(buf) {
|
|
350
|
+
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
|
351
|
+
return buf.subarray(2, 2 + ((buf.length - 2) & ~1)).toString("utf16le");
|
|
352
|
+
}
|
|
353
|
+
if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) {
|
|
354
|
+
return Buffer.from(buf.subarray(2, 2 + ((buf.length - 2) & ~1))).swap16().toString("utf16le");
|
|
355
|
+
}
|
|
356
|
+
return buf.toString("utf8").replace(/^/, "");
|
|
357
|
+
}
|
|
358
|
+
/** `readEnvText` for a file, UTF-8 or UTF-16. A missing file reads as empty. */
|
|
359
|
+
export function readEnvFile(filePath) {
|
|
360
|
+
if (!fs.existsSync(filePath))
|
|
361
|
+
return { values: {}, skipped: [] };
|
|
362
|
+
return readEnvText(decodeText(fs.readFileSync(filePath)));
|
|
363
|
+
}
|
|
364
|
+
/** Names that appear more than once in an env file, with their count. dotenv keeps the last one. */
|
|
365
|
+
export function duplicateEnvNames(filePath) {
|
|
366
|
+
if (!fs.existsSync(filePath))
|
|
367
|
+
return [];
|
|
368
|
+
const counts = new Map();
|
|
369
|
+
const text = decodeText(fs.readFileSync(filePath));
|
|
370
|
+
for (const line of splitEnvEntries(text)) {
|
|
371
|
+
// Same line rule as dotenv: optional `export`, a name, then = or :.
|
|
372
|
+
const m = line.match(/^\s*(?:export\s+)?([\w.-]+)\s*[=:]/);
|
|
373
|
+
if (m?.[1])
|
|
374
|
+
counts.set(m[1], (counts.get(m[1]) ?? 0) + 1);
|
|
375
|
+
}
|
|
376
|
+
return [...counts.entries()].filter(([, n]) => n > 1).map(([name, count]) => ({ name, count }));
|
|
377
|
+
}
|
|
378
|
+
/** Lines of an env file, skipping the inside of quoted multiline values. */
|
|
379
|
+
function splitEnvEntries(text) {
|
|
380
|
+
const out = [];
|
|
381
|
+
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
382
|
+
let open = null;
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
if (open) {
|
|
385
|
+
if (line.includes(open))
|
|
386
|
+
open = null;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
out.push(line);
|
|
390
|
+
const m = line.match(/^\s*(?:export\s+)?[\w.-]+\s*[=:]\s*(["'`])(.*)$/);
|
|
391
|
+
if (m && !m[2].includes(m[1]))
|
|
392
|
+
open = m[1];
|
|
312
393
|
}
|
|
313
394
|
return out;
|
|
314
395
|
}
|
|
315
396
|
/** `parseEnvText` for a file. A missing file reads as empty. */
|
|
316
397
|
export function parseEnvFile(filePath) {
|
|
317
|
-
|
|
318
|
-
return {};
|
|
319
|
-
return parseEnvText(fs.readFileSync(filePath, "utf8"));
|
|
398
|
+
return readEnvFile(filePath).values;
|
|
320
399
|
}
|
|
321
400
|
const ENV_SKIP_DIRS = new Set([
|
|
322
401
|
"node_modules",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Staff additions to the known keys list,
|
|
3
|
-
* refreshed once a day. Any failure keeps the
|
|
2
|
+
* Staff additions to the known keys list, and the defaults staff turned off,
|
|
3
|
+
* cached in the config dir and refreshed once a day. Any failure keeps the
|
|
4
|
+
* bundled list.
|
|
4
5
|
*/
|
|
5
6
|
import { type KnownKeyIndex } from "@getmyenv/shared";
|
|
6
7
|
/** The bundled list plus the cached additions. No network. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Staff additions to the known keys list,
|
|
3
|
-
* refreshed once a day. Any failure keeps the
|
|
2
|
+
* Staff additions to the known keys list, and the defaults staff turned off,
|
|
3
|
+
* cached in the config dir and refreshed once a day. Any failure keeps the
|
|
4
|
+
* bundled list.
|
|
4
5
|
*/
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import path from "node:path";
|
|
@@ -25,6 +26,8 @@ function readCache() {
|
|
|
25
26
|
etag: typeof data.etag === "string" ? data.etag : null,
|
|
26
27
|
checkedAt: data.checkedAt,
|
|
27
28
|
entries: data.entries,
|
|
29
|
+
// Caches from older CLIs have no disabled list.
|
|
30
|
+
disabled: Array.isArray(data.disabled) ? data.disabled : [],
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
catch {
|
|
@@ -44,7 +47,10 @@ export function knownKeys() {
|
|
|
44
47
|
if (index)
|
|
45
48
|
return index;
|
|
46
49
|
const cached = readCache();
|
|
47
|
-
index =
|
|
50
|
+
index =
|
|
51
|
+
cached && (cached.entries.length || cached.disabled.length)
|
|
52
|
+
? createKnownKeyIndex(KNOWN_KEYS, cached.entries, cached.disabled)
|
|
53
|
+
: BUNDLED_KNOWN_KEYS;
|
|
48
54
|
return index;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
@@ -77,7 +83,13 @@ export async function refreshKnownKeys(apiUrl) {
|
|
|
77
83
|
const data = JSON.parse(text);
|
|
78
84
|
if (!Array.isArray(data.entries))
|
|
79
85
|
return;
|
|
80
|
-
writeCache({
|
|
86
|
+
writeCache({
|
|
87
|
+
apiUrl,
|
|
88
|
+
etag: res.headers.get("etag"),
|
|
89
|
+
checkedAt,
|
|
90
|
+
entries: data.entries,
|
|
91
|
+
disabled: Array.isArray(data.disabled) ? data.disabled : [],
|
|
92
|
+
});
|
|
81
93
|
index = null;
|
|
82
94
|
}
|
|
83
95
|
catch {
|
package/dist/lib/known-keys.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import pc from "
|
|
1
|
+
import { pcErr as pc } from "./colors.js";
|
|
2
2
|
import { publicSecretWarning } from "@getmyenv/shared";
|
|
3
3
|
import { knownKeys } from "./known-keys-cache.js";
|
|
4
4
|
/** "Did you mean OPENAI_API_KEY?" for names close to a known one. Nothing is renamed. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import pc from "
|
|
1
|
+
import { pcErr as pc } from "./colors.js";
|
|
2
2
|
import { assertRecoveryWrap, createRecoveryWrap, createUserKeys, formatRecoveryCode, recoveryCodesMatch, wipe, } from "@getmyenv/crypto";
|
|
3
3
|
import { FACTS } from "@getmyenv/shared";
|
|
4
4
|
import { readLine } from "./fs.js";
|
package/dist/lib/resolve.js
CHANGED
package/dist/lib/run-env.d.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
export { explainLines, mergeEnv, runContextLabel, validationError, type MissingKey } from "@getmyenv/shared";
|
|
2
2
|
/** Names from the server go into an environment or a file. Refuse any that are not variable names. */
|
|
3
3
|
export declare function assertVariableNames(names: Iterable<string>): void;
|
|
4
|
+
/**
|
|
5
|
+
* One argument for a Windows program: quoted by the MSVCRT rules (backslashes
|
|
6
|
+
* before a quote or the end are doubled, inner quotes become \"), then every
|
|
7
|
+
* cmd.exe metacharacter, quotes included, is escaped with ^ so cmd passes the
|
|
8
|
+
* text through untouched and never expands %VAR%. A .cmd or .bat file reads
|
|
9
|
+
* its arguments through cmd a second time, so those get a second ^.
|
|
10
|
+
*/
|
|
11
|
+
export declare function windowsArgument(arg: string, batch?: boolean): string;
|
|
4
12
|
/**
|
|
5
13
|
* One cmd.exe command line from argv. Windows needs the shell to find `npm.cmd`
|
|
6
14
|
* and friends, and Node does not quote args passed alongside `shell: true`.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
15
|
+
* Arguments follow cross-spawn's rules (see `windowsArgument`). The command
|
|
16
|
+
* is quoted when it holds a space, so a path like C:\Program Files works.
|
|
17
|
+
*/
|
|
18
|
+
export declare function windowsCommandLine(args: string[], opts?: {
|
|
19
|
+
batch?: boolean;
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* True when cmd.exe would start `command` from a .cmd or .bat file (npm, npx,
|
|
23
|
+
* pnpm and most node_modules/.bin shims). Looks in the current folder, then
|
|
24
|
+
* PATH, with PATHEXT, the way cmd does.
|
|
9
25
|
*/
|
|
10
|
-
export declare function
|
|
26
|
+
export declare function isWindowsBatch(command: string, env: Record<string, string | undefined>, cwd?: string): boolean;
|
|
11
27
|
/** Exit code for a child that ended by signal: 128 + signal number, like a shell. */
|
|
12
28
|
export declare function signalExitCode(signal: NodeJS.Signals, numbers: Record<string, number>): number;
|
package/dist/lib/run-env.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Pure pieces of `getmyenv run`. Merge, validate and explain live in
|
|
3
3
|
* @getmyenv/shared so the homepage demo prints the same text.
|
|
4
4
|
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
5
7
|
import { isValidVariableName } from "@getmyenv/shared";
|
|
6
8
|
export { explainLines, mergeEnv, runContextLabel, validationError } from "@getmyenv/shared";
|
|
7
9
|
/** Names from the server go into an environment or a file. Refuse any that are not variable names. */
|
|
@@ -12,23 +14,74 @@ export function assertVariableNames(names) {
|
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
}
|
|
17
|
+
/** Characters cmd.exe treats as special. Each one gets a ^ in front, outside or inside quotes. */
|
|
18
|
+
const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
19
|
+
/** Args made only of these go through as they are. */
|
|
20
|
+
const PLAIN_ARG = /^[\w\-.:/\\+@]+$/;
|
|
21
|
+
/**
|
|
22
|
+
* One argument for a Windows program: quoted by the MSVCRT rules (backslashes
|
|
23
|
+
* before a quote or the end are doubled, inner quotes become \"), then every
|
|
24
|
+
* cmd.exe metacharacter, quotes included, is escaped with ^ so cmd passes the
|
|
25
|
+
* text through untouched and never expands %VAR%. A .cmd or .bat file reads
|
|
26
|
+
* its arguments through cmd a second time, so those get a second ^.
|
|
27
|
+
*/
|
|
28
|
+
export function windowsArgument(arg, batch = false) {
|
|
29
|
+
if (arg !== "" && PLAIN_ARG.test(arg))
|
|
30
|
+
return arg;
|
|
31
|
+
let quoted = arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
|
|
32
|
+
quoted = `"${quoted}"`.replace(CMD_META, "^$1");
|
|
33
|
+
return batch ? quoted.replace(CMD_META, "^$1") : quoted;
|
|
34
|
+
}
|
|
15
35
|
/**
|
|
16
36
|
* One cmd.exe command line from argv. Windows needs the shell to find `npm.cmd`
|
|
17
37
|
* and friends, and Node does not quote args passed alongside `shell: true`.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
38
|
+
* Arguments follow cross-spawn's rules (see `windowsArgument`). The command
|
|
39
|
+
* is quoted when it holds a space, so a path like C:\Program Files works.
|
|
40
|
+
*/
|
|
41
|
+
export function windowsCommandLine(args, opts = {}) {
|
|
42
|
+
const [command = "", ...rest] = args;
|
|
43
|
+
return [windowsCommandName(command), ...rest.map((a) => windowsArgument(a, opts.batch === true))].join(" ");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The program to start. cmd finds it by its own quote rules, and the program
|
|
47
|
+
* reads its own name from the same text, so a path with spaces is plainly
|
|
48
|
+
* quoted. A % is closed out of the quotes and escaped, so cmd does not expand it.
|
|
20
49
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
function windowsCommandName(command) {
|
|
51
|
+
if (PLAIN_ARG.test(command))
|
|
52
|
+
return command;
|
|
53
|
+
return `"${command.replace(/"/g, "").replace(/%/g, '"^%"')}"`;
|
|
54
|
+
}
|
|
55
|
+
function envValue(env, name) {
|
|
56
|
+
const key = Object.keys(env).find((k) => k.toUpperCase() === name);
|
|
57
|
+
return key ? env[key] : undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* True when cmd.exe would start `command` from a .cmd or .bat file (npm, npx,
|
|
61
|
+
* pnpm and most node_modules/.bin shims). Looks in the current folder, then
|
|
62
|
+
* PATH, with PATHEXT, the way cmd does.
|
|
63
|
+
*/
|
|
64
|
+
export function isWindowsBatch(command, env, cwd = process.cwd()) {
|
|
65
|
+
if (/\.(cmd|bat)$/i.test(command))
|
|
66
|
+
return true;
|
|
67
|
+
if (path.extname(command))
|
|
68
|
+
return false;
|
|
69
|
+
const exts = (envValue(env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
70
|
+
const hasDir = /[\\/]/.test(command);
|
|
71
|
+
const dirs = hasDir ? [""] : [cwd, ...(envValue(env, "PATH") ?? "").split(";").filter(Boolean)];
|
|
72
|
+
for (const dir of dirs) {
|
|
73
|
+
for (const ext of exts) {
|
|
74
|
+
const file = path.resolve(dir || cwd, `${command}${ext}`);
|
|
75
|
+
try {
|
|
76
|
+
if (fs.statSync(file).isFile())
|
|
77
|
+
return /^\.(cmd|bat)$/i.test(ext);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* not here */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
32
85
|
}
|
|
33
86
|
/** Exit code for a child that ended by signal: 128 + signal number, like a shell. */
|
|
34
87
|
export function signalExitCode(signal, numbers) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import pc from "
|
|
3
|
+
import { pcErr as pc } from "./colors.js";
|
|
4
4
|
import { mergeTemplateFile, templateFileSchema, TEMPLATE_FILE, TEMPLATE_FILE_MAX_KEYS, } from "@getmyenv/shared";
|
|
5
5
|
import { getLocalDir } from "./fs.js";
|
|
6
6
|
export function templateFilePath(cwd = process.cwd()) {
|
package/dist/lib/update-check.js
CHANGED
|
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import pc from "
|
|
9
|
+
import { pcErr as pc } from "./colors.js";
|
|
10
10
|
import { CLI_VERSION } from "./api.js";
|
|
11
11
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
12
|
const FETCH_TIMEOUT_MS = 2500;
|
package/dist/lib/upload.js
CHANGED
|
@@ -5,10 +5,10 @@ import { writeKey } from "./vault.js";
|
|
|
5
5
|
/** Fail fast before prompting or encrypting. Mirrors the server's `checkWrite`, which still enforces it. */
|
|
6
6
|
export function assertWritable(linked, ctx) {
|
|
7
7
|
if (linked.who.token.kind === "server") {
|
|
8
|
-
throw new Error(`Server tokens
|
|
8
|
+
throw new Error(`Server tokens cannot change values. ${FACTS.readOnlyWho}`);
|
|
9
9
|
}
|
|
10
10
|
if (ctx.protection === "locked") {
|
|
11
|
-
throw new Error(`${ctx.slug} is
|
|
11
|
+
throw new Error(`${ctx.slug} is Read-only. ${FACTS.readOnlyWho}`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
/**
|
package/dist/lib/vault.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import pc from "
|
|
1
|
+
import { pcErr as pc } from "./colors.js";
|
|
2
2
|
import { createUserKeys, generateContextKey, openSealedKey, sealContextKey, splitServerToken, unwrapPrivateKey, wipe, } from "@getmyenv/crypto";
|
|
3
3
|
import { VAULT_UNLOCK_DURATION } from "@getmyenv/shared";
|
|
4
4
|
import { ApiError } from "./api.js";
|
package/dist/providers/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withDeadline } from "../lib/api.js";
|
|
2
2
|
export class PlatformError extends Error {
|
|
3
3
|
status;
|
|
4
4
|
constructor(message, status) {
|
|
@@ -29,23 +29,26 @@ function errorMessage(body) {
|
|
|
29
29
|
*/
|
|
30
30
|
export async function platformRequest(opts) {
|
|
31
31
|
let res;
|
|
32
|
+
let text;
|
|
32
33
|
try {
|
|
33
|
-
res = await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
({ res, text } = await withDeadline(async (signal) => {
|
|
35
|
+
const res = await fetch(`${opts.base}${opts.path}`, {
|
|
36
|
+
method: opts.method,
|
|
37
|
+
headers: {
|
|
38
|
+
authorization: `Bearer ${opts.token}`,
|
|
39
|
+
...(opts.body !== undefined ? { "content-type": "application/json" } : {}),
|
|
40
|
+
},
|
|
41
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
42
|
+
redirect: "error",
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
return { res, text: await res.text() };
|
|
46
|
+
}));
|
|
43
47
|
}
|
|
44
48
|
catch (err) {
|
|
45
49
|
const why = err instanceof Error && err.name === "TimeoutError" ? "timed out" : "is unreachable";
|
|
46
50
|
throw new PlatformError(`${opts.title} ${why}.`, 0);
|
|
47
51
|
}
|
|
48
|
-
const text = await res.text();
|
|
49
52
|
let parsed = null;
|
|
50
53
|
if (text) {
|
|
51
54
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isPublicEnvName, isReservedPushName } from "@getmyenv/shared";
|
|
4
|
+
import { pushApiBase } from "../lib/api-url.js";
|
|
4
5
|
import { PlatformError, platformRequest } from "./http.js";
|
|
5
6
|
export const NETLIFY_API = "https://api.netlify.com";
|
|
6
7
|
const MAX_VALUE_CHARS = 5000;
|
|
@@ -21,11 +22,12 @@ export function readNetlifyStateFile(cwd = process.cwd()) {
|
|
|
21
22
|
}
|
|
22
23
|
export function createNetlifyAdapter(opts) {
|
|
23
24
|
const target = opts.target;
|
|
25
|
+
const base = pushApiBase(NETLIFY_API);
|
|
24
26
|
let siteId = "";
|
|
25
27
|
let accountId = "";
|
|
26
28
|
let vars = null;
|
|
27
29
|
const secretRefused = [];
|
|
28
|
-
const request = (method, p, body) => platformRequest({ title: "Netlify", base
|
|
30
|
+
const request = (method, p, body) => platformRequest({ title: "Netlify", base, token: opts.token, method, path: p, body });
|
|
29
31
|
const envPath = (suffix = "") => `/api/v1/accounts/${encodeURIComponent(accountId)}/env${suffix}?site_id=${encodeURIComponent(siteId)}`;
|
|
30
32
|
const valuesOf = (v) => v.values ?? [];
|
|
31
33
|
const find = (name) => (vars ?? []).find((v) => v.key === name);
|
package/dist/providers/vercel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { isPublicEnvName, isReservedPushName } from "@getmyenv/shared";
|
|
4
|
+
import { pushApiBase } from "../lib/api-url.js";
|
|
4
5
|
import { PlatformError, platformRequest } from "./http.js";
|
|
5
6
|
export const VERCEL_API = "https://api.vercel.com";
|
|
6
7
|
const MAX_TOTAL_BYTES = 64 * 1024;
|
|
@@ -31,13 +32,14 @@ function vercelType(type) {
|
|
|
31
32
|
}
|
|
32
33
|
export function createVercelAdapter(opts) {
|
|
33
34
|
const target = opts.target;
|
|
35
|
+
const base = pushApiBase(VERCEL_API);
|
|
34
36
|
let projectId = "";
|
|
35
37
|
let teamId;
|
|
36
38
|
let envs = null;
|
|
37
39
|
const request = (method, p, body) => {
|
|
38
40
|
const sep = p.includes("?") ? "&" : "?";
|
|
39
41
|
const full = teamId ? `${p}${sep}teamId=${encodeURIComponent(teamId)}` : p;
|
|
40
|
-
return platformRequest({ title: "Vercel", base
|
|
42
|
+
return platformRequest({ title: "Vercel", base, token: opts.token, method, path: full, body });
|
|
41
43
|
};
|
|
42
44
|
/** Entries that set a value for the pushed target on every branch. Branch-only entries are not ours. */
|
|
43
45
|
const forTarget = (key) => (envs ?? []).filter((e) => !e.gitBranch && targetsOf(e).includes(target) && (key === undefined || e.key === key));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getmyenv",
|
|
3
|
-
"version": "0.
|
|
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://
|
|
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
|
-
"
|
|
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.6.
|
|
37
|
-
"@getmyenv/shared": "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": "^
|
|
51
|
+
"@types/node": "^24.19.0",
|
|
45
52
|
"typescript": "^7.0.2",
|
|
46
53
|
"vitest": "^4.1.11"
|
|
47
54
|
},
|