@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.mjs
CHANGED
|
@@ -276,6 +276,14 @@ var datastoreRequirementSchema = z.object({
|
|
|
276
276
|
id: tableNameSchema,
|
|
277
277
|
/** "app" = this project's app-local store; "org" = an org-level shared store. */
|
|
278
278
|
scope: z.enum(["app", "org"]).default("app"),
|
|
279
|
+
/**
|
|
280
|
+
* Which kind of connected store satisfies this requirement. A "database"
|
|
281
|
+
* requirement only ever binds to a database store and a "storage" one
|
|
282
|
+
* (file/object storage — uploads, images) only to a storage store; the
|
|
283
|
+
* rail never crosses the two. Defaults to "database", the only kind the
|
|
284
|
+
* rail resolved before this field existed.
|
|
285
|
+
*/
|
|
286
|
+
type: z.enum(["database", "storage"]).default("database"),
|
|
279
287
|
/** true → install fails when no store resolves; false → binding omitted, module degrades. */
|
|
280
288
|
required: z.boolean().default(true),
|
|
281
289
|
/** Checked against the connected store's accessLevel at install. */
|
|
@@ -2029,6 +2037,44 @@ var initialModuleCompositionSchema = z6.object({
|
|
|
2029
2037
|
});
|
|
2030
2038
|
var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
|
|
2031
2039
|
|
|
2040
|
+
// ../../packages/lib/src/shared/compose-inputs.ts
|
|
2041
|
+
import { z as z7 } from "zod";
|
|
2042
|
+
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
2043
|
+
var composeInputsSchema = z7.strictObject({
|
|
2044
|
+
connectedStores: z7.array(
|
|
2045
|
+
z7.strictObject({
|
|
2046
|
+
storeId: z7.string().min(1),
|
|
2047
|
+
slug: z7.string().min(1),
|
|
2048
|
+
// A row with an empty binding key must not fail a deploy: the rail
|
|
2049
|
+
// treats falsy as "no binding key", so normalise rather than reject.
|
|
2050
|
+
bindingKey: z7.string().nullish().transform((value) => value || null),
|
|
2051
|
+
accessLevel: z7.enum(["read", "write", "admin"]),
|
|
2052
|
+
// Defaulted so a file written before the platform emitted store types
|
|
2053
|
+
// (database rows only) still parses. The platform starts writing
|
|
2054
|
+
// storage rows, with `type` set, once forks run a compose that reads it.
|
|
2055
|
+
type: z7.enum(["database", "storage"]).default("database")
|
|
2056
|
+
})
|
|
2057
|
+
).default([]),
|
|
2058
|
+
installs: z7.record(
|
|
2059
|
+
z7.string(),
|
|
2060
|
+
z7.strictObject({
|
|
2061
|
+
installedVersion: z7.string().min(1),
|
|
2062
|
+
datastoreBindings: z7.record(z7.string(), z7.string()).nullish()
|
|
2063
|
+
})
|
|
2064
|
+
).default({})
|
|
2065
|
+
});
|
|
2066
|
+
var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
2067
|
+
var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
2068
|
+
function escapeStubIgnorePattern(pattern) {
|
|
2069
|
+
const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
|
|
2070
|
+
return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
|
|
2071
|
+
}
|
|
2072
|
+
function buildStubIgnore(artifactPaths) {
|
|
2073
|
+
const prefix = "apps/web/src/app/";
|
|
2074
|
+
const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
|
|
2075
|
+
return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2032
2078
|
// ../../packages/lib/src/shared/module-enablement.ts
|
|
2033
2079
|
function isModuleEnabledByList(raw, name, kindOf) {
|
|
2034
2080
|
if (!raw) return true;
|
|
@@ -3530,6 +3576,33 @@ function dirnamePosix(filePath) {
|
|
|
3530
3576
|
return idx <= 0 ? "." : filePath.slice(0, idx);
|
|
3531
3577
|
}
|
|
3532
3578
|
|
|
3579
|
+
// ../../packages/lib/src/server/module-rail/compose-inputs.ts
|
|
3580
|
+
async function buildComposeInputs(input) {
|
|
3581
|
+
const { db, projectId, deps } = input;
|
|
3582
|
+
const connectedStores = (await deps.listConnectedStoresForProject(db, projectId)).filter(
|
|
3583
|
+
(store) => store.type === "database"
|
|
3584
|
+
);
|
|
3585
|
+
const installRows = await Promise.all(
|
|
3586
|
+
input.moduleNames.map(async (moduleName) => ({
|
|
3587
|
+
moduleName,
|
|
3588
|
+
install: await deps.getModuleInstall(db, projectId, moduleName)
|
|
3589
|
+
}))
|
|
3590
|
+
);
|
|
3591
|
+
const installs = {};
|
|
3592
|
+
for (const { moduleName, install } of installRows) {
|
|
3593
|
+
if (!install) continue;
|
|
3594
|
+
installs[moduleName] = {
|
|
3595
|
+
installedVersion: install.installedVersion,
|
|
3596
|
+
datastoreBindings: install.datastoreBindings ?? null
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
const parsed = composeInputsSchema.parse({ connectedStores, installs });
|
|
3600
|
+
return {
|
|
3601
|
+
...parsed,
|
|
3602
|
+
connectedStores: parsed.connectedStores.map(({ type: _type, ...store }) => store)
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3533
3606
|
// ../../packages/lib/src/shared/data-store-manifest.ts
|
|
3534
3607
|
function dataStoreSlug(name) {
|
|
3535
3608
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
@@ -3553,7 +3626,7 @@ function compareStores(a, b) {
|
|
|
3553
3626
|
function describeCandidates(stores) {
|
|
3554
3627
|
if (stores.length === 0) return "none";
|
|
3555
3628
|
return stores.map(
|
|
3556
|
-
(store) => `${store.storeId} (slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
|
|
3629
|
+
(store) => `${store.storeId} (type=${store.type}, slug=${store.slug}, bindingKey=${store.bindingKey ?? "none"}, access=${store.accessLevel})`
|
|
3557
3630
|
).join(", ");
|
|
3558
3631
|
}
|
|
3559
3632
|
function groupStoresById(stores) {
|
|
@@ -3584,14 +3657,15 @@ function accessLevelForBinding(group, binding) {
|
|
|
3584
3657
|
);
|
|
3585
3658
|
}
|
|
3586
3659
|
function resolveModuleDataStoreBindings(input) {
|
|
3587
|
-
const
|
|
3588
|
-
const storeGroups = groupStoresById(stores);
|
|
3660
|
+
const allStores = [...input.connectedStores].sort(compareStores);
|
|
3589
3661
|
const requirements = [...input.requirements].sort((a, b) => compareStrings(a.id, b.id));
|
|
3590
3662
|
const overrides = input.explicitOverrides ?? {};
|
|
3591
3663
|
const bindings = {};
|
|
3592
3664
|
const errors = [];
|
|
3593
|
-
const candidates = describeCandidates(stores);
|
|
3594
3665
|
for (const requirement of requirements) {
|
|
3666
|
+
const stores = allStores.filter((store) => store.type === requirement.type);
|
|
3667
|
+
const storeGroups = groupStoresById(stores);
|
|
3668
|
+
const candidates = describeCandidates(allStores);
|
|
3595
3669
|
const logicalSlug = dataStoreSlug(requirement.id);
|
|
3596
3670
|
let selected;
|
|
3597
3671
|
if (Object.prototype.hasOwnProperty.call(overrides, requirement.id)) {
|
|
@@ -3604,7 +3678,7 @@ function resolveModuleDataStoreBindings(input) {
|
|
|
3604
3678
|
moduleName: input.moduleName,
|
|
3605
3679
|
logicalId: requirement.id,
|
|
3606
3680
|
kind: "explicit-override",
|
|
3607
|
-
message: matches.length === 0 ? `Module "${input.moduleName}" data store "${requirement.id}" override "${override}" did not match a connected
|
|
3681
|
+
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))}`
|
|
3608
3682
|
});
|
|
3609
3683
|
continue;
|
|
3610
3684
|
}
|
|
@@ -3615,6 +3689,8 @@ function resolveModuleDataStoreBindings(input) {
|
|
|
3615
3689
|
);
|
|
3616
3690
|
if (conventionMatches.length === 1) {
|
|
3617
3691
|
selected = conventionMatches[0];
|
|
3692
|
+
} else if (conventionMatches.length === 0 && requirement.type === "storage") {
|
|
3693
|
+
if (storeGroups.length === 1) selected = storeGroups[0];
|
|
3618
3694
|
} else if (conventionMatches.length === 0 && requirement.scope === "app") {
|
|
3619
3695
|
if (storeGroups.length === 1) {
|
|
3620
3696
|
selected = storeGroups[0];
|
|
@@ -3726,162 +3802,162 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
|
|
|
3726
3802
|
}
|
|
3727
3803
|
|
|
3728
3804
|
// ../../packages/lib/src/shared/schema-ops.ts
|
|
3729
|
-
import { z as
|
|
3730
|
-
var columnSpecSchema =
|
|
3731
|
-
name:
|
|
3732
|
-
pgType:
|
|
3733
|
-
nullable:
|
|
3805
|
+
import { z as z8 } from "zod";
|
|
3806
|
+
var columnSpecSchema = z8.object({
|
|
3807
|
+
name: z8.string().min(1).max(63),
|
|
3808
|
+
pgType: z8.string().min(1),
|
|
3809
|
+
nullable: z8.boolean(),
|
|
3734
3810
|
/**
|
|
3735
3811
|
* A SQL expression used as the column default, as a raw string (e.g. `'t'`,
|
|
3736
3812
|
* `0`, `now()`, `gen_random_uuid()`). `null` or omitted means no default.
|
|
3737
3813
|
* NOT a JS literal — must already be a valid SQL expression.
|
|
3738
3814
|
*/
|
|
3739
|
-
default:
|
|
3740
|
-
fieldType:
|
|
3741
|
-
fieldConfig:
|
|
3815
|
+
default: z8.string().nullable().optional(),
|
|
3816
|
+
fieldType: z8.string().optional(),
|
|
3817
|
+
fieldConfig: z8.record(z8.string(), z8.unknown()).optional()
|
|
3742
3818
|
});
|
|
3743
|
-
var referentialActionSchema =
|
|
3819
|
+
var referentialActionSchema = z8.enum([
|
|
3744
3820
|
"cascade",
|
|
3745
3821
|
"set null",
|
|
3746
3822
|
"set default",
|
|
3747
3823
|
"restrict",
|
|
3748
3824
|
"no action"
|
|
3749
3825
|
]);
|
|
3750
|
-
var schemaOpSchema =
|
|
3826
|
+
var schemaOpSchema = z8.discriminatedUnion("type", [
|
|
3751
3827
|
// Tables
|
|
3752
|
-
|
|
3753
|
-
type:
|
|
3754
|
-
table:
|
|
3755
|
-
columns:
|
|
3756
|
-
primaryKey:
|
|
3757
|
-
ifNotExists:
|
|
3828
|
+
z8.object({
|
|
3829
|
+
type: z8.literal("createTable"),
|
|
3830
|
+
table: z8.string().min(1).max(63),
|
|
3831
|
+
columns: z8.array(columnSpecSchema).min(1),
|
|
3832
|
+
primaryKey: z8.array(z8.string()).optional(),
|
|
3833
|
+
ifNotExists: z8.boolean().optional()
|
|
3758
3834
|
}),
|
|
3759
|
-
|
|
3760
|
-
type:
|
|
3761
|
-
table:
|
|
3762
|
-
cascade:
|
|
3763
|
-
ifExists:
|
|
3835
|
+
z8.object({
|
|
3836
|
+
type: z8.literal("dropTable"),
|
|
3837
|
+
table: z8.string().min(1).max(63),
|
|
3838
|
+
cascade: z8.boolean().optional(),
|
|
3839
|
+
ifExists: z8.boolean().optional()
|
|
3764
3840
|
}),
|
|
3765
|
-
|
|
3766
|
-
type:
|
|
3767
|
-
from:
|
|
3768
|
-
to:
|
|
3841
|
+
z8.object({
|
|
3842
|
+
type: z8.literal("renameTable"),
|
|
3843
|
+
from: z8.string().min(1).max(63),
|
|
3844
|
+
to: z8.string().min(1).max(63)
|
|
3769
3845
|
}),
|
|
3770
3846
|
// Columns
|
|
3771
|
-
|
|
3772
|
-
type:
|
|
3773
|
-
table:
|
|
3847
|
+
z8.object({
|
|
3848
|
+
type: z8.literal("addColumn"),
|
|
3849
|
+
table: z8.string().min(1).max(63),
|
|
3774
3850
|
column: columnSpecSchema,
|
|
3775
|
-
ifNotExists:
|
|
3851
|
+
ifNotExists: z8.boolean().optional()
|
|
3776
3852
|
}),
|
|
3777
|
-
|
|
3778
|
-
type:
|
|
3779
|
-
table:
|
|
3780
|
-
column:
|
|
3781
|
-
cascade:
|
|
3782
|
-
ifExists:
|
|
3853
|
+
z8.object({
|
|
3854
|
+
type: z8.literal("dropColumn"),
|
|
3855
|
+
table: z8.string().min(1).max(63),
|
|
3856
|
+
column: z8.string().min(1).max(63),
|
|
3857
|
+
cascade: z8.boolean().optional(),
|
|
3858
|
+
ifExists: z8.boolean().optional()
|
|
3783
3859
|
}),
|
|
3784
|
-
|
|
3785
|
-
type:
|
|
3786
|
-
table:
|
|
3787
|
-
from:
|
|
3788
|
-
to:
|
|
3860
|
+
z8.object({
|
|
3861
|
+
type: z8.literal("renameColumn"),
|
|
3862
|
+
table: z8.string().min(1).max(63),
|
|
3863
|
+
from: z8.string().min(1).max(63),
|
|
3864
|
+
to: z8.string().min(1).max(63)
|
|
3789
3865
|
}),
|
|
3790
|
-
|
|
3791
|
-
type:
|
|
3792
|
-
table:
|
|
3793
|
-
column:
|
|
3794
|
-
newType:
|
|
3866
|
+
z8.object({
|
|
3867
|
+
type: z8.literal("alterColumnType"),
|
|
3868
|
+
table: z8.string().min(1).max(63),
|
|
3869
|
+
column: z8.string().min(1).max(63),
|
|
3870
|
+
newType: z8.string().min(1),
|
|
3795
3871
|
/** Optional USING expression for potentially-lossy casts */
|
|
3796
|
-
using:
|
|
3872
|
+
using: z8.string().optional()
|
|
3797
3873
|
}),
|
|
3798
|
-
|
|
3799
|
-
type:
|
|
3800
|
-
table:
|
|
3801
|
-
column:
|
|
3802
|
-
nullable:
|
|
3874
|
+
z8.object({
|
|
3875
|
+
type: z8.literal("alterColumnNullable"),
|
|
3876
|
+
table: z8.string().min(1).max(63),
|
|
3877
|
+
column: z8.string().min(1).max(63),
|
|
3878
|
+
nullable: z8.boolean()
|
|
3803
3879
|
}),
|
|
3804
|
-
|
|
3805
|
-
type:
|
|
3806
|
-
table:
|
|
3807
|
-
column:
|
|
3880
|
+
z8.object({
|
|
3881
|
+
type: z8.literal("alterColumnDefault"),
|
|
3882
|
+
table: z8.string().min(1).max(63),
|
|
3883
|
+
column: z8.string().min(1).max(63),
|
|
3808
3884
|
/** null means drop the default; otherwise a SQL expression string. */
|
|
3809
|
-
default:
|
|
3885
|
+
default: z8.string().nullable()
|
|
3810
3886
|
}),
|
|
3811
3887
|
// Indexes
|
|
3812
|
-
|
|
3813
|
-
type:
|
|
3814
|
-
table:
|
|
3815
|
-
name:
|
|
3816
|
-
columns:
|
|
3817
|
-
unique:
|
|
3888
|
+
z8.object({
|
|
3889
|
+
type: z8.literal("addIndex"),
|
|
3890
|
+
table: z8.string().min(1).max(63),
|
|
3891
|
+
name: z8.string().min(1).max(63),
|
|
3892
|
+
columns: z8.array(z8.string().min(1)).min(1),
|
|
3893
|
+
unique: z8.boolean().optional(),
|
|
3818
3894
|
/** Partial-index WHERE clause, as a raw SQL expression */
|
|
3819
|
-
where:
|
|
3820
|
-
ifNotExists:
|
|
3895
|
+
where: z8.string().optional(),
|
|
3896
|
+
ifNotExists: z8.boolean().optional()
|
|
3821
3897
|
}),
|
|
3822
|
-
|
|
3823
|
-
type:
|
|
3824
|
-
name:
|
|
3825
|
-
cascade:
|
|
3826
|
-
ifExists:
|
|
3898
|
+
z8.object({
|
|
3899
|
+
type: z8.literal("dropIndex"),
|
|
3900
|
+
name: z8.string().min(1).max(63),
|
|
3901
|
+
cascade: z8.boolean().optional(),
|
|
3902
|
+
ifExists: z8.boolean().optional()
|
|
3827
3903
|
}),
|
|
3828
3904
|
// Constraints
|
|
3829
|
-
|
|
3830
|
-
type:
|
|
3831
|
-
table:
|
|
3832
|
-
name:
|
|
3833
|
-
columns:
|
|
3834
|
-
refTable:
|
|
3835
|
-
refColumns:
|
|
3905
|
+
z8.object({
|
|
3906
|
+
type: z8.literal("addForeignKey"),
|
|
3907
|
+
table: z8.string().min(1).max(63),
|
|
3908
|
+
name: z8.string().min(1).max(63),
|
|
3909
|
+
columns: z8.array(z8.string().min(1)).min(1),
|
|
3910
|
+
refTable: z8.string().min(1).max(63),
|
|
3911
|
+
refColumns: z8.array(z8.string().min(1)).min(1),
|
|
3836
3912
|
onDelete: referentialActionSchema.optional(),
|
|
3837
3913
|
onUpdate: referentialActionSchema.optional()
|
|
3838
3914
|
}),
|
|
3839
|
-
|
|
3840
|
-
type:
|
|
3841
|
-
table:
|
|
3842
|
-
name:
|
|
3843
|
-
columns:
|
|
3915
|
+
z8.object({
|
|
3916
|
+
type: z8.literal("addUniqueConstraint"),
|
|
3917
|
+
table: z8.string().min(1).max(63),
|
|
3918
|
+
name: z8.string().min(1).max(63),
|
|
3919
|
+
columns: z8.array(z8.string().min(1)).min(1)
|
|
3844
3920
|
}),
|
|
3845
|
-
|
|
3846
|
-
type:
|
|
3847
|
-
table:
|
|
3848
|
-
name:
|
|
3849
|
-
expression:
|
|
3921
|
+
z8.object({
|
|
3922
|
+
type: z8.literal("addCheckConstraint"),
|
|
3923
|
+
table: z8.string().min(1).max(63),
|
|
3924
|
+
name: z8.string().min(1).max(63),
|
|
3925
|
+
expression: z8.string().min(1)
|
|
3850
3926
|
}),
|
|
3851
|
-
|
|
3852
|
-
type:
|
|
3853
|
-
table:
|
|
3854
|
-
name:
|
|
3855
|
-
cascade:
|
|
3856
|
-
ifExists:
|
|
3927
|
+
z8.object({
|
|
3928
|
+
type: z8.literal("dropConstraint"),
|
|
3929
|
+
table: z8.string().min(1).max(63),
|
|
3930
|
+
name: z8.string().min(1).max(63),
|
|
3931
|
+
cascade: z8.boolean().optional(),
|
|
3932
|
+
ifExists: z8.boolean().optional()
|
|
3857
3933
|
}),
|
|
3858
3934
|
// Enum types
|
|
3859
|
-
|
|
3860
|
-
type:
|
|
3861
|
-
name:
|
|
3862
|
-
values:
|
|
3935
|
+
z8.object({
|
|
3936
|
+
type: z8.literal("createEnum"),
|
|
3937
|
+
name: z8.string().min(1).max(63),
|
|
3938
|
+
values: z8.array(z8.string().min(1)).min(1)
|
|
3863
3939
|
}),
|
|
3864
|
-
|
|
3865
|
-
type:
|
|
3866
|
-
name:
|
|
3867
|
-
value:
|
|
3940
|
+
z8.object({
|
|
3941
|
+
type: z8.literal("alterEnumAddValue"),
|
|
3942
|
+
name: z8.string().min(1).max(63),
|
|
3943
|
+
value: z8.string().min(1),
|
|
3868
3944
|
/** Position the new value before an existing one (mutually exclusive with `after`) */
|
|
3869
|
-
before:
|
|
3945
|
+
before: z8.string().optional(),
|
|
3870
3946
|
/** Position the new value after an existing one (mutually exclusive with `before`) */
|
|
3871
|
-
after:
|
|
3872
|
-
ifNotExists:
|
|
3947
|
+
after: z8.string().optional(),
|
|
3948
|
+
ifNotExists: z8.boolean().optional()
|
|
3873
3949
|
}),
|
|
3874
|
-
|
|
3875
|
-
type:
|
|
3876
|
-
name:
|
|
3877
|
-
cascade:
|
|
3878
|
-
ifExists:
|
|
3950
|
+
z8.object({
|
|
3951
|
+
type: z8.literal("dropEnum"),
|
|
3952
|
+
name: z8.string().min(1).max(63),
|
|
3953
|
+
cascade: z8.boolean().optional(),
|
|
3954
|
+
ifExists: z8.boolean().optional()
|
|
3879
3955
|
}),
|
|
3880
3956
|
// Extensions
|
|
3881
|
-
|
|
3882
|
-
type:
|
|
3883
|
-
name:
|
|
3884
|
-
ifNotExists:
|
|
3957
|
+
z8.object({
|
|
3958
|
+
type: z8.literal("createExtension"),
|
|
3959
|
+
name: z8.string().min(1),
|
|
3960
|
+
ifNotExists: z8.boolean().optional()
|
|
3885
3961
|
}),
|
|
3886
3962
|
// Data operations
|
|
3887
3963
|
/**
|
|
@@ -3889,26 +3965,26 @@ var schemaOpSchema = z7.discriminatedUnion("type", [
|
|
|
3889
3965
|
* destructive — requires explicit confirm to replay on prod. The
|
|
3890
3966
|
* `description` is what the agent/user sees at confirm time.
|
|
3891
3967
|
*/
|
|
3892
|
-
|
|
3893
|
-
type:
|
|
3894
|
-
description:
|
|
3895
|
-
sql:
|
|
3968
|
+
z8.object({
|
|
3969
|
+
type: z8.literal("backfill"),
|
|
3970
|
+
description: z8.string().min(1),
|
|
3971
|
+
sql: z8.string().min(1)
|
|
3896
3972
|
}),
|
|
3897
3973
|
/**
|
|
3898
3974
|
* Escape hatch for DDL the structured ops can't express. `destructive`
|
|
3899
3975
|
* forces classification — use false only for genuinely additive-only SQL
|
|
3900
3976
|
* (e.g. CREATE EXTENSION variants the structured op doesn't cover).
|
|
3901
3977
|
*/
|
|
3902
|
-
|
|
3903
|
-
type:
|
|
3904
|
-
description:
|
|
3905
|
-
sql:
|
|
3906
|
-
destructive:
|
|
3978
|
+
z8.object({
|
|
3979
|
+
type: z8.literal("rawSql"),
|
|
3980
|
+
description: z8.string().min(1),
|
|
3981
|
+
sql: z8.string().min(1),
|
|
3982
|
+
destructive: z8.boolean()
|
|
3907
3983
|
})
|
|
3908
3984
|
]);
|
|
3909
3985
|
|
|
3910
3986
|
// ../../packages/lib/src/server/module-rail/ops.ts
|
|
3911
|
-
import { z as
|
|
3987
|
+
import { z as z9 } from "zod";
|
|
3912
3988
|
|
|
3913
3989
|
// ../../packages/lib/src/server/module-rail/reconcile.ts
|
|
3914
3990
|
var SANDBOX_BATCH_SIZE = 200;
|
|
@@ -3949,8 +4025,8 @@ function countedCompositionContext(context) {
|
|
|
3949
4025
|
}
|
|
3950
4026
|
};
|
|
3951
4027
|
}
|
|
3952
|
-
async function listPresentModuleNames(
|
|
3953
|
-
const entries = await listImmediateEntries(
|
|
4028
|
+
async function listPresentModuleNames(sandbox) {
|
|
4029
|
+
const entries = await listImmediateEntries(sandbox, MODULES_SANDBOX_DIR, {
|
|
3954
4030
|
optional: true,
|
|
3955
4031
|
label: "modules"
|
|
3956
4032
|
});
|
|
@@ -3973,7 +4049,7 @@ async function resolveCompositionDataStoreBindings(context, presentNames, manife
|
|
|
3973
4049
|
if (modulesWithRequirements.length === 0) return bindingsByModule;
|
|
3974
4050
|
const deps = context.deps;
|
|
3975
4051
|
if (!deps) throw new Error("Module rail dependencies are required for data-store bindings");
|
|
3976
|
-
const connectedStores = await deps.
|
|
4052
|
+
const connectedStores = await deps.listConnectedStoresForProject(context.db, context.projectId);
|
|
3977
4053
|
const installs = await Promise.all(
|
|
3978
4054
|
modulesWithRequirements.map(
|
|
3979
4055
|
(moduleName) => deps.getModuleInstall(context.db, context.projectId, moduleName)
|
|
@@ -4154,24 +4230,24 @@ async function discoverModuleRouteEntries(context, moduleName) {
|
|
|
4154
4230
|
}
|
|
4155
4231
|
return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
|
|
4156
4232
|
}
|
|
4157
|
-
async function writeSandboxFileAtomic(
|
|
4233
|
+
async function writeSandboxFileAtomic(target, path3, content) {
|
|
4158
4234
|
const dir = dirnamePosix(path3);
|
|
4159
|
-
const tmp = `${path3}.tmp.${
|
|
4235
|
+
const tmp = `${path3}.tmp.${target.runId}`;
|
|
4160
4236
|
const b64 = Buffer.from(content, "utf8").toString("base64");
|
|
4161
4237
|
const cmd = [
|
|
4162
4238
|
`mkdir -p ${shellQuote(dir)}`,
|
|
4163
4239
|
`printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
|
|
4164
4240
|
`mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
|
|
4165
4241
|
].join(" && ");
|
|
4166
|
-
const result = await
|
|
4242
|
+
const result = await target.sandbox.exec(cmd, { raiseOnError: false });
|
|
4167
4243
|
if (result.exitCode !== 0) {
|
|
4168
|
-
const cleanup = await
|
|
4244
|
+
const cleanup = await target.sandbox.exec(`rm -f ${shellQuote(tmp)}`, {
|
|
4169
4245
|
raiseOnError: false
|
|
4170
4246
|
});
|
|
4171
4247
|
if (cleanup.exitCode !== 0) {
|
|
4172
4248
|
const cleanupDetail = typeof cleanup.error === "string" && cleanup.error.trim() || cleanup.output.trim() || `exit ${cleanup.exitCode}`;
|
|
4173
4249
|
console.log(
|
|
4174
|
-
`[ModuleRail] runId=${
|
|
4250
|
+
`[ModuleRail] runId=${target.runId} composition artifacts failed to clean temp ${redactSecrets(tmp)}: ${redactSecrets(cleanupDetail)}`
|
|
4175
4251
|
);
|
|
4176
4252
|
}
|
|
4177
4253
|
const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
|
|
@@ -4229,6 +4305,55 @@ async function deleteSandboxFile(context, path3) {
|
|
|
4229
4305
|
);
|
|
4230
4306
|
}
|
|
4231
4307
|
}
|
|
4308
|
+
async function writeComposeInputs(target) {
|
|
4309
|
+
const ignored = await target.sandbox.exec(
|
|
4310
|
+
`git check-ignore -q ${shellQuote(COMPOSE_INPUTS_PATH)}`,
|
|
4311
|
+
{ raiseOnError: false }
|
|
4312
|
+
);
|
|
4313
|
+
if (ignored.exitCode === 1) {
|
|
4314
|
+
console.log(
|
|
4315
|
+
`[ComposeInputs] project=${target.projectId} skipped: ${COMPOSE_INPUTS_PATH} is not ignored in this checkout`
|
|
4316
|
+
);
|
|
4317
|
+
return false;
|
|
4318
|
+
}
|
|
4319
|
+
if (ignored.exitCode !== 0) {
|
|
4320
|
+
const detail = typeof ignored.error === "string" && ignored.error.trim() || ignored.output.trim() || "(no output)";
|
|
4321
|
+
throw new Error(
|
|
4322
|
+
`[ComposeInputs] git check-ignore failed in this checkout (exit ${ignored.exitCode}): ${redactSecrets(detail)}`
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
const moduleNames = await listPresentModuleNames(target.sandbox);
|
|
4326
|
+
const inputs = await buildComposeInputs({
|
|
4327
|
+
db: target.db,
|
|
4328
|
+
projectId: target.projectId,
|
|
4329
|
+
deps: target.deps,
|
|
4330
|
+
moduleNames
|
|
4331
|
+
});
|
|
4332
|
+
const content = `${JSON.stringify(inputs, null, 2)}
|
|
4333
|
+
`;
|
|
4334
|
+
const existing = await target.sandbox.fileExists(COMPOSE_INPUTS_PATH) ? await target.sandbox.readFile(COMPOSE_INPUTS_PATH) : null;
|
|
4335
|
+
const written = existing === null || existing.trimEnd() !== content.trimEnd();
|
|
4336
|
+
if (written) await writeSandboxFileAtomic(target, COMPOSE_INPUTS_PATH, content);
|
|
4337
|
+
console.log(
|
|
4338
|
+
`[ComposeInputs] project=${target.projectId} stores=${inputs.connectedStores.length} installs=${Object.keys(inputs.installs).length} written=${written}`
|
|
4339
|
+
);
|
|
4340
|
+
return written;
|
|
4341
|
+
}
|
|
4342
|
+
async function readStubIgnore(context) {
|
|
4343
|
+
const existing = await context.sandbox.fileExists(STUB_IGNORE_PATH) ? await context.sandbox.readFile(STUB_IGNORE_PATH) : null;
|
|
4344
|
+
if (existing !== null && existing.split("\n", 1)[0] !== STUB_IGNORE_HEADER) {
|
|
4345
|
+
throw new Error(
|
|
4346
|
+
`[ModuleRail] refusing to overwrite ${STUB_IGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
|
|
4347
|
+
);
|
|
4348
|
+
}
|
|
4349
|
+
return existing;
|
|
4350
|
+
}
|
|
4351
|
+
async function writeStubIgnore(context, existing, artifactPaths) {
|
|
4352
|
+
const content = buildStubIgnore(artifactPaths);
|
|
4353
|
+
const written = existing === null || existing.trimEnd() !== content.trimEnd();
|
|
4354
|
+
if (written) await writeSandboxFileAtomic(context, STUB_IGNORE_PATH, content);
|
|
4355
|
+
return written;
|
|
4356
|
+
}
|
|
4232
4357
|
async function applyCompositionArtifactMutations(context, plan, existingFiles) {
|
|
4233
4358
|
const appliedWrites = [];
|
|
4234
4359
|
const appliedDeletes = [];
|
|
@@ -4864,7 +4989,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
4864
4989
|
context = counted.context;
|
|
4865
4990
|
const deps = context.deps;
|
|
4866
4991
|
if (!deps) throw new Error("Module rail dependencies are required for composition artifacts");
|
|
4867
|
-
const presentRaw = await listPresentModuleNames(context);
|
|
4992
|
+
const presentRaw = await listPresentModuleNames(context.sandbox);
|
|
4868
4993
|
const presentNames = presentRaw.filter((name) => isValidModuleName(name)).sort((a, b) => a.localeCompare(b));
|
|
4869
4994
|
const moduleFacts = [];
|
|
4870
4995
|
const manifestsByName = {};
|
|
@@ -5084,9 +5209,26 @@ async function reconcileCompositionArtifacts(context, options = {}) {
|
|
|
5084
5209
|
console.log(
|
|
5085
5210
|
`[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}`
|
|
5086
5211
|
);
|
|
5212
|
+
if (!options.dryRun && !options.skipComposeInputs) {
|
|
5213
|
+
await writeComposeInputs({
|
|
5214
|
+
db: context.db,
|
|
5215
|
+
projectId: context.projectId,
|
|
5216
|
+
runId: context.runId,
|
|
5217
|
+
sandbox: context.sandbox,
|
|
5218
|
+
deps
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
const maintainIgnore = !options.dryRun && !options.enabledModulesRaw;
|
|
5222
|
+
const existingIgnore = maintainIgnore ? await readStubIgnore(context) : null;
|
|
5087
5223
|
if (changed && !options.dryRun) {
|
|
5088
5224
|
await applyCompositionArtifactMutations(context, planned.plan, existingFiles);
|
|
5089
5225
|
}
|
|
5226
|
+
if (maintainIgnore) {
|
|
5227
|
+
const wroteIgnore = await writeStubIgnore(context, existingIgnore, planned.plan.desiredPaths);
|
|
5228
|
+
console.log(
|
|
5229
|
+
`[ModuleRail] runId=${context.runId} stub ignore list entries=${planned.plan.desiredPaths.length} written=${wroteIgnore}`
|
|
5230
|
+
);
|
|
5231
|
+
}
|
|
5090
5232
|
return {
|
|
5091
5233
|
registryPath: planned.plan.registryPath,
|
|
5092
5234
|
writePaths: planned.plan.writes.map((w) => w.path),
|
|
@@ -5101,26 +5243,7 @@ var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
|
|
|
5101
5243
|
var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
|
|
5102
5244
|
|
|
5103
5245
|
// src/index.ts
|
|
5104
|
-
import { z as z9 } from "zod";
|
|
5105
5246
|
var MODULES_DIR2 = MODULES_SANDBOX_DIR;
|
|
5106
|
-
var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
|
|
5107
|
-
var composeInputsSchema = z9.strictObject({
|
|
5108
|
-
connectedStores: z9.array(
|
|
5109
|
-
z9.strictObject({
|
|
5110
|
-
storeId: z9.string().min(1),
|
|
5111
|
-
slug: z9.string().min(1),
|
|
5112
|
-
bindingKey: z9.string().min(1).nullish(),
|
|
5113
|
-
accessLevel: z9.enum(["read", "write", "admin"])
|
|
5114
|
-
})
|
|
5115
|
-
).default([]),
|
|
5116
|
-
installs: z9.record(
|
|
5117
|
-
z9.string(),
|
|
5118
|
-
z9.strictObject({
|
|
5119
|
-
installedVersion: z9.string().min(1),
|
|
5120
|
-
datastoreBindings: z9.record(z9.string(), z9.string()).nullish()
|
|
5121
|
-
})
|
|
5122
|
-
).default({})
|
|
5123
|
-
});
|
|
5124
5247
|
function readComposeInputs(cwd) {
|
|
5125
5248
|
const file = path2.join(cwd, COMPOSE_INPUTS_PATH);
|
|
5126
5249
|
if (!fs2.existsSync(file)) return { connectedStores: [], installs: {} };
|
|
@@ -5142,7 +5265,8 @@ function readComposeInputs(cwd) {
|
|
|
5142
5265
|
storeId: store.storeId,
|
|
5143
5266
|
slug: store.slug,
|
|
5144
5267
|
bindingKey: store.bindingKey ?? null,
|
|
5145
|
-
accessLevel: store.accessLevel
|
|
5268
|
+
accessLevel: store.accessLevel,
|
|
5269
|
+
type: store.type
|
|
5146
5270
|
})),
|
|
5147
5271
|
installs: parsed.data.installs
|
|
5148
5272
|
};
|
|
@@ -5154,6 +5278,7 @@ function assertSupportedTypescript(versionMajorMinor) {
|
|
|
5154
5278
|
}
|
|
5155
5279
|
}
|
|
5156
5280
|
var APP_DIR_PREFIX2 = "apps/web/src/app/";
|
|
5281
|
+
var APP_PACKAGE_JSON = "apps/web/package.json";
|
|
5157
5282
|
var STUB_GITIGNORE_PATH = "apps/web/src/app/.gitignore";
|
|
5158
5283
|
var STUB_GITIGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
|
|
5159
5284
|
function escapeGitignore(pattern) {
|
|
@@ -5185,10 +5310,10 @@ function assertStubGitignoreOwned(cwd) {
|
|
|
5185
5310
|
}
|
|
5186
5311
|
}
|
|
5187
5312
|
function assertAppRoot(cwd) {
|
|
5188
|
-
const
|
|
5189
|
-
if (!fs2.existsSync(
|
|
5313
|
+
const appPath = path2.join(cwd, APP_PACKAGE_JSON);
|
|
5314
|
+
if (!fs2.existsSync(appPath)) {
|
|
5190
5315
|
throw new Error(
|
|
5191
|
-
`[compose] ${
|
|
5316
|
+
`[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
|
|
5192
5317
|
);
|
|
5193
5318
|
}
|
|
5194
5319
|
}
|
|
@@ -5275,7 +5400,7 @@ async function compose(options) {
|
|
|
5275
5400
|
const deps = {
|
|
5276
5401
|
sandbox,
|
|
5277
5402
|
getModuleInstall: async (_db, _projectId, moduleName) => inputs.installs[moduleName] ?? null,
|
|
5278
|
-
|
|
5403
|
+
listConnectedStoresForProject: async () => inputs.connectedStores
|
|
5279
5404
|
};
|
|
5280
5405
|
const context = {
|
|
5281
5406
|
db: {},
|
|
@@ -5292,7 +5417,10 @@ async function compose(options) {
|
|
|
5292
5417
|
assertNoAppLayerImporters(options.cwd, preview.excludedModules);
|
|
5293
5418
|
}
|
|
5294
5419
|
const result = await reconcileCompositionArtifacts(context, {
|
|
5295
|
-
enabledModulesRaw: options.enabled
|
|
5420
|
+
enabledModulesRaw: options.enabled,
|
|
5421
|
+
// compose is the inputs file's reader: the deps above echo it back, so
|
|
5422
|
+
// letting the rail rewrite it would round-trip a fork's own input.
|
|
5423
|
+
skipComposeInputs: true
|
|
5296
5424
|
});
|
|
5297
5425
|
const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
|
|
5298
5426
|
let pruned = 0;
|