@devdogsuga/devtools 0.1.35 → 0.1.37

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,8 +1,8 @@
1
- import { c as YES, i as suppressFailureLog, l as createCatalog, o as JSON_FLAG, s as SCOPES } from "./failure-log-BBPGejQv.js";
2
- import { a as listContainerNames, i as foreignStackProjectId, n as containerPrefix, o as readProjectId, p as supabaseCapture, r as foreignStackMessage, t as STACK_API_PORT } from "./supabase-project-0knM0mqd.js";
1
+ import { c as YES, d as positionals, i as suppressFailureLog, l as createCatalog, o as JSON_FLAG, s as SCOPES } from "./failure-log-B5GG_8T1.js";
2
+ import { a as listContainerNames, i as foreignStackProjectId, n as containerPrefix, o as readProjectId, p as supabaseCapture, r as foreignStackMessage, t as STACK_API_PORT } from "./supabase-project-B7JMicl-.js";
3
3
  import { r as findRepoRoot } from "./root-C2OmZDwD.js";
4
4
  import { n as loadEnv, r as loadEnvLoad } from "./peers-RBplwovA.js";
5
- import { c as reportDevtoolsError, f as isNonInteractive } from "./telemetry-El7OainP.js";
5
+ import { c as reportDevtoolsError, f as isNonInteractive } from "./telemetry-Do13RFhc.js";
6
6
  import { cancel, confirm, isCancel, log, note, select, text } from "@clack/prompts";
7
7
  import { existsSync } from "node:fs";
8
8
  import { writeFile } from "node:fs/promises";
@@ -78,15 +78,28 @@ function scopedBody(commands) {
78
78
  open = command.scope;
79
79
  lines.push(`${INDENT}${SCOPES[open].help}:`);
80
80
  }
81
- lines.push(`${INDENT.repeat(2)}${command.name.padEnd(width)} ${command.summary}`);
81
+ lines.push(`${INDENT.repeat(2)}${command.name.padEnd(width)} ${describe(command)}`);
82
82
  }
83
83
  return lines;
84
84
  }
85
85
  function optionRows(options) {
86
86
  return options.map((option) => [optionLabel(option), option.summary]);
87
87
  }
88
+ /** A command's summary as a list prints it, with what else it answers to. */
89
+ function describe(command) {
90
+ const aliases = (command.aliases ?? []).map((alias) => alias.name);
91
+ const also = aliases.length > 0 ? ` (also: ${aliases.join(", ")})` : "";
92
+ const deprecated = command.deprecated ? " (deprecated)" : "";
93
+ return `${command.summary}${also}${deprecated}`;
94
+ }
88
95
  function childRows(children) {
89
- return children.map((child) => [child.name, child.deprecated ? `${child.summary} (deprecated)` : child.summary]);
96
+ return children.map((child) => [child.name, describe(child)]);
97
+ }
98
+ /** `cron` is `jobs --kind sync`, for a command page's alias line. */
99
+ function aliasLine(node) {
100
+ const aliases = node.aliases ?? [];
101
+ if (aliases.length === 0) return void 0;
102
+ return `Also typed as ${aliases.map((alias) => alias.implies?.length ? `${alias.name} (${node.name} … ${alias.implies.join(" ")})` : alias.name).join(", ")}.`;
90
103
  }
91
104
  /** `pnpm devtools --help`: the groups, and nothing below them. */
92
105
  function renderRoot(catalog) {
@@ -121,6 +134,8 @@ function renderCommand(catalog, path, node) {
121
134
  "",
122
135
  node.summary
123
136
  ];
137
+ const also = aliasLine(node);
138
+ if (also) lines.push("", also);
124
139
  if (node.deprecated) lines.push("", `Deprecated. ${node.deprecated}`);
125
140
  if (children.length > 0) lines.push("", "Subcommands:", ...scopedBody(children));
126
141
  if (options.length > 0) lines.push("", "Options:", ...columns(optionRows(options)));
@@ -137,8 +152,9 @@ function renderCommand(catalog, path, node) {
137
152
  function renderHelp(catalog, path = []) {
138
153
  if (path.length === 0) return renderRoot(catalog);
139
154
  const node = catalog.findCommand(path);
140
- if (!node) return renderRoot(catalog);
141
- return renderCommand(catalog, path, node);
155
+ const canonical = catalog.canonicalPath(path);
156
+ if (!node || !canonical) return renderRoot(catalog);
157
+ return renderCommand(catalog, canonical, node);
142
158
  }
143
159
  /**
144
160
  * The command names in `argv` that precede any flag.
@@ -175,7 +191,8 @@ function commandList(catalog) {
175
191
  path: path.join(" "),
176
192
  summary: node.summary,
177
193
  surface: cliOnly ? "cli-only" : "interactive",
178
- ...node.deprecated ? { deprecated: node.deprecated } : {}
194
+ ...node.deprecated ? { deprecated: node.deprecated } : {},
195
+ ...node.aliases?.length ? { aliases: node.aliases.map((alias) => alias.name) } : {}
179
196
  });
180
197
  visit(node.subcommands ?? [], path, cliOnly);
181
198
  }
@@ -254,73 +271,10 @@ const checkCommand = {
254
271
  ]
255
272
  };
256
273
  //#endregion
257
- //#region src/cron/catalog.ts
258
- /**
259
- * `cron`'s place in the command tree: declaration only, nothing here runs.
260
- * The handler lives in `commands.ts` beside it.
261
- */
262
- const cronCommand = {
263
- name: "cron",
264
- summary: "Cloudflare cron triggers: list schedules or fire one now.",
265
- hint: "list schedules, run a job",
266
- subcommands: [{
267
- name: "list",
268
- dryRun: "read-only",
269
- summary: "Every registered cron: schedule, English description, routes.",
270
- hint: "the audit view",
271
- options: [
272
- {
273
- flag: "--app",
274
- value: "<slug>",
275
- summary: "Limit to one app. All apps if omitted."
276
- },
277
- {
278
- flag: "--tier",
279
- value: "<t>",
280
- summary: "Whose wrangler schedules to read. All tiers if omitted.",
281
- prompt: {
282
- kind: "select",
283
- message: "Which tier's wrangler schedules?",
284
- choices: [
285
- {
286
- value: "development",
287
- hint: "the default"
288
- },
289
- { value: "staging" },
290
- { value: "production" }
291
- ]
292
- }
293
- },
294
- JSON_FLAG
295
- ]
296
- }, {
297
- name: "run",
298
- summary: "Choose and fire a configured cron schedule now.",
299
- hint: "pick a job from the Worker configuration",
300
- options: [
301
- {
302
- flag: "--app",
303
- value: "<slug>",
304
- summary: "Limit the discovered jobs to one app."
305
- },
306
- {
307
- flag: "--tier",
308
- value: "<t>",
309
- summary: "development, staging or production. Asked when absent."
310
- },
311
- {
312
- flag: "--cron",
313
- value: "<expr>",
314
- summary: "Fire this schedule without opening the picker."
315
- },
316
- YES
317
- ]
318
- }]
319
- };
320
- //#endregion
321
274
  //#region src/doctor/catalog.ts
322
275
  const doctorCommand = {
323
276
  name: "doctor",
277
+ title: "Troubleshoot my setup",
324
278
  dryRun: "read-only",
325
279
  summary: "Check this machine's environment against what the repo needs.",
326
280
  hint: "node, pnpm, Docker, .env, hosted Supabase, OAuth — read-only",
@@ -335,7 +289,9 @@ const doctorCommand = {
335
289
  };
336
290
  const envCommand = {
337
291
  name: "env",
292
+ title: "Manage local env files",
338
293
  summary: "Create, regenerate and clear the local env files.",
294
+ hint: "create, regenerate or clear .env files",
339
295
  subcommands: [
340
296
  {
341
297
  name: "init",
@@ -400,6 +356,129 @@ const envCommand = {
400
356
  ]
401
357
  };
402
358
  //#endregion
359
+ //#region src/jobs/catalog.ts
360
+ /**
361
+ * `jobs`' place in the command tree: declaration only, nothing here runs.
362
+ * The handler lives in `commands.ts` beside it.
363
+ *
364
+ * One command for both kinds of background job an app runs on Cloudflare:
365
+ * quick syncs (cron triggers that call a route) and long-running jobs
366
+ * (Workflows). They used to be the separate `cron` and `workflows` commands,
367
+ * which asked a contributor to know how a job is built before they could run
368
+ * it. Both names stay, as aliases that narrow `jobs` to their own kind, so
369
+ * every existing `cron run` and `workflows serve` keeps working.
370
+ */
371
+ /**
372
+ * Narrows `run` and `list` to one kind. What the `cron` and `workflows`
373
+ * aliases stand for, and promptless: the picker's headings already split the
374
+ * two kinds, so a wizard question in front of it would ask the same thing
375
+ * twice.
376
+ */
377
+ const KIND = {
378
+ flag: "--kind",
379
+ value: "<k>",
380
+ summary: "sync or long-running. Both when absent."
381
+ };
382
+ const APP = {
383
+ flag: "--app",
384
+ value: "<slug>",
385
+ summary: "Limit to one app."
386
+ };
387
+ const PORT = {
388
+ flag: "--port",
389
+ value: "<n>",
390
+ summary: "Local Workflow runtime port. Defaults to 8787."
391
+ };
392
+ const jobsCommand = {
393
+ name: "jobs",
394
+ title: "Background jobs",
395
+ summary: "Run, list or serve the apps' cron syncs and Workflows.",
396
+ hint: "run one now, list them, or keep a Workflow runtime open",
397
+ aliases: [{
398
+ name: "cron",
399
+ implies: ["--kind", "sync"]
400
+ }, {
401
+ name: "workflows",
402
+ implies: ["--kind", "long-running"]
403
+ }],
404
+ subcommands: [
405
+ {
406
+ name: "run",
407
+ title: "Run a job now",
408
+ summary: "Choose a job and run it now on a tier.",
409
+ hint: "pick from the apps' Worker configuration",
410
+ options: [
411
+ {
412
+ ...APP,
413
+ summary: "Limit the discovered jobs to one app."
414
+ },
415
+ {
416
+ flag: "--tier",
417
+ value: "<t>",
418
+ summary: "development, staging or production. Asked when absent."
419
+ },
420
+ {
421
+ flag: "--cron",
422
+ value: "<expr>",
423
+ summary: "Run this quick sync without opening the picker."
424
+ },
425
+ {
426
+ flag: "--workflow",
427
+ value: "<name>",
428
+ summary: "Run this long-running job without opening the picker."
429
+ },
430
+ {
431
+ flag: "--params",
432
+ value: "<json>",
433
+ summary: "JSON parameters passed to a Workflow instance."
434
+ },
435
+ PORT,
436
+ KIND,
437
+ YES
438
+ ]
439
+ },
440
+ {
441
+ name: "list",
442
+ title: "List every job and when it runs",
443
+ dryRun: "read-only",
444
+ summary: "Every job, its schedule, and any schedule that never fires.",
445
+ hint: "the audit view",
446
+ options: [
447
+ APP,
448
+ {
449
+ flag: "--tier",
450
+ value: "<t>",
451
+ summary: "Whose wrangler schedules to read. All tiers if omitted.",
452
+ prompt: {
453
+ kind: "select",
454
+ message: "Which tier's wrangler schedules?",
455
+ choices: [
456
+ {
457
+ value: "development",
458
+ hint: "the default"
459
+ },
460
+ { value: "staging" },
461
+ { value: "production" }
462
+ ]
463
+ }
464
+ },
465
+ KIND,
466
+ JSON_FLAG
467
+ ]
468
+ },
469
+ {
470
+ name: "serve",
471
+ title: "Keep a local Workflow runtime open",
472
+ summary: "Run an app's Workflows locally until Ctrl+C, even via cron.",
473
+ hint: "secure alternative to bare wrangler dev",
474
+ options: [{
475
+ ...APP,
476
+ summary: "Serve the development Workflows for this app."
477
+ }, PORT]
478
+ }
479
+ ]
480
+ };
481
+ //#endregion
403
482
  //#region src/roles/catalog.ts
404
483
  /**
405
484
  * `roles`' place in the command tree: declaration only, nothing here runs.
@@ -411,6 +490,7 @@ const envCommand = {
411
490
  */
412
491
  const rolesCommand = {
413
492
  name: "roles",
493
+ title: "Manage platform roles",
414
494
  summary: "See who holds each role, and grant or revoke one.",
415
495
  hint: "list, grant, revoke",
416
496
  subcommands: [
@@ -437,6 +517,7 @@ const rolesCommand = {
437
517
  //#region src/oauth/catalog.ts
438
518
  const oauthCommand = {
439
519
  name: "oauth",
520
+ title: "Set up \"Sign in with DevDogs\"",
440
521
  summary: "Configure \"Sign in with DevDogs\" for this directory.",
441
522
  hint: "works outside a DevDogsUGA checkout too",
442
523
  envFree: true,
@@ -494,51 +575,54 @@ const psqlCommand = {
494
575
  //#endregion
495
576
  //#region src/preset/catalog.ts
496
577
  /**
497
- * The TUI presets' place in the command tree: declaration only, nothing here
498
- * runs. The handlers live in `commands.ts` beside it.
578
+ * The Supabase jobs' places in the command tree: declaration only, nothing
579
+ * here runs. The handlers live in `commands.ts` beside it.
499
580
  *
500
- * A preset sequences several tool calls (or asks a question between them) and
501
- * prints the commands it ran. Each is also typed as `devtools preset <name>`.
581
+ * Each sequences several tool calls (or asks a question between them) and
582
+ * prints the commands it ran. They were once subcommands of `preset`; now each
583
+ * is a top-level command under its own name, so the menu's first screen can
584
+ * offer them directly. `preset` is refused with the new name (`cli.ts`'s
585
+ * `RETIRED`).
502
586
  */
503
- const presetCommand = {
504
- name: "preset",
587
+ const restartStackCommand = {
588
+ name: "restart-stack",
589
+ title: "Restart local Supabase",
505
590
  dryRun: "handled",
506
- summary: "Common Supabase jobs, each a few tool calls in a row.",
507
- hint: "restart the stack, new migration, apply migrations, push config",
508
- subcommands: [
509
- {
510
- name: "restart-stack",
511
- summary: "Restart the local Supabase stack.",
512
- hint: "supabase stop, then start; picks up config.toml",
513
- scope: "machine",
514
- when: "instance-running"
515
- },
516
- {
517
- name: "new-migration",
518
- summary: "Create an empty migration for an app's schema.",
519
- hint: "supabase migration new <schema>_<description>",
520
- scope: "repo",
521
- options: [{
522
- flag: "--app",
523
- value: "<slug>",
524
- summary: "Whose schema. Asked for when absent."
525
- }]
526
- },
527
- {
528
- name: "apply-migrations",
529
- summary: "Apply new migrations to the session's database.",
530
- hint: "supabase db push, then offers types:db",
531
- scope: "endpoint",
532
- options: [YES]
533
- },
534
- {
535
- name: "push-config",
536
- summary: "Push config.toml to the session's hosted project.",
537
- hint: "shows the diff first; the local stack reads it on restart",
538
- scope: "endpoint",
539
- options: [YES]
540
- }
541
- ]
591
+ summary: "Restart the local Supabase stack.",
592
+ hint: "supabase stop, then start; picks up config.toml",
593
+ scope: "machine",
594
+ when: "instance-running"
595
+ };
596
+ const newMigrationCommand = {
597
+ name: "new-migration",
598
+ title: "Create a new migration",
599
+ dryRun: "handled",
600
+ summary: "Create an empty migration for an app's schema.",
601
+ hint: "supabase migration new <schema>_<description>",
602
+ scope: "repo",
603
+ options: [{
604
+ flag: "--app",
605
+ value: "<slug>",
606
+ summary: "Whose schema. Asked for when absent."
607
+ }]
608
+ };
609
+ const applyMigrationsCommand = {
610
+ name: "apply-migrations",
611
+ title: "Apply migrations to the database",
612
+ dryRun: "handled",
613
+ summary: "Apply new migrations to the session's database.",
614
+ hint: "supabase db push, then offers types:db",
615
+ scope: "endpoint",
616
+ options: [YES]
617
+ };
618
+ const pushConfigCommand = {
619
+ name: "push-config",
620
+ title: "Push Supabase settings",
621
+ dryRun: "handled",
622
+ summary: "Push config.toml to the session's hosted project.",
623
+ hint: "shows the diff first; the local stack reads it on restart",
624
+ scope: "endpoint",
625
+ options: [YES]
542
626
  };
543
627
  //#endregion
544
628
  //#region src/run/catalog.ts
@@ -550,7 +634,7 @@ const presetCommand = {
550
634
  * asked "every package?", "which filter?" and "which tier?" up front would
551
635
  * ask the same question three times and let the answers disagree. `--tier`
552
636
  * is promptless for a different reason than `--filter`/`--all`, though: it is
553
- * not a question `run` asks itself elsewhere (unlike `cron run`'s own
637
+ * not a question `run` asks itself elsewhere (unlike `jobs run`'s own
554
638
  * `--tier`, which opens a live picker) — it is a scripting input with no
555
639
  * wizard equivalent, the same category `--json` sits in. `--help` still
556
640
  * documents all three, which is where someone scripting this will look.
@@ -578,11 +662,10 @@ const catalog = createCatalog({
578
662
  usage: "pnpm devtools",
579
663
  commonTasks: [
580
664
  ["setup", "Prepare a new checkout"],
581
- ["run dev", "Start development servers"],
582
665
  ["supabase start", "Start Supabase on this machine"],
583
666
  ["supabase <args>", "Run the Supabase CLI against this session's tier"],
584
- ["preset apply-migrations", "Push migrations, then offer types:db"],
585
- ["cron run", "Choose and run a scheduled job"]
667
+ ["apply-migrations", "Push migrations, then offer types:db"],
668
+ ["jobs run", "Choose and run a background job"]
586
669
  ],
587
670
  groups: [
588
671
  {
@@ -590,21 +673,25 @@ const catalog = createCatalog({
590
673
  commands: [
591
674
  {
592
675
  name: "setup",
676
+ title: "Set up this checkout",
593
677
  summary: "Check prerequisites and seed .env.",
594
678
  hint: "run this first"
595
679
  },
596
- oauthCommand,
597
680
  {
598
681
  name: "script",
682
+ title: "Run a package script",
599
683
  dryRun: "handled",
600
684
  summary: "Pick a package, then one of its scripts, and run it.",
601
685
  hint: "pnpm -F <package> run <script>"
602
686
  },
687
+ oauthCommand,
688
+ doctorCommand,
603
689
  {
604
690
  name: "run",
605
691
  dryRun: "handled",
606
692
  summary: "Run a pnpm workspace task, asking which apps first.",
607
693
  hint: "deprecated: pnpm -r run <task>",
694
+ surface: "cli-only",
608
695
  deprecated: "Use `pnpm -r run <task>` for every package or `pnpm -F <app> <task>` for one. It still runs, and each use is reported so it can be removed (TASK-404).",
609
696
  subcommands: [
610
697
  {
@@ -642,84 +729,22 @@ const catalog = createCatalog({
642
729
  ]
643
730
  },
644
731
  {
645
- title: "Runtime & infrastructure",
732
+ title: "Supabase",
646
733
  commands: [
647
- presetCommand,
648
- cronCommand,
649
- {
650
- name: "workflows",
651
- summary: "Cloudflare Workflows: list configured bindings or trigger one.",
652
- hint: "pick a workflow from wrangler.jsonc",
653
- subcommands: [
654
- {
655
- name: "list",
656
- dryRun: "read-only",
657
- summary: "List every Workflow binding declared by each Wrangler tier.",
658
- options: [
659
- {
660
- flag: "--app",
661
- value: "<slug>",
662
- summary: "Limit to one app."
663
- },
664
- {
665
- flag: "--tier",
666
- value: "<t>",
667
- summary: "Limit to development, staging or production."
668
- },
669
- JSON_FLAG
670
- ]
671
- },
672
- {
673
- name: "run",
674
- summary: "Choose and trigger a Workflow through Wrangler.",
675
- hint: "local session or a deployed tier",
676
- options: [
677
- {
678
- flag: "--app",
679
- value: "<slug>",
680
- summary: "Limit the discovered Workflows to one app."
681
- },
682
- {
683
- flag: "--tier",
684
- value: "<t>",
685
- summary: "development, staging or production. Asked when absent."
686
- },
687
- {
688
- flag: "--workflow",
689
- value: "<name>",
690
- summary: "Trigger this configured name without opening the picker."
691
- },
692
- {
693
- flag: "--params",
694
- value: "<json>",
695
- summary: "JSON parameters passed to the Workflow instance."
696
- },
697
- {
698
- flag: "--port",
699
- value: "<n>",
700
- summary: "Local Wrangler session port. Defaults to 8787."
701
- },
702
- YES
703
- ]
704
- },
705
- {
706
- name: "serve",
707
- summary: "Start an app-scoped local Wrangler runtime until Ctrl+C.",
708
- hint: "secure alternative to bare wrangler dev",
709
- options: [{
710
- flag: "--app",
711
- value: "<slug>",
712
- summary: "Serve the development Workflow for this app."
713
- }, {
714
- flag: "--port",
715
- value: "<n>",
716
- summary: "Local Wrangler session port. Defaults to 8787."
717
- }]
718
- }
719
- ]
720
- }
734
+ restartStackCommand,
735
+ newMigrationCommand,
736
+ applyMigrationsCommand,
737
+ pushConfigCommand
721
738
  ]
722
739
  },
740
+ {
741
+ title: "Background jobs",
742
+ commands: [jobsCommand]
743
+ },
744
+ {
745
+ title: "Configuration & integrations",
746
+ commands: [envCommand, rolesCommand]
747
+ },
723
748
  {
724
749
  title: "The real tools",
725
750
  commands: [
@@ -729,14 +754,6 @@ const catalog = createCatalog({
729
754
  psqlCommand
730
755
  ]
731
756
  },
732
- {
733
- title: "Configuration & integrations",
734
- commands: [envCommand, rolesCommand]
735
- },
736
- {
737
- title: "Environment",
738
- commands: [doctorCommand]
739
- },
740
757
  {
741
758
  title: "CI & CLI utilities",
742
759
  commands: [checkCommand, completionsCommand]
@@ -973,23 +990,31 @@ function realEnvEntryDeps(envLoad, envSession, startStack) {
973
990
  }
974
991
  /**
975
992
  * Commands that bring the local stack up or down themselves: `db
976
- * start|stop|restart`, the `supabase start|stop` passthrough, and the
977
- * restart-stack preset. An offer to start the stack before one of these would
993
+ * start|stop|restart`, the `supabase start|stop` passthrough, and
994
+ * `restart-stack`. An offer to start the stack before one of these would
978
995
  * be asking a question the command is about to answer.
979
996
  */
980
997
  function isStackLifecycle(argv) {
981
998
  const [first, second] = argv;
982
999
  if (first === "db") return second === "start" || second === "stop" || second === "restart";
983
1000
  if (first === "supabase") return second === "start" || second === "stop";
984
- return first === "preset" && second === "restart-stack";
1001
+ return first === "restart-stack";
985
1002
  }
986
1003
  /**
987
1004
  * Commands that may continue in a degraded entry while the stack is down:
988
- * the ones that can fix it (`db`, `supabase`, the presets), and whose data
989
- * commands re-check the connection themselves and say what is missing.
1005
+ * the ones that can fix it (`db`, `supabase`, the Supabase jobs), and whose
1006
+ * data commands re-check the connection themselves and say what is missing.
990
1007
  */
1008
+ const STACK_OWNERS = /* @__PURE__ */ new Set([
1009
+ "db",
1010
+ "supabase",
1011
+ "restart-stack",
1012
+ "new-migration",
1013
+ "apply-migrations",
1014
+ "push-config"
1015
+ ]);
991
1016
  function ownsStack(argv) {
992
- return argv[0] === "db" || argv[0] === "supabase" || argv[0] === "preset";
1017
+ return argv[0] !== void 0 && STACK_OWNERS.has(argv[0]);
993
1018
  }
994
1019
  /**
995
1020
  * Enters `tier` (and `devDatabase`'s overlay) for the current process,
@@ -1172,71 +1197,6 @@ async function resolveDbConnection(opts = {}) {
1172
1197
  };
1173
1198
  }
1174
1199
  //#endregion
1175
- //#region ../cli-core/src/args.ts
1176
- /**
1177
- * Positional arguments, with flag values excluded.
1178
- *
1179
- * Its own module because of one bug it prevents. The first positional is the
1180
- * subcommand, and `pull`, `push` and `audit` do very different things to live
1181
- * credentials. A naive `filter((a) => !a.startsWith("--"))` reads a flag's
1182
- * VALUE as a positional, so `env --file notes.env audit` fails loudly:
1183
- * `notes.env` is not a subcommand and the command refuses. The quiet version
1184
- * is the one that matters:
1185
- *
1186
- * env --file push audit
1187
- *
1188
- * which runs `push`, writing to Bitwarden and GitHub, when the caller asked
1189
- * for `audit`, which writes nothing at all.
1190
- */
1191
- /**
1192
- * Flags that consume the token after them.
1193
- */
1194
- const VALUE_FLAGS = /* @__PURE__ */ new Set([
1195
- "--access-token",
1196
- "--format",
1197
- "--out",
1198
- "--version",
1199
- "--app",
1200
- "--apps",
1201
- "--base-url",
1202
- "--file",
1203
- "--source",
1204
- "--target",
1205
- "--tier",
1206
- "--cron",
1207
- "--workflow",
1208
- "--params",
1209
- "--port",
1210
- "--preview-url",
1211
- "--db-url",
1212
- "--user",
1213
- "--filter",
1214
- "--shell"
1215
- ]);
1216
- function positionals(argv) {
1217
- const found = [];
1218
- for (let i = 0; i < argv.length; i += 1) {
1219
- const arg = argv[i];
1220
- if (!arg.startsWith("-")) {
1221
- found.push(arg);
1222
- continue;
1223
- }
1224
- const next = argv[i + 1];
1225
- if (VALUE_FLAGS.has(arg) && next !== void 0 && !next.startsWith("-")) i += 1;
1226
- }
1227
- return found;
1228
- }
1229
- /**
1230
- * The value after `flag`, or `undefined` when the flag is absent or is
1231
- * followed by another flag instead of a value.
1232
- */
1233
- function flagValue(rest, flag) {
1234
- const index = rest.indexOf(flag);
1235
- if (index === -1) return void 0;
1236
- const value = rest[index + 1];
1237
- return value && !value.startsWith("--") ? value : void 0;
1238
- }
1239
- //#endregion
1240
1200
  //#region ../cli-core/src/environment.ts
1241
1201
  /**
1242
1202
  * What this machine currently is, read once before the menu draws itself.
@@ -1389,7 +1349,7 @@ function describeEnvironment(env) {
1389
1349
  * A command's inputs arrive from two places that never meet: the wizard walks
1390
1350
  * `commands.ts` and gathers the *declared* options into the argv it dispatches
1391
1351
  * (see `menu.ts`), while a runner resolves anything it prompts for *itself* —
1392
- * `workflows run` picking a tier, `env init` picking projects — deep inside its
1352
+ * `jobs run` picking a tier, `env init` picking projects — deep inside its
1393
1353
  * own call stack, where the dispatched argv is long out of reach. Threading a
1394
1354
  * return value back up through every runner would touch each one's signature;
1395
1355
  * a recorder every layer can reach touches only the layers that actually
@@ -1445,13 +1405,22 @@ function recordResolved(...fragment) {
1445
1405
  interactive = true;
1446
1406
  }
1447
1407
  /**
1408
+ * One argv token as a POSIX shell reads it back. A cron expression is the case
1409
+ * that forced this: `--cron 0 0 * * *` pasted unquoted is three stray words
1410
+ * and two globs.
1411
+ */
1412
+ function shellQuote(token) {
1413
+ if (/^[\w@%+=:,./-]+$/.test(token)) return token;
1414
+ return `'${token.replaceAll("'", `'\\''`)}'`;
1415
+ }
1416
+ /**
1448
1417
  * The command to print, or `null` when there is nothing worth printing —
1449
1418
  * either no invocation ran, or the user typed a complete command and answered
1450
1419
  * no prompts, so echoing it back would be noise.
1451
1420
  */
1452
1421
  function reproducibleCommand() {
1453
1422
  if (argv === null || !interactive || argv.length === 0) return null;
1454
- return `pnpm devtools ${[...enteredTier ? ["--tier", enteredTier] : [], ...argv].join(" ")}`;
1423
+ return `pnpm devtools ${[...enteredTier ? ["--tier", enteredTier] : [], ...argv].map(shellQuote).join(" ")}`;
1455
1424
  }
1456
1425
  //#endregion
1457
1426
  //#region ../cli-core/src/menu.ts
@@ -1471,6 +1440,16 @@ function reproducibleCommand() {
1471
1440
  *
1472
1441
  * Walking the catalog means an interactive command added there is in the
1473
1442
  * menu the same day, with its options. Commands marked `cli-only` do not appear here.
1443
+ *
1444
+ * ## One flat first screen
1445
+ *
1446
+ * The first screen lists every top-level command, in the catalog's order,
1447
+ * under its `title`: "Restart local Supabase" rather than `restart-stack`.
1448
+ * There used to be a screen of groups above it, which put a door between the
1449
+ * reader and every command and named the doors after how the CLI is built
1450
+ * rather than what the reader came to do. The command's real name rides in
1451
+ * the hint (`restart-stack · …`), so a walk still teaches what to type next
1452
+ * time. Aliases are never drawn: the menu builds an argv for a real command.
1474
1453
  */
1475
1454
  /** Chosen when a submenu should return to the screen above it. */
1476
1455
  const BACK = Symbol("back");
@@ -1495,39 +1474,38 @@ function hintFor(node) {
1495
1474
  const said = node.hint ?? node.summary;
1496
1475
  return node.scope ? `${SCOPES[node.scope].menu} · ${said}` : said;
1497
1476
  }
1498
- async function pickGroup(catalog, env) {
1499
- const groups = catalog.groups.filter((group) => offered(group.commands, env).length > 0);
1477
+ /**
1478
+ * One command's entry on a screen: its title, with its real name leading the
1479
+ * hint so the reader learns what to type. A command with no title is labelled
1480
+ * by its name, which then has no reason to appear twice.
1481
+ */
1482
+ function entryFor(node) {
1483
+ return node.title ? {
1484
+ label: node.title,
1485
+ hint: `${node.name} · ${hintFor(node)}`
1486
+ } : {
1487
+ label: node.name,
1488
+ hint: hintFor(node)
1489
+ };
1490
+ }
1491
+ async function pickTopLevel(catalog, env) {
1500
1492
  return unwrap(await select({
1501
1493
  message: "What would you like to do?",
1502
- options: [...groups.map((group) => ({
1503
- value: group,
1504
- label: group.title,
1505
- hint: offered(group.commands, env).map((command) => command.name).join(", ")
1494
+ options: [...offered(catalog.topLevel, env).map((command) => ({
1495
+ value: command,
1496
+ ...entryFor(command)
1506
1497
  })), {
1507
1498
  value: null,
1508
1499
  label: "Quit"
1509
1500
  }]
1510
1501
  }));
1511
1502
  }
1512
- async function pickCommand(group, env) {
1513
- const commands = offered(group.commands, env);
1514
- if (commands.length === 1) return commands[0];
1515
- return unwrap(await select({
1516
- message: `${group.title}:`,
1517
- options: [...commands.map((command) => ({
1518
- value: command,
1519
- label: command.name,
1520
- hint: hintFor(command)
1521
- })), BACK_OPTION]
1522
- }));
1523
- }
1524
1503
  async function pickSubcommand(node, env) {
1525
1504
  return unwrap(await select({
1526
- message: `${node.name}:`,
1505
+ message: `${node.title ?? node.name}:`,
1527
1506
  options: [...offered(node.subcommands ?? [], env).map((child) => ({
1528
1507
  value: child,
1529
- label: child.name,
1530
- hint: hintFor(child)
1508
+ ...entryFor(child)
1531
1509
  })), BACK_OPTION]
1532
1510
  }));
1533
1511
  }
@@ -1573,7 +1551,7 @@ async function askOptions(node) {
1573
1551
  * that leaf's options.
1574
1552
  *
1575
1553
  * Shared by both entries into a walk: the top-of-tree loop below, which calls
1576
- * this once a group and its first command are chosen, and a resumed walk
1554
+ * this once a top-level command is chosen, and a resumed walk
1577
1555
  * (`walk`'s `startPath` branch), which calls it directly on a node the caller
1578
1556
  * already picked by argv. `while` rather than a single step because the tree
1579
1557
  * is two deep today and this does not care. The condition counts the OFFERED
@@ -1581,7 +1559,7 @@ async function askOptions(node) {
1581
1559
  * hidden is treated as the leaf it has become instead of opening a screen
1582
1560
  * holding only "Back".
1583
1561
  */
1584
- async function descendFrom(start, pathNames, env) {
1562
+ async function descendFrom(start, pathNames, env, carried = []) {
1585
1563
  let node = start;
1586
1564
  const path = [...pathNames];
1587
1565
  while (offered(node.subcommands ?? [], env).length > 0) {
@@ -1592,7 +1570,11 @@ async function descendFrom(start, pathNames, env) {
1592
1570
  }
1593
1571
  return {
1594
1572
  node,
1595
- argv: [...path, ...await askOptions(node)]
1573
+ argv: [
1574
+ ...path,
1575
+ ...await askOptions(node),
1576
+ ...carried
1577
+ ]
1596
1578
  };
1597
1579
  }
1598
1580
  /**
@@ -1600,24 +1582,24 @@ async function descendFrom(start, pathNames, env) {
1600
1582
  * a known group.
1601
1583
  *
1602
1584
  * `startPath`, when given, is a path to a group node from `bareGroupStartPath`
1603
- * — `["db"]` for `devtools db` — and lands here instead of at `pickGroup`.
1604
- * There is no group screen above a resumed node (the caller already named it
1605
- * by typing `db`), so BACK on its first subcommand screen has nowhere to
1585
+ * — `["jobs"]` for `devtools jobs` — and lands here instead of at the first
1586
+ * screen. There is no screen above a resumed node (the caller already named
1587
+ * it by typing `jobs`), so BACK on its first subcommand screen has nowhere to
1606
1588
  * return to but out, unlike BACK from the top of the tree, which returns to
1607
- * `pickGroup`.
1589
+ * the first screen. `startFlags` are the flags typed beside it, carried onto
1590
+ * the end of the argv the walk builds.
1608
1591
  */
1609
- async function walk(catalog, env, startPath) {
1592
+ async function walk(catalog, env, startPath, startFlags = []) {
1610
1593
  if (startPath) {
1611
1594
  const start = catalog.findCommand(startPath);
1612
- if (!start) return null;
1613
- const chosen = await descendFrom(start, [...startPath], env);
1595
+ const path = catalog.canonicalPath(startPath);
1596
+ if (!start || !path) return null;
1597
+ const chosen = await descendFrom(start, path, env, startFlags);
1614
1598
  return chosen === BACK ? null : chosen;
1615
1599
  }
1616
1600
  for (;;) {
1617
- const group = await pickGroup(catalog, env);
1618
- if (!group) return null;
1619
- const first = await pickCommand(group, env);
1620
- if (first === BACK) continue;
1601
+ const first = await pickTopLevel(catalog, env);
1602
+ if (!first) return null;
1621
1603
  const chosen = await descendFrom(first, [first.name], env);
1622
1604
  if (chosen === BACK) continue;
1623
1605
  return chosen;
@@ -1638,6 +1620,19 @@ function bareGroupStartPath(catalog, argv) {
1638
1620
  return path;
1639
1621
  }
1640
1622
  /**
1623
+ * The flags in `argv` beside a bare group's `path`, for `runMenu`'s
1624
+ * `startFlags`: what `devtools jobs --kind sync` (or the `cron` alias that
1625
+ * stands for it) keeps once the wizard resumes at `jobs`.
1626
+ */
1627
+ function bareGroupStartFlags(argv, path) {
1628
+ const rest = [...argv];
1629
+ for (const name of path) {
1630
+ const at = rest.indexOf(name);
1631
+ if (at !== -1) rest.splice(at, 1);
1632
+ }
1633
+ return rest;
1634
+ }
1635
+ /**
1641
1636
  * Runs the wizard, returning the `outro()` line its command earned.
1642
1637
  *
1643
1638
  * `dispatch` is the CLI's own argv handler, injected rather than imported so
@@ -1648,7 +1643,8 @@ function bareGroupStartPath(catalog, argv) {
1648
1643
  * from the command line.
1649
1644
  *
1650
1645
  * `options.startPath`, from `bareGroupStartPath`, skips straight to that
1651
- * node's subcommand screen — see `walk`'s `startPath` branch.
1646
+ * node's subcommand screen — see `walk`'s `startPath` branch — and
1647
+ * `options.startFlags` (from `bareGroupStartFlags`) rides along to the end.
1652
1648
  *
1653
1649
  * The deploy tier is NOT asked here any more. `src/launch.ts` resolves it
1654
1650
  * before `cli.ts` — and therefore this module — is even imported, and
@@ -1670,7 +1666,7 @@ function bareGroupStartPath(catalog, argv) {
1670
1666
  */
1671
1667
  async function runMenu(catalog, dispatch, env = probeEnvironment(), options = {}) {
1672
1668
  note(describeEnvironment(env), "This machine");
1673
- const chosen = await walk(catalog, env, options.startPath);
1669
+ const chosen = await walk(catalog, env, options.startPath, options.startFlags);
1674
1670
  if (!chosen) return null;
1675
1671
  beginInvocation(chosen.argv, true);
1676
1672
  const enteredTier = process.env.DEPLOY_ENV ?? "development";
@@ -1681,4 +1677,4 @@ async function runMenu(catalog, dispatch, env = probeEnvironment(), options = {}
1681
1677
  return dispatch(chosen.argv);
1682
1678
  }
1683
1679
  //#endregion
1684
- export { catalog as C, renderHelp as E, unwrap as S, renderCommandList as T, setMenuEnvHook as _, recordResolved as a, explain as b, probeEnvironment as c, nonEmpty as d, resolveDbConnection as f, realEnvEntryDeps as g, enterSessionEnvironment as h, recordEnteredTier as i, flagValue as l, sessionLabel as m, runMenu as n, reproducibleCommand as o, sessionIsLocal as p, beginInvocation as r, describeEnvironment as s, bareGroupStartPath as t, positionals as u, bail as v, helpPath as w, explainError as x, errorMessage as y };
1680
+ export { helpPath as C, catalog as S, renderHelp as T, bail as _, recordEnteredTier as a, explainError as b, describeEnvironment as c, resolveDbConnection as d, sessionIsLocal as f, setMenuEnvHook as g, realEnvEntryDeps as h, beginInvocation as i, probeEnvironment as l, enterSessionEnvironment as m, bareGroupStartPath as n, recordResolved as o, sessionLabel as p, runMenu as r, reproducibleCommand as s, bareGroupStartFlags as t, nonEmpty as u, errorMessage as v, renderCommandList as w, unwrap as x, explain as y };