@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/SKILL.md +42 -11
- package/dist/cli.js +302 -138
- package/dist/index.d.mts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +307 -139
- package/dist/index.mjs +302 -138
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2029,6 +2029,29 @@ var initialModuleCompositionSchema = z6.object({
|
|
|
2029
2029
|
});
|
|
2030
2030
|
var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
|
|
2031
2031
|
|
|
2032
|
+
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
2033
|
+
import { z as z7 } from "zod";
|
|
2034
|
+
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
2035
|
+
var composeInputsSchema = z7.strictObject({
|
|
2036
|
+
connectedStores: z7.array(
|
|
2037
|
+
z7.strictObject({
|
|
2038
|
+
storeId: z7.string().min(1),
|
|
2039
|
+
slug: z7.string().min(1),
|
|
2040
|
+
// A row with an empty binding key must not fail a deploy: the rail
|
|
2041
|
+
// treats falsy as "no binding key", so normalise rather than reject.
|
|
2042
|
+
bindingKey: z7.string().nullish().transform((value) => value || null),
|
|
2043
|
+
accessLevel: z7.enum(["read", "write", "admin"])
|
|
2044
|
+
})
|
|
2045
|
+
).default([]),
|
|
2046
|
+
installs: z7.record(
|
|
2047
|
+
z7.string(),
|
|
2048
|
+
z7.strictObject({
|
|
2049
|
+
installedVersion: z7.string().min(1),
|
|
2050
|
+
datastoreBindings: z7.record(z7.string(), z7.string()).nullish()
|
|
2051
|
+
})
|
|
2052
|
+
).default({})
|
|
2053
|
+
});
|
|
2054
|
+
|
|
2032
2055
|
// ../../packages/lib/src/shared/module-enablement.ts
|
|
2033
2056
|
function isModuleEnabledByList(raw, name, kindOf) {
|
|
2034
2057
|
if (!raw) return true;
|
|
@@ -3520,6 +3543,7 @@ function planCompositionArtifacts(input) {
|
|
|
3520
3543
|
endpointStubs: stubs.endpointStubs,
|
|
3521
3544
|
routeStubs: stubs.routeStubs,
|
|
3522
3545
|
writes,
|
|
3546
|
+
desiredPaths: [...desired.keys()].sort(compareNames2),
|
|
3523
3547
|
deletes
|
|
3524
3548
|
}
|
|
3525
3549
|
};
|
|
@@ -3529,6 +3553,27 @@ function dirnamePosix(filePath) {
|
|
|
3529
3553
|
return idx <= 0 ? "." : filePath.slice(0, idx);
|
|
3530
3554
|
}
|
|
3531
3555
|
|
|
3556
|
+
// ../../packages/lib/src/server/module-rail/compose-inputs.ts
|
|
3557
|
+
async function buildComposeInputs(input) {
|
|
3558
|
+
const { db, projectId, deps } = input;
|
|
3559
|
+
const connectedStores = await deps.listDatabaseStoresForProject(db, projectId);
|
|
3560
|
+
const installRows = await Promise.all(
|
|
3561
|
+
input.moduleNames.map(async (moduleName) => ({
|
|
3562
|
+
moduleName,
|
|
3563
|
+
install: await deps.getModuleInstall(db, projectId, moduleName)
|
|
3564
|
+
}))
|
|
3565
|
+
);
|
|
3566
|
+
const installs = {};
|
|
3567
|
+
for (const { moduleName, install } of installRows) {
|
|
3568
|
+
if (!install) continue;
|
|
3569
|
+
installs[moduleName] = {
|
|
3570
|
+
installedVersion: install.installedVersion,
|
|
3571
|
+
datastoreBindings: install.datastoreBindings ?? null
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
return composeInputsSchema.parse({ connectedStores, installs });
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3532
3577
|
// ../../packages/lib/src/shared/data-store-manifest.ts
|
|
3533
3578
|
function dataStoreSlug(name) {
|
|
3534
3579
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -3725,162 +3770,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
3725
3770
|
}
|
|
3726
3771
|
|
|
3727
3772
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
3728
|
-
import { z as
|
|
3729
|
-
var columnSpecSchema =
|
|
3730
|
-
name:
|
|
3731
|
-
pgType:
|
|
3732
|
-
nullable:
|
|
3773
|
+
import { z as z8 } from "zod";
|
|
3774
|
+
var columnSpecSchema = z8.object({
|
|
3775
|
+
name: z8.string().min(1).max(63),
|
|
3776
|
+
pgType: z8.string().min(1),
|
|
3777
|
+
nullable: z8.boolean(),
|
|
3733
3778
|
/**
|
|
3734
3779
|
* A SQL expression used as the column default, as a raw string (e.g. `'t'`,
|
|
3735
3780
|
* `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
|
|
3736
3781
|
* NOT a JS literal — must already be a valid SQL expression.
|
|
3737
3782
|
*/
|
|
3738
|
-
default:
|
|
3739
|
-
fieldType:
|
|
3740
|
-
fieldConfig:
|
|
3783
|
+
default: z8.string().nullable().optional(),
|
|
3784
|
+
fieldType: z8.string().optional(),
|
|
3785
|
+
fieldConfig: z8.record(z8.string(), z8.unknown()).optional()
|
|
3741
3786
|
});
|
|
3742
|
-
var referentialActionSchema =
|
|
3787
|
+
var referentialActionSchema = z8.enum([
|
|
3743
3788
|
"cascade",
|
|
3744
3789
|
"set null",
|
|
3745
3790
|
"set default",
|
|
3746
3791
|
"restrict",
|
|
3747
3792
|
"no action"
|
|
3748
3793
|
]);
|
|
3749
|
-
var schemaOpSchema =
|
|
3794
|
+
var schemaOpSchema = z8.discriminatedUnion("type", [
|
|
3750
3795
|
// Tables
|
|
3751
|
-
|
|
3752
|
-
type:
|
|
3753
|
-
table:
|
|
3754
|
-
columns:
|
|
3755
|
-
primaryKey:
|
|
3756
|
-
ifNotExists:
|
|
3796
|
+
z8.object({
|
|
3797
|
+
type: z8.literal("createTable"),
|
|
3798
|
+
table: z8.string().min(1).max(63),
|
|
3799
|
+
columns: z8.array(columnSpecSchema).min(1),
|
|
3800
|
+
primaryKey: z8.array(z8.string()).optional(),
|
|
3801
|
+
ifNotExists: z8.boolean().optional()
|
|
3757
3802
|
}),
|
|
3758
|
-
|
|
3759
|
-
type:
|
|
3760
|
-
table:
|
|
3761
|
-
cascade:
|
|
3762
|
-
ifExists:
|
|
3803
|
+
z8.object({
|
|
3804
|
+
type: z8.literal("dropTable"),
|
|
3805
|
+
table: z8.string().min(1).max(63),
|
|
3806
|
+
cascade: z8.boolean().optional(),
|
|
3807
|
+
ifExists: z8.boolean().optional()
|
|
3763
3808
|
}),
|
|
3764
|
-
|
|
3765
|
-
type:
|
|
3766
|
-
from:
|
|
3767
|
-
to:
|
|
3809
|
+
z8.object({
|
|
3810
|
+
type: z8.literal("renameTable"),
|
|
3811
|
+
from: z8.string().min(1).max(63),
|
|
3812
|
+
to: z8.string().min(1).max(63)
|
|
3768
3813
|
}),
|
|
3769
3814
|
// Columns
|
|
3770
|
-
|
|
3771
|
-
type:
|
|
3772
|
-
table:
|
|
3815
|
+
z8.object({
|
|
3816
|
+
type: z8.literal("addColumn"),
|
|
3817
|
+
table: z8.string().min(1).max(63),
|
|
3773
3818
|
column: columnSpecSchema,
|
|
3774
|
-
ifNotExists:
|
|
3819
|
+
ifNotExists: z8.boolean().optional()
|
|
3775
3820
|
}),
|
|
3776
|
-
|
|
3777
|
-
type:
|
|
3778
|
-
table:
|
|
3779
|
-
column:
|
|
3780
|
-
cascade:
|
|
3781
|
-
ifExists:
|
|
3821
|
+
z8.object({
|
|
3822
|
+
type: z8.literal("dropColumn"),
|
|
3823
|
+
table: z8.string().min(1).max(63),
|
|
3824
|
+
column: z8.string().min(1).max(63),
|
|
3825
|
+
cascade: z8.boolean().optional(),
|
|
3826
|
+
ifExists: z8.boolean().optional()
|
|
3782
3827
|
}),
|
|
3783
|
-
|
|
3784
|
-
type:
|
|
3785
|
-
table:
|
|
3786
|
-
from:
|
|
3787
|
-
to:
|
|
3828
|
+
z8.object({
|
|
3829
|
+
type: z8.literal("renameColumn"),
|
|
3830
|
+
table: z8.string().min(1).max(63),
|
|
3831
|
+
from: z8.string().min(1).max(63),
|
|
3832
|
+
to: z8.string().min(1).max(63)
|
|
3788
3833
|
}),
|
|
3789
|
-
|
|
3790
|
-
type:
|
|
3791
|
-
table:
|
|
3792
|
-
column:
|
|
3793
|
-
newType:
|
|
3834
|
+
z8.object({
|
|
3835
|
+
type: z8.literal("alterColumnType"),
|
|
3836
|
+
table: z8.string().min(1).max(63),
|
|
3837
|
+
column: z8.string().min(1).max(63),
|
|
3838
|
+
newType: z8.string().min(1),
|
|
3794
3839
|
/** Optional USING expression for potentially-lossy casts */
|
|
3795
|
-
using:
|
|
3840
|
+
using: z8.string().optional()
|
|
3796
3841
|
}),
|
|
3797
|
-
|
|
3798
|
-
type:
|
|
3799
|
-
table:
|
|
3800
|
-
column:
|
|
3801
|
-
nullable:
|
|
3842
|
+
z8.object({
|
|
3843
|
+
type: z8.literal("alterColumnNullable"),
|
|
3844
|
+
table: z8.string().min(1).max(63),
|
|
3845
|
+
column: z8.string().min(1).max(63),
|
|
3846
|
+
nullable: z8.boolean()
|
|
3802
3847
|
}),
|
|
3803
|
-
|
|
3804
|
-
type:
|
|
3805
|
-
table:
|
|
3806
|
-
column:
|
|
3848
|
+
z8.object({
|
|
3849
|
+
type: z8.literal("alterColumnDefault"),
|
|
3850
|
+
table: z8.string().min(1).max(63),
|
|
3851
|
+
column: z8.string().min(1).max(63),
|
|
3807
3852
|
/** null means drop the default; otherwise a SQL expression string. */
|
|
3808
|
-
default:
|
|
3853
|
+
default: z8.string().nullable()
|
|
3809
3854
|
}),
|
|
3810
3855
|
// Indexes
|
|
3811
|
-
|
|
3812
|
-
type:
|
|
3813
|
-
table:
|
|
3814
|
-
name:
|
|
3815
|
-
columns:
|
|
3816
|
-
unique:
|
|
3856
|
+
z8.object({
|
|
3857
|
+
type: z8.literal("addIndex"),
|
|
3858
|
+
table: z8.string().min(1).max(63),
|
|
3859
|
+
name: z8.string().min(1).max(63),
|
|
3860
|
+
columns: z8.array(z8.string().min(1)).min(1),
|
|
3861
|
+
unique: z8.boolean().optional(),
|
|
3817
3862
|
/** Partial-index WHERE clause, as a raw SQL expression */
|
|
3818
|
-
where:
|
|
3819
|
-
ifNotExists:
|
|
3863
|
+
where: z8.string().optional(),
|
|
3864
|
+
ifNotExists: z8.boolean().optional()
|
|
3820
3865
|
}),
|
|
3821
|
-
|
|
3822
|
-
type:
|
|
3823
|
-
name:
|
|
3824
|
-
cascade:
|
|
3825
|
-
ifExists:
|
|
3866
|
+
z8.object({
|
|
3867
|
+
type: z8.literal("dropIndex"),
|
|
3868
|
+
name: z8.string().min(1).max(63),
|
|
3869
|
+
cascade: z8.boolean().optional(),
|
|
3870
|
+
ifExists: z8.boolean().optional()
|
|
3826
3871
|
}),
|
|
3827
3872
|
// Constraints
|
|
3828
|
-
|
|
3829
|
-
type:
|
|
3830
|
-
table:
|
|
3831
|
-
name:
|
|
3832
|
-
columns:
|
|
3833
|
-
refTable:
|
|
3834
|
-
refColumns:
|
|
3873
|
+
z8.object({
|
|
3874
|
+
type: z8.literal("addForeignKey"),
|
|
3875
|
+
table: z8.string().min(1).max(63),
|
|
3876
|
+
name: z8.string().min(1).max(63),
|
|
3877
|
+
columns: z8.array(z8.string().min(1)).min(1),
|
|
3878
|
+
refTable: z8.string().min(1).max(63),
|
|
3879
|
+
refColumns: z8.array(z8.string().min(1)).min(1),
|
|
3835
3880
|
onDelete: referentialActionSchema.optional(),
|
|
3836
3881
|
onUpdate: referentialActionSchema.optional()
|
|
3837
3882
|
}),
|
|
3838
|
-
|
|
3839
|
-
type:
|
|
3840
|
-
table:
|
|
3841
|
-
name:
|
|
3842
|
-
columns:
|
|
3883
|
+
z8.object({
|
|
3884
|
+
type: z8.literal("addUniqueConstraint"),
|
|
3885
|
+
table: z8.string().min(1).max(63),
|
|
3886
|
+
name: z8.string().min(1).max(63),
|
|
3887
|
+
columns: z8.array(z8.string().min(1)).min(1)
|
|
3843
3888
|
}),
|
|
3844
|
-
|
|
3845
|
-
type:
|
|
3846
|
-
table:
|
|
3847
|
-
name:
|
|
3848
|
-
expression:
|
|
3889
|
+
z8.object({
|
|
3890
|
+
type: z8.literal("addCheckConstraint"),
|
|
3891
|
+
table: z8.string().min(1).max(63),
|
|
3892
|
+
name: z8.string().min(1).max(63),
|
|
3893
|
+
expression: z8.string().min(1)
|
|
3849
3894
|
}),
|
|
3850
|
-
|
|
3851
|
-
type:
|
|
3852
|
-
table:
|
|
3853
|
-
name:
|
|
3854
|
-
cascade:
|
|
3855
|
-
ifExists:
|
|
3895
|
+
z8.object({
|
|
3896
|
+
type: z8.literal("dropConstraint"),
|
|
3897
|
+
table: z8.string().min(1).max(63),
|
|
3898
|
+
name: z8.string().min(1).max(63),
|
|
3899
|
+
cascade: z8.boolean().optional(),
|
|
3900
|
+
ifExists: z8.boolean().optional()
|
|
3856
3901
|
}),
|
|
3857
3902
|
// Enum types
|
|
3858
|
-
|
|
3859
|
-
type:
|
|
3860
|
-
name:
|
|
3861
|
-
values:
|
|
3903
|
+
z8.object({
|
|
3904
|
+
type: z8.literal("createEnum"),
|
|
3905
|
+
name: z8.string().min(1).max(63),
|
|
3906
|
+
values: z8.array(z8.string().min(1)).min(1)
|
|
3862
3907
|
}),
|
|
3863
|
-
|
|
3864
|
-
type:
|
|
3865
|
-
name:
|
|
3866
|
-
value:
|
|
3908
|
+
z8.object({
|
|
3909
|
+
type: z8.literal("alterEnumAddValue"),
|
|
3910
|
+
name: z8.string().min(1).max(63),
|
|
3911
|
+
value: z8.string().min(1),
|
|
3867
3912
|
/** Position the new value before an existing one (mutually exclusive with `after`) */
|
|
3868
|
-
before:
|
|
3913
|
+
before: z8.string().optional(),
|
|
3869
3914
|
/** Position the new value after an existing one (mutually exclusive with `before`) */
|
|
3870
|
-
after:
|
|
3871
|
-
ifNotExists:
|
|
3915
|
+
after: z8.string().optional(),
|
|
3916
|
+
ifNotExists: z8.boolean().optional()
|
|
3872
3917
|
}),
|
|
3873
|
-
|
|
3874
|
-
type:
|
|
3875
|
-
name:
|
|
3876
|
-
cascade:
|
|
3877
|
-
ifExists:
|
|
3918
|
+
z8.object({
|
|
3919
|
+
type: z8.literal("dropEnum"),
|
|
3920
|
+
name: z8.string().min(1).max(63),
|
|
3921
|
+
cascade: z8.boolean().optional(),
|
|
3922
|
+
ifExists: z8.boolean().optional()
|
|
3878
3923
|
}),
|
|
3879
3924
|
// Extensions
|
|
3880
|
-
|
|
3881
|
-
type:
|
|
3882
|
-
name:
|
|
3883
|
-
ifNotExists:
|
|
3925
|
+
z8.object({
|
|
3926
|
+
type: z8.literal("createExtension"),
|
|
3927
|
+
name: z8.string().min(1),
|
|
3928
|
+
ifNotExists: z8.boolean().optional()
|
|
3884
3929
|
}),
|
|
3885
3930
|
// Data operations
|
|
3886
3931
|
/**
|
|
@@ -3888,26 +3933,26 @@ var schemaOpSchema = z7.discriminatedUnion("type", [
|
|
|
3888
3933
|
* destructive — requires explicit confirm to replay on prod. The
|
|
3889
3934
|
* `description` is what the agent/user sees at confirm time.
|
|
3890
3935
|
*/
|
|
3891
|
-
|
|
3892
|
-
type:
|
|
3893
|
-
description:
|
|
3894
|
-
sql:
|
|
3936
|
+
z8.object({
|
|
3937
|
+
type: z8.literal("backfill"),
|
|
3938
|
+
description: z8.string().min(1),
|
|
3939
|
+
sql: z8.string().min(1)
|
|
3895
3940
|
}),
|
|
3896
3941
|
/**
|
|
3897
3942
|
* Escape hatch for DDL the structured ops can't express. `destructive`
|
|
3898
3943
|
* forces classification — use false only for genuinely additive-only SQL
|
|
3899
3944
|
* (e.g. CREATE EXTENSION variants the structured op doesn't cover).
|
|
3900
3945
|
*/
|
|
3901
|
-
|
|
3902
|
-
type:
|
|
3903
|
-
description:
|
|
3904
|
-
sql:
|
|
3905
|
-
destructive:
|
|
3946
|
+
z8.object({
|
|
3947
|
+
type: z8.literal("rawSql"),
|
|
3948
|
+
description: z8.string().min(1),
|
|
3949
|
+
sql: z8.string().min(1),
|
|
3950
|
+
destructive: z8.boolean()
|
|
3906
3951
|
})
|
|
3907
3952
|
]);
|
|
3908
3953
|
|
|
3909
3954
|
// ../../packages/lib/src/server/module-rail/ops.ts
|
|
3910
|
-
import { z as
|
|
3955
|
+
import { z as z9 } from "zod";
|
|
3911
3956
|
|
|
3912
3957
|
// ../../packages/lib/src/server/module-rail/reconcile.ts
|
|
3913
3958
|
var SANDBOX_BATCH_SIZE = 200;
|
|
@@ -3948,8 +3993,8 @@ function countedCompositionContext(context) {
|
|
|
3948
3993
|
}
|
|
3949
3994
|
};
|
|
3950
3995
|
}
|
|
3951
|
-
async function listPresentModuleNames(
|
|
3952
|
-
const entries = await listImmediateEntries(
|
|
3996
|
+
async function listPresentModuleNames(sandbox) {
|
|
3997
|
+
const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
|
|
3953
3998
|
optional: true,
|
|
3954
3999
|
label: "modules"
|
|
3955
4000
|
});
|
|
@@ -4153,24 +4198,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
4153
4198
|
}
|
|
4154
4199
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
4155
4200
|
}
|
|
4156
|
-
async function writeSandboxFileAtomic(
|
|
4201
|
+
async function writeSandboxFileAtomic(target, path3, content) {
|
|
4157
4202
|
const dir = dirnamePosix(path3);
|
|
4158
|
-
const tmp = `${path3}.tmp.${
|
|
4203
|
+
const tmp = `${path3}.tmp.${target.runId}`;
|
|
4159
4204
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
4160
4205
|
const cmd = [
|
|
4161
4206
|
`mkdir -p ${shellQuote(dir)}`,
|
|
4162
4207
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
4163
4208
|
`mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
|
|
4164
4209
|
].join(" && ");
|
|
4165
|
-
const result = await
|
|
4210
|
+
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
4166
4211
|
if (result.exitCode !== 0) {
|
|
4167
|
-
const cleanup = await
|
|
4212
|
+
const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
|
|
4168
4213
|
raiseOnError: false
|
|
4169
4214
|
});
|
|
4170
4215
|
if (cleanup.exitCode !== 0) {
|
|
4171
4216
|
const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
|
|
4172
4217
|
console.log(
|
|
4173
|
-
`[ModuleRail] runId=${
|
|
4218
|
+
`[ModuleRail] runId=${target.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
|
|
4174
4219
|
);
|
|
4175
4220
|
}
|
|
4176
4221
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
@@ -4228,6 +4273,40 @@ async function deleteSandboxFile(context, path3) {
|
|
|
4228
4273
|
);
|
|
4229
4274
|
}
|
|
4230
4275
|
}
|
|
4276
|
+
async function writeComposeInputs(target) {
|
|
4277
|
+
const ignored = await target.sandbox.exec(
|
|
4278
|
+
`git check-ignore -q ${shellQuote(COMPOSE_INPUTS_PATH)}`,
|
|
4279
|
+
{ raiseOnError: false }
|
|
4280
|
+
);
|
|
4281
|
+
if (ignored.exitCode === 1) {
|
|
4282
|
+
console.log(
|
|
4283
|
+
`[ComposeInputs] project=${target.projectId} skipped: ${COMPOSE_INPUTS_PATH} is not ignored in this checkout`
|
|
4284
|
+
);
|
|
4285
|
+
return false;
|
|
4286
|
+
}
|
|
4287
|
+
if (ignored.exitCode !== 0) {
|
|
4288
|
+
const detail = typeof ignored.error === "string" && ignored.error.trim() || ignored.output.trim() || "(no output)";
|
|
4289
|
+
throw new Error(
|
|
4290
|
+
`[ComposeInputs] git check-ignore failed in this checkout (exit ${ignored.exitCode}): ${redactSecrets(detail)}`
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
4293
|
+
const moduleNames = await listPresentModuleNames(target.sandbox);
|
|
4294
|
+
const inputs = await buildComposeInputs({
|
|
4295
|
+
db: target.db,
|
|
4296
|
+
projectId: target.projectId,
|
|
4297
|
+
deps: target.deps,
|
|
4298
|
+
moduleNames
|
|
4299
|
+
});
|
|
4300
|
+
const content = `${JSON.stringify(inputs, null, 2)}
|
|
4301
|
+
`;
|
|
4302
|
+
const existing = await target.sandbox.fileExists(COMPOSE_INPUTS_PATH) ? await target.sandbox.readFile(COMPOSE_INPUTS_PATH) : null;
|
|
4303
|
+
const written = existing === null || existing.trimEnd() !== content.trimEnd();
|
|
4304
|
+
if (written) await writeSandboxFileAtomic(target, COMPOSE_INPUTS_PATH, content);
|
|
4305
|
+
console.log(
|
|
4306
|
+
`[ComposeInputs] project=${target.projectId} stores=${inputs.connectedStores.length} installs=${Object.keys(inputs.installs).length} written=${written}`
|
|
4307
|
+
);
|
|
4308
|
+
return written;
|
|
4309
|
+
}
|
|
4231
4310
|
async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
4232
4311
|
const appliedWrites = [];
|
|
4233
4312
|
const appliedDeletes = [];
|
|
@@ -4863,7 +4942,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
4863
4942
|
context = counted.context;
|
|
4864
4943
|
const deps = context.deps;
|
|
4865
4944
|
if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
|
|
4866
|
-
const presentRaw = await listPresentModuleNames(context);
|
|
4945
|
+
const presentRaw = await listPresentModuleNames(context.sandbox);
|
|
4867
4946
|
const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
|
|
4868
4947
|
const moduleFacts = [];
|
|
4869
4948
|
const manifestsByName = {};
|
|
@@ -5083,6 +5162,15 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
5083
5162
|
console.log(
|
|
5084
5163
|
`[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}`
|
|
5085
5164
|
);
|
|
5165
|
+
if (!options.dryRun && !options.skipComposeInputs) {
|
|
5166
|
+
await writeComposeInputs({
|
|
5167
|
+
db: context.db,
|
|
5168
|
+
projectId: context.projectId,
|
|
5169
|
+
runId: context.runId,
|
|
5170
|
+
sandbox: context.sandbox,
|
|
5171
|
+
deps
|
|
5172
|
+
});
|
|
5173
|
+
}
|
|
5086
5174
|
if (changed && !options.dryRun) {
|
|
5087
5175
|
await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
|
|
5088
5176
|
}
|
|
@@ -5090,6 +5178,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
5090
5178
|
registryPath: planned.plan.registryPath,
|
|
5091
5179
|
writePaths: planned.plan.writes.map((w) => w.path),
|
|
5092
5180
|
deletePaths: planned.plan.deletes,
|
|
5181
|
+
artifactPaths: planned.plan.desiredPaths,
|
|
5093
5182
|
changed,
|
|
5094
5183
|
composedModules: composedNames,
|
|
5095
5184
|
excludedModules
|
|
@@ -5100,11 +5189,75 @@ var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
|
5100
5189
|
|
|
5101
5190
|
// src/index.ts
|
|
5102
5191
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
5192
|
+
function readComposeInputs(cwd) {
|
|
5193
|
+
const file = path2.join(cwd, COMPOSE_INPUTS_PATH);
|
|
5194
|
+
if (!fs2.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
5195
|
+
let raw;
|
|
5196
|
+
try {
|
|
5197
|
+
raw = JSON.parse(fs2.readFileSync(file, "utf8"));
|
|
5198
|
+
} catch (error) {
|
|
5199
|
+
throw new Error(
|
|
5200
|
+
`[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
5201
|
+
);
|
|
5202
|
+
}
|
|
5203
|
+
const parsed = composeInputsSchema.safeParse(raw);
|
|
5204
|
+
if (!parsed.success) {
|
|
5205
|
+
const issues = parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ");
|
|
5206
|
+
throw new Error(`[compose] ${file} is invalid: ${issues}`);
|
|
5207
|
+
}
|
|
5208
|
+
return {
|
|
5209
|
+
connectedStores: parsed.data.connectedStores.map((store) => ({
|
|
5210
|
+
storeId: store.storeId,
|
|
5211
|
+
slug: store.slug,
|
|
5212
|
+
bindingKey: store.bindingKey ?? null,
|
|
5213
|
+
accessLevel: store.accessLevel
|
|
5214
|
+
})),
|
|
5215
|
+
installs: parsed.data.installs
|
|
5216
|
+
};
|
|
5217
|
+
}
|
|
5218
|
+
function assertSupportedTypescript(versionMajorMinor) {
|
|
5219
|
+
const major = Number.parseInt(versionMajorMinor.split(".")[0] ?? "", 10);
|
|
5220
|
+
if (!Number.isInteger(major) || major < 5 || major >= 7) {
|
|
5221
|
+
throw new Error(`[compose] typescript ${versionMajorMinor} is not supported (need >=5 <7)`);
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
var APP_DIR_PREFIX2 = "apps/web/src/app/";
|
|
5225
|
+
var APP_PACKAGE_JSON = "apps/web/package.json";
|
|
5226
|
+
var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
5227
|
+
var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
5228
|
+
function escapeGitignore(pattern) {
|
|
5229
|
+
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
5230
|
+
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
5231
|
+
}
|
|
5232
|
+
function stubGitignoreEntries(artifactPaths) {
|
|
5233
|
+
return artifactPaths.filter((artifact) => artifact.startsWith(APP_DIR_PREFIX2)).map((artifact) => escapeGitignore(`/${artifact.slice(APP_DIR_PREFIX2.length)}`)).sort();
|
|
5234
|
+
}
|
|
5235
|
+
function writeStubGitignore(cwd, artifactPaths) {
|
|
5236
|
+
const entries = stubGitignoreEntries(artifactPaths);
|
|
5237
|
+
const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
5238
|
+
const file = path2.join(cwd, STUB_GITIGNORE_PATH);
|
|
5239
|
+
const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf8") : null;
|
|
5240
|
+
if (existing !== content) {
|
|
5241
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
5242
|
+
fs2.writeFileSync(file, content);
|
|
5243
|
+
}
|
|
5244
|
+
return entries.length;
|
|
5245
|
+
}
|
|
5246
|
+
function assertStubGitignoreOwned(cwd) {
|
|
5247
|
+
const file = path2.join(cwd, STUB_GITIGNORE_PATH);
|
|
5248
|
+
if (!fs2.existsSync(file)) return;
|
|
5249
|
+
const firstLine = fs2.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
|
|
5250
|
+
if (firstLine !== STUB_GITIGNORE_HEADER) {
|
|
5251
|
+
throw new Error(
|
|
5252
|
+
`[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
5253
|
+
);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5103
5256
|
function assertAppRoot(cwd) {
|
|
5104
|
-
const
|
|
5105
|
-
if (!fs2.existsSync(
|
|
5257
|
+
const appPath = path2.join(cwd, APP_PACKAGE_JSON);
|
|
5258
|
+
if (!fs2.existsSync(appPath)) {
|
|
5106
5259
|
throw new Error(
|
|
5107
|
-
`[compose] ${
|
|
5260
|
+
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
5108
5261
|
);
|
|
5109
5262
|
}
|
|
5110
5263
|
}
|
|
@@ -5184,12 +5337,14 @@ function removeDir(absolute) {
|
|
|
5184
5337
|
}
|
|
5185
5338
|
async function compose(options) {
|
|
5186
5339
|
assertAppRoot(options.cwd);
|
|
5340
|
+
assertStubGitignoreOwned(options.cwd);
|
|
5341
|
+
const inputs = readComposeInputs(options.cwd);
|
|
5187
5342
|
if (options.prune) assertModuleTreeClean(options.cwd);
|
|
5188
5343
|
const sandbox = localFsSandbox(options.cwd);
|
|
5189
5344
|
const deps = {
|
|
5190
5345
|
sandbox,
|
|
5191
|
-
getModuleInstall: async () => null,
|
|
5192
|
-
listDatabaseStoresForProject: async () =>
|
|
5346
|
+
getModuleInstall: async (_db, _projectId, moduleName) => inputs.installs[moduleName] ?? null,
|
|
5347
|
+
listDatabaseStoresForProject: async () => inputs.connectedStores
|
|
5193
5348
|
};
|
|
5194
5349
|
const context = {
|
|
5195
5350
|
db: {},
|
|
@@ -5206,8 +5361,12 @@ async function compose(options) {
|
|
|
5206
5361
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
5207
5362
|
}
|
|
5208
5363
|
const result = await reconcileCompositionArtifacts(context, {
|
|
5209
|
-
enabledModulesRaw: options.enabled
|
|
5364
|
+
enabledModulesRaw: options.enabled,
|
|
5365
|
+
// compose is the inputs file's reader: the deps above echo it back, so
|
|
5366
|
+
// letting the rail rewrite it would round-trip a fork's own input.
|
|
5367
|
+
skipComposeInputs: true
|
|
5210
5368
|
});
|
|
5369
|
+
const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
|
|
5211
5370
|
let pruned = 0;
|
|
5212
5371
|
if (options.prune) {
|
|
5213
5372
|
for (const name of result.excludedModules) {
|
|
@@ -5224,9 +5383,14 @@ async function compose(options) {
|
|
|
5224
5383
|
excludedModules: result.excludedModules,
|
|
5225
5384
|
writes: result.writePaths.length,
|
|
5226
5385
|
deletes: result.deletePaths.length,
|
|
5227
|
-
pruned
|
|
5386
|
+
pruned,
|
|
5387
|
+
gitignoreEntries
|
|
5228
5388
|
};
|
|
5229
5389
|
}
|
|
5230
5390
|
export {
|
|
5231
|
-
|
|
5391
|
+
COMPOSE_INPUTS_PATH,
|
|
5392
|
+
assertSupportedTypescript,
|
|
5393
|
+
compose,
|
|
5394
|
+
escapeGitignore,
|
|
5395
|
+
readComposeInputs
|
|
5232
5396
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/compose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Regenerates a Stardeck app's Module-rail artifacts — the five src/*.gen.ts registries and every route/endpoint stub — from src/modules/*/module.json",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"zod": "^4.3.5"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"typescript": ">=5.0.0"
|
|
54
|
+
"typescript": ">=5.0.0 <7"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@eslint/js": "^9.0.0",
|