@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/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
|
|
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
|
|
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
|
|
3759
|
-
var columnSpecSchema =
|
|
3760
|
-
name:
|
|
3761
|
-
pgType:
|
|
3762
|
-
nullable:
|
|
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:
|
|
3769
|
-
fieldType:
|
|
3770
|
-
fieldConfig:
|
|
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 =
|
|
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 =
|
|
3855
|
+
var schemaOpSchema = import_zod8.z.discriminatedUnion("type", [
|
|
3780
3856
|
// Tables
|
|
3781
|
-
|
|
3782
|
-
type:
|
|
3783
|
-
table:
|
|
3784
|
-
columns:
|
|
3785
|
-
primaryKey:
|
|
3786
|
-
ifNotExists:
|
|
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
|
-
|
|
3789
|
-
type:
|
|
3790
|
-
table:
|
|
3791
|
-
cascade:
|
|
3792
|
-
ifExists:
|
|
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
|
-
|
|
3795
|
-
type:
|
|
3796
|
-
from:
|
|
3797
|
-
to:
|
|
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
|
-
|
|
3801
|
-
type:
|
|
3802
|
-
table:
|
|
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:
|
|
3880
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3805
3881
|
}),
|
|
3806
|
-
|
|
3807
|
-
type:
|
|
3808
|
-
table:
|
|
3809
|
-
column:
|
|
3810
|
-
cascade:
|
|
3811
|
-
ifExists:
|
|
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
|
-
|
|
3814
|
-
type:
|
|
3815
|
-
table:
|
|
3816
|
-
from:
|
|
3817
|
-
to:
|
|
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
|
-
|
|
3820
|
-
type:
|
|
3821
|
-
table:
|
|
3822
|
-
column:
|
|
3823
|
-
newType:
|
|
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:
|
|
3901
|
+
using: import_zod8.z.string().optional()
|
|
3826
3902
|
}),
|
|
3827
|
-
|
|
3828
|
-
type:
|
|
3829
|
-
table:
|
|
3830
|
-
column:
|
|
3831
|
-
nullable:
|
|
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
|
-
|
|
3834
|
-
type:
|
|
3835
|
-
table:
|
|
3836
|
-
column:
|
|
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:
|
|
3914
|
+
default: import_zod8.z.string().nullable()
|
|
3839
3915
|
}),
|
|
3840
3916
|
// Indexes
|
|
3841
|
-
|
|
3842
|
-
type:
|
|
3843
|
-
table:
|
|
3844
|
-
name:
|
|
3845
|
-
columns:
|
|
3846
|
-
unique:
|
|
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:
|
|
3849
|
-
ifNotExists:
|
|
3924
|
+
where: import_zod8.z.string().optional(),
|
|
3925
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3850
3926
|
}),
|
|
3851
|
-
|
|
3852
|
-
type:
|
|
3853
|
-
name:
|
|
3854
|
-
cascade:
|
|
3855
|
-
ifExists:
|
|
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
|
-
|
|
3859
|
-
type:
|
|
3860
|
-
table:
|
|
3861
|
-
name:
|
|
3862
|
-
columns:
|
|
3863
|
-
refTable:
|
|
3864
|
-
refColumns:
|
|
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
|
-
|
|
3869
|
-
type:
|
|
3870
|
-
table:
|
|
3871
|
-
name:
|
|
3872
|
-
columns:
|
|
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
|
-
|
|
3875
|
-
type:
|
|
3876
|
-
table:
|
|
3877
|
-
name:
|
|
3878
|
-
expression:
|
|
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
|
-
|
|
3881
|
-
type:
|
|
3882
|
-
table:
|
|
3883
|
-
name:
|
|
3884
|
-
cascade:
|
|
3885
|
-
ifExists:
|
|
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
|
-
|
|
3889
|
-
type:
|
|
3890
|
-
name:
|
|
3891
|
-
values:
|
|
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
|
-
|
|
3894
|
-
type:
|
|
3895
|
-
name:
|
|
3896
|
-
value:
|
|
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:
|
|
3974
|
+
before: import_zod8.z.string().optional(),
|
|
3899
3975
|
/** Position the new value after an existing one (mutually exclusive with `before`) */
|
|
3900
|
-
after:
|
|
3901
|
-
ifNotExists:
|
|
3976
|
+
after: import_zod8.z.string().optional(),
|
|
3977
|
+
ifNotExists: import_zod8.z.boolean().optional()
|
|
3902
3978
|
}),
|
|
3903
|
-
|
|
3904
|
-
type:
|
|
3905
|
-
name:
|
|
3906
|
-
cascade:
|
|
3907
|
-
ifExists:
|
|
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
|
-
|
|
3911
|
-
type:
|
|
3912
|
-
name:
|
|
3913
|
-
ifNotExists:
|
|
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
|
-
|
|
3922
|
-
type:
|
|
3923
|
-
description:
|
|
3924
|
-
sql:
|
|
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
|
-
|
|
3932
|
-
type:
|
|
3933
|
-
description:
|
|
3934
|
-
sql:
|
|
3935
|
-
destructive:
|
|
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
|
|
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(
|
|
3982
|
-
const entries = await listImmediateEntries(
|
|
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.
|
|
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(
|
|
4262
|
+
async function writeSandboxFileAtomic(target, path3, content) {
|
|
4187
4263
|
const dir = dirnamePosix(path3);
|
|
4188
|
-
const tmp = `${path3}.tmp.${
|
|
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
|
|
4271
|
+
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
4196
4272
|
if (result.exitCode !== 0) {
|
|
4197
|
-
const cleanup = await
|
|
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=${
|
|
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
|
|
5218
|
-
if (!import_node_fs2.default.existsSync(
|
|
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] ${
|
|
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
|
-
|
|
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;
|