@hyprcart/app-manifest 1.0.0 → 1.0.1
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/dist/index.d.ts +390 -4
- package/dist/index.js +559 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,16 +3,171 @@ var supportedSurfaces = [
|
|
|
3
3
|
"storefront_block",
|
|
4
4
|
"admin_surface",
|
|
5
5
|
"checkout_ui",
|
|
6
|
+
"deterministic_function",
|
|
6
7
|
"checkout_function",
|
|
7
8
|
"pixel",
|
|
9
|
+
"event_subscription",
|
|
10
|
+
"background_job",
|
|
8
11
|
"event",
|
|
9
12
|
"flow",
|
|
10
13
|
"mcp"
|
|
11
14
|
];
|
|
15
|
+
var storefrontBlockRenderTiers = ["static", "edge_render", "edge_stream"];
|
|
16
|
+
var legacyStorefrontBlockRenderTiers = ["edge-function", "edge-stream"];
|
|
17
|
+
var hydrationTriggers = ["load", "visible", "interaction", "idle", "manual"];
|
|
12
18
|
var defaultRequiredLocales = ["en", "de"];
|
|
13
19
|
function isSupportedSurface(value) {
|
|
14
20
|
return supportedSurfaces.includes(value);
|
|
15
21
|
}
|
|
22
|
+
function normalizeStorefrontBlockRenderTier(value) {
|
|
23
|
+
if (value === "edge-function") return "edge_render";
|
|
24
|
+
if (value === "edge-stream") return "edge_stream";
|
|
25
|
+
if (storefrontBlockRenderTiers.includes(value)) return value;
|
|
26
|
+
return "static";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/surface-contracts.ts
|
|
30
|
+
function validateExtensionSurfaceDeclarations(manifest) {
|
|
31
|
+
return (manifest.surfaces ?? []).map((surface, index) => validateExtensionSurface(surface, `surfaces.${index}`));
|
|
32
|
+
}
|
|
33
|
+
function validateExtensionSurface(surface, basePath = "surface") {
|
|
34
|
+
const diagnostics = [];
|
|
35
|
+
if (!surface.label || Object.keys(surface.label).length === 0) {
|
|
36
|
+
diagnostics.push(diagnostic("surface.label.missing", "blocking", `${basePath}.label`, "Surface must declare localized labels."));
|
|
37
|
+
}
|
|
38
|
+
if (!surface.entry?.trim()) {
|
|
39
|
+
diagnostics.push(diagnostic("surface.entry.missing", "blocking", `${basePath}.entry`, "Surface must declare an entry file."));
|
|
40
|
+
}
|
|
41
|
+
if (surface.type === "admin_surface") validateAdminSurface(surface, basePath, diagnostics);
|
|
42
|
+
if (surface.type === "checkout_ui") validateCheckoutSurface(surface, basePath, diagnostics);
|
|
43
|
+
if (surface.type === "deterministic_function" || surface.type === "checkout_function") validateDeterministicFunction(surface, basePath, diagnostics);
|
|
44
|
+
if (surface.type === "pixel") validatePixel(surface, basePath, diagnostics);
|
|
45
|
+
if (surface.type === "event_subscription") validateEventSubscription(surface, basePath, diagnostics);
|
|
46
|
+
if (surface.type === "background_job") validateBackgroundJob(surface, basePath, diagnostics);
|
|
47
|
+
if (surface.type === "flow" || surface.type === "event") {
|
|
48
|
+
diagnostics.push(
|
|
49
|
+
diagnostic(
|
|
50
|
+
"surface.flow.deferred_to_075a",
|
|
51
|
+
"warning",
|
|
52
|
+
`${basePath}.type`,
|
|
53
|
+
"Flow trigger/action declarations are owned by 075a and are not activated by 049c.",
|
|
54
|
+
"https://hyprcart.com/docs/app-platform/storefront-app-blocks-internal"
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const blocking = diagnostics.some((entry) => entry.severity === "blocking");
|
|
59
|
+
return {
|
|
60
|
+
surfaceId: surface.id,
|
|
61
|
+
surfaceType: surface.type,
|
|
62
|
+
status: blocking ? "rejected" : surface.type === "storefront_block" ? "active" : surface.type === "flow" || surface.type === "event" ? "deferred" : "preview",
|
|
63
|
+
runtimeOwner: ownerForSurface(surface.type),
|
|
64
|
+
diagnostics
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function validateAdminSurface(surface, basePath, diagnostics) {
|
|
68
|
+
if (!surface.adminSurfaceType) {
|
|
69
|
+
diagnostics.push(diagnostic("surface.admin.type_missing", "blocking", `${basePath}.adminSurfaceType`, "Admin surfaces must declare a specific admin surface type."));
|
|
70
|
+
}
|
|
71
|
+
if (!surface.placement && !surface.compatibleSlots?.length) {
|
|
72
|
+
diagnostics.push(diagnostic("surface.admin.placement_missing", "blocking", `${basePath}.placement`, "Admin surfaces must declare placement targets."));
|
|
73
|
+
}
|
|
74
|
+
if (!surface.scopes?.length) {
|
|
75
|
+
diagnostics.push(diagnostic("surface.admin.scopes_missing", "blocking", `${basePath}.scopes`, "Admin surfaces must declare required scopes."));
|
|
76
|
+
}
|
|
77
|
+
if (!surface.componentKit?.minVersion || !surface.componentKit.components.length) {
|
|
78
|
+
diagnostics.push(diagnostic("surface.admin.component_kit_missing", "blocking", `${basePath}.componentKit`, "Admin surfaces must declare Hyprcart admin component kit version and components."));
|
|
79
|
+
}
|
|
80
|
+
if (surface.adminSurfaceType === "admin_ui_block") {
|
|
81
|
+
const removal = new Set(surface.removalBehavior ?? []);
|
|
82
|
+
if (!removal.has("remove_on_disable") || !removal.has("remove_on_uninstall")) {
|
|
83
|
+
diagnostics.push(diagnostic("surface.admin_block.removal_missing", "blocking", `${basePath}.removalBehavior`, "Admin UI blocks must remove placements on disable and uninstall without tombstones."));
|
|
84
|
+
}
|
|
85
|
+
if (!surface.dimensions) {
|
|
86
|
+
diagnostics.push(diagnostic("surface.admin_block.dimensions_missing", "blocking", `${basePath}.dimensions`, "Admin UI blocks must reserve stable dimensions."));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!surface.inputSchema && !surface.outputSchema) {
|
|
90
|
+
diagnostics.push(diagnostic("surface.admin.schema_missing", "warning", `${basePath}.inputSchema`, "Admin surfaces should declare typed input or output schemas."));
|
|
91
|
+
}
|
|
92
|
+
if (!surface.sandboxPolicy) {
|
|
93
|
+
diagnostics.push(diagnostic("surface.admin.sandbox_missing", "warning", `${basePath}.sandboxPolicy`, "Admin surfaces should declare a sandbox policy."));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validateCheckoutSurface(surface, basePath, diagnostics) {
|
|
97
|
+
const sandbox = surface.sandboxPolicy ?? {};
|
|
98
|
+
if (sandbox.allowArbitraryDom === true || sandbox.allowScripts === true || sandbox.allowPaymentBypass === true) {
|
|
99
|
+
diagnostics.push(diagnostic("surface.checkout.unsupported_behavior", "blocking", `${basePath}.sandboxPolicy`, "Checkout UI extensions cannot declare arbitrary DOM, scripts, or payment bypass behavior."));
|
|
100
|
+
}
|
|
101
|
+
if (!surface.placement && !surface.compatibleSlots?.length) {
|
|
102
|
+
diagnostics.push(diagnostic("surface.checkout.placement_missing", "blocking", `${basePath}.placement`, "Checkout UI extensions must declare placement targets."));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateDeterministicFunction(surface, basePath, diagnostics) {
|
|
106
|
+
if (surface.determinism?.networkAccess) {
|
|
107
|
+
diagnostics.push(diagnostic("surface.function.network_forbidden", "blocking", `${basePath}.determinism.networkAccess`, "Deterministic functions cannot use arbitrary network access."));
|
|
108
|
+
}
|
|
109
|
+
if (surface.determinism?.mutationSideEffects) {
|
|
110
|
+
diagnostics.push(diagnostic("surface.function.side_effects_forbidden", "blocking", `${basePath}.determinism.mutationSideEffects`, "Deterministic functions cannot perform direct mutation side effects."));
|
|
111
|
+
}
|
|
112
|
+
if (!surface.inputSchema || !surface.outputSchema) {
|
|
113
|
+
diagnostics.push(diagnostic("surface.function.schema_missing", "blocking", `${basePath}.inputSchema`, "Deterministic functions require typed input and output schemas."));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function validatePixel(surface, basePath, diagnostics) {
|
|
117
|
+
const sandbox = surface.sandboxPolicy ?? {};
|
|
118
|
+
if (sandbox.domRead === true || sandbox.domWrite === true || sandbox.allowDomScrape === true || sandbox.allowDomWrite === true) {
|
|
119
|
+
diagnostics.push(diagnostic("surface.pixel.dom_access_forbidden", "blocking", `${basePath}.sandboxPolicy`, "Pixels cannot scrape or write storefront DOM."));
|
|
120
|
+
}
|
|
121
|
+
if (surface.consent?.bypass === true || sandbox.consentBypass === true) {
|
|
122
|
+
diagnostics.push(diagnostic("surface.pixel.consent_bypass_forbidden", "blocking", `${basePath}.consent`, "Pixels cannot bypass consent checks."));
|
|
123
|
+
}
|
|
124
|
+
if (!surface.consent?.category) {
|
|
125
|
+
diagnostics.push(diagnostic("surface.pixel.consent_missing", "blocking", `${basePath}.consent.category`, "Pixels must declare a consent category."));
|
|
126
|
+
}
|
|
127
|
+
if (!surface.consent?.events?.length) {
|
|
128
|
+
diagnostics.push(diagnostic("surface.pixel.events_missing", "blocking", `${basePath}.consent.events`, "Pixels must declare events."));
|
|
129
|
+
}
|
|
130
|
+
if (!surface.consent?.destinations?.length) {
|
|
131
|
+
diagnostics.push(diagnostic("surface.pixel.destinations_missing", "blocking", `${basePath}.consent.destinations`, "Pixels must declare destinations."));
|
|
132
|
+
}
|
|
133
|
+
if (!surface.consent?.regions?.length) {
|
|
134
|
+
diagnostics.push(diagnostic("surface.pixel.regions_missing", "warning", `${basePath}.consent.regions`, "Pixels should declare regional availability."));
|
|
135
|
+
}
|
|
136
|
+
const fields = surface.consent?.fields ?? [];
|
|
137
|
+
if (!fields.length || fields.some((field) => /email|phone|name|address|token|secret|session|payment|card/i.test(field))) {
|
|
138
|
+
diagnostics.push(diagnostic("surface.pixel.field_selection_invalid", "blocking", `${basePath}.consent.fields`, "Pixels must declare minimized buyer-safe fields only."));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function validateEventSubscription(surface, basePath, diagnostics) {
|
|
142
|
+
if (!surface.consent?.events?.length && !surface.requiredContexts?.length) {
|
|
143
|
+
diagnostics.push(diagnostic("surface.event.events_missing", "blocking", `${basePath}.requiredContexts`, "Event subscriptions must declare subscribed event names."));
|
|
144
|
+
}
|
|
145
|
+
if (!surface.scopes?.length) {
|
|
146
|
+
diagnostics.push(diagnostic("surface.event.scopes_missing", "blocking", `${basePath}.scopes`, "Event subscriptions must declare required scopes."));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function validateBackgroundJob(surface, basePath, diagnostics) {
|
|
150
|
+
if (!surface.retryPolicy) {
|
|
151
|
+
diagnostics.push(diagnostic("surface.background_job.retry_missing", "blocking", `${basePath}.retryPolicy`, "Background jobs must declare bounded retry policy."));
|
|
152
|
+
}
|
|
153
|
+
if ((surface.adminSurfaceType === "bulk_action" || surface.type === "background_job") && surface.overlapPolicy === void 0) {
|
|
154
|
+
diagnostics.push(diagnostic("surface.background_job.overlap_missing", "blocking", `${basePath}.overlapPolicy`, "Background jobs must declare overlap policy."));
|
|
155
|
+
}
|
|
156
|
+
if (!surface.quotaClass) {
|
|
157
|
+
diagnostics.push(diagnostic("surface.background_job.quota_missing", "warning", `${basePath}.quotaClass`, "Background jobs should declare a quota class."));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function ownerForSurface(surface) {
|
|
161
|
+
if (surface === "storefront_block") return "049c";
|
|
162
|
+
if (surface === "flow" || surface === "event") return "075a";
|
|
163
|
+
if (surface === "admin_surface" || surface === "checkout_ui" || surface === "checkout_function" || surface === "deterministic_function" || surface === "pixel" || surface === "event_subscription" || surface === "background_job") {
|
|
164
|
+
return "049e";
|
|
165
|
+
}
|
|
166
|
+
return "deferred";
|
|
167
|
+
}
|
|
168
|
+
function diagnostic(code, severity, path, message, docsUrl) {
|
|
169
|
+
return { code, severity, path, message, docsUrl };
|
|
170
|
+
}
|
|
16
171
|
|
|
17
172
|
// src/validation.ts
|
|
18
173
|
var riskyDependencyPatterns = [/child_process/i, /node:fs/i, /eval\(/i];
|
|
@@ -66,7 +221,24 @@ function validateManifest(manifest, options = {}) {
|
|
|
66
221
|
)
|
|
67
222
|
);
|
|
68
223
|
}
|
|
224
|
+
if (surface.type === "storefront_block") {
|
|
225
|
+
validateStorefrontBlockSurface(surface, basePath, issues);
|
|
226
|
+
}
|
|
69
227
|
});
|
|
228
|
+
for (const surfaceResult of validateExtensionSurfaceDeclarations(manifest)) {
|
|
229
|
+
for (const diagnostic2 of surfaceResult.diagnostics) {
|
|
230
|
+
issues.push(
|
|
231
|
+
issue(
|
|
232
|
+
diagnostic2.code,
|
|
233
|
+
diagnostic2.severity === "blocking" ? "blocker" : diagnostic2.severity === "error" ? "error" : diagnostic2.severity === "warning" ? "warning" : "info",
|
|
234
|
+
diagnostic2.path,
|
|
235
|
+
diagnostic2.message,
|
|
236
|
+
void 0,
|
|
237
|
+
diagnostic2.docsUrl
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
70
242
|
const translationStatus = getTranslationStatus(manifest, requiredLocales);
|
|
71
243
|
for (const gap of translationStatus.missing) {
|
|
72
244
|
issues.push(
|
|
@@ -115,6 +287,38 @@ function validateManifest(manifest, options = {}) {
|
|
|
115
287
|
translationStatus
|
|
116
288
|
};
|
|
117
289
|
}
|
|
290
|
+
function validateStorefrontBlockSurface(surface, basePath, issues) {
|
|
291
|
+
const renderTier = normalizeStorefrontBlockRenderTier(surface.renderTier ?? surface.tier);
|
|
292
|
+
if (renderTier === "edge_stream" && (!surface.placeholder?.width || !surface.placeholder?.height || !surface.placeholder?.skeleton)) {
|
|
293
|
+
issues.push(
|
|
294
|
+
issue(
|
|
295
|
+
"storefront_block.placeholder.missing",
|
|
296
|
+
"blocker",
|
|
297
|
+
`${basePath}.placeholder`,
|
|
298
|
+
"Streaming app blocks must declare placeholder width, height, and skeleton before install.",
|
|
299
|
+
"Add exact placeholder dimensions and skeleton type.",
|
|
300
|
+
"https://developer.hyprcart.com/docs/app-platform/surfaces"
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
for (const island of surface.clientIslands ?? []) {
|
|
305
|
+
if (island.compressedSizeBytes > 32 * 1024 && !island.exceptionId) {
|
|
306
|
+
issues.push(
|
|
307
|
+
issue(
|
|
308
|
+
"client_island.bundle_budget.exceeded",
|
|
309
|
+
"blocker",
|
|
310
|
+
`${basePath}.clientIslands`,
|
|
311
|
+
"Client island bundle exceeds the 32 KB compressed budget.",
|
|
312
|
+
"Reduce bundle size or request a platform exception.",
|
|
313
|
+
"https://developer.hyprcart.com/docs/app-platform/surfaces"
|
|
314
|
+
)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (!island.domBoundarySelector?.startsWith("[data-hc-app-block")) {
|
|
318
|
+
issues.push(issue("client_island.dom_boundary.invalid", "blocker", `${basePath}.clientIslands`, "Client island DOM boundary must stay inside the app block wrapper."));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
118
322
|
function getTranslationStatus(manifest, requiredLocales) {
|
|
119
323
|
const missing = [];
|
|
120
324
|
const completeLocales = [];
|
|
@@ -147,27 +351,40 @@ function isCompatibleSdk(version, supportedMajor) {
|
|
|
147
351
|
return Number.isFinite(major) && major === supportedMajor;
|
|
148
352
|
}
|
|
149
353
|
function hashObject(value) {
|
|
150
|
-
const json =
|
|
354
|
+
const json = stableStringify(value);
|
|
151
355
|
let hash = 0;
|
|
152
356
|
for (let index = 0; index < json.length; index += 1) {
|
|
153
357
|
hash = hash * 31 + json.charCodeAt(index) >>> 0;
|
|
154
358
|
}
|
|
155
359
|
return `mf_${hash.toString(16).padStart(8, "0")}`;
|
|
156
360
|
}
|
|
361
|
+
function stableStringify(value) {
|
|
362
|
+
return JSON.stringify(sortValue(value));
|
|
363
|
+
}
|
|
364
|
+
function sortValue(value) {
|
|
365
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
366
|
+
if (!value || typeof value !== "object") return value;
|
|
367
|
+
return Object.fromEntries(
|
|
368
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, sortValue(entry)])
|
|
369
|
+
);
|
|
370
|
+
}
|
|
157
371
|
|
|
158
372
|
// src/contract-generation.ts
|
|
159
373
|
function generateContracts(manifest, schemaVersion = "2026-06-06") {
|
|
160
374
|
const surfaces = manifest.surfaces.map(renderSurfaceContract).join("\n\n");
|
|
375
|
+
const surfaceMetadata = validateExtensionSurfaceDeclarations(manifest);
|
|
161
376
|
const content = [
|
|
162
377
|
"/* Generated by Hyprcart app tooling. */",
|
|
163
378
|
`export const schemaVersion = ${JSON.stringify(schemaVersion)};`,
|
|
164
379
|
`export const sdkVersion = ${JSON.stringify(manifest.sdkVersion)};`,
|
|
380
|
+
`export const surfaceContracts = ${JSON.stringify(surfaceMetadata, null, 2)} as const;`,
|
|
165
381
|
surfaces
|
|
166
382
|
].join("\n");
|
|
167
383
|
return {
|
|
168
384
|
manifestHash: stableHash(JSON.stringify(manifest)),
|
|
169
385
|
schemaVersion,
|
|
170
386
|
sdkVersion: manifest.sdkVersion,
|
|
387
|
+
surfaces: surfaceMetadata,
|
|
171
388
|
files: [
|
|
172
389
|
{
|
|
173
390
|
path: "generated/hyprcart.contracts.ts",
|
|
@@ -266,15 +483,355 @@ var developerToolingOperationMetadata = [
|
|
|
266
483
|
stability: "preview"
|
|
267
484
|
}
|
|
268
485
|
];
|
|
486
|
+
|
|
487
|
+
// src/admin-component-kit.ts
|
|
488
|
+
var defaultSupportedComponents = [
|
|
489
|
+
"Page",
|
|
490
|
+
"Section",
|
|
491
|
+
"Stack",
|
|
492
|
+
"InlineStack",
|
|
493
|
+
"Grid",
|
|
494
|
+
"ResourcePanel",
|
|
495
|
+
"AdminBlock",
|
|
496
|
+
"Modal",
|
|
497
|
+
"Sheet",
|
|
498
|
+
"Text",
|
|
499
|
+
"Heading",
|
|
500
|
+
"Badge",
|
|
501
|
+
"Status",
|
|
502
|
+
"DescriptionList",
|
|
503
|
+
"DataTable",
|
|
504
|
+
"Timeline",
|
|
505
|
+
"Metric",
|
|
506
|
+
"EmptyState",
|
|
507
|
+
"Skeleton",
|
|
508
|
+
"Form",
|
|
509
|
+
"TextField",
|
|
510
|
+
"NumberField",
|
|
511
|
+
"Select",
|
|
512
|
+
"Combobox",
|
|
513
|
+
"Checkbox",
|
|
514
|
+
"RadioGroup",
|
|
515
|
+
"DatePicker",
|
|
516
|
+
"FilePicker",
|
|
517
|
+
"ResourcePicker",
|
|
518
|
+
"ValidationMessage",
|
|
519
|
+
"Button",
|
|
520
|
+
"IconButton",
|
|
521
|
+
"Banner",
|
|
522
|
+
"Toast",
|
|
523
|
+
"InlineError",
|
|
524
|
+
"Progress",
|
|
525
|
+
"Spinner",
|
|
526
|
+
"Tooltip",
|
|
527
|
+
"ConfirmDialog",
|
|
528
|
+
"TitleBar",
|
|
529
|
+
"Breadcrumbs",
|
|
530
|
+
"Tabs",
|
|
531
|
+
"ActionList",
|
|
532
|
+
"ContextualSaveBar",
|
|
533
|
+
"CommandAction",
|
|
534
|
+
"SearchResult"
|
|
535
|
+
];
|
|
536
|
+
var supportedAdminComponentKitComponents = [...defaultSupportedComponents];
|
|
537
|
+
function validateAdminComponentKitManifest(input) {
|
|
538
|
+
const diagnostics = [];
|
|
539
|
+
if (!input.minVersion) {
|
|
540
|
+
diagnostics.push({ code: "admin_component_kit.version_missing", path: "componentKit.minVersion", message: "Admin component kit minimum version is required." });
|
|
541
|
+
}
|
|
542
|
+
const supported = new Set(input.supportedComponents ?? supportedAdminComponentKitComponents);
|
|
543
|
+
for (const component of input.components ?? []) {
|
|
544
|
+
if (!supported.has(component)) {
|
|
545
|
+
diagnostics.push({ code: "admin_component_kit.component_unsupported", path: "componentKit.components", message: `${component} is not an approved admin component.` });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const [key, value] of Object.entries(input.tokens ?? {})) {
|
|
549
|
+
if (/(?:#[0-9a-f]{3,8}\b|rgba?\(|font|radius|shadow)/i.test(`${key}:${String(value)}`)) {
|
|
550
|
+
diagnostics.push({ code: "admin_component_kit.token_forbidden", path: `componentKit.tokens.${key}`, message: "Apps cannot declare custom visual tokens outside the platform kit." });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (input.css && /(?:font-family|border-radius|box-shadow|position\s*:\s*fixed)/i.test(input.css)) {
|
|
554
|
+
diagnostics.push({ code: "admin_component_kit.css_forbidden", path: "componentKit.css", message: "Apps cannot inject global CSS or incompatible visual rules into admin surfaces." });
|
|
555
|
+
}
|
|
556
|
+
return { valid: diagnostics.length === 0, diagnostics };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/capability-declarations.ts
|
|
560
|
+
var appGovernanceCapabilityTypes = [
|
|
561
|
+
"api_operation",
|
|
562
|
+
"storefront_block",
|
|
563
|
+
"admin_surface",
|
|
564
|
+
"checkout_surface",
|
|
565
|
+
"pixel",
|
|
566
|
+
"event_subscription",
|
|
567
|
+
"mcp_tool",
|
|
568
|
+
"mcp_resource",
|
|
569
|
+
"mcp_resource_template",
|
|
570
|
+
"mcp_prompt",
|
|
571
|
+
"background_job"
|
|
572
|
+
];
|
|
573
|
+
var appGovernanceDataClasses = ["public", "merchant_operational", "restricted_customer", "payment_adjacent", "auth_sensitive", "security", "operator_only"];
|
|
574
|
+
var appGovernanceRiskClasses = ["low", "medium", "high", "critical"];
|
|
575
|
+
var appGovernanceCostClasses = ["free", "low", "medium", "high", "operator"];
|
|
576
|
+
var appGovernanceApprovalPolicies = ["required", "optional", "operator_only", "automation_policy_required", "not_approvable"];
|
|
577
|
+
function validateAppCapabilityDeclarations(input) {
|
|
578
|
+
const issues = [];
|
|
579
|
+
const capabilityKeys = /* @__PURE__ */ new Set();
|
|
580
|
+
for (const [index, capability] of (input.capabilities ?? []).entries()) {
|
|
581
|
+
const path = `capabilities.${index}`;
|
|
582
|
+
if (!capability.key?.trim()) issues.push(issue2("capability.key.missing", `${path}.key`, "Capability key is required."));
|
|
583
|
+
if (capabilityKeys.has(capability.key)) issues.push(issue2("capability.key.duplicate", `${path}.key`, "Capability key must be unique."));
|
|
584
|
+
capabilityKeys.add(capability.key);
|
|
585
|
+
if (!isOneOf(capability.type, appGovernanceCapabilityTypes)) issues.push(issue2("capability.type.invalid", `${path}.type`, "Capability type is not supported."));
|
|
586
|
+
if (!isOneOf(capability.dataClass, appGovernanceDataClasses)) issues.push(issue2("capability.data_class.invalid", `${path}.dataClass`, "Data class is not supported."));
|
|
587
|
+
if (!isOneOf(capability.riskClass, appGovernanceRiskClasses)) issues.push(issue2("capability.risk_class.invalid", `${path}.riskClass`, "Risk class is not supported."));
|
|
588
|
+
if (!isOneOf(capability.costClass, appGovernanceCostClasses)) issues.push(issue2("capability.cost_class.invalid", `${path}.costClass`, "Cost class is not supported."));
|
|
589
|
+
if (!isOneOf(capability.approvalPolicy, appGovernanceApprovalPolicies)) issues.push(issue2("capability.approval_policy.invalid", `${path}.approvalPolicy`, "Approval policy is not supported."));
|
|
590
|
+
if (capability.mutating && capability.riskClass === "low") {
|
|
591
|
+
issues.push(issue2("capability.mutating.low_risk", `${path}.riskClass`, "Mutating capabilities must be medium risk or higher."));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
for (const [index, egress] of (input.egress ?? []).entries()) {
|
|
595
|
+
const path = `egress.${index}`;
|
|
596
|
+
for (const host of egress.hosts ?? []) {
|
|
597
|
+
if (!isNormalizedHostPattern(host)) issues.push(issue2("egress.host.invalid", `${path}.hosts`, "Egress host must be normalized and must not contain credentials, path, query, or fragment."));
|
|
598
|
+
}
|
|
599
|
+
for (const capabilityKey of egress.requiredForCapabilities ?? []) {
|
|
600
|
+
if (!capabilityKeys.has(capabilityKey)) issues.push(issue2("egress.capability.unknown", `${path}.requiredForCapabilities`, `Unknown capability ${capabilityKey}.`));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
for (const [index, retention] of (input.dataRetention ?? []).entries()) {
|
|
604
|
+
const path = `dataRetention.${index}`;
|
|
605
|
+
if (retention.merchantVisible !== true) {
|
|
606
|
+
issues.push(issue2("retention.visibility.required", `${path}.merchantVisible`, "Retention policy must be visible to merchants at install and uninstall."));
|
|
607
|
+
}
|
|
608
|
+
for (const capabilityKey of retention.appliesTo ?? []) {
|
|
609
|
+
if (!capabilityKeys.has(capabilityKey)) issues.push(issue2("retention.capability.unknown", `${path}.appliesTo`, `Unknown capability ${capabilityKey}.`));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return { valid: issues.length === 0, issues };
|
|
613
|
+
}
|
|
614
|
+
function isNormalizedHostPattern(host) {
|
|
615
|
+
if (!host || host.includes("@") || host.includes("/") || host.includes("?") || host.includes("#")) return false;
|
|
616
|
+
return /^[a-z0-9*.-]+(?::\d+)?$/i.test(host);
|
|
617
|
+
}
|
|
618
|
+
function isOneOf(value, values) {
|
|
619
|
+
return values.includes(value);
|
|
620
|
+
}
|
|
621
|
+
function issue2(code, path, message) {
|
|
622
|
+
return { code, path, message };
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/ai-mcp-metadata.ts
|
|
626
|
+
var aiMcpDescriptorTypes = ["tool", "resource", "resource_template", "prompt"];
|
|
627
|
+
var aiMcpHumanApprovalPolicies = ["not_required", "required", "required_for_mutation", "automation_policy_allowed"];
|
|
628
|
+
var unsafeAuthorityPattern = /(ignore previous|system prompt|act as admin|bypass|override policy|grant .*scope|no approval)/i;
|
|
629
|
+
function validateAiMcpDescriptorMetadata(descriptors) {
|
|
630
|
+
const issues = [];
|
|
631
|
+
const names = /* @__PURE__ */ new Set();
|
|
632
|
+
descriptors.forEach((descriptor, index) => {
|
|
633
|
+
const path = `ai.mcp.${descriptor.descriptorType}.${index}`;
|
|
634
|
+
if (!descriptor.name?.trim()) issues.push(issue3("ai_mcp.name.missing", `${path}.name`, "Descriptor name is required."));
|
|
635
|
+
if (names.has(descriptor.name)) issues.push(issue3("ai_mcp.name.duplicate", `${path}.name`, "Descriptor name must be unique per app installation."));
|
|
636
|
+
names.add(descriptor.name);
|
|
637
|
+
if (!aiMcpDescriptorTypes.includes(descriptor.descriptorType)) issues.push(issue3("ai_mcp.type.invalid", `${path}.descriptorType`, "Descriptor type is not supported."));
|
|
638
|
+
if (!aiMcpHumanApprovalPolicies.includes(descriptor.humanApprovalPolicy)) issues.push(issue3("ai_mcp.approval.invalid", `${path}.humanApprovalPolicy`, "Human approval policy is not supported."));
|
|
639
|
+
if (descriptor.mutating && descriptor.humanApprovalPolicy === "not_required") {
|
|
640
|
+
issues.push(issue3("ai_mcp.mutating.approval_required", `${path}.humanApprovalPolicy`, "Mutating descriptors require human approval or automation policy."));
|
|
641
|
+
}
|
|
642
|
+
if (descriptor.resourceUriTemplate && /token|secret|bearer/i.test(descriptor.resourceUriTemplate)) {
|
|
643
|
+
issues.push(issue3("ai_mcp.resource_template.secret", `${path}.resourceUriTemplate`, "Resource URI templates must not embed tokens or secrets."));
|
|
644
|
+
}
|
|
645
|
+
for (const text of [descriptor.description, descriptor.promptTemplate].filter((value) => Boolean(value))) {
|
|
646
|
+
if (unsafeAuthorityPattern.test(text)) {
|
|
647
|
+
issues.push(issue3("ai_mcp.metadata.untrusted_authority", path, "Descriptor text is untrusted metadata and cannot claim platform authority."));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
return { valid: issues.length === 0, issues };
|
|
652
|
+
}
|
|
653
|
+
function classifyAiMcpMetadataAuthority(text) {
|
|
654
|
+
return unsafeAuthorityPattern.test(text) ? "untrusted_authority_claim" : "safe_descriptor";
|
|
655
|
+
}
|
|
656
|
+
function issue3(code, path, message) {
|
|
657
|
+
return { code, path, message };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/launch-readiness.ts
|
|
661
|
+
var REQUIRED_LOCALES = ["en", "de"];
|
|
662
|
+
function validateLaunchReadinessManifest(manifest) {
|
|
663
|
+
const issues = [];
|
|
664
|
+
const launch = manifest.launch;
|
|
665
|
+
if (!launch) {
|
|
666
|
+
return issue4("launch.missing", "launch", "Launch readiness declaration is required.");
|
|
667
|
+
}
|
|
668
|
+
validateAdminApi(launch, issues);
|
|
669
|
+
validateAppData(launch, issues);
|
|
670
|
+
validateEnvironments(launch, issues);
|
|
671
|
+
validateSecrets(launch, issues);
|
|
672
|
+
validateSettings(launch, issues);
|
|
673
|
+
validateConsent(launch, issues);
|
|
674
|
+
validateLifecycleCallbacks(launch, issues);
|
|
675
|
+
validateAssets(launch, issues);
|
|
676
|
+
validateSandbox(launch, issues);
|
|
677
|
+
validateEvidence(launch, issues);
|
|
678
|
+
validateCompatibility(launch, issues);
|
|
679
|
+
return { valid: issues.length === 0, issues };
|
|
680
|
+
}
|
|
681
|
+
function assertLaunchReadinessManifest(manifest) {
|
|
682
|
+
const result = validateLaunchReadinessManifest(manifest);
|
|
683
|
+
if (!result.valid) {
|
|
684
|
+
throw new Error(result.issues.map((entry) => `${entry.code}:${entry.path}`).join(", "));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function validateAdminApi(launch, issues) {
|
|
688
|
+
if (!launch.adminApi?.minApiVersion) addIssue(issues, "launch.admin_api.missing", "launch.adminApi", "Admin API declaration is required.");
|
|
689
|
+
if (!launch.adminApi?.operations?.length) addIssue(issues, "launch.admin_api.operations_missing", "launch.adminApi.operations", "At least one Admin API operation declaration is required.");
|
|
690
|
+
}
|
|
691
|
+
function validateAppData(launch, issues) {
|
|
692
|
+
if (!launch.appData?.length) addIssue(issues, "launch.app_data.missing", "launch.appData", "App data declarations are required.");
|
|
693
|
+
for (const [index, declaration] of (launch.appData ?? []).entries()) {
|
|
694
|
+
if (!declaration.namespace) addIssue(issues, "launch.app_data.namespace_missing", `launch.appData.${index}.namespace`, "App data namespace is required.");
|
|
695
|
+
if (declaration.visibility === "storefront_public" && !declaration.storefrontMaterialization?.cacheTags?.length) {
|
|
696
|
+
addIssue(issues, "launch.app_data.cache_tags_missing", `launch.appData.${index}.storefrontMaterialization.cacheTags`, "Storefront-public app data requires cache tags.");
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function validateEnvironments(launch, issues) {
|
|
701
|
+
const keys = new Set((launch.environments ?? []).map((environment) => environment.key));
|
|
702
|
+
for (const key of ["development", "staging", "production"]) {
|
|
703
|
+
if (!keys.has(key)) addIssue(issues, "launch.environment.missing", `launch.environments.${key}`, `${key} environment is required.`);
|
|
704
|
+
}
|
|
705
|
+
const production = (launch.environments ?? []).find((environment) => environment.key === "production");
|
|
706
|
+
if (production && !production.promotionRequiresCertification) {
|
|
707
|
+
addIssue(issues, "launch.environment.production_certification", "launch.environments.production", "Production environment must require certification.");
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function validateSecrets(launch, issues) {
|
|
711
|
+
if (!launch.secrets?.length) addIssue(issues, "launch.secrets.missing", "launch.secrets", "At least one secret declaration is required.");
|
|
712
|
+
for (const [index, secret] of (launch.secrets ?? []).entries()) {
|
|
713
|
+
if (secret.runtimeExposure === "runtime_only" && secret.key.toLowerCase().startsWith("next_public")) {
|
|
714
|
+
addIssue(issues, "launch.secrets.client_bundle", `launch.secrets.${index}.key`, "Secrets cannot target client bundles.");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function validateSettings(launch, issues) {
|
|
719
|
+
if (!launch.settings?.length) addIssue(issues, "launch.settings.missing", "launch.settings", "Merchant settings declaration is required.");
|
|
720
|
+
for (const [index, setting] of (launch.settings ?? []).entries()) {
|
|
721
|
+
for (const locale of REQUIRED_LOCALES) {
|
|
722
|
+
if (!setting.label[locale]) addIssue(issues, "launch.settings.locale_missing", `launch.settings.${index}.label.${locale}`, "Setting labels require English and German.");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function validateConsent(launch, issues) {
|
|
727
|
+
const consent = launch.consent;
|
|
728
|
+
if (!consent) {
|
|
729
|
+
addIssue(issues, "launch.consent.missing", "launch.consent", "Consent metadata is required.");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
for (const [field, value] of Object.entries({
|
|
733
|
+
appIcon: consent.appIcon,
|
|
734
|
+
developerName: consent.developerName,
|
|
735
|
+
supportUrl: consent.supportUrl,
|
|
736
|
+
privacyPolicyUrl: consent.privacyPolicyUrl,
|
|
737
|
+
termsUrl: consent.termsUrl,
|
|
738
|
+
reviewerInstructions: consent.reviewerInstructions
|
|
739
|
+
})) {
|
|
740
|
+
if (!value) addIssue(issues, "launch.consent.field_missing", `launch.consent.${field}`, "Consent metadata field is required.");
|
|
741
|
+
}
|
|
742
|
+
if (!consent.screenshots.length) addIssue(issues, "launch.consent.screenshots_missing", "launch.consent.screenshots", "At least one screenshot is required.");
|
|
743
|
+
if (!consent.dataCategories.length) addIssue(issues, "launch.consent.data_categories_missing", "launch.consent.dataCategories", "Data categories are required.");
|
|
744
|
+
}
|
|
745
|
+
function validateLifecycleCallbacks(launch, issues) {
|
|
746
|
+
if (!launch.lifecycleCallbacks?.length) addIssue(issues, "launch.callbacks.missing", "launch.lifecycleCallbacks", "Lifecycle callback declarations are required.");
|
|
747
|
+
}
|
|
748
|
+
function validateAssets(launch, issues) {
|
|
749
|
+
const kinds = new Set((launch.assets ?? []).map((asset) => asset.kind));
|
|
750
|
+
for (const kind of ["icon", "screenshot"]) {
|
|
751
|
+
if (!kinds.has(kind)) addIssue(issues, "launch.assets.required_missing", `launch.assets.${kind}`, `${kind} asset is required.`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function validateSandbox(launch, issues) {
|
|
755
|
+
if (!launch.sandbox?.fixturePackId) addIssue(issues, "launch.sandbox.missing", "launch.sandbox", "Sandbox fixture declaration is required.");
|
|
756
|
+
if (launch.sandbox && !launch.sandbox.anonymizationRequired) {
|
|
757
|
+
addIssue(issues, "launch.sandbox.anonymization_required", "launch.sandbox.anonymizationRequired", "Sandbox fixtures must require anonymization.");
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function validateEvidence(launch, issues) {
|
|
761
|
+
if (!launch.evidence?.testCommand) addIssue(issues, "launch.evidence.test_missing", "launch.evidence.testCommand", "Evidence test command is required.");
|
|
762
|
+
if (!launch.evidence?.buildCommand) addIssue(issues, "launch.evidence.build_missing", "launch.evidence.buildCommand", "Evidence build command is required.");
|
|
763
|
+
}
|
|
764
|
+
function validateCompatibility(launch, issues) {
|
|
765
|
+
if (!launch.compatibility?.adminApiVersion) addIssue(issues, "launch.compatibility.missing", "launch.compatibility", "Compatibility declaration is required.");
|
|
766
|
+
}
|
|
767
|
+
function addIssue(issues, code, path, message) {
|
|
768
|
+
issues.push({ code, path, message });
|
|
769
|
+
}
|
|
770
|
+
function issue4(code, path, message) {
|
|
771
|
+
return { valid: false, issues: [{ code, path, message }] };
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/preview-validation.ts
|
|
775
|
+
function validateNoDeployPreviewManifest(manifest) {
|
|
776
|
+
const validation = validateManifest(manifest);
|
|
777
|
+
const diagnostics = validation.issues.map((issue5) => ({
|
|
778
|
+
code: `manifest.${issue5.code}`,
|
|
779
|
+
message: issue5.message,
|
|
780
|
+
path: issue5.path
|
|
781
|
+
}));
|
|
782
|
+
if (manifest.surfaces.length === 0) {
|
|
783
|
+
diagnostics.push({
|
|
784
|
+
code: "preview.surface_missing",
|
|
785
|
+
message: "No-deploy preview requires at least one app surface.",
|
|
786
|
+
path: "surfaces"
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
for (const surface of manifest.surfaces) {
|
|
790
|
+
if (!surface.entry) {
|
|
791
|
+
diagnostics.push({
|
|
792
|
+
code: "preview.surface_entry_missing",
|
|
793
|
+
message: "Preview surfaces must declare an entry point.",
|
|
794
|
+
path: `surfaces.${surface.id}.entry`
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
status: diagnostics.length === 0 ? "valid" : "invalid",
|
|
800
|
+
createdProductionState: false,
|
|
801
|
+
diagnostics
|
|
802
|
+
};
|
|
803
|
+
}
|
|
269
804
|
export {
|
|
805
|
+
aiMcpDescriptorTypes,
|
|
806
|
+
aiMcpHumanApprovalPolicies,
|
|
807
|
+
appGovernanceApprovalPolicies,
|
|
808
|
+
appGovernanceCapabilityTypes,
|
|
809
|
+
appGovernanceCostClasses,
|
|
810
|
+
appGovernanceDataClasses,
|
|
811
|
+
appGovernanceRiskClasses,
|
|
812
|
+
assertLaunchReadinessManifest,
|
|
270
813
|
checkCompatibility,
|
|
814
|
+
classifyAiMcpMetadataAuthority,
|
|
271
815
|
defaultRequiredLocales,
|
|
272
816
|
developerToolingOperationMetadata,
|
|
273
817
|
generateContracts,
|
|
274
818
|
generateIdeMetadata,
|
|
275
819
|
getTranslationStatus,
|
|
820
|
+
hydrationTriggers,
|
|
821
|
+
isNormalizedHostPattern,
|
|
276
822
|
isSupportedSurface,
|
|
823
|
+
legacyStorefrontBlockRenderTiers,
|
|
824
|
+
normalizeStorefrontBlockRenderTier,
|
|
825
|
+
storefrontBlockRenderTiers,
|
|
826
|
+
supportedAdminComponentKitComponents,
|
|
277
827
|
supportedSurfaces,
|
|
278
|
-
|
|
828
|
+
validateAdminComponentKitManifest,
|
|
829
|
+
validateAiMcpDescriptorMetadata,
|
|
830
|
+
validateAppCapabilityDeclarations,
|
|
831
|
+
validateExtensionSurface,
|
|
832
|
+
validateExtensionSurfaceDeclarations,
|
|
833
|
+
validateLaunchReadinessManifest,
|
|
834
|
+
validateManifest,
|
|
835
|
+
validateNoDeployPreviewManifest
|
|
279
836
|
};
|
|
280
837
|
//# sourceMappingURL=index.js.map
|