@stardeck-customer-apps/compose 0.2.0 → 0.3.0

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/dist/index.d.ts CHANGED
@@ -8,6 +8,37 @@ type ConnectedStore = {
8
8
  accessLevel: "read" | "write" | "admin";
9
9
  };
10
10
 
11
+ /**
12
+ * Repo-relative inputs file: the platform's two database reads, as JSON.
13
+ *
14
+ * `@stardeck-customer-apps/compose` runs inside a customer app with no platform
15
+ * database, so the two facts the rail reads from one — the data stores connected
16
+ * to the app and each Module's install row — reach it through this file. The
17
+ * platform writes it (`writeComposeInputs`), compose reads it
18
+ * (`readComposeInputs`); the schema below is the single definition both sides
19
+ * parse against, so a drift on the writing side fails in lib rather than as an
20
+ * unexplained empty datastore binding inside a fork's build.
21
+ */
22
+ declare const COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
23
+ declare const composeInputsSchema: z.ZodObject<{
24
+ connectedStores: z.ZodDefault<z.ZodArray<z.ZodObject<{
25
+ storeId: z.ZodString;
26
+ slug: z.ZodString;
27
+ bindingKey: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
28
+ accessLevel: z.ZodEnum<{
29
+ read: "read";
30
+ write: "write";
31
+ admin: "admin";
32
+ }>;
33
+ }, z.core.$strict>>>;
34
+ installs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
35
+ installedVersion: z.ZodString;
36
+ datastoreBindings: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
37
+ }, z.core.$strict>>>;
38
+ }, z.core.$strict>;
39
+ type ComposeInputsFile = z.infer<typeof composeInputsSchema>;
40
+ type ComposeInputsInstall = ComposeInputsFile["installs"][string];
41
+
11
42
  interface ComposeOptions {
12
43
  /** Repo root of the customer app (the directory holding `apps/web`). */
13
44
  cwd: string;
@@ -30,28 +61,10 @@ interface ComposeResult {
30
61
  /** Null when a partial (`--enabled`) compose left the committed list untouched. */
31
62
  gitignoreEntries: number | null;
32
63
  }
33
- /** Repo-relative inputs file: the platform's two database reads, as JSON. */
34
- declare const COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
35
- declare const composeInputsSchema: z.ZodObject<{
36
- connectedStores: z.ZodDefault<z.ZodArray<z.ZodObject<{
37
- storeId: z.ZodString;
38
- slug: z.ZodString;
39
- bindingKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
- accessLevel: z.ZodEnum<{
41
- read: "read";
42
- write: "write";
43
- admin: "admin";
44
- }>;
45
- }, z.core.$strict>>>;
46
- installs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
47
- installedVersion: z.ZodString;
48
- datastoreBindings: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
49
- }, z.core.$strict>>>;
50
- }, z.core.$strict>;
51
- type ComposeInstall = z.infer<typeof composeInputsSchema>["installs"][string];
64
+
52
65
  interface ComposeInputs {
53
66
  connectedStores: ConnectedStore[];
54
- installs: Record<string, ComposeInstall>;
67
+ installs: Record<string, ComposeInputsInstall>;
55
68
  }
56
69
  /**
57
70
  * The platform rail resolves data-store bindings and installed versions from
package/dist/index.js CHANGED
@@ -2067,6 +2067,29 @@ var initialModuleCompositionSchema = import_zod6.z.object({
2067
2067
  });
2068
2068
  var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
2069
2069
 
2070
+ // ../../packages/lib/src/shared/compose-inputs.ts
2071
+ var import_zod7 = require("zod");
2072
+ var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
2073
+ var composeInputsSchema = import_zod7.z.strictObject({
2074
+ connectedStores: import_zod7.z.array(
2075
+ import_zod7.z.strictObject({
2076
+ storeId: import_zod7.z.string().min(1),
2077
+ slug: import_zod7.z.string().min(1),
2078
+ // A row with an empty binding key must not fail a deploy: the rail
2079
+ // treats falsy as "no binding key", so normalise rather than reject.
2080
+ bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
2081
+ accessLevel: import_zod7.z.enum(["read", "write", "admin"])
2082
+ })
2083
+ ).default([]),
2084
+ installs: import_zod7.z.record(
2085
+ import_zod7.z.string(),
2086
+ import_zod7.z.strictObject({
2087
+ installedVersion: import_zod7.z.string().min(1),
2088
+ datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
2089
+ })
2090
+ ).default({})
2091
+ });
2092
+
2070
2093
  // ../../packages/lib/src/shared/module-enablement.ts
2071
2094
  function isModuleEnabledByList(raw, name, kindOf) {
2072
2095
  if (!raw) return true;
@@ -3568,6 +3591,27 @@ function dirnamePosix(filePath) {
3568
3591
  return idx <= 0 ? "." : filePath.slice(0, idx);
3569
3592
  }
3570
3593
 
3594
+ // ../../packages/lib/src/server/module-rail/compose-inputs.ts
3595
+ async function buildComposeInputs(input) {
3596
+ const { db, projectId, deps } = input;
3597
+ const connectedStores = await deps.listDatabaseStoresForProject(db, projectId);
3598
+ const installRows = await Promise.all(
3599
+ input.moduleNames.map(async (moduleName) => ({
3600
+ moduleName,
3601
+ install: await deps.getModuleInstall(db, projectId, moduleName)
3602
+ }))
3603
+ );
3604
+ const installs = {};
3605
+ for (const { moduleName, install } of installRows) {
3606
+ if (!install) continue;
3607
+ installs[moduleName] = {
3608
+ installedVersion: install.installedVersion,
3609
+ datastoreBindings: install.datastoreBindings ?? null
3610
+ };
3611
+ }
3612
+ return composeInputsSchema.parse({ connectedStores, installs });
3613
+ }
3614
+
3571
3615
  // ../../packages/lib/src/shared/data-store-manifest.ts
3572
3616
  function dataStoreSlug(name) {
3573
3617
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
@@ -3764,162 +3808,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
3764
3808
  }
3765
3809
 
3766
3810
  // ../../packages/lib/src/shared/schema-ops.ts
3767
- var import_zod7 = require("zod");
3768
- var columnSpecSchema = import_zod7.z.object({
3769
- name: import_zod7.z.string().min(1).max(63),
3770
- pgType: import_zod7.z.string().min(1),
3771
- nullable: import_zod7.z.boolean(),
3811
+ var import_zod8 = require("zod");
3812
+ var columnSpecSchema = import_zod8.z.object({
3813
+ name: import_zod8.z.string().min(1).max(63),
3814
+ pgType: import_zod8.z.string().min(1),
3815
+ nullable: import_zod8.z.boolean(),
3772
3816
  /**
3773
3817
  * A SQL expression used as the column default, as a raw string (e.g. `'t'`,
3774
3818
  * `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
3775
3819
  * NOT a JS literal — must already be a valid SQL expression.
3776
3820
  */
3777
- default: import_zod7.z.string().nullable().optional(),
3778
- fieldType: import_zod7.z.string().optional(),
3779
- fieldConfig: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).optional()
3821
+ default: import_zod8.z.string().nullable().optional(),
3822
+ fieldType: import_zod8.z.string().optional(),
3823
+ fieldConfig: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
3780
3824
  });
3781
- var referentialActionSchema = import_zod7.z.enum([
3825
+ var referentialActionSchema = import_zod8.z.enum([
3782
3826
  "cascade",
3783
3827
  "set null",
3784
3828
  "set default",
3785
3829
  "restrict",
3786
3830
  "no action"
3787
3831
  ]);
3788
- var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3832
+ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
3789
3833
  // Tables
3790
- import_zod7.z.object({
3791
- type: import_zod7.z.literal("createTable"),
3792
- table: import_zod7.z.string().min(1).max(63),
3793
- columns: import_zod7.z.array(columnSpecSchema).min(1),
3794
- primaryKey: import_zod7.z.array(import_zod7.z.string()).optional(),
3795
- ifNotExists: import_zod7.z.boolean().optional()
3834
+ import_zod8.z.object({
3835
+ type: import_zod8.z.literal("createTable"),
3836
+ table: import_zod8.z.string().min(1).max(63),
3837
+ columns: import_zod8.z.array(columnSpecSchema).min(1),
3838
+ primaryKey: import_zod8.z.array(import_zod8.z.string()).optional(),
3839
+ ifNotExists: import_zod8.z.boolean().optional()
3796
3840
  }),
3797
- import_zod7.z.object({
3798
- type: import_zod7.z.literal("dropTable"),
3799
- table: import_zod7.z.string().min(1).max(63),
3800
- cascade: import_zod7.z.boolean().optional(),
3801
- ifExists: import_zod7.z.boolean().optional()
3841
+ import_zod8.z.object({
3842
+ type: import_zod8.z.literal("dropTable"),
3843
+ table: import_zod8.z.string().min(1).max(63),
3844
+ cascade: import_zod8.z.boolean().optional(),
3845
+ ifExists: import_zod8.z.boolean().optional()
3802
3846
  }),
3803
- import_zod7.z.object({
3804
- type: import_zod7.z.literal("renameTable"),
3805
- from: import_zod7.z.string().min(1).max(63),
3806
- to: import_zod7.z.string().min(1).max(63)
3847
+ import_zod8.z.object({
3848
+ type: import_zod8.z.literal("renameTable"),
3849
+ from: import_zod8.z.string().min(1).max(63),
3850
+ to: import_zod8.z.string().min(1).max(63)
3807
3851
  }),
3808
3852
  // Columns
3809
- import_zod7.z.object({
3810
- type: import_zod7.z.literal("addColumn"),
3811
- table: import_zod7.z.string().min(1).max(63),
3853
+ import_zod8.z.object({
3854
+ type: import_zod8.z.literal("addColumn"),
3855
+ table: import_zod8.z.string().min(1).max(63),
3812
3856
  column: columnSpecSchema,
3813
- ifNotExists: import_zod7.z.boolean().optional()
3857
+ ifNotExists: import_zod8.z.boolean().optional()
3814
3858
  }),
3815
- import_zod7.z.object({
3816
- type: import_zod7.z.literal("dropColumn"),
3817
- table: import_zod7.z.string().min(1).max(63),
3818
- column: import_zod7.z.string().min(1).max(63),
3819
- cascade: import_zod7.z.boolean().optional(),
3820
- ifExists: import_zod7.z.boolean().optional()
3859
+ import_zod8.z.object({
3860
+ type: import_zod8.z.literal("dropColumn"),
3861
+ table: import_zod8.z.string().min(1).max(63),
3862
+ column: import_zod8.z.string().min(1).max(63),
3863
+ cascade: import_zod8.z.boolean().optional(),
3864
+ ifExists: import_zod8.z.boolean().optional()
3821
3865
  }),
3822
- import_zod7.z.object({
3823
- type: import_zod7.z.literal("renameColumn"),
3824
- table: import_zod7.z.string().min(1).max(63),
3825
- from: import_zod7.z.string().min(1).max(63),
3826
- to: import_zod7.z.string().min(1).max(63)
3866
+ import_zod8.z.object({
3867
+ type: import_zod8.z.literal("renameColumn"),
3868
+ table: import_zod8.z.string().min(1).max(63),
3869
+ from: import_zod8.z.string().min(1).max(63),
3870
+ to: import_zod8.z.string().min(1).max(63)
3827
3871
  }),
3828
- import_zod7.z.object({
3829
- type: import_zod7.z.literal("alterColumnType"),
3830
- table: import_zod7.z.string().min(1).max(63),
3831
- column: import_zod7.z.string().min(1).max(63),
3832
- newType: import_zod7.z.string().min(1),
3872
+ import_zod8.z.object({
3873
+ type: import_zod8.z.literal("alterColumnType"),
3874
+ table: import_zod8.z.string().min(1).max(63),
3875
+ column: import_zod8.z.string().min(1).max(63),
3876
+ newType: import_zod8.z.string().min(1),
3833
3877
  /** Optional USING expression for potentially-lossy casts */
3834
- using: import_zod7.z.string().optional()
3878
+ using: import_zod8.z.string().optional()
3835
3879
  }),
3836
- import_zod7.z.object({
3837
- type: import_zod7.z.literal("alterColumnNullable"),
3838
- table: import_zod7.z.string().min(1).max(63),
3839
- column: import_zod7.z.string().min(1).max(63),
3840
- nullable: import_zod7.z.boolean()
3880
+ import_zod8.z.object({
3881
+ type: import_zod8.z.literal("alterColumnNullable"),
3882
+ table: import_zod8.z.string().min(1).max(63),
3883
+ column: import_zod8.z.string().min(1).max(63),
3884
+ nullable: import_zod8.z.boolean()
3841
3885
  }),
3842
- import_zod7.z.object({
3843
- type: import_zod7.z.literal("alterColumnDefault"),
3844
- table: import_zod7.z.string().min(1).max(63),
3845
- column: import_zod7.z.string().min(1).max(63),
3886
+ import_zod8.z.object({
3887
+ type: import_zod8.z.literal("alterColumnDefault"),
3888
+ table: import_zod8.z.string().min(1).max(63),
3889
+ column: import_zod8.z.string().min(1).max(63),
3846
3890
  /** null means drop the default; otherwise a SQL expression string. */
3847
- default: import_zod7.z.string().nullable()
3891
+ default: import_zod8.z.string().nullable()
3848
3892
  }),
3849
3893
  // Indexes
3850
- import_zod7.z.object({
3851
- type: import_zod7.z.literal("addIndex"),
3852
- table: import_zod7.z.string().min(1).max(63),
3853
- name: import_zod7.z.string().min(1).max(63),
3854
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3855
- unique: import_zod7.z.boolean().optional(),
3894
+ import_zod8.z.object({
3895
+ type: import_zod8.z.literal("addIndex"),
3896
+ table: import_zod8.z.string().min(1).max(63),
3897
+ name: import_zod8.z.string().min(1).max(63),
3898
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3899
+ unique: import_zod8.z.boolean().optional(),
3856
3900
  /** Partial-index WHERE clause, as a raw SQL expression */
3857
- where: import_zod7.z.string().optional(),
3858
- ifNotExists: import_zod7.z.boolean().optional()
3901
+ where: import_zod8.z.string().optional(),
3902
+ ifNotExists: import_zod8.z.boolean().optional()
3859
3903
  }),
3860
- import_zod7.z.object({
3861
- type: import_zod7.z.literal("dropIndex"),
3862
- name: import_zod7.z.string().min(1).max(63),
3863
- cascade: import_zod7.z.boolean().optional(),
3864
- ifExists: import_zod7.z.boolean().optional()
3904
+ import_zod8.z.object({
3905
+ type: import_zod8.z.literal("dropIndex"),
3906
+ name: import_zod8.z.string().min(1).max(63),
3907
+ cascade: import_zod8.z.boolean().optional(),
3908
+ ifExists: import_zod8.z.boolean().optional()
3865
3909
  }),
3866
3910
  // Constraints
3867
- import_zod7.z.object({
3868
- type: import_zod7.z.literal("addForeignKey"),
3869
- table: import_zod7.z.string().min(1).max(63),
3870
- name: import_zod7.z.string().min(1).max(63),
3871
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3872
- refTable: import_zod7.z.string().min(1).max(63),
3873
- refColumns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3911
+ import_zod8.z.object({
3912
+ type: import_zod8.z.literal("addForeignKey"),
3913
+ table: import_zod8.z.string().min(1).max(63),
3914
+ name: import_zod8.z.string().min(1).max(63),
3915
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3916
+ refTable: import_zod8.z.string().min(1).max(63),
3917
+ refColumns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3874
3918
  onDelete: referentialActionSchema.optional(),
3875
3919
  onUpdate: referentialActionSchema.optional()
3876
3920
  }),
3877
- import_zod7.z.object({
3878
- type: import_zod7.z.literal("addUniqueConstraint"),
3879
- table: import_zod7.z.string().min(1).max(63),
3880
- name: import_zod7.z.string().min(1).max(63),
3881
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1)
3921
+ import_zod8.z.object({
3922
+ type: import_zod8.z.literal("addUniqueConstraint"),
3923
+ table: import_zod8.z.string().min(1).max(63),
3924
+ name: import_zod8.z.string().min(1).max(63),
3925
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
3882
3926
  }),
3883
- import_zod7.z.object({
3884
- type: import_zod7.z.literal("addCheckConstraint"),
3885
- table: import_zod7.z.string().min(1).max(63),
3886
- name: import_zod7.z.string().min(1).max(63),
3887
- expression: import_zod7.z.string().min(1)
3927
+ import_zod8.z.object({
3928
+ type: import_zod8.z.literal("addCheckConstraint"),
3929
+ table: import_zod8.z.string().min(1).max(63),
3930
+ name: import_zod8.z.string().min(1).max(63),
3931
+ expression: import_zod8.z.string().min(1)
3888
3932
  }),
3889
- import_zod7.z.object({
3890
- type: import_zod7.z.literal("dropConstraint"),
3891
- table: import_zod7.z.string().min(1).max(63),
3892
- name: import_zod7.z.string().min(1).max(63),
3893
- cascade: import_zod7.z.boolean().optional(),
3894
- ifExists: import_zod7.z.boolean().optional()
3933
+ import_zod8.z.object({
3934
+ type: import_zod8.z.literal("dropConstraint"),
3935
+ table: import_zod8.z.string().min(1).max(63),
3936
+ name: import_zod8.z.string().min(1).max(63),
3937
+ cascade: import_zod8.z.boolean().optional(),
3938
+ ifExists: import_zod8.z.boolean().optional()
3895
3939
  }),
3896
3940
  // Enum types
3897
- import_zod7.z.object({
3898
- type: import_zod7.z.literal("createEnum"),
3899
- name: import_zod7.z.string().min(1).max(63),
3900
- values: import_zod7.z.array(import_zod7.z.string().min(1)).min(1)
3941
+ import_zod8.z.object({
3942
+ type: import_zod8.z.literal("createEnum"),
3943
+ name: import_zod8.z.string().min(1).max(63),
3944
+ values: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
3901
3945
  }),
3902
- import_zod7.z.object({
3903
- type: import_zod7.z.literal("alterEnumAddValue"),
3904
- name: import_zod7.z.string().min(1).max(63),
3905
- value: import_zod7.z.string().min(1),
3946
+ import_zod8.z.object({
3947
+ type: import_zod8.z.literal("alterEnumAddValue"),
3948
+ name: import_zod8.z.string().min(1).max(63),
3949
+ value: import_zod8.z.string().min(1),
3906
3950
  /** Position the new value before an existing one (mutually exclusive with `after`) */
3907
- before: import_zod7.z.string().optional(),
3951
+ before: import_zod8.z.string().optional(),
3908
3952
  /** Position the new value after an existing one (mutually exclusive with `before`) */
3909
- after: import_zod7.z.string().optional(),
3910
- ifNotExists: import_zod7.z.boolean().optional()
3953
+ after: import_zod8.z.string().optional(),
3954
+ ifNotExists: import_zod8.z.boolean().optional()
3911
3955
  }),
3912
- import_zod7.z.object({
3913
- type: import_zod7.z.literal("dropEnum"),
3914
- name: import_zod7.z.string().min(1).max(63),
3915
- cascade: import_zod7.z.boolean().optional(),
3916
- ifExists: import_zod7.z.boolean().optional()
3956
+ import_zod8.z.object({
3957
+ type: import_zod8.z.literal("dropEnum"),
3958
+ name: import_zod8.z.string().min(1).max(63),
3959
+ cascade: import_zod8.z.boolean().optional(),
3960
+ ifExists: import_zod8.z.boolean().optional()
3917
3961
  }),
3918
3962
  // Extensions
3919
- import_zod7.z.object({
3920
- type: import_zod7.z.literal("createExtension"),
3921
- name: import_zod7.z.string().min(1),
3922
- ifNotExists: import_zod7.z.boolean().optional()
3963
+ import_zod8.z.object({
3964
+ type: import_zod8.z.literal("createExtension"),
3965
+ name: import_zod8.z.string().min(1),
3966
+ ifNotExists: import_zod8.z.boolean().optional()
3923
3967
  }),
3924
3968
  // Data operations
3925
3969
  /**
@@ -3927,26 +3971,26 @@ var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3927
3971
  * destructive — requires explicit confirm to replay on prod. The
3928
3972
  * `description` is what the agent/user sees at confirm time.
3929
3973
  */
3930
- import_zod7.z.object({
3931
- type: import_zod7.z.literal("backfill"),
3932
- description: import_zod7.z.string().min(1),
3933
- sql: import_zod7.z.string().min(1)
3974
+ import_zod8.z.object({
3975
+ type: import_zod8.z.literal("backfill"),
3976
+ description: import_zod8.z.string().min(1),
3977
+ sql: import_zod8.z.string().min(1)
3934
3978
  }),
3935
3979
  /**
3936
3980
  * Escape hatch for DDL the structured ops can't express. `destructive`
3937
3981
  * forces classification — use false only for genuinely additive-only SQL
3938
3982
  * (e.g. CREATE EXTENSION variants the structured op doesn't cover).
3939
3983
  */
3940
- import_zod7.z.object({
3941
- type: import_zod7.z.literal("rawSql"),
3942
- description: import_zod7.z.string().min(1),
3943
- sql: import_zod7.z.string().min(1),
3944
- destructive: import_zod7.z.boolean()
3984
+ import_zod8.z.object({
3985
+ type: import_zod8.z.literal("rawSql"),
3986
+ description: import_zod8.z.string().min(1),
3987
+ sql: import_zod8.z.string().min(1),
3988
+ destructive: import_zod8.z.boolean()
3945
3989
  })
3946
3990
  ]);
3947
3991
 
3948
3992
  // ../../packages/lib/src/server/module-rail/ops.ts
3949
- var import_zod8 = require("zod");
3993
+ var import_zod9 = require("zod");
3950
3994
 
3951
3995
  // ../../packages/lib/src/server/module-rail/reconcile.ts
3952
3996
  var SANDBOX_BATCH_SIZE = 200;
@@ -3987,8 +4031,8 @@ function countedCompositionContext(context) {
3987
4031
  }
3988
4032
  };
3989
4033
  }
3990
- async function listPresentModuleNames(context) {
3991
- const entries = await listImmediateEntries(context.sandbox, MODULES_SANDBOX_DIR, {
4034
+ async function listPresentModuleNames(sandbox) {
4035
+ const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
3992
4036
  optional: true,
3993
4037
  label: "modules"
3994
4038
  });
@@ -4192,24 +4236,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
4192
4236
  }
4193
4237
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
4194
4238
  }
4195
- async function writeSandboxFileAtomic(context, path3, content) {
4239
+ async function writeSandboxFileAtomic(target, path3, content) {
4196
4240
  const dir = dirnamePosix(path3);
4197
- const tmp = `${path3}.tmp.${context.runId}`;
4241
+ const tmp = `${path3}.tmp.${target.runId}`;
4198
4242
  const b64 = Buffer.from(content, "utf8").toString("base64");
4199
4243
  const cmd = [
4200
4244
  `mkdir -p ${shellQuote(dir)}`,
4201
4245
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
4202
4246
  `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
4203
4247
  ].join(" && ");
4204
- const result = await context.sandbox.exec(cmd, { raiseOnError: false });
4248
+ const result = await target.sandbox.exec(cmd, { raiseOnError: false });
4205
4249
  if (result.exitCode !== 0) {
4206
- const cleanup = await context.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4250
+ const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4207
4251
  raiseOnError: false
4208
4252
  });
4209
4253
  if (cleanup.exitCode !== 0) {
4210
4254
  const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
4211
4255
  console.log(
4212
- `[ModuleRail] runId=${context.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
4256
+ `[ModuleRail] runId=${target.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
4213
4257
  );
4214
4258
  }
4215
4259
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
@@ -4267,6 +4311,40 @@ async function deleteSandboxFile(context, path3) {
4267
4311
  );
4268
4312
  }
4269
4313
  }
4314
+ async function writeComposeInputs(target) {
4315
+ const ignored = await target.sandbox.exec(
4316
+ `git check-ignore -q ${shellQuote(COMPOSE_INPUTS_PATH)}`,
4317
+ { raiseOnError: false }
4318
+ );
4319
+ if (ignored.exitCode === 1) {
4320
+ console.log(
4321
+ `[ComposeInputs] project=${target.projectId} skipped: ${COMPOSE_INPUTS_PATH} is not ignored in this checkout`
4322
+ );
4323
+ return false;
4324
+ }
4325
+ if (ignored.exitCode !== 0) {
4326
+ const detail = typeof ignored.error === "string" && ignored.error.trim() || ignored.output.trim() || "(no output)";
4327
+ throw new Error(
4328
+ `[ComposeInputs] git check-ignore failed in this checkout (exit ${ignored.exitCode}): ${redactSecrets(detail)}`
4329
+ );
4330
+ }
4331
+ const moduleNames = await listPresentModuleNames(target.sandbox);
4332
+ const inputs = await buildComposeInputs({
4333
+ db: target.db,
4334
+ projectId: target.projectId,
4335
+ deps: target.deps,
4336
+ moduleNames
4337
+ });
4338
+ const content = `${JSON.stringify(inputs, null, 2)}
4339
+ `;
4340
+ const existing = await target.sandbox.fileExists(COMPOSE_INPUTS_PATH) ? await target.sandbox.readFile(COMPOSE_INPUTS_PATH) : null;
4341
+ const written = existing === null || existing.trimEnd() !== content.trimEnd();
4342
+ if (written) await writeSandboxFileAtomic(target, COMPOSE_INPUTS_PATH, content);
4343
+ console.log(
4344
+ `[ComposeInputs] project=${target.projectId} stores=${inputs.connectedStores.length} installs=${Object.keys(inputs.installs).length} written=${written}`
4345
+ );
4346
+ return written;
4347
+ }
4270
4348
  async function applyCompositionArtifactMutations(context, plan, existingFiles) {
4271
4349
  const appliedWrites = [];
4272
4350
  const appliedDeletes = [];
@@ -4902,7 +4980,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
4902
4980
  context = counted.context;
4903
4981
  const deps = context.deps;
4904
4982
  if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
4905
- const presentRaw = await listPresentModuleNames(context);
4983
+ const presentRaw = await listPresentModuleNames(context.sandbox);
4906
4984
  const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
4907
4985
  const moduleFacts = [];
4908
4986
  const manifestsByName = {};
@@ -5122,6 +5200,15 @@ async function reconcileCompositionArtifacts(context, options = {}) {
5122
5200
  console.log(
5123
5201
  `[ModuleRail] runId=${context.runId} composition artifacts writes=${planned.plan.writes.length} deletes=${planned.plan.deletes.length} modules=${registry.modules.length} excluded=${excludedModules.length} changed=${changed} dryRun=${options.dryRun === true} sandboxExecs=${counted.counts.sandboxExecs} sandboxReads=${counted.counts.sandboxReads}`
5124
5202
  );
5203
+ if (!options.dryRun && !options.skipComposeInputs) {
5204
+ await writeComposeInputs({
5205
+ db: context.db,
5206
+ projectId: context.projectId,
5207
+ runId: context.runId,
5208
+ sandbox: context.sandbox,
5209
+ deps
5210
+ });
5211
+ }
5125
5212
  if (changed && !options.dryRun) {
5126
5213
  await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
5127
5214
  }
@@ -5139,26 +5226,7 @@ var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
5139
5226
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
5140
5227
 
5141
5228
  // src/index.ts
5142
- var import_zod9 = require("zod");
5143
5229
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
5144
- var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
5145
- var composeInputsSchema = import_zod9.z.strictObject({
5146
- connectedStores: import_zod9.z.array(
5147
- import_zod9.z.strictObject({
5148
- storeId: import_zod9.z.string().min(1),
5149
- slug: import_zod9.z.string().min(1),
5150
- bindingKey: import_zod9.z.string().min(1).nullish(),
5151
- accessLevel: import_zod9.z.enum(["read", "write", "admin"])
5152
- })
5153
- ).default([]),
5154
- installs: import_zod9.z.record(
5155
- import_zod9.z.string(),
5156
- import_zod9.z.strictObject({
5157
- installedVersion: import_zod9.z.string().min(1),
5158
- datastoreBindings: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.string()).nullish()
5159
- })
5160
- ).default({})
5161
- });
5162
5230
  function readComposeInputs(cwd) {
5163
5231
  const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
5164
5232
  if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
@@ -5192,6 +5260,7 @@ function assertSupportedTypescript(versionMajorMinor) {
5192
5260
  }
5193
5261
  }
5194
5262
  var APP_DIR_PREFIX2 = "apps/web/src/app/";
5263
+ var APP_PACKAGE_JSON = "apps/web/package.json";
5195
5264
  var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
5196
5265
  var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
5197
5266
  function escapeGitignore(pattern) {
@@ -5223,10 +5292,10 @@ function assertStubGitignoreOwned(cwd) {
5223
5292
  }
5224
5293
  }
5225
5294
  function assertAppRoot(cwd) {
5226
- const modulesPath = import_node_path2.default.join(cwd, MODULES_DIR2);
5227
- if (!import_node_fs2.default.existsSync(modulesPath)) {
5295
+ const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
5296
+ if (!import_node_fs2.default.existsSync(appPath)) {
5228
5297
  throw new Error(
5229
- `[compose] ${modulesPath} not found \u2014 run this from the repo root of a Stardeck app`
5298
+ `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
5230
5299
  );
5231
5300
  }
5232
5301
  }
@@ -5330,7 +5399,10 @@ async function compose(options) {
5330
5399
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
5331
5400
  }
5332
5401
  const result = await reconcileCompositionArtifacts(context, {
5333
- enabledModulesRaw: options.enabled
5402
+ enabledModulesRaw: options.enabled,
5403
+ // compose is the inputs file's reader: the deps above echo it back, so
5404
+ // letting the rail rewrite it would round-trip a fork's own input.
5405
+ skipComposeInputs: true
5334
5406
  });
5335
5407
  const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
5336
5408
  let pruned = 0;