getmyenv 0.11.1 → 0.12.1
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 +6 -3
- package/dist/commands/import.js +1 -1
- package/dist/commands/run.js +15 -0
- package/dist/lib/first-run.d.ts +24 -0
- package/dist/lib/first-run.js +92 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,9 @@ npx getmyenv run -- npm run dev # start with the default context
|
|
|
12
12
|
npx getmyenv run staging -- npm start # or pick one
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
- Run getmyenv where your app starts: your machine, the server, the CI job, or the container's start command. It needs Node 24 or newer there. run is the default. If getmyenv can't run where the app starts, export writes a plaintext .env instead.
|
|
16
|
+
- run fetches the values from getmyenv each time it starts. If it can't reach the server, the command does not start. A running process keeps its values.
|
|
17
|
+
|
|
15
18
|
## Commands
|
|
16
19
|
|
|
17
20
|
- `getmyenv`: setup in a new folder, status in a linked one. Without a TTY it prints next steps and exits.
|
|
@@ -46,7 +49,7 @@ my-app/
|
|
|
46
49
|
```
|
|
47
50
|
|
|
48
51
|
<!-- cli:help -->
|
|
49
|
-
Output of `npx getmyenv --help` (0.
|
|
52
|
+
Output of `npx getmyenv --help` (0.12.1):
|
|
50
53
|
|
|
51
54
|
```text
|
|
52
55
|
Usage: getmyenv [options] [command]
|
|
@@ -108,7 +111,7 @@ One Vault password per account unlocks your private key on your device. Each con
|
|
|
108
111
|
|
|
109
112
|
## CI and servers
|
|
110
113
|
|
|
111
|
-
Create a server token for one context
|
|
114
|
+
Create a server token for one context in Project settings, Tokens. It holds that context's key.
|
|
112
115
|
|
|
113
116
|
```bash
|
|
114
117
|
export GETMYENV_TOKEN=... # server token from the dashboard
|
|
@@ -117,7 +120,7 @@ npx getmyenv run -- node server.js
|
|
|
117
120
|
|
|
118
121
|
Server tokens are read-only. `set` needs the folder token from browser sign-in, or a guest project. `printf %s "$VALUE" | npx getmyenv set API_KEY --stdin` keeps the value out of argv.
|
|
119
122
|
|
|
120
|
-
- `GETMYENV_TOKEN`: a token from
|
|
123
|
+
- `GETMYENV_TOKEN`: a server token from Project settings, Tokens.
|
|
121
124
|
- `GETMYENV_CONFIG_DIR`: machine key and cache (default `~/.config/getmyenv`). Use a persistent volume in containers.
|
|
122
125
|
- `GETMYENV_API_URL`: API origin. https only, http for localhost.
|
|
123
126
|
- `GETMYENV_MACHINE_LABEL`: the machine name shown in approval requests.
|
package/dist/commands/import.js
CHANGED
|
@@ -166,7 +166,7 @@ export async function importCommand(file, opts) {
|
|
|
166
166
|
}
|
|
167
167
|
ensureGitignoreHasEnv();
|
|
168
168
|
if (imported > 0) {
|
|
169
|
-
console.error(pc.dim(
|
|
169
|
+
console.error(pc.dim(`Your .env files are unchanged. ${FACTS.runIgnoresEnvFile}\n`));
|
|
170
170
|
if (!opts.noRunLine) {
|
|
171
171
|
console.error("Start your app with:");
|
|
172
172
|
console.error(pc.cyan(` ${runLine(explicitContext ?? defaultContextSlug(who, meta))}\n`));
|
package/dist/commands/run.js
CHANGED
|
@@ -4,6 +4,7 @@ import pc from "picocolors";
|
|
|
4
4
|
import { decryptSecret, parsePayload } from "@getmyenv/crypto";
|
|
5
5
|
import { ensureTokenAndLink } from "../lib/bootstrap.js";
|
|
6
6
|
import { resolveContext } from "../lib/context.js";
|
|
7
|
+
import { pendingFirstRunHints } from "../lib/first-run.js";
|
|
7
8
|
import { resolveValues } from "../lib/resolve.js";
|
|
8
9
|
import { contextKey } from "../lib/vault.js";
|
|
9
10
|
import { assertVariableNames, explainLines, mergeEnv, runContextLabel, signalExitCode, validationError, windowsCommandLine, } from "../lib/run-env.js";
|
|
@@ -51,6 +52,19 @@ export async function runCommand(opts, childArgs) {
|
|
|
51
52
|
if (merged.unresolved.length > 0) {
|
|
52
53
|
log(pc.yellow(`Not set: ${merged.unresolved.map((m) => m.name).join(", ")}`));
|
|
53
54
|
}
|
|
55
|
+
const hints = opts.explain
|
|
56
|
+
? { lines: [], markShown: () => undefined }
|
|
57
|
+
: pendingFirstRunHints({
|
|
58
|
+
isTTY: Boolean(process.stderr.isTTY),
|
|
59
|
+
env: process.env,
|
|
60
|
+
tokenKind: who.token.kind,
|
|
61
|
+
apiUrl: linked.apiUrl,
|
|
62
|
+
projectId: who.project.id,
|
|
63
|
+
childArgs,
|
|
64
|
+
cwd: process.cwd(),
|
|
65
|
+
});
|
|
66
|
+
for (const line of hints.lines)
|
|
67
|
+
log(pc.dim(line));
|
|
54
68
|
const [cmd, ...args] = childArgs;
|
|
55
69
|
const child = process.platform === "win32"
|
|
56
70
|
? spawn(windowsCommandLine(childArgs), { env: merged.env, stdio: "inherit", shell: true })
|
|
@@ -64,6 +78,7 @@ export async function runCommand(opts, childArgs) {
|
|
|
64
78
|
child.kill(sig);
|
|
65
79
|
});
|
|
66
80
|
}
|
|
81
|
+
child.once("spawn", hints.markShown);
|
|
67
82
|
const code = await new Promise((resolve) => {
|
|
68
83
|
child.on("error", (err) => {
|
|
69
84
|
log(pc.red(`Could not start ${cmd}: ${err.message}`));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type HintContext = {
|
|
2
|
+
isTTY: boolean;
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
tokenKind: "folder" | "guest" | "server";
|
|
5
|
+
};
|
|
6
|
+
export declare function hintsEnabled({ isTTY, env, tokenKind }: HintContext): boolean;
|
|
7
|
+
/** The package.json script a command like `npm start` or `pnpm run dev` starts, if any. */
|
|
8
|
+
export declare function packageScriptName(childArgs: string[]): string | null;
|
|
9
|
+
/** True when that script passes --env-file to node. */
|
|
10
|
+
export declare function scriptUsesEnvFile(childArgs: string[], packageJson: unknown): boolean;
|
|
11
|
+
export declare function hintLines(childArgs: string[], cwd: string): string[];
|
|
12
|
+
/** One marker per server origin and project, in the CLI config dir. */
|
|
13
|
+
export declare function hintMarkerPath(configDir: string, apiUrl: string, projectId: string): string;
|
|
14
|
+
export type PendingHints = {
|
|
15
|
+
lines: string[];
|
|
16
|
+
markShown: () => void;
|
|
17
|
+
};
|
|
18
|
+
export declare function pendingFirstRunHints(input: HintContext & {
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
projectId: string;
|
|
21
|
+
childArgs: string[];
|
|
22
|
+
cwd: string;
|
|
23
|
+
configDir?: () => string;
|
|
24
|
+
}): PendingHints;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hints printed by the first `run` of a project on this machine. Never in CI,
|
|
3
|
+
* with GETMYENV_TOKEN or a server token, or when stderr is not a terminal.
|
|
4
|
+
* The marker is written once the child has spawned, so a run that fails before
|
|
5
|
+
* that shows the hints again. Marker errors never change what `run` does.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { FACTS } from "@getmyenv/shared";
|
|
11
|
+
import { getConfigDir } from "./fs.js";
|
|
12
|
+
import { keychainAccount } from "./keychain.js";
|
|
13
|
+
export function hintsEnabled({ isTTY, env, tokenKind }) {
|
|
14
|
+
if (!isTTY || tokenKind === "server" || env.GETMYENV_TOKEN?.trim())
|
|
15
|
+
return false;
|
|
16
|
+
const ci = env.CI?.trim().toLowerCase();
|
|
17
|
+
return !ci || ci === "false" || ci === "0";
|
|
18
|
+
}
|
|
19
|
+
const MANAGERS = new Set(["npm", "pnpm", "yarn", "bun"]);
|
|
20
|
+
/** The package.json script a command like `npm start` or `pnpm run dev` starts, if any. */
|
|
21
|
+
export function packageScriptName(childArgs) {
|
|
22
|
+
const [cmd, ...rest] = childArgs;
|
|
23
|
+
if (!cmd)
|
|
24
|
+
return null;
|
|
25
|
+
const manager = path.basename(cmd).replace(/\.(cmd|exe|ps1)$/i, "").toLowerCase();
|
|
26
|
+
if (!MANAGERS.has(manager))
|
|
27
|
+
return null;
|
|
28
|
+
const args = rest.filter((a) => !a.startsWith("-"));
|
|
29
|
+
if (args[0] === "run" || args[0] === "run-script")
|
|
30
|
+
return args[1] ?? null;
|
|
31
|
+
if (manager === "npm")
|
|
32
|
+
return args[0] === "start" || args[0] === "test" ? args[0] : null;
|
|
33
|
+
return args[0] ?? null;
|
|
34
|
+
}
|
|
35
|
+
/** True when that script passes --env-file to node. */
|
|
36
|
+
export function scriptUsesEnvFile(childArgs, packageJson) {
|
|
37
|
+
const name = packageScriptName(childArgs);
|
|
38
|
+
if (!name)
|
|
39
|
+
return false;
|
|
40
|
+
const scripts = packageJson?.scripts;
|
|
41
|
+
const script = scripts?.[name];
|
|
42
|
+
return typeof script === "string" && /--env-file\b/.test(script);
|
|
43
|
+
}
|
|
44
|
+
function readPackageJson(cwd) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function hintLines(childArgs, cwd) {
|
|
53
|
+
const lines = [FACTS.runExplainHint];
|
|
54
|
+
if (fs.existsSync(path.join(cwd, ".env")))
|
|
55
|
+
lines.push(FACTS.runIgnoresEnvFile);
|
|
56
|
+
if (scriptUsesEnvFile(childArgs, readPackageJson(cwd)))
|
|
57
|
+
lines.push(FACTS.envFileScripts);
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
60
|
+
/** One marker per server origin and project, in the CLI config dir. */
|
|
61
|
+
export function hintMarkerPath(configDir, apiUrl, projectId) {
|
|
62
|
+
const id = createHash("sha256").update(keychainAccount(apiUrl, projectId)).digest("hex").slice(0, 32);
|
|
63
|
+
return path.join(configDir, "hints", `${id}.json`);
|
|
64
|
+
}
|
|
65
|
+
const NONE = { lines: [], markShown: () => undefined };
|
|
66
|
+
export function pendingFirstRunHints(input) {
|
|
67
|
+
if (!hintsEnabled(input))
|
|
68
|
+
return NONE;
|
|
69
|
+
let marker = null;
|
|
70
|
+
try {
|
|
71
|
+
marker = hintMarkerPath((input.configDir ?? getConfigDir)(), input.apiUrl, input.projectId);
|
|
72
|
+
if (fs.existsSync(marker))
|
|
73
|
+
return NONE;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
marker = null;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
lines: hintLines(input.childArgs, input.cwd),
|
|
80
|
+
markShown: () => {
|
|
81
|
+
if (!marker)
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true, mode: 0o700 });
|
|
85
|
+
fs.writeFileSync(marker, `${JSON.stringify({ shownAt: new Date().toISOString() })}\n`, { mode: 0o600 });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* the hints show again next time */
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getmyenv",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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.5.0",
|
|
37
|
-
"@getmyenv/shared": "0.
|
|
37
|
+
"@getmyenv/shared": "0.11.0",
|
|
38
38
|
"@napi-rs/keyring": "^2.1.0",
|
|
39
39
|
"commander": "^15.0.0",
|
|
40
40
|
"dotenv": "^16.4.7",
|