@hadoku/dev-vault 1.5.1 → 1.5.3
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/dev-vault.mjs +6 -1
- package/login.mjs +21 -3
- package/package.json +3 -2
- package/spawn-spec.mjs +23 -0
package/dev-vault.mjs
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
*/
|
|
74
74
|
|
|
75
75
|
import { spawn } from 'node:child_process';
|
|
76
|
+
import { spawnSpec } from './spawn-spec.mjs';
|
|
76
77
|
import { existsSync, readFileSync } from 'node:fs';
|
|
77
78
|
import { resolve } from 'node:path';
|
|
78
79
|
import process from 'node:process';
|
|
@@ -330,7 +331,11 @@ if (!process.env.NODE_AUTH_TOKEN && fetched.HADOKU_NPM_READ_TOKEN) {
|
|
|
330
331
|
// guard that consumed this was removed 2026-08-03 — pnpm 11 runs preinstall
|
|
331
332
|
// AFTER dependency fetch, so it could never rescue an unauthenticated
|
|
332
333
|
// install; `pnpm vault-install` is the documented path.)
|
|
333
|
-
|
|
334
|
+
// Through cmd.exe on Windows, where pnpm/vite/playwright are .cmd shims
|
|
335
|
+
// (spawn-spec.mjs).
|
|
336
|
+
const [spawnCmd, spawnArgs, spawnOpts] = spawnSpec(childCmd);
|
|
337
|
+
const child = spawn(spawnCmd, spawnArgs, {
|
|
338
|
+
...spawnOpts,
|
|
334
339
|
stdio: 'inherit',
|
|
335
340
|
env: { ...process.env, ...fetched, ...derived, HADOKU_DEV_VAULT_ACTIVE: '1' },
|
|
336
341
|
});
|
package/login.mjs
CHANGED
|
@@ -21,8 +21,23 @@ import { mainCheckoutOf } from './key-file.mjs';
|
|
|
21
21
|
export const EDGE = 'https://hadoku.me';
|
|
22
22
|
const FILE = '.devvault.local.json';
|
|
23
23
|
const POLL_MS = 3000;
|
|
24
|
+
// A request lives a day (edge DEVICE_TTL_S), so the approver can get to it
|
|
25
|
+
// later. Poll briskly while someone is likely approving right now, then slowly:
|
|
26
|
+
// a day at 3s would be ~29k requests for one abandoned login.
|
|
27
|
+
const BRISK_MS = 10 * 60 * 1000;
|
|
28
|
+
const SLOW_POLL_MS = 30_000;
|
|
24
29
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
25
30
|
|
|
31
|
+
/** 86_400_000 -> "24 hours", 600_000 -> "10 minutes". */
|
|
32
|
+
export function spell(ms) {
|
|
33
|
+
const min = Math.round(ms / 60_000);
|
|
34
|
+
if (min % 60 === 0) {
|
|
35
|
+
const h = min / 60;
|
|
36
|
+
return `${h} hour${h === 1 ? '' : 's'}`;
|
|
37
|
+
}
|
|
38
|
+
return `${min} minute${min === 1 ? '' : 's'}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
function runGit(cwd, args) {
|
|
27
42
|
const r = spawnSync('git', args, { cwd, encoding: 'utf-8' });
|
|
28
43
|
return r.status === 0 ? r.stdout.trim() : '';
|
|
@@ -94,9 +109,12 @@ export async function login({
|
|
|
94
109
|
log(`[dev-vault] code ${userCode} · ${host}${repo ? ` · ${repo}` : ''}`);
|
|
95
110
|
log('[dev-vault] waiting for approval…');
|
|
96
111
|
|
|
97
|
-
const
|
|
112
|
+
const ttlMs = (Number(expiresIn) || 600) * 1000;
|
|
113
|
+
const startedAt = now();
|
|
114
|
+
const deadline = startedAt + ttlMs;
|
|
115
|
+
log(`[dev-vault] (the link stays valid for ${spell(ttlMs)}; leave this running)`);
|
|
98
116
|
while (now() < deadline) {
|
|
99
|
-
await sleep(POLL_MS);
|
|
117
|
+
await sleep(now() - startedAt < BRISK_MS ? POLL_MS : SLOW_POLL_MS);
|
|
100
118
|
let polled;
|
|
101
119
|
try {
|
|
102
120
|
polled = await postJson(fetchImpl, `${base}/auth/service-access/device/poll`, {
|
|
@@ -129,6 +147,6 @@ export async function login({
|
|
|
129
147
|
);
|
|
130
148
|
return 0;
|
|
131
149
|
}
|
|
132
|
-
log(
|
|
150
|
+
log(`[dev-vault] login: nobody approved within ${spell(ttlMs)}. Run \`dev-vault login\` again.`);
|
|
133
151
|
return 1;
|
|
134
152
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hadoku/dev-vault",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "1.5.3",
|
|
4
|
+
"gitHead": "4942c6f53fb701a7ba3f411dbb76c55ec65d1b9c",
|
|
5
5
|
"description": "Local-dev vault-broker shim — fetches secrets from the hadoku mgmt-api broker and execs a child command with them in process.env. Works for any consuming repo (Node or non-Node).",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"key-file.mjs",
|
|
14
14
|
"install-id.mjs",
|
|
15
15
|
"login.mjs",
|
|
16
|
+
"spawn-spec.mjs",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
package/spawn-spec.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How to start the child command on this platform.
|
|
3
|
+
*
|
|
4
|
+
* On Windows, `pnpm`, `vite`, `playwright` and every other npm-installed
|
|
5
|
+
* command is a `.cmd` shim, and Node starts a `.cmd` only through a shell
|
|
6
|
+
* (since Node 20.12 it refuses outright, CVE-2024-27980). Spawned bare,
|
|
7
|
+
* `dev-vault -- pnpm install` failed with ENOENT/EINVAL for every Windows
|
|
8
|
+
* contributor, which is most of the people outside the fleet. So on win32 the
|
|
9
|
+
* child goes through cmd.exe, with each argument quoted where cmd would
|
|
10
|
+
* otherwise split or reinterpret it. Everywhere else nothing changes.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** One argument as cmd.exe reads it back verbatim. */
|
|
14
|
+
export function quoteForCmd(arg) {
|
|
15
|
+
if (arg !== '' && !/[\s"&|<>^%()!,;=]/.test(arg)) return arg;
|
|
16
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `[command, args, options]` for `spawn`. */
|
|
20
|
+
export function spawnSpec(argv, platform = process.platform) {
|
|
21
|
+
if (platform !== 'win32') return [argv[0], argv.slice(1), {}];
|
|
22
|
+
return [argv.map(quoteForCmd).join(' '), [], { shell: true }];
|
|
23
|
+
}
|