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

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,10 +1,10 @@
1
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";
2
+ import { n as discoverRepoRoot, r as findRepoRoot } from "./root-RaJ8ECCO.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-D5iI9zve.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-CZK0L1Vf.js";
5
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
+ import { f as hasYes, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive } from "./telemetry-BDOwjuoC.js";
7
+ import { t as runStackCommand } from "./stack-CzNe6rxl.js";
8
8
  import { register } from "node:module";
9
9
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
10
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -4920,7 +4920,7 @@ function productPin(cwd) {
4920
4920
  * a sibling project, where the Supabase CLI is normally installed globally;
4921
4921
  * this monorepo, where it is a workspace devDependency and not on PATH at all;
4922
4922
  * and a product repo (`devtools.config.json`), where `devtools setup` ran the
4923
- * 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
4924
4924
  * wins, since `pnpm exec` in a non-pnpm project would fail for reasons that have
4925
4925
  * nothing to do with Supabase.
4926
4926
  */
@@ -4942,19 +4942,32 @@ function detectLocalSupabase(cwd) {
4942
4942
  ...status
4943
4943
  ]];
4944
4944
  const pin = productPin(cwd);
4945
- if (pin !== null) attempts.push([
4946
- "npx",
4947
- "--yes",
4948
- `supabase@${pin}`,
4949
- ...status
4950
- ]);
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
+ }
4951
4959
  let output;
4952
4960
  let first;
4953
- for (const argv of attempts) try {
4954
- output = runStatus(argv, cwd);
4955
- break;
4956
- } catch (error) {
4957
- 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
+ }
4958
4971
  }
4959
4972
  if (output === void 0) {
4960
4973
  if (first.code === "ENOENT") throw new Error("supabase CLI not found — install it from https://supabase.com/docs/guides/cli");
@@ -7034,7 +7047,9 @@ function commandLine(command, args) {
7034
7047
  *
7035
7048
  * 1. `node_modules/.bin/supabase` in the repo, when its version is the pin;
7036
7049
  * 2. a `supabase` on PATH whose version is the pin;
7037
- * 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.
7038
7053
  *
7039
7054
  * The pin is also part of the generated-types hash, so a contributor on a
7040
7055
  * different CLI would generate different types than CI; matching it is not
@@ -7043,7 +7058,7 @@ function commandLine(command, args) {
7043
7058
  function cliLine(cli, args) {
7044
7059
  return commandLine(cli.command, [...cli.prefix, ...args]);
7045
7060
  }
7046
- function versionOf(output) {
7061
+ function versionOf$1(output) {
7047
7062
  return /\b(\d+\.\d+\.\d+)\b/.exec(output)?.[1] ?? null;
7048
7063
  }
7049
7064
  async function resolveSupabaseCli(root, pin, run, fileExists = existsSync) {
@@ -7061,7 +7076,14 @@ async function resolveSupabaseCli(root, pin, run, fileExists = existsSync) {
7061
7076
  });
7062
7077
  for (const candidate of candidates) try {
7063
7078
  const result = await run(candidate.command, ["--version"], { cwd: root });
7064
- if (result.code === 0 && versionOf(result.stdout) === pin) return candidate;
7079
+ if (result.code === 0 && versionOf$1(result.stdout) === pin) return candidate;
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
+ };
7065
7087
  } catch {}
7066
7088
  return {
7067
7089
  command: "npx",
@@ -7207,19 +7229,27 @@ async function resolveTarget(product, cli, run, preference) {
7207
7229
  * project, and the SQL runs in one transaction.
7208
7230
  */
7209
7231
  var DevDataError = class extends Error {
7232
+ /** A failure the contributor or the product's SQL caused, not a devtools bug: never sent to Sentry. */
7233
+ expected = true;
7210
7234
  constructor(message) {
7211
7235
  super(message);
7212
7236
  this.name = "DevDataError";
7213
7237
  }
7214
7238
  };
7239
+ /** A Postgres SQLSTATE: five characters, digits and capitals. Connection errors (`ECONNREFUSED`) are not. */
7240
+ function isSqlState(code) {
7241
+ return typeof code === "string" && /^[0-9A-Z]{5}$/.test(code);
7242
+ }
7215
7243
  /**
7216
- * Whether `err` is Postgres saying a table or schema does not exist
7217
- * (SQLSTATE 42P01 undefined_table, 3F000 invalid_schema_name): the database
7218
- * has not had the product's schema applied yet.
7244
+ * The line of `text` a Postgres error points at, from the error's 1-based
7245
+ * character `position`, or null when it carries none.
7219
7246
  */
7220
- function isMissingSchemaError(err) {
7221
- const code = err?.code;
7222
- return code === "42P01" || code === "3F000";
7247
+ function errorLine(err, text) {
7248
+ const raw = err?.position;
7249
+ const position = typeof raw === "string" ? Number(raw) : raw;
7250
+ if (typeof position !== "number" || !Number.isInteger(position)) return null;
7251
+ if (position < 1 || position > text.length + 1) return null;
7252
+ return text.slice(0, position - 1).split("\n").length;
7223
7253
  }
7224
7254
  function postgresSession(dbUrl) {
7225
7255
  const sql = postgres(dbUrl, {
@@ -7251,6 +7281,19 @@ function postgresSession(dbUrl) {
7251
7281
  lastSignInAt: row.last_sign_in_at?.toISOString() ?? null
7252
7282
  } : null;
7253
7283
  },
7284
+ async appliedMigrations() {
7285
+ const [table] = await sql`
7286
+ select to_regclass('supabase_migrations.schema_migrations')::text as present`;
7287
+ if (!table?.present) return null;
7288
+ return (await sql`
7289
+ select version from supabase_migrations.schema_migrations`).map((r) => r.version);
7290
+ },
7291
+ async missingSchemas(schemas) {
7292
+ const rows = await sql`
7293
+ select nspname from pg_namespace where nspname = any(${[...schemas]})`;
7294
+ const present = new Set(rows.map((r) => r.nspname));
7295
+ return schemas.filter((name) => !present.has(name));
7296
+ },
7254
7297
  async runAs(memberId, text) {
7255
7298
  await sql.begin(async (tx) => {
7256
7299
  await tx`select set_config('devtools.member_id', ${memberId}, true)`;
@@ -7274,10 +7317,9 @@ async function runDevData(product, target, open, options) {
7274
7317
  const text = readFileSync(file, "utf8");
7275
7318
  const session = open(target.dbUrl);
7276
7319
  try {
7320
+ const missing = await session.missingSchemas(product.config.supabase.schemas);
7321
+ if (missing.length > 0) throw new DevDataError(`This database does not have ${product.config.name}'s ${missing.length === 1 ? "schema" : "schemas"} yet (missing: ${missing.join(", ")}). Run \`devtools setup\` first, or \`devtools db sync\` if you have already set up.`);
7277
7322
  return await seed(session, devData.file, text, options, log);
7278
- } catch (err) {
7279
- 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.`);
7280
- throw err;
7281
7323
  } finally {
7282
7324
  await session.close();
7283
7325
  }
@@ -7290,7 +7332,13 @@ async function seed(session, file, text, options, log) {
7290
7332
  log(`Would run ${file} (${text.split("\n").length} lines) in one transaction.`);
7291
7333
  return null;
7292
7334
  }
7293
- await session.runAs(member.id, text);
7335
+ try {
7336
+ await session.runAs(member.id, text);
7337
+ } catch (err) {
7338
+ if (!isSqlState(err?.code)) throw err;
7339
+ const line = errorLine(err, text);
7340
+ throw new DevDataError(`${file}${line === null ? "" : `:${line}`}: ${err.message}. This is a problem in the product's dev-data SQL, not in devtools; nothing was seeded (the file runs in one transaction).`);
7341
+ }
7294
7342
  return {
7295
7343
  member,
7296
7344
  file
@@ -7598,6 +7646,7 @@ async function dbSync(product, cli, target, run, options) {
7598
7646
  const restored = migrationFiles(migrationsDir).length;
7599
7647
  if (fetched.code === 0 && restored > 0) log(`Restored ${restored} applied migration${restored === 1 ? "" : "s"} from the project's history.`);
7600
7648
  }
7649
+ await reconcileRecord(product, cli, target, run, options, log);
7601
7650
  const before = new Set(migrationFiles(migrationsDir));
7602
7651
  let generated = await run(cli.command, generateArgs, { cwd: product.root });
7603
7652
  if (generated.code !== 0 && isContainerFailure(generated)) {
@@ -7648,6 +7697,70 @@ async function dbSync(product, cli, target, run, options) {
7648
7697
  destructive
7649
7698
  };
7650
7699
  }
7700
+ /** `20261011120000` from `20261011120000_devtools_sync.sql`. */
7701
+ function versionOf(file) {
7702
+ return file.split("_")[0] ?? file;
7703
+ }
7704
+ /**
7705
+ * Makes the local record agree with the database. The declarative diff only
7706
+ * compares `supabase/schemas` with `supabase/migrations`, so a database that
7707
+ * lost its contents behind the record's back (a hosted project reset from the
7708
+ * dashboard, a checkout switched from the local stack to a hosted project)
7709
+ * would read as "already matches". This asks the target:
7710
+ *
7711
+ * - a migration in the record that is not in the database's ledger was never
7712
+ * applied there: it is dropped from the record, so the diff re-creates it;
7713
+ * - a configured schema that does not exist means the ledger is stale too:
7714
+ * its entries are reverted and the whole record dropped, so the diff
7715
+ * writes the full schema again.
7716
+ *
7717
+ * Nothing is reset or dropped in the database; only ledger rows of schemas
7718
+ * that are already gone are removed. A database that cannot be reached is
7719
+ * left to the apply step to report.
7720
+ */
7721
+ async function reconcileRecord(product, cli, target, run, options, log) {
7722
+ if (!options.open) return;
7723
+ const { migrationsDir } = product.paths;
7724
+ const files = migrationFiles(migrationsDir);
7725
+ let ledger;
7726
+ let missing;
7727
+ let session;
7728
+ try {
7729
+ session = options.open(target.dbUrl);
7730
+ ledger = await session.appliedMigrations();
7731
+ missing = await session.missingSchemas(product.config.supabase.schemas);
7732
+ } catch (err) {
7733
+ log(`Could not check what the database has applied (${err.message}); trusting the local record.`);
7734
+ return;
7735
+ } finally {
7736
+ await session?.close().catch(() => void 0);
7737
+ }
7738
+ if (missing.length > 0) {
7739
+ const stale = ledger ?? [];
7740
+ log(`The database is missing ${missing.length === 1 ? "schema" : "schemas"} ${missing.join(", ")}, so the record of applied migrations is out of date; rebuilding it from supabase/schemas.`);
7741
+ if (stale.length > 0) {
7742
+ const reverted = await run(cli.command, [
7743
+ ...cli.prefix,
7744
+ "migration",
7745
+ "repair",
7746
+ "--status",
7747
+ "reverted",
7748
+ "--db-url",
7749
+ target.dbUrl,
7750
+ ...stale
7751
+ ], { cwd: product.root });
7752
+ if (reverted.code !== 0) throw new SyncError("The database lost its schema but its migration history still lists migrations as applied, and clearing that history failed.", reverted.stderr || reverted.stdout);
7753
+ }
7754
+ for (const file of files) rmSync(join(migrationsDir, file));
7755
+ return;
7756
+ }
7757
+ const applied = new Set(ledger ?? []);
7758
+ const unapplied = files.filter((file) => !applied.has(versionOf(file)));
7759
+ if (unapplied.length > 0) {
7760
+ log(`${unapplied.length} migration${unapplied.length === 1 ? "" : "s"} in the local record ${unapplied.length === 1 ? "is" : "are"} not in this database's history (it was reset, or this checkout switched databases); re-applying ${unapplied.length === 1 ? "it" : "them"}.`);
7761
+ for (const file of unapplied) rmSync(join(migrationsDir, file));
7762
+ }
7763
+ }
7651
7764
  function isContainerFailure(result) {
7652
7765
  return /docker|container|image/i.test(result.stderr + result.stdout);
7653
7766
  }
@@ -7910,7 +8023,8 @@ async function runSync(product, deps, rest) {
7910
8023
  yes: hasYes(rest),
7911
8024
  confirmDestructive: deps.interactive() ? confirmDestructive : void 0,
7912
8025
  dryRun,
7913
- log: (line) => log.info(line)
8026
+ log: (line) => log.info(line),
8027
+ open: deps.openSql
7914
8028
  });
7915
8029
  if (rest.includes("--rebuild") && !dryRun) try {
7916
8030
  const row = await reRegisterSavedProvider(product.paths.toolingFile, target);
@@ -8051,7 +8165,7 @@ async function runDoctor(product, deps) {
8051
8165
  const info = (message) => void lines.push(`INFO ${message}`);
8052
8166
  ok(`${product.config.name} (${product.config.product}), Supabase CLI pinned at ${product.config.supabase.version}`);
8053
8167
  const cli = await cliFor(product, deps);
8054
- if (cli.source === "npx") info(`The pinned Supabase CLI is fetched with npx on first use (${cliLine(cli, ["--version"])}).`);
8168
+ if (cli.source === "pnpm" || cli.source === "npx") info(`The pinned Supabase CLI is fetched with ${cli.command} on first use (${cliLine(cli, ["--version"])}).`);
8055
8169
  else ok(`Supabase CLI ${product.config.supabase.version} (${cli.source})`);
8056
8170
  const docker = await deps.run("docker", [
8057
8171
  "info",
@@ -8211,7 +8325,8 @@ async function runSetup(product, deps, rest) {
8211
8325
  const result = await dbSync(product, cli, target, deps.run, {
8212
8326
  yes: hasYes(rest),
8213
8327
  confirmDestructive: deps.interactive() ? confirmDestructive : void 0,
8214
- log: (line) => log.info(line)
8328
+ log: (line) => log.info(line),
8329
+ open: deps.openSql
8215
8330
  });
8216
8331
  log.success(result.migration ? "Applied supabase/schemas to your database." : "Your database already matched supabase/schemas.");
8217
8332
  if (target.kind === "local") await waitForRest(target, deps, product);
package/dist/launch.js CHANGED
@@ -1,8 +1,8 @@
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";
1
+ import { i as markOutsideCheckout, n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError } from "./root-RaJ8ECCO.js";
2
+ import { b as setDryRun, d as loadEnvLoad, f as loadEnvSession, m as formatCommand, v as isDryRun, y as resolveDryRun } from "./run-D5iI9zve.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-CZK0L1Vf.js";
4
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
+ import { c as lastSentryEventId, f as hasYes, h as stripTierFlag, m as stripNoEnvFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive } from "./telemetry-BDOwjuoC.js";
6
6
  import { confirm, select } from "@clack/prompts";
7
7
  //#region ../cli-core/src/session.ts
8
8
  /**
@@ -172,7 +172,7 @@ function outsideRepoMessage(rest) {
172
172
  /** The offer's stack start (`env-entry.ts` holds the TTY prompt, this holds
173
173
  * the lifecycle command), imported lazily like the rest of the commands. */
174
174
  async function startStack() {
175
- const { runStackCommand } = await import("./stack-Cl_B_dt7.js");
175
+ const { runStackCommand } = await import("./stack-VZ-1SmuM.js");
176
176
  return runStackCommand("start");
177
177
  }
178
178
  /**
@@ -183,11 +183,12 @@ async function startStack() {
183
183
  * post-tier-resolution path.
184
184
  */
185
185
  async function dispatch(argv) {
186
- const { main } = await import("./cli-D98LfCHU.js");
186
+ const { main } = await import("./cli-CJtevyVy.js");
187
187
  try {
188
188
  await main(argv);
189
189
  } catch (err) {
190
- process.stderr.write(`devtools: ${err instanceof RepoNotFoundError ? outsideRepoMessage(argv) : errorMessage(err)}\n`);
190
+ const outside = markOutsideCheckout(err);
191
+ process.stderr.write(`devtools: ${outside ? outsideRepoMessage(argv) : errorMessage(err)}\n`);
191
192
  await captureDevtoolsError(err);
192
193
  process.exit(1);
193
194
  }
@@ -1,2 +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";
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-lrgJ5Kz7.js";
2
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 };
@@ -521,7 +521,7 @@ async function probeLocalStack(port = LOCAL_STACK_PORT, timeoutMs = 250) {
521
521
  * worse failure than a rejected promise.
522
522
  */
523
523
  async function locateRepoRoot() {
524
- const { discoverRepoRoot, RepoNotFoundError } = await import("./root-DfH38DFq.js");
524
+ const { discoverRepoRoot, RepoNotFoundError } = await import("./root-IVsv7i8N.js");
525
525
  const fromCwd = discoverRepoRoot();
526
526
  if (fromCwd !== null) return fromCwd;
527
527
  const { existsSync } = await import("node:fs");
@@ -1,8 +1,8 @@
1
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";
2
+ import { n as discoverRepoRoot } from "./root-RaJ8ECCO.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-D5iI9zve.js";
4
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";
5
+ import { l as reportDevtoolsError, p as isNonInteractive } from "./telemetry-BDOwjuoC.js";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { z } from "zod";
@@ -1,2 +1,2 @@
1
- import { n as discoverRepoRoot, t as RepoNotFoundError } from "./root-PA8K5G2d.js";
1
+ import { n as discoverRepoRoot, t as RepoNotFoundError } from "./root-RaJ8ECCO.js";
2
2
  export { RepoNotFoundError, discoverRepoRoot };
@@ -73,5 +73,17 @@ function findRepoRoot() {
73
73
  if (cachedRoot === null) throw new RepoNotFoundError();
74
74
  return cachedRoot;
75
75
  }
76
+ /**
77
+ * Whether `err` is the user running a checkout-requiring command with no
78
+ * DevDogsUGA checkout above the working directory, and if so marks it
79
+ * `expected` so telemetry drops it. A `RepoNotFoundError` raised while a
80
+ * checkout does exist (a `findRepoRoot()` call that should not have needed
81
+ * one, as in DEVTOOLS-D) is a bug and stays reportable.
82
+ */
83
+ function markOutsideCheckout(err, startDir = process.cwd()) {
84
+ if (!(err instanceof RepoNotFoundError) || discoverRepoRoot(startDir) !== null) return false;
85
+ Object.assign(err, { expected: true });
86
+ return true;
87
+ }
76
88
  //#endregion
77
- export { discoverRepoRoot as n, findRepoRoot as r, RepoNotFoundError as t };
89
+ export { markOutsideCheckout as i, discoverRepoRoot as n, findRepoRoot as r, RepoNotFoundError as t };
@@ -1,5 +1,5 @@
1
1
  import { n as execSync, r as spawnProcess, t as exec } from "./spawn-BvLQzc_K.js";
2
- import { r as findRepoRoot, t as RepoNotFoundError } from "./root-PA8K5G2d.js";
2
+ import { r as findRepoRoot, t as RepoNotFoundError } from "./root-RaJ8ECCO.js";
3
3
  import { c as cliName, r as noteRan } from "./failure-log-MSdCgvLc.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
@@ -631,11 +631,11 @@ function getEnvSync() {
631
631
  }
632
632
  /** The env loader (`loadEnvironment` and the tier/file selection). Bundled. */
633
633
  function loadEnvLoad() {
634
- return import("./load-yn7JCcS2.js");
634
+ return import("./load-aeN4iX1h.js");
635
635
  }
636
636
  /** The session policy (tier resolution, `enterEnvironment`). Bundled. */
637
637
  function loadEnvSession() {
638
- return import("./session-DzgtIg6K.js");
638
+ return import("./session-DdF8eqv3.js");
639
639
  }
640
640
  //#endregion
641
641
  //#region ../cli-core/src/db/run.ts
@@ -1,2 +1,2 @@
1
- import { a as parseSessionSelector, c as rememberDevDatabase, i as enterEnvironment, l as rememberedDevDatabase, n as availableTiers, o as resolveSessionTier, r as developmentRemoteCandidate, s as askSession, t as SESSION_SELECTORS } from "./session-CuexI1f9.js";
1
+ import { a as parseSessionSelector, c as rememberDevDatabase, i as enterEnvironment, l as rememberedDevDatabase, n as availableTiers, o as resolveSessionTier, r as developmentRemoteCandidate, s as askSession, t as SESSION_SELECTORS } from "./session-bPPOkolU.js";
2
2
  export { SESSION_SELECTORS, askSession, availableTiers, developmentRemoteCandidate, enterEnvironment, parseSessionSelector, rememberDevDatabase, rememberedDevDatabase, resolveSessionTier };
@@ -1,4 +1,4 @@
1
- import { d as loadEnvironment, g as DEPLOY_ENVIRONMENTS, m as probeLocalStack, p as parseRawAssignments, t as DEV_DB_ENV, u as isDevDatabase, v as fileFor, y as isDeployEnvironment } from "./load-Z2CXCpyx.js";
1
+ import { d as loadEnvironment, g as DEPLOY_ENVIRONMENTS, m as probeLocalStack, p as parseRawAssignments, t as DEV_DB_ENV, u as isDevDatabase, v as fileFor, y as isDeployEnvironment } from "./load-lrgJ5Kz7.js";
2
2
  //#region ../cli-core/src/env-runtime/ask.ts
3
3
  /**
4
4
  * The interactive half of session resolution, shared by `with-env` and the
@@ -1,4 +1,4 @@
1
- import { D as readProjectId, E as listContainerNames, P as resolveLayout, S as STACK_API_PORT, T as foreignStackProjectId, a as seedBuckets, d as loadEnvLoad, f as loadEnvSession, o as supabase, s as supabaseCapture, v as isDryRun, w as foreignStackMessage } from "./run-CRBte7p0.js";
1
+ import { D as readProjectId, E as listContainerNames, P as resolveLayout, S as STACK_API_PORT, T as foreignStackProjectId, a as seedBuckets, d as loadEnvLoad, f as loadEnvSession, o as supabase, s as supabaseCapture, v as isDryRun, w as foreignStackMessage } from "./run-D5iI9zve.js";
2
2
  import { join } from "node:path";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  //#region src/db/session-refresh.ts
@@ -0,0 +1,2 @@
1
+ import { t as runStackCommand } from "./stack-CzNe6rxl.js";
2
+ export { runStackCommand };
@@ -1,4 +1,4 @@
1
- import { n as discoverRepoRoot, t as RepoNotFoundError } from "./root-PA8K5G2d.js";
1
+ import { n as discoverRepoRoot } from "./root-RaJ8ECCO.js";
2
2
  import { c as cliName, n as noteError, s as ownVersion } from "./failure-log-MSdCgvLc.js";
3
3
  import { readFileSync } from "node:fs";
4
4
  import * as Sentry from "@sentry/node";
@@ -301,7 +301,9 @@ function hasRealCaller(inRepo, isTTY) {
301
301
  }
302
302
  /**
303
303
  * A failure the person caused and can fix without anyone's help: the wrong
304
- * directory, a bad flag, a database that has not been set up. These are
304
+ * directory, a bad flag, a database that has not been set up. A `RepoNotFoundError` is
305
+ * not on this list by class: only the dispatcher marks the genuine
306
+ * outside-a-checkout case `expected` (`markOutsideCheckout`). These are
305
307
  * printed and exit non-zero but are not bugs, so they are never sent to Sentry
306
308
  * (the failure log still records them). Matched by name as well as class so
307
309
  * that `UsageError` (`ui.ts`, which imports this module) and the product
@@ -309,7 +311,6 @@ function hasRealCaller(inRepo, isTTY) {
309
311
  * opts in by setting `expected = true`.
310
312
  */
311
313
  function isExpectedFailure(err) {
312
- if (err instanceof RepoNotFoundError) return true;
313
314
  if (!(err instanceof Error)) return false;
314
315
  return err.name === "UsageError" || err.expected === true;
315
316
  }
package/dist/telemetry.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as hasRealCaller, c as lastSentryEventId, d as resolveDevtoolsDsn, i as devtoolsTelemetryEnabled, l as reportDevtoolsError, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as isExpectedFailure, t as captureDevtoolsDeprecation, u as reportDevtoolsFailure } from "./telemetry-BDTAGquh.js";
1
+ import { a as hasRealCaller, c as lastSentryEventId, d as resolveDevtoolsDsn, i as devtoolsTelemetryEnabled, l as reportDevtoolsError, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, s as isExpectedFailure, t as captureDevtoolsDeprecation, u as reportDevtoolsFailure } from "./telemetry-BDOwjuoC.js";
2
2
  export { captureDevtoolsDeprecation, captureDevtoolsError, devtoolsEnvironment, devtoolsTelemetryEnabled, hasRealCaller, initDevtoolsTelemetry, isExpectedFailure, lastSentryEventId, reportDevtoolsError, reportDevtoolsFailure, resolveDevtoolsDsn };
package/dist/with-env.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { r as spawnProcess, t as exec } from "./spawn-BvLQzc_K.js";
2
- import { _ as UnknownEnvironmentError, a as LocalStackOfflineError, c as applyDeployTierAliases, d as loadEnvironment, l as applyWranglerLocalDatabaseAlias, m as probeLocalStack, n as GENERATED_FILE, o as MissingEnvFileError } from "./load-Z2CXCpyx.js";
3
- import { n as availableTiers, s as askSession, t as SESSION_SELECTORS } from "./session-CuexI1f9.js";
2
+ import { _ as UnknownEnvironmentError, a as LocalStackOfflineError, c as applyDeployTierAliases, d as loadEnvironment, l as applyWranglerLocalDatabaseAlias, m as probeLocalStack, n as GENERATED_FILE, o as MissingEnvFileError } from "./load-lrgJ5Kz7.js";
3
+ import { n as availableTiers, s as askSession, t as SESSION_SELECTORS } from "./session-bPPOkolU.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { existsSync, statSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join, resolve } from "node:path";
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@devdogsuga/devtools",
3
- "version": "0.1.51-next.544",
3
+ "version": "0.1.51-next.547",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Contributor CLI for DevDogs repositories (the platform monorepo, DogDays, DogPack): the real tools with the session's tier filled in, `setup`, `db sync`, `db types`, `dev-data`, a menu over them, the checks CI runs, and the `with-env` launcher. Ships built JS; run via `pnpm dlx @devdogsuga/devtools` or `npx @devdogsuga/devtools` (or the `devtools` bin once installed) from inside a clone.",
6
+ "description": "Contributor CLI for DevDogs repositories (the platform monorepo, DogDays, DogPack): the real tools with the session's tier filled in, `setup`, `db sync`, `db types`, `dev-data`, a menu over them, the checks CI runs, and the `with-env` launcher. Ships built JS; run via `pnpm devtools` in a product repo, `pnpm dlx @devdogsuga/devtools` (or the `devtools` bin once installed) from inside a clone.",
7
7
  "license": "BSD-3-Clause",
8
8
  "repository": {
9
9
  "type": "git",
@@ -1,2 +0,0 @@
1
- import { t as runStackCommand } from "./stack-BRevXgI3.js";
2
- export { runStackCommand };