@devdogsuga/devtools 0.1.17 → 0.1.19
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/dist/bws/bw.d.ts +9 -0
- package/dist/bws/bw.js +26 -5
- package/dist/bws/vault.js +5 -4
- package/dist/ci.js +2 -0
- package/dist/launch-ci.js +2 -0
- package/dist/launch.js +2 -0
- package/dist/pipes.d.ts +1 -0
- package/dist/pipes.js +25 -0
- package/dist/telemetry.d.ts +10 -0
- package/dist/telemetry.js +16 -0
- package/env.ts +21 -0
- package/package.json +3 -3
package/dist/bws/bw.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `[command, args]` pair that runs the bundled Bitwarden CLI.
|
|
3
|
+
*
|
|
4
|
+
* Runs `build/bw.js` under this Node rather than through a shim, so it works
|
|
5
|
+
* the same wherever devtools is installed. Falls back to `bw` on PATH when the
|
|
6
|
+
* package cannot be resolved, which keeps a missing install surfacing as the
|
|
7
|
+
* ENOENT every caller already handles.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bwCommand(args: string[]): [string, string[]];
|
|
1
10
|
/**
|
|
2
11
|
* Runs the Bitwarden CLI with `args`, then exits with its status.
|
|
3
12
|
*
|
package/dist/bws/bw.js
CHANGED
|
@@ -12,12 +12,33 @@
|
|
|
12
12
|
* `pnpm --filter @devdogsuga/devtools exec bw`, the last script at the
|
|
13
13
|
* workspace root whose whole job was to reach into this package.
|
|
14
14
|
*
|
|
15
|
-
* ⚠️
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* ⚠️ The binary is resolved from `@bitwarden/cli` itself, never looked up on
|
|
16
|
+
* PATH. A dependency's bin is only linked into the `node_modules/.bin` of the
|
|
17
|
+
* package that depends on it, so under `pnpm dlx`, or with devtools installed
|
|
18
|
+
* in a clone, nothing puts `bw` on PATH and a bare `spawn("bw")` is ENOENT.
|
|
19
19
|
*/
|
|
20
20
|
import { spawn } from "node:child_process";
|
|
21
|
+
import { createRequire } from "node:module";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
/**
|
|
24
|
+
* The `[command, args]` pair that runs the bundled Bitwarden CLI.
|
|
25
|
+
*
|
|
26
|
+
* Runs `build/bw.js` under this Node rather than through a shim, so it works
|
|
27
|
+
* the same wherever devtools is installed. Falls back to `bw` on PATH when the
|
|
28
|
+
* package cannot be resolved, which keeps a missing install surfacing as the
|
|
29
|
+
* ENOENT every caller already handles.
|
|
30
|
+
*/
|
|
31
|
+
export function bwCommand(args) {
|
|
32
|
+
try {
|
|
33
|
+
const require_ = createRequire(import.meta.url);
|
|
34
|
+
const pkgPath = require_.resolve("@bitwarden/cli/package.json");
|
|
35
|
+
const pkg = require_(pkgPath);
|
|
36
|
+
return [process.execPath, [join(dirname(pkgPath), pkg.bin.bw), ...args]];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return ["bw", args];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
21
42
|
/**
|
|
22
43
|
* Runs the Bitwarden CLI with `args`, then exits with its status.
|
|
23
44
|
*
|
|
@@ -28,7 +49,7 @@ import { spawn } from "node:child_process";
|
|
|
28
49
|
*/
|
|
29
50
|
export function runBw(args) {
|
|
30
51
|
return new Promise(() => {
|
|
31
|
-
const child = spawn(
|
|
52
|
+
const child = spawn(...bwCommand(args), { stdio: "inherit" });
|
|
32
53
|
child.on("error", (err) => {
|
|
33
54
|
// ENOENT here means the dependency is not installed rather than that the
|
|
34
55
|
// user mistyped, so say the thing that fixes it.
|
package/dist/bws/vault.js
CHANGED
|
@@ -21,6 +21,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
21
21
|
import { promisify } from "node:util";
|
|
22
22
|
import { confirm, log, spinner } from "@clack/prompts";
|
|
23
23
|
import { unwrap } from "../ui.js";
|
|
24
|
+
import { bwCommand } from "./bw.js";
|
|
24
25
|
const run = promisify(execFile);
|
|
25
26
|
/**
|
|
26
27
|
* The item this looks for, and creates.
|
|
@@ -33,7 +34,7 @@ export const VAULT_ITEM_NAME = "DevDogs Secrets Manager access token (admin)";
|
|
|
33
34
|
/** `bw status`, or `unavailable` when the CLI is not installed. */
|
|
34
35
|
export async function vaultStatus() {
|
|
35
36
|
try {
|
|
36
|
-
const { stdout } = await run(
|
|
37
|
+
const { stdout } = await run(...bwCommand(bwArgs(["status", "--response"])), {
|
|
37
38
|
shell: false,
|
|
38
39
|
});
|
|
39
40
|
// `--response` wraps the payload; the bare form is also accepted, so read
|
|
@@ -80,7 +81,7 @@ async function session(status) {
|
|
|
80
81
|
return new Promise((resolve) => {
|
|
81
82
|
// stdin inherited so the master password goes to `bw` and not through here;
|
|
82
83
|
// stdout piped so the session key can be captured rather than printed.
|
|
83
|
-
const child = spawn(
|
|
84
|
+
const child = spawn(...bwCommand(["unlock", "--raw"]), {
|
|
84
85
|
stdio: ["inherit", "pipe", "inherit"],
|
|
85
86
|
shell: false,
|
|
86
87
|
});
|
|
@@ -127,7 +128,7 @@ export async function readTokenFromVault() {
|
|
|
127
128
|
const s = spinner();
|
|
128
129
|
s.start("Looking in your Bitwarden vault");
|
|
129
130
|
try {
|
|
130
|
-
const { stdout } = await run(
|
|
131
|
+
const { stdout } = await run(...bwCommand(bwArgs(["get", "password", VAULT_ITEM_NAME, "--raw"], key)), { shell: false });
|
|
131
132
|
const token = stdout.trim();
|
|
132
133
|
if (token === "") {
|
|
133
134
|
s.stop("Nothing stored in the vault yet");
|
|
@@ -172,7 +173,7 @@ export async function saveTokenToVault(token) {
|
|
|
172
173
|
login: { username: null, password: token, totp: null },
|
|
173
174
|
};
|
|
174
175
|
return new Promise((resolve) => {
|
|
175
|
-
const child = spawn(
|
|
176
|
+
const child = spawn(...bwCommand(bwArgs(["create", "item"], key)), {
|
|
176
177
|
stdio: ["pipe", "ignore", "pipe"],
|
|
177
178
|
shell: false,
|
|
178
179
|
});
|
package/dist/ci.js
CHANGED
|
@@ -47,6 +47,7 @@ import { loadRegistry } from "./env/discovery.js";
|
|
|
47
47
|
import { positionals } from "./args.js";
|
|
48
48
|
import { findCiCommand, subcommandCiNames } from "./commands.js";
|
|
49
49
|
import { isWorkerApp, workerApps } from "./workers.js";
|
|
50
|
+
import { ignoreClosedPipes } from "./pipes.js";
|
|
50
51
|
import { captureDevtoolsError, initDevtoolsTelemetry, reportDevtoolsError, reportDevtoolsFailure, } from "./telemetry.js";
|
|
51
52
|
function flagValue(rest, flag) {
|
|
52
53
|
const index = rest.indexOf(flag);
|
|
@@ -288,6 +289,7 @@ export async function main(argv) {
|
|
|
288
289
|
// any dispatch below. `[first, rest[0]]` names the step (`deploy
|
|
289
290
|
// secrets-file`, `deploy platform`, …) as the `command` tag rather than just
|
|
290
291
|
// "deploy", which every invocation here would otherwise share.
|
|
292
|
+
ignoreClosedPipes();
|
|
291
293
|
initDevtoolsTelemetry([first, rest[0]].filter(Boolean).join(" ") || "help");
|
|
292
294
|
if (!first || first === "--help" || first === "-h") {
|
|
293
295
|
const steps = subcommandCiNames(["deploy"]);
|
package/dist/launch-ci.js
CHANGED
|
@@ -32,10 +32,12 @@ import { findRepoRoot } from "./repo/root.js";
|
|
|
32
32
|
import { loadEnvLoad, loadEnvSession } from "./repo/peers.js";
|
|
33
33
|
import { stripTierFlag } from "./launch.js";
|
|
34
34
|
import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
|
|
35
|
+
import { ignoreClosedPipes } from "./pipes.js";
|
|
35
36
|
export async function launchCi(argv) {
|
|
36
37
|
const { explicit, rest } = stripTierFlag(argv);
|
|
37
38
|
// See `launch.ts`'s matching call: early, so tier resolution and env entry
|
|
38
39
|
// are covered too. Same tag `ci.ts`'s `main()` would compute.
|
|
40
|
+
ignoreClosedPipes();
|
|
39
41
|
initDevtoolsTelemetry(rest.slice(0, 2).join(" ") || "help");
|
|
40
42
|
const envLoad = await loadEnvLoad();
|
|
41
43
|
const envSession = await loadEnvSession();
|
package/dist/launch.js
CHANGED
|
@@ -55,6 +55,7 @@ import { bareGroupStartPath } from "./menu.js";
|
|
|
55
55
|
import { discoverRepoRoot, findRepoRoot, RepoNotFoundError } from "./repo/root.js";
|
|
56
56
|
import { loadEnvLoad, loadEnvSession } from "./repo/peers.js";
|
|
57
57
|
import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
|
|
58
|
+
import { ignoreClosedPipes } from "./pipes.js";
|
|
58
59
|
import { errorMessage, unwrap } from "./ui.js";
|
|
59
60
|
/**
|
|
60
61
|
* Pulls a global `--tier <t>` out of `argv`, wherever it sits, leaving every
|
|
@@ -144,6 +145,7 @@ export async function launch(argv) {
|
|
|
144
145
|
// resolving or entering the tier below is reported too. `main()`'s own
|
|
145
146
|
// call is then a no-op (`initDevtoolsTelemetry` is idempotent) and this
|
|
146
147
|
// tag, computed from the same argv `main()` receives, stands.
|
|
148
|
+
ignoreClosedPipes();
|
|
147
149
|
initDevtoolsTelemetry(rest[0] ?? "menu");
|
|
148
150
|
// Same test `cli.ts`'s own `main()` uses to decide whether a bare command
|
|
149
151
|
// group resumes the wizard instead of dispatching — computed here too so
|
package/dist/pipes.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ignoreClosedPipes(): void;
|
package/dist/pipes.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps a closed stdout/stderr from crashing devtools.
|
|
3
|
+
*
|
|
4
|
+
* When whatever reads devtools' output goes away first (`devtools … | head`,
|
|
5
|
+
* or a wrapper that stopped listening), the next write fails with EPIPE. The
|
|
6
|
+
* stream emits that as an `'error'` event nobody handles, so Node throws it as
|
|
7
|
+
* an uncaught exception and Sentry files it as a fatal. Nobody is left to read
|
|
8
|
+
* the output, so dropping it is the right outcome. Any other stream error is
|
|
9
|
+
* rethrown and crashes as it did before.
|
|
10
|
+
*
|
|
11
|
+
* Idempotent, because `launch`, `launchCi` and `ci.ts`'s `main` can all run in
|
|
12
|
+
* one process.
|
|
13
|
+
*/
|
|
14
|
+
let installed = false;
|
|
15
|
+
export function ignoreClosedPipes() {
|
|
16
|
+
if (installed)
|
|
17
|
+
return;
|
|
18
|
+
installed = true;
|
|
19
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
20
|
+
stream.on("error", (err) => {
|
|
21
|
+
if (err.code !== "EPIPE")
|
|
22
|
+
throw err;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -13,6 +13,16 @@ export declare function devtoolsEnvironment(): "ci" | "local";
|
|
|
13
13
|
* or between two invocations in the same process, still works).
|
|
14
14
|
*/
|
|
15
15
|
export declare function devtoolsTelemetryEnabled(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether anyone is actually using devtools in this process. Package-registry
|
|
18
|
+
* scanners install every published version within minutes and run it in a
|
|
19
|
+
* throwaway sandbox (random `DESKTOP-xxxxxx` hosts, Firecracker VMs, a fuzzer
|
|
20
|
+
* that turns `process.exit` into a throw). Left unfiltered, each release filed
|
|
21
|
+
* the same two issues from them. They run with no DevDogsUGA checkout and no
|
|
22
|
+
* terminal. A person has at least one of the two (`setup` runs outside a
|
|
23
|
+
* checkout, but in a terminal), and CI always has a checkout.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hasRealCaller(inRepo: boolean, isTTY: boolean): boolean;
|
|
16
26
|
/**
|
|
17
27
|
* Initializes `@sentry/node` for this CLI process. Safe to call more than
|
|
18
28
|
* once (idempotent) and safe to call with no DSN configured (no-ops via
|
package/dist/telemetry.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { readFileSync } from "node:fs";
|
|
30
30
|
import * as Sentry from "@sentry/node";
|
|
31
31
|
import { buildSentryOptions } from "@devdogsuga/telemetry";
|
|
32
|
+
import { discoverRepoRoot } from "./repo/root.js";
|
|
32
33
|
import { ownVersion } from "./version.js";
|
|
33
34
|
/** The DSN baked into this build, or "" if there is none (a source run under
|
|
34
35
|
* tsx, or a build made without `DEVTOOLS_SENTRY_DSN`). */
|
|
@@ -63,6 +64,18 @@ export function devtoolsEnvironment() {
|
|
|
63
64
|
export function devtoolsTelemetryEnabled() {
|
|
64
65
|
return process.env.DEVTOOLS_TELEMETRY !== "0";
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether anyone is actually using devtools in this process. Package-registry
|
|
69
|
+
* scanners install every published version within minutes and run it in a
|
|
70
|
+
* throwaway sandbox (random `DESKTOP-xxxxxx` hosts, Firecracker VMs, a fuzzer
|
|
71
|
+
* that turns `process.exit` into a throw). Left unfiltered, each release filed
|
|
72
|
+
* the same two issues from them. They run with no DevDogsUGA checkout and no
|
|
73
|
+
* terminal. A person has at least one of the two (`setup` runs outside a
|
|
74
|
+
* checkout, but in a terminal), and CI always has a checkout.
|
|
75
|
+
*/
|
|
76
|
+
export function hasRealCaller(inRepo, isTTY) {
|
|
77
|
+
return inRepo || isTTY;
|
|
78
|
+
}
|
|
66
79
|
/**
|
|
67
80
|
* Initializes `@sentry/node` for this CLI process. Safe to call more than
|
|
68
81
|
* once (idempotent) and safe to call with no DSN configured (no-ops via
|
|
@@ -78,6 +91,9 @@ export function initDevtoolsTelemetry(command) {
|
|
|
78
91
|
initialized = true;
|
|
79
92
|
if (!devtoolsTelemetryEnabled())
|
|
80
93
|
return;
|
|
94
|
+
if (!hasRealCaller(discoverRepoRoot() !== null, process.stdin.isTTY === true)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
81
97
|
const options = buildSentryOptions({
|
|
82
98
|
service: "devtools",
|
|
83
99
|
environment: devtoolsEnvironment(),
|
package/env.ts
CHANGED
|
@@ -88,6 +88,27 @@ declare({
|
|
|
88
88
|
example: "61d185ff419ef7bd5bd4b3d314081a49",
|
|
89
89
|
},
|
|
90
90
|
),
|
|
91
|
+
// Read only by the deploy workflow's Sentry release step and the deploy
|
|
92
|
+
// smoke test, never by an app. Without the token, deploys skip the release
|
|
93
|
+
// and its source-map upload with a warning. With it, a failed upload fails
|
|
94
|
+
// the deploy.
|
|
95
|
+
SENTRY_AUTH_TOKEN: define(z.string().min(1).optional(), {
|
|
96
|
+
doc:
|
|
97
|
+
"A Sentry organization auth token. Deploys use it to create each " +
|
|
98
|
+
"release and upload its source maps, and the smoke test uses it to " +
|
|
99
|
+
"read cron check-ins. One token serves every environment.",
|
|
100
|
+
scope: "environment",
|
|
101
|
+
secrecy: "secret",
|
|
102
|
+
commented: true,
|
|
103
|
+
}),
|
|
104
|
+
SENTRY_ORG: define(z.string().min(1).optional(), {
|
|
105
|
+
doc:
|
|
106
|
+
"The Sentry organization slug the release step and smoke test " +
|
|
107
|
+
"address. Identifies, does not authorize.",
|
|
108
|
+
scope: "default",
|
|
109
|
+
secrecy: "public",
|
|
110
|
+
example: "devdogsuga",
|
|
111
|
+
}),
|
|
91
112
|
// Developer-scoped even though the VALUE is org-wide: only operators
|
|
92
113
|
// running `env pull/push/audit` on their own machines read it, no app and
|
|
93
114
|
// no CI job does, and developer scope is what keeps a purely local input
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devdogsuga/devtools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Contributor CLI for a DevDogsUGA checkout: database, env, images, emails, cron, deploy. Ships built JS; run via `pnpm dlx @devdogsuga/devtools` (or the `devtools`/`devtools-ci` bins once installed) from inside a DevDogsUGA clone.",
|
|
@@ -66,11 +66,11 @@
|
|
|
66
66
|
"@vitest/coverage-v8": "^4.1.11",
|
|
67
67
|
"react": "^19.2.8",
|
|
68
68
|
"vitest": "^4.1.11",
|
|
69
|
-
"@devdogsuga/brand": "0.1.2",
|
|
70
69
|
"@devdogsuga/config": "0.1.1",
|
|
70
|
+
"@devdogsuga/db": "0.1.1",
|
|
71
71
|
"@devdogsuga/env": "0.1.2",
|
|
72
72
|
"@devdogsuga/events": "0.1.3",
|
|
73
|
-
"@devdogsuga/
|
|
73
|
+
"@devdogsuga/brand": "0.1.2"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsc && node scripts/write-build-info.mjs",
|