@devdogsuga/devtools 0.1.51-next.545 → 0.1.51-next.548

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,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";
@@ -7058,7 +7058,7 @@ function commandLine(command, args) {
7058
7058
  function cliLine(cli, args) {
7059
7059
  return commandLine(cli.command, [...cli.prefix, ...args]);
7060
7060
  }
7061
- function versionOf(output) {
7061
+ function versionOf$1(output) {
7062
7062
  return /\b(\d+\.\d+\.\d+)\b/.exec(output)?.[1] ?? null;
7063
7063
  }
7064
7064
  async function resolveSupabaseCli(root, pin, run, fileExists = existsSync) {
@@ -7076,7 +7076,7 @@ async function resolveSupabaseCli(root, pin, run, fileExists = existsSync) {
7076
7076
  });
7077
7077
  for (const candidate of candidates) try {
7078
7078
  const result = await run(candidate.command, ["--version"], { cwd: root });
7079
- if (result.code === 0 && versionOf(result.stdout) === pin) return candidate;
7079
+ if (result.code === 0 && versionOf$1(result.stdout) === pin) return candidate;
7080
7080
  } catch {}
7081
7081
  try {
7082
7082
  if ((await run("pnpm", ["--version"], { cwd: root })).code === 0) return {
@@ -7229,19 +7229,36 @@ async function resolveTarget(product, cli, run, preference) {
7229
7229
  * project, and the SQL runs in one transaction.
7230
7230
  */
7231
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;
7232
7234
  constructor(message) {
7233
7235
  super(message);
7234
7236
  this.name = "DevDataError";
7235
7237
  }
7236
7238
  };
7237
7239
  /**
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.
7240
+ * Whether `err` is the product's own SQL being wrong: a Postgres error whose
7241
+ * SQLSTATE class is a cardinality violation (21), data exception (22),
7242
+ * integrity constraint violation (23) or syntax error or access rule
7243
+ * violation (42). Everything else is not the file's fault and reports as a
7244
+ * normal failure: a refused or dropped connection (class 08 and
7245
+ * `ECONNREFUSED`), an operator shutdown (57, `admin_shutdown`), a server out
7246
+ * of resources (53), a transaction that was rolled back (40), and so on.
7241
7247
  */
7242
- function isMissingSchemaError(err) {
7248
+ function isSqlFileError(err) {
7243
7249
  const code = err?.code;
7244
- return code === "42P01" || code === "3F000";
7250
+ return typeof code === "string" && /^(21|22|23|42)[0-9A-Z]{3}$/.test(code);
7251
+ }
7252
+ /**
7253
+ * The line of `text` a Postgres error points at, from the error's 1-based
7254
+ * character `position`, or null when it carries none.
7255
+ */
7256
+ function errorLine(err, text) {
7257
+ const raw = err?.position;
7258
+ const position = typeof raw === "string" ? Number(raw) : raw;
7259
+ if (typeof position !== "number" || !Number.isInteger(position)) return null;
7260
+ if (position < 1 || position > text.length + 1) return null;
7261
+ return text.slice(0, position - 1).split("\n").length;
7245
7262
  }
7246
7263
  function postgresSession(dbUrl) {
7247
7264
  const sql = postgres(dbUrl, {
@@ -7273,6 +7290,24 @@ function postgresSession(dbUrl) {
7273
7290
  lastSignInAt: row.last_sign_in_at?.toISOString() ?? null
7274
7291
  } : null;
7275
7292
  },
7293
+ async appliedMigrations() {
7294
+ const [table] = await sql`
7295
+ select to_regclass('supabase_migrations.schema_migrations')::text as present`;
7296
+ if (!table?.present) return null;
7297
+ return (await sql`
7298
+ select version from supabase_migrations.schema_migrations`).map((r) => r.version);
7299
+ },
7300
+ async missingSchemas(schemas) {
7301
+ const rows = await sql`
7302
+ select nspname from pg_namespace where nspname = any(${[...schemas]})`;
7303
+ const present = new Set(rows.map((r) => r.nspname));
7304
+ return schemas.filter((name) => !present.has(name));
7305
+ },
7306
+ async exec(text) {
7307
+ await sql.begin(async (tx) => {
7308
+ await tx.unsafe(text);
7309
+ });
7310
+ },
7276
7311
  async runAs(memberId, text) {
7277
7312
  await sql.begin(async (tx) => {
7278
7313
  await tx`select set_config('devtools.member_id', ${memberId}, true)`;
@@ -7296,10 +7331,9 @@ async function runDevData(product, target, open, options) {
7296
7331
  const text = readFileSync(file, "utf8");
7297
7332
  const session = open(target.dbUrl);
7298
7333
  try {
7334
+ const missing = await session.missingSchemas(product.config.supabase.schemas);
7335
+ 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.`);
7299
7336
  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;
7303
7337
  } finally {
7304
7338
  await session.close();
7305
7339
  }
@@ -7312,7 +7346,13 @@ async function seed(session, file, text, options, log) {
7312
7346
  log(`Would run ${file} (${text.split("\n").length} lines) in one transaction.`);
7313
7347
  return null;
7314
7348
  }
7315
- await session.runAs(member.id, text);
7349
+ try {
7350
+ await session.runAs(member.id, text);
7351
+ } catch (err) {
7352
+ if (!isSqlFileError(err)) throw err;
7353
+ const line = errorLine(err, text);
7354
+ 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).`);
7355
+ }
7316
7356
  return {
7317
7357
  member,
7318
7358
  file
@@ -7546,6 +7586,15 @@ function typesAssetName(product, hash) {
7546
7586
  * Without that, the diff would start from an empty database and write the whole
7547
7587
  * schema as one migration, which `db push` rejects as already applied.
7548
7588
  *
7589
+ * The record only says what *should* be in the database. Before the diff,
7590
+ * sync therefore compares the database itself with the record (`supabase db
7591
+ * diff --from <database> --to migrations`, restricted to the product's
7592
+ * schemas) and re-applies whatever is missing: a dropped table, a dropped
7593
+ * schema, a database reset behind the record's back. The migration ledger is
7594
+ * never trusted to decide anything; it is rewritten to list the record when it
7595
+ * is missing or short. "The database already matches" is therefore a
7596
+ * statement about the live database.
7597
+ *
7549
7598
  * It never resets the database. A reset wipes `auth.custom_oauth_providers`,
7550
7599
  * which is where "Sign in with DevDogs" is registered (Q83); `--rebuild` is
7551
7600
  * the explicit escape hatch for a database that has drifted from its record,
@@ -7597,6 +7646,7 @@ async function dbSync(product, cli, target, run, options) {
7597
7646
  "--db-url",
7598
7647
  "<DB_URL>"
7599
7648
  ])} (this machine has no record of what the project has had applied)`);
7649
+ if (options.open) log(`Would run: ${cliLine(cli, liveDiffArgs(product, target).slice(cli.prefix.length))} (and restore anything the database lost)`);
7600
7650
  log(`Would run: ${cliLine(cli, generateArgs.slice(cli.prefix.length))}`);
7601
7651
  log(target.kind === "local" ? `Would run: ${cliLine(cli, [
7602
7652
  "migration",
@@ -7620,6 +7670,7 @@ async function dbSync(product, cli, target, run, options) {
7620
7670
  const restored = migrationFiles(migrationsDir).length;
7621
7671
  if (fetched.code === 0 && restored > 0) log(`Restored ${restored} applied migration${restored === 1 ? "" : "s"} from the project's history.`);
7622
7672
  }
7673
+ const restored = await restoreDatabase(product, cli, target, run, options, log);
7623
7674
  const before = new Set(migrationFiles(migrationsDir));
7624
7675
  let generated = await run(cli.command, generateArgs, { cwd: product.root });
7625
7676
  if (generated.code !== 0 && isContainerFailure(generated)) {
@@ -7629,11 +7680,11 @@ async function dbSync(product, cli, target, run, options) {
7629
7680
  if (generated.code !== 0) throw new SyncError(isContainerFailure(generated) ? "Could not diff the schema: Docker is needed for the shadow database. Start Docker and try again." : "Could not diff the schema against what this database has had applied.", generated.stderr || generated.stdout);
7630
7681
  const created = migrationFiles(migrationsDir).filter((f) => !before.has(f));
7631
7682
  if (created.length === 0) {
7632
- log("The database already matches supabase/schemas.");
7683
+ if (!restored.applied) log("The database already matches supabase/schemas.");
7633
7684
  return {
7634
7685
  migration: null,
7635
- applied: false,
7636
- destructive: []
7686
+ applied: restored.applied,
7687
+ destructive: restored.destructive
7637
7688
  };
7638
7689
  }
7639
7690
  const migration = created[created.length - 1];
@@ -7667,8 +7718,137 @@ async function dbSync(product, cli, target, run, options) {
7667
7718
  return {
7668
7719
  migration,
7669
7720
  applied: true,
7670
- destructive
7721
+ destructive: [...restored.destructive, ...destructive]
7722
+ };
7723
+ }
7724
+ /** `20261011120000` from `20261011120000_devtools_sync.sql`. */
7725
+ function versionOf(file) {
7726
+ return file.split("_")[0] ?? file;
7727
+ }
7728
+ function liveDiffArgs(product, target) {
7729
+ return [
7730
+ "db",
7731
+ "diff",
7732
+ "--from",
7733
+ target.kind === "local" ? "local" : target.dbUrl,
7734
+ "--to",
7735
+ "migrations",
7736
+ "--use-pg-delta",
7737
+ "--schema",
7738
+ product.config.supabase.schemas.join(","),
7739
+ "--output-format",
7740
+ "json"
7741
+ ];
7742
+ }
7743
+ /**
7744
+ * The SQL that turns the database into what the record describes, from the
7745
+ * CLI's pg-delta diff (database to migrations baseline). Empty when they
7746
+ * agree. Only the product's own schemas are compared, so the platform's
7747
+ * (auth, storage, ...) never show up as drift.
7748
+ */
7749
+ async function liveDiff(product, cli, target, run, log) {
7750
+ const args = [...cli.prefix, ...liveDiffArgs(product, target)];
7751
+ let result = await run(cli.command, args, { cwd: product.root });
7752
+ if (result.code !== 0 && isContainerFailure(result)) {
7753
+ log("The shadow database did not start; trying once more.");
7754
+ result = await run(cli.command, args, { cwd: product.root });
7755
+ }
7756
+ if (result.code !== 0) throw new SyncError(isContainerFailure(result) ? "Could not compare the database with its record: Docker is needed for the shadow database. Start Docker and try again." : "Could not compare the database with what devtools recorded as applied.", result.stderr || result.stdout);
7757
+ for (const line of result.stdout.split("\n").reverse()) {
7758
+ const text = line.trim();
7759
+ if (!text.startsWith("{")) continue;
7760
+ try {
7761
+ const parsed = JSON.parse(text);
7762
+ if (typeof parsed.diff === "string") return parsed.diff;
7763
+ } catch {}
7764
+ }
7765
+ throw new SyncError("Could not read the result of comparing the database with its record.", result.stdout || result.stderr);
7766
+ }
7767
+ /**
7768
+ * Makes the database agree with the local record, from the database itself.
7769
+ * The declarative diff only compares `supabase/schemas` with
7770
+ * `supabase/migrations`, so a database that lost objects behind the record's
7771
+ * back (a table dropped by hand, a hosted project reset from the dashboard, a
7772
+ * checkout switched from the local stack to a hosted project) would read as
7773
+ * "already matches". So, with a record to compare against:
7774
+ *
7775
+ * 1. the database is diffed against the record, per configured schema, and
7776
+ * whatever it lacks is created again (data in what remains is untouched);
7777
+ * statements that drop something need the same approval as any change;
7778
+ * 2. the migration ledger is made to list every migration in the record
7779
+ * (marking them applied, which they now are), so the next
7780
+ * `migration up` applies only what is new.
7781
+ *
7782
+ * A missing or unreadable ledger decides nothing by itself: it is rewritten,
7783
+ * never read as "start over". A database that cannot be reached is left to
7784
+ * the apply step to report.
7785
+ */
7786
+ async function restoreDatabase(product, cli, target, run, options, log) {
7787
+ const nothing = {
7788
+ applied: false,
7789
+ destructive: []
7671
7790
  };
7791
+ const files = migrationFiles(product.paths.migrationsDir);
7792
+ if (!options.open || files.length === 0) return nothing;
7793
+ let session;
7794
+ let missing;
7795
+ let ledger;
7796
+ try {
7797
+ session = options.open(target.dbUrl);
7798
+ missing = await session.missingSchemas(product.config.supabase.schemas);
7799
+ } catch (err) {
7800
+ log(`Could not check what the database has (${err.message}); trusting the local record.`);
7801
+ await session?.close().catch(() => void 0);
7802
+ return nothing;
7803
+ }
7804
+ try {
7805
+ try {
7806
+ ledger = await session.appliedMigrations();
7807
+ } catch (err) {
7808
+ ledger = void 0;
7809
+ log(`Could not read the migration history (${err.message}); deciding from the schemas alone.`);
7810
+ }
7811
+ const sql = (await liveDiff(product, cli, target, run, log)).trim();
7812
+ let applied = false;
7813
+ let destructive = [];
7814
+ if (sql) {
7815
+ destructive = destructiveStatements(sql);
7816
+ if (destructive.length > 0 && !options.yes) {
7817
+ if (!(options.confirmDestructive ? await options.confirmDestructive(sql, destructive) : false)) throw new SyncError(`The database differs from supabase/schemas and putting it right drops data in your development database (${destructive.length} statement${destructive.length === 1 ? "" : "s"}). Re-run with --yes to allow it.`, destructive.join(";\n"));
7818
+ }
7819
+ log(missing.length > 0 ? `The database is missing ${missing.length === 1 ? "schema" : "schemas"} ${missing.join(", ")}; creating ${missing.length === 1 ? "it" : "them"} again from supabase/schemas.` : "The database has lost part of supabase/schemas (a dropped table, view or function, perhaps); restoring it. Existing data is kept.");
7820
+ try {
7821
+ await session.exec(sql);
7822
+ } catch (err) {
7823
+ throw new SyncError("Could not restore what the database lost. `devtools db sync --rebuild` starts over (it wipes local data).", err.message);
7824
+ }
7825
+ applied = true;
7826
+ }
7827
+ if (ledger !== void 0) {
7828
+ const known = new Set(ledger ?? []);
7829
+ const unlisted = files.map(versionOf).filter((version) => !known.has(version));
7830
+ if (unlisted.length > 0) {
7831
+ if (ledger === null) log("The database has no migration history table; recreating it from the local record.");
7832
+ const repaired = await run(cli.command, [
7833
+ ...cli.prefix,
7834
+ "migration",
7835
+ "repair",
7836
+ "--status",
7837
+ "applied",
7838
+ "--db-url",
7839
+ target.dbUrl,
7840
+ ...unlisted
7841
+ ], { cwd: product.root });
7842
+ if (repaired.code !== 0) throw new SyncError("Could not record the applied migrations in the database's history.", repaired.stderr || repaired.stdout);
7843
+ }
7844
+ }
7845
+ return {
7846
+ applied,
7847
+ destructive
7848
+ };
7849
+ } finally {
7850
+ await session.close().catch(() => void 0);
7851
+ }
7672
7852
  }
7673
7853
  function isContainerFailure(result) {
7674
7854
  return /docker|container|image/i.test(result.stderr + result.stdout);
@@ -7932,7 +8112,8 @@ async function runSync(product, deps, rest) {
7932
8112
  yes: hasYes(rest),
7933
8113
  confirmDestructive: deps.interactive() ? confirmDestructive : void 0,
7934
8114
  dryRun,
7935
- log: (line) => log.info(line)
8115
+ log: (line) => log.info(line),
8116
+ open: deps.openSql
7936
8117
  });
7937
8118
  if (rest.includes("--rebuild") && !dryRun) try {
7938
8119
  const row = await reRegisterSavedProvider(product.paths.toolingFile, target);
@@ -7957,7 +8138,7 @@ async function regenerateAfterSync(product, cli, target, result, deps) {
7957
8138
  log.info(`Would regenerate ${product.config.types.out}.`);
7958
8139
  return;
7959
8140
  }
7960
- if (!result.migration && typesAreCurrent(product)) return;
8141
+ if (!result.applied && typesAreCurrent(product)) return;
7961
8142
  try {
7962
8143
  await generateTypes(product, cli, target, deps.run, (line) => log.info(line));
7963
8144
  log.success(`Regenerated ${product.config.types.out}.`);
@@ -8003,7 +8184,7 @@ function createProductHandlers(deps = defaultDeps) {
8003
8184
  switch (sub) {
8004
8185
  case "sync": {
8005
8186
  const { result, cli, target } = await runSync(product, deps, flags);
8006
- if (result.migration) log.success("Your database now matches supabase/schemas.");
8187
+ if (result.applied) log.success("Your database now matches supabase/schemas.");
8007
8188
  await regenerateAfterSync(product, cli, target, result, deps);
8008
8189
  return DONE;
8009
8190
  }
@@ -8233,9 +8414,10 @@ async function runSetup(product, deps, rest) {
8233
8414
  const result = await dbSync(product, cli, target, deps.run, {
8234
8415
  yes: hasYes(rest),
8235
8416
  confirmDestructive: deps.interactive() ? confirmDestructive : void 0,
8236
- log: (line) => log.info(line)
8417
+ log: (line) => log.info(line),
8418
+ open: deps.openSql
8237
8419
  });
8238
- log.success(result.migration ? "Applied supabase/schemas to your database." : "Your database already matched supabase/schemas.");
8420
+ log.success(result.applied ? "Applied supabase/schemas to your database." : "Your database already matched supabase/schemas.");
8239
8421
  if (target.kind === "local") await waitForRest(target, deps, product);
8240
8422
  if (rest.includes("--download-types")) try {
8241
8423
  await downloadTypes(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-DNZ-6WEI.js");
186
+ const { main } = await import("./cli-DV6tvG1E.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@devdogsuga/devtools",
3
- "version": "0.1.51-next.545",
3
+ "version": "0.1.51-next.548",
4
4
  "private": false,
5
5
  "type": "module",
6
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.",
@@ -1,2 +0,0 @@
1
- import { t as runStackCommand } from "./stack-BRevXgI3.js";
2
- export { runStackCommand };