@devdogsuga/devtools 0.1.41 → 0.1.43

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
@@ -74,6 +74,24 @@ rather than a table here. Its first screen lists every interactive command
74
74
  under a plain-English title ("Restart local Supabase") with the command to
75
75
  type beside it (`restart-stack`).
76
76
 
77
+ ## Which repo it runs in
78
+
79
+ Both CLIs run from a DevDogsUGA checkout or from Backstage's (`package.json`
80
+ name `devdogs-monorepo` or `backstage`; the layout code is
81
+ `packages/cli-core/src/repo/layout.ts`). From Backstage, DevDogsUGA is
82
+ `<root>/devdogsuga`: a gitignored symlink to the sibling clone locally
83
+ (`pnpm devdogsuga`), a real checkout at the pinned SHA in CI.
84
+
85
+ | What | Where |
86
+ | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
87
+ | `supabase/` (CLI cwd, config, migrations, seeds, avatars, env manifest), `database.types.ts`, `docs/` | DevDogsUGA's root (`devdogsuga/` from Backstage) |
88
+ | `workers.json`, `.github/` | the repo you are in; DevDogsUGA without one manages no Workers |
89
+ | apps (`cron`, `run`, `deploy <app>`, env manifests) | the repo you are in plus DevDogsUGA's `apps/`; on a name clash the repo you are in wins |
90
+ | `.env`, `.env.<tier>`, `.env.generated` | the root of the repo you are in; `deploy write-env` and the local stack's `.env.generated` also write DevDogsUGA's root, because schedule-builder reads its own repo root. `env pull\|push\|reset` touch only the root you are in. |
91
+
92
+ `pnpm exec supabase` and `wrangler` run inside DevDogsUGA's workspace, so
93
+ `devdogsuga/` needs its own `pnpm install` before the commands that use them.
94
+
77
95
  ## Telemetry disclosure
78
96
 
79
97
  This CLI reports its own crashes to Sentry (see `src/telemetry.ts`), on by
@@ -1,11 +1,11 @@
1
- import { a as cliName, d as positionals, u as flagValue } from "./failure-log-B5GG_8T1.js";
2
- import { C as helpPath, S as catalog, T as renderHelp, _ as bail, a as recordEnteredTier, b as explainError, c as describeEnvironment, d as resolveDbConnection, f as sessionIsLocal, i as beginInvocation, 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, w as renderCommandList, x as unwrap, y as explain } from "./menu-Craj11Sn.js";
3
- import { _ as dryRunKind, c as reportRuns, f as supabase, g as runInGroup, h as reportRan, l as run$1, s as dbPush, u as runWithStderr, v as isDryRun, x as wouldRunLine } from "./supabase-project-Bw8sBFcE.js";
4
- import { a as ownVersion, n as discoverRepoRoot, r as findRepoRoot } from "./root-C2OmZDwD.js";
5
- import { i as loadEnvSession, n as loadEnv, r as loadEnvLoad, t as getEnvSync } from "./peers-RBplwovA.js";
6
- import { d as hasYes, f as isNonInteractive, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, t as captureDevtoolsDeprecation } from "./telemetry-Do13RFhc.js";
7
- import { n as assertRegistryLoaded, r as loadRegistry, t as scopedProcessEnv } from "./local-env-hH4encfD.js";
8
- import { t as runStackCommand } from "./stack-7PuRcaI2.js";
1
+ import { a as cliName, d as positionals, u as flagValue } from "./failure-log-DOt6E3hS.js";
2
+ import { C as helpPath, S as catalog, T as renderHelp, _ as bail, a as recordEnteredTier, b as explainError, c as describeEnvironment, d as resolveDbConnection, f as sessionIsLocal, i as beginInvocation, 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, w as renderCommandList, x as unwrap, y as explain } from "./menu-DYQ-Dd-O.js";
3
+ import { S as wouldRunLine, _ as runInGroup, c as reportRuns, f as supabase, g as reportRan, l as run$1, m as supabaseRoot, s as dbPush, u as runWithStderr, v as dryRunKind, y as isDryRun } from "./supabase-project-C93xRFkN.js";
4
+ import { a as ownVersion, n as discoverRepoRoot, r as findRepoRoot } from "./root-CjPWNkBu.js";
5
+ import { c as devdogsugaRootOrNull, d as listApps, f as resolveAppPath, i as loadEnvSession, l as findApp, n as loadEnv, p as resolveLayout, r as loadEnvLoad, s as appWorkspaceRoot, t as getEnvSync, u as layoutAt } from "./peers-DvUH5VxX.js";
6
+ import { d as hasYes, f as isNonInteractive, n as captureDevtoolsError, o as initDevtoolsTelemetry, r as devtoolsEnvironment, t as captureDevtoolsDeprecation } from "./telemetry-C07-ioHm.js";
7
+ import { n as assertRegistryLoaded, r as loadRegistry, t as scopedProcessEnv } from "./local-env-BuHU5Ckd.js";
8
+ import { t as runStackCommand } from "./stack-C_50FafG.js";
9
9
  import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, password, select, spinner, text } from "@clack/prompts";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
11
11
  import { chmod, readFile, writeFile } from "node:fs/promises";
@@ -112,6 +112,19 @@ async function checkEnv(root) {
112
112
  ]
113
113
  ];
114
114
  for (const [label, actual, expected] of pins) if (JSON.stringify(actual) !== JSON.stringify(expected)) problems.push(`The ${label} set is [${actual.join(", ")}]; expected exactly [${expected.join(", ")}]. A change here re-routes a credential: update this pin deliberately.`);
115
+ if (typeof env.buildKeys === "function") {
116
+ const build = env.buildKeys();
117
+ const expectedBuild = [
118
+ "API_URL",
119
+ "BASE_URL",
120
+ "NEXT_PUBLIC_TURNSTILE_SITE_KEY",
121
+ "PLATFORM_SENTRY_DSN",
122
+ "PUBLISHABLE_KEY",
123
+ "SCHEDULE_BUILDER_SENTRY_DSN"
124
+ ];
125
+ if (JSON.stringify(build) !== JSON.stringify(expectedBuild)) problems.push(`The build set is [${build.join(", ")}]; expected exactly [${expectedBuild.join(", ")}]. A change here changes what the build workflows read from \`<tier>-build\`: update both deliberately.`);
126
+ for (const key of build) if (!variableSet.has(key)) problems.push(`${key} is build: true but is not in variableKeys(), so push cannot write it to the build environment.`);
127
+ }
115
128
  for (const key of narrowed) for (const entry of registry.get(key) ?? []) if (entry.meta.scope !== "environment") problems.push(`${key} is narrowed with scope "${entry.meta.scope}".`);
116
129
  let routed = 0;
117
130
  for (const [key, entries] of registry) {
@@ -245,7 +258,7 @@ function checkMigrationOrder(root, baseRef) {
245
258
  //#endregion
246
259
  //#region ../cli-core/src/workers.ts
247
260
  /**
248
- * The one list of Worker apps, read from root `workers.json`.
261
+ * The one list of Worker apps, read from `workers.json` at the root of the repo you are in.
249
262
  *
250
263
  * Six call sites across `devtools` and `env` used to carry their own copy of
251
264
  * `["platform", "sandbox", "schedule-builder"]` — a CLI's app-choice list, a
@@ -270,11 +283,27 @@ function parseWorkerEntries(json) {
270
283
  throw new Error(`workers.json entry ${index} must be a path string or an object with a "path".`);
271
284
  });
272
285
  }
273
- /** Every entry in root `workers.json`. */
286
+ /**
287
+ * Every entry in `workers.json` at the root of the repo you are in. DevDogsUGA
288
+ * after the platform moved to Backstage has no such file: no Worker apps are
289
+ * managed there, so the list is empty rather than an error. A Backstage
290
+ * checkout always has one, so a missing file there is still an error.
291
+ */
274
292
  function workerEntries() {
275
- cachedWorkerEntries ??= parseWorkerEntries(JSON.parse(readFileSync(join(findRepoRoot(), "workers.json"), "utf8")));
293
+ if (cachedWorkerEntries === void 0) {
294
+ const layout = resolveLayout();
295
+ const file = join(layout.root, "workers.json");
296
+ cachedWorkerEntries = layout.kind === "devdogsuga" && !existsSync(file) ? [] : parseWorkerEntries(JSON.parse(readFileSync(file, "utf8")));
297
+ }
276
298
  return cachedWorkerEntries;
277
299
  }
300
+ /**
301
+ * The directory of a Worker app by its `workers.json` path: under the repo
302
+ * you are in, else under DevDogsUGA's (schedule-builder, from Backstage).
303
+ */
304
+ function workerAppDir(entryPath) {
305
+ return resolveAppPath(entryPath);
306
+ }
278
307
  /** Workspace-relative paths, exactly as listed in root `workers.json`. */
279
308
  function workerPaths() {
280
309
  return workerEntries().map((entry) => entry.path);
@@ -353,17 +382,37 @@ function deployMatrixApps(yamlText) {
353
382
  function sameSet(a, b) {
354
383
  return a.length === b.length && a.every((item) => b.includes(item));
355
384
  }
356
- /** Every drift between `workers.json`, the workspace and the deploy matrix,
357
- * as one readable line each. Empty means in step. */
358
- function checkWorkers(root) {
385
+ /**
386
+ * Every drift between `workers.json`, the workspace and the deploy matrix,
387
+ * as one readable line each. Empty means in step.
388
+ *
389
+ * `workers.json` lives at the root of the repo being checked. DevDogsUGA
390
+ * without one manages no Worker apps (the platform moved to Backstage), so
391
+ * there is nothing to check. From a Backstage checkout the apps are looked up
392
+ * in both repos, since schedule-builder is deployed from Backstage but stays in
393
+ * DevDogsUGA's `apps/`.
394
+ */
395
+ function checkWorkers(root, layout = layoutAt(root)) {
359
396
  const problems = [];
360
- const paths = parseWorkerEntries(JSON.parse(readFileSync(join(root, "workers.json"), "utf8"))).map((entry) => entry.path);
397
+ const workersFile = join(root, "workers.json");
398
+ if (!existsSync(workersFile)) {
399
+ if (layout.kind === "devdogsuga") return [];
400
+ return ["workers.json does not exist."];
401
+ }
402
+ const paths = parseWorkerEntries(JSON.parse(readFileSync(workersFile, "utf8"))).map((entry) => entry.path);
361
403
  const apps = paths.map((path) => basename(path));
362
- const withWrangler = expandWorkspacePackages(root, workspaceGlobs(readFileSync(join(root, "pnpm-workspace.yaml"), "utf8"))).filter((relative) => existsSync(join(root, relative, "wrangler.jsonc")));
404
+ const roots = [root];
405
+ if (layout.kind === "backstage" && layout.hasDevdogsuga) roots.push(layout.devdogsugaRoot);
406
+ const scanned = [...new Set(roots.flatMap((dir) => {
407
+ const workspace = join(dir, "pnpm-workspace.yaml");
408
+ return existsSync(workspace) ? expandWorkspacePackages(dir, workspaceGlobs(readFileSync(workspace, "utf8"))) : [];
409
+ }))];
410
+ const dirOf = (relative) => join(roots.find((dir) => existsSync(join(dir, relative))) ?? root, relative);
411
+ const withWrangler = scanned.filter((relative) => existsSync(join(dirOf(relative), "wrangler.jsonc")));
363
412
  for (const path of withWrangler.filter((p) => !paths.includes(p))) problems.push(`${path} has a wrangler.jsonc but is not in workers.json.`);
364
413
  for (const path of paths.filter((p) => !withWrangler.includes(p))) problems.push(`workers.json lists ${path}, which has no wrangler.jsonc.`);
365
414
  for (const path of paths) {
366
- const manifest = join(root, path, "package.json");
415
+ const manifest = join(dirOf(path), "package.json");
367
416
  if (!existsSync(manifest)) continue;
368
417
  const name = JSON.parse(readFileSync(manifest, "utf8")).name;
369
418
  const slug = path.split("/").pop();
@@ -371,6 +420,7 @@ function checkWorkers(root) {
371
420
  }
372
421
  const deployApp = join(root, ".github", "workflows", "deploy-app.yaml");
373
422
  if (!existsSync(deployApp)) {
423
+ if (layout.kind === "backstage") return problems;
374
424
  problems.push(".github/workflows/deploy-app.yaml does not exist.");
375
425
  return problems;
376
426
  }
@@ -511,7 +561,7 @@ function report(name, problems, ok) {
511
561
  }
512
562
  function migrations(rest) {
513
563
  const base = flagValue(rest, "--base") ?? DEFAULT_BASE;
514
- const { baseLatest, violations } = checkMigrationOrder(findRepoRoot(), base);
564
+ const { baseLatest, violations } = checkMigrationOrder(resolveLayout().devdogsugaRoot, base);
515
565
  return report("migrations", violations.map((v) => `${v.filename} (${v.timestamp}) sorts before ${base}'s newest migration (${baseLatest}). Recreate it with a fresh timestamp (\`devtools new-migration\`) and regenerate types.`), `in order (${base}'s newest: ${baseLatest ?? "none"}).`);
516
566
  }
517
567
  async function run(rest) {
@@ -1201,10 +1251,11 @@ async function runEnvironmentDoctor(opts) {
1201
1251
  fix: machine.stack === "no" && machine.docker === "yes" ? "Start it with `pnpm devtools supabase start`." : void 0
1202
1252
  });
1203
1253
  }
1204
- if (repoRoot) checks.push(typesFreshness(repoRoot));
1254
+ const supabaseRoot = repoRoot ? devdogsugaRootOrNull(layoutAt(repoRoot)) : null;
1255
+ if (supabaseRoot) checks.push(typesFreshness(supabaseRoot));
1205
1256
  const sessionDbUrl = process.env.DB_URL;
1206
- if (repoRoot && sessionDbUrl) try {
1207
- checks.push(...databaseChecks(await probeDatabase(sessionDbUrl), readDeclaredBuckets(repoRoot)));
1257
+ if (supabaseRoot && sessionDbUrl) try {
1258
+ checks.push(...databaseChecks(await probeDatabase(sessionDbUrl), readDeclaredBuckets(supabaseRoot)));
1208
1259
  } catch (err) {
1209
1260
  checks.push({
1210
1261
  id: "database-unreachable",
@@ -1931,7 +1982,7 @@ function targetHeader(target, count) {
1931
1982
  "#",
1932
1983
  ...comment(`The ${count} key${count === 1 ? "" : "s"} a \`pnpm devtools env push --target ${target}\` routes, and nothing else. The committed defaults and the per-developer values go nowhere, and a minted credential is signed at deploy time rather than stored, so none of the three has a line here to fill in.`),
1933
1984
  "#",
1934
- ...comment(target === "production" ? "The apply-tier credential IS here — SUPABASE_ACCESS_TOKEN reaches the production-apply GitHub environment, and no other target carries it." : `The apply-tier credential is NOT here. It exists to reshape production, so a copy in ${target} would be a second write-capable token to rotate for no benefit — and \`env push\` skips it outside production anyway.`),
1985
+ ...comment(target === "production" ? "The apply-tier credential IS here — SUPABASE_ACCESS_TOKEN reaches the production GitHub environment, and no other target carries it." : `The apply-tier credential is NOT here. It exists to reshape production, so a copy in ${target} would be a second write-capable token to rotate for no benefit — and \`env push\` skips it outside production anyway.`),
1935
1986
  "#",
1936
1987
  ...comment(`Fill the values in, or run \`pnpm devtools env pull --target ${target}\` to fetch what Bitwarden already holds. Nothing is commented out: push reads assignments, and a commented line is not one — a value typed on it would be silently skipped.`),
1937
1988
  "#",
@@ -2328,9 +2379,8 @@ function workflowsForTier(config, tier) {
2328
2379
  }
2329
2380
  /** Every app that actually carries a Wrangler configuration. */
2330
2381
  function discoverWranglerConfigs() {
2331
- const appsRoot = join(findRepoRoot(), "apps");
2332
- return readdirSync(appsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
2333
- const path = join(appsRoot, entry.name, "wrangler.jsonc");
2382
+ return listApps().flatMap((entry) => {
2383
+ const path = join(entry.dir, "wrangler.jsonc");
2334
2384
  return existsSync(path) ? [{
2335
2385
  app: entry.name,
2336
2386
  path,
@@ -2347,7 +2397,7 @@ function discoverWranglerConfigs() {
2347
2397
  async function discoverCronMaps() {
2348
2398
  const results = [];
2349
2399
  for (const { app } of discoverWranglerConfigs()) {
2350
- const scheduledPath = join(findRepoRoot(), "apps", app, "cloudflare", "scheduled.ts");
2400
+ const scheduledPath = join(findApp(app) ?? join(findRepoRoot(), "apps", app), "cloudflare", "scheduled.ts");
2351
2401
  if (!existsSync(scheduledPath)) continue;
2352
2402
  const mod = await import(pathToFileURL(scheduledPath).href);
2353
2403
  const parsed = CronRoutes.safeParse(mod.CRON_ROUTES);
@@ -2560,7 +2610,7 @@ function readEnvFile(path) {
2560
2610
  /** Resolve the selected app's origin without assuming every app uses port 3000. */
2561
2611
  function resolveBaseUrl(app, tier, config, tierEnv) {
2562
2612
  if (tier === "development" && app) {
2563
- const appRoot = join(findRepoRoot(), "apps", app);
2613
+ const appRoot = findApp(app) ?? join(findRepoRoot(), "apps", app);
2564
2614
  const local = {
2565
2615
  ...readEnvFile(join(appRoot, ".dev.vars.example")),
2566
2616
  ...readEnvFile(join(appRoot, ".dev.vars"))
@@ -2630,7 +2680,7 @@ function workspaceDepsBuildCommand(app) {
2630
2680
  /** Build only `app`'s workspace dependencies — all `vinext dev` needs, since
2631
2681
  * it compiles the app itself on demand. */
2632
2682
  function buildWorkspaceDeps(app) {
2633
- return run$1(workspaceDepsBuildCommand(app));
2683
+ return run$1(workspaceDepsBuildCommand(app), void 0, appWorkspaceRoot(app));
2634
2684
  }
2635
2685
  //#endregion
2636
2686
  //#region src/workflows/commands.ts
@@ -2770,7 +2820,7 @@ function pidIsAlive(pid) {
2770
2820
  }
2771
2821
  function appDir(app) {
2772
2822
  const path = workerPaths().find((candidate) => basename(candidate) === app);
2773
- return path === void 0 ? void 0 : join(findRepoRoot(), path);
2823
+ return path === void 0 ? void 0 : workerAppDir(path);
2774
2824
  }
2775
2825
  /** Probe Wrangler's Workflow explorer API, rather than merely checking a port. */
2776
2826
  async function isWranglerDevRunning(port, timeoutMs = 1e3, fetcher = fetch) {
@@ -2853,7 +2903,7 @@ async function startTemporaryWrangler(app, requestedPort) {
2853
2903
  return null;
2854
2904
  }
2855
2905
  const child = spawn("pnpm", vinextDevArgs(app, port), {
2856
- cwd: findRepoRoot(),
2906
+ cwd: appWorkspaceRoot(app),
2857
2907
  stdio: "inherit",
2858
2908
  detached: process.platform !== "win32",
2859
2909
  env: await scopedProcessEnv(app, loaded.env, "development")
@@ -3060,7 +3110,7 @@ async function runWorkflowsRun(argv) {
3060
3110
  if (givenPort === void 0 && options.port && options.port !== "8787") recordResolved("--port", options.port);
3061
3111
  process.stdout.write(`→ ${choice.name} (${choice.app}, ${tier})\n`);
3062
3112
  try {
3063
- const result = await runWithStderr(workflowTriggerArgs(choice, options), triggerEnv);
3113
+ const result = await runWithStderr(workflowTriggerArgs(choice, options), triggerEnv, appWorkspaceRoot(choice.app));
3064
3114
  if (result.code !== 0 && tier === "development" && isWranglerDevConnectionFailure(result.stderr)) process.stderr.write(wranglerDevConnectionHint(choice.app, options.port ?? "8787"));
3065
3115
  if (result.code === 0 && tier === "development" && options.instanceId) try {
3066
3116
  return await waitForLocalWorkflow(choice.name, options.instanceId, options.port ?? "8787");
@@ -3980,6 +4030,11 @@ function runStatus(argv, cwd) {
3980
4030
  * reasons that have nothing to do with Supabase.
3981
4031
  */
3982
4032
  function detectLocalSupabase(cwd) {
4033
+ const repoRoot = discoverRepoRoot(cwd);
4034
+ if (repoRoot !== null) {
4035
+ const layout = layoutAt(repoRoot);
4036
+ if (layout.kind === "backstage" && layout.hasDevdogsuga) cwd = layout.devdogsugaRoot;
4037
+ }
3983
4038
  let output;
3984
4039
  try {
3985
4040
  output = runStatus([
@@ -5408,7 +5463,7 @@ async function runSupabaseRaw(args) {
5408
5463
  "supabase",
5409
5464
  ...args
5410
5465
  ];
5411
- const result = await runInGroup("pnpm", full, { cwd: findRepoRoot() });
5466
+ const result = await runInGroup("pnpm", full, { cwd: supabaseRoot() });
5412
5467
  reportRan("pnpm", full, result);
5413
5468
  return result.code;
5414
5469
  }
@@ -5482,7 +5537,7 @@ async function regenerateTypes() {
5482
5537
  "run",
5483
5538
  "types:db"
5484
5539
  ];
5485
- const result = await runInGroup("pnpm", args, { cwd: findRepoRoot() });
5540
+ const result = await runInGroup("pnpm", args, { cwd: supabaseRoot() });
5486
5541
  reportRan("pnpm", args, result);
5487
5542
  return result.code;
5488
5543
  }
@@ -5710,6 +5765,28 @@ const NEEDS_DEPS_BUILT = /* @__PURE__ */ new Set([
5710
5765
  "lint:fix",
5711
5766
  "test"
5712
5767
  ]);
5768
+ /** The workspace root an app lives in: `<root>/apps/<name>` gives `<root>`. */
5769
+ function workspaceRootOfApp(dir) {
5770
+ return dirname(dirname(dir));
5771
+ }
5772
+ /**
5773
+ * Splits `filters` by the workspace that holds the app each one names. From a
5774
+ * Backstage checkout `schedule-builder` belongs to DevDogsUGA's workspace, and
5775
+ * a `pnpm --filter` run from Backstage's root would match nothing. A filter
5776
+ * naming no known app (a glob, a path) stays with the repo you are in. No
5777
+ * filters means the whole workspace of the repo you are in.
5778
+ */
5779
+ function groupFiltersByRoot(filters, apps = appsWith(), repoRoot = findRepoRoot()) {
5780
+ const groups = /* @__PURE__ */ new Map([[repoRoot, []]]);
5781
+ for (const filter of filters) {
5782
+ const root = apps.find((app) => app.name === filter)?.root ?? repoRoot;
5783
+ const group = groups.get(root) ?? [];
5784
+ group.push(filter);
5785
+ groups.set(root, group);
5786
+ }
5787
+ if (filters.length > 0 && groups.get(repoRoot)?.length === 0) groups.delete(repoRoot);
5788
+ return groups;
5789
+ }
5713
5790
  /**
5714
5791
  * Runs one `pnpm` invocation to completion, reporting its own exit the way
5715
5792
  * `passthroughApps` needs to: it returns to keep going, and exits the process
@@ -5727,9 +5804,9 @@ const NEEDS_DEPS_BUILT = /* @__PURE__ */ new Set([
5727
5804
  * `packages/devtools`, and a `pnpm -r`/`--filter` invoked there would scope
5728
5805
  * itself relative to that one package rather than the workspace root.
5729
5806
  */
5730
- async function runOne(args, extraEnv, { final }) {
5807
+ async function runOne({ args, cwd }, extraEnv, { final }) {
5731
5808
  const result = await runInGroup("pnpm", args, {
5732
- cwd: findRepoRoot(),
5809
+ cwd,
5733
5810
  env: {
5734
5811
  ...process.env,
5735
5812
  ...extraEnv,
@@ -5749,7 +5826,7 @@ async function runOne(args, extraEnv, { final }) {
5749
5826
  * Never returns: `runOne` exits on a failure and on the final command.
5750
5827
  */
5751
5828
  async function passthrough(commands, extraEnv) {
5752
- for (const [index, args] of commands.entries()) await runOne(args, extraEnv, { final: index === commands.length - 1 });
5829
+ for (const [index, command] of commands.entries()) await runOne(command, extraEnv, { final: index === commands.length - 1 });
5753
5830
  process.exit(1);
5754
5831
  }
5755
5832
  /**
@@ -5820,19 +5897,31 @@ function extractFilters(args) {
5820
5897
  * `packages/*`/`docs` package is reachable from at least one app's
5821
5898
  * dependency graph, and no app depends on another app).
5822
5899
  */
5823
- function allAppNames() {
5824
- const dir = join(findRepoRoot(), "apps");
5825
- if (!existsSync(dir)) return [];
5900
+ function allAppNames(root = findRepoRoot()) {
5826
5901
  const names = [];
5827
- for (const entry of readdirSync(dir)) {
5828
- const manifest = join(dir, entry, "package.json");
5902
+ for (const app of appManifests(root)) if (app.pkg.name) names.push(app.pkg.name);
5903
+ return names;
5904
+ }
5905
+ /**
5906
+ * The parsed `package.json` of every app in the repo you are in, plus DevDogsUGA's
5907
+ * from a Backstage checkout (`repo/layout.ts`). With `root`, only that
5908
+ * workspace's. An unparsable manifest is one we cannot use, so it is skipped.
5909
+ */
5910
+ function appManifests(root) {
5911
+ const found = [];
5912
+ for (const app of listApps(resolveLayout())) {
5913
+ const appRoot = workspaceRootOfApp(app.dir);
5914
+ if (root !== void 0 && appRoot !== root) continue;
5915
+ const manifest = join(app.dir, "package.json");
5829
5916
  if (!existsSync(manifest)) continue;
5830
5917
  try {
5831
- const pkg = JSON.parse(readFileSync(manifest, "utf8"));
5832
- if (pkg.name) names.push(pkg.name);
5918
+ found.push({
5919
+ root: appRoot,
5920
+ pkg: JSON.parse(readFileSync(manifest, "utf8"))
5921
+ });
5833
5922
  } catch {}
5834
5923
  }
5835
- return names;
5924
+ return found;
5836
5925
  }
5837
5926
  /**
5838
5927
  * Builds the `pnpm run <task>` invocation(s) for a set of package filters (or
@@ -5885,27 +5974,35 @@ function allAppNames() {
5885
5974
  */
5886
5975
  async function passthroughApps(task, filters, rest, extraEnv) {
5887
5976
  const commands = [];
5888
- if (NEEDS_DEPS_BUILT.has(task) && (task !== "build" || filters.length > 0)) {
5889
- const depsFilters = (filters.length > 0 ? filters : allAppNames()).flatMap((f) => ["--filter", `${f}^...`]);
5890
- commands.push([
5891
- "-r",
5892
- "--if-present",
5893
- ...depsFilters,
5894
- "run",
5895
- "build"
5896
- ]);
5977
+ for (const [cwd, groupFilters] of groupFiltersByRoot(filters)) {
5978
+ if (NEEDS_DEPS_BUILT.has(task) && (task !== "build" || filters.length > 0)) {
5979
+ const depsFilters = (groupFilters.length > 0 ? groupFilters : allAppNames(cwd)).flatMap((f) => ["--filter", `${f}^...`]);
5980
+ commands.push({
5981
+ args: [
5982
+ "-r",
5983
+ "--if-present",
5984
+ ...depsFilters,
5985
+ "run",
5986
+ "build"
5987
+ ],
5988
+ cwd
5989
+ });
5990
+ }
5991
+ const ownFilters = groupFilters.flatMap((f) => ["--filter", f]);
5992
+ const parallel = task === "dev" ? ["--parallel"] : [];
5993
+ commands.push({
5994
+ args: [
5995
+ "-r",
5996
+ "--if-present",
5997
+ ...parallel,
5998
+ ...ownFilters,
5999
+ "run",
6000
+ task,
6001
+ ...rest
6002
+ ],
6003
+ cwd
6004
+ });
5897
6005
  }
5898
- const ownFilters = filters.flatMap((f) => ["--filter", f]);
5899
- const parallel = task === "dev" ? ["--parallel"] : [];
5900
- commands.push([
5901
- "-r",
5902
- "--if-present",
5903
- ...parallel,
5904
- ...ownFilters,
5905
- "run",
5906
- task,
5907
- ...rest
5908
- ]);
5909
6006
  return passthrough(commands, extraEnv);
5910
6007
  }
5911
6008
  /** A `dev` script that starts `vinext dev` (platform's `with-env vinext dev`). */
@@ -5954,15 +6051,18 @@ function planDev(apps, filters, rest) {
5954
6051
  * The env is the session's: `launch.ts` has already entered the tier into
5955
6052
  * `process.env` (or `--tier` loaded one into `tierEnv`).
5956
6053
  */
5957
- async function runDev(plan, filters, rest, tierEnv) {
5958
- await runOne([
5959
- "-r",
5960
- "--if-present",
5961
- ...(filters.length > 0 ? filters : allAppNames()).flatMap((f) => ["--filter", `${f}^...`]),
5962
- "run",
5963
- "build"
5964
- ], tierEnv, { final: false });
5965
- const { scopedProcessEnv } = await import("./local-env-CR3McKHZ.js");
6054
+ async function runDev(plan, filters, rest, tierEnv, root) {
6055
+ await runOne({
6056
+ args: [
6057
+ "-r",
6058
+ "--if-present",
6059
+ ...(filters.length > 0 ? filters : allAppNames(root)).flatMap((f) => ["--filter", `${f}^...`]),
6060
+ "run",
6061
+ "build"
6062
+ ],
6063
+ cwd: root
6064
+ }, tierEnv, { final: false });
6065
+ const { scopedProcessEnv } = await import("./local-env-MqH18s-N.js");
5966
6066
  const environment = {
5967
6067
  ...process.env,
5968
6068
  ...tierEnv
@@ -5986,7 +6086,7 @@ async function runDev(plan, filters, rest, tierEnv) {
5986
6086
  let first;
5987
6087
  const runs = children.map(async ({ args, env }) => {
5988
6088
  const result = await runInGroup("pnpm", args, {
5989
- cwd: findRepoRoot(),
6089
+ cwd: root,
5990
6090
  env: {
5991
6091
  ...env,
5992
6092
  DEVDOGS_PICK: "0"
@@ -6004,8 +6104,14 @@ async function runDev(plan, filters, rest, tierEnv) {
6004
6104
  * `runDev`. */
6005
6105
  async function dispatch$1(task, filters, rest, tierEnv) {
6006
6106
  if (task === "dev") {
6007
- const plan = planDev(appsWith("dev"), filters, rest);
6008
- if (plan.vinext.length > 0) return runDev(plan, filters, rest, tierEnv);
6107
+ const groups = groupFiltersByRoot(filters);
6108
+ if (groups.size > 1) {
6109
+ process.stderr.write("devtools run: dev servers from DevDogsUGA and Backstage cannot be started together; start them from their own checkouts.\n");
6110
+ process.exit(1);
6111
+ }
6112
+ const root = [...groups.keys()][0];
6113
+ const plan = planDev(appsWith("dev", root), filters, rest);
6114
+ if (plan.vinext.length > 0) return runDev(plan, filters, rest, tierEnv, root);
6009
6115
  }
6010
6116
  return passthroughApps(task, filters, rest, tierEnv);
6011
6117
  }
@@ -6053,21 +6159,15 @@ function parseTierArg(args) {
6053
6159
  * out to a dry-run equivalent, which costs about a second before the first
6054
6160
  * question.
6055
6161
  */
6056
- function appsWith(task) {
6057
- const dir = join(findRepoRoot(), "apps");
6058
- if (!existsSync(dir)) return [];
6162
+ function appsWith(task, root) {
6059
6163
  const found = [];
6060
- for (const entry of readdirSync(dir)) {
6061
- const manifest = join(dir, entry, "package.json");
6062
- if (!existsSync(manifest)) continue;
6063
- try {
6064
- const pkg = JSON.parse(readFileSync(manifest, "utf8"));
6065
- const script = pkg.scripts?.[task];
6066
- if (pkg.name && script) found.push({
6067
- name: pkg.name,
6068
- script
6069
- });
6070
- } catch {}
6164
+ for (const app of appManifests(root)) {
6165
+ const script = task === void 0 ? "" : app.pkg.scripts?.[task];
6166
+ if (app.pkg.name && (task === void 0 || script)) found.push({
6167
+ name: app.pkg.name,
6168
+ script: script ?? "",
6169
+ root: app.root
6170
+ });
6071
6171
  }
6072
6172
  return found;
6073
6173
  }
@@ -1,4 +1,4 @@
1
- import { a as ownVersion } from "./root-C2OmZDwD.js";
1
+ import { a as ownVersion } from "./root-CjPWNkBu.js";
2
2
  import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { homedir, platform, release } from "node:os";
package/dist/launch.js CHANGED
@@ -1,9 +1,9 @@
1
- import { a as cliName, t as installFailureLog } from "./failure-log-B5GG_8T1.js";
2
- import { C as helpPath, S as catalog, _ as bail, g as setMenuEnvHook, h as realEnvEntryDeps, m as enterSessionEnvironment, n as bareGroupStartPath, u as nonEmpty, v as errorMessage, x as unwrap } from "./menu-Craj11Sn.js";
3
- import { b as setDryRun, m as formatCommand, v as isDryRun, y as resolveDryRun } from "./supabase-project-Bw8sBFcE.js";
4
- import { n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError } from "./root-C2OmZDwD.js";
5
- import { i as loadEnvSession, r as loadEnvLoad } from "./peers-RBplwovA.js";
6
- import { d as hasYes, f as isNonInteractive, m as stripTierFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as stripNoEnvFlag, s as lastSentryEventId } from "./telemetry-Do13RFhc.js";
1
+ import { a as cliName, t as installFailureLog } from "./failure-log-DOt6E3hS.js";
2
+ import { C as helpPath, S as catalog, _ as bail, g as setMenuEnvHook, h as realEnvEntryDeps, m as enterSessionEnvironment, n as bareGroupStartPath, u as nonEmpty, v as errorMessage, x as unwrap } from "./menu-DYQ-Dd-O.js";
3
+ import { b as resolveDryRun, h as formatCommand, x as setDryRun, y as isDryRun } from "./supabase-project-C93xRFkN.js";
4
+ import { n as discoverRepoRoot, r as findRepoRoot, t as RepoNotFoundError } from "./root-CjPWNkBu.js";
5
+ import { i as loadEnvSession, r as loadEnvLoad } from "./peers-DvUH5VxX.js";
6
+ import { d as hasYes, f as isNonInteractive, m as stripTierFlag, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as stripNoEnvFlag, s as lastSentryEventId } from "./telemetry-C07-ioHm.js";
7
7
  import { confirm, select } from "@clack/prompts";
8
8
  //#region ../cli-core/src/session.ts
9
9
  /**
@@ -155,7 +155,7 @@ function skipsEnvEntry(rest) {
155
155
  /** The offer's stack start (`env-entry.ts` holds the TTY prompt, this holds
156
156
  * the lifecycle command), imported lazily like the rest of the commands. */
157
157
  async function startStack() {
158
- const { runStackCommand } = await import("./stack-B5UHASY2.js");
158
+ const { runStackCommand } = await import("./stack-o1Kml3V0.js");
159
159
  return runStackCommand("start");
160
160
  }
161
161
  /**
@@ -166,7 +166,7 @@ async function startStack() {
166
166
  * post-tier-resolution path.
167
167
  */
168
168
  async function dispatch(argv) {
169
- const { main } = await import("./cli-CeCAuSJd.js");
169
+ const { main } = await import("./cli-B_AiECh4.js");
170
170
  try {
171
171
  await main(argv);
172
172
  } catch (err) {