@devdogsuga/devtools 0.1.16 → 0.1.18

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/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
@@ -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
+ }
@@ -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.16",
3
+ "version": "0.1.18",
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,9 +66,9 @@
66
66
  "@vitest/coverage-v8": "^4.1.11",
67
67
  "react": "^19.2.8",
68
68
  "vitest": "^4.1.11",
69
+ "@devdogsuga/db": "0.1.1",
69
70
  "@devdogsuga/brand": "0.1.2",
70
71
  "@devdogsuga/config": "0.1.1",
71
- "@devdogsuga/db": "0.1.1",
72
72
  "@devdogsuga/env": "0.1.2",
73
73
  "@devdogsuga/events": "0.1.3"
74
74
  },