@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 CHANGED
@@ -97,12 +97,15 @@ name `devdogs-monorepo` or `backstage`; the layout code is
97
97
 
98
98
  A repository with a `devtools.config.json` at its root is a **product** (DogDays, DogPack): devtools
99
99
  reads the file and serves a smaller command set that needs no tiers, no env registry and no workspace.
100
- DogPack has no `package.json`, so it runs the published CLI directly:
100
+ Both products carry a `package.json` whose `devtools` script is a `pnpm dlx` run of the published CLI, so
101
+ inside a checkout:
101
102
 
102
103
  ```sh
103
- npx @devdogsuga/devtools@next setup
104
+ pnpm devtools setup
104
105
  ```
105
106
 
107
+ Anywhere else, the one-off form is `pnpm dlx @devdogsuga/devtools@next setup`.
108
+
106
109
  | Command | What it does |
107
110
  | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
111
  | `setup [--local \| --hosted]` | Writes `.env` from `.env.example`; starts the Docker stack (or takes your own hosted project's URL, keys and session-pooler `DB_URL`); `db sync`; the types; then `oauth`. |
@@ -136,7 +139,7 @@ How it works, in short:
136
139
  `oauth` (the tooling file) check that they are git-ignored and add them to the repo's `.gitignore`
137
140
  when they are not (Flutter's default ignore file leaves them out).
138
141
  - **The pinned CLI.** devtools finds `supabase` in `node_modules/.bin` or on PATH when its version is the
139
- pin, and otherwise runs `npx --yes supabase@<pin>`.
142
+ pin, and otherwise runs `pnpm dlx supabase@<pin>` (`npx --yes supabase@<pin>` only when pnpm is not on PATH).
140
143
  - **A hosted project** needs custom OAuth providers turned on and the app's return addresses allowed.
141
144
  `setup` does both through the Management API when `SUPABASE_ACCESS_TOKEN` is set, and prints the two
142
145
  dashboard steps when it is not.
@@ -1,16 +1,18 @@
1
- import { c as ownPackageDir, g as positionals, h as flagValue, l as ownVersion, n as discoverRepoRoot, r as findRepoRoot, u as cliName } from "./root-CsocfxnK.js";
2
- import { A as helpPath, C as currentProduct, D as isProductMode, E as activeCatalog, M as renderHelp, S as ProductConfigError, T as loadProductConfig, _ as bail, a as recordEnteredTier, b as explainError, c as describeEnvironment, d as resolveDbConnection, f as sessionIsLocal, i as beginInvocation, j as renderCommandList, k as catalog, l as probeEnvironment, n as bareGroupStartPath, o as recordResolved, p as sessionLabel, r as runMenu, s as reproducibleCommand, t as bareGroupStartFlags, u as nonEmpty, v as errorMessage, x as unwrap, y as explain } from "./menu-C30rQm5v.js";
3
- import { A as dryRunKind, C as layoutAt, E as resolveLayout, O as reportRan, P as wouldRunLine, S as findApp, T as resolveAppPath, _ as loadEnvLoad, b as appWorkspaceRoot, c as reportRuns, f as supabase, g as loadEnv, h as getEnvSync, j as isDryRun, k as runInGroup, l as run$1, m as supabaseRoot, s as dbPush, u as runWithStderr, v as loadEnvSession, w as listApps, x as devdogsugaRootOrNull, y as repoPeerUrl } from "./supabase-project-BoJKMCxL.js";
4
- import { d as hasYes, f as isNonInteractive, n as captureDevtoolsError, o as initDevtoolsTelemetry } from "./telemetry-BKFDtzXI.js";
5
- import { t as runStackCommand } from "./stack-7Lbym6Wq.js";
1
+ import { n as execSync, r as spawnProcess } from "./spawn-BvLQzc_K.js";
2
+ import { n as discoverRepoRoot, r as findRepoRoot } from "./root-PA8K5G2d.js";
3
+ import { A as findApp, M as listApps, N as resolveAppPath, O as appWorkspaceRoot, P as resolveLayout, _ as dryRunKind, c as supabaseRoot, d as loadEnvLoad, f as loadEnvSession, g as runInGroup, h as reportRan, i as runWithStderr, j as layoutAt, k as devdogsugaRootOrNull, l as getEnvSync, n as reportRuns, o as supabase, p as repoPeerUrl, r as run$1, t as dbPush, u as loadEnv, v as isDryRun, x as wouldRunLine } from "./run-CRBte7p0.js";
4
+ import { A as renderHelp, C as loadProductConfig, D as catalog, M as probeEnvironment, O as helpPath, T as isProductMode, _ as explain, a as recordEnteredTier, b as ProductConfigError, c as nonEmpty, d as sessionLabel, g as errorMessage, h as bail, i as beginInvocation, j as describeEnvironment, k as renderCommandList, l as resolveDbConnection, n as bareGroupStartPath, o as recordResolved, r as runMenu, s as reproducibleCommand, t as bareGroupStartFlags, u as sessionIsLocal, v as explainError, w as activeCatalog, x as currentProduct, y as unwrap } from "./menu-oQE5IqAI.js";
5
+ import { a as isVersionRequest, c as cliName, m as positionals, o as ownPackageDir, p as flagValue, s as ownVersion } from "./failure-log-MSdCgvLc.js";
6
+ import { f as hasYes, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive } from "./telemetry-BDTAGquh.js";
7
+ import { t as runStackCommand } from "./stack-BRevXgI3.js";
6
8
  import { register } from "node:module";
7
9
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
10
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
9
11
  import { z } from "zod";
10
12
  import { confirm, intro, log, multiselect, note, outro, password, select, spinner, text } from "@clack/prompts";
11
13
  import { chmod, readFile, writeFile } from "node:fs/promises";
12
- import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
13
14
  import { pathToFileURL } from "node:url";
15
+ import { execFileSync, spawnSync } from "node:child_process";
14
16
  import { parse } from "dotenv";
15
17
  import postgres from "postgres";
16
18
  import { flattenDiagnosticMessageText, parseConfigFileTextToJson } from "typescript";
@@ -1417,14 +1419,11 @@ function checkEnvKeysPresent(env, keys) {
1417
1419
  }
1418
1420
  function has$1(cmd, args) {
1419
1421
  try {
1420
- return execFileSync(cmd, args, {
1421
- stdio: [
1422
- "ignore",
1423
- "pipe",
1424
- "ignore"
1425
- ],
1426
- encoding: "utf8"
1427
- }).trim();
1422
+ return execSync(cmd, args, { stdio: [
1423
+ "ignore",
1424
+ "pipe",
1425
+ "ignore"
1426
+ ] }).trim();
1428
1427
  } catch {
1429
1428
  return null;
1430
1429
  }
@@ -3325,7 +3324,7 @@ async function startTemporaryWrangler(app, requestedPort) {
3325
3324
  process.stderr.write(`devtools jobs: building ${app}'s workspace dependencies failed (exit ${buildCode}); not starting ${runtime}.\n`);
3326
3325
  return null;
3327
3326
  }
3328
- const child = spawn("pnpm", vinextDevArgs(app, port), {
3327
+ const child = spawnProcess("pnpm", vinextDevArgs(app, port), {
3329
3328
  cwd: appWorkspaceRoot(app),
3330
3329
  stdio: "inherit",
3331
3330
  detached: process.platform !== "win32",
@@ -4895,9 +4894,8 @@ function formatValue(value) {
4895
4894
  //#endregion
4896
4895
  //#region src/oauth/db.ts
4897
4896
  function runStatus(argv, cwd) {
4898
- return execFileSync(argv[0], argv.slice(1), {
4897
+ return execSync(argv[0], argv.slice(1), {
4899
4898
  cwd,
4900
- encoding: "utf-8",
4901
4899
  stdio: [
4902
4900
  "pipe",
4903
4901
  "pipe",
@@ -4922,7 +4920,7 @@ function productPin(cwd) {
4922
4920
  * a sibling project, where the Supabase CLI is normally installed globally;
4923
4921
  * this monorepo, where it is a workspace devDependency and not on PATH at all;
4924
4922
  * and a product repo (`devtools.config.json`), where `devtools setup` ran the
4925
- * pinned CLI through `npx` and a new member has none installed. A global install
4923
+ * pinned CLI through `pnpm dlx` and a new member has none installed. A global install
4926
4924
  * wins, since `pnpm exec` in a non-pnpm project would fail for reasons that have
4927
4925
  * nothing to do with Supabase.
4928
4926
  */
@@ -4944,19 +4942,32 @@ function detectLocalSupabase(cwd) {
4944
4942
  ...status
4945
4943
  ]];
4946
4944
  const pin = productPin(cwd);
4947
- if (pin !== null) attempts.push([
4948
- "npx",
4949
- "--yes",
4950
- `supabase@${pin}`,
4951
- ...status
4952
- ]);
4945
+ if (pin !== null) {
4946
+ attempts.push([
4947
+ "pnpm",
4948
+ "dlx",
4949
+ `supabase@${pin}`,
4950
+ ...status
4951
+ ]);
4952
+ attempts.push([
4953
+ "npx",
4954
+ "--yes",
4955
+ `supabase@${pin}`,
4956
+ ...status
4957
+ ]);
4958
+ }
4953
4959
  let output;
4954
4960
  let first;
4955
- for (const argv of attempts) try {
4956
- output = runStatus(argv, cwd);
4957
- break;
4958
- } catch (error) {
4959
- first ??= error;
4961
+ let pnpmMissing = false;
4962
+ for (const argv of attempts) {
4963
+ if (argv[0] === "npx" && !pnpmMissing) continue;
4964
+ try {
4965
+ output = runStatus(argv, cwd);
4966
+ break;
4967
+ } catch (error) {
4968
+ first ??= error;
4969
+ if (argv[0] === "pnpm" && error.code === "ENOENT") pnpmMissing = true;
4970
+ }
4960
4971
  }
4961
4972
  if (output === void 0) {
4962
4973
  if (first.code === "ENOENT") throw new Error("supabase CLI not found — install it from https://supabase.com/docs/guides/cli");
@@ -5097,14 +5108,22 @@ async function fetchIssuerFromAny(baseUrls) {
5097
5108
  //#region src/oauth/browser.ts
5098
5109
  /** Opens `url` in the user's default browser, cross-platform. */
5099
5110
  function openBrowser(url) {
5100
- if (process.platform === "win32") execFile("cmd", [
5101
- "/c",
5102
- "start",
5103
- "",
5104
- url
5105
- ]);
5106
- else if (process.platform === "darwin") execFile("open", [url]);
5107
- else execFile("xdg-open", [url]);
5111
+ const [command, args] = browserCommand(url, process.platform);
5112
+ spawnProcess(command, args, {
5113
+ stdio: "ignore",
5114
+ detached: true
5115
+ }).on("error", () => void 0).unref();
5116
+ }
5117
+ /**
5118
+ * The program that opens `url`. Windows uses `rundll32`, a real executable, not
5119
+ * `cmd /c start`: through `cmd.exe` an `&` in the URL ends the command and
5120
+ * starts another, which truncates every OAuth authorize URL at its first query
5121
+ * parameter.
5122
+ */
5123
+ function browserCommand(url, platform) {
5124
+ if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
5125
+ if (platform === "darwin") return ["open", [url]];
5126
+ return ["xdg-open", [url]];
5108
5127
  }
5109
5128
  //#endregion
5110
5129
  //#region src/oauth/pkce.ts
@@ -6756,14 +6775,11 @@ const handleScript = async (rest) => {
6756
6775
  */
6757
6776
  function has(cmd, args = ["--version"]) {
6758
6777
  try {
6759
- return execFileSync(cmd, args, {
6760
- stdio: [
6761
- "ignore",
6762
- "pipe",
6763
- "ignore"
6764
- ],
6765
- encoding: "utf8"
6766
- }).trim();
6778
+ return execSync(cmd, args, { stdio: [
6779
+ "ignore",
6780
+ "pipe",
6781
+ "ignore"
6782
+ ] }).trim();
6767
6783
  } catch {
6768
6784
  return null;
6769
6785
  }
@@ -6988,7 +7004,7 @@ const OAUTH = {
6988
7004
  //#endregion
6989
7005
  //#region src/product/exec.ts
6990
7006
  const spawnRunner = (command, args, options) => new Promise((resolve, reject) => {
6991
- const child = spawn(command, [...args], {
7007
+ const child = spawnProcess(command, args, {
6992
7008
  cwd: options.cwd,
6993
7009
  env: {
6994
7010
  ...process.env,
@@ -7031,7 +7047,9 @@ function commandLine(command, args) {
7031
7047
  *
7032
7048
  * 1. `node_modules/.bin/supabase` in the repo, when its version is the pin;
7033
7049
  * 2. a `supabase` on PATH whose version is the pin;
7034
- * 3. `npx --yes supabase@<pin>`, which downloads and caches the right one.
7050
+ * 3. `pnpm dlx supabase@<pin>`, which downloads and caches the right one.
7051
+ * Members are expected to have pnpm (both product repos pin it);
7052
+ * 4. `npx --yes supabase@<pin>`, only when pnpm is not on PATH.
7035
7053
  *
7036
7054
  * The pin is also part of the generated-types hash, so a contributor on a
7037
7055
  * different CLI would generate different types than CI; matching it is not
@@ -7060,6 +7078,13 @@ async function resolveSupabaseCli(root, pin, run, fileExists = existsSync) {
7060
7078
  const result = await run(candidate.command, ["--version"], { cwd: root });
7061
7079
  if (result.code === 0 && versionOf(result.stdout) === pin) return candidate;
7062
7080
  } catch {}
7081
+ try {
7082
+ if ((await run("pnpm", ["--version"], { cwd: root })).code === 0) return {
7083
+ command: "pnpm",
7084
+ prefix: ["dlx", `supabase@${pin}`],
7085
+ source: "pnpm"
7086
+ };
7087
+ } catch {}
7063
7088
  return {
7064
7089
  command: "npx",
7065
7090
  prefix: ["--yes", `supabase@${pin}`],
@@ -7209,6 +7234,15 @@ var DevDataError = class extends Error {
7209
7234
  this.name = "DevDataError";
7210
7235
  }
7211
7236
  };
7237
+ /**
7238
+ * Whether `err` is Postgres saying a table or schema does not exist
7239
+ * (SQLSTATE 42P01 undefined_table, 3F000 invalid_schema_name): the database
7240
+ * has not had the product's schema applied yet.
7241
+ */
7242
+ function isMissingSchemaError(err) {
7243
+ const code = err?.code;
7244
+ return code === "42P01" || code === "3F000";
7245
+ }
7212
7246
  function postgresSession(dbUrl) {
7213
7247
  const sql = postgres(dbUrl, {
7214
7248
  max: 1,
@@ -7262,22 +7296,28 @@ async function runDevData(product, target, open, options) {
7262
7296
  const text = readFileSync(file, "utf8");
7263
7297
  const session = open(target.dbUrl);
7264
7298
  try {
7265
- const member = options.member ? await session.memberByRef(options.member) : (await session.members(options.provider))[0] ?? null;
7266
- if (!member) throw new DevDataError(options.member ? `No user matches ${options.member} in this database.` : `Nobody has signed in with DevDogs on this database yet. Start the app, sign in, then run this again (or name someone with --member).`);
7267
- log(`Seeding as ${member.email ?? member.id} (${member.id})`);
7268
- if (options.dryRun) {
7269
- log(`Would run ${devData.file} (${text.split("\n").length} lines) in one transaction.`);
7270
- return null;
7271
- }
7272
- await session.runAs(member.id, text);
7273
- return {
7274
- member,
7275
- file: devData.file
7276
- };
7299
+ return await seed(session, devData.file, text, options, log);
7300
+ } catch (err) {
7301
+ if (isMissingSchemaError(err)) throw new DevDataError(`This database does not have ${product.config.name}'s tables yet (${err.message}). Run \`devtools setup\` first, or \`devtools db sync\` if you have already set up.`);
7302
+ throw err;
7277
7303
  } finally {
7278
7304
  await session.close();
7279
7305
  }
7280
7306
  }
7307
+ async function seed(session, file, text, options, log) {
7308
+ const member = options.member ? await session.memberByRef(options.member) : (await session.members(options.provider))[0] ?? null;
7309
+ if (!member) throw new DevDataError(options.member ? `No user matches ${options.member} in this database.` : `Nobody has signed in with DevDogs on this database yet. Start the app, sign in, then run this again (or name someone with --member).`);
7310
+ log(`Seeding as ${member.email ?? member.id} (${member.id})`);
7311
+ if (options.dryRun) {
7312
+ log(`Would run ${file} (${text.split("\n").length} lines) in one transaction.`);
7313
+ return null;
7314
+ }
7315
+ await session.runAs(member.id, text);
7316
+ return {
7317
+ member,
7318
+ file
7319
+ };
7320
+ }
7281
7321
  //#endregion
7282
7322
  //#region src/product/management.ts
7283
7323
  /**
@@ -8033,7 +8073,7 @@ async function runDoctor(product, deps) {
8033
8073
  const info = (message) => void lines.push(`INFO ${message}`);
8034
8074
  ok(`${product.config.name} (${product.config.product}), Supabase CLI pinned at ${product.config.supabase.version}`);
8035
8075
  const cli = await cliFor(product, deps);
8036
- if (cli.source === "npx") info(`The pinned Supabase CLI is fetched with npx on first use (${cliLine(cli, ["--version"])}).`);
8076
+ if (cli.source === "pnpm" || cli.source === "npx") info(`The pinned Supabase CLI is fetched with ${cli.command} on first use (${cliLine(cli, ["--version"])}).`);
8037
8077
  else ok(`Supabase CLI ${product.config.supabase.version} (${cli.source})`);
8038
8078
  const docker = await deps.run("docker", [
8039
8079
  "info",
@@ -8507,7 +8547,7 @@ async function main(typed) {
8507
8547
  await handleCompletions(argv.slice(1));
8508
8548
  return;
8509
8549
  }
8510
- if (argv[0] === "version") {
8550
+ if (isVersionRequest(argv)) {
8511
8551
  process.stdout.write(`${ownVersion()}\n`);
8512
8552
  return;
8513
8553
  }
@@ -8517,6 +8557,10 @@ async function main(typed) {
8517
8557
  return;
8518
8558
  }
8519
8559
  const startPath = process.stdin.isTTY ? bareGroupStartPath(catalog, argv) : null;
8560
+ if (argv.length === 0 && isNonInteractive()) {
8561
+ console.log(renderHelp(catalog));
8562
+ return;
8563
+ }
8520
8564
  const banners = !isNonInteractive();
8521
8565
  if (banners) intro("DevDogs devtools");
8522
8566
  let closing;
@@ -270,6 +270,10 @@ function ownVersion() {
270
270
  const pkg = JSON.parse(readFileSync(join(ownPackageDir(), "package.json"), "utf8"));
271
271
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
272
272
  }
273
+ /** `devtools version`, `devtools --version`, `devtools -V`: answered anywhere. */
274
+ function isVersionRequest(argv) {
275
+ return argv[0] === "version" || argv[0] === "--version" || argv[0] === "-V";
276
+ }
273
277
  //#endregion
274
278
  //#region ../cli-core/src/failure-log.ts
275
279
  /**
@@ -425,78 +429,4 @@ function installFailureLog(options) {
425
429
  });
426
430
  }
427
431
  //#endregion
428
- //#region ../cli-core/src/repo/root.ts
429
- /**
430
- * Where devtools finds the DevDogsUGA checkout it is running against.
431
- *
432
- * devtools used to live inside `packages/devtools` of that repo, so its own
433
- * `import.meta.url` WAS a path into the repo (`PROJECT_ROOT` /
434
- * `REPO_ROOT` walked up a fixed number of `..` segments from this file).
435
- * Published as its own package and run through `pnpm dlx`, that assumption
436
- * breaks: this file's `import.meta.url` points into a dlx cache directory
437
- * or an isolated `node_modules` tree that has nothing to do with the repo
438
- * the contributor is standing in.
439
- *
440
- * So repo discovery walks up from `process.cwd()` instead, looking for the
441
- * marker validated in the `devtools-dlx` prototype
442
- * (`/home/sloan/scratchpad/devdogs/prototypes/devtools-dlx/FINDINGS.md`,
443
- * experiment 1): a directory containing `pnpm-workspace.yaml` AND whose
444
- * `package.json` has `"name": "devdogs-monorepo"` (DevDogsUGA) or
445
- * `"backstage"` (Backstage, which the CLIs also run from; `layout.ts` says
446
- * what differs). The name check is not redundant — a bare
447
- * `pnpm-workspace.yaml` matches any pnpm workspace, which devtools should
448
- * refuse rather than silently "find".
449
- */
450
- /**
451
- * The `package.json` names a repo root may carry: DevDogsUGA's, and Backstage's
452
- * (the CLIs run from both; see `layout.ts`).
453
- */
454
- const REPO_MARKER_NAMES = ["devdogs-monorepo", "backstage"];
455
- var RepoNotFoundError = class extends Error {
456
- constructor() {
457
- super("run this from inside a DevDogsUGA clone");
458
- this.name = "RepoNotFoundError";
459
- }
460
- };
461
- function looksLikeRepoRoot(dir) {
462
- if (!existsSync(join(dir, "pnpm-workspace.yaml"))) return false;
463
- try {
464
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
465
- return REPO_MARKER_NAMES.includes(pkg.name);
466
- } catch {
467
- return false;
468
- }
469
- }
470
- /**
471
- * Walks up from `startDir` (default `process.cwd()`) looking for the repo
472
- * marker. Returns `null` rather than throwing — `findRepoRoot()` below is
473
- * the throwing convenience most callers want; this is exported for callers
474
- * (like `setup`) that need to know without failing.
475
- */
476
- function discoverRepoRoot(startDir = process.cwd()) {
477
- let dir = startDir;
478
- for (;;) {
479
- if (looksLikeRepoRoot(dir)) return dir;
480
- const parent = dirname(dir);
481
- if (parent === dir) return null;
482
- dir = parent;
483
- }
484
- }
485
- let cachedRoot;
486
- /**
487
- * The memoized, throwing repo-root lookup. Lazy on purpose: importing this
488
- * module must never fail just because nothing has called it yet (`setup`
489
- * and `completions` in `launch.ts` never need a repo at all, and must stay
490
- * that way — see that file's header comment on why).
491
- *
492
- * Resolved once per process: `process.cwd()` does not change mid-invocation
493
- * for this CLI, and re-walking the filesystem on every call would be pure
494
- * waste.
495
- */
496
- function findRepoRoot() {
497
- if (cachedRoot === void 0) cachedRoot = process.env.DEVTOOLS_TEST_REPO_ROOT ?? discoverRepoRoot();
498
- if (cachedRoot === null) throw new RepoNotFoundError();
499
- return cachedRoot;
500
- }
501
- //#endregion
502
- export { noteError as a, ownPackageDir as c, JSON_FLAG as d, SCOPES as f, positionals as g, flagValue as h, installFailureLog as i, ownVersion as l, createCatalog as m, discoverRepoRoot as n, noteRan as o, YES as p, findRepoRoot as r, suppressFailureLog as s, RepoNotFoundError as t, cliName as u };
432
+ export { isVersionRequest as a, cliName as c, YES as d, createCatalog as f, suppressFailureLog as i, JSON_FLAG as l, positionals as m, noteError as n, ownPackageDir as o, flagValue as p, noteRan as r, ownVersion as s, installFailureLog as t, SCOPES as u };
package/dist/launch.js CHANGED
@@ -1,7 +1,8 @@
1
- import { i as installFailureLog, n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError, u as cliName } from "./root-CsocfxnK.js";
2
- import { A as helpPath, O as setProductMode, _ as bail, g as setMenuEnvHook, h as realEnvEntryDeps, k as catalog, m as enterSessionEnvironment, n as bareGroupStartPath, u as nonEmpty, v as errorMessage, w as findProductRoot, x as unwrap } from "./menu-C30rQm5v.js";
3
- import { D as formatCommand, M as resolveDryRun, N as setDryRun, _ as loadEnvLoad, j as isDryRun, v as loadEnvSession } from "./supabase-project-BoJKMCxL.js";
4
- import { d as hasYes, f as isNonInteractive, m as stripTierFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as stripNoEnvFlag, s as lastSentryEventId } from "./telemetry-BKFDtzXI.js";
1
+ import { n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError } from "./root-PA8K5G2d.js";
2
+ import { b as setDryRun, d as loadEnvLoad, f as loadEnvSession, m as formatCommand, v as isDryRun, y as resolveDryRun } from "./run-CRBte7p0.js";
3
+ import { D as catalog, E as setProductMode, N as setEnvironmentRoot, O as helpPath, S as findProductRoot, c as nonEmpty, f as enterSessionEnvironment, g as errorMessage, h as bail, m as setMenuEnvHook, n as bareGroupStartPath, p as realEnvEntryDeps, y as unwrap } from "./menu-oQE5IqAI.js";
4
+ import { a as isVersionRequest, c as cliName, t as installFailureLog } from "./failure-log-MSdCgvLc.js";
5
+ import { c as lastSentryEventId, f as hasYes, h as stripTierFlag, m as stripNoEnvFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive } from "./telemetry-BDTAGquh.js";
5
6
  import { confirm, select } from "@clack/prompts";
6
7
  //#region ../cli-core/src/session.ts
7
8
  /**
@@ -158,10 +159,20 @@ function isEnvFreeCommand(rest) {
158
159
  function skipsEnvEntry(rest) {
159
160
  return declaredCommand(rest)?.noEnv === true;
160
161
  }
162
+ /**
163
+ * The one clear sentence for a command that needs a checkout when run from
164
+ * somewhere that is neither a DevDogsUGA clone nor a product repository.
165
+ * Printed and exited on directly, so it is never a stack trace or a Sentry
166
+ * event: the person only needs to change directory.
167
+ */
168
+ function outsideRepoMessage(rest) {
169
+ const path = helpPath(rest);
170
+ return `${path.length > 0 ? `\`devtools ${path.join(" ")}\`` : "`devtools`"} needs a DevDogsUGA clone or a product repository (one with a devtools.config.json, like DogDays or DogPack), and this directory is neither. \`devtools --help\` and \`devtools --version\` work anywhere.`;
171
+ }
161
172
  /** The offer's stack start (`env-entry.ts` holds the TTY prompt, this holds
162
173
  * the lifecycle command), imported lazily like the rest of the commands. */
163
174
  async function startStack() {
164
- const { runStackCommand } = await import("./stack-7qA2UP9g.js");
175
+ const { runStackCommand } = await import("./stack-Cl_B_dt7.js");
165
176
  return runStackCommand("start");
166
177
  }
167
178
  /**
@@ -172,11 +183,11 @@ async function startStack() {
172
183
  * post-tier-resolution path.
173
184
  */
174
185
  async function dispatch(argv) {
175
- const { main } = await import("./cli-BoTmTNqH.js");
186
+ const { main } = await import("./cli-DNZ-6WEI.js");
176
187
  try {
177
188
  await main(argv);
178
189
  } catch (err) {
179
- process.stderr.write(`devtools: ${errorMessage(err)}\n`);
190
+ process.stderr.write(`devtools: ${err instanceof RepoNotFoundError ? outsideRepoMessage(argv) : errorMessage(err)}\n`);
180
191
  await captureDevtoolsError(err);
181
192
  process.exit(1);
182
193
  }
@@ -242,8 +253,10 @@ async function launch(argv) {
242
253
  });
243
254
  ignoreClosedPipes();
244
255
  initDevtoolsTelemetry(rest[0] ?? "menu");
245
- if (findProductRoot() !== null) {
256
+ const productRoot = findProductRoot();
257
+ if (productRoot !== null) {
246
258
  setProductMode(true);
259
+ setEnvironmentRoot(productRoot);
247
260
  process.env.DEPLOY_ENV = "development";
248
261
  await dispatch(rest);
249
262
  return;
@@ -253,10 +266,14 @@ async function launch(argv) {
253
266
  await dispatch(rest);
254
267
  return;
255
268
  }
256
- if (rest[0] === "version") {
269
+ if (isVersionRequest(rest)) {
257
270
  await dispatch(rest);
258
271
  return;
259
272
  }
273
+ if (discoverRepoRoot() === null && rest[0] !== "setup" && rest[0] !== "completions" && !skipsEnvEntry(rest) && !isEnvFreeCommand(rest)) {
274
+ process.stderr.write(`devtools: ${outsideRepoMessage(rest)}\n`);
275
+ process.exit(1);
276
+ }
260
277
  const envLoad = await loadEnvLoad();
261
278
  const envSession = await loadEnvSession();
262
279
  process.env[envLoad.SHELL_KEYS_ENV] ??= Object.keys(process.env).join(",");
@@ -303,7 +320,7 @@ async function launch(argv) {
303
320
  return;
304
321
  }
305
322
  const run = () => gated(tier, rest, () => dispatch(rest), void 0);
306
- if (noEnv || skipsEnvEntry(rest)) {
323
+ if (noEnv || skipsEnvEntry(rest) || isEnvFreeCommand(rest) && discoverRepoRoot() === null) {
307
324
  process.env.DEPLOY_ENV = tier;
308
325
  if (devDatabase !== void 0) process.env.DEV_DB = devDatabase;
309
326
  await run();
@@ -313,4 +330,4 @@ async function launch(argv) {
313
330
  }
314
331
  if (import.meta.url === `file://${process.argv[1]}`) await launch(process.argv.slice(2));
315
332
  //#endregion
316
- export { launch, stripTierFlag };
333
+ export { launch, outsideRepoMessage, stripTierFlag };
@@ -520,7 +520,10 @@ async function probeLocalStack(port = LOCAL_STACK_PORT, timeoutMs = 250) {
520
520
  * exiting the whole process out from under an unrelated caller would be a far
521
521
  * worse failure than a rejected promise.
522
522
  */
523
- async function findRepoRoot() {
523
+ async function locateRepoRoot() {
524
+ const { discoverRepoRoot, RepoNotFoundError } = await import("./root-DfH38DFq.js");
525
+ const fromCwd = discoverRepoRoot();
526
+ if (fromCwd !== null) return fromCwd;
524
527
  const { existsSync } = await import("node:fs");
525
528
  const { dirname, join, resolve } = await import("node:path");
526
529
  const { fileURLToPath } = await import("node:url");
@@ -528,7 +531,7 @@ async function findRepoRoot() {
528
531
  for (;;) {
529
532
  if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
530
533
  const parent = dirname(dir);
531
- if (parent === dir) throw new Error("loadEnvironment: could not locate the monorepo root (no pnpm-workspace.yaml found above this package).");
534
+ if (parent === dir) throw new RepoNotFoundError();
532
535
  dir = parent;
533
536
  }
534
537
  }
@@ -564,7 +567,7 @@ async function findRepoRoot() {
564
567
  */
565
568
  async function loadEnvironment(deployEnv, options, context) {
566
569
  const override = options?.override ?? false;
567
- const root = context?.root ?? await findRepoRoot();
570
+ const root = context?.root ?? await locateRepoRoot();
568
571
  let exists = context?.exists;
569
572
  if (!exists) {
570
573
  const { existsSync } = await import("node:fs");
@@ -637,4 +640,4 @@ async function loadEnvironment(deployEnv, options, context) {
637
640
  };
638
641
  }
639
642
  //#endregion
640
- export { fileFor as _, LocalStackOfflineError as a, applyDeployTierAliases as c, loadEnvironment as d, parseRawAssignments as f, UnknownEnvironmentError as g, DEPLOY_ENVIRONMENTS as h, LOCAL_STACK_PORT as i, applyWranglerLocalDatabaseAlias as l, selectEnvFiles as m, GENERATED_FILE as n, MissingEnvFileError as o, probeLocalStack as p, HYPERDRIVE_LOCAL_CONNECTION_ENV as r, SHELL_KEYS_ENV as s, DEV_DB_ENV as t, isDevDatabase as u, isDeployEnvironment as v };
643
+ export { UnknownEnvironmentError as _, LocalStackOfflineError as a, applyDeployTierAliases as c, loadEnvironment as d, locateRepoRoot as f, DEPLOY_ENVIRONMENTS as g, selectEnvFiles as h, LOCAL_STACK_PORT as i, applyWranglerLocalDatabaseAlias as l, probeLocalStack as m, GENERATED_FILE as n, MissingEnvFileError as o, parseRawAssignments as p, HYPERDRIVE_LOCAL_CONNECTION_ENV as r, SHELL_KEYS_ENV as s, DEV_DB_ENV as t, isDevDatabase as u, fileFor as v, isDeployEnvironment as y };
@@ -0,0 +1,2 @@
1
+ import { a as LocalStackOfflineError, c as applyDeployTierAliases, d as loadEnvironment, f as locateRepoRoot, h as selectEnvFiles, i as LOCAL_STACK_PORT, l as applyWranglerLocalDatabaseAlias, m as probeLocalStack, n as GENERATED_FILE, o as MissingEnvFileError, p as parseRawAssignments, r as HYPERDRIVE_LOCAL_CONNECTION_ENV, s as SHELL_KEYS_ENV, t as DEV_DB_ENV, u as isDevDatabase } from "./load-Z2CXCpyx.js";
2
+ export { DEV_DB_ENV, GENERATED_FILE, HYPERDRIVE_LOCAL_CONNECTION_ENV, LOCAL_STACK_PORT, LocalStackOfflineError, MissingEnvFileError, SHELL_KEYS_ENV, applyDeployTierAliases, applyWranglerLocalDatabaseAlias, isDevDatabase, loadEnvironment, locateRepoRoot, parseRawAssignments, probeLocalStack, selectEnvFiles };