@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/SKILL.md +12 -6
- package/dist/cli.js +289 -161
- package/dist/index.d.mts +39 -21
- package/dist/index.d.ts +39 -21
- package/dist/index.js +289 -161
- package/dist/index.mjs +289 -161
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -314,6 +314,14 @@ var datastoreRequirementSchema = import_zod.z.object({
|
|
|
314
314
|
id: tableNameSchema,
|
|
315
315
|
/** "app" = this project's app-local store; "org" = an org-level shared store. */
|
|
316
316
|
scope: import_zod.z.enum(["app", "org"]).default("app"),
|
|
317
|
+
/**
|
|
318
|
+
* Which kind of connected store satisfies this requirement. A "database"
|
|
319
|
+
* requirement only ever binds to a database store and a "storage" one
|
|
320
|
+
* (file/object storage — uploads, images) only to a storage store; the
|
|
321
|
+
* rail never crosses the two. Defaults to "database", the only kind the
|
|
322
|
+
* rail resolved before this field existed.
|
|
323
|
+
*/
|
|
324
|
+
type: import_zod.z.enum(["database", "storage"]).default("database"),
|
|
317
325
|
/** true → install fails when no store resolves; false → binding omitted, module degrades. */
|
|
318
326
|
required: import_zod.z.boolean().default(true),
|
|
319
327
|
/** Checked against the connected store's accessLevel at install. */
|
|
@@ -2067,6 +2075,44 @@ var initialModuleCompositionSchema = import_zod6.z.object({
|
|
|
2067
2075
|
});
|
|
2068
2076
|
var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
|
|
2069
2077
|
|
|
2078
|
+
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
2079
|
+
var import_zod7 = require("zod");
|
|
2080
|
+
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
2081
|
+
var composeInputsSchema = import_zod7.z.strictObject({
|
|
2082
|
+
connectedStores: import_zod7.z.array(
|
|
2083
|
+
import_zod7.z.strictObject({
|
|
2084
|
+
storeId: import_zod7.z.string().min(1),
|
|
2085
|
+
slug: import_zod7.z.string().min(1),
|
|
2086
|
+
// A row with an empty binding key must not fail a deploy: the rail
|
|
2087
|
+
// treats falsy as "no binding key", so normalise rather than reject.
|
|
2088
|
+
bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
|
|
2089
|
+
accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
|
|
2090
|
+
// Defaulted so a file written before the platform emitted store types
|
|
2091
|
+
// (database rows only) still parses. The platform starts writing
|
|
2092
|
+
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
2093
|
+
type: import_zod7.z.enum(["database", "storage"]).default("database")
|
|
2094
|
+
})
|
|
2095
|
+
).default([]),
|
|
2096
|
+
installs: import_zod7.z.record(
|
|
2097
|
+
import_zod7.z.string(),
|
|
2098
|
+
import_zod7.z.strictObject({
|
|
2099
|
+
installedVersion: import_zod7.z.string().min(1),
|
|
2100
|
+
datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
|
|
2101
|
+
})
|
|
2102
|
+
).default({})
|
|
2103
|
+
});
|
|
2104
|
+
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
2105
|
+
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
2106
|
+
function escapeStubIgnorePattern(pattern) {
|
|
2107
|
+
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
2108
|
+
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
2109
|
+
}
|
|
2110
|
+
function buildStubIgnore(artifactPaths) {
|
|
2111
|
+
const prefix = "apps/web/src/app/";
|
|
2112
|
+
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
2113
|
+
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2070
2116
|
// ../../packages/lib/src/shared/module-enablement.ts
|
|
2071
2117
|
function isModuleEnabledByList(raw, name, kindOf) {
|
|
2072
2118
|
if (!raw) return true;
|
|
@@ -3568,6 +3614,33 @@ function dirnamePosix(filePath) {
|
|
|
3568
3614
|
return idx <= 0 ? "." : filePath.slice(0, idx);
|
|
3569
3615
|
}
|
|
3570
3616
|
|
|
3617
|
+
// ../../packages/lib/src/server/module-rail/compose-inputs.ts
|
|
3618
|
+
async function buildComposeInputs(input) {
|
|
3619
|
+
const { db, projectId, deps } = input;
|
|
3620
|
+
const connectedStores = (await deps.listConnectedStoresForProject(db, projectId)).filter(
|
|
3621
|
+
(store) => store.type === "database"
|
|
3622
|
+
);
|
|
3623
|
+
const installRows = await Promise.all(
|
|
3624
|
+
input.moduleNames.map(async (moduleName) => ({
|
|
3625
|
+
moduleName,
|
|
3626
|
+
install: await deps.getModuleInstall(db, projectId, moduleName)
|
|
3627
|
+
}))
|
|
3628
|
+
);
|
|
3629
|
+
const installs = {};
|
|
3630
|
+
for (const { moduleName, install } of installRows) {
|
|
3631
|
+
if (!install) continue;
|
|
3632
|
+
installs[moduleName] = {
|
|
3633
|
+
installedVersion: install.installedVersion,
|
|
3634
|
+
datastoreBindings: install.datastoreBindings ?? null
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
const parsed = composeInputsSchema.parse({ connectedStores, installs });
|
|
3638
|
+
return {
|
|
3639
|
+
...parsed,
|
|
3640
|
+
connectedStores: parsed.connectedStores.map(({ type: _type, ...store }) => store)
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3571
3644
|
// ../../packages/lib/src/shared/data-store-manifest.ts
|
|
3572
3645
|
function dataStoreSlug(name) {
|
|
3573
3646
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -3591,7 +3664,7 @@ function compareStores(a, b) {
|
|
|
3591
3664
|
function describeCandidates(stores) {
|
|
3592
3665
|
if (stores.length === 0) return "none";
|
|
3593
3666
|
return stores.map(
|
|
3594
|
-
(store) => `${store.storeId} (slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
|
|
3667
|
+
(store) => `${store.storeId} (type=${store.type}, slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
|
|
3595
3668
|
).join(", ");
|
|
3596
3669
|
}
|
|
3597
3670
|
function groupStoresById(stores) {
|
|
@@ -3622,14 +3695,15 @@ function accessLevelForBinding(group, binding) {
|
|
|
3622
3695
|
);
|
|
3623
3696
|
}
|
|
3624
3697
|
function resolveModuleDataStoreBindings(input) {
|
|
3625
|
-
const
|
|
3626
|
-
const storeGroups = groupStoresById(stores);
|
|
3698
|
+
const allStores = [...input.connectedStores].sort(compareStores);
|
|
3627
3699
|
const requirements = [...input.requirements].sort((a, b) => compareStrings(a.id, b.id));
|
|
3628
3700
|
const overrides = input.explicitOverrides ?? {};
|
|
3629
3701
|
const bindings = {};
|
|
3630
3702
|
const errors = [];
|
|
3631
|
-
const candidates = describeCandidates(stores);
|
|
3632
3703
|
for (const requirement of requirements) {
|
|
3704
|
+
const stores = allStores.filter((store) => store.type === requirement.type);
|
|
3705
|
+
const storeGroups = groupStoresById(stores);
|
|
3706
|
+
const candidates = describeCandidates(allStores);
|
|
3633
3707
|
const logicalSlug = dataStoreSlug(requirement.id);
|
|
3634
3708
|
let selected;
|
|
3635
3709
|
if (Object.prototype.hasOwnProperty.call(overrides, requirement.id)) {
|
|
@@ -3642,7 +3716,7 @@ function resolveModuleDataStoreBindings(input) {
|
|
|
3642
3716
|
moduleName: input.moduleName,
|
|
3643
3717
|
logicalId: requirement.id,
|
|
3644
3718
|
kind: "explicit-override",
|
|
3645
|
-
message: matches.length === 0 ? `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" did not match a connected
|
|
3719
|
+
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))}`
|
|
3646
3720
|
});
|
|
3647
3721
|
continue;
|
|
3648
3722
|
}
|
|
@@ -3653,6 +3727,8 @@ function resolveModuleDataStoreBindings(input) {
|
|
|
3653
3727
|
);
|
|
3654
3728
|
if (conventionMatches.length === 1) {
|
|
3655
3729
|
selected = conventionMatches[0];
|
|
3730
|
+
} else if (conventionMatches.length === 0 && requirement.type === "storage") {
|
|
3731
|
+
if (storeGroups.length === 1) selected = storeGroups[0];
|
|
3656
3732
|
} else if (conventionMatches.length === 0 && requirement.scope === "app") {
|
|
3657
3733
|
if (storeGroups.length === 1) {
|
|
3658
3734
|
selected = storeGroups[0];
|
|
@@ -3764,162 +3840,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
3764
3840
|
}
|
|
3765
3841
|
|
|
3766
3842
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
3767
|
-
var
|
|
3768
|
-
var columnSpecSchema =
|
|
3769
|
-
name:
|
|
3770
|
-
pgType:
|
|
3771
|
-
nullable:
|
|
3843
|
+
var import_zod8 = require("zod");
|
|
3844
|
+
var columnSpecSchema = import_zod8.z.object({
|
|
3845
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3846
|
+
pgType: import_zod8.z.string().min(1),
|
|
3847
|
+
nullable: import_zod8.z.boolean(),
|
|
3772
3848
|
/**
|
|
3773
3849
|
* A SQL expression used as the column default, as a raw string (e.g. `'t'`,
|
|
3774
3850
|
* `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
|
|
3775
3851
|
* NOT a JS literal — must already be a valid SQL expression.
|
|
3776
3852
|
*/
|
|
3777
|
-
default:
|
|
3778
|
-
fieldType:
|
|
3779
|
-
fieldConfig:
|
|
3853
|
+
default: import_zod8.z.string().nullable().optional(),
|
|
3854
|
+
fieldType: import_zod8.z.string().optional(),
|
|
3855
|
+
fieldConfig: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
|
|
3780
3856
|
});
|
|
3781
|
-
var referentialActionSchema =
|
|
3857
|
+
var referentialActionSchema = import_zod8.z.enum([
|
|
3782
3858
|
"cascade",
|
|
3783
3859
|
"set null",
|
|
3784
3860
|
"set default",
|
|
3785
3861
|
"restrict",
|
|
3786
3862
|
"no action"
|
|
3787
3863
|
]);
|
|
3788
|
-
var schemaOpSchema =
|
|
3864
|
+
var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
|
|
3789
3865
|
// Tables
|
|
3790
|
-
|
|
3791
|
-
type:
|
|
3792
|
-
table:
|
|
3793
|
-
columns:
|
|
3794
|
-
primaryKey:
|
|
3795
|
-
ifNotExists:
|
|
3866
|
+
import_zod8.z.object({
|
|
3867
|
+
type: import_zod8.z.literal("createTable"),
|
|
3868
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3869
|
+
columns: import_zod8.z.array(columnSpecSchema).min(1),
|
|
3870
|
+
primaryKey: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
3871
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3796
3872
|
}),
|
|
3797
|
-
|
|
3798
|
-
type:
|
|
3799
|
-
table:
|
|
3800
|
-
cascade:
|
|
3801
|
-
ifExists:
|
|
3873
|
+
import_zod8.z.object({
|
|
3874
|
+
type: import_zod8.z.literal("dropTable"),
|
|
3875
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3876
|
+
cascade: import_zod8.z.boolean().optional(),
|
|
3877
|
+
ifExists: import_zod8.z.boolean().optional()
|
|
3802
3878
|
}),
|
|
3803
|
-
|
|
3804
|
-
type:
|
|
3805
|
-
from:
|
|
3806
|
-
to:
|
|
3879
|
+
import_zod8.z.object({
|
|
3880
|
+
type: import_zod8.z.literal("renameTable"),
|
|
3881
|
+
from: import_zod8.z.string().min(1).max(63),
|
|
3882
|
+
to: import_zod8.z.string().min(1).max(63)
|
|
3807
3883
|
}),
|
|
3808
3884
|
// Columns
|
|
3809
|
-
|
|
3810
|
-
type:
|
|
3811
|
-
table:
|
|
3885
|
+
import_zod8.z.object({
|
|
3886
|
+
type: import_zod8.z.literal("addColumn"),
|
|
3887
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3812
3888
|
column: columnSpecSchema,
|
|
3813
|
-
ifNotExists:
|
|
3889
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3814
3890
|
}),
|
|
3815
|
-
|
|
3816
|
-
type:
|
|
3817
|
-
table:
|
|
3818
|
-
column:
|
|
3819
|
-
cascade:
|
|
3820
|
-
ifExists:
|
|
3891
|
+
import_zod8.z.object({
|
|
3892
|
+
type: import_zod8.z.literal("dropColumn"),
|
|
3893
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3894
|
+
column: import_zod8.z.string().min(1).max(63),
|
|
3895
|
+
cascade: import_zod8.z.boolean().optional(),
|
|
3896
|
+
ifExists: import_zod8.z.boolean().optional()
|
|
3821
3897
|
}),
|
|
3822
|
-
|
|
3823
|
-
type:
|
|
3824
|
-
table:
|
|
3825
|
-
from:
|
|
3826
|
-
to:
|
|
3898
|
+
import_zod8.z.object({
|
|
3899
|
+
type: import_zod8.z.literal("renameColumn"),
|
|
3900
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3901
|
+
from: import_zod8.z.string().min(1).max(63),
|
|
3902
|
+
to: import_zod8.z.string().min(1).max(63)
|
|
3827
3903
|
}),
|
|
3828
|
-
|
|
3829
|
-
type:
|
|
3830
|
-
table:
|
|
3831
|
-
column:
|
|
3832
|
-
newType:
|
|
3904
|
+
import_zod8.z.object({
|
|
3905
|
+
type: import_zod8.z.literal("alterColumnType"),
|
|
3906
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3907
|
+
column: import_zod8.z.string().min(1).max(63),
|
|
3908
|
+
newType: import_zod8.z.string().min(1),
|
|
3833
3909
|
/** Optional USING expression for potentially-lossy casts */
|
|
3834
|
-
using:
|
|
3910
|
+
using: import_zod8.z.string().optional()
|
|
3835
3911
|
}),
|
|
3836
|
-
|
|
3837
|
-
type:
|
|
3838
|
-
table:
|
|
3839
|
-
column:
|
|
3840
|
-
nullable:
|
|
3912
|
+
import_zod8.z.object({
|
|
3913
|
+
type: import_zod8.z.literal("alterColumnNullable"),
|
|
3914
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3915
|
+
column: import_zod8.z.string().min(1).max(63),
|
|
3916
|
+
nullable: import_zod8.z.boolean()
|
|
3841
3917
|
}),
|
|
3842
|
-
|
|
3843
|
-
type:
|
|
3844
|
-
table:
|
|
3845
|
-
column:
|
|
3918
|
+
import_zod8.z.object({
|
|
3919
|
+
type: import_zod8.z.literal("alterColumnDefault"),
|
|
3920
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3921
|
+
column: import_zod8.z.string().min(1).max(63),
|
|
3846
3922
|
/** null means drop the default; otherwise a SQL expression string. */
|
|
3847
|
-
default:
|
|
3923
|
+
default: import_zod8.z.string().nullable()
|
|
3848
3924
|
}),
|
|
3849
3925
|
// Indexes
|
|
3850
|
-
|
|
3851
|
-
type:
|
|
3852
|
-
table:
|
|
3853
|
-
name:
|
|
3854
|
-
columns:
|
|
3855
|
-
unique:
|
|
3926
|
+
import_zod8.z.object({
|
|
3927
|
+
type: import_zod8.z.literal("addIndex"),
|
|
3928
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3929
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3930
|
+
columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
|
|
3931
|
+
unique: import_zod8.z.boolean().optional(),
|
|
3856
3932
|
/** Partial-index WHERE clause, as a raw SQL expression */
|
|
3857
|
-
where:
|
|
3858
|
-
ifNotExists:
|
|
3933
|
+
where: import_zod8.z.string().optional(),
|
|
3934
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3859
3935
|
}),
|
|
3860
|
-
|
|
3861
|
-
type:
|
|
3862
|
-
name:
|
|
3863
|
-
cascade:
|
|
3864
|
-
ifExists:
|
|
3936
|
+
import_zod8.z.object({
|
|
3937
|
+
type: import_zod8.z.literal("dropIndex"),
|
|
3938
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3939
|
+
cascade: import_zod8.z.boolean().optional(),
|
|
3940
|
+
ifExists: import_zod8.z.boolean().optional()
|
|
3865
3941
|
}),
|
|
3866
3942
|
// Constraints
|
|
3867
|
-
|
|
3868
|
-
type:
|
|
3869
|
-
table:
|
|
3870
|
-
name:
|
|
3871
|
-
columns:
|
|
3872
|
-
refTable:
|
|
3873
|
-
refColumns:
|
|
3943
|
+
import_zod8.z.object({
|
|
3944
|
+
type: import_zod8.z.literal("addForeignKey"),
|
|
3945
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3946
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3947
|
+
columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
|
|
3948
|
+
refTable: import_zod8.z.string().min(1).max(63),
|
|
3949
|
+
refColumns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1),
|
|
3874
3950
|
onDelete: referentialActionSchema.optional(),
|
|
3875
3951
|
onUpdate: referentialActionSchema.optional()
|
|
3876
3952
|
}),
|
|
3877
|
-
|
|
3878
|
-
type:
|
|
3879
|
-
table:
|
|
3880
|
-
name:
|
|
3881
|
-
columns:
|
|
3953
|
+
import_zod8.z.object({
|
|
3954
|
+
type: import_zod8.z.literal("addUniqueConstraint"),
|
|
3955
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3956
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3957
|
+
columns: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
|
|
3882
3958
|
}),
|
|
3883
|
-
|
|
3884
|
-
type:
|
|
3885
|
-
table:
|
|
3886
|
-
name:
|
|
3887
|
-
expression:
|
|
3959
|
+
import_zod8.z.object({
|
|
3960
|
+
type: import_zod8.z.literal("addCheckConstraint"),
|
|
3961
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3962
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3963
|
+
expression: import_zod8.z.string().min(1)
|
|
3888
3964
|
}),
|
|
3889
|
-
|
|
3890
|
-
type:
|
|
3891
|
-
table:
|
|
3892
|
-
name:
|
|
3893
|
-
cascade:
|
|
3894
|
-
ifExists:
|
|
3965
|
+
import_zod8.z.object({
|
|
3966
|
+
type: import_zod8.z.literal("dropConstraint"),
|
|
3967
|
+
table: import_zod8.z.string().min(1).max(63),
|
|
3968
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3969
|
+
cascade: import_zod8.z.boolean().optional(),
|
|
3970
|
+
ifExists: import_zod8.z.boolean().optional()
|
|
3895
3971
|
}),
|
|
3896
3972
|
// Enum types
|
|
3897
|
-
|
|
3898
|
-
type:
|
|
3899
|
-
name:
|
|
3900
|
-
values:
|
|
3973
|
+
import_zod8.z.object({
|
|
3974
|
+
type: import_zod8.z.literal("createEnum"),
|
|
3975
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3976
|
+
values: import_zod8.z.array(import_zod8.z.string().min(1)).min(1)
|
|
3901
3977
|
}),
|
|
3902
|
-
|
|
3903
|
-
type:
|
|
3904
|
-
name:
|
|
3905
|
-
value:
|
|
3978
|
+
import_zod8.z.object({
|
|
3979
|
+
type: import_zod8.z.literal("alterEnumAddValue"),
|
|
3980
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3981
|
+
value: import_zod8.z.string().min(1),
|
|
3906
3982
|
/** Position the new value before an existing one (mutually exclusive with `after`) */
|
|
3907
|
-
before:
|
|
3983
|
+
before: import_zod8.z.string().optional(),
|
|
3908
3984
|
/** Position the new value after an existing one (mutually exclusive with `before`) */
|
|
3909
|
-
after:
|
|
3910
|
-
ifNotExists:
|
|
3985
|
+
after: import_zod8.z.string().optional(),
|
|
3986
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3911
3987
|
}),
|
|
3912
|
-
|
|
3913
|
-
type:
|
|
3914
|
-
name:
|
|
3915
|
-
cascade:
|
|
3916
|
-
ifExists:
|
|
3988
|
+
import_zod8.z.object({
|
|
3989
|
+
type: import_zod8.z.literal("dropEnum"),
|
|
3990
|
+
name: import_zod8.z.string().min(1).max(63),
|
|
3991
|
+
cascade: import_zod8.z.boolean().optional(),
|
|
3992
|
+
ifExists: import_zod8.z.boolean().optional()
|
|
3917
3993
|
}),
|
|
3918
3994
|
// Extensions
|
|
3919
|
-
|
|
3920
|
-
type:
|
|
3921
|
-
name:
|
|
3922
|
-
ifNotExists:
|
|
3995
|
+
import_zod8.z.object({
|
|
3996
|
+
type: import_zod8.z.literal("createExtension"),
|
|
3997
|
+
name: import_zod8.z.string().min(1),
|
|
3998
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3923
3999
|
}),
|
|
3924
4000
|
// Data operations
|
|
3925
4001
|
/**
|
|
@@ -3927,26 +4003,26 @@ var schemaOpSchema = import_zod7.z.discriminatedUnion("type", [
|
|
|
3927
4003
|
* destructive — requires explicit confirm to replay on prod. The
|
|
3928
4004
|
* `description` is what the agent/user sees at confirm time.
|
|
3929
4005
|
*/
|
|
3930
|
-
|
|
3931
|
-
type:
|
|
3932
|
-
description:
|
|
3933
|
-
sql:
|
|
4006
|
+
import_zod8.z.object({
|
|
4007
|
+
type: import_zod8.z.literal("backfill"),
|
|
4008
|
+
description: import_zod8.z.string().min(1),
|
|
4009
|
+
sql: import_zod8.z.string().min(1)
|
|
3934
4010
|
}),
|
|
3935
4011
|
/**
|
|
3936
4012
|
* Escape hatch for DDL the structured ops can't express. `destructive`
|
|
3937
4013
|
* forces classification — use false only for genuinely additive-only SQL
|
|
3938
4014
|
* (e.g. CREATE EXTENSION variants the structured op doesn't cover).
|
|
3939
4015
|
*/
|
|
3940
|
-
|
|
3941
|
-
type:
|
|
3942
|
-
description:
|
|
3943
|
-
sql:
|
|
3944
|
-
destructive:
|
|
4016
|
+
import_zod8.z.object({
|
|
4017
|
+
type: import_zod8.z.literal("rawSql"),
|
|
4018
|
+
description: import_zod8.z.string().min(1),
|
|
4019
|
+
sql: import_zod8.z.string().min(1),
|
|
4020
|
+
destructive: import_zod8.z.boolean()
|
|
3945
4021
|
})
|
|
3946
4022
|
]);
|
|
3947
4023
|
|
|
3948
4024
|
// ../../packages/lib/src/server/module-rail/ops.ts
|
|
3949
|
-
var
|
|
4025
|
+
var import_zod9 = require("zod");
|
|
3950
4026
|
|
|
3951
4027
|
// ../../packages/lib/src/server/module-rail/reconcile.ts
|
|
3952
4028
|
var SANDBOX_BATCH_SIZE = 200;
|
|
@@ -3987,8 +4063,8 @@ function countedCompositionContext(context) {
|
|
|
3987
4063
|
}
|
|
3988
4064
|
};
|
|
3989
4065
|
}
|
|
3990
|
-
async function listPresentModuleNames(
|
|
3991
|
-
const entries = await listImmediateEntries(
|
|
4066
|
+
async function listPresentModuleNames(sandbox) {
|
|
4067
|
+
const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
|
|
3992
4068
|
optional: true,
|
|
3993
4069
|
label: "modules"
|
|
3994
4070
|
});
|
|
@@ -4011,7 +4087,7 @@ async function resolveCompositionDataStoreBindings(context, presentNames, manife
|
|
|
4011
4087
|
if (modulesWithRequirements.length === 0) return bindingsByModule;
|
|
4012
4088
|
const deps = context.deps;
|
|
4013
4089
|
if (!deps) throw new Error("Module rail dependencies are required for data-store bindings");
|
|
4014
|
-
const connectedStores = await deps.
|
|
4090
|
+
const connectedStores = await deps.listConnectedStoresForProject(context.db, context.projectId);
|
|
4015
4091
|
const installs = await Promise.all(
|
|
4016
4092
|
modulesWithRequirements.map(
|
|
4017
4093
|
(moduleName) => deps.getModuleInstall(context.db, context.projectId, moduleName)
|
|
@@ -4192,24 +4268,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
4192
4268
|
}
|
|
4193
4269
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
4194
4270
|
}
|
|
4195
|
-
async function writeSandboxFileAtomic(
|
|
4271
|
+
async function writeSandboxFileAtomic(target, path3, content) {
|
|
4196
4272
|
const dir = dirnamePosix(path3);
|
|
4197
|
-
const tmp = `${path3}.tmp.${
|
|
4273
|
+
const tmp = `${path3}.tmp.${target.runId}`;
|
|
4198
4274
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
4199
4275
|
const cmd = [
|
|
4200
4276
|
`mkdir -p ${shellQuote(dir)}`,
|
|
4201
4277
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
4202
4278
|
`mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
|
|
4203
4279
|
].join(" && ");
|
|
4204
|
-
const result = await
|
|
4280
|
+
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
4205
4281
|
if (result.exitCode !== 0) {
|
|
4206
|
-
const cleanup = await
|
|
4282
|
+
const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
|
|
4207
4283
|
raiseOnError: false
|
|
4208
4284
|
});
|
|
4209
4285
|
if (cleanup.exitCode !== 0) {
|
|
4210
4286
|
const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
|
|
4211
4287
|
console.log(
|
|
4212
|
-
`[ModuleRail] runId=${
|
|
4288
|
+
`[ModuleRail] runId=${target.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
|
|
4213
4289
|
);
|
|
4214
4290
|
}
|
|
4215
4291
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
@@ -4267,6 +4343,55 @@ async function deleteSandboxFile(context, path3) {
|
|
|
4267
4343
|
);
|
|
4268
4344
|
}
|
|
4269
4345
|
}
|
|
4346
|
+
async function writeComposeInputs(target) {
|
|
4347
|
+
const ignored = await target.sandbox.exec(
|
|
4348
|
+
`git check-ignore -q ${shellQuote(COMPOSE_INPUTS_PATH)}`,
|
|
4349
|
+
{ raiseOnError: false }
|
|
4350
|
+
);
|
|
4351
|
+
if (ignored.exitCode === 1) {
|
|
4352
|
+
console.log(
|
|
4353
|
+
`[ComposeInputs] project=${target.projectId} skipped: ${COMPOSE_INPUTS_PATH} is not ignored in this checkout`
|
|
4354
|
+
);
|
|
4355
|
+
return false;
|
|
4356
|
+
}
|
|
4357
|
+
if (ignored.exitCode !== 0) {
|
|
4358
|
+
const detail = typeof ignored.error === "string" && ignored.error.trim() || ignored.output.trim() || "(no output)";
|
|
4359
|
+
throw new Error(
|
|
4360
|
+
`[ComposeInputs] git check-ignore failed in this checkout (exit ${ignored.exitCode}): ${redactSecrets(detail)}`
|
|
4361
|
+
);
|
|
4362
|
+
}
|
|
4363
|
+
const moduleNames = await listPresentModuleNames(target.sandbox);
|
|
4364
|
+
const inputs = await buildComposeInputs({
|
|
4365
|
+
db: target.db,
|
|
4366
|
+
projectId: target.projectId,
|
|
4367
|
+
deps: target.deps,
|
|
4368
|
+
moduleNames
|
|
4369
|
+
});
|
|
4370
|
+
const content = `${JSON.stringify(inputs, null, 2)}
|
|
4371
|
+
`;
|
|
4372
|
+
const existing = await target.sandbox.fileExists(COMPOSE_INPUTS_PATH) ? await target.sandbox.readFile(COMPOSE_INPUTS_PATH) : null;
|
|
4373
|
+
const written = existing === null || existing.trimEnd() !== content.trimEnd();
|
|
4374
|
+
if (written) await writeSandboxFileAtomic(target, COMPOSE_INPUTS_PATH, content);
|
|
4375
|
+
console.log(
|
|
4376
|
+
`[ComposeInputs] project=${target.projectId} stores=${inputs.connectedStores.length} installs=${Object.keys(inputs.installs).length} written=${written}`
|
|
4377
|
+
);
|
|
4378
|
+
return written;
|
|
4379
|
+
}
|
|
4380
|
+
async function readStubIgnore(context) {
|
|
4381
|
+
const existing = await context.sandbox.fileExists(STUB_IGNORE_PATH) ? await context.sandbox.readFile(STUB_IGNORE_PATH) : null;
|
|
4382
|
+
if (existing !== null && existing.split("\n", 1)[0] !== STUB_IGNORE_HEADER) {
|
|
4383
|
+
throw new Error(
|
|
4384
|
+
`[ModuleRail] refusing to overwrite ${STUB_IGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
4385
|
+
);
|
|
4386
|
+
}
|
|
4387
|
+
return existing;
|
|
4388
|
+
}
|
|
4389
|
+
async function writeStubIgnore(context, existing, artifactPaths) {
|
|
4390
|
+
const content = buildStubIgnore(artifactPaths);
|
|
4391
|
+
const written = existing === null || existing.trimEnd() !== content.trimEnd();
|
|
4392
|
+
if (written) await writeSandboxFileAtomic(context, STUB_IGNORE_PATH, content);
|
|
4393
|
+
return written;
|
|
4394
|
+
}
|
|
4270
4395
|
async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
4271
4396
|
const appliedWrites = [];
|
|
4272
4397
|
const appliedDeletes = [];
|
|
@@ -4902,7 +5027,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
4902
5027
|
context = counted.context;
|
|
4903
5028
|
const deps = context.deps;
|
|
4904
5029
|
if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
|
|
4905
|
-
const presentRaw = await listPresentModuleNames(context);
|
|
5030
|
+
const presentRaw = await listPresentModuleNames(context.sandbox);
|
|
4906
5031
|
const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
|
|
4907
5032
|
const moduleFacts = [];
|
|
4908
5033
|
const manifestsByName = {};
|
|
@@ -5122,9 +5247,26 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
5122
5247
|
console.log(
|
|
5123
5248
|
`[ModuleRail] runId=${context.runId} composition artifacts writes=${planned.plan.writes.length} deletes=${planned.plan.deletes.length} modules=${registry.modules.length} excluded=${excludedModules.length} changed=${changed} dryRun=${options.dryRun === true} sandboxExecs=${counted.counts.sandboxExecs} sandboxReads=${counted.counts.sandboxReads}`
|
|
5124
5249
|
);
|
|
5250
|
+
if (!options.dryRun && !options.skipComposeInputs) {
|
|
5251
|
+
await writeComposeInputs({
|
|
5252
|
+
db: context.db,
|
|
5253
|
+
projectId: context.projectId,
|
|
5254
|
+
runId: context.runId,
|
|
5255
|
+
sandbox: context.sandbox,
|
|
5256
|
+
deps
|
|
5257
|
+
});
|
|
5258
|
+
}
|
|
5259
|
+
const maintainIgnore = !options.dryRun && !options.enabledModulesRaw;
|
|
5260
|
+
const existingIgnore = maintainIgnore ? await readStubIgnore(context) : null;
|
|
5125
5261
|
if (changed && !options.dryRun) {
|
|
5126
5262
|
await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
|
|
5127
5263
|
}
|
|
5264
|
+
if (maintainIgnore) {
|
|
5265
|
+
const wroteIgnore = await writeStubIgnore(context, existingIgnore, planned.plan.desiredPaths);
|
|
5266
|
+
console.log(
|
|
5267
|
+
`[ModuleRail] runId=${context.runId} stub ignore list entries=${planned.plan.desiredPaths.length} written=${wroteIgnore}`
|
|
5268
|
+
);
|
|
5269
|
+
}
|
|
5128
5270
|
return {
|
|
5129
5271
|
registryPath: planned.plan.registryPath,
|
|
5130
5272
|
writePaths: planned.plan.writes.map((w) => w.path),
|
|
@@ -5139,26 +5281,7 @@ var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
|
5139
5281
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
5140
5282
|
|
|
5141
5283
|
// src/index.ts
|
|
5142
|
-
var import_zod9 = require("zod");
|
|
5143
5284
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
5144
|
-
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
5145
|
-
var composeInputsSchema = import_zod9.z.strictObject({
|
|
5146
|
-
connectedStores: import_zod9.z.array(
|
|
5147
|
-
import_zod9.z.strictObject({
|
|
5148
|
-
storeId: import_zod9.z.string().min(1),
|
|
5149
|
-
slug: import_zod9.z.string().min(1),
|
|
5150
|
-
bindingKey: import_zod9.z.string().min(1).nullish(),
|
|
5151
|
-
accessLevel: import_zod9.z.enum(["read", "write", "admin"])
|
|
5152
|
-
})
|
|
5153
|
-
).default([]),
|
|
5154
|
-
installs: import_zod9.z.record(
|
|
5155
|
-
import_zod9.z.string(),
|
|
5156
|
-
import_zod9.z.strictObject({
|
|
5157
|
-
installedVersion: import_zod9.z.string().min(1),
|
|
5158
|
-
datastoreBindings: import_zod9.z.record(import_zod9.z.string(), import_zod9.z.string()).nullish()
|
|
5159
|
-
})
|
|
5160
|
-
).default({})
|
|
5161
|
-
});
|
|
5162
5285
|
function readComposeInputs(cwd) {
|
|
5163
5286
|
const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
|
|
5164
5287
|
if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
@@ -5180,7 +5303,8 @@ function readComposeInputs(cwd) {
|
|
|
5180
5303
|
storeId: store.storeId,
|
|
5181
5304
|
slug: store.slug,
|
|
5182
5305
|
bindingKey: store.bindingKey ?? null,
|
|
5183
|
-
accessLevel: store.accessLevel
|
|
5306
|
+
accessLevel: store.accessLevel,
|
|
5307
|
+
type: store.type
|
|
5184
5308
|
})),
|
|
5185
5309
|
installs: parsed.data.installs
|
|
5186
5310
|
};
|
|
@@ -5192,6 +5316,7 @@ function assertSupportedTypescript(versionMajorMinor) {
|
|
|
5192
5316
|
}
|
|
5193
5317
|
}
|
|
5194
5318
|
var APP_DIR_PREFIX2 = "apps/web/src/app/";
|
|
5319
|
+
var APP_PACKAGE_JSON = "apps/web/package.json";
|
|
5195
5320
|
var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
5196
5321
|
var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
5197
5322
|
function escapeGitignore(pattern) {
|
|
@@ -5223,10 +5348,10 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
5223
5348
|
}
|
|
5224
5349
|
}
|
|
5225
5350
|
function assertAppRoot(cwd) {
|
|
5226
|
-
const
|
|
5227
|
-
if (!import_node_fs2.default.existsSync(
|
|
5351
|
+
const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
|
|
5352
|
+
if (!import_node_fs2.default.existsSync(appPath)) {
|
|
5228
5353
|
throw new Error(
|
|
5229
|
-
`[compose] ${
|
|
5354
|
+
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
5230
5355
|
);
|
|
5231
5356
|
}
|
|
5232
5357
|
}
|
|
@@ -5313,7 +5438,7 @@ async function compose(options) {
|
|
|
5313
5438
|
const deps = {
|
|
5314
5439
|
sandbox,
|
|
5315
5440
|
getModuleInstall: async (_db, _projectId, moduleName) => inputs.installs[moduleName] ?? null,
|
|
5316
|
-
|
|
5441
|
+
listConnectedStoresForProject: async () => inputs.connectedStores
|
|
5317
5442
|
};
|
|
5318
5443
|
const context = {
|
|
5319
5444
|
db: {},
|
|
@@ -5330,7 +5455,10 @@ async function compose(options) {
|
|
|
5330
5455
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
5331
5456
|
}
|
|
5332
5457
|
const result = await reconcileCompositionArtifacts(context, {
|
|
5333
|
-
enabledModulesRaw: options.enabled
|
|
5458
|
+
enabledModulesRaw: options.enabled,
|
|
5459
|
+
// compose is the inputs file's reader: the deps above echo it back, so
|
|
5460
|
+
// letting the rail rewrite it would round-trip a fork's own input.
|
|
5461
|
+
skipComposeInputs: true
|
|
5334
5462
|
});
|
|
5335
5463
|
const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
|
|
5336
5464
|
let pruned = 0;
|