@augmenting-integrations/deploy-tools 8.7.0 → 8.9.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/README.md +18 -4
- package/dist/canonical/schema.d.ts +20 -0
- package/dist/canonical/schema.d.ts.map +1 -1
- package/dist/canonical/schema.js +8 -0
- package/dist/canonical/schema.js.map +1 -0
- package/dist/chunk-DN2IENY7.js +234 -0
- package/dist/chunk-DN2IENY7.js.map +1 -0
- package/dist/chunk-SYUDHJI7.js +142 -0
- package/dist/chunk-SYUDHJI7.js.map +1 -0
- package/dist/chunk-ZQE3CYBR.js +70 -0
- package/dist/chunk-ZQE3CYBR.js.map +1 -0
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/prisma-parser.d.ts +34 -4
- package/dist/prisma-parser.d.ts.map +1 -1
- package/dist/prisma-parser.js +10 -0
- package/dist/prisma-parser.js.map +1 -0
- package/dist/validate-spoke.d.ts +25 -0
- package/dist/validate-spoke.d.ts.map +1 -1
- package/dist/validate-spoke.js +7 -3
- package/package.json +17 -3
- package/dist/chunk-A7IIB34G.js +0 -230
- package/dist/chunk-A7IIB34G.js.map +0 -1
package/README.md
CHANGED
|
@@ -41,7 +41,21 @@ read. Specifically:
|
|
|
41
41
|
- When `features.billing`: `PaymentMethod`, `CreditTransaction`
|
|
42
42
|
- When `features.impersonation` or `features.invitations`: `ActivityLog`
|
|
43
43
|
|
|
44
|
-
Within each model, the
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
Within each model, the validator checks:
|
|
45
|
+
|
|
46
|
+
1. **Field name** must match the canonical name (e.g. `credit_balance`,
|
|
47
|
+
not `creditBalance` or `credits_balance`).
|
|
48
|
+
2. **Scalar type** must be in the compatible set
|
|
49
|
+
(`User.id` is `Int | BigInt`, `User.credit_balance` is
|
|
50
|
+
`Decimal | Float`, etc.). Native database types (`@db.VarChar`,
|
|
51
|
+
`@db.Decimal(...)`) are not validated.
|
|
52
|
+
3. **Single-column unique constraint** on canonical lookup keys:
|
|
53
|
+
`User.email`, `Invitation.token`, `PaymentMethod.stripe_payment_method_id`,
|
|
54
|
+
`CreditTransaction.stripe_payment_intent_id`. Composite `@@unique([...])`
|
|
55
|
+
does not satisfy this (the library handlers do
|
|
56
|
+
`findUnique({ where: { token: ... } })`).
|
|
57
|
+
|
|
58
|
+
Structural casts that bridge the generated Prisma client to the library
|
|
59
|
+
handler factories are isolated to `src/platform/repositories.ts` +
|
|
60
|
+
`src/platform/app-user.ts`. Product wiring (`src/lib/*.ts`, route
|
|
61
|
+
handlers) MUST NOT cast.
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { AppManifest } from "@augmenting-integrations/platform/manifest";
|
|
2
|
+
/**
|
|
3
|
+
* One of more compatible Prisma scalar types. We model compatibility as a
|
|
4
|
+
* set so a spoke can choose `Int` or `BigInt` for ids, `Decimal` or `Float`
|
|
5
|
+
* for money, etc.
|
|
6
|
+
*
|
|
7
|
+
* `*` means "any scalar type" -- used when the canonical contract only
|
|
8
|
+
* cares about the field's NAME existing (e.g. `metadata Json?` is the only
|
|
9
|
+
* supported shape but a spoke that picks `String` for a stringified-JSON
|
|
10
|
+
* column would still satisfy the handler).
|
|
11
|
+
*/
|
|
12
|
+
export type CompatibleTypes = readonly string[];
|
|
2
13
|
export type RequiredField = {
|
|
3
14
|
name: string;
|
|
4
15
|
/**
|
|
@@ -7,6 +18,15 @@ export type RequiredField = {
|
|
|
7
18
|
* found missing.
|
|
8
19
|
*/
|
|
9
20
|
reason: string;
|
|
21
|
+
/**
|
|
22
|
+
* Set of compatible Prisma scalar types. Empty means "any scalar".
|
|
23
|
+
*/
|
|
24
|
+
types: CompatibleTypes;
|
|
25
|
+
/**
|
|
26
|
+
* Must this field carry a single-column unique constraint? Implies
|
|
27
|
+
* the field is a stable lookup key for library handlers.
|
|
28
|
+
*/
|
|
29
|
+
unique?: boolean;
|
|
10
30
|
};
|
|
11
31
|
export type RequiredModel = {
|
|
12
32
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/canonical/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAE9E,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/canonical/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAE9E;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,MAAM,EAAE,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,EAAE,CAgOhF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/canonical/schema.ts
|
|
4
|
+
var ID = ["Int", "BigInt"];
|
|
5
|
+
var ID_NULLABLE = ID;
|
|
6
|
+
var MONEY = ["Decimal", "Float"];
|
|
7
|
+
var STRING = ["String"];
|
|
8
|
+
var BOOL = ["Boolean"];
|
|
9
|
+
var TIME = ["DateTime"];
|
|
10
|
+
var JSONISH = ["Json", "String"];
|
|
11
|
+
var ANY = [];
|
|
12
|
+
function requiredModelsForManifest(manifest) {
|
|
13
|
+
const out = [];
|
|
14
|
+
const userFields = [
|
|
15
|
+
{
|
|
16
|
+
name: "id",
|
|
17
|
+
reason: "JIT lookup + impersonation token claims",
|
|
18
|
+
types: ID
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "email",
|
|
22
|
+
reason: "JIT email lookup + auto-promote-to-admin",
|
|
23
|
+
types: STRING,
|
|
24
|
+
unique: true
|
|
25
|
+
},
|
|
26
|
+
{ name: "name", reason: "/api/auth/me, UserMenu display", types: STRING },
|
|
27
|
+
{ name: "role", reason: "JIT role resolution + admin gating", types: STRING },
|
|
28
|
+
{ name: "is_active", reason: "/api/auth/me response shape", types: BOOL },
|
|
29
|
+
{
|
|
30
|
+
name: "credit_balance",
|
|
31
|
+
reason: "JIT initial balance + /api/auth/me",
|
|
32
|
+
types: MONEY
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "password",
|
|
36
|
+
reason: "schema-inherited not-null filler at JIT create",
|
|
37
|
+
types: STRING
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "parent_id",
|
|
41
|
+
reason: "invitation auto-accept sets parent_id from invite",
|
|
42
|
+
types: ID_NULLABLE
|
|
43
|
+
},
|
|
44
|
+
{ name: "created_at", reason: "audit / ordering", types: TIME },
|
|
45
|
+
{ name: "updated_at", reason: "audit / ordering", types: TIME }
|
|
46
|
+
];
|
|
47
|
+
if (manifest.features.billing) {
|
|
48
|
+
userFields.push(
|
|
49
|
+
{
|
|
50
|
+
name: "stripe_customer_id",
|
|
51
|
+
reason: "createBillingHandlers user shape",
|
|
52
|
+
types: STRING
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "stripe_default_payment_method",
|
|
56
|
+
reason: "createBillingHandlers user shape",
|
|
57
|
+
types: STRING
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "stripe_default_payment_method_display",
|
|
61
|
+
reason: "createBillingHandlers user shape",
|
|
62
|
+
types: STRING
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "auto_recharge_enabled",
|
|
66
|
+
reason: "/api/billing/auto-recharge",
|
|
67
|
+
types: BOOL
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "auto_recharge_threshold",
|
|
71
|
+
reason: "/api/billing/auto-recharge",
|
|
72
|
+
types: MONEY
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "auto_recharge_amount",
|
|
76
|
+
reason: "/api/billing/auto-recharge",
|
|
77
|
+
types: MONEY
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
out.push({ name: "User", fields: userFields });
|
|
82
|
+
if (manifest.features.billing) {
|
|
83
|
+
out.push({
|
|
84
|
+
name: "PaymentMethod",
|
|
85
|
+
fields: [
|
|
86
|
+
{ name: "id", reason: "createBillingHandlers row shape", types: ID },
|
|
87
|
+
{
|
|
88
|
+
name: "user_id",
|
|
89
|
+
reason: "createBillingHandlers row shape",
|
|
90
|
+
types: ID
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "stripe_payment_method_id",
|
|
94
|
+
reason: "createBillingHandlers row shape (unique key)",
|
|
95
|
+
types: STRING,
|
|
96
|
+
unique: true
|
|
97
|
+
},
|
|
98
|
+
{ name: "brand", reason: "createBillingHandlers row shape", types: STRING },
|
|
99
|
+
{ name: "last4", reason: "createBillingHandlers row shape", types: STRING },
|
|
100
|
+
{
|
|
101
|
+
name: "exp_month",
|
|
102
|
+
reason: "createBillingHandlers row shape",
|
|
103
|
+
types: ["Int"]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "exp_year",
|
|
107
|
+
reason: "createBillingHandlers row shape",
|
|
108
|
+
types: ["Int"]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "is_default",
|
|
112
|
+
reason: "createBillingHandlers row shape",
|
|
113
|
+
types: BOOL
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "created_at",
|
|
117
|
+
reason: "createBillingHandlers row shape",
|
|
118
|
+
types: TIME
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
out.push({
|
|
123
|
+
name: "CreditTransaction",
|
|
124
|
+
fields: [
|
|
125
|
+
{ name: "id", reason: "createBillingHandlers row shape", types: ID },
|
|
126
|
+
{
|
|
127
|
+
name: "user_id",
|
|
128
|
+
reason: "createBillingHandlers row shape",
|
|
129
|
+
types: ID
|
|
130
|
+
},
|
|
131
|
+
// `type` is commonly an enum -- accept ANY scalar so spokes can use
|
|
132
|
+
// `String` or a Prisma enum interchangeably.
|
|
133
|
+
{ name: "type", reason: "createBillingHandlers row shape", types: ANY },
|
|
134
|
+
{
|
|
135
|
+
name: "amount",
|
|
136
|
+
reason: "createBillingHandlers row shape",
|
|
137
|
+
types: MONEY
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "description",
|
|
141
|
+
reason: "createBillingHandlers row shape",
|
|
142
|
+
types: STRING
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "stripe_payment_intent_id",
|
|
146
|
+
reason: "createBillingHandlers webhook idempotency",
|
|
147
|
+
types: STRING,
|
|
148
|
+
unique: true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "payment_method_display",
|
|
152
|
+
reason: "createBillingHandlers row shape",
|
|
153
|
+
types: STRING
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "created_at",
|
|
157
|
+
reason: "createBillingHandlers row shape",
|
|
158
|
+
types: TIME
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (manifest.features.invitations) {
|
|
164
|
+
out.push({
|
|
165
|
+
name: "Invitation",
|
|
166
|
+
fields: [
|
|
167
|
+
{
|
|
168
|
+
name: "id",
|
|
169
|
+
reason: "createInvitationHandlers + JIT invitation accept",
|
|
170
|
+
types: ID
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "token",
|
|
174
|
+
reason: "createInvitationHandlers findUnique",
|
|
175
|
+
types: STRING,
|
|
176
|
+
unique: true
|
|
177
|
+
},
|
|
178
|
+
{ name: "email", reason: "JIT invitation lookup", types: STRING },
|
|
179
|
+
{
|
|
180
|
+
name: "intended_role",
|
|
181
|
+
reason: "JIT role inheritance",
|
|
182
|
+
types: STRING
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "parent_id",
|
|
186
|
+
reason: "JIT parent_id inheritance",
|
|
187
|
+
types: ID_NULLABLE
|
|
188
|
+
},
|
|
189
|
+
{ name: "expires_at", reason: "JIT expiry filter", types: TIME },
|
|
190
|
+
{
|
|
191
|
+
name: "accepted_at",
|
|
192
|
+
reason: "JIT acceptance toggle",
|
|
193
|
+
types: TIME
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "accepted_by_user_id",
|
|
197
|
+
reason: "JIT writes acceptance with new user id",
|
|
198
|
+
types: ID_NULLABLE
|
|
199
|
+
},
|
|
200
|
+
{ name: "created_at", reason: "ordering", types: TIME }
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (manifest.features.impersonation || manifest.features.invitations) {
|
|
205
|
+
out.push({
|
|
206
|
+
name: "ActivityLog",
|
|
207
|
+
fields: [
|
|
208
|
+
{ name: "id", reason: "audit row", types: ID },
|
|
209
|
+
{
|
|
210
|
+
name: "user_id",
|
|
211
|
+
reason: "createImpersonateHandlers audit writes",
|
|
212
|
+
types: ID_NULLABLE
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "action",
|
|
216
|
+
reason: "createImpersonateHandlers audit writes",
|
|
217
|
+
types: STRING
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "metadata",
|
|
221
|
+
reason: "createImpersonateHandlers audit writes",
|
|
222
|
+
types: JSONISH
|
|
223
|
+
},
|
|
224
|
+
{ name: "created_at", reason: "audit row", types: TIME }
|
|
225
|
+
]
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export {
|
|
232
|
+
requiredModelsForManifest
|
|
233
|
+
};
|
|
234
|
+
//# sourceMappingURL=chunk-DN2IENY7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/canonical/schema.ts"],"sourcesContent":["// =============================================================================\n// Canonical schema contract.\n//\n// Encodes the Prisma model + field names + scalar types the library handler\n// factories read at runtime. The validator (`augint validate-spoke`) parses\n// the spoke's prisma/schema.prisma and asserts that every required model +\n// field for the manifest's enabled features is present with a compatible\n// scalar type. Required uniqueness is enforced for lookup keys.\n//\n// Drift in canonical naming AND type is the most common class of integration\n// bug. The validator catches it before `next build` runs.\n// =============================================================================\n\nimport type { AppManifest } from \"@augmenting-integrations/platform/manifest\";\n\n/**\n * One of more compatible Prisma scalar types. We model compatibility as a\n * set so a spoke can choose `Int` or `BigInt` for ids, `Decimal` or `Float`\n * for money, etc.\n *\n * `*` means \"any scalar type\" -- used when the canonical contract only\n * cares about the field's NAME existing (e.g. `metadata Json?` is the only\n * supported shape but a spoke that picks `String` for a stringified-JSON\n * column would still satisfy the handler).\n */\nexport type CompatibleTypes = readonly string[];\n\nexport type RequiredField = {\n name: string;\n /**\n * Reason this field is required (shown in the error message). Helps\n * developers understand WHY the validator cares, not just WHAT it\n * found missing.\n */\n reason: string;\n /**\n * Set of compatible Prisma scalar types. Empty means \"any scalar\".\n */\n types: CompatibleTypes;\n /**\n * Must this field carry a single-column unique constraint? Implies\n * the field is a stable lookup key for library handlers.\n */\n unique?: boolean;\n};\n\nexport type RequiredModel = {\n name: string;\n fields: RequiredField[];\n};\n\n// Common type aliases -- keep the per-field declarations terse.\nconst ID = [\"Int\", \"BigInt\"] as const;\nconst ID_NULLABLE = ID;\nconst MONEY = [\"Decimal\", \"Float\"] as const;\nconst STRING = [\"String\"] as const;\nconst BOOL = [\"Boolean\"] as const;\nconst TIME = [\"DateTime\"] as const;\nconst JSONISH = [\"Json\", \"String\"] as const;\nconst ANY: readonly string[] = [];\n\n/**\n * Returns the list of required models for a given manifest, in priority\n * order. Each model lists the fields its library consumers read.\n *\n * `User` is always required.\n * `Invitation` is required when features.invitations.\n * `PaymentMethod` + `CreditTransaction` are required when features.billing.\n * `ActivityLog` is required when features.impersonation or features.invitations.\n */\nexport function requiredModelsForManifest(manifest: AppManifest): RequiredModel[] {\n const out: RequiredModel[] = [];\n\n const userFields: RequiredField[] = [\n {\n name: \"id\",\n reason: \"JIT lookup + impersonation token claims\",\n types: ID,\n },\n {\n name: \"email\",\n reason: \"JIT email lookup + auto-promote-to-admin\",\n types: STRING,\n unique: true,\n },\n { name: \"name\", reason: \"/api/auth/me, UserMenu display\", types: STRING },\n { name: \"role\", reason: \"JIT role resolution + admin gating\", types: STRING },\n { name: \"is_active\", reason: \"/api/auth/me response shape\", types: BOOL },\n {\n name: \"credit_balance\",\n reason: \"JIT initial balance + /api/auth/me\",\n types: MONEY,\n },\n {\n name: \"password\",\n reason: \"schema-inherited not-null filler at JIT create\",\n types: STRING,\n },\n {\n name: \"parent_id\",\n reason: \"invitation auto-accept sets parent_id from invite\",\n types: ID_NULLABLE,\n },\n { name: \"created_at\", reason: \"audit / ordering\", types: TIME },\n { name: \"updated_at\", reason: \"audit / ordering\", types: TIME },\n ];\n\n if (manifest.features.billing) {\n userFields.push(\n {\n name: \"stripe_customer_id\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"stripe_default_payment_method\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"stripe_default_payment_method_display\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"auto_recharge_enabled\",\n reason: \"/api/billing/auto-recharge\",\n types: BOOL,\n },\n {\n name: \"auto_recharge_threshold\",\n reason: \"/api/billing/auto-recharge\",\n types: MONEY,\n },\n {\n name: \"auto_recharge_amount\",\n reason: \"/api/billing/auto-recharge\",\n types: MONEY,\n },\n );\n }\n\n out.push({ name: \"User\", fields: userFields });\n\n if (manifest.features.billing) {\n out.push({\n name: \"PaymentMethod\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\", types: ID },\n {\n name: \"user_id\",\n reason: \"createBillingHandlers row shape\",\n types: ID,\n },\n {\n name: \"stripe_payment_method_id\",\n reason: \"createBillingHandlers row shape (unique key)\",\n types: STRING,\n unique: true,\n },\n { name: \"brand\", reason: \"createBillingHandlers row shape\", types: STRING },\n { name: \"last4\", reason: \"createBillingHandlers row shape\", types: STRING },\n {\n name: \"exp_month\",\n reason: \"createBillingHandlers row shape\",\n types: [\"Int\"],\n },\n {\n name: \"exp_year\",\n reason: \"createBillingHandlers row shape\",\n types: [\"Int\"],\n },\n {\n name: \"is_default\",\n reason: \"createBillingHandlers row shape\",\n types: BOOL,\n },\n {\n name: \"created_at\",\n reason: \"createBillingHandlers row shape\",\n types: TIME,\n },\n ],\n });\n out.push({\n name: \"CreditTransaction\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\", types: ID },\n {\n name: \"user_id\",\n reason: \"createBillingHandlers row shape\",\n types: ID,\n },\n // `type` is commonly an enum -- accept ANY scalar so spokes can use\n // `String` or a Prisma enum interchangeably.\n { name: \"type\", reason: \"createBillingHandlers row shape\", types: ANY },\n {\n name: \"amount\",\n reason: \"createBillingHandlers row shape\",\n types: MONEY,\n },\n {\n name: \"description\",\n reason: \"createBillingHandlers row shape\",\n types: STRING,\n },\n {\n name: \"stripe_payment_intent_id\",\n reason: \"createBillingHandlers webhook idempotency\",\n types: STRING,\n unique: true,\n },\n {\n name: \"payment_method_display\",\n reason: \"createBillingHandlers row shape\",\n types: STRING,\n },\n {\n name: \"created_at\",\n reason: \"createBillingHandlers row shape\",\n types: TIME,\n },\n ],\n });\n }\n\n if (manifest.features.invitations) {\n out.push({\n name: \"Invitation\",\n fields: [\n {\n name: \"id\",\n reason: \"createInvitationHandlers + JIT invitation accept\",\n types: ID,\n },\n {\n name: \"token\",\n reason: \"createInvitationHandlers findUnique\",\n types: STRING,\n unique: true,\n },\n { name: \"email\", reason: \"JIT invitation lookup\", types: STRING },\n {\n name: \"intended_role\",\n reason: \"JIT role inheritance\",\n types: STRING,\n },\n {\n name: \"parent_id\",\n reason: \"JIT parent_id inheritance\",\n types: ID_NULLABLE,\n },\n { name: \"expires_at\", reason: \"JIT expiry filter\", types: TIME },\n {\n name: \"accepted_at\",\n reason: \"JIT acceptance toggle\",\n types: TIME,\n },\n {\n name: \"accepted_by_user_id\",\n reason: \"JIT writes acceptance with new user id\",\n types: ID_NULLABLE,\n },\n { name: \"created_at\", reason: \"ordering\", types: TIME },\n ],\n });\n }\n\n if (manifest.features.impersonation || manifest.features.invitations) {\n out.push({\n name: \"ActivityLog\",\n fields: [\n { name: \"id\", reason: \"audit row\", types: ID },\n {\n name: \"user_id\",\n reason: \"createImpersonateHandlers audit writes\",\n types: ID_NULLABLE,\n },\n {\n name: \"action\",\n reason: \"createImpersonateHandlers audit writes\",\n types: STRING,\n },\n {\n name: \"metadata\",\n reason: \"createImpersonateHandlers audit writes\",\n types: JSONISH,\n },\n { name: \"created_at\", reason: \"audit row\", types: TIME },\n ],\n });\n }\n\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
modelHasSingleColumnUnique,
|
|
4
|
+
parsePrismaSchema
|
|
5
|
+
} from "./chunk-ZQE3CYBR.js";
|
|
6
|
+
import {
|
|
7
|
+
requiredModelsForManifest
|
|
8
|
+
} from "./chunk-DN2IENY7.js";
|
|
9
|
+
|
|
10
|
+
// src/validate-spoke.ts
|
|
11
|
+
import { readFileSync, existsSync } from "fs";
|
|
12
|
+
import { resolve, join } from "path";
|
|
13
|
+
import process from "process";
|
|
14
|
+
import { loadManifest } from "@augmenting-integrations/platform/manifest";
|
|
15
|
+
function formatFailure(f) {
|
|
16
|
+
switch (f.kind) {
|
|
17
|
+
case "missing_model":
|
|
18
|
+
return ` missing model: ${f.model}`;
|
|
19
|
+
case "missing_field":
|
|
20
|
+
return ` ${f.model}.${f.field} missing (required by: ${f.reason})`;
|
|
21
|
+
case "wrong_type":
|
|
22
|
+
return ` ${f.model}.${f.field} has type ${f.actual}, expected one of: ${f.expected.join(" | ")} (required by: ${f.reason})`;
|
|
23
|
+
case "missing_unique":
|
|
24
|
+
return ` ${f.model}.${f.field} missing single-column @unique (required by: ${f.reason})`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function validateSpokeAgainstParsed(manifest, parsed) {
|
|
28
|
+
const required = requiredModelsForManifest(manifest);
|
|
29
|
+
const failures = [];
|
|
30
|
+
for (const req of required) {
|
|
31
|
+
const found = parsed.find((m) => m.name === req.name);
|
|
32
|
+
if (!found) {
|
|
33
|
+
failures.push({ kind: "missing_model", model: req.name });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const byName = new Map(found.fields.map((f) => [f.name, f]));
|
|
37
|
+
for (const reqField of req.fields) {
|
|
38
|
+
const actual = byName.get(reqField.name);
|
|
39
|
+
if (!actual) {
|
|
40
|
+
failures.push({
|
|
41
|
+
kind: "missing_field",
|
|
42
|
+
model: req.name,
|
|
43
|
+
field: reqField.name,
|
|
44
|
+
reason: reqField.reason
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (reqField.types.length > 0 && !reqField.types.includes(actual.type)) {
|
|
49
|
+
failures.push({
|
|
50
|
+
kind: "wrong_type",
|
|
51
|
+
model: req.name,
|
|
52
|
+
field: reqField.name,
|
|
53
|
+
actual: actual.type,
|
|
54
|
+
expected: reqField.types,
|
|
55
|
+
reason: reqField.reason
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (reqField.unique && !modelHasSingleColumnUnique(found, reqField.name)) {
|
|
59
|
+
failures.push({
|
|
60
|
+
kind: "missing_unique",
|
|
61
|
+
model: req.name,
|
|
62
|
+
field: reqField.name,
|
|
63
|
+
reason: reqField.reason
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return failures;
|
|
69
|
+
}
|
|
70
|
+
async function runValidateSpoke(argv) {
|
|
71
|
+
let cwd = process.cwd();
|
|
72
|
+
for (let i = 0; i < argv.length; i++) {
|
|
73
|
+
const a = argv[i];
|
|
74
|
+
if (a === "--cwd" || a === "--root") {
|
|
75
|
+
const next = argv[i + 1];
|
|
76
|
+
if (next) {
|
|
77
|
+
cwd = resolve(next);
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let manifest;
|
|
83
|
+
try {
|
|
84
|
+
manifest = loadManifest({ cwd });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
process.stderr.write(`validate-spoke: ${err.message}
|
|
87
|
+
`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
if (manifest.dataPlane.type === "none") {
|
|
91
|
+
process.stdout.write(
|
|
92
|
+
`validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.
|
|
93
|
+
`
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const schemaPath = join(cwd, "prisma", "schema.prisma");
|
|
98
|
+
if (!existsSync(schemaPath)) {
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`validate-spoke: prisma/schema.prisma not found at ${schemaPath}
|
|
101
|
+
Manifest declares a data plane but the schema file is missing.
|
|
102
|
+
`
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const schemaSrc = readFileSync(schemaPath, "utf8");
|
|
107
|
+
const parsed = parsePrismaSchema(schemaSrc);
|
|
108
|
+
const failures = validateSpokeAgainstParsed(manifest, parsed);
|
|
109
|
+
if (failures.length > 0) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:
|
|
112
|
+
`
|
|
113
|
+
);
|
|
114
|
+
for (const f of failures) {
|
|
115
|
+
process.stderr.write(`${formatFailure(f)}
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
process.stderr.write(
|
|
119
|
+
"\nThe app.manifest.json features list expects the listed canonical models,\nfields, scalar types, and unique constraints. Either fix prisma/schema.prisma\nor turn the feature off in the manifest. See\n@augmenting-integrations/deploy-tools README for the contract.\n"
|
|
120
|
+
);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(
|
|
124
|
+
`validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` + Object.entries(manifest.features).filter(([, v]) => v).map(([k]) => k).join(", ") + "\n"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
var isDirectInvocation = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("validate-spoke.js");
|
|
128
|
+
if (isDirectInvocation) {
|
|
129
|
+
runValidateSpoke(process.argv.slice(2)).catch((err) => {
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`validate-spoke: ${err instanceof Error ? err.message : String(err)}
|
|
132
|
+
`
|
|
133
|
+
);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
validateSpokeAgainstParsed,
|
|
140
|
+
runValidateSpoke
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=chunk-SYUDHJI7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validate-spoke.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { loadManifest } from \"@augmenting-integrations/platform/manifest\";\n\nimport { requiredModelsForManifest } from \"./canonical/schema.js\";\nimport {\n parsePrismaSchema,\n modelHasSingleColumnUnique,\n type ParsedModel,\n} from \"./prisma-parser.js\";\n\n// =============================================================================\n// `augint validate-spoke`\n//\n// Inputs:\n// - app.manifest.json (validated by platform/manifest loader)\n// - prisma/schema.prisma (parsed locally)\n//\n// Output:\n// - Exit 0 on success with a one-line \"OK\" summary.\n// - Exit 1 on validation failure with a field-by-field list of what's\n// missing/wrong and WHY each requirement is enforced.\n//\n// Apex apps with `dataPlane.type === \"none\"` skip Prisma validation\n// entirely (no schema file expected). Spokes always validate.\n// =============================================================================\n\ntype Failure =\n | { kind: \"missing_model\"; model: string }\n | { kind: \"missing_field\"; model: string; field: string; reason: string }\n | {\n kind: \"wrong_type\";\n model: string;\n field: string;\n actual: string;\n expected: readonly string[];\n reason: string;\n }\n | { kind: \"missing_unique\"; model: string; field: string; reason: string };\n\nfunction formatFailure(f: Failure): string {\n switch (f.kind) {\n case \"missing_model\":\n return ` missing model: ${f.model}`;\n case \"missing_field\":\n return ` ${f.model}.${f.field} missing (required by: ${f.reason})`;\n case \"wrong_type\":\n return (\n ` ${f.model}.${f.field} has type ${f.actual}, expected one of: ` +\n `${f.expected.join(\" | \")} (required by: ${f.reason})`\n );\n case \"missing_unique\":\n return (\n ` ${f.model}.${f.field} missing single-column @unique` +\n ` (required by: ${f.reason})`\n );\n }\n}\n\nexport function validateSpokeAgainstParsed(\n manifest: ReturnType<typeof loadManifest>,\n parsed: ParsedModel[],\n): Failure[] {\n const required = requiredModelsForManifest(manifest);\n const failures: Failure[] = [];\n\n for (const req of required) {\n const found = parsed.find((m) => m.name === req.name);\n if (!found) {\n failures.push({ kind: \"missing_model\", model: req.name });\n continue;\n }\n const byName = new Map(found.fields.map((f) => [f.name, f]));\n\n for (const reqField of req.fields) {\n const actual = byName.get(reqField.name);\n if (!actual) {\n failures.push({\n kind: \"missing_field\",\n model: req.name,\n field: reqField.name,\n reason: reqField.reason,\n });\n continue;\n }\n\n if (reqField.types.length > 0 && !reqField.types.includes(actual.type)) {\n failures.push({\n kind: \"wrong_type\",\n model: req.name,\n field: reqField.name,\n actual: actual.type,\n expected: reqField.types,\n reason: reqField.reason,\n });\n }\n\n if (reqField.unique && !modelHasSingleColumnUnique(found, reqField.name)) {\n failures.push({\n kind: \"missing_unique\",\n model: req.name,\n field: reqField.name,\n reason: reqField.reason,\n });\n }\n }\n }\n\n return failures;\n}\n\nexport async function runValidateSpoke(argv: string[]): Promise<void> {\n let cwd = process.cwd();\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--cwd\" || a === \"--root\") {\n const next = argv[i + 1];\n if (next) {\n cwd = resolve(next);\n i++;\n }\n }\n }\n\n let manifest: ReturnType<typeof loadManifest>;\n try {\n manifest = loadManifest({ cwd });\n } catch (err) {\n process.stderr.write(`validate-spoke: ${(err as Error).message}\\n`);\n process.exit(1);\n }\n\n // Apex auth-broker has no data plane -- nothing to validate.\n if (manifest.dataPlane.type === \"none\") {\n process.stdout.write(\n `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.\\n`,\n );\n return;\n }\n\n const schemaPath = join(cwd, \"prisma\", \"schema.prisma\");\n if (!existsSync(schemaPath)) {\n process.stderr.write(\n `validate-spoke: prisma/schema.prisma not found at ${schemaPath}\\n` +\n \"Manifest declares a data plane but the schema file is missing.\\n\",\n );\n process.exit(1);\n }\n\n const schemaSrc = readFileSync(schemaPath, \"utf8\");\n const parsed = parsePrismaSchema(schemaSrc);\n const failures = validateSpokeAgainstParsed(manifest, parsed);\n\n if (failures.length > 0) {\n process.stderr.write(\n `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:\\n`,\n );\n for (const f of failures) {\n process.stderr.write(`${formatFailure(f)}\\n`);\n }\n process.stderr.write(\n \"\\nThe app.manifest.json features list expects the listed canonical models,\\n\" +\n \"fields, scalar types, and unique constraints. Either fix prisma/schema.prisma\\n\" +\n \"or turn the feature off in the manifest. See\\n\" +\n \"@augmenting-integrations/deploy-tools README for the contract.\\n\",\n );\n process.exit(1);\n }\n\n process.stdout.write(\n `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` +\n Object.entries(manifest.features)\n .filter(([, v]) => v)\n .map(([k]) => k)\n .join(\", \") +\n \"\\n\",\n );\n}\n\nconst isDirectInvocation =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"validate-spoke.js\");\nif (isDirectInvocation) {\n runValidateSpoke(process.argv.slice(2)).catch((err) => {\n process.stderr.write(\n `validate-spoke: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/prisma-parser.ts
|
|
4
|
+
var MODEL_BLOCK = /model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\n\s*\}/g;
|
|
5
|
+
var FIELD_HEAD = /^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z0-9_]*)(\?|\[\])?/;
|
|
6
|
+
var MODEL_ATTR_LIST = /\(\s*\[([^\]]+)\]/;
|
|
7
|
+
function parseAttributeList(line) {
|
|
8
|
+
const m = line.match(MODEL_ATTR_LIST);
|
|
9
|
+
if (!m) return [];
|
|
10
|
+
return m[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => s.split(":")[0].trim());
|
|
11
|
+
}
|
|
12
|
+
function parsePrismaSchema(source) {
|
|
13
|
+
const stripped = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const match of stripped.matchAll(MODEL_BLOCK)) {
|
|
16
|
+
const name = match[1];
|
|
17
|
+
const body = match[2];
|
|
18
|
+
const fields = [];
|
|
19
|
+
const uniqueGroups = [];
|
|
20
|
+
const indexGroups = [];
|
|
21
|
+
for (const rawLine of body.split("\n")) {
|
|
22
|
+
const line = rawLine.trim();
|
|
23
|
+
if (line === "") continue;
|
|
24
|
+
if (line.startsWith("@@")) {
|
|
25
|
+
if (line.startsWith("@@unique")) {
|
|
26
|
+
const fieldsInGroup = parseAttributeList(line);
|
|
27
|
+
if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);
|
|
28
|
+
} else if (line.startsWith("@@index")) {
|
|
29
|
+
const fieldsInGroup = parseAttributeList(line);
|
|
30
|
+
if (fieldsInGroup.length > 0) indexGroups.push(fieldsInGroup);
|
|
31
|
+
} else if (line.startsWith("@@id")) {
|
|
32
|
+
const fieldsInGroup = parseAttributeList(line);
|
|
33
|
+
if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (line.startsWith("@")) continue;
|
|
38
|
+
const head = line.match(FIELD_HEAD);
|
|
39
|
+
if (!head) continue;
|
|
40
|
+
const fieldName = head[1];
|
|
41
|
+
const fieldType = head[2];
|
|
42
|
+
const tail = head[3] ?? "";
|
|
43
|
+
const optional = tail === "?";
|
|
44
|
+
const list = tail === "[]";
|
|
45
|
+
const attributes = /* @__PURE__ */ new Set();
|
|
46
|
+
if (/\s@id(\s|$|\()/.test(line)) attributes.add("id");
|
|
47
|
+
if (/\s@unique(\s|$|\()/.test(line)) attributes.add("unique");
|
|
48
|
+
if (attributes.has("unique")) uniqueGroups.push([fieldName]);
|
|
49
|
+
if (attributes.has("id")) uniqueGroups.push([fieldName]);
|
|
50
|
+
fields.push({
|
|
51
|
+
name: fieldName,
|
|
52
|
+
type: fieldType,
|
|
53
|
+
optional,
|
|
54
|
+
list,
|
|
55
|
+
attributes
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
out.push({ name, fields, uniqueGroups, indexGroups });
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function modelHasSingleColumnUnique(model, field) {
|
|
63
|
+
return model.uniqueGroups.some((g) => g.length === 1 && g[0] === field);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
parsePrismaSchema,
|
|
68
|
+
modelHasSingleColumnUnique
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=chunk-ZQE3CYBR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/prisma-parser.ts"],"sourcesContent":["// =============================================================================\n// Minimal Prisma schema parser. Extracts model declarations, per-field\n// metadata (name + scalar type + optional/list marker + inline @unique /\n// @id), and model-level attributes (@@unique, @@index, @@id).\n//\n// Scope: only what `augint validate-spoke` needs to enforce the canonical\n// library contract. We intentionally do NOT validate Prisma-side native\n// type mappings (`@db.VarChar`, `@db.Decimal(...)`); those are out of the\n// contract surface. Type drift at the scalar level (`String` vs `Int`,\n// `Decimal` vs `String`) is what library handlers actually break on.\n// =============================================================================\n\nexport type ParsedFieldAttribute = \"id\" | \"unique\";\n\nexport type ParsedField = {\n /** Field name as it appears in the model body. */\n name: string;\n /** Scalar type, e.g. `String`, `BigInt`, `Decimal`, `Json`, `DateTime`, `Boolean`, `Int`. */\n type: string;\n /** True if the type carried a trailing `?`. */\n optional: boolean;\n /** True if the type carried a trailing `[]` (relation list or scalar list). */\n list: boolean;\n /** Inline field attributes (`@id`, `@unique`). Relation attributes are ignored. */\n attributes: Set<ParsedFieldAttribute>;\n};\n\nexport type ParsedModel = {\n name: string;\n fields: ParsedField[];\n /**\n * Field names participating in a single-column or composite `@@unique([...])`.\n * Each inner array is one composite. A single-column `@unique` is folded in\n * automatically as `[fieldName]` so callers can ask one question.\n */\n uniqueGroups: string[][];\n /**\n * Field names participating in `@@index([...])`. Each inner array is one\n * index.\n */\n indexGroups: string[][];\n};\n\nconst MODEL_BLOCK = /model\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{([\\s\\S]*?)\\n\\s*\\}/g;\nconst FIELD_HEAD = /^([A-Za-z_][A-Za-z0-9_]*)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\?|\\[\\])?/;\nconst MODEL_ATTR_LIST = /\\(\\s*\\[([^\\]]+)\\]/;\n\nfunction parseAttributeList(line: string): string[] {\n const m = line.match(MODEL_ATTR_LIST);\n if (!m) return [];\n return (\n m[1]!\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n // Drop named-group qualifiers like `name: ...` -- we only care about field names.\n .map((s) => s.split(\":\")[0]!.trim())\n );\n}\n\n/**\n * Parse a Prisma schema file. Comments are stripped first; the result\n * preserves model declaration order.\n */\nexport function parsePrismaSchema(source: string): ParsedModel[] {\n // Strip line comments and block comments before pattern-matching so an\n // attribute embedded in a comment doesn't get treated as real.\n const stripped = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/\\/\\/[^\\n]*/g, \"\");\n\n const out: ParsedModel[] = [];\n for (const match of stripped.matchAll(MODEL_BLOCK)) {\n const name = match[1]!;\n const body = match[2]!;\n const fields: ParsedField[] = [];\n const uniqueGroups: string[][] = [];\n const indexGroups: string[][] = [];\n\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n\n if (line.startsWith(\"@@\")) {\n if (line.startsWith(\"@@unique\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);\n } else if (line.startsWith(\"@@index\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) indexGroups.push(fieldsInGroup);\n } else if (line.startsWith(\"@@id\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);\n }\n continue;\n }\n\n if (line.startsWith(\"@\")) continue;\n\n const head = line.match(FIELD_HEAD);\n if (!head) continue;\n\n const fieldName = head[1]!;\n const fieldType = head[2]!;\n const tail = head[3] ?? \"\";\n const optional = tail === \"?\";\n const list = tail === \"[]\";\n\n const attributes = new Set<ParsedFieldAttribute>();\n if (/\\s@id(\\s|$|\\()/.test(line)) attributes.add(\"id\");\n if (/\\s@unique(\\s|$|\\()/.test(line)) attributes.add(\"unique\");\n\n // Single-column @unique folds into uniqueGroups for uniform lookup.\n if (attributes.has(\"unique\")) uniqueGroups.push([fieldName]);\n // Single-column @id (e.g. `id Int @id`) also counts as unique.\n if (attributes.has(\"id\")) uniqueGroups.push([fieldName]);\n\n fields.push({\n name: fieldName,\n type: fieldType,\n optional,\n list,\n attributes,\n });\n }\n\n out.push({ name, fields, uniqueGroups, indexGroups });\n }\n return out;\n}\n\n/**\n * True if the model has a single-column unique constraint on `field`.\n * Composite uniques never count -- the canonical contract requires\n * single-column lookup keys (the library handlers do `findUnique({ where:\n * { token: ... } })`, which a composite wouldn't satisfy).\n */\nexport function modelHasSingleColumnUnique(model: ParsedModel, field: string): boolean {\n return model.uniqueGroups.some((g) => g.length === 1 && g[0] === field);\n}\n"],"mappings":";;;AA2CA,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,kBAAkB;AAExB,SAAS,mBAAmB,MAAwB;AAClD,QAAM,IAAI,KAAK,MAAM,eAAe;AACpC,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,SACE,EAAE,CAAC,EACA,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAE1B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG,KAAK,CAAC;AAEzC;AAMO,SAAS,kBAAkB,QAA+B;AAG/D,QAAM,WAAW,OAAO,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,eAAe,EAAE;AAElF,QAAM,MAAqB,CAAC;AAC5B,aAAW,SAAS,SAAS,SAAS,WAAW,GAAG;AAClD,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,SAAwB,CAAC;AAC/B,UAAM,eAA2B,CAAC;AAClC,UAAM,cAA0B,CAAC;AAEjC,eAAW,WAAW,KAAK,MAAM,IAAI,GAAG;AACtC,YAAM,OAAO,QAAQ,KAAK;AAC1B,UAAI,SAAS,GAAI;AAEjB,UAAI,KAAK,WAAW,IAAI,GAAG;AACzB,YAAI,KAAK,WAAW,UAAU,GAAG;AAC/B,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,cAAa,KAAK,aAAa;AAAA,QAC/D,WAAW,KAAK,WAAW,SAAS,GAAG;AACrC,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,aAAY,KAAK,aAAa;AAAA,QAC9D,WAAW,KAAK,WAAW,MAAM,GAAG;AAClC,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,cAAa,KAAK,aAAa;AAAA,QAC/D;AACA;AAAA,MACF;AAEA,UAAI,KAAK,WAAW,GAAG,EAAG;AAE1B,YAAM,OAAO,KAAK,MAAM,UAAU;AAClC,UAAI,CAAC,KAAM;AAEX,YAAM,YAAY,KAAK,CAAC;AACxB,YAAM,YAAY,KAAK,CAAC;AACxB,YAAM,OAAO,KAAK,CAAC,KAAK;AACxB,YAAM,WAAW,SAAS;AAC1B,YAAM,OAAO,SAAS;AAEtB,YAAM,aAAa,oBAAI,IAA0B;AACjD,UAAI,iBAAiB,KAAK,IAAI,EAAG,YAAW,IAAI,IAAI;AACpD,UAAI,qBAAqB,KAAK,IAAI,EAAG,YAAW,IAAI,QAAQ;AAG5D,UAAI,WAAW,IAAI,QAAQ,EAAG,cAAa,KAAK,CAAC,SAAS,CAAC;AAE3D,UAAI,WAAW,IAAI,IAAI,EAAG,cAAa,KAAK,CAAC,SAAS,CAAC;AAEvD,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,EAAE,MAAM,QAAQ,cAAc,YAAY,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAQO,SAAS,2BAA2B,OAAoB,OAAwB;AACrF,SAAO,MAAM,aAAa,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,EAAE,CAAC,MAAM,KAAK;AACxE;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runValidateSpoke
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SYUDHJI7.js";
|
|
5
5
|
import {
|
|
6
6
|
runValidateAppRoster
|
|
7
7
|
} from "./chunk-ZOKDIN2F.js";
|
|
8
8
|
import {
|
|
9
9
|
runPackageNextLambda
|
|
10
10
|
} from "./chunk-RYVRSM25.js";
|
|
11
|
+
import "./chunk-ZQE3CYBR.js";
|
|
12
|
+
import "./chunk-DN2IENY7.js";
|
|
11
13
|
|
|
12
14
|
// src/cli.ts
|
|
13
15
|
import process from "process";
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import process from \"node:process\";\nimport { runValidateSpoke } from \"./validate-spoke.js\";\nimport { runPackageNextLambda } from \"./package-next-lambda.js\";\nimport { runValidateAppRoster } from \"./validate-app-roster.js\";\n\nconst USAGE = `Usage: augint <command> [args]\n\nCommands:\n validate-spoke Validate Prisma schema against canonical contract\n validate-app-roster Validate tenant app roster (infra YAML + apex JSON + optional spoke mirrors)\n package-next-lambda Package Next standalone build for Lambda\n`;\n\nasync function main(): Promise<void> {\n const [, , cmd, ...rest] = process.argv;\n if (!cmd || cmd === \"--help\" || cmd === \"-h\") {\n process.stdout.write(USAGE);\n return;\n }\n switch (cmd) {\n case \"validate-spoke\":\n await runValidateSpoke(rest);\n return;\n case \"validate-app-roster\":\n await runValidateAppRoster(rest);\n return;\n case \"package-next-lambda\":\n await runPackageNextLambda(rest);\n return;\n default:\n process.stderr.write(`augint: unknown command '${cmd}'\\n${USAGE}`);\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`augint: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import process from \"node:process\";\nimport { runValidateSpoke } from \"./validate-spoke.js\";\nimport { runPackageNextLambda } from \"./package-next-lambda.js\";\nimport { runValidateAppRoster } from \"./validate-app-roster.js\";\n\nconst USAGE = `Usage: augint <command> [args]\n\nCommands:\n validate-spoke Validate Prisma schema against canonical contract\n validate-app-roster Validate tenant app roster (infra YAML + apex JSON + optional spoke mirrors)\n package-next-lambda Package Next standalone build for Lambda\n`;\n\nasync function main(): Promise<void> {\n const [, , cmd, ...rest] = process.argv;\n if (!cmd || cmd === \"--help\" || cmd === \"-h\") {\n process.stdout.write(USAGE);\n return;\n }\n switch (cmd) {\n case \"validate-spoke\":\n await runValidateSpoke(rest);\n return;\n case \"validate-app-roster\":\n await runValidateAppRoster(rest);\n return;\n case \"package-next-lambda\":\n await runPackageNextLambda(rest);\n return;\n default:\n process.stderr.write(`augint: unknown command '${cmd}'\\n${USAGE}`);\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`augint: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO,aAAa;AAKpB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQd,eAAe,OAAsB;AACnC,QAAM,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,IAAI,QAAQ;AACnC,MAAI,CAAC,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC5C,YAAQ,OAAO,MAAM,KAAK;AAC1B;AAAA,EACF;AACA,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,YAAM,iBAAiB,IAAI;AAC3B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF;AACE,cAAQ,OAAO,MAAM,4BAA4B,GAAG;AAAA,EAAM,KAAK,EAAE;AACjE,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACpF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/dist/prisma-parser.d.ts
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
|
+
export type ParsedFieldAttribute = "id" | "unique";
|
|
2
|
+
export type ParsedField = {
|
|
3
|
+
/** Field name as it appears in the model body. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Scalar type, e.g. `String`, `BigInt`, `Decimal`, `Json`, `DateTime`, `Boolean`, `Int`. */
|
|
6
|
+
type: string;
|
|
7
|
+
/** True if the type carried a trailing `?`. */
|
|
8
|
+
optional: boolean;
|
|
9
|
+
/** True if the type carried a trailing `[]` (relation list or scalar list). */
|
|
10
|
+
list: boolean;
|
|
11
|
+
/** Inline field attributes (`@id`, `@unique`). Relation attributes are ignored. */
|
|
12
|
+
attributes: Set<ParsedFieldAttribute>;
|
|
13
|
+
};
|
|
1
14
|
export type ParsedModel = {
|
|
2
15
|
name: string;
|
|
3
|
-
fields:
|
|
16
|
+
fields: ParsedField[];
|
|
17
|
+
/**
|
|
18
|
+
* Field names participating in a single-column or composite `@@unique([...])`.
|
|
19
|
+
* Each inner array is one composite. A single-column `@unique` is folded in
|
|
20
|
+
* automatically as `[fieldName]` so callers can ask one question.
|
|
21
|
+
*/
|
|
22
|
+
uniqueGroups: string[][];
|
|
23
|
+
/**
|
|
24
|
+
* Field names participating in `@@index([...])`. Each inner array is one
|
|
25
|
+
* index.
|
|
26
|
+
*/
|
|
27
|
+
indexGroups: string[][];
|
|
4
28
|
};
|
|
5
29
|
/**
|
|
6
|
-
* Parse a Prisma schema file.
|
|
7
|
-
*
|
|
8
|
-
* each non-blank, non-comment, non-attribute line inside the body.
|
|
30
|
+
* Parse a Prisma schema file. Comments are stripped first; the result
|
|
31
|
+
* preserves model declaration order.
|
|
9
32
|
*/
|
|
10
33
|
export declare function parsePrismaSchema(source: string): ParsedModel[];
|
|
34
|
+
/**
|
|
35
|
+
* True if the model has a single-column unique constraint on `field`.
|
|
36
|
+
* Composite uniques never count -- the canonical contract requires
|
|
37
|
+
* single-column lookup keys (the library handlers do `findUnique({ where:
|
|
38
|
+
* { token: ... } })`, which a composite wouldn't satisfy).
|
|
39
|
+
*/
|
|
40
|
+
export declare function modelHasSingleColumnUnique(model: ParsedModel, field: string): boolean;
|
|
11
41
|
//# sourceMappingURL=prisma-parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prisma-parser.d.ts","sourceRoot":"","sources":["../src/prisma-parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prisma-parser.d.ts","sourceRoot":"","sources":["../src/prisma-parser.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,QAAQ,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG;IACxB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,IAAI,EAAE,OAAO,CAAC;IACd,mFAAmF;IACnF,UAAU,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;CACzB,CAAC;AAmBF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CA+D/D;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/validate-spoke.d.ts
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
|
+
import { loadManifest } from "@augmenting-integrations/platform/manifest";
|
|
2
|
+
import { type ParsedModel } from "./prisma-parser.js";
|
|
3
|
+
type Failure = {
|
|
4
|
+
kind: "missing_model";
|
|
5
|
+
model: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "missing_field";
|
|
8
|
+
model: string;
|
|
9
|
+
field: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "wrong_type";
|
|
13
|
+
model: string;
|
|
14
|
+
field: string;
|
|
15
|
+
actual: string;
|
|
16
|
+
expected: readonly string[];
|
|
17
|
+
reason: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: "missing_unique";
|
|
20
|
+
model: string;
|
|
21
|
+
field: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function validateSpokeAgainstParsed(manifest: ReturnType<typeof loadManifest>, parsed: ParsedModel[]): Failure[];
|
|
1
25
|
export declare function runValidateSpoke(argv: string[]): Promise<void>;
|
|
26
|
+
export {};
|
|
2
27
|
//# sourceMappingURL=validate-spoke.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-spoke.d.ts","sourceRoot":"","sources":["../src/validate-spoke.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate-spoke.d.ts","sourceRoot":"","sources":["../src/validate-spoke.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAG1E,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAkB5B,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvE;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAqB7E,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,EACzC,MAAM,EAAE,WAAW,EAAE,GACpB,OAAO,EAAE,CA+CX;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEpE"}
|
package/dist/validate-spoke.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
runValidateSpoke
|
|
4
|
-
|
|
3
|
+
runValidateSpoke,
|
|
4
|
+
validateSpokeAgainstParsed
|
|
5
|
+
} from "./chunk-SYUDHJI7.js";
|
|
6
|
+
import "./chunk-ZQE3CYBR.js";
|
|
7
|
+
import "./chunk-DN2IENY7.js";
|
|
5
8
|
export {
|
|
6
|
-
runValidateSpoke
|
|
9
|
+
runValidateSpoke,
|
|
10
|
+
validateSpokeAgainstParsed
|
|
7
11
|
};
|
|
8
12
|
//# sourceMappingURL=validate-spoke.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@augmenting-integrations/deploy-tools",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.9.0",
|
|
4
4
|
"description": "Augint platform deploy tooling. `augint validate-spoke` checks Prisma canonical schema against the app manifest; `augint package-next-lambda` packages a Next standalone build for Lambda. Shared across every spoke + apex repo so deploy logic isn't copy/pasted into each app's CI workflow.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
"./prisma-parser": {
|
|
12
|
+
"types": "./dist/prisma-parser.d.ts",
|
|
13
|
+
"default": "./dist/prisma-parser.js"
|
|
14
|
+
},
|
|
15
|
+
"./validate-spoke": {
|
|
16
|
+
"types": "./dist/validate-spoke.d.ts",
|
|
17
|
+
"default": "./dist/validate-spoke.js"
|
|
18
|
+
},
|
|
19
|
+
"./canonical/schema": {
|
|
20
|
+
"types": "./dist/canonical/schema.d.ts",
|
|
21
|
+
"default": "./dist/canonical/schema.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
10
24
|
"bin": {
|
|
11
25
|
"augint": "./dist/cli.js",
|
|
12
26
|
"augint-validate-spoke": "./dist/validate-spoke.js",
|
|
@@ -19,14 +33,14 @@
|
|
|
19
33
|
"README.md"
|
|
20
34
|
],
|
|
21
35
|
"dependencies": {
|
|
22
|
-
"@augmenting-integrations/platform": "8.
|
|
36
|
+
"@augmenting-integrations/platform": "8.9.0"
|
|
23
37
|
},
|
|
24
38
|
"devDependencies": {
|
|
25
39
|
"@types/node": "^20.17.6",
|
|
26
40
|
"tsup": "^8.3.5",
|
|
27
41
|
"typescript": "^5.7.2",
|
|
28
42
|
"vitest": "^4.1.5",
|
|
29
|
-
"@augmenting-integrations/platform": "8.
|
|
43
|
+
"@augmenting-integrations/platform": "8.9.0"
|
|
30
44
|
},
|
|
31
45
|
"scripts": {
|
|
32
46
|
"build": "tsup",
|
package/dist/chunk-A7IIB34G.js
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/validate-spoke.ts
|
|
4
|
-
import { readFileSync, existsSync } from "fs";
|
|
5
|
-
import { resolve, join } from "path";
|
|
6
|
-
import process from "process";
|
|
7
|
-
import { loadManifest } from "@augmenting-integrations/platform/manifest";
|
|
8
|
-
|
|
9
|
-
// src/canonical/schema.ts
|
|
10
|
-
function requiredModelsForManifest(manifest) {
|
|
11
|
-
const out = [];
|
|
12
|
-
out.push({
|
|
13
|
-
name: "User",
|
|
14
|
-
fields: [
|
|
15
|
-
{ name: "id", reason: "JIT lookup + impersonation token claims" },
|
|
16
|
-
{ name: "email", reason: "JIT email lookup + auto-promote-to-admin" },
|
|
17
|
-
{ name: "name", reason: "/api/auth/me, UserMenu display" },
|
|
18
|
-
{ name: "role", reason: "JIT role resolution + admin gating" },
|
|
19
|
-
{ name: "is_active", reason: "/api/auth/me response shape" },
|
|
20
|
-
{ name: "credit_balance", reason: "JIT initial balance + /api/auth/me" },
|
|
21
|
-
{ name: "password", reason: "schema-inherited not-null filler at JIT create" },
|
|
22
|
-
{ name: "parent_id", reason: "invitation auto-accept sets parent_id from invite" },
|
|
23
|
-
{ name: "created_at", reason: "audit / ordering" },
|
|
24
|
-
{ name: "updated_at", reason: "audit / ordering" }
|
|
25
|
-
]
|
|
26
|
-
});
|
|
27
|
-
if (manifest.features.billing) {
|
|
28
|
-
out[0].fields.push(
|
|
29
|
-
{ name: "stripe_customer_id", reason: "createBillingHandlers user shape" },
|
|
30
|
-
{
|
|
31
|
-
name: "stripe_default_payment_method",
|
|
32
|
-
reason: "createBillingHandlers user shape"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: "stripe_default_payment_method_display",
|
|
36
|
-
reason: "createBillingHandlers user shape"
|
|
37
|
-
},
|
|
38
|
-
{ name: "auto_recharge_enabled", reason: "/api/billing/auto-recharge" },
|
|
39
|
-
{ name: "auto_recharge_threshold", reason: "/api/billing/auto-recharge" },
|
|
40
|
-
{ name: "auto_recharge_amount", reason: "/api/billing/auto-recharge" }
|
|
41
|
-
);
|
|
42
|
-
out.push({
|
|
43
|
-
name: "PaymentMethod",
|
|
44
|
-
fields: [
|
|
45
|
-
{ name: "id", reason: "createBillingHandlers row shape" },
|
|
46
|
-
{ name: "user_id", reason: "createBillingHandlers row shape" },
|
|
47
|
-
{
|
|
48
|
-
name: "stripe_payment_method_id",
|
|
49
|
-
reason: "createBillingHandlers row shape (unique key)"
|
|
50
|
-
},
|
|
51
|
-
{ name: "brand", reason: "createBillingHandlers row shape" },
|
|
52
|
-
{ name: "last4", reason: "createBillingHandlers row shape" },
|
|
53
|
-
{ name: "exp_month", reason: "createBillingHandlers row shape" },
|
|
54
|
-
{ name: "exp_year", reason: "createBillingHandlers row shape" },
|
|
55
|
-
{ name: "is_default", reason: "createBillingHandlers row shape" },
|
|
56
|
-
{ name: "created_at", reason: "createBillingHandlers row shape" }
|
|
57
|
-
]
|
|
58
|
-
});
|
|
59
|
-
out.push({
|
|
60
|
-
name: "CreditTransaction",
|
|
61
|
-
fields: [
|
|
62
|
-
{ name: "id", reason: "createBillingHandlers row shape" },
|
|
63
|
-
{ name: "user_id", reason: "createBillingHandlers row shape" },
|
|
64
|
-
{ name: "type", reason: "createBillingHandlers row shape" },
|
|
65
|
-
{ name: "amount", reason: "createBillingHandlers row shape" },
|
|
66
|
-
{ name: "description", reason: "createBillingHandlers row shape" },
|
|
67
|
-
{
|
|
68
|
-
name: "stripe_payment_intent_id",
|
|
69
|
-
reason: "createBillingHandlers webhook idempotency"
|
|
70
|
-
},
|
|
71
|
-
{ name: "payment_method_display", reason: "createBillingHandlers row shape" },
|
|
72
|
-
{ name: "created_at", reason: "createBillingHandlers row shape" }
|
|
73
|
-
]
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (manifest.features.invitations) {
|
|
77
|
-
out.push({
|
|
78
|
-
name: "Invitation",
|
|
79
|
-
fields: [
|
|
80
|
-
{ name: "id", reason: "createInvitationHandlers + JIT invitation accept" },
|
|
81
|
-
{ name: "token", reason: "createInvitationHandlers findUnique" },
|
|
82
|
-
{ name: "email", reason: "JIT invitation lookup" },
|
|
83
|
-
{ name: "intended_role", reason: "JIT role inheritance" },
|
|
84
|
-
{ name: "parent_id", reason: "JIT parent_id inheritance" },
|
|
85
|
-
{ name: "expires_at", reason: "JIT expiry filter" },
|
|
86
|
-
{ name: "accepted_at", reason: "JIT acceptance toggle" },
|
|
87
|
-
{
|
|
88
|
-
name: "accepted_by_user_id",
|
|
89
|
-
reason: "JIT writes acceptance with new user id"
|
|
90
|
-
},
|
|
91
|
-
{ name: "created_at", reason: "ordering" }
|
|
92
|
-
]
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
if (manifest.features.impersonation || manifest.features.invitations) {
|
|
96
|
-
out.push({
|
|
97
|
-
name: "ActivityLog",
|
|
98
|
-
fields: [
|
|
99
|
-
{ name: "id", reason: "audit row" },
|
|
100
|
-
{ name: "user_id", reason: "createImpersonateHandlers audit writes" },
|
|
101
|
-
{ name: "action", reason: "createImpersonateHandlers audit writes" },
|
|
102
|
-
{ name: "metadata", reason: "createImpersonateHandlers audit writes" },
|
|
103
|
-
{ name: "created_at", reason: "audit row" }
|
|
104
|
-
]
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// src/prisma-parser.ts
|
|
111
|
-
function parsePrismaSchema(source) {
|
|
112
|
-
const out = [];
|
|
113
|
-
const stripped = source.replace(/\/\/[^\n]*/g, "");
|
|
114
|
-
const modelRegex = /model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/g;
|
|
115
|
-
for (const match of stripped.matchAll(modelRegex)) {
|
|
116
|
-
const name = match[1];
|
|
117
|
-
const body = match[2];
|
|
118
|
-
const fields = [];
|
|
119
|
-
for (const rawLine of body.split("\n")) {
|
|
120
|
-
const line = rawLine.trim();
|
|
121
|
-
if (line === "") continue;
|
|
122
|
-
if (line.startsWith("@@")) continue;
|
|
123
|
-
if (line.startsWith("@")) continue;
|
|
124
|
-
const fieldMatch = /^([A-Za-z_][A-Za-z0-9_]*)\s+/.exec(line);
|
|
125
|
-
if (fieldMatch) fields.push(fieldMatch[1]);
|
|
126
|
-
}
|
|
127
|
-
out.push({ name, fields });
|
|
128
|
-
}
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// src/validate-spoke.ts
|
|
133
|
-
async function runValidateSpoke(argv) {
|
|
134
|
-
let cwd = process.cwd();
|
|
135
|
-
for (let i = 0; i < argv.length; i++) {
|
|
136
|
-
const a = argv[i];
|
|
137
|
-
if (a === "--cwd" || a === "--root") {
|
|
138
|
-
const next = argv[i + 1];
|
|
139
|
-
if (next) {
|
|
140
|
-
cwd = resolve(next);
|
|
141
|
-
i++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
let manifest;
|
|
146
|
-
try {
|
|
147
|
-
manifest = loadManifest({ cwd });
|
|
148
|
-
} catch (err) {
|
|
149
|
-
process.stderr.write(`validate-spoke: ${err.message}
|
|
150
|
-
`);
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
if (manifest.dataPlane.type === "none") {
|
|
154
|
-
process.stdout.write(
|
|
155
|
-
`validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.
|
|
156
|
-
`
|
|
157
|
-
);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const schemaPath = join(cwd, "prisma", "schema.prisma");
|
|
161
|
-
if (!existsSync(schemaPath)) {
|
|
162
|
-
process.stderr.write(
|
|
163
|
-
`validate-spoke: prisma/schema.prisma not found at ${schemaPath}
|
|
164
|
-
Manifest declares a data plane but the schema file is missing.
|
|
165
|
-
`
|
|
166
|
-
);
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
const schemaSrc = readFileSync(schemaPath, "utf8");
|
|
170
|
-
const parsed = parsePrismaSchema(schemaSrc);
|
|
171
|
-
const required = requiredModelsForManifest(manifest);
|
|
172
|
-
const failures = [];
|
|
173
|
-
for (const req of required) {
|
|
174
|
-
const found = parsed.find((m) => m.name === req.name);
|
|
175
|
-
if (!found) {
|
|
176
|
-
failures.push({ kind: "missing_model", model: req.name });
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
const has = new Set(found.fields);
|
|
180
|
-
for (const field of req.fields) {
|
|
181
|
-
if (!has.has(field.name)) {
|
|
182
|
-
failures.push({
|
|
183
|
-
kind: "missing_field",
|
|
184
|
-
model: req.name,
|
|
185
|
-
field: field.name,
|
|
186
|
-
reason: field.reason
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
if (failures.length > 0) {
|
|
192
|
-
process.stderr.write(
|
|
193
|
-
`validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:
|
|
194
|
-
`
|
|
195
|
-
);
|
|
196
|
-
for (const f of failures) {
|
|
197
|
-
if (f.kind === "missing_model") {
|
|
198
|
-
process.stderr.write(` missing model: ${f.model}
|
|
199
|
-
`);
|
|
200
|
-
} else {
|
|
201
|
-
process.stderr.write(
|
|
202
|
-
` ${f.model}.${f.field} missing (required by: ${f.reason})
|
|
203
|
-
`
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
process.stderr.write(
|
|
208
|
-
"\nThe app.manifest.json features list expects the listed canonical fields.\nEither add them to prisma/schema.prisma or turn the feature off in the\nmanifest. See @augmenting-integrations/deploy-tools README for the contract.\n"
|
|
209
|
-
);
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
process.stdout.write(
|
|
213
|
-
`validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` + Object.entries(manifest.features).filter(([, v]) => v).map(([k]) => k).join(", ") + "\n"
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
var isDirectInvocation = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("validate-spoke.js");
|
|
217
|
-
if (isDirectInvocation) {
|
|
218
|
-
runValidateSpoke(process.argv.slice(2)).catch((err) => {
|
|
219
|
-
process.stderr.write(
|
|
220
|
-
`validate-spoke: ${err instanceof Error ? err.message : String(err)}
|
|
221
|
-
`
|
|
222
|
-
);
|
|
223
|
-
process.exit(1);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export {
|
|
228
|
-
runValidateSpoke
|
|
229
|
-
};
|
|
230
|
-
//# sourceMappingURL=chunk-A7IIB34G.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/validate-spoke.ts","../src/canonical/schema.ts","../src/prisma-parser.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { loadManifest } from \"@augmenting-integrations/platform/manifest\";\n\nimport { requiredModelsForManifest } from \"./canonical/schema.js\";\nimport { parsePrismaSchema } from \"./prisma-parser.js\";\n\n// =============================================================================\n// `augint validate-spoke`\n//\n// Inputs:\n// - app.manifest.json (validated by platform/manifest loader)\n// - prisma/schema.prisma (parsed locally)\n//\n// Output:\n// - Exit 0 on success with a one-line \"OK\" summary.\n// - Exit 1 on validation failure with a field-by-field list of what's\n// missing and WHY each missing field is required by the library\n// handlers it serves.\n//\n// Apex apps with `dataPlane.type === \"none\"` skip Prisma validation\n// entirely (no schema file expected). Spokes always validate.\n// =============================================================================\n\nexport async function runValidateSpoke(argv: string[]): Promise<void> {\n let cwd = process.cwd();\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--cwd\" || a === \"--root\") {\n const next = argv[i + 1];\n if (next) {\n cwd = resolve(next);\n i++;\n }\n }\n }\n\n let manifest: ReturnType<typeof loadManifest>;\n try {\n manifest = loadManifest({ cwd });\n } catch (err) {\n process.stderr.write(`validate-spoke: ${(err as Error).message}\\n`);\n process.exit(1);\n }\n\n // Apex auth-broker has no data plane -- nothing to validate.\n if (manifest.dataPlane.type === \"none\") {\n process.stdout.write(\n `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.\\n`,\n );\n return;\n }\n\n const schemaPath = join(cwd, \"prisma\", \"schema.prisma\");\n if (!existsSync(schemaPath)) {\n process.stderr.write(\n `validate-spoke: prisma/schema.prisma not found at ${schemaPath}\\n` +\n \"Manifest declares a data plane but the schema file is missing.\\n\",\n );\n process.exit(1);\n }\n\n const schemaSrc = readFileSync(schemaPath, \"utf8\");\n const parsed = parsePrismaSchema(schemaSrc);\n const required = requiredModelsForManifest(manifest);\n\n type Failure =\n | { kind: \"missing_model\"; model: string }\n | { kind: \"missing_field\"; model: string; field: string; reason: string };\n const failures: Failure[] = [];\n\n for (const req of required) {\n const found = parsed.find((m) => m.name === req.name);\n if (!found) {\n failures.push({ kind: \"missing_model\", model: req.name });\n continue;\n }\n const has = new Set(found.fields);\n for (const field of req.fields) {\n if (!has.has(field.name)) {\n failures.push({\n kind: \"missing_field\",\n model: req.name,\n field: field.name,\n reason: field.reason,\n });\n }\n }\n }\n\n if (failures.length > 0) {\n process.stderr.write(\n `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:\\n`,\n );\n for (const f of failures) {\n if (f.kind === \"missing_model\") {\n process.stderr.write(` missing model: ${f.model}\\n`);\n } else {\n process.stderr.write(\n ` ${f.model}.${f.field} missing (required by: ${f.reason})\\n`,\n );\n }\n }\n process.stderr.write(\n \"\\nThe app.manifest.json features list expects the listed canonical fields.\\n\" +\n \"Either add them to prisma/schema.prisma or turn the feature off in the\\n\" +\n \"manifest. See @augmenting-integrations/deploy-tools README for the contract.\\n\",\n );\n process.exit(1);\n }\n\n process.stdout.write(\n `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` +\n Object.entries(manifest.features)\n .filter(([, v]) => v)\n .map(([k]) => k)\n .join(\", \") +\n \"\\n\",\n );\n}\n\nconst isDirectInvocation =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"validate-spoke.js\");\nif (isDirectInvocation) {\n runValidateSpoke(process.argv.slice(2)).catch((err) => {\n process.stderr.write(\n `validate-spoke: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n}\n","// =============================================================================\n// Canonical schema contract.\n//\n// Encodes the Prisma model + field names that the library handler factories\n// read at runtime. The validator (`augint validate-spoke`) parses the\n// spoke's prisma/schema.prisma and asserts that every required model + field\n// for the manifest's enabled features is present by name.\n//\n// Drift in canonical naming is the most common class of integration bug\n// (e.g. `password` vs `password_hash`). The validator catches it before\n// `next build` runs.\n// =============================================================================\n\nimport type { AppManifest } from \"@augmenting-integrations/platform/manifest\";\n\nexport type RequiredField = {\n name: string;\n /**\n * Reason this field is required (shown in the error message). Helps\n * developers understand WHY the validator cares, not just WHAT it\n * found missing.\n */\n reason: string;\n};\n\nexport type RequiredModel = {\n name: string;\n fields: RequiredField[];\n};\n\n/**\n * Returns the list of required models for a given manifest, in priority\n * order. Each model lists the fields its library consumers read.\n *\n * `User` is always required.\n * `Invitation` is required when features.invitations.\n * `PaymentMethod` + `CreditTransaction` are required when features.billing.\n * `ActivityLog` is required when features.impersonation or features.invitations.\n */\nexport function requiredModelsForManifest(manifest: AppManifest): RequiredModel[] {\n const out: RequiredModel[] = [];\n\n out.push({\n name: \"User\",\n fields: [\n { name: \"id\", reason: \"JIT lookup + impersonation token claims\" },\n { name: \"email\", reason: \"JIT email lookup + auto-promote-to-admin\" },\n { name: \"name\", reason: \"/api/auth/me, UserMenu display\" },\n { name: \"role\", reason: \"JIT role resolution + admin gating\" },\n { name: \"is_active\", reason: \"/api/auth/me response shape\" },\n { name: \"credit_balance\", reason: \"JIT initial balance + /api/auth/me\" },\n { name: \"password\", reason: \"schema-inherited not-null filler at JIT create\" },\n { name: \"parent_id\", reason: \"invitation auto-accept sets parent_id from invite\" },\n { name: \"created_at\", reason: \"audit / ordering\" },\n { name: \"updated_at\", reason: \"audit / ordering\" },\n ],\n });\n\n if (manifest.features.billing) {\n out[0]!.fields.push(\n { name: \"stripe_customer_id\", reason: \"createBillingHandlers user shape\" },\n {\n name: \"stripe_default_payment_method\",\n reason: \"createBillingHandlers user shape\",\n },\n {\n name: \"stripe_default_payment_method_display\",\n reason: \"createBillingHandlers user shape\",\n },\n { name: \"auto_recharge_enabled\", reason: \"/api/billing/auto-recharge\" },\n { name: \"auto_recharge_threshold\", reason: \"/api/billing/auto-recharge\" },\n { name: \"auto_recharge_amount\", reason: \"/api/billing/auto-recharge\" },\n );\n\n out.push({\n name: \"PaymentMethod\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\" },\n { name: \"user_id\", reason: \"createBillingHandlers row shape\" },\n {\n name: \"stripe_payment_method_id\",\n reason: \"createBillingHandlers row shape (unique key)\",\n },\n { name: \"brand\", reason: \"createBillingHandlers row shape\" },\n { name: \"last4\", reason: \"createBillingHandlers row shape\" },\n { name: \"exp_month\", reason: \"createBillingHandlers row shape\" },\n { name: \"exp_year\", reason: \"createBillingHandlers row shape\" },\n { name: \"is_default\", reason: \"createBillingHandlers row shape\" },\n { name: \"created_at\", reason: \"createBillingHandlers row shape\" },\n ],\n });\n out.push({\n name: \"CreditTransaction\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\" },\n { name: \"user_id\", reason: \"createBillingHandlers row shape\" },\n { name: \"type\", reason: \"createBillingHandlers row shape\" },\n { name: \"amount\", reason: \"createBillingHandlers row shape\" },\n { name: \"description\", reason: \"createBillingHandlers row shape\" },\n {\n name: \"stripe_payment_intent_id\",\n reason: \"createBillingHandlers webhook idempotency\",\n },\n { name: \"payment_method_display\", reason: \"createBillingHandlers row shape\" },\n { name: \"created_at\", reason: \"createBillingHandlers row shape\" },\n ],\n });\n }\n\n if (manifest.features.invitations) {\n out.push({\n name: \"Invitation\",\n fields: [\n { name: \"id\", reason: \"createInvitationHandlers + JIT invitation accept\" },\n { name: \"token\", reason: \"createInvitationHandlers findUnique\" },\n { name: \"email\", reason: \"JIT invitation lookup\" },\n { name: \"intended_role\", reason: \"JIT role inheritance\" },\n { name: \"parent_id\", reason: \"JIT parent_id inheritance\" },\n { name: \"expires_at\", reason: \"JIT expiry filter\" },\n { name: \"accepted_at\", reason: \"JIT acceptance toggle\" },\n {\n name: \"accepted_by_user_id\",\n reason: \"JIT writes acceptance with new user id\",\n },\n { name: \"created_at\", reason: \"ordering\" },\n ],\n });\n }\n\n if (manifest.features.impersonation || manifest.features.invitations) {\n out.push({\n name: \"ActivityLog\",\n fields: [\n { name: \"id\", reason: \"audit row\" },\n { name: \"user_id\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"action\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"metadata\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"created_at\", reason: \"audit row\" },\n ],\n });\n }\n\n return out;\n}\n","// =============================================================================\n// Minimal Prisma schema parser. Extracts model names and the field names\n// declared inside each model body. Sufficient to validate that the spoke's\n// schema has the canonical models + fields the library handler factories\n// read.\n//\n// We deliberately do NOT validate Prisma TYPES (BigInt vs Int, Decimal vs\n// Int, etc.). The platform adapter in src/platform/repositories.ts widens\n// to structural shapes; runtime correctness depends on the canonical names\n// being present. Type drift is caught by the spoke's own `tsc` pass when\n// it tries to construct adapters against an actual generated client.\n// =============================================================================\n\nexport type ParsedModel = {\n name: string;\n fields: string[];\n};\n\n/**\n * Parse a Prisma schema file. Returns one ParsedModel per `model X { ... }`\n * block in declaration order. Field names are extracted from the start of\n * each non-blank, non-comment, non-attribute line inside the body.\n */\nexport function parsePrismaSchema(source: string): ParsedModel[] {\n const out: ParsedModel[] = [];\n const stripped = source.replace(/\\/\\/[^\\n]*/g, \"\"); // line comments\n const modelRegex = /model\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{([\\s\\S]*?)\\}/g;\n for (const match of stripped.matchAll(modelRegex)) {\n const name = match[1]!;\n const body = match[2]!;\n const fields: string[] = [];\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n if (line.startsWith(\"@@\")) continue;\n if (line.startsWith(\"@\")) continue;\n const fieldMatch = /^([A-Za-z_][A-Za-z0-9_]*)\\s+/.exec(line);\n if (fieldMatch) fields.push(fieldMatch[1]!);\n }\n out.push({ name, fields });\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|