@devdogsuga/devtools 0.1.50 → 0.1.51-next.527

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,12 +1,11 @@
1
- import { c as YES, d as positionals, i as suppressFailureLog, l as createCatalog, o as JSON_FLAG, s as SCOPES } from "./failure-log-DOt6E3hS.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-C93xRFkN.js";
3
- import { r as findRepoRoot } from "./root-CjPWNkBu.js";
4
- import { c as devdogsugaRootOrNull, n as loadEnv, p as resolveLayout, r as loadEnvLoad } from "./peers-DvUH5VxX.js";
5
- import { c as reportDevtoolsError, f as isNonInteractive } from "./telemetry-C07-ioHm.js";
1
+ import { d as JSON_FLAG, f as SCOPES, g as positionals, m as createCatalog, p as YES, r as findRepoRoot, s as suppressFailureLog } from "./root-CsocfxnK.js";
2
+ import { E as resolveLayout, _ as loadEnvLoad, a as listContainerNames, g as loadEnv, i as foreignStackProjectId, n as containerPrefix, o as readProjectId, p as supabaseCapture, r as foreignStackMessage, t as STACK_API_PORT, x as devdogsugaRootOrNull } from "./supabase-project-BoJKMCxL.js";
3
+ import { c as reportDevtoolsError, f as isNonInteractive } from "./telemetry-BKFDtzXI.js";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { z } from "zod";
6
7
  import { cancel, confirm, isCancel, log, note, select, text } from "@clack/prompts";
7
- import { existsSync } from "node:fs";
8
8
  import { writeFile } from "node:fs/promises";
9
- import { join } from "node:path";
10
9
  import { execFileSync } from "node:child_process";
11
10
  //#region ../cli-core/src/help.ts
12
11
  /**
@@ -479,6 +478,67 @@ const jobsCommand = {
479
478
  ]
480
479
  };
481
480
  //#endregion
481
+ //#region src/moderation/catalog.ts
482
+ const TO = {
483
+ flag: "--to",
484
+ value: "<url>",
485
+ summary: "The receiver's URL. Defaults to the local Edge Function, http://127.0.0.1:54321/functions/v1/platform-moderation."
486
+ };
487
+ const SECRET = {
488
+ flag: "--secret",
489
+ value: "<whsec_...>",
490
+ summary: "The signing secret the receiver verifies with. Defaults to $PLATFORM_MODERATION_SECRET, then the well-known sample secret."
491
+ };
492
+ const moderationCommand = {
493
+ name: "moderation",
494
+ title: "Test a Report & Block receiver",
495
+ summary: "Send signed moderation messages to your app's receiver.",
496
+ hint: "send one action, or run the conformance samples",
497
+ subcommands: [{
498
+ name: "send",
499
+ title: "Send one signed action",
500
+ summary: "Send one signed moderation action to a receiver.",
501
+ hint: "like a moderator's decision, without the platform",
502
+ envFree: true,
503
+ noEnv: true,
504
+ options: [
505
+ TO,
506
+ SECRET,
507
+ {
508
+ flag: "--content-type",
509
+ value: "<type>",
510
+ summary: "For content.* actions. Defaults to session."
511
+ },
512
+ {
513
+ flag: "--content-ref",
514
+ value: "<id>",
515
+ summary: "For content.* actions: your row's id. Defaults to a sample id."
516
+ },
517
+ {
518
+ flag: "--user",
519
+ value: "<uuid>",
520
+ summary: "For user.* actions: the platform user id. Defaults to a sample id."
521
+ },
522
+ {
523
+ flag: "--replay",
524
+ summary: "Send the same message twice. The second must be ignored."
525
+ },
526
+ {
527
+ flag: "--stale",
528
+ summary: "Date the decision a minute in the past, as a late retry would. A receiver that has seen a newer decision must ignore it."
529
+ }
530
+ ]
531
+ }, {
532
+ name: "samples",
533
+ title: "Run the conformance samples",
534
+ summary: "Send the conformance sequence and check each answer.",
535
+ hint: "pass or fail",
536
+ envFree: true,
537
+ noEnv: true,
538
+ options: [TO, SECRET]
539
+ }]
540
+ };
541
+ //#endregion
482
542
  //#region src/roles/catalog.ts
483
543
  /**
484
544
  * `roles`' place in the command tree: declaration only, nothing here runs.
@@ -542,122 +602,6 @@ const oauthCommand = {
542
602
  }
543
603
  ]
544
604
  };
545
- //#endregion
546
- //#region src/passthrough/catalog.ts
547
- const supabaseCommand = {
548
- name: "supabase",
549
- dryRun: "handled",
550
- summary: "Run the Supabase CLI against the session's tier.",
551
- hint: "fills in --db-url / --project-ref; the rest is yours",
552
- surface: "cli-only"
553
- };
554
- const wranglerCommand = {
555
- name: "wrangler",
556
- dryRun: "handled",
557
- summary: "Run Wrangler with the session's env.",
558
- hint: "everything after the name goes to wrangler",
559
- surface: "cli-only"
560
- };
561
- const drizzleKitCommand = {
562
- name: "drizzle-kit",
563
- dryRun: "handled",
564
- summary: "Run drizzle-kit with the session's env.",
565
- hint: "run it from the app that owns the config",
566
- surface: "cli-only"
567
- };
568
- const psqlCommand = {
569
- name: "psql",
570
- dryRun: "handled",
571
- summary: "Run psql against the session's database.",
572
- hint: "connects with the tier's DB_URL unless you say otherwise",
573
- surface: "cli-only"
574
- };
575
- //#endregion
576
- //#region src/preset/catalog.ts
577
- /**
578
- * The Supabase jobs' places in the command tree: declaration only, nothing
579
- * here runs. The handlers live in `commands.ts` beside it.
580
- *
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`).
586
- */
587
- const restartStackCommand = {
588
- name: "restart-stack",
589
- title: "Restart local Supabase",
590
- dryRun: "handled",
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]
626
- };
627
- //#endregion
628
- //#region src/run/catalog.ts
629
- /**
630
- * The three flags every `run` task takes.
631
- *
632
- * None carries a `prompt`, and that is the point rather than an omission.
633
- * `run` opens a multiselect of the apps defining the task, so a wizard that
634
- * asked "every package?", "which filter?" and "which tier?" up front would
635
- * ask the same question three times and let the answers disagree. `--tier`
636
- * is promptless for a different reason than `--filter`/`--all`, though: it is
637
- * not a question `run` asks itself elsewhere (unlike `jobs run`'s own
638
- * `--tier`, which opens a live picker) — it is a scripting input with no
639
- * wizard equivalent, the same category `--json` sits in. `--help` still
640
- * documents all three, which is where someone scripting this will look.
641
- *
642
- * Same reasoning as `VAULT_TARGET` above: the command owns the question, so the
643
- * tree declares the flag and stays quiet.
644
- */
645
- const RUN_OPTIONS = [
646
- {
647
- flag: "--filter",
648
- value: "<pkg>",
649
- summary: "Limit to a package. pnpm's own flag; skips the question."
650
- },
651
- {
652
- flag: "--all",
653
- summary: "Every package, unfiltered, with nothing asked."
654
- },
655
- {
656
- flag: "--tier",
657
- value: "<t>",
658
- summary: "Load a tier's env into the run. CLI-only; never prompted."
659
- }
660
- ];
661
605
  const catalog = createCatalog({
662
606
  usage: "pnpm devtools",
663
607
  commonTasks: [
@@ -685,56 +629,52 @@ const catalog = createCatalog({
685
629
  hint: "pnpm -F <package> run <script>"
686
630
  },
687
631
  oauthCommand,
688
- doctorCommand,
689
- {
690
- name: "run",
691
- dryRun: "handled",
692
- summary: "Run a pnpm workspace task, asking which apps first.",
693
- hint: "deprecated: pnpm -r run <task>",
694
- surface: "cli-only",
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).",
696
- subcommands: [
697
- {
698
- name: "build",
699
- summary: "Compile every package an app needs.",
700
- options: RUN_OPTIONS
701
- },
702
- {
703
- name: "dev",
704
- summary: "Start the development servers.",
705
- options: RUN_OPTIONS
706
- },
707
- {
708
- name: "typecheck",
709
- summary: "Run tsc across the workspace.",
710
- options: RUN_OPTIONS
711
- },
712
- {
713
- name: "lint",
714
- summary: "Run ESLint across the workspace.",
715
- options: RUN_OPTIONS
716
- },
717
- {
718
- name: "lint:fix",
719
- summary: "Run ESLint and write what it can fix.",
720
- options: RUN_OPTIONS
721
- },
722
- {
723
- name: "test",
724
- summary: "Run the unit tests.",
725
- options: RUN_OPTIONS
726
- }
727
- ]
728
- }
632
+ doctorCommand
729
633
  ]
730
634
  },
731
635
  {
732
636
  title: "Supabase",
733
637
  commands: [
734
- restartStackCommand,
735
- newMigrationCommand,
736
- applyMigrationsCommand,
737
- pushConfigCommand
638
+ {
639
+ name: "restart-stack",
640
+ title: "Restart local Supabase",
641
+ dryRun: "handled",
642
+ summary: "Restart the local Supabase stack.",
643
+ hint: "supabase stop, then start; picks up config.toml",
644
+ scope: "machine",
645
+ when: "instance-running"
646
+ },
647
+ {
648
+ name: "new-migration",
649
+ title: "Create a new migration",
650
+ dryRun: "handled",
651
+ summary: "Create an empty migration for an app's schema.",
652
+ hint: "supabase migration new <schema>_<description>",
653
+ scope: "repo",
654
+ options: [{
655
+ flag: "--app",
656
+ value: "<slug>",
657
+ summary: "Whose schema. Asked for when absent."
658
+ }]
659
+ },
660
+ {
661
+ name: "apply-migrations",
662
+ title: "Apply migrations to the database",
663
+ dryRun: "handled",
664
+ summary: "Apply new migrations to the session's database.",
665
+ hint: "supabase db push, then offers types:db",
666
+ scope: "endpoint",
667
+ options: [YES]
668
+ },
669
+ {
670
+ name: "push-config",
671
+ title: "Push Supabase settings",
672
+ dryRun: "handled",
673
+ summary: "Push config.toml to the session's hosted project.",
674
+ hint: "shows the diff first; the local stack reads it on restart",
675
+ scope: "endpoint",
676
+ options: [YES]
677
+ }
738
678
  ]
739
679
  },
740
680
  {
@@ -743,15 +683,43 @@ const catalog = createCatalog({
743
683
  },
744
684
  {
745
685
  title: "Configuration & integrations",
746
- commands: [envCommand, rolesCommand]
686
+ commands: [
687
+ envCommand,
688
+ rolesCommand,
689
+ moderationCommand
690
+ ]
747
691
  },
748
692
  {
749
693
  title: "The real tools",
750
694
  commands: [
751
- supabaseCommand,
752
- wranglerCommand,
753
- drizzleKitCommand,
754
- psqlCommand
695
+ {
696
+ name: "supabase",
697
+ dryRun: "handled",
698
+ summary: "Run the Supabase CLI against the session's tier.",
699
+ hint: "fills in --db-url / --project-ref; the rest is yours",
700
+ surface: "cli-only"
701
+ },
702
+ {
703
+ name: "wrangler",
704
+ dryRun: "handled",
705
+ summary: "Run Wrangler with the session's env.",
706
+ hint: "everything after the name goes to wrangler",
707
+ surface: "cli-only"
708
+ },
709
+ {
710
+ name: "drizzle-kit",
711
+ dryRun: "handled",
712
+ summary: "Run drizzle-kit with the session's env.",
713
+ hint: "run it from the app that owns the config",
714
+ surface: "cli-only"
715
+ },
716
+ {
717
+ name: "psql",
718
+ dryRun: "handled",
719
+ summary: "Run psql against the session's database.",
720
+ hint: "connects with the tier's DB_URL unless you say otherwise",
721
+ surface: "cli-only"
722
+ }
755
723
  ]
756
724
  },
757
725
  {
@@ -761,6 +729,335 @@ const catalog = createCatalog({
761
729
  ]
762
730
  });
763
731
  //#endregion
732
+ //#region src/product/catalog.ts
733
+ /**
734
+ * The command tree devtools shows inside a product repository (one with a
735
+ * `devtools.config.json`): declaration only, nothing here runs. The handlers
736
+ * are in `commands.ts`.
737
+ *
738
+ * Smaller than the platform's tree on purpose. A product has no tiers, no env
739
+ * registry and no workspace of Workers, so everything about those is absent
740
+ * rather than present and refusing.
741
+ */
742
+ const TARGET_OPTIONS = [{
743
+ flag: "--local",
744
+ summary: "Use the Docker stack on this machine."
745
+ }, {
746
+ flag: "--hosted",
747
+ summary: "Use your own hosted project (from .env.tooling)."
748
+ }];
749
+ const productSetupCommand = {
750
+ name: "setup",
751
+ title: "Set up this checkout",
752
+ summary: "Your own database (Docker or a hosted project), the schema, the types and sign in.",
753
+ hint: "run this first",
754
+ envFree: true,
755
+ options: [
756
+ ...TARGET_OPTIONS,
757
+ {
758
+ flag: "--download-types",
759
+ summary: "Fetch the types CI built for your schema instead of generating them."
760
+ },
761
+ {
762
+ flag: "--skip-oauth",
763
+ summary: "Leave \"Sign in with DevDogs\" for later (`devtools oauth`)."
764
+ },
765
+ {
766
+ flag: "--yes",
767
+ summary: "Allow the schema sync to drop things in your database."
768
+ }
769
+ ]
770
+ };
771
+ const productDoctorCommand = {
772
+ name: "doctor",
773
+ title: "Check my setup",
774
+ summary: "Docker, the pinned Supabase CLI, .env, the database and the types.",
775
+ dryRun: "read-only",
776
+ envFree: true
777
+ };
778
+ const productDbCommand = {
779
+ name: "db",
780
+ summary: "Keep your development database and generated types in step with supabase/schemas.",
781
+ envFree: true,
782
+ subcommands: [
783
+ {
784
+ name: "sync",
785
+ title: "Apply the schema to my database",
786
+ summary: "Diff supabase/schemas against what your database has had applied, and apply the difference.",
787
+ envFree: true,
788
+ dryRun: "handled",
789
+ options: [
790
+ ...TARGET_OPTIONS,
791
+ {
792
+ flag: "--rebuild",
793
+ summary: "Reset the local database and start over from the schema, then register sign in again."
794
+ },
795
+ {
796
+ flag: "--yes",
797
+ summary: "Allow changes that drop tables or columns."
798
+ }
799
+ ]
800
+ },
801
+ {
802
+ name: "types",
803
+ title: "Generate the database types",
804
+ summary: "Generate (or download) the types for the current schema. Never committed.",
805
+ envFree: true,
806
+ dryRun: "handled",
807
+ options: [
808
+ ...TARGET_OPTIONS,
809
+ {
810
+ flag: "--download",
811
+ summary: "Download the types CI built for this exact schema, with no database needed."
812
+ },
813
+ {
814
+ flag: "--pack",
815
+ value: "<file>",
816
+ summary: "Also pack the generated output into a .tgz release asset (CI)."
817
+ }
818
+ ]
819
+ },
820
+ {
821
+ name: "hash",
822
+ summary: "Print the schema hash that names the generated types.",
823
+ surface: "cli-only",
824
+ envFree: true,
825
+ dryRun: "read-only"
826
+ }
827
+ ]
828
+ };
829
+ const productDevDataCommand = {
830
+ name: "dev-data",
831
+ title: "Fill my database with sample data",
832
+ summary: "Run the product's dev-data SQL for the member who signed in, in one transaction.",
833
+ hint: "after your first sign in",
834
+ envFree: true,
835
+ dryRun: "handled",
836
+ options: [...TARGET_OPTIONS, {
837
+ flag: "--member",
838
+ value: "<email|id>",
839
+ summary: "Seed for this user instead of the latest DevDogs sign in."
840
+ }]
841
+ };
842
+ const productCatalog = createCatalog({
843
+ usage: "devtools",
844
+ commonTasks: [
845
+ ["setup", "Prepare this checkout"],
846
+ ["db sync", "Apply supabase/schemas to your database"],
847
+ ["db types --download", "Get the generated types without a database"],
848
+ ["dev-data", "Fill your database with sample data"],
849
+ ["doctor", "Check the setup"]
850
+ ],
851
+ groups: [
852
+ {
853
+ title: "Get started",
854
+ commands: [
855
+ productSetupCommand,
856
+ productDoctorCommand,
857
+ oauthCommand
858
+ ]
859
+ },
860
+ {
861
+ title: "Database",
862
+ commands: [productDbCommand, productDevDataCommand]
863
+ },
864
+ {
865
+ title: "Report & Block",
866
+ commands: [moderationCommand]
867
+ },
868
+ {
869
+ title: "The real tools",
870
+ commands: [{
871
+ name: "supabase",
872
+ summary: "The Supabase CLI at the version this product is pinned to.",
873
+ hint: "start, status, stop, ...",
874
+ surface: "cli-only",
875
+ envFree: true,
876
+ dryRun: "handled"
877
+ }]
878
+ },
879
+ {
880
+ title: "CI",
881
+ commands: [{
882
+ name: "check",
883
+ summary: "Structural checks over the checkout, for CI.",
884
+ surface: "cli-only",
885
+ envFree: true,
886
+ noEnv: true,
887
+ dryRun: "read-only",
888
+ subcommands: [{
889
+ name: "env-example",
890
+ summary: ".env.example holds public values only: it ships in the app.",
891
+ surface: "cli-only",
892
+ envFree: true,
893
+ noEnv: true
894
+ }]
895
+ }, completionsCommand]
896
+ }
897
+ ]
898
+ });
899
+ //#endregion
900
+ //#region src/active.ts
901
+ let product = false;
902
+ /** Switches to the product tree. Called by `launch()`; tests reset it. */
903
+ function setProductMode(on) {
904
+ product = on;
905
+ }
906
+ function isProductMode() {
907
+ return product;
908
+ }
909
+ function activeCatalog() {
910
+ return product ? productCatalog : catalog;
911
+ }
912
+ //#endregion
913
+ //#region src/product/config.ts
914
+ /**
915
+ * `devtools.config.json`: what makes a repository a product devtools knows how
916
+ * to set up (DogDays, DogPack, and any future one).
917
+ *
918
+ * The file sits at the repo root. Its presence is what switches devtools into
919
+ * product mode: a smaller command set (`setup`, `db sync`, `db types`,
920
+ * `dev-data`, `oauth`) that needs no tiers, no env registry and no workspace.
921
+ * The platform repository has no such file and keeps the full CLI.
922
+ *
923
+ * Nothing in the file is secret. Secrets live in `.env.tooling`, which is
924
+ * git-ignored; the public configuration the app reads at run time lives in
925
+ * `.env`, written from `.env.example`.
926
+ */
927
+ const CONFIG_FILE = "devtools.config.json";
928
+ const relativePath = z.string().min(1).refine((p) => !p.startsWith("/") && !p.includes(".."), { message: "must be a relative path inside the repository" });
929
+ const typesSchema = z.discriminatedUnion("kind", [z.object({
930
+ kind: z.literal("typescript"),
931
+ /** The generated `.ts` file. Git-ignored. */
932
+ out: relativePath
933
+ }), z.object({
934
+ kind: z.literal("supadart"),
935
+ /** The directory supadart writes models to. Git-ignored. */
936
+ out: relativePath,
937
+ /** supadart's own config, whose `output` must match `out`. */
938
+ config: relativePath.default("supadart.yaml")
939
+ })]);
940
+ const productConfigSchema = z.object({
941
+ $schema: z.string().optional(),
942
+ /** Lowercase id: `dogdays`. Names the generated-types assets. */
943
+ product: z.string().regex(/^[a-z][a-z0-9-]*$/),
944
+ /** Display name: `DogDays`. */
945
+ name: z.string().min(1),
946
+ supabase: z.object({
947
+ /** The directory holding `config.toml` and `schemas/`. */
948
+ dir: relativePath.default("supabase"),
949
+ /** The Supabase CLI version this product is built with. Part of the types hash. */
950
+ version: z.string().regex(/^\d+\.\d+\.\d+$/, "an exact version, 2.117.0"),
951
+ /** Schemas that hold the product's tables, for type generation. */
952
+ schemas: z.array(z.string().min(1)).min(1)
953
+ }),
954
+ env: z.object({
955
+ /** The committed manifest: public keys only. */
956
+ example: relativePath.default(".env.example"),
957
+ /** What the app reads. Git-ignored. */
958
+ file: relativePath.default(".env"),
959
+ /** Secrets the tooling needs (`DB_URL`, `SECRET_KEY`). Git-ignored. */
960
+ tooling: relativePath.default(".env.tooling"),
961
+ /** Which `.env` keys receive the project's URL and publishable key. */
962
+ keys: z.object({
963
+ url: z.string().default("SUPABASE_URL"),
964
+ publishableKey: z.string().default("SUPABASE_PUBLISHABLE_KEY")
965
+ }).default({
966
+ url: "SUPABASE_URL",
967
+ publishableKey: "SUPABASE_PUBLISHABLE_KEY"
968
+ })
969
+ }).default({
970
+ example: ".env.example",
971
+ file: ".env",
972
+ tooling: ".env.tooling",
973
+ keys: {
974
+ url: "SUPABASE_URL",
975
+ publishableKey: "SUPABASE_PUBLISHABLE_KEY"
976
+ }
977
+ }),
978
+ types: typesSchema,
979
+ /** Where CI publishes generated types, for `db types --download`. */
980
+ generatedTypes: z.object({
981
+ /** `DevDogsUGA/DogDays`. */
982
+ repo: z.string().regex(/^[\w.-]+\/[\w.-]+$/),
983
+ tag: z.string().default("generated-types")
984
+ }).optional(),
985
+ oauth: z.object({
986
+ /**
987
+ * App return addresses to allow on the project (`dev.dogpack://login-callback`,
988
+ * `http://localhost:3000/auth/callback`). Added to `uri_allow_list` on a
989
+ * hosted project; for local Docker they belong in `supabase/config.toml`.
990
+ */
991
+ redirectUris: z.array(z.string()).default([]) }).default({ redirectUris: [] }),
992
+ /** `dev-data`: a SQL file that fills a development database. */
993
+ devData: z.object({
994
+ file: relativePath,
995
+ /** One line for `--help`. */
996
+ summary: z.string().optional()
997
+ }).optional(),
998
+ /** What `setup` prints as the last step. */
999
+ runHint: z.string().optional(),
1000
+ /** Databases `dev-data` must never touch. */
1001
+ production: z.object({
1002
+ /** Hosted project refs and vanity names: `dogpack`, `abcdefghij`. */
1003
+ projects: z.array(z.string()).default([]) }).default({ projects: [] })
1004
+ });
1005
+ var ProductConfigError = class extends Error {
1006
+ file;
1007
+ constructor(message, file) {
1008
+ super(message);
1009
+ this.file = file;
1010
+ this.name = "ProductConfigError";
1011
+ }
1012
+ };
1013
+ /** The directory holding `devtools.config.json`, searching upward from `from`. */
1014
+ function findProductRoot(from = process.cwd()) {
1015
+ let dir = resolve(from);
1016
+ for (;;) {
1017
+ if (existsSync(join(dir, "devtools.config.json"))) return dir;
1018
+ const parent = dirname(dir);
1019
+ if (parent === dir) return null;
1020
+ dir = parent;
1021
+ }
1022
+ }
1023
+ function parseProductConfig(text, file) {
1024
+ let json;
1025
+ try {
1026
+ json = JSON.parse(text);
1027
+ } catch (err) {
1028
+ throw new ProductConfigError(`${file} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`, file);
1029
+ }
1030
+ const result = productConfigSchema.safeParse(json);
1031
+ if (!result.success) throw new ProductConfigError(`${file} is not valid:\n${result.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n")}`, file);
1032
+ return result.data;
1033
+ }
1034
+ function loadProductConfig(root) {
1035
+ const file = join(root, CONFIG_FILE);
1036
+ return parseProductConfig(readFileSync(file, "utf8"), file);
1037
+ }
1038
+ function productAt(root) {
1039
+ const config = loadProductConfig(root);
1040
+ const supabaseDir = join(root, config.supabase.dir);
1041
+ return {
1042
+ root,
1043
+ config,
1044
+ paths: {
1045
+ supabaseDir,
1046
+ schemasDir: join(supabaseDir, "schemas"),
1047
+ migrationsDir: join(supabaseDir, "migrations"),
1048
+ envExample: join(root, config.env.example),
1049
+ envFile: join(root, config.env.file),
1050
+ toolingFile: join(root, config.env.tooling),
1051
+ stateFile: join(root, ".devtools", "state.json")
1052
+ }
1053
+ };
1054
+ }
1055
+ /** The product the working directory belongs to, or null outside one. */
1056
+ function currentProduct(from = process.cwd()) {
1057
+ const root = findProductRoot(from);
1058
+ return root === null ? null : productAt(root);
1059
+ }
1060
+ //#endregion
764
1061
  //#region ../cli-core/src/db/generated-env.ts
765
1062
  /**
766
1063
  * Regenerating `.env.generated` from a Supabase stack that is ALREADY
@@ -772,7 +1069,7 @@ const catalog = createCatalog({
772
1069
  * except `.env.generated` (the connection block `startLocalStack`,
773
1070
  * the CLI's `db/stack.ts`, writes) only ever lands in the workspace that actually ran
774
1071
  * it. Left alone, this checkout sees port 54321 listening with the file
775
- * missing, and `@devdogsuga/env/load`'s `selectEnvFiles` either warns (an
1072
+ * missing, and `env-runtime/load`'s `selectEnvFiles` either warns (an
776
1073
  * unqualified session) or throws `LocalStackOfflineError` (an explicit
777
1074
  * `development:local` one) — both of which used to mean the contributor
778
1075
  * ran `supabase status -o env > .env.generated` by hand.
@@ -793,9 +1090,9 @@ const catalog = createCatalog({
793
1090
  * otherwise just repeat "nothing is listening" advice for a port that very
794
1091
  * much has something listening on it.
795
1092
  */
796
- /** The file this module writes and checks — see `@devdogsuga/env/load`'s
1093
+ /** The file this module writes and checks — see `env-runtime/load`'s
797
1094
  * `GENERATED_FILE`, duplicated as a literal rather than imported so this
798
- * module never has to load the `@devdogsuga/env` peer just for one
1095
+ * module never has to load the `@devdogsuga/sdk/env` peer just for one
799
1096
  * constant. Keep in sync if that constant's value ever changes. */
800
1097
  const GENERATED_FILE = ".env.generated";
801
1098
  /**
@@ -846,7 +1143,7 @@ async function ensureGeneratedEnvFile(tier, devDatabase, deps) {
846
1143
  * The production wiring for `EnsureGeneratedEnvDeps`, shared by
847
1144
  * `../env-entry.ts` (via `realEnvEntryDeps`) and `../stack.ts`'s `db
848
1145
  * status`. `probeLocalStack` is passed in rather than resolved here because
849
- * both callers already hold the loaded `@devdogsuga/env/load` peer their
1146
+ * both callers already hold the loaded `env-runtime/load` peer their
850
1147
  * own dispatch needs anyway — resolving it again here would mean two
851
1148
  * different copies of the same optional peer in play.
852
1149
  */
@@ -1681,4 +1978,4 @@ async function runMenu(catalog, dispatch, env = probeEnvironment(), options = {}
1681
1978
  return dispatch(chosen.argv);
1682
1979
  }
1683
1980
  //#endregion
1684
- 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 };
1981
+ export { renderCommandList as A, currentProduct as C, setProductMode as D, isProductMode as E, catalog as O, ProductConfigError as S, activeCatalog 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, renderHelp as j, helpPath as k, 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, findProductRoot as w, unwrap as x, explain as y };