@devdogsuga/devtools 0.1.51-next.541 → 0.1.51-next.545
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/{cli-BoTmTNqH.js → cli-DNZ-6WEI.js} +105 -61
- package/dist/{root-CsocfxnK.js → failure-log-MSdCgvLc.js} +5 -75
- package/dist/launch.js +28 -11
- package/dist/{load-Cp8AMERM.js → load-Z2CXCpyx.js} +7 -4
- package/dist/load-yn7JCcS2.js +2 -0
- package/dist/{menu-C30rQm5v.js → menu-oQE5IqAI.js} +168 -146
- package/dist/root-DfH38DFq.js +2 -0
- package/dist/root-PA8K5G2d.js +77 -0
- package/dist/{supabase-project-BoJKMCxL.js → run-CRBte7p0.js} +235 -249
- package/dist/{session-rbONwFvY.js → session-CuexI1f9.js} +1 -1
- package/dist/{session-CmDxHEMn.js → session-DzgtIg6K.js} +1 -1
- package/dist/spawn-BvLQzc_K.js +100 -0
- package/dist/{stack-7Lbym6Wq.js → stack-BRevXgI3.js} +1 -1
- package/dist/stack-Cl_B_dt7.js +2 -0
- package/dist/{telemetry-BKFDtzXI.js → telemetry-BDTAGquh.js} +19 -4
- package/dist/telemetry.js +2 -2
- package/dist/with-env.js +15 -17
- package/package.json +6 -4
- package/dist/load-Czhayrsk.js +0 -2
- package/dist/stack-7qA2UP9g.js +0 -2
|
@@ -1,12 +1,175 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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,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 };
|