@sequenceholdings/artifact-studio 0.2.5 → 0.3.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 +35 -0
- package/VERSION-PIN.md +14 -3
- package/dist/api.d.ts +11 -1
- package/dist/api.js +26 -9
- package/dist/atlas-ui-css-plugin.d.ts +11 -7
- package/dist/atlas-ui-css-plugin.js +38 -10
- package/dist/build.d.ts +2 -2
- package/dist/build.js +14 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +108 -37
- package/dist/dev-link.d.ts +1 -1
- package/dist/dev-link.js +2 -1
- package/dist/gateway/admission.d.ts +55 -0
- package/dist/gateway/admission.js +132 -0
- package/dist/gateway/control.d.ts +91 -0
- package/dist/gateway/control.js +113 -0
- package/dist/gateway/hosted-proxy.d.ts +36 -0
- package/dist/gateway/hosted-proxy.js +347 -0
- package/dist/gateway/release-admission.d.ts +23 -0
- package/dist/gateway/release-admission.js +259 -0
- package/dist/gateway/server.d.ts +29 -0
- package/dist/gateway/server.js +298 -0
- package/dist/gateway/session.d.ts +71 -0
- package/dist/gateway/session.js +143 -0
- package/dist/gateway/usage.d.ts +37 -0
- package/dist/gateway/usage.js +46 -0
- package/dist/gateway-main.d.ts +1 -0
- package/dist/gateway-main.js +84 -0
- package/dist/git-clone.d.ts +23 -1
- package/dist/git-clone.js +32 -15
- package/dist/manifest-next.d.ts +217 -0
- package/dist/manifest-next.js +120 -0
- package/dist/manifest-static.d.ts +76 -0
- package/dist/manifest-static.js +124 -0
- package/dist/manifest.d.ts +124 -19
- package/dist/manifest.js +12 -118
- package/dist/next/bridge.d.ts +47 -0
- package/dist/next/bridge.js +229 -0
- package/dist/next/browser-logs.d.ts +7 -0
- package/dist/next/browser-logs.js +51 -0
- package/dist/next/browser.d.ts +6 -0
- package/dist/next/browser.js +57 -0
- package/dist/next/protocol.d.ts +73 -0
- package/dist/next/protocol.js +56 -0
- package/dist/next/release.d.ts +15 -0
- package/dist/next/release.js +46 -0
- package/dist/next/requests.d.ts +15 -0
- package/dist/next/requests.js +59 -0
- package/dist/next/routes.d.ts +4 -0
- package/dist/next/routes.js +21 -0
- package/dist/next/streams.d.ts +16 -0
- package/dist/next/streams.js +132 -0
- package/dist/next/theme.d.ts +10 -0
- package/dist/next/theme.js +45 -0
- package/dist/next/transport.d.ts +15 -0
- package/dist/next/transport.js +13 -0
- package/dist/next-build.d.ts +29 -0
- package/dist/next-build.js +129 -0
- package/dist/next-cache/adapter-runtime.d.ts +51 -0
- package/dist/next-cache/adapter-runtime.js +73 -0
- package/dist/next-cache/component-handler.d.ts +3 -0
- package/dist/next-cache/component-handler.js +10 -0
- package/dist/next-cache/components.d.ts +20 -0
- package/dist/next-cache/components.js +113 -0
- package/dist/next-cache/config.d.ts +9 -0
- package/dist/next-cache/config.js +78 -0
- package/dist/next-cache/incremental-handler.d.ts +18 -0
- package/dist/next-cache/incremental-handler.js +17 -0
- package/dist/next-cache/incremental.d.ts +27 -0
- package/dist/next-cache/incremental.js +99 -0
- package/dist/next-cache/index.d.ts +18 -0
- package/dist/next-cache/index.js +16 -0
- package/dist/next-cache/memory-store.d.ts +27 -0
- package/dist/next-cache/memory-store.js +118 -0
- package/dist/next-cache/redis-scripts.d.ts +3 -0
- package/dist/next-cache/redis-scripts.js +85 -0
- package/dist/next-cache/redis-store.d.ts +18 -0
- package/dist/next-cache/redis-store.js +56 -0
- package/dist/next-cache/runtime.d.ts +29 -0
- package/dist/next-cache/runtime.js +63 -0
- package/dist/next-cache/serialization.d.ts +39 -0
- package/dist/next-cache/serialization.js +333 -0
- package/dist/next-cache/store.d.ts +54 -0
- package/dist/next-cache/store.js +45 -0
- package/dist/next-cache/valkey-transport.d.ts +52 -0
- package/dist/next-cache/valkey-transport.js +189 -0
- package/dist/next-dev-control.d.ts +48 -0
- package/dist/next-dev-control.js +63 -0
- package/dist/next-dev-process.d.ts +17 -0
- package/dist/next-dev-process.js +110 -0
- package/dist/next-dev.d.ts +12 -0
- package/dist/next-dev.js +267 -0
- package/dist/next-image-config.d.ts +9 -0
- package/dist/next-image-config.js +101 -0
- package/dist/next-project.d.ts +22 -0
- package/dist/next-project.js +64 -0
- package/dist/next-public-fonts-local.d.ts +7 -0
- package/dist/next-public-fonts-local.js +64 -0
- package/dist/next-public-fonts.d.ts +13 -0
- package/dist/next-public-fonts.js +49 -0
- package/dist/next-source-hash.d.ts +14 -0
- package/dist/next-source-hash.js +18 -0
- package/dist/next-source.d.ts +35 -0
- package/dist/next-source.js +155 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.js +5 -0
- package/dist/project.d.ts +3 -0
- package/dist/project.js +9 -1
- package/dist/runtime-contracts.d.ts +229 -0
- package/dist/runtime-contracts.js +224 -0
- package/dist/sdk.d.ts +420 -1
- package/dist/sdk.js +35 -0
- package/dist/server/broker-contract.d.ts +68 -0
- package/dist/server/broker-contract.js +33 -0
- package/dist/server/client.d.ts +67 -0
- package/dist/server/client.js +159 -0
- package/dist/server/logger.d.ts +26 -0
- package/dist/server/logger.js +24 -0
- package/dist/server/release-forms.d.ts +13 -0
- package/dist/server/release-forms.js +124 -0
- package/dist/server/request-context.d.ts +74 -0
- package/dist/server/request-context.js +103 -0
- package/dist/server/runtime-config.d.ts +53 -0
- package/dist/server/runtime-config.js +64 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +4 -0
- package/dist/templates/nextjs/.artifactignore +14 -0
- package/dist/templates/nextjs/README.md +151 -0
- package/dist/templates/nextjs/app/about/page.tsx +26 -0
- package/dist/templates/nextjs/app/actions.ts +18 -0
- package/dist/templates/nextjs/app/api/health/route.ts +6 -0
- package/dist/templates/nextjs/app/components/counter.tsx +19 -0
- package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/dist/templates/nextjs/app/globals.css +4 -0
- package/dist/templates/nextjs/app/layout.tsx +27 -0
- package/dist/templates/nextjs/app/page.tsx +51 -0
- package/dist/templates/nextjs/app/providers.tsx +107 -0
- package/dist/templates/nextjs/artifact.bundle.yml +55 -0
- package/dist/templates/nextjs/next.config.mjs +27 -0
- package/dist/templates/nextjs/package.json +32 -0
- package/dist/templates/nextjs/pnpm-workspace.yaml +8 -0
- package/dist/templates/nextjs/postcss.config.mjs +5 -0
- package/dist/templates/nextjs/tsconfig.json +20 -0
- package/dist/templates/react-vite/artifact.bundle.yml +0 -1
- package/dist/templates/react-vite/package.json +1 -1
- package/dist/tunnel/broker.d.ts +75 -0
- package/dist/tunnel/broker.js +451 -0
- package/dist/tunnel/client.d.ts +22 -0
- package/dist/tunnel/client.js +177 -0
- package/dist/tunnel/index.d.ts +6 -0
- package/dist/tunnel/index.js +4 -0
- package/dist/tunnel/io.d.ts +33 -0
- package/dist/tunnel/io.js +108 -0
- package/dist/tunnel/peer.d.ts +25 -0
- package/dist/tunnel/peer.js +147 -0
- package/dist/tunnel/protocol.d.ts +69 -0
- package/dist/tunnel/protocol.js +196 -0
- package/dist/tunnel/redis-ownership.d.ts +38 -0
- package/dist/tunnel/redis-ownership.js +100 -0
- package/dist/tunnel/redis-socket.d.ts +13 -0
- package/dist/tunnel/redis-socket.js +48 -0
- package/dist/tunnel/redis-types.d.ts +70 -0
- package/dist/tunnel/redis-types.js +9 -0
- package/dist/tunnel/redis.d.ts +53 -0
- package/dist/tunnel/redis.js +391 -0
- package/dist/tunnel/transport.d.ts +17 -0
- package/dist/tunnel/transport.js +1 -0
- package/package.json +68 -4
- package/templates/nextjs/.artifactignore +14 -0
- package/templates/nextjs/README.md +151 -0
- package/templates/nextjs/app/about/page.tsx +26 -0
- package/templates/nextjs/app/actions.ts +18 -0
- package/templates/nextjs/app/api/health/route.ts +6 -0
- package/templates/nextjs/app/components/counter.tsx +19 -0
- package/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/templates/nextjs/app/globals.css +4 -0
- package/templates/nextjs/app/layout.tsx +27 -0
- package/templates/nextjs/app/page.tsx +51 -0
- package/templates/nextjs/app/providers.tsx +107 -0
- package/templates/nextjs/artifact.bundle.yml +55 -0
- package/templates/nextjs/next.config.mjs +27 -0
- package/templates/nextjs/package.json +32 -0
- package/templates/nextjs/pnpm-workspace.yaml +8 -0
- package/templates/nextjs/postcss.config.mjs +5 -0
- package/templates/nextjs/tsconfig.json +20 -0
- package/templates/react-vite/artifact.bundle.yml +0 -1
- package/templates/react-vite/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { storageScopesSchema } from '@sequenceholdings/storage';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { capabilityPinsSchema, customRolesBlockSchema, validateCapabilityPins } from './manifest-custom-roles.js';
|
|
4
|
+
import { apiCapabilityPathAllowed, artifactStudioStaticManifestSchema, sidebarBadgeSchema } from './manifest-static.js';
|
|
5
|
+
/** Approved major/minor profile. Builds additionally record exact patch versions. */
|
|
6
|
+
export const ARTIFACT_NEXT_RUNTIME = Object.freeze({ sdk: 'sequence', node: 24, react: 19, next: '16.3' });
|
|
7
|
+
/** Admission policy for new development sessions and builds; retained releases keep their recorded toolchain. */
|
|
8
|
+
export const ARTIFACT_NEXT_APPROVED_PACKAGES = Object.freeze({ next: '16.3.4', react: '19.2.6', 'react-dom': '19.2.6' });
|
|
9
|
+
export const ARTIFACT_NEXT_RESOURCE_DEFAULTS = {
|
|
10
|
+
cpu: 1, memory_mb: 1024, concurrency: 20, min_instances: 0, max_instances: 3, timeout_seconds: 60,
|
|
11
|
+
};
|
|
12
|
+
const boundedRef = z.string().min(1).max(255).regex(/^[a-zA-Z0-9][a-zA-Z0-9_./:-]*$/).refine((value) => value.split('/').every((part) => part !== '' && part !== '.' && part !== '..'), 'Resource references cannot contain empty segments or traversal');
|
|
13
|
+
const serviceAccountRef = z.string().regex(/^([a-z][a-z0-9-]{1,98}|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/);
|
|
14
|
+
const apiPath = z.string().min(1).max(512).refine((value) => {
|
|
15
|
+
if (!value.startsWith('/api/') || value === '/api/' || value === '/api/*')
|
|
16
|
+
return false;
|
|
17
|
+
if (/[\\%?#\s\x00-\x1f]/.test(value))
|
|
18
|
+
return false;
|
|
19
|
+
return value.split('/').slice(1).every((part, index, parts) => part !== '.' && part !== '..' && (part !== '' || index === parts.length - 1));
|
|
20
|
+
}, 'API capability paths must be scoped below /api/ without encoded paths, traversal, queries, or fragments');
|
|
21
|
+
const reachShape = {
|
|
22
|
+
storage: storageScopesSchema.optional(),
|
|
23
|
+
data: z.strictObject({ read: z.array(boundedRef).max(256).default([]), write: z.array(boundedRef).max(256).default([]) }).prefault({}),
|
|
24
|
+
api: z.strictObject({ read: z.array(apiPath).max(256).default([]), write: z.array(apiPath).max(256).default([]) }).prefault({}),
|
|
25
|
+
functions: z.strictObject({ invoke: z.array(boundedRef).max(256).default([]) }).prefault({}),
|
|
26
|
+
};
|
|
27
|
+
export const artifactNextServerCapabilitiesSchema = z.strictObject(reachShape).prefault({});
|
|
28
|
+
// Reuse role semantics and validation; only the new manifest rejects unknown keys.
|
|
29
|
+
const strictCustomRoles = customRolesBlockSchema.safeExtend({
|
|
30
|
+
definitions: z.record(customRolesBlockSchema.shape.definitions.unwrap().keyType, customRolesBlockSchema.shape.definitions.unwrap().valueType.strict()).default({}),
|
|
31
|
+
capabilities: z.record(customRolesBlockSchema.shape.capabilities.unwrap().keyType, customRolesBlockSchema.shape.capabilities.unwrap().valueType.strict()).default({}),
|
|
32
|
+
}).strict();
|
|
33
|
+
const browserCapabilities = z.strictObject({
|
|
34
|
+
...reachShape,
|
|
35
|
+
allow_downloads: z.boolean().default(false),
|
|
36
|
+
allow_popups: z.boolean().default(false),
|
|
37
|
+
roles: strictCustomRoles.optional(),
|
|
38
|
+
uses: capabilityPinsSchema,
|
|
39
|
+
}).prefault({});
|
|
40
|
+
export const artifactNextResourceLimitsSchema = z.strictObject({
|
|
41
|
+
cpu: z.union([z.literal(1), z.literal(2), z.literal(4), z.literal(8)]).default(1),
|
|
42
|
+
memory_mb: z.number().int().min(512).max(32768).default(1024),
|
|
43
|
+
concurrency: z.number().int().min(1).max(80).default(20),
|
|
44
|
+
min_instances: z.number().int().min(0).max(20).default(0),
|
|
45
|
+
max_instances: z.number().int().min(1).max(100).default(3),
|
|
46
|
+
timeout_seconds: z.number().int().min(1).max(300).default(60),
|
|
47
|
+
}).superRefine((limits, ctx) => {
|
|
48
|
+
if (limits.min_instances > limits.max_instances)
|
|
49
|
+
ctx.addIssue({ code: 'custom', path: ['min_instances'], message: 'min_instances cannot exceed max_instances' });
|
|
50
|
+
if (limits.memory_mb < limits.cpu * 512 || limits.memory_mb > limits.cpu * 8192) {
|
|
51
|
+
ctx.addIssue({ code: 'custom', path: ['memory_mb'], message: 'memory_mb must be between 512 and 8192 MiB per CPU' });
|
|
52
|
+
}
|
|
53
|
+
}).prefault({});
|
|
54
|
+
const envName = z.string().max(128).regex(/^[A-Z][A-Z0-9_]*$/);
|
|
55
|
+
const reservedServerEnv = /^(NEXT_PUBLIC_|ARTIFACT_|SEQUENCE_|GOOGLE_|GCP_|GCLOUD_|DOPPLER_|MODAL_|AUTH0_|NPM_CONFIG_|npm_config_)/;
|
|
56
|
+
export const artifactNextSecretNameSchema = envName.refine((name) => !reservedServerEnv.test(name) && !['NODE_OPTIONS', 'NODE_ENV', 'PORT', 'HOSTNAME', 'PATH', 'HOME', 'LD_PRELOAD', 'LD_LIBRARY_PATH'].includes(name), 'Secret names cannot override platform/runtime environment variables or expose NEXT_PUBLIC_ values');
|
|
57
|
+
export const artifactNextSecretBindingSchema = z.strictObject({
|
|
58
|
+
name: artifactNextSecretNameSchema,
|
|
59
|
+
secret_id: z.uuid(),
|
|
60
|
+
version: z.number().int().positive(),
|
|
61
|
+
});
|
|
62
|
+
const publicEnvironmentName = envName.refine((name) => name.startsWith('NEXT_PUBLIC_') && !/(^|_)(SECRET|TOKEN|PASSWORD|CREDENTIALS?|PRIVATE_KEY|API_KEY)(_|$)/.test(name), 'Public build variables require NEXT_PUBLIC_ and must not contain secret, token, password, credential, or private/API key names');
|
|
63
|
+
/** Pinned public assets fetched by the trusted preparation stage before offline compilation. */
|
|
64
|
+
export const artifactNextPublicFontSchema = z.strictObject({
|
|
65
|
+
path: z.string().max(512).regex(/^public\/fonts\/[A-Za-z0-9][A-Za-z0-9_-]*\.woff2$/, 'Font paths must be WOFF2 files directly under app_root/public/fonts with ASCII filenames'),
|
|
66
|
+
url: z.string().max(2048).regex(/^https:\/\/fonts\.gstatic\.com\/s\/[a-z0-9]+\/v[0-9]+\/[A-Za-z0-9_-]+\.woff2$/, 'Font URLs must use the exact HTTPS fonts.gstatic.com versioned WOFF2 path without credentials, ports, queries, fragments, or encoding'),
|
|
67
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/, 'Expected a lowercase SHA-256 digest'),
|
|
68
|
+
});
|
|
69
|
+
export const artifactNextBuildConfigSchema = z.strictObject({
|
|
70
|
+
public_env: z.record(publicEnvironmentName, z.string().max(8192)).refine((values) => Object.keys(values).length <= 64, 'At most 64 public build variables are allowed').default({}),
|
|
71
|
+
public_fonts: z.array(artifactNextPublicFontSchema).max(8).refine((fonts) => new Set(fonts.map((font) => font.path)).size === fonts.length, 'Public font destination paths must be unique').default([]),
|
|
72
|
+
}).prefault({});
|
|
73
|
+
const hostname = z.string().max(253).regex(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/, 'Egress entries must be exact lowercase DNS hostnames without schemes, ports, paths, IPs, or wildcards').refine((host) => !host.endsWith('.localhost') && !host.endsWith('.local') && !host.endsWith('.internal'), 'Egress cannot target local or platform-internal hostnames');
|
|
74
|
+
export const artifactNextServerSchema = z.strictObject({
|
|
75
|
+
capabilities: artifactNextServerCapabilitiesSchema,
|
|
76
|
+
service_account: serviceAccountRef.optional(),
|
|
77
|
+
secrets: z.array(artifactNextSecretBindingSchema).max(32).default([]),
|
|
78
|
+
egress: z.array(hostname).max(64).default([]),
|
|
79
|
+
resources: artifactNextResourceLimitsSchema,
|
|
80
|
+
cache: z.strictObject({ capacity_gb: z.number().int().min(1).max(16) }).optional(),
|
|
81
|
+
}).superRefine((server, ctx) => {
|
|
82
|
+
if (new Set(server.secrets.map((secret) => secret.name)).size !== server.secrets.length) {
|
|
83
|
+
ctx.addIssue({ code: 'custom', path: ['secrets'], message: 'Each secret environment name must be unique' });
|
|
84
|
+
}
|
|
85
|
+
if (new Set(server.egress).size !== server.egress.length) {
|
|
86
|
+
ctx.addIssue({ code: 'custom', path: ['egress'], message: 'Egress hosts must be unique' });
|
|
87
|
+
}
|
|
88
|
+
}).prefault({});
|
|
89
|
+
const appRoot = z.string().min(1).max(512).refine((value) => {
|
|
90
|
+
if (value === '.')
|
|
91
|
+
return true;
|
|
92
|
+
return !value.startsWith('/') && !/[\\:\s\x00-\x1f]/.test(value) &&
|
|
93
|
+
value.split('/').every((part) => part !== '' && part !== '.' && part !== '..' && !part.startsWith('.') && part !== 'node_modules');
|
|
94
|
+
}, 'app_root must be a relative source directory without traversal, hidden directories, or node_modules').default('.');
|
|
95
|
+
export const artifactStudioNextManifestSchema = z.strictObject({
|
|
96
|
+
bundle: z.strictObject({ name: z.string().min(1).max(255), schema_version: z.literal(2) }),
|
|
97
|
+
artifact: z.strictObject({
|
|
98
|
+
project_id: artifactStudioStaticManifestSchema.shape.artifact.shape.project_id,
|
|
99
|
+
title: z.string().min(1).max(255),
|
|
100
|
+
description: z.string().max(2000).optional(),
|
|
101
|
+
type: z.literal('nextjs'),
|
|
102
|
+
app_root: appRoot,
|
|
103
|
+
}),
|
|
104
|
+
runtime: z.strictObject({
|
|
105
|
+
sdk: z.literal('sequence').default('sequence'), node: z.literal(24).default(24), react: z.literal(19).default(19), next: z.literal('16.3').default('16.3'),
|
|
106
|
+
}).prefault({}),
|
|
107
|
+
capabilities: browserCapabilities,
|
|
108
|
+
build: artifactNextBuildConfigSchema,
|
|
109
|
+
server: artifactNextServerSchema,
|
|
110
|
+
targets: z.record(z.string().min(1), z.strictObject({ url: z.string().url() })).default({}),
|
|
111
|
+
sidebarIcon: z.string().max(64).optional(),
|
|
112
|
+
sidebarBadge: sidebarBadgeSchema.strict().optional(),
|
|
113
|
+
}).superRefine((manifest, ctx) => {
|
|
114
|
+
for (const message of validateCapabilityPins({ uses: manifest.capabilities.uses, roles: manifest.capabilities.roles })) {
|
|
115
|
+
ctx.addIssue({ code: 'custom', path: ['capabilities', 'uses'], message });
|
|
116
|
+
}
|
|
117
|
+
if (manifest.sidebarBadge && !apiCapabilityPathAllowed(manifest.sidebarBadge.endpoint, manifest.capabilities.api.read)) {
|
|
118
|
+
ctx.addIssue({ code: 'custom', path: ['sidebarBadge', 'endpoint'], message: 'sidebarBadge.endpoint must be declared in capabilities.api.read' });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export { CUSTOM_CAPABILITY_PIN_RE, CUSTOM_ROLE_KEY_RE, capabilityPinsSchema, customRolesBlockSchema, customRolesDeclared, parseCapabilityPin, validateCapabilityPins, type ArtifactStudioCustomRoles, type ParsedCapabilityPin, } from './manifest-custom-roles.js';
|
|
3
|
+
export declare const sidebarBadgeSchema: z.ZodObject<{
|
|
4
|
+
endpoint: z.ZodString;
|
|
5
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
6
|
+
alert: "alert";
|
|
7
|
+
muted: "muted";
|
|
8
|
+
}>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type SidebarBadgeManifest = z.infer<typeof sidebarBadgeSchema>;
|
|
11
|
+
export declare function apiCapabilityPathAllowed(pathname: string, allowed: readonly string[]): boolean;
|
|
12
|
+
export declare const artifactStudioStaticManifestSchema: z.ZodObject<{
|
|
13
|
+
bundle: z.ZodObject<{
|
|
14
|
+
name: z.ZodString;
|
|
15
|
+
schema_version: z.ZodDefault<z.ZodLiteral<1>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
artifact: z.ZodObject<{
|
|
18
|
+
project_id: z.ZodString;
|
|
19
|
+
title: z.ZodString;
|
|
20
|
+
description: z.ZodOptional<z.ZodString>;
|
|
21
|
+
type: z.ZodDefault<z.ZodEnum<{
|
|
22
|
+
react: "react";
|
|
23
|
+
html: "html";
|
|
24
|
+
}>>;
|
|
25
|
+
entrypoint: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
runtime: z.ZodDefault<z.ZodObject<{
|
|
28
|
+
sdk: z.ZodDefault<z.ZodLiteral<"sequence">>;
|
|
29
|
+
react: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
capabilities: z.ZodDefault<z.ZodObject<{
|
|
32
|
+
storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
|
|
33
|
+
viewer: "viewer";
|
|
34
|
+
editor: "editor";
|
|
35
|
+
admin: "admin";
|
|
36
|
+
}>>>;
|
|
37
|
+
data: z.ZodDefault<z.ZodObject<{
|
|
38
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
39
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
40
|
+
}, z.core.$strip>>;
|
|
41
|
+
api: z.ZodDefault<z.ZodObject<{
|
|
42
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
43
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
functions: z.ZodDefault<z.ZodObject<{
|
|
46
|
+
invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
47
|
+
}, z.core.$strip>>;
|
|
48
|
+
allow_downloads: z.ZodDefault<z.ZodBoolean>;
|
|
49
|
+
allow_popups: z.ZodDefault<z.ZodBoolean>;
|
|
50
|
+
roles: z.ZodOptional<z.ZodObject<{
|
|
51
|
+
definitions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
52
|
+
description: z.ZodOptional<z.ZodString>;
|
|
53
|
+
default: z.ZodDefault<z.ZodBoolean>;
|
|
54
|
+
inherits: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
55
|
+
grants: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
56
|
+
}, z.core.$strip>>>;
|
|
57
|
+
capabilities: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
58
|
+
description: z.ZodOptional<z.ZodString>;
|
|
59
|
+
}, z.core.$strip>>>;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
uses: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
62
|
+
}, z.core.$strip>>;
|
|
63
|
+
targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
64
|
+
url: z.ZodString;
|
|
65
|
+
}, z.core.$strict>>>;
|
|
66
|
+
sidebarIcon: z.ZodOptional<z.ZodString>;
|
|
67
|
+
sidebarBadge: z.ZodOptional<z.ZodObject<{
|
|
68
|
+
endpoint: z.ZodString;
|
|
69
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
70
|
+
alert: "alert";
|
|
71
|
+
muted: "muted";
|
|
72
|
+
}>>;
|
|
73
|
+
}, z.core.$strip>>;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
export type ArtifactStudioStaticManifest = z.infer<typeof artifactStudioStaticManifestSchema>;
|
|
76
|
+
export declare function parseArtifactStudioStaticManifest(value: unknown): ArtifactStudioStaticManifest;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { storageScopesSchema } from '@sequenceholdings/storage';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { capabilityPinsSchema, customRolesBlockSchema, validateCapabilityPins, } from './manifest-custom-roles.js';
|
|
4
|
+
import { normalizeArtifactPath } from './paths.js';
|
|
5
|
+
export { CUSTOM_CAPABILITY_PIN_RE, CUSTOM_ROLE_KEY_RE, capabilityPinsSchema, customRolesBlockSchema, customRolesDeclared, parseCapabilityPin, validateCapabilityPins, } from './manifest-custom-roles.js';
|
|
6
|
+
const apiCapabilityPathSchema = z.string().min(1).refine((path) => path.startsWith('/api/') && path !== '/api/' && path !== '/api/*', 'API capability paths must be scoped below /api/');
|
|
7
|
+
const apiCapabilitiesSchema = z.object({
|
|
8
|
+
read: z.array(apiCapabilityPathSchema).default([]),
|
|
9
|
+
write: z.array(apiCapabilityPathSchema).default([]),
|
|
10
|
+
}).default({ read: [], write: [] });
|
|
11
|
+
const sidebarBadgeToneSchema = z.enum(['alert', 'muted']);
|
|
12
|
+
export const sidebarBadgeSchema = z.object({
|
|
13
|
+
endpoint: apiCapabilityPathSchema,
|
|
14
|
+
tone: sidebarBadgeToneSchema.optional(),
|
|
15
|
+
});
|
|
16
|
+
function matchesApiCapabilityEntry(pathname, entry) {
|
|
17
|
+
if (!entry.startsWith('/api/'))
|
|
18
|
+
return false;
|
|
19
|
+
if (entry === pathname)
|
|
20
|
+
return true;
|
|
21
|
+
if (entry.includes('*') && (!entry.endsWith('/*') || hasMidPathWildcard(entry))) {
|
|
22
|
+
const pathParts = pathname.split('/');
|
|
23
|
+
const entryParts = entry.split('/');
|
|
24
|
+
return (pathParts.length === entryParts.length &&
|
|
25
|
+
entryParts.every((part, index) => part === '*' || part === pathParts[index]));
|
|
26
|
+
}
|
|
27
|
+
if (entry.endsWith('/*')) {
|
|
28
|
+
const prefix = entry.slice(0, -1);
|
|
29
|
+
return pathname.startsWith(prefix);
|
|
30
|
+
}
|
|
31
|
+
if (entry.endsWith('/')) {
|
|
32
|
+
return pathname.startsWith(entry);
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function hasMidPathWildcard(entry) {
|
|
37
|
+
const parts = entry.split('/');
|
|
38
|
+
return parts.slice(1, -1).some((part) => part === '*');
|
|
39
|
+
}
|
|
40
|
+
export function apiCapabilityPathAllowed(pathname, allowed) {
|
|
41
|
+
return allowed.some((entry) => matchesApiCapabilityEntry(pathname, entry));
|
|
42
|
+
}
|
|
43
|
+
export const artifactStudioStaticManifestSchema = z.object({
|
|
44
|
+
bundle: z.object({
|
|
45
|
+
name: z.string().min(1).max(255),
|
|
46
|
+
schema_version: z.literal(1).default(1),
|
|
47
|
+
}),
|
|
48
|
+
artifact: z.object({
|
|
49
|
+
project_id: z.string().min(1).max(255).regex(/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/),
|
|
50
|
+
title: z.string().min(1).max(255),
|
|
51
|
+
description: z.string().max(2000).optional(),
|
|
52
|
+
type: z.enum(['react', 'html']).default('react'),
|
|
53
|
+
entrypoint: z.string().min(1).max(512).transform(normalizeArtifactPath),
|
|
54
|
+
}),
|
|
55
|
+
runtime: z.object({
|
|
56
|
+
sdk: z.literal('sequence').default('sequence'),
|
|
57
|
+
react: z.union([z.literal(18), z.literal(19)]).default(18),
|
|
58
|
+
}).default({ sdk: 'sequence', react: 18 }),
|
|
59
|
+
capabilities: z.object({
|
|
60
|
+
storage: storageScopesSchema.optional(),
|
|
61
|
+
data: z.object({
|
|
62
|
+
read: z.array(z.string().min(1)).default([]),
|
|
63
|
+
write: z.array(z.string().min(1)).default([]),
|
|
64
|
+
}).default({ read: [], write: [] }),
|
|
65
|
+
api: apiCapabilitiesSchema,
|
|
66
|
+
functions: z.object({
|
|
67
|
+
invoke: z.array(z.string().min(1)).default([]),
|
|
68
|
+
}).default({ invoke: [] }),
|
|
69
|
+
/** Opt-in: enables iframe `allow-downloads`. Default false. */
|
|
70
|
+
allow_downloads: z.boolean().default(false),
|
|
71
|
+
/**
|
|
72
|
+
* Opt-in: enables iframe `allow-popups allow-popups-to-escape-sandbox` so
|
|
73
|
+
* the artifact can open a new top-level (unsandboxed) browser tab — needed
|
|
74
|
+
* to render a PDF in the native viewer, which the sandbox CSP blocks
|
|
75
|
+
* in-frame. Default false; grant only to trusted-authored artifacts.
|
|
76
|
+
*/
|
|
77
|
+
allow_popups: z.boolean().default(false),
|
|
78
|
+
/**
|
|
79
|
+
* Custom Roles & Capabilities (PER-61): manifest-declared roles and the
|
|
80
|
+
* capability keys the artifact's code checks. Reconciled into
|
|
81
|
+
* `custom_role` FGA structural tuples when a deploy activates.
|
|
82
|
+
*/
|
|
83
|
+
roles: customRolesBlockSchema.optional(),
|
|
84
|
+
/**
|
|
85
|
+
* Capability pins (PER-76): registry capabilities this artifact's code
|
|
86
|
+
* checks — `org/<key>` or a bare project-scoped `<key>`. Reconciled into
|
|
87
|
+
* the host's attachment set at activation; existence is validated
|
|
88
|
+
* server-side there.
|
|
89
|
+
*/
|
|
90
|
+
uses: capabilityPinsSchema,
|
|
91
|
+
}).default({
|
|
92
|
+
data: { read: [], write: [] },
|
|
93
|
+
api: { read: [], write: [] },
|
|
94
|
+
functions: { invoke: [] },
|
|
95
|
+
allow_downloads: false,
|
|
96
|
+
allow_popups: false,
|
|
97
|
+
uses: [],
|
|
98
|
+
}),
|
|
99
|
+
targets: z.record(z.string().min(1), z.object({
|
|
100
|
+
url: z.string().url(),
|
|
101
|
+
}).strict()).default({}),
|
|
102
|
+
sidebarIcon: z.string().max(64).optional(),
|
|
103
|
+
sidebarBadge: sidebarBadgeSchema.optional(),
|
|
104
|
+
}).superRefine((manifest, ctx) => {
|
|
105
|
+
for (const message of validateCapabilityPins({
|
|
106
|
+
uses: manifest.capabilities.uses,
|
|
107
|
+
roles: manifest.capabilities.roles,
|
|
108
|
+
})) {
|
|
109
|
+
ctx.addIssue({ code: 'custom', message, path: ['capabilities', 'uses'] });
|
|
110
|
+
}
|
|
111
|
+
if (!manifest.sidebarBadge)
|
|
112
|
+
return;
|
|
113
|
+
const endpoint = manifest.sidebarBadge.endpoint;
|
|
114
|
+
if (!apiCapabilityPathAllowed(endpoint, manifest.capabilities.api.read)) {
|
|
115
|
+
ctx.addIssue({
|
|
116
|
+
code: 'custom',
|
|
117
|
+
message: `sidebarBadge.endpoint ${endpoint} must be declared in capabilities.api.read`,
|
|
118
|
+
path: ['sidebarBadge', 'endpoint'],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
export function parseArtifactStudioStaticManifest(value) {
|
|
123
|
+
return artifactStudioStaticManifestSchema.parse(value);
|
|
124
|
+
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}>>;
|
|
9
|
-
}, z.core.$strip>;
|
|
10
|
-
export type SidebarBadgeManifest = z.infer<typeof sidebarBadgeSchema>;
|
|
11
|
-
export declare function apiCapabilityPathAllowed(pathname: string, allowed: readonly string[]): boolean;
|
|
12
|
-
export declare const artifactStudioManifestSchema: z.ZodObject<{
|
|
2
|
+
import { type ArtifactStudioStaticManifest } from './manifest-static.js';
|
|
3
|
+
import { type ArtifactStudioNextManifest } from './manifest-next.js';
|
|
4
|
+
export * from './manifest-static.js';
|
|
5
|
+
export * from './manifest-next.js';
|
|
6
|
+
/** Versioned parsing keeps schema-v1 stripping/default behavior unchanged. */
|
|
7
|
+
export declare const artifactStudioManifestSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
13
8
|
bundle: z.ZodObject<{
|
|
14
9
|
name: z.ZodString;
|
|
15
10
|
schema_version: z.ZodDefault<z.ZodLiteral<1>>;
|
|
@@ -29,6 +24,11 @@ export declare const artifactStudioManifestSchema: z.ZodObject<{
|
|
|
29
24
|
react: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<18>, z.ZodLiteral<19>]>>;
|
|
30
25
|
}, z.core.$strip>>;
|
|
31
26
|
capabilities: z.ZodDefault<z.ZodObject<{
|
|
27
|
+
storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
|
|
28
|
+
viewer: "viewer";
|
|
29
|
+
editor: "editor";
|
|
30
|
+
admin: "admin";
|
|
31
|
+
}>>>;
|
|
32
32
|
data: z.ZodDefault<z.ZodObject<{
|
|
33
33
|
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
34
|
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
@@ -57,12 +57,8 @@ export declare const artifactStudioManifestSchema: z.ZodObject<{
|
|
|
57
57
|
}, z.core.$strip>>;
|
|
58
58
|
targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
59
59
|
url: z.ZodString;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
private: "private";
|
|
63
|
-
shared: "shared";
|
|
64
|
-
}>>;
|
|
65
|
-
}, z.core.$strip>>>;
|
|
60
|
+
}, z.core.$strict>>>;
|
|
61
|
+
sidebarIcon: z.ZodOptional<z.ZodString>;
|
|
66
62
|
sidebarBadge: z.ZodOptional<z.ZodObject<{
|
|
67
63
|
endpoint: z.ZodString;
|
|
68
64
|
tone: z.ZodOptional<z.ZodEnum<{
|
|
@@ -70,6 +66,115 @@ export declare const artifactStudioManifestSchema: z.ZodObject<{
|
|
|
70
66
|
muted: "muted";
|
|
71
67
|
}>>;
|
|
72
68
|
}, z.core.$strip>>;
|
|
73
|
-
}, z.core.$strip
|
|
74
|
-
|
|
69
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
70
|
+
bundle: z.ZodObject<{
|
|
71
|
+
name: z.ZodString;
|
|
72
|
+
schema_version: z.ZodLiteral<2>;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
artifact: z.ZodObject<{
|
|
75
|
+
project_id: z.ZodString;
|
|
76
|
+
title: z.ZodString;
|
|
77
|
+
description: z.ZodOptional<z.ZodString>;
|
|
78
|
+
type: z.ZodLiteral<"nextjs">;
|
|
79
|
+
app_root: z.ZodDefault<z.ZodString>;
|
|
80
|
+
}, z.core.$strict>;
|
|
81
|
+
runtime: z.ZodPrefault<z.ZodObject<{
|
|
82
|
+
sdk: z.ZodDefault<z.ZodLiteral<"sequence">>;
|
|
83
|
+
node: z.ZodDefault<z.ZodLiteral<24>>;
|
|
84
|
+
react: z.ZodDefault<z.ZodLiteral<19>>;
|
|
85
|
+
next: z.ZodDefault<z.ZodLiteral<"16.3">>;
|
|
86
|
+
}, z.core.$strict>>;
|
|
87
|
+
capabilities: z.ZodPrefault<z.ZodObject<{
|
|
88
|
+
allow_downloads: z.ZodDefault<z.ZodBoolean>;
|
|
89
|
+
allow_popups: z.ZodDefault<z.ZodBoolean>;
|
|
90
|
+
roles: z.ZodOptional<z.ZodObject<{
|
|
91
|
+
definitions: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
92
|
+
description: z.ZodOptional<z.ZodString>;
|
|
93
|
+
default: z.ZodDefault<z.ZodBoolean>;
|
|
94
|
+
inherits: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
95
|
+
grants: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
96
|
+
}, z.core.$strict>>>;
|
|
97
|
+
capabilities: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
98
|
+
description: z.ZodOptional<z.ZodString>;
|
|
99
|
+
}, z.core.$strict>>>;
|
|
100
|
+
}, z.core.$strict>>;
|
|
101
|
+
uses: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
102
|
+
storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
|
|
103
|
+
viewer: "viewer";
|
|
104
|
+
editor: "editor";
|
|
105
|
+
admin: "admin";
|
|
106
|
+
}>>>;
|
|
107
|
+
data: z.ZodPrefault<z.ZodObject<{
|
|
108
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
109
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
110
|
+
}, z.core.$strict>>;
|
|
111
|
+
api: z.ZodPrefault<z.ZodObject<{
|
|
112
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
113
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
114
|
+
}, z.core.$strict>>;
|
|
115
|
+
functions: z.ZodPrefault<z.ZodObject<{
|
|
116
|
+
invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
117
|
+
}, z.core.$strict>>;
|
|
118
|
+
}, z.core.$strict>>;
|
|
119
|
+
build: z.ZodPrefault<z.ZodObject<{
|
|
120
|
+
public_env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
121
|
+
public_fonts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
122
|
+
path: z.ZodString;
|
|
123
|
+
url: z.ZodString;
|
|
124
|
+
sha256: z.ZodString;
|
|
125
|
+
}, z.core.$strict>>>;
|
|
126
|
+
}, z.core.$strict>>;
|
|
127
|
+
server: z.ZodPrefault<z.ZodObject<{
|
|
128
|
+
capabilities: z.ZodPrefault<z.ZodObject<{
|
|
129
|
+
storage: z.ZodOptional<z.ZodRecord<z.ZodUUID, z.ZodEnum<{
|
|
130
|
+
viewer: "viewer";
|
|
131
|
+
editor: "editor";
|
|
132
|
+
admin: "admin";
|
|
133
|
+
}>>>;
|
|
134
|
+
data: z.ZodPrefault<z.ZodObject<{
|
|
135
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
136
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
137
|
+
}, z.core.$strict>>;
|
|
138
|
+
api: z.ZodPrefault<z.ZodObject<{
|
|
139
|
+
read: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
140
|
+
write: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
141
|
+
}, z.core.$strict>>;
|
|
142
|
+
functions: z.ZodPrefault<z.ZodObject<{
|
|
143
|
+
invoke: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
144
|
+
}, z.core.$strict>>;
|
|
145
|
+
}, z.core.$strict>>;
|
|
146
|
+
service_account: z.ZodOptional<z.ZodString>;
|
|
147
|
+
secrets: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
148
|
+
name: z.ZodString;
|
|
149
|
+
secret_id: z.ZodUUID;
|
|
150
|
+
version: z.ZodNumber;
|
|
151
|
+
}, z.core.$strict>>>;
|
|
152
|
+
egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
153
|
+
resources: z.ZodPrefault<z.ZodObject<{
|
|
154
|
+
cpu: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<4>, z.ZodLiteral<8>]>>;
|
|
155
|
+
memory_mb: z.ZodDefault<z.ZodNumber>;
|
|
156
|
+
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
157
|
+
min_instances: z.ZodDefault<z.ZodNumber>;
|
|
158
|
+
max_instances: z.ZodDefault<z.ZodNumber>;
|
|
159
|
+
timeout_seconds: z.ZodDefault<z.ZodNumber>;
|
|
160
|
+
}, z.core.$strict>>;
|
|
161
|
+
cache: z.ZodOptional<z.ZodObject<{
|
|
162
|
+
capacity_gb: z.ZodNumber;
|
|
163
|
+
}, z.core.$strict>>;
|
|
164
|
+
}, z.core.$strict>>;
|
|
165
|
+
targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
166
|
+
url: z.ZodString;
|
|
167
|
+
}, z.core.$strict>>>;
|
|
168
|
+
sidebarIcon: z.ZodOptional<z.ZodString>;
|
|
169
|
+
sidebarBadge: z.ZodOptional<z.ZodObject<{
|
|
170
|
+
endpoint: z.ZodString;
|
|
171
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
172
|
+
alert: "alert";
|
|
173
|
+
muted: "muted";
|
|
174
|
+
}>>;
|
|
175
|
+
}, z.core.$strict>>;
|
|
176
|
+
}, z.core.$strict>]>;
|
|
177
|
+
export type ArtifactStudioManifest = ArtifactStudioStaticManifest | ArtifactStudioNextManifest;
|
|
178
|
+
export type ArtifactStudioManifestInput = z.input<typeof artifactStudioManifestSchema>;
|
|
75
179
|
export declare function parseArtifactStudioManifest(value: unknown): ArtifactStudioManifest;
|
|
180
|
+
export declare function isNextArtifactManifest(manifest: ArtifactStudioManifest): manifest is ArtifactStudioNextManifest;
|
package/dist/manifest.js
CHANGED
|
@@ -1,122 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export const sidebarBadgeSchema = z.object({
|
|
12
|
-
endpoint: apiCapabilityPathSchema,
|
|
13
|
-
tone: sidebarBadgeToneSchema.optional(),
|
|
14
|
-
});
|
|
15
|
-
function matchesApiCapabilityEntry(pathname, entry) {
|
|
16
|
-
if (!entry.startsWith('/api/'))
|
|
17
|
-
return false;
|
|
18
|
-
if (entry === pathname)
|
|
19
|
-
return true;
|
|
20
|
-
if (entry.includes('*') && (!entry.endsWith('/*') || hasMidPathWildcard(entry))) {
|
|
21
|
-
const pathParts = pathname.split('/');
|
|
22
|
-
const entryParts = entry.split('/');
|
|
23
|
-
return (pathParts.length === entryParts.length &&
|
|
24
|
-
entryParts.every((part, index) => part === '*' || part === pathParts[index]));
|
|
25
|
-
}
|
|
26
|
-
if (entry.endsWith('/*')) {
|
|
27
|
-
const prefix = entry.slice(0, -1);
|
|
28
|
-
return pathname.startsWith(prefix);
|
|
29
|
-
}
|
|
30
|
-
if (entry.endsWith('/')) {
|
|
31
|
-
return pathname.startsWith(entry);
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
function hasMidPathWildcard(entry) {
|
|
36
|
-
const parts = entry.split('/');
|
|
37
|
-
return parts.slice(1, -1).some((part) => part === '*');
|
|
38
|
-
}
|
|
39
|
-
export function apiCapabilityPathAllowed(pathname, allowed) {
|
|
40
|
-
return allowed.some((entry) => matchesApiCapabilityEntry(pathname, entry));
|
|
41
|
-
}
|
|
42
|
-
export const artifactStudioManifestSchema = z.object({
|
|
43
|
-
bundle: z.object({
|
|
44
|
-
name: z.string().min(1).max(255),
|
|
45
|
-
schema_version: z.literal(1).default(1),
|
|
46
|
-
}),
|
|
47
|
-
artifact: z.object({
|
|
48
|
-
project_id: z.string().min(1).max(255).regex(/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/),
|
|
49
|
-
title: z.string().min(1).max(255),
|
|
50
|
-
description: z.string().max(2000).optional(),
|
|
51
|
-
type: z.enum(['react', 'html']).default('react'),
|
|
52
|
-
entrypoint: z.string().min(1).max(512).transform(normalizeArtifactPath),
|
|
53
|
-
}),
|
|
54
|
-
runtime: z.object({
|
|
55
|
-
sdk: z.literal('sequence').default('sequence'),
|
|
56
|
-
react: z.union([z.literal(18), z.literal(19)]).default(18),
|
|
57
|
-
}).default({ sdk: 'sequence', react: 18 }),
|
|
58
|
-
capabilities: z.object({
|
|
59
|
-
data: z.object({
|
|
60
|
-
read: z.array(z.string().min(1)).default([]),
|
|
61
|
-
write: z.array(z.string().min(1)).default([]),
|
|
62
|
-
}).default({ read: [], write: [] }),
|
|
63
|
-
api: apiCapabilitiesSchema,
|
|
64
|
-
functions: z.object({
|
|
65
|
-
invoke: z.array(z.string().min(1)).default([]),
|
|
66
|
-
}).default({ invoke: [] }),
|
|
67
|
-
/** Opt-in: enables iframe `allow-downloads`. Default false. */
|
|
68
|
-
allow_downloads: z.boolean().default(false),
|
|
69
|
-
/**
|
|
70
|
-
* Opt-in: enables iframe `allow-popups allow-popups-to-escape-sandbox` so
|
|
71
|
-
* the artifact can open a new top-level (unsandboxed) browser tab — needed
|
|
72
|
-
* to render a PDF in the native viewer, which the sandbox CSP blocks
|
|
73
|
-
* in-frame. Default false; grant only to trusted-authored artifacts.
|
|
74
|
-
*/
|
|
75
|
-
allow_popups: z.boolean().default(false),
|
|
76
|
-
/**
|
|
77
|
-
* Custom Roles & Capabilities (PER-61): manifest-declared roles and the
|
|
78
|
-
* capability keys the artifact's code checks. Reconciled into
|
|
79
|
-
* `custom_role` FGA structural tuples when a deploy activates.
|
|
80
|
-
*/
|
|
81
|
-
roles: customRolesBlockSchema.optional(),
|
|
82
|
-
/**
|
|
83
|
-
* Capability pins (PER-76): registry capabilities this artifact's code
|
|
84
|
-
* checks — `org/<key>` or a bare project-scoped `<key>`. Reconciled into
|
|
85
|
-
* the host's attachment set at activation; existence is validated
|
|
86
|
-
* server-side there.
|
|
87
|
-
*/
|
|
88
|
-
uses: capabilityPinsSchema,
|
|
89
|
-
}).default({
|
|
90
|
-
data: { read: [], write: [] },
|
|
91
|
-
api: { read: [], write: [] },
|
|
92
|
-
functions: { invoke: [] },
|
|
93
|
-
allow_downloads: false,
|
|
94
|
-
allow_popups: false,
|
|
95
|
-
uses: [],
|
|
96
|
-
}),
|
|
97
|
-
targets: z.record(z.string().min(1), z.object({
|
|
98
|
-
url: z.string().url(),
|
|
99
|
-
visibility: z.enum(['private', 'shared', 'published']).default('private'),
|
|
100
|
-
})).default({}),
|
|
101
|
-
sidebarBadge: sidebarBadgeSchema.optional(),
|
|
102
|
-
}).superRefine((manifest, ctx) => {
|
|
103
|
-
for (const message of validateCapabilityPins({
|
|
104
|
-
uses: manifest.capabilities.uses,
|
|
105
|
-
roles: manifest.capabilities.roles,
|
|
106
|
-
})) {
|
|
107
|
-
ctx.addIssue({ code: 'custom', message, path: ['capabilities', 'uses'] });
|
|
108
|
-
}
|
|
109
|
-
if (!manifest.sidebarBadge)
|
|
110
|
-
return;
|
|
111
|
-
const endpoint = manifest.sidebarBadge.endpoint;
|
|
112
|
-
if (!apiCapabilityPathAllowed(endpoint, manifest.capabilities.api.read)) {
|
|
113
|
-
ctx.addIssue({
|
|
114
|
-
code: 'custom',
|
|
115
|
-
message: `sidebarBadge.endpoint ${endpoint} must be declared in capabilities.api.read`,
|
|
116
|
-
path: ['sidebarBadge', 'endpoint'],
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
});
|
|
2
|
+
import { artifactStudioStaticManifestSchema } from './manifest-static.js';
|
|
3
|
+
import { artifactStudioNextManifestSchema } from './manifest-next.js';
|
|
4
|
+
export * from './manifest-static.js';
|
|
5
|
+
export * from './manifest-next.js';
|
|
6
|
+
/** Versioned parsing keeps schema-v1 stripping/default behavior unchanged. */
|
|
7
|
+
export const artifactStudioManifestSchema = z.union([
|
|
8
|
+
artifactStudioStaticManifestSchema,
|
|
9
|
+
artifactStudioNextManifestSchema,
|
|
10
|
+
]);
|
|
120
11
|
export function parseArtifactStudioManifest(value) {
|
|
121
12
|
return artifactStudioManifestSchema.parse(value);
|
|
122
13
|
}
|
|
14
|
+
export function isNextArtifactManifest(manifest) {
|
|
15
|
+
return manifest.artifact.type === 'nextjs';
|
|
16
|
+
}
|