@stardeck-customer-apps/compose 0.2.0 → 0.4.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/cli.js CHANGED
@@ -239,6 +239,14 @@ var datastoreRequirementSchema = import_zod.z.object({
239
239
  id: tableNameSchema,
240
240
  /** "app" = this project's app-local store; "org" = an org-level shared store. */
241
241
  scope: import_zod.z.enum(["app", "org"]).default("app"),
242
+ /**
243
+ * Which kind of connected store satisfies this requirement. A "database"
244
+ * requirement only ever binds to a database store and a "storage" one
245
+ * (file/object storage — uploads, images) only to a storage store; the
246
+ * rail never crosses the two. Defaults to "database", the only kind the
247
+ * rail resolved before this field existed.
248
+ */
249
+ type: import_zod.z.enum(["database", "storage"]).default("database"),
242
250
  /** true → install fails when no store resolves; false → binding omitted, module degrades. */
243
251
  required: import_zod.z.boolean().default(true),
244
252
  /** Checked against the connected store's accessLevel at install. */
@@ -2069,6 +2077,44 @@ var initialModuleCompositionSchema = import_zod6.z.object({
2069
2077
  });
2070
2078
  var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
2071
2079
 
2080
+ // ../../packages/lib/src/shared/compose-inputs.ts
2081
+ var import_zod7 = require("zod");
2082
+ var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
2083
+ var composeInputsSchema = import_zod7.z.strictObject({
2084
+ connectedStores: import_zod7.z.array(
2085
+ import_zod7.z.strictObject({
2086
+ storeId: import_zod7.z.string().min(1),
2087
+ slug: import_zod7.z.string().min(1),
2088
+ // A row with an empty binding key must not fail a deploy: the rail
2089
+ // treats falsy as "no binding key", so normalise rather than reject.
2090
+ bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
2091
+ accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
2092
+ // Defaulted so a file written before the platform emitted store types
2093
+ // (database rows only) still parses. The platform starts writing
2094
+ // storage rows, with `type` set, once forks run a compose that reads it.
2095
+ type: import_zod7.z.enum(["database", "storage"]).default("database")
2096
+ })
2097
+ ).default([]),
2098
+ installs: import_zod7.z.record(
2099
+ import_zod7.z.string(),
2100
+ import_zod7.z.strictObject({
2101
+ installedVersion: import_zod7.z.string().min(1),
2102
+ datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
2103
+ })
2104
+ ).default({})
2105
+ });
2106
+ var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
2107
+ var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
2108
+ function escapeStubIgnorePattern(pattern) {
2109
+ const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
2110
+ return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
2111
+ }
2112
+ function buildStubIgnore(artifactPaths) {
2113
+ const prefix = "apps/web/src/app/";
2114
+ const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
2115
+ return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
2116
+ }
2117
+
2072
2118
  // ../../packages/lib/src/shared/module-enablement.ts
2073
2119
  function isModuleEnabledByList(raw, name, kindOf) {
2074
2120
  if (!raw) return true;
@@ -3559,6 +3605,33 @@ function dirnamePosix(filePath) {
3559
3605
  return idx <= 0 ? "." : filePath.slice(0, idx);
3560
3606
  }
3561
3607
 
3608
+ // ../../packages/lib/src/server/module-rail/compose-inputs.ts
3609
+ async function buildComposeInputs(input) {
3610
+ const { db, projectId, deps } = input;
3611
+ const connectedStores = (await deps.listConnectedStoresForProject(db, projectId)).filter(
3612
+ (store) => store.type === "database"
3613
+ );
3614
+ const installRows = await Promise.all(
3615
+ input.moduleNames.map(async (moduleName) => ({
3616
+ moduleName,
3617
+ install: await deps.getModuleInstall(db, projectId, moduleName)
3618
+ }))
3619
+ );
3620
+ const installs = {};
3621
+ for (const { moduleName, install } of installRows) {
3622
+ if (!install) continue;
3623
+ installs[moduleName] = {
3624
+ installedVersion: install.installedVersion,
3625
+ datastoreBindings: install.datastoreBindings ?? null
3626
+ };
3627
+ }
3628
+ const parsed = composeInputsSchema.parse({ connectedStores, installs });
3629
+ return {
3630
+ ...parsed,
3631
+ connectedStores: parsed.connectedStores.map(({ type: _type, ...store }) => store)
3632
+ };
3633
+ }
3634
+
3562
3635
  // ../../packages/lib/src/shared/data-store-manifest.ts
3563
3636
  function dataStoreSlug(name) {
3564
3637
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
@@ -3582,7 +3655,7 @@ function compareStores(a, b) {
3582
3655
  function describeCandidates(stores) {
3583
3656
  if (stores.length === 0) return "none";
3584
3657
  return stores.map(
3585
- (store) => `${store.storeId} (slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
3658
+ (store) => `${store.storeId} (type=${store.type}, slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
3586
3659
  ).join(", ");
3587
3660
  }
3588
3661
  function groupStoresById(stores) {
@@ -3613,14 +3686,15 @@ function accessLevelForBinding(group, binding) {
3613
3686
  );
3614
3687
  }
3615
3688
  function resolveModuleDataStoreBindings(input) {
3616
- const stores = [...input.connectedStores].sort(compareStores);
3617
- const storeGroups = groupStoresById(stores);
3689
+ const allStores = [...input.connectedStores].sort(compareStores);
3618
3690
  const requirements = [...input.requirements].sort((a, b) => compareStrings(a.id, b.id));
3619
3691
  const overrides = input.explicitOverrides ?? {};
3620
3692
  const bindings = {};
3621
3693
  const errors = [];
3622
- const candidates = describeCandidates(stores);
3623
3694
  for (const requirement of requirements) {
3695
+ const stores = allStores.filter((store) => store.type === requirement.type);
3696
+ const storeGroups = groupStoresById(stores);
3697
+ const candidates = describeCandidates(allStores);
3624
3698
  const logicalSlug = dataStoreSlug(requirement.id);
3625
3699
  let selected;
3626
3700
  if (Object.prototype.hasOwnProperty.call(overrides, requirement.id)) {
@@ -3633,7 +3707,7 @@ function resolveModuleDataStoreBindings(input) {
3633
3707
  moduleName: input.moduleName,
3634
3708
  logicalId: requirement.id,
3635
3709
  kind: "explicit-override",
3636
- message: matches.length === 0 ? `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" did not match a connected database store; candidates: ${candidates}` : `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" is ambiguous; matches: ${describeCandidates(matches.flatMap((group) => group.stores))}`
3710
+ message: matches.length === 0 ? `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" did not match a connected ${requirement.type} store; candidates: ${candidates}` : `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" is ambiguous; matches: ${describeCandidates(matches.flatMap((group) => group.stores))}`
3637
3711
  });
3638
3712
  continue;
3639
3713
  }
@@ -3644,6 +3718,8 @@ function resolveModuleDataStoreBindings(input) {
3644
3718
  );
3645
3719
  if (conventionMatches.length === 1) {
3646
3720
  selected = conventionMatches[0];
3721
+ } else if (conventionMatches.length === 0 && requirement.type === "storage") {
3722
+ if (storeGroups.length === 1) selected = storeGroups[0];
3647
3723
  } else if (conventionMatches.length === 0 && requirement.scope === "app") {
3648
3724
  if (storeGroups.length === 1) {
3649
3725
  selected = storeGroups[0];
@@ -3755,162 +3831,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
3755
3831
  }
3756
3832
 
3757
3833
  // ../../packages/lib/src/shared/schema-ops.ts
3758
- var import_zod7 = require("zod");
3759
- var columnSpecSchema = import_zod7.z.object({
3760
- name: import_zod7.z.string().min(1).max(63),
3761
- pgType: import_zod7.z.string().min(1),
3762
- nullable: import_zod7.z.boolean(),
3834
+ var import_zod8 = require("zod");
3835
+ var columnSpecSchema = import_zod8.z.object({
3836
+ name: import_zod8.z.string().min(1).max(63),
3837
+ pgType: import_zod8.z.string().min(1),
3838
+ nullable: import_zod8.z.boolean(),
3763
3839
  /**
3764
3840
  * A SQL expression used as the column default, as a raw string (e.g. `'t'`,
3765
3841
  * `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
3766
3842
  * NOT a JS literal — must already be a valid SQL expression.
3767
3843
  */
3768
- default: import_zod7.z.string().nullable().optional(),
3769
- fieldType: import_zod7.z.string().optional(),
3770
- fieldConfig: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).optional()
3844
+ default: import_zod8.z.string().nullable().optional(),
3845
+ fieldType: import_zod8.z.string().optional(),
3846
+ fieldConfig: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
3771
3847
  });
3772
- var referentialActionSchema = import_zod7.z.enum([
3848
+ var referentialActionSchema = import_zod8.z.enum([
3773
3849
  "cascade",
3774
3850
  "set null",
3775
3851
  "set default",
3776
3852
  "restrict",
3777
3853
  "no action"
3778
3854
  ]);
3779
- var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3855
+ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
3780
3856
  // Tables
3781
- import_zod7.z.object({
3782
- type: import_zod7.z.literal("createTable"),
3783
- table: import_zod7.z.string().min(1).max(63),
3784
- columns: import_zod7.z.array(columnSpecSchema).min(1),
3785
- primaryKey: import_zod7.z.array(import_zod7.z.string()).optional(),
3786
- ifNotExists: import_zod7.z.boolean().optional()
3857
+ import_zod8.z.object({
3858
+ type: import_zod8.z.literal("createTable"),
3859
+ table: import_zod8.z.string().min(1).max(63),
3860
+ columns: import_zod8.z.array(columnSpecSchema).min(1),
3861
+ primaryKey: import_zod8.z.array(import_zod8.z.string()).optional(),
3862
+ ifNotExists: import_zod8.z.boolean().optional()
3787
3863
  }),
3788
- import_zod7.z.object({
3789
- type: import_zod7.z.literal("dropTable"),
3790
- table: import_zod7.z.string().min(1).max(63),
3791
- cascade: import_zod7.z.boolean().optional(),
3792
- ifExists: import_zod7.z.boolean().optional()
3864
+ import_zod8.z.object({
3865
+ type: import_zod8.z.literal("dropTable"),
3866
+ table: import_zod8.z.string().min(1).max(63),
3867
+ cascade: import_zod8.z.boolean().optional(),
3868
+ ifExists: import_zod8.z.boolean().optional()
3793
3869
  }),
3794
- import_zod7.z.object({
3795
- type: import_zod7.z.literal("renameTable"),
3796
- from: import_zod7.z.string().min(1).max(63),
3797
- to: import_zod7.z.string().min(1).max(63)
3870
+ import_zod8.z.object({
3871
+ type: import_zod8.z.literal("renameTable"),
3872
+ from: import_zod8.z.string().min(1).max(63),
3873
+ to: import_zod8.z.string().min(1).max(63)
3798
3874
  }),
3799
3875
  // Columns
3800
- import_zod7.z.object({
3801
- type: import_zod7.z.literal("addColumn"),
3802
- table: import_zod7.z.string().min(1).max(63),
3876
+ import_zod8.z.object({
3877
+ type: import_zod8.z.literal("addColumn"),
3878
+ table: import_zod8.z.string().min(1).max(63),
3803
3879
  column: columnSpecSchema,
3804
- ifNotExists: import_zod7.z.boolean().optional()
3880
+ ifNotExists: import_zod8.z.boolean().optional()
3805
3881
  }),
3806
- import_zod7.z.object({
3807
- type: import_zod7.z.literal("dropColumn"),
3808
- table: import_zod7.z.string().min(1).max(63),
3809
- column: import_zod7.z.string().min(1).max(63),
3810
- cascade: import_zod7.z.boolean().optional(),
3811
- ifExists: import_zod7.z.boolean().optional()
3882
+ import_zod8.z.object({
3883
+ type: import_zod8.z.literal("dropColumn"),
3884
+ table: import_zod8.z.string().min(1).max(63),
3885
+ column: import_zod8.z.string().min(1).max(63),
3886
+ cascade: import_zod8.z.boolean().optional(),
3887
+ ifExists: import_zod8.z.boolean().optional()
3812
3888
  }),
3813
- import_zod7.z.object({
3814
- type: import_zod7.z.literal("renameColumn"),
3815
- table: import_zod7.z.string().min(1).max(63),
3816
- from: import_zod7.z.string().min(1).max(63),
3817
- to: import_zod7.z.string().min(1).max(63)
3889
+ import_zod8.z.object({
3890
+ type: import_zod8.z.literal("renameColumn"),
3891
+ table: import_zod8.z.string().min(1).max(63),
3892
+ from: import_zod8.z.string().min(1).max(63),
3893
+ to: import_zod8.z.string().min(1).max(63)
3818
3894
  }),
3819
- import_zod7.z.object({
3820
- type: import_zod7.z.literal("alterColumnType"),
3821
- table: import_zod7.z.string().min(1).max(63),
3822
- column: import_zod7.z.string().min(1).max(63),
3823
- newType: import_zod7.z.string().min(1),
3895
+ import_zod8.z.object({
3896
+ type: import_zod8.z.literal("alterColumnType"),
3897
+ table: import_zod8.z.string().min(1).max(63),
3898
+ column: import_zod8.z.string().min(1).max(63),
3899
+ newType: import_zod8.z.string().min(1),
3824
3900
  /** Optional USING expression for potentially-lossy casts */
3825
- using: import_zod7.z.string().optional()
3901
+ using: import_zod8.z.string().optional()
3826
3902
  }),
3827
- import_zod7.z.object({
3828
- type: import_zod7.z.literal("alterColumnNullable"),
3829
- table: import_zod7.z.string().min(1).max(63),
3830
- column: import_zod7.z.string().min(1).max(63),
3831
- nullable: import_zod7.z.boolean()
3903
+ import_zod8.z.object({
3904
+ type: import_zod8.z.literal("alterColumnNullable"),
3905
+ table: import_zod8.z.string().min(1).max(63),
3906
+ column: import_zod8.z.string().min(1).max(63),
3907
+ nullable: import_zod8.z.boolean()
3832
3908
  }),
3833
- import_zod7.z.object({
3834
- type: import_zod7.z.literal("alterColumnDefault"),
3835
- table: import_zod7.z.string().min(1).max(63),
3836
- column: import_zod7.z.string().min(1).max(63),
3909
+ import_zod8.z.object({
3910
+ type: import_zod8.z.literal("alterColumnDefault"),
3911
+ table: import_zod8.z.string().min(1).max(63),
3912
+ column: import_zod8.z.string().min(1).max(63),
3837
3913
  /** null means drop the default; otherwise a SQL expression string. */
3838
- default: import_zod7.z.string().nullable()
3914
+ default: import_zod8.z.string().nullable()
3839
3915
  }),
3840
3916
  // Indexes
3841
- import_zod7.z.object({
3842
- type: import_zod7.z.literal("addIndex"),
3843
- table: import_zod7.z.string().min(1).max(63),
3844
- name: import_zod7.z.string().min(1).max(63),
3845
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3846
- unique: import_zod7.z.boolean().optional(),
3917
+ import_zod8.z.object({
3918
+ type: import_zod8.z.literal("addIndex"),
3919
+ table: import_zod8.z.string().min(1).max(63),
3920
+ name: import_zod8.z.string().min(1).max(63),
3921
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3922
+ unique: import_zod8.z.boolean().optional(),
3847
3923
  /** Partial-index WHERE clause, as a raw SQL expression */
3848
- where: import_zod7.z.string().optional(),
3849
- ifNotExists: import_zod7.z.boolean().optional()
3924
+ where: import_zod8.z.string().optional(),
3925
+ ifNotExists: import_zod8.z.boolean().optional()
3850
3926
  }),
3851
- import_zod7.z.object({
3852
- type: import_zod7.z.literal("dropIndex"),
3853
- name: import_zod7.z.string().min(1).max(63),
3854
- cascade: import_zod7.z.boolean().optional(),
3855
- ifExists: import_zod7.z.boolean().optional()
3927
+ import_zod8.z.object({
3928
+ type: import_zod8.z.literal("dropIndex"),
3929
+ name: import_zod8.z.string().min(1).max(63),
3930
+ cascade: import_zod8.z.boolean().optional(),
3931
+ ifExists: import_zod8.z.boolean().optional()
3856
3932
  }),
3857
3933
  // Constraints
3858
- import_zod7.z.object({
3859
- type: import_zod7.z.literal("addForeignKey"),
3860
- table: import_zod7.z.string().min(1).max(63),
3861
- name: import_zod7.z.string().min(1).max(63),
3862
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3863
- refTable: import_zod7.z.string().min(1).max(63),
3864
- refColumns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3934
+ import_zod8.z.object({
3935
+ type: import_zod8.z.literal("addForeignKey"),
3936
+ table: import_zod8.z.string().min(1).max(63),
3937
+ name: import_zod8.z.string().min(1).max(63),
3938
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3939
+ refTable: import_zod8.z.string().min(1).max(63),
3940
+ refColumns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
3865
3941
  onDelete: referentialActionSchema.optional(),
3866
3942
  onUpdate: referentialActionSchema.optional()
3867
3943
  }),
3868
- import_zod7.z.object({
3869
- type: import_zod7.z.literal("addUniqueConstraint"),
3870
- table: import_zod7.z.string().min(1).max(63),
3871
- name: import_zod7.z.string().min(1).max(63),
3872
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1)
3944
+ import_zod8.z.object({
3945
+ type: import_zod8.z.literal("addUniqueConstraint"),
3946
+ table: import_zod8.z.string().min(1).max(63),
3947
+ name: import_zod8.z.string().min(1).max(63),
3948
+ columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
3873
3949
  }),
3874
- import_zod7.z.object({
3875
- type: import_zod7.z.literal("addCheckConstraint"),
3876
- table: import_zod7.z.string().min(1).max(63),
3877
- name: import_zod7.z.string().min(1).max(63),
3878
- expression: import_zod7.z.string().min(1)
3950
+ import_zod8.z.object({
3951
+ type: import_zod8.z.literal("addCheckConstraint"),
3952
+ table: import_zod8.z.string().min(1).max(63),
3953
+ name: import_zod8.z.string().min(1).max(63),
3954
+ expression: import_zod8.z.string().min(1)
3879
3955
  }),
3880
- import_zod7.z.object({
3881
- type: import_zod7.z.literal("dropConstraint"),
3882
- table: import_zod7.z.string().min(1).max(63),
3883
- name: import_zod7.z.string().min(1).max(63),
3884
- cascade: import_zod7.z.boolean().optional(),
3885
- ifExists: import_zod7.z.boolean().optional()
3956
+ import_zod8.z.object({
3957
+ type: import_zod8.z.literal("dropConstraint"),
3958
+ table: import_zod8.z.string().min(1).max(63),
3959
+ name: import_zod8.z.string().min(1).max(63),
3960
+ cascade: import_zod8.z.boolean().optional(),
3961
+ ifExists: import_zod8.z.boolean().optional()
3886
3962
  }),
3887
3963
  // Enum types
3888
- import_zod7.z.object({
3889
- type: import_zod7.z.literal("createEnum"),
3890
- name: import_zod7.z.string().min(1).max(63),
3891
- values: import_zod7.z.array(import_zod7.z.string().min(1)).min(1)
3964
+ import_zod8.z.object({
3965
+ type: import_zod8.z.literal("createEnum"),
3966
+ name: import_zod8.z.string().min(1).max(63),
3967
+ values: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
3892
3968
  }),
3893
- import_zod7.z.object({
3894
- type: import_zod7.z.literal("alterEnumAddValue"),
3895
- name: import_zod7.z.string().min(1).max(63),
3896
- value: import_zod7.z.string().min(1),
3969
+ import_zod8.z.object({
3970
+ type: import_zod8.z.literal("alterEnumAddValue"),
3971
+ name: import_zod8.z.string().min(1).max(63),
3972
+ value: import_zod8.z.string().min(1),
3897
3973
  /** Position the new value before an existing one (mutually exclusive with `after`) */
3898
- before: import_zod7.z.string().optional(),
3974
+ before: import_zod8.z.string().optional(),
3899
3975
  /** Position the new value after an existing one (mutually exclusive with `before`) */
3900
- after: import_zod7.z.string().optional(),
3901
- ifNotExists: import_zod7.z.boolean().optional()
3976
+ after: import_zod8.z.string().optional(),
3977
+ ifNotExists: import_zod8.z.boolean().optional()
3902
3978
  }),
3903
- import_zod7.z.object({
3904
- type: import_zod7.z.literal("dropEnum"),
3905
- name: import_zod7.z.string().min(1).max(63),
3906
- cascade: import_zod7.z.boolean().optional(),
3907
- ifExists: import_zod7.z.boolean().optional()
3979
+ import_zod8.z.object({
3980
+ type: import_zod8.z.literal("dropEnum"),
3981
+ name: import_zod8.z.string().min(1).max(63),
3982
+ cascade: import_zod8.z.boolean().optional(),
3983
+ ifExists: import_zod8.z.boolean().optional()
3908
3984
  }),
3909
3985
  // Extensions
3910
- import_zod7.z.object({
3911
- type: import_zod7.z.literal("createExtension"),
3912
- name: import_zod7.z.string().min(1),
3913
- ifNotExists: import_zod7.z.boolean().optional()
3986
+ import_zod8.z.object({
3987
+ type: import_zod8.z.literal("createExtension"),
3988
+ name: import_zod8.z.string().min(1),
3989
+ ifNotExists: import_zod8.z.boolean().optional()
3914
3990
  }),
3915
3991
  // Data operations
3916
3992
  /**
@@ -3918,26 +3994,26 @@ var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3918
3994
  * destructive — requires explicit confirm to replay on prod. The
3919
3995
  * `description` is what the agent/user sees at confirm time.
3920
3996
  */
3921
- import_zod7.z.object({
3922
- type: import_zod7.z.literal("backfill"),
3923
- description: import_zod7.z.string().min(1),
3924
- sql: import_zod7.z.string().min(1)
3997
+ import_zod8.z.object({
3998
+ type: import_zod8.z.literal("backfill"),
3999
+ description: import_zod8.z.string().min(1),
4000
+ sql: import_zod8.z.string().min(1)
3925
4001
  }),
3926
4002
  /**
3927
4003
  * Escape hatch for DDL the structured ops can't express. `destructive`
3928
4004
  * forces classification — use false only for genuinely additive-only SQL
3929
4005
  * (e.g. CREATE EXTENSION variants the structured op doesn't cover).
3930
4006
  */
3931
- import_zod7.z.object({
3932
- type: import_zod7.z.literal("rawSql"),
3933
- description: import_zod7.z.string().min(1),
3934
- sql: import_zod7.z.string().min(1),
3935
- destructive: import_zod7.z.boolean()
4007
+ import_zod8.z.object({
4008
+ type: import_zod8.z.literal("rawSql"),
4009
+ description: import_zod8.z.string().min(1),
4010
+ sql: import_zod8.z.string().min(1),
4011
+ destructive: import_zod8.z.boolean()
3936
4012
  })
3937
4013
  ]);
3938
4014
 
3939
4015
  // ../../packages/lib/src/server/module-rail/ops.ts
3940
- var import_zod8 = require("zod");
4016
+ var import_zod9 = require("zod");
3941
4017
 
3942
4018
  // ../../packages/lib/src/server/module-rail/reconcile.ts
3943
4019
  var SANDBOX_BATCH_SIZE = 200;
@@ -3978,8 +4054,8 @@ function countedCompositionContext(context) {
3978
4054
  }
3979
4055
  };
3980
4056
  }
3981
- async function listPresentModuleNames(context) {
3982
- const entries = await listImmediateEntries(context.sandbox, MODULES_SANDBOX_DIR, {
4057
+ async function listPresentModuleNames(sandbox) {
4058
+ const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
3983
4059
  optional: true,
3984
4060
  label: "modules"
3985
4061
  });
@@ -4002,7 +4078,7 @@ async function resolveCompositionDataStoreBindings(context, presentNames, manife
4002
4078
  if (modulesWithRequirements.length === 0) return bindingsByModule;
4003
4079
  const deps = context.deps;
4004
4080
  if (!deps) throw new Error("Module rail dependencies are required for data-store bindings");
4005
- const connectedStores = await deps.listDatabaseStoresForProject(context.db, context.projectId);
4081
+ const connectedStores = await deps.listConnectedStoresForProject(context.db, context.projectId);
4006
4082
  const installs = await Promise.all(
4007
4083
  modulesWithRequirements.map(
4008
4084
  (moduleName) => deps.getModuleInstall(context.db, context.projectId, moduleName)
@@ -4183,24 +4259,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
4183
4259
  }
4184
4260
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
4185
4261
  }
4186
- async function writeSandboxFileAtomic(context, path3, content) {
4262
+ async function writeSandboxFileAtomic(target, path3, content) {
4187
4263
  const dir = dirnamePosix(path3);
4188
- const tmp = `${path3}.tmp.${context.runId}`;
4264
+ const tmp = `${path3}.tmp.${target.runId}`;
4189
4265
  const b64 = Buffer.from(content, "utf8").toString("base64");
4190
4266
  const cmd = [
4191
4267
  `mkdir -p ${shellQuote(dir)}`,
4192
4268
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
4193
4269
  `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
4194
4270
  ].join(" && ");
4195
- const result = await context.sandbox.exec(cmd, { raiseOnError: false });
4271
+ const result = await target.sandbox.exec(cmd, { raiseOnError: false });
4196
4272
  if (result.exitCode !== 0) {
4197
- const cleanup = await context.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4273
+ const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4198
4274
  raiseOnError: false
4199
4275
  });
4200
4276
  if (cleanup.exitCode !== 0) {
4201
4277
  const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
4202
4278
  console.log(
4203
- `[ModuleRail] runId=${context.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
4279
+ `[ModuleRail] runId=${target.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
4204
4280
  );
4205
4281
  }
4206
4282
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
@@ -4258,6 +4334,55 @@ async function deleteSandboxFile(context, path3) {
4258
4334
  );
4259
4335
  }
4260
4336
  }
4337
+ async function writeComposeInputs(target) {
4338
+ const ignored = await target.sandbox.exec(
4339
+ `git check-ignore -q ${shellQuote(COMPOSE_INPUTS_PATH)}`,
4340
+ { raiseOnError: false }
4341
+ );
4342
+ if (ignored.exitCode === 1) {
4343
+ console.log(
4344
+ `[ComposeInputs] project=${target.projectId} skipped: ${COMPOSE_INPUTS_PATH} is not ignored in this checkout`
4345
+ );
4346
+ return false;
4347
+ }
4348
+ if (ignored.exitCode !== 0) {
4349
+ const detail = typeof ignored.error === "string" && ignored.error.trim() || ignored.output.trim() || "(no output)";
4350
+ throw new Error(
4351
+ `[ComposeInputs] git check-ignore failed in this checkout (exit ${ignored.exitCode}): ${redactSecrets(detail)}`
4352
+ );
4353
+ }
4354
+ const moduleNames = await listPresentModuleNames(target.sandbox);
4355
+ const inputs = await buildComposeInputs({
4356
+ db: target.db,
4357
+ projectId: target.projectId,
4358
+ deps: target.deps,
4359
+ moduleNames
4360
+ });
4361
+ const content = `${JSON.stringify(inputs, null, 2)}
4362
+ `;
4363
+ const existing = await target.sandbox.fileExists(COMPOSE_INPUTS_PATH) ? await target.sandbox.readFile(COMPOSE_INPUTS_PATH) : null;
4364
+ const written = existing === null || existing.trimEnd() !== content.trimEnd();
4365
+ if (written) await writeSandboxFileAtomic(target, COMPOSE_INPUTS_PATH, content);
4366
+ console.log(
4367
+ `[ComposeInputs] project=${target.projectId} stores=${inputs.connectedStores.length} installs=${Object.keys(inputs.installs).length} written=${written}`
4368
+ );
4369
+ return written;
4370
+ }
4371
+ async function readStubIgnore(context) {
4372
+ const existing = await context.sandbox.fileExists(STUB_IGNORE_PATH) ? await context.sandbox.readFile(STUB_IGNORE_PATH) : null;
4373
+ if (existing !== null && existing.split("\n", 1)[0] !== STUB_IGNORE_HEADER) {
4374
+ throw new Error(
4375
+ `[ModuleRail] refusing to overwrite ${STUB_IGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
4376
+ );
4377
+ }
4378
+ return existing;
4379
+ }
4380
+ async function writeStubIgnore(context, existing, artifactPaths) {
4381
+ const content = buildStubIgnore(artifactPaths);
4382
+ const written = existing === null || existing.trimEnd() !== content.trimEnd();
4383
+ if (written) await writeSandboxFileAtomic(context, STUB_IGNORE_PATH, content);
4384
+ return written;
4385
+ }
4261
4386
  async function applyCompositionArtifactMutations(context, plan, existingFiles) {
4262
4387
  const appliedWrites = [];
4263
4388
  const appliedDeletes = [];
@@ -4893,7 +5018,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
4893
5018
  context = counted.context;
4894
5019
  const deps = context.deps;
4895
5020
  if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
4896
- const presentRaw = await listPresentModuleNames(context);
5021
+ const presentRaw = await listPresentModuleNames(context.sandbox);
4897
5022
  const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
4898
5023
  const moduleFacts = [];
4899
5024
  const manifestsByName = {};
@@ -5113,9 +5238,26 @@ async function reconcileCompositionArtifacts(context, options = {}) {
5113
5238
  console.log(
5114
5239
  `[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}`
5115
5240
  );
5241
+ if (!options.dryRun && !options.skipComposeInputs) {
5242
+ await writeComposeInputs({
5243
+ db: context.db,
5244
+ projectId: context.projectId,
5245
+ runId: context.runId,
5246
+ sandbox: context.sandbox,
5247
+ deps
5248
+ });
5249
+ }
5250
+ const maintainIgnore = !options.dryRun && !options.enabledModulesRaw;
5251
+ const existingIgnore = maintainIgnore ? await readStubIgnore(context) : null;
5116
5252
  if (changed && !options.dryRun) {
5117
5253
  await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
5118
5254
  }
5255
+ if (maintainIgnore) {
5256
+ const wroteIgnore = await writeStubIgnore(context, existingIgnore, planned.plan.desiredPaths);
5257
+ console.log(
5258
+ `[ModuleRail] runId=${context.runId} stub ignore list entries=${planned.plan.desiredPaths.length} written=${wroteIgnore}`
5259
+ );
5260
+ }
5119
5261
  return {
5120
5262
  registryPath: planned.plan.registryPath,
5121
5263
  writePaths: planned.plan.writes.map((w) => w.path),
@@ -5130,26 +5272,7 @@ var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
5130
5272
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
5131
5273
 
5132
5274
  // src/index.ts
5133
- var import_zod9 = require("zod");
5134
5275
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
5135
- var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
5136
- var composeInputsSchema = import_zod9.z.strictObject({
5137
- connectedStores: import_zod9.z.array(
5138
- import_zod9.z.strictObject({
5139
- storeId: import_zod9.z.string().min(1),
5140
- slug: import_zod9.z.string().min(1),
5141
- bindingKey: import_zod9.z.string().min(1).nullish(),
5142
- accessLevel: import_zod9.z.enum(["read", "write", "admin"])
5143
- })
5144
- ).default([]),
5145
- installs: import_zod9.z.record(
5146
- import_zod9.z.string(),
5147
- import_zod9.z.strictObject({
5148
- installedVersion: import_zod9.z.string().min(1),
5149
- datastoreBindings: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.string()).nullish()
5150
- })
5151
- ).default({})
5152
- });
5153
5276
  function readComposeInputs(cwd) {
5154
5277
  const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
5155
5278
  if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
@@ -5171,7 +5294,8 @@ function readComposeInputs(cwd) {
5171
5294
  storeId: store.storeId,
5172
5295
  slug: store.slug,
5173
5296
  bindingKey: store.bindingKey ?? null,
5174
- accessLevel: store.accessLevel
5297
+ accessLevel: store.accessLevel,
5298
+ type: store.type
5175
5299
  })),
5176
5300
  installs: parsed.data.installs
5177
5301
  };
@@ -5183,6 +5307,7 @@ function assertSupportedTypescript(versionMajorMinor) {
5183
5307
  }
5184
5308
  }
5185
5309
  var APP_DIR_PREFIX2 = "apps/web/src/app/";
5310
+ var APP_PACKAGE_JSON = "apps/web/package.json";
5186
5311
  var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
5187
5312
  var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
5188
5313
  function escapeGitignore(pattern) {
@@ -5214,10 +5339,10 @@ function assertStubGitignoreOwned(cwd) {
5214
5339
  }
5215
5340
  }
5216
5341
  function assertAppRoot(cwd) {
5217
- const modulesPath = import_node_path2.default.join(cwd, MODULES_DIR2);
5218
- if (!import_node_fs2.default.existsSync(modulesPath)) {
5342
+ const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
5343
+ if (!import_node_fs2.default.existsSync(appPath)) {
5219
5344
  throw new Error(
5220
- `[compose] ${modulesPath} not found \u2014 run this from the repo root of a Stardeck app`
5345
+ `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
5221
5346
  );
5222
5347
  }
5223
5348
  }
@@ -5304,7 +5429,7 @@ async function compose(options) {
5304
5429
  const deps = {
5305
5430
  sandbox,
5306
5431
  getModuleInstall: async (_db, _projectId, moduleName) => inputs.installs[moduleName] ?? null,
5307
- listDatabaseStoresForProject: async () => inputs.connectedStores
5432
+ listConnectedStoresForProject: async () => inputs.connectedStores
5308
5433
  };
5309
5434
  const context = {
5310
5435
  db: {},
@@ -5321,7 +5446,10 @@ async function compose(options) {
5321
5446
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
5322
5447
  }
5323
5448
  const result = await reconcileCompositionArtifacts(context, {
5324
- enabledModulesRaw: options.enabled
5449
+ enabledModulesRaw: options.enabled,
5450
+ // compose is the inputs file's reader: the deps above echo it back, so
5451
+ // letting the rail rewrite it would round-trip a fork's own input.
5452
+ skipComposeInputs: true
5325
5453
  });
5326
5454
  const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
5327
5455
  let pruned = 0;