@devdogsuga/devtools 0.1.51-next.541 → 0.1.51-next.544

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.
@@ -1,12 +1,175 @@
1
- import { d as JSON_FLAG, f as SCOPES, g as positionals, m as createCatalog, p as YES, r as findRepoRoot, s as suppressFailureLog } from "./root-CsocfxnK.js";
2
- import { E as resolveLayout, _ as loadEnvLoad, a as listContainerNames, g as loadEnv, i as foreignStackProjectId, n as containerPrefix, o as readProjectId, p as supabaseCapture, r as foreignStackMessage, t as STACK_API_PORT, x as devdogsugaRootOrNull } from "./supabase-project-BoJKMCxL.js";
3
- import { c as reportDevtoolsError, f as isNonInteractive } from "./telemetry-BKFDtzXI.js";
1
+ import { n as execSync } from "./spawn-BvLQzc_K.js";
2
+ import { n as discoverRepoRoot } from "./root-PA8K5G2d.js";
3
+ import { C as containerPrefix, D as readProjectId, E as listContainerNames, P as resolveLayout, S as STACK_API_PORT, T as foreignStackProjectId, d as loadEnvLoad, k as devdogsugaRootOrNull, s as supabaseCapture, u as loadEnv, w as foreignStackMessage } from "./run-CRBte7p0.js";
4
+ import { d as YES, f as createCatalog, i as suppressFailureLog, l as JSON_FLAG, m as positionals, u as SCOPES } from "./failure-log-MSdCgvLc.js";
5
+ import { l as reportDevtoolsError, p as isNonInteractive } from "./telemetry-BDTAGquh.js";
4
6
  import { existsSync, readFileSync } from "node:fs";
5
7
  import { dirname, join, resolve } from "node:path";
6
8
  import { z } from "zod";
7
9
  import { cancel, confirm, isCancel, log, note, select, text } from "@clack/prompts";
8
10
  import { writeFile } from "node:fs/promises";
9
- import { execFileSync } from "node:child_process";
11
+ //#region ../cli-core/src/environment.ts
12
+ /**
13
+ * What this machine currently is, read once before the menu draws itself.
14
+ *
15
+ * The wizard used to offer the same commands to everyone: someone whose Docker
16
+ * daemon was not running was invited to reset a database that could not
17
+ * answer, and someone whose stack was already up had no way to stop it at all,
18
+ * because "stop" was not in the tree. Both are the same bug. The menu
19
+ * described the CLI rather than the situation.
20
+ *
21
+ * So this reads three facts and `menu.ts` adapts to them. The facts are few
22
+ * and cheap on purpose; see `probeEnvironment` for the budget and why.
23
+ *
24
+ * ## Unknown is not "no"
25
+ *
26
+ * Every probe can come back `"unknown"`, and that is what a failed probe
27
+ * reports rather than guessing. Nothing is hidden or flagged on `"unknown"`: a
28
+ * machine this cannot read gets the full menu, exactly as before. A tool that
29
+ * hides a command because a `docker ps` timed out is worse than one that never
30
+ * adapted at all, because the command it hides is the one you were looking
31
+ * for.
32
+ */
33
+ /**
34
+ * A product repository's root (DogDays, DogPack), set by the launcher in
35
+ * product mode. The probes below read `.env` and `supabase/config.toml` from
36
+ * "the checkout", which in a product is not a DevDogsUGA clone; without this
37
+ * the menu in a product repo died on `RepoNotFoundError` before drawing.
38
+ */
39
+ let productRoot = null;
40
+ function setEnvironmentRoot(root) {
41
+ productRoot = root;
42
+ }
43
+ /**
44
+ * Where the probes look. Never throws: a menu that cannot find a checkout
45
+ * should still draw, with the machine's facts marked unknown or "no".
46
+ */
47
+ function probeRoot() {
48
+ return productRoot ?? discoverRepoRoot() ?? process.cwd();
49
+ }
50
+ /** The directory holding `supabase/config.toml`, or null when there is none. */
51
+ function probeSupabaseRoot() {
52
+ if (productRoot !== null) return existsSync(join(productRoot, "supabase", "config.toml")) ? productRoot : null;
53
+ return discoverRepoRoot() === null ? null : devdogsugaRootOrNull(resolveLayout());
54
+ }
55
+ /**
56
+ * Every probe is bounded, silent, and total.
57
+ *
58
+ * `timeout` because a Docker daemon that is starting up (or a VM that has just
59
+ * been resumed) accepts the connection and then never answers, and the failure
60
+ * that produces is a menu that hangs before printing anything, with no output
61
+ * to explain what it is waiting for.
62
+ */
63
+ function run(file, args) {
64
+ try {
65
+ return execSync(file, args, {
66
+ cwd: probeRoot(),
67
+ stdio: [
68
+ "ignore",
69
+ "pipe",
70
+ "ignore"
71
+ ],
72
+ timeout: 3e3
73
+ });
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ /**
79
+ * Reads the machine.
80
+ *
81
+ * Two subprocesses at worst, one when Docker is down, plus an `existsSync`.
82
+ * The budget is "cheaper than the first keystroke", because this runs before
83
+ * the wizard's first screen and anything slower would be paid on every
84
+ * invocation to save a few of them later.
85
+ *
86
+ * It does NOT call `supabase status`, which is the authoritative answer but
87
+ * takes the better part of a second: it shells out through `pnpm exec` and
88
+ * then talks to every service in the stack. `docker ps` answers the only
89
+ * question the menu has (is it up?) in a fraction of that, and the commands
90
+ * that need real credentials go through `resolveInstance` (`instance.ts`),
91
+ * which reads the session's already-entered env at the moment it matters.
92
+ */
93
+ function probeEnvironment(execute = run) {
94
+ const envFile = existsSync(join(probeRoot(), ".env")) ? "yes" : "no";
95
+ const docker = execute("docker", ["info"]) === null ? "no" : "yes";
96
+ if (docker === "no") return {
97
+ docker,
98
+ stack: "no",
99
+ envFile
100
+ };
101
+ const names = execute("docker", [
102
+ "ps",
103
+ "--format",
104
+ "{{.Names}}"
105
+ ]);
106
+ if (names === null) return {
107
+ docker,
108
+ stack: "unknown",
109
+ envFile
110
+ };
111
+ const supabaseRoot = probeSupabaseRoot();
112
+ const prefix = containerPrefix(supabaseRoot === null ? null : readProjectId(supabaseRoot));
113
+ return {
114
+ docker,
115
+ stack: names.split("\n").some((name) => name.trim().startsWith(prefix)) ? "yes" : "no",
116
+ envFile
117
+ };
118
+ }
119
+ /**
120
+ * Whether a `Condition` from the command tree currently holds.
121
+ *
122
+ * The tree declares conditions as strings so that it stays inert data the docs
123
+ * build can render (see `catalog.ts`); this is the one place that knows what
124
+ * they mean. `"unknown"` propagates rather than collapsing to false, and both
125
+ * callers below read it as "do nothing".
126
+ */
127
+ function holds(condition, env) {
128
+ switch (condition) {
129
+ case "docker": return env.docker;
130
+ case "instance-running": return env.stack;
131
+ case "instance-stopped":
132
+ if (env.stack === "unknown") return "unknown";
133
+ return env.stack === "no" ? "yes" : "no";
134
+ }
135
+ }
136
+ /**
137
+ * Whether the wizard should offer this command at all.
138
+ *
139
+ * False only for a command whose `when` is definitively unmet: `stop`
140
+ * against a stack that is already stopped. This is reserved for commands that
141
+ * would be *meaningless*, never merely inconvenient: one that would fail with
142
+ * a good error message stays in the menu, because a contributor who cannot
143
+ * find a command they know exists is worse off than one who runs it and is
144
+ * told why it did not work.
145
+ *
146
+ * Hiding is a wizard-only decision. `--help` still lists these, the dispatcher
147
+ * still accepts them, and the generated reference still documents them. The
148
+ * menu is a guide to right now, and those three are the reference.
149
+ */
150
+ function isOffered(node, env) {
151
+ return node.when === void 0 || holds(node.when, env) !== "no";
152
+ }
153
+ const LABELS = {
154
+ yes: "yes",
155
+ no: "no",
156
+ unknown: "could not tell"
157
+ };
158
+ /**
159
+ * The three facts, as the wizard's opening note and as `status --local`.
160
+ *
161
+ * Printed before the first question rather than discovered through failures:
162
+ * "Docker running no" at the top of the screen is the whole explanation for
163
+ * why the database commands below it are flagged, and it costs one glance.
164
+ */
165
+ function describeEnvironment(env) {
166
+ return [
167
+ `Docker running ${LABELS[env.docker]}`,
168
+ `Local stack up ${LABELS[env.stack]}`,
169
+ `.env present ${LABELS[env.envFile]}`
170
+ ].join("\n");
171
+ }
172
+ //#endregion
10
173
  //#region ../cli-core/src/help.ts
11
174
  /**
12
175
  * `--help`, one level at a time.
@@ -1497,147 +1660,6 @@ async function resolveDbConnection(opts = {}) {
1497
1660
  };
1498
1661
  }
1499
1662
  //#endregion
1500
- //#region ../cli-core/src/environment.ts
1501
- /**
1502
- * What this machine currently is, read once before the menu draws itself.
1503
- *
1504
- * The wizard used to offer the same commands to everyone: someone whose Docker
1505
- * daemon was not running was invited to reset a database that could not
1506
- * answer, and someone whose stack was already up had no way to stop it at all,
1507
- * because "stop" was not in the tree. Both are the same bug. The menu
1508
- * described the CLI rather than the situation.
1509
- *
1510
- * So this reads three facts and `menu.ts` adapts to them. The facts are few
1511
- * and cheap on purpose; see `probeEnvironment` for the budget and why.
1512
- *
1513
- * ## Unknown is not "no"
1514
- *
1515
- * Every probe can come back `"unknown"`, and that is what a failed probe
1516
- * reports rather than guessing. Nothing is hidden or flagged on `"unknown"`: a
1517
- * machine this cannot read gets the full menu, exactly as before. A tool that
1518
- * hides a command because a `docker ps` timed out is worse than one that never
1519
- * adapted at all, because the command it hides is the one you were looking
1520
- * for.
1521
- */
1522
- /**
1523
- * Every probe is bounded, silent, and total.
1524
- *
1525
- * `timeout` because a Docker daemon that is starting up (or a VM that has just
1526
- * been resumed) accepts the connection and then never answers, and the failure
1527
- * that produces is a menu that hangs before printing anything, with no output
1528
- * to explain what it is waiting for.
1529
- */
1530
- function run(file, args) {
1531
- try {
1532
- return execFileSync(file, args, {
1533
- cwd: findRepoRoot(),
1534
- encoding: "utf8",
1535
- stdio: [
1536
- "ignore",
1537
- "pipe",
1538
- "ignore"
1539
- ],
1540
- timeout: 3e3
1541
- });
1542
- } catch {
1543
- return null;
1544
- }
1545
- }
1546
- /**
1547
- * Reads the machine.
1548
- *
1549
- * Two subprocesses at worst, one when Docker is down, plus an `existsSync`.
1550
- * The budget is "cheaper than the first keystroke", because this runs before
1551
- * the wizard's first screen and anything slower would be paid on every
1552
- * invocation to save a few of them later.
1553
- *
1554
- * It does NOT call `supabase status`, which is the authoritative answer but
1555
- * takes the better part of a second: it shells out through `pnpm exec` and
1556
- * then talks to every service in the stack. `docker ps` answers the only
1557
- * question the menu has (is it up?) in a fraction of that, and the commands
1558
- * that need real credentials go through `resolveInstance` (`instance.ts`),
1559
- * which reads the session's already-entered env at the moment it matters.
1560
- */
1561
- function probeEnvironment(execute = run) {
1562
- const envFile = existsSync(join(findRepoRoot(), ".env")) ? "yes" : "no";
1563
- const docker = execute("docker", ["info"]) === null ? "no" : "yes";
1564
- if (docker === "no") return {
1565
- docker,
1566
- stack: "no",
1567
- envFile
1568
- };
1569
- const names = execute("docker", [
1570
- "ps",
1571
- "--format",
1572
- "{{.Names}}"
1573
- ]);
1574
- if (names === null) return {
1575
- docker,
1576
- stack: "unknown",
1577
- envFile
1578
- };
1579
- const supabaseRoot = devdogsugaRootOrNull(resolveLayout());
1580
- const prefix = containerPrefix(supabaseRoot === null ? null : readProjectId(supabaseRoot));
1581
- return {
1582
- docker,
1583
- stack: names.split("\n").some((name) => name.trim().startsWith(prefix)) ? "yes" : "no",
1584
- envFile
1585
- };
1586
- }
1587
- /**
1588
- * Whether a `Condition` from the command tree currently holds.
1589
- *
1590
- * The tree declares conditions as strings so that it stays inert data the docs
1591
- * build can render (see `catalog.ts`); this is the one place that knows what
1592
- * they mean. `"unknown"` propagates rather than collapsing to false, and both
1593
- * callers below read it as "do nothing".
1594
- */
1595
- function holds(condition, env) {
1596
- switch (condition) {
1597
- case "docker": return env.docker;
1598
- case "instance-running": return env.stack;
1599
- case "instance-stopped":
1600
- if (env.stack === "unknown") return "unknown";
1601
- return env.stack === "no" ? "yes" : "no";
1602
- }
1603
- }
1604
- /**
1605
- * Whether the wizard should offer this command at all.
1606
- *
1607
- * False only for a command whose `when` is definitively unmet: `stop`
1608
- * against a stack that is already stopped. This is reserved for commands that
1609
- * would be *meaningless*, never merely inconvenient: one that would fail with
1610
- * a good error message stays in the menu, because a contributor who cannot
1611
- * find a command they know exists is worse off than one who runs it and is
1612
- * told why it did not work.
1613
- *
1614
- * Hiding is a wizard-only decision. `--help` still lists these, the dispatcher
1615
- * still accepts them, and the generated reference still documents them. The
1616
- * menu is a guide to right now, and those three are the reference.
1617
- */
1618
- function isOffered(node, env) {
1619
- return node.when === void 0 || holds(node.when, env) !== "no";
1620
- }
1621
- const LABELS = {
1622
- yes: "yes",
1623
- no: "no",
1624
- unknown: "could not tell"
1625
- };
1626
- /**
1627
- * The three facts, as the wizard's opening note and as `status --local`.
1628
- *
1629
- * Printed before the first question rather than discovered through failures:
1630
- * "Docker running no" at the top of the screen is the whole explanation for
1631
- * why the database commands below it are flagged, and it costs one glance.
1632
- */
1633
- function describeEnvironment(env) {
1634
- return [
1635
- `Docker running ${LABELS[env.docker]}`,
1636
- `Local stack up ${LABELS[env.stack]}`,
1637
- `.env present ${LABELS[env.envFile]}`
1638
- ].join("\n");
1639
- }
1640
- //#endregion
1641
1663
  //#region ../cli-core/src/invocation.ts
1642
1664
  /**
1643
1665
  * Records the fully-resolved argv of the command being run, so the CLI can
@@ -1978,4 +2000,4 @@ async function runMenu(catalog, dispatch, env = probeEnvironment(), options = {}
1978
2000
  return dispatch(chosen.argv);
1979
2001
  }
1980
2002
  //#endregion
1981
- export { helpPath as A, currentProduct as C, isProductMode as D, activeCatalog as E, renderHelp as M, setProductMode as O, ProductConfigError as S, loadProductConfig as T, bail as _, recordEnteredTier as a, explainError as b, describeEnvironment as c, resolveDbConnection as d, sessionIsLocal as f, setMenuEnvHook as g, realEnvEntryDeps as h, beginInvocation as i, renderCommandList as j, catalog as k, probeEnvironment as l, enterSessionEnvironment as m, bareGroupStartPath as n, recordResolved as o, sessionLabel as p, runMenu as r, reproducibleCommand as s, bareGroupStartFlags as t, nonEmpty as u, errorMessage as v, findProductRoot as w, unwrap as x, explain as y };
2003
+ export { renderHelp as A, loadProductConfig as C, catalog as D, setProductMode as E, probeEnvironment as M, setEnvironmentRoot as N, helpPath as O, findProductRoot as S, isProductMode as T, explain as _, recordEnteredTier as a, ProductConfigError as b, nonEmpty as c, sessionLabel as d, enterSessionEnvironment as f, errorMessage as g, bail as h, beginInvocation as i, describeEnvironment as j, renderCommandList as k, resolveDbConnection as l, setMenuEnvHook as m, bareGroupStartPath as n, recordResolved as o, realEnvEntryDeps as p, runMenu as r, reproducibleCommand as s, bareGroupStartFlags as t, sessionIsLocal as u, explainError as v, activeCatalog as w, currentProduct as x, unwrap as y };
@@ -0,0 +1,2 @@
1
+ import { n as discoverRepoRoot, t as RepoNotFoundError } from "./root-PA8K5G2d.js";
2
+ export { RepoNotFoundError, discoverRepoRoot };
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ //#region ../cli-core/src/repo/root.ts
4
+ /**
5
+ * Where devtools finds the DevDogsUGA checkout it is running against.
6
+ *
7
+ * devtools used to live inside `packages/devtools` of that repo, so its own
8
+ * `import.meta.url` WAS a path into the repo (`PROJECT_ROOT` /
9
+ * `REPO_ROOT` walked up a fixed number of `..` segments from this file).
10
+ * Published as its own package and run through `pnpm dlx`, that assumption
11
+ * breaks: this file's `import.meta.url` points into a dlx cache directory
12
+ * or an isolated `node_modules` tree that has nothing to do with the repo
13
+ * the contributor is standing in.
14
+ *
15
+ * So repo discovery walks up from `process.cwd()` instead, looking for the
16
+ * marker validated in the `devtools-dlx` prototype
17
+ * (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
18
+ * experiment 1): a directory containing `pnpm-workspace.yaml` AND whose
19
+ * `package.json` has `"name": "devdogs-monorepo"` (DevDogsUGA) or
20
+ * `"backstage"` (Backstage, which the CLIs also run from; `layout.ts` says
21
+ * what differs). The name check is not redundant — a bare
22
+ * `pnpm-workspace.yaml` matches any pnpm workspace, which devtools should
23
+ * refuse rather than silently "find".
24
+ */
25
+ /**
26
+ * The `package.json` names a repo root may carry: DevDogsUGA's, and Backstage's
27
+ * (the CLIs run from both; see `layout.ts`).
28
+ */
29
+ const REPO_MARKER_NAMES = ["devdogs-monorepo", "backstage"];
30
+ var RepoNotFoundError = class extends Error {
31
+ constructor() {
32
+ super("run this from inside a DevDogsUGA clone");
33
+ this.name = "RepoNotFoundError";
34
+ }
35
+ };
36
+ function looksLikeRepoRoot(dir) {
37
+ if (!existsSync(join(dir, "pnpm-workspace.yaml"))) return false;
38
+ try {
39
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
40
+ return REPO_MARKER_NAMES.includes(pkg.name);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ /**
46
+ * Walks up from `startDir` (default `process.cwd()`) looking for the repo
47
+ * marker. Returns `null` rather than throwing — `findRepoRoot()` below is
48
+ * the throwing convenience most callers want; this is exported for callers
49
+ * (like `setup`) that need to know without failing.
50
+ */
51
+ function discoverRepoRoot(startDir = process.cwd()) {
52
+ let dir = startDir;
53
+ for (;;) {
54
+ if (looksLikeRepoRoot(dir)) return dir;
55
+ const parent = dirname(dir);
56
+ if (parent === dir) return null;
57
+ dir = parent;
58
+ }
59
+ }
60
+ let cachedRoot;
61
+ /**
62
+ * The memoized, throwing repo-root lookup. Lazy on purpose: importing this
63
+ * module must never fail just because nothing has called it yet (`setup`
64
+ * and `completions` in `launch.ts` never need a repo at all, and must stay
65
+ * that way — see that file's header comment on why).
66
+ *
67
+ * Resolved once per process: `process.cwd()` does not change mid-invocation
68
+ * for this CLI, and re-walking the filesystem on every call would be pure
69
+ * waste.
70
+ */
71
+ function findRepoRoot() {
72
+ if (cachedRoot === void 0) cachedRoot = process.env.DEVTOOLS_TEST_REPO_ROOT ?? discoverRepoRoot();
73
+ if (cachedRoot === null) throw new RepoNotFoundError();
74
+ return cachedRoot;
75
+ }
76
+ //#endregion
77
+ export { discoverRepoRoot as n, findRepoRoot as r, RepoNotFoundError as t };