@stardeck-customer-apps/compose 0.1.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.js CHANGED
@@ -30,7 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- compose: () => compose
33
+ COMPOSE_INPUTS_PATH: () => COMPOSE_INPUTS_PATH,
34
+ assertSupportedTypescript: () => assertSupportedTypescript,
35
+ compose: () => compose,
36
+ escapeGitignore: () => escapeGitignore,
37
+ readComposeInputs: () => readComposeInputs
34
38
  });
35
39
  module.exports = __toCommonJS(index_exports);
36
40
  var import_node_child_process2 = require("child_process");
@@ -2063,6 +2067,29 @@ var initialModuleCompositionSchema = import_zod6.z.object({
2063
2067
  });
2064
2068
  var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
2065
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
+
2066
2093
  // ../../packages/lib/src/shared/module-enablement.ts
2067
2094
  function isModuleEnabledByList(raw, name, kindOf) {
2068
2095
  if (!raw) return true;
@@ -3554,6 +3581,7 @@ function planCompositionArtifacts(input) {
3554
3581
  endpointStubs: stubs.endpointStubs,
3555
3582
  routeStubs: stubs.routeStubs,
3556
3583
  writes,
3584
+ desiredPaths: [...desired.keys()].sort(compareNames2),
3557
3585
  deletes
3558
3586
  }
3559
3587
  };
@@ -3563,6 +3591,27 @@ function dirnamePosix(filePath) {
3563
3591
  return idx <= 0 ? "." : filePath.slice(0, idx);
3564
3592
  }
3565
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
+
3566
3615
  // ../../packages/lib/src/shared/data-store-manifest.ts
3567
3616
  function dataStoreSlug(name) {
3568
3617
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
@@ -3759,162 +3808,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
3759
3808
  }
3760
3809
 
3761
3810
  // ../../packages/lib/src/shared/schema-ops.ts
3762
- var import_zod7 = require("zod");
3763
- var columnSpecSchema = import_zod7.z.object({
3764
- name: import_zod7.z.string().min(1).max(63),
3765
- pgType: import_zod7.z.string().min(1),
3766
- 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(),
3767
3816
  /**
3768
3817
  * A SQL expression used as the column default, as a raw string (e.g. `'t'`,
3769
3818
  * `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
3770
3819
  * NOT a JS literal — must already be a valid SQL expression.
3771
3820
  */
3772
- default: import_zod7.z.string().nullable().optional(),
3773
- fieldType: import_zod7.z.string().optional(),
3774
- 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()
3775
3824
  });
3776
- var referentialActionSchema = import_zod7.z.enum([
3825
+ var referentialActionSchema = import_zod8.z.enum([
3777
3826
  "cascade",
3778
3827
  "set null",
3779
3828
  "set default",
3780
3829
  "restrict",
3781
3830
  "no action"
3782
3831
  ]);
3783
- var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3832
+ var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
3784
3833
  // Tables
3785
- import_zod7.z.object({
3786
- type: import_zod7.z.literal("createTable"),
3787
- table: import_zod7.z.string().min(1).max(63),
3788
- columns: import_zod7.z.array(columnSpecSchema).min(1),
3789
- primaryKey: import_zod7.z.array(import_zod7.z.string()).optional(),
3790
- 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()
3791
3840
  }),
3792
- import_zod7.z.object({
3793
- type: import_zod7.z.literal("dropTable"),
3794
- table: import_zod7.z.string().min(1).max(63),
3795
- cascade: import_zod7.z.boolean().optional(),
3796
- 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()
3797
3846
  }),
3798
- import_zod7.z.object({
3799
- type: import_zod7.z.literal("renameTable"),
3800
- from: import_zod7.z.string().min(1).max(63),
3801
- 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)
3802
3851
  }),
3803
3852
  // Columns
3804
- import_zod7.z.object({
3805
- type: import_zod7.z.literal("addColumn"),
3806
- 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),
3807
3856
  column: columnSpecSchema,
3808
- ifNotExists: import_zod7.z.boolean().optional()
3857
+ ifNotExists: import_zod8.z.boolean().optional()
3809
3858
  }),
3810
- import_zod7.z.object({
3811
- type: import_zod7.z.literal("dropColumn"),
3812
- table: import_zod7.z.string().min(1).max(63),
3813
- column: import_zod7.z.string().min(1).max(63),
3814
- cascade: import_zod7.z.boolean().optional(),
3815
- 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()
3816
3865
  }),
3817
- import_zod7.z.object({
3818
- type: import_zod7.z.literal("renameColumn"),
3819
- table: import_zod7.z.string().min(1).max(63),
3820
- from: import_zod7.z.string().min(1).max(63),
3821
- 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)
3822
3871
  }),
3823
- import_zod7.z.object({
3824
- type: import_zod7.z.literal("alterColumnType"),
3825
- table: import_zod7.z.string().min(1).max(63),
3826
- column: import_zod7.z.string().min(1).max(63),
3827
- 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),
3828
3877
  /** Optional USING expression for potentially-lossy casts */
3829
- using: import_zod7.z.string().optional()
3878
+ using: import_zod8.z.string().optional()
3830
3879
  }),
3831
- import_zod7.z.object({
3832
- type: import_zod7.z.literal("alterColumnNullable"),
3833
- table: import_zod7.z.string().min(1).max(63),
3834
- column: import_zod7.z.string().min(1).max(63),
3835
- 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()
3836
3885
  }),
3837
- import_zod7.z.object({
3838
- type: import_zod7.z.literal("alterColumnDefault"),
3839
- table: import_zod7.z.string().min(1).max(63),
3840
- 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),
3841
3890
  /** null means drop the default; otherwise a SQL expression string. */
3842
- default: import_zod7.z.string().nullable()
3891
+ default: import_zod8.z.string().nullable()
3843
3892
  }),
3844
3893
  // Indexes
3845
- import_zod7.z.object({
3846
- type: import_zod7.z.literal("addIndex"),
3847
- table: import_zod7.z.string().min(1).max(63),
3848
- name: import_zod7.z.string().min(1).max(63),
3849
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3850
- 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(),
3851
3900
  /** Partial-index WHERE clause, as a raw SQL expression */
3852
- where: import_zod7.z.string().optional(),
3853
- ifNotExists: import_zod7.z.boolean().optional()
3901
+ where: import_zod8.z.string().optional(),
3902
+ ifNotExists: import_zod8.z.boolean().optional()
3854
3903
  }),
3855
- import_zod7.z.object({
3856
- type: import_zod7.z.literal("dropIndex"),
3857
- name: import_zod7.z.string().min(1).max(63),
3858
- cascade: import_zod7.z.boolean().optional(),
3859
- 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()
3860
3909
  }),
3861
3910
  // Constraints
3862
- import_zod7.z.object({
3863
- type: import_zod7.z.literal("addForeignKey"),
3864
- table: import_zod7.z.string().min(1).max(63),
3865
- name: import_zod7.z.string().min(1).max(63),
3866
- columns: import_zod7.z.array(import_zod7.z.string().min(1)).min(1),
3867
- refTable: import_zod7.z.string().min(1).max(63),
3868
- 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),
3869
3918
  onDelete: referentialActionSchema.optional(),
3870
3919
  onUpdate: referentialActionSchema.optional()
3871
3920
  }),
3872
- import_zod7.z.object({
3873
- type: import_zod7.z.literal("addUniqueConstraint"),
3874
- table: import_zod7.z.string().min(1).max(63),
3875
- name: import_zod7.z.string().min(1).max(63),
3876
- 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)
3877
3926
  }),
3878
- import_zod7.z.object({
3879
- type: import_zod7.z.literal("addCheckConstraint"),
3880
- table: import_zod7.z.string().min(1).max(63),
3881
- name: import_zod7.z.string().min(1).max(63),
3882
- 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)
3883
3932
  }),
3884
- import_zod7.z.object({
3885
- type: import_zod7.z.literal("dropConstraint"),
3886
- table: import_zod7.z.string().min(1).max(63),
3887
- name: import_zod7.z.string().min(1).max(63),
3888
- cascade: import_zod7.z.boolean().optional(),
3889
- 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()
3890
3939
  }),
3891
3940
  // Enum types
3892
- import_zod7.z.object({
3893
- type: import_zod7.z.literal("createEnum"),
3894
- name: import_zod7.z.string().min(1).max(63),
3895
- 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)
3896
3945
  }),
3897
- import_zod7.z.object({
3898
- type: import_zod7.z.literal("alterEnumAddValue"),
3899
- name: import_zod7.z.string().min(1).max(63),
3900
- 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),
3901
3950
  /** Position the new value before an existing one (mutually exclusive with `after`) */
3902
- before: import_zod7.z.string().optional(),
3951
+ before: import_zod8.z.string().optional(),
3903
3952
  /** Position the new value after an existing one (mutually exclusive with `before`) */
3904
- after: import_zod7.z.string().optional(),
3905
- ifNotExists: import_zod7.z.boolean().optional()
3953
+ after: import_zod8.z.string().optional(),
3954
+ ifNotExists: import_zod8.z.boolean().optional()
3906
3955
  }),
3907
- import_zod7.z.object({
3908
- type: import_zod7.z.literal("dropEnum"),
3909
- name: import_zod7.z.string().min(1).max(63),
3910
- cascade: import_zod7.z.boolean().optional(),
3911
- 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()
3912
3961
  }),
3913
3962
  // Extensions
3914
- import_zod7.z.object({
3915
- type: import_zod7.z.literal("createExtension"),
3916
- name: import_zod7.z.string().min(1),
3917
- 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()
3918
3967
  }),
3919
3968
  // Data operations
3920
3969
  /**
@@ -3922,26 +3971,26 @@ var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
3922
3971
  * destructive — requires explicit confirm to replay on prod. The
3923
3972
  * `description` is what the agent/user sees at confirm time.
3924
3973
  */
3925
- import_zod7.z.object({
3926
- type: import_zod7.z.literal("backfill"),
3927
- description: import_zod7.z.string().min(1),
3928
- 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)
3929
3978
  }),
3930
3979
  /**
3931
3980
  * Escape hatch for DDL the structured ops can't express. `destructive`
3932
3981
  * forces classification — use false only for genuinely additive-only SQL
3933
3982
  * (e.g. CREATE EXTENSION variants the structured op doesn't cover).
3934
3983
  */
3935
- import_zod7.z.object({
3936
- type: import_zod7.z.literal("rawSql"),
3937
- description: import_zod7.z.string().min(1),
3938
- sql: import_zod7.z.string().min(1),
3939
- 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()
3940
3989
  })
3941
3990
  ]);
3942
3991
 
3943
3992
  // ../../packages/lib/src/server/module-rail/ops.ts
3944
- var import_zod8 = require("zod");
3993
+ var import_zod9 = require("zod");
3945
3994
 
3946
3995
  // ../../packages/lib/src/server/module-rail/reconcile.ts
3947
3996
  var SANDBOX_BATCH_SIZE = 200;
@@ -3982,8 +4031,8 @@ function countedCompositionContext(context) {
3982
4031
  }
3983
4032
  };
3984
4033
  }
3985
- async function listPresentModuleNames(context) {
3986
- const entries = await listImmediateEntries(context.sandbox, MODULES_SANDBOX_DIR, {
4034
+ async function listPresentModuleNames(sandbox) {
4035
+ const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
3987
4036
  optional: true,
3988
4037
  label: "modules"
3989
4038
  });
@@ -4187,24 +4236,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
4187
4236
  }
4188
4237
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
4189
4238
  }
4190
- async function writeSandboxFileAtomic(context, path3, content) {
4239
+ async function writeSandboxFileAtomic(target, path3, content) {
4191
4240
  const dir = dirnamePosix(path3);
4192
- const tmp = `${path3}.tmp.${context.runId}`;
4241
+ const tmp = `${path3}.tmp.${target.runId}`;
4193
4242
  const b64 = Buffer.from(content, "utf8").toString("base64");
4194
4243
  const cmd = [
4195
4244
  `mkdir -p ${shellQuote(dir)}`,
4196
4245
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
4197
4246
  `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
4198
4247
  ].join(" && ");
4199
- const result = await context.sandbox.exec(cmd, { raiseOnError: false });
4248
+ const result = await target.sandbox.exec(cmd, { raiseOnError: false });
4200
4249
  if (result.exitCode !== 0) {
4201
- const cleanup = await context.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4250
+ const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
4202
4251
  raiseOnError: false
4203
4252
  });
4204
4253
  if (cleanup.exitCode !== 0) {
4205
4254
  const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
4206
4255
  console.log(
4207
- `[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)}`
4208
4257
  );
4209
4258
  }
4210
4259
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
@@ -4262,6 +4311,40 @@ async function deleteSandboxFile(context, path3) {
4262
4311
  );
4263
4312
  }
4264
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
+ }
4265
4348
  async function applyCompositionArtifactMutations(context, plan, existingFiles) {
4266
4349
  const appliedWrites = [];
4267
4350
  const appliedDeletes = [];
@@ -4897,7 +4980,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
4897
4980
  context = counted.context;
4898
4981
  const deps = context.deps;
4899
4982
  if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
4900
- const presentRaw = await listPresentModuleNames(context);
4983
+ const presentRaw = await listPresentModuleNames(context.sandbox);
4901
4984
  const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
4902
4985
  const moduleFacts = [];
4903
4986
  const manifestsByName = {};
@@ -5117,6 +5200,15 @@ async function reconcileCompositionArtifacts(context, options = {}) {
5117
5200
  console.log(
5118
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}`
5119
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
+ }
5120
5212
  if (changed && !options.dryRun) {
5121
5213
  await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
5122
5214
  }
@@ -5124,6 +5216,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
5124
5216
  registryPath: planned.plan.registryPath,
5125
5217
  writePaths: planned.plan.writes.map((w) => w.path),
5126
5218
  deletePaths: planned.plan.deletes,
5219
+ artifactPaths: planned.plan.desiredPaths,
5127
5220
  changed,
5128
5221
  composedModules: composedNames,
5129
5222
  excludedModules
@@ -5134,11 +5227,75 @@ var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
5134
5227
 
5135
5228
  // src/index.ts
5136
5229
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
5230
+ function readComposeInputs(cwd) {
5231
+ const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
5232
+ if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
5233
+ let raw;
5234
+ try {
5235
+ raw = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
5236
+ } catch (error) {
5237
+ throw new Error(
5238
+ `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
5239
+ );
5240
+ }
5241
+ const parsed = composeInputsSchema.safeParse(raw);
5242
+ if (!parsed.success) {
5243
+ const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
5244
+ throw new Error(`[compose] ${file} is invalid: ${issues}`);
5245
+ }
5246
+ return {
5247
+ connectedStores: parsed.data.connectedStores.map((store) => ({
5248
+ storeId: store.storeId,
5249
+ slug: store.slug,
5250
+ bindingKey: store.bindingKey ?? null,
5251
+ accessLevel: store.accessLevel
5252
+ })),
5253
+ installs: parsed.data.installs
5254
+ };
5255
+ }
5256
+ function assertSupportedTypescript(versionMajorMinor) {
5257
+ const major = Number.parseInt(versionMajorMinor.split(".")[0] ?? "", 10);
5258
+ if (!Number.isInteger(major) || major < 5 || major >= 7) {
5259
+ throw new Error(`[compose] typescript ${versionMajorMinor} is not supported (need >=5 <7)`);
5260
+ }
5261
+ }
5262
+ var APP_DIR_PREFIX2 = "apps/web/src/app/";
5263
+ var APP_PACKAGE_JSON = "apps/web/package.json";
5264
+ var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
5265
+ var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
5266
+ function escapeGitignore(pattern) {
5267
+ const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
5268
+ return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
5269
+ }
5270
+ function stubGitignoreEntries(artifactPaths) {
5271
+ return artifactPaths.filter((artifact) => artifact.startsWith(APP_DIR_PREFIX2)).map((artifact) => escapeGitignore(`/${artifact.slice(APP_DIR_PREFIX2.length)}`)).sort();
5272
+ }
5273
+ function writeStubGitignore(cwd, artifactPaths) {
5274
+ const entries = stubGitignoreEntries(artifactPaths);
5275
+ const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
5276
+ const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
5277
+ const existing = import_node_fs2.default.existsSync(file) ? import_node_fs2.default.readFileSync(file, "utf8") : null;
5278
+ if (existing !== content) {
5279
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
5280
+ import_node_fs2.default.writeFileSync(file, content);
5281
+ }
5282
+ return entries.length;
5283
+ }
5284
+ function assertStubGitignoreOwned(cwd) {
5285
+ const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
5286
+ if (!import_node_fs2.default.existsSync(file)) return;
5287
+ const firstLine = import_node_fs2.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
5288
+ if (firstLine !== STUB_GITIGNORE_HEADER) {
5289
+ throw new Error(
5290
+ `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
5291
+ );
5292
+ }
5293
+ }
5137
5294
  function assertAppRoot(cwd) {
5138
- const modulesPath = import_node_path2.default.join(cwd, MODULES_DIR2);
5139
- 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)) {
5140
5297
  throw new Error(
5141
- `[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`
5142
5299
  );
5143
5300
  }
5144
5301
  }
@@ -5218,12 +5375,14 @@ function removeDir(absolute) {
5218
5375
  }
5219
5376
  async function compose(options) {
5220
5377
  assertAppRoot(options.cwd);
5378
+ assertStubGitignoreOwned(options.cwd);
5379
+ const inputs = readComposeInputs(options.cwd);
5221
5380
  if (options.prune) assertModuleTreeClean(options.cwd);
5222
5381
  const sandbox = localFsSandbox(options.cwd);
5223
5382
  const deps = {
5224
5383
  sandbox,
5225
- getModuleInstall: async () => null,
5226
- listDatabaseStoresForProject: async () => []
5384
+ getModuleInstall: async (_db, _projectId, moduleName) => inputs.installs[moduleName] ?? null,
5385
+ listDatabaseStoresForProject: async () => inputs.connectedStores
5227
5386
  };
5228
5387
  const context = {
5229
5388
  db: {},
@@ -5240,8 +5399,12 @@ async function compose(options) {
5240
5399
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
5241
5400
  }
5242
5401
  const result = await reconcileCompositionArtifacts(context, {
5243
- 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
5244
5406
  });
5407
+ const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
5245
5408
  let pruned = 0;
5246
5409
  if (options.prune) {
5247
5410
  for (const name of result.excludedModules) {
@@ -5258,10 +5421,15 @@ async function compose(options) {
5258
5421
  excludedModules: result.excludedModules,
5259
5422
  writes: result.writePaths.length,
5260
5423
  deletes: result.deletePaths.length,
5261
- pruned
5424
+ pruned,
5425
+ gitignoreEntries
5262
5426
  };
5263
5427
  }
5264
5428
  // Annotate the CommonJS export names for ESM import in node:
5265
5429
  0 && (module.exports = {
5266
- compose
5430
+ COMPOSE_INPUTS_PATH,
5431
+ assertSupportedTypescript,
5432
+ compose,
5433
+ escapeGitignore,
5434
+ readComposeInputs
5267
5435
  });