@contractspec/bundle.library 3.9.10 → 3.10.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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +54 -40
  2. package/CHANGELOG.md +35 -0
  3. package/README.md +1 -0
  4. package/dist/components/integrations/atoms/IntegrationCredentialFieldList.d.ts +6 -0
  5. package/dist/components/integrations/atoms/IntegrationCredentialFieldList.js +2 -0
  6. package/dist/components/integrations/atoms/IntegrationCredentialModeTabs.d.ts +7 -0
  7. package/dist/components/integrations/atoms/IntegrationCredentialModeTabs.js +2 -0
  8. package/dist/components/integrations/atoms/IntegrationEnvAliasPreview.d.ts +7 -0
  9. package/dist/components/integrations/atoms/IntegrationEnvAliasPreview.js +2 -0
  10. package/dist/components/integrations/blocks/IntegrationCredentialSetupBlock.d.ts +11 -0
  11. package/dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js +2 -0
  12. package/dist/components/integrations/byok-env-ui-kit.test.d.ts +1 -0
  13. package/dist/components/integrations/helpers/credentialSetupAliases.d.ts +3 -0
  14. package/dist/components/integrations/helpers/credentialSetupAliases.js +2 -0
  15. package/dist/components/integrations/helpers/credentialSetupModel.d.ts +58 -0
  16. package/dist/components/integrations/helpers/credentialSetupModel.js +2 -0
  17. package/dist/components/integrations/index.d.ts +5 -0
  18. package/dist/components/integrations/index.js +2 -2
  19. package/dist/components/integrations/organisms/IntegrationSettings.d.ts +3 -1
  20. package/dist/components/integrations/organisms/IntegrationSettings.js +2 -2
  21. package/dist/components/integrations/organisms/IntegrationSettingsSecretReference.d.ts +8 -0
  22. package/dist/components/integrations/organisms/IntegrationSettingsSecretReference.js +2 -0
  23. package/dist/index.js +302 -302
  24. package/dist/node/components/integrations/atoms/IntegrationCredentialFieldList.js +1 -0
  25. package/dist/node/components/integrations/atoms/IntegrationCredentialModeTabs.js +1 -0
  26. package/dist/node/components/integrations/atoms/IntegrationEnvAliasPreview.js +1 -0
  27. package/dist/node/components/integrations/blocks/IntegrationCredentialSetupBlock.js +1 -0
  28. package/dist/node/components/integrations/helpers/credentialSetupAliases.js +1 -0
  29. package/dist/node/components/integrations/helpers/credentialSetupModel.js +1 -0
  30. package/dist/node/components/integrations/index.js +2 -2
  31. package/dist/node/components/integrations/organisms/IntegrationSettings.js +2 -2
  32. package/dist/node/components/integrations/organisms/IntegrationSettingsSecretReference.js +1 -0
  33. package/dist/node/index.js +302 -302
  34. package/package.json +107 -23
  35. package/src/components/integrations/atoms/IntegrationCredentialFieldList.tsx +51 -0
  36. package/src/components/integrations/atoms/IntegrationCredentialModeTabs.tsx +44 -0
  37. package/src/components/integrations/atoms/IntegrationEnvAliasPreview.tsx +56 -0
  38. package/src/components/integrations/blocks/IntegrationCredentialSetupBlock.tsx +95 -0
  39. package/src/components/integrations/byok-env-ui-kit.test.tsx +194 -0
  40. package/src/components/integrations/helpers/credentialSetupAliases.ts +137 -0
  41. package/src/components/integrations/helpers/credentialSetupModel.ts +218 -0
  42. package/src/components/integrations/index.ts +5 -0
  43. package/src/components/integrations/organisms/IntegrationSettings.tsx +91 -97
  44. package/src/components/integrations/organisms/IntegrationSettingsSecretReference.tsx +84 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/bundle.library",
3
- "version": "3.9.10",
3
+ "version": "3.10.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rm -rf dist",
@@ -1254,6 +1254,42 @@
1254
1254
  "node": "./dist/node/components/integrations/index.js",
1255
1255
  "default": "./dist/components/integrations/index.js"
1256
1256
  },
1257
+ "./components/integrations/atoms/IntegrationCredentialFieldList": {
1258
+ "types": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.d.ts",
1259
+ "bun": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.js",
1260
+ "node": "./dist/node/components/integrations/atoms/IntegrationCredentialFieldList.js",
1261
+ "default": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.js"
1262
+ },
1263
+ "./components/integrations/atoms/IntegrationCredentialModeTabs": {
1264
+ "types": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.d.ts",
1265
+ "bun": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.js",
1266
+ "node": "./dist/node/components/integrations/atoms/IntegrationCredentialModeTabs.js",
1267
+ "default": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.js"
1268
+ },
1269
+ "./components/integrations/atoms/IntegrationEnvAliasPreview": {
1270
+ "types": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.d.ts",
1271
+ "bun": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.js",
1272
+ "node": "./dist/node/components/integrations/atoms/IntegrationEnvAliasPreview.js",
1273
+ "default": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.js"
1274
+ },
1275
+ "./components/integrations/blocks/IntegrationCredentialSetupBlock": {
1276
+ "types": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.d.ts",
1277
+ "bun": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js",
1278
+ "node": "./dist/node/components/integrations/blocks/IntegrationCredentialSetupBlock.js",
1279
+ "default": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js"
1280
+ },
1281
+ "./components/integrations/helpers/credentialSetupAliases": {
1282
+ "types": "./dist/components/integrations/helpers/credentialSetupAliases.d.ts",
1283
+ "bun": "./dist/components/integrations/helpers/credentialSetupAliases.js",
1284
+ "node": "./dist/node/components/integrations/helpers/credentialSetupAliases.js",
1285
+ "default": "./dist/components/integrations/helpers/credentialSetupAliases.js"
1286
+ },
1287
+ "./components/integrations/helpers/credentialSetupModel": {
1288
+ "types": "./dist/components/integrations/helpers/credentialSetupModel.d.ts",
1289
+ "bun": "./dist/components/integrations/helpers/credentialSetupModel.js",
1290
+ "node": "./dist/node/components/integrations/helpers/credentialSetupModel.js",
1291
+ "default": "./dist/components/integrations/helpers/credentialSetupModel.js"
1292
+ },
1257
1293
  "./components/integrations/molecules/IntegrationCard": {
1258
1294
  "types": "./dist/components/integrations/molecules/IntegrationCard.d.ts",
1259
1295
  "bun": "./dist/components/integrations/molecules/IntegrationCard.js",
@@ -1272,6 +1308,12 @@
1272
1308
  "node": "./dist/node/components/integrations/organisms/IntegrationSettings.js",
1273
1309
  "default": "./dist/components/integrations/organisms/IntegrationSettings.js"
1274
1310
  },
1311
+ "./components/integrations/organisms/IntegrationSettingsSecretReference": {
1312
+ "types": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.d.ts",
1313
+ "bun": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.js",
1314
+ "node": "./dist/node/components/integrations/organisms/IntegrationSettingsSecretReference.js",
1315
+ "default": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.js"
1316
+ },
1275
1317
  "./components/integrations/organisms/KnowledgeSourceList": {
1276
1318
  "types": "./dist/components/integrations/organisms/KnowledgeSourceList.d.ts",
1277
1319
  "bun": "./dist/components/integrations/organisms/KnowledgeSourceList.js",
@@ -1986,27 +2028,27 @@
1986
2028
  "react": "19.2.0"
1987
2029
  },
1988
2030
  "dependencies": {
1989
- "@contractspec/bundle.workspace": "4.6.0",
2031
+ "@contractspec/bundle.workspace": "4.7.0",
1990
2032
  "@apollo/client": "^4.1.7",
1991
2033
  "@modelcontextprotocol/sdk": "^1.29.0",
1992
- "@contractspec/example.data-grid-showcase": "3.8.22",
1993
- "@contractspec/lib.ai-providers": "3.7.20",
1994
- "@contractspec/lib.contracts-spec": "6.2.0",
1995
- "@contractspec/lib.contracts-library": "3.7.27",
1996
- "@contractspec/lib.content-gen": "3.7.27",
1997
- "@contractspec/lib.contracts-runtime-server-mcp": "3.8.7",
1998
- "@contractspec/lib.design-system": "4.4.1",
1999
- "@contractspec/lib.surface-runtime": "0.5.27",
2000
- "@contractspec/lib.provider-ranking": "0.7.20",
2001
- "@contractspec/lib.example-shared-ui": "7.0.7",
2002
- "@contractspec/lib.knowledge": "3.8.3",
2003
- "@contractspec/lib.logger": "3.7.20",
2004
- "@contractspec/lib.runtime-sandbox": "3.0.6",
2034
+ "@contractspec/example.data-grid-showcase": "3.8.23",
2035
+ "@contractspec/lib.ai-providers": "3.7.21",
2036
+ "@contractspec/lib.contracts-spec": "6.3.0",
2037
+ "@contractspec/lib.contracts-library": "3.7.28",
2038
+ "@contractspec/lib.content-gen": "3.7.28",
2039
+ "@contractspec/lib.contracts-runtime-server-mcp": "3.8.8",
2040
+ "@contractspec/lib.design-system": "4.4.2",
2041
+ "@contractspec/lib.surface-runtime": "0.5.28",
2042
+ "@contractspec/lib.provider-ranking": "0.7.21",
2043
+ "@contractspec/lib.example-shared-ui": "7.0.8",
2044
+ "@contractspec/lib.knowledge": "3.8.4",
2045
+ "@contractspec/lib.logger": "3.7.21",
2046
+ "@contractspec/lib.runtime-sandbox": "3.0.7",
2005
2047
  "@contractspec/lib.schema": "3.7.14",
2006
- "@contractspec/lib.ui-kit-web": "3.13.2",
2007
- "@contractspec/lib.ui-link": "3.7.22",
2008
- "@contractspec/module.context-storage": "0.7.26",
2009
- "@contractspec/module.examples": "4.0.7",
2048
+ "@contractspec/lib.ui-kit-web": "3.13.3",
2049
+ "@contractspec/lib.ui-link": "3.7.23",
2050
+ "@contractspec/module.context-storage": "0.7.27",
2051
+ "@contractspec/module.examples": "4.0.8",
2010
2052
  "@dnd-kit/core": "^6.1.0",
2011
2053
  "@dnd-kit/sortable": "^10.0.0",
2012
2054
  "@dnd-kit/utilities": "^3.2.2",
@@ -2022,10 +2064,10 @@
2022
2064
  "posthog-react-native": "^4.43.10",
2023
2065
  "react-hook-form": "^7.74.0",
2024
2066
  "zod": "^4.3.5",
2025
- "@contractspec/lib.contracts-integrations": "3.8.19",
2026
- "@contractspec/lib.contracts-runtime-server-rest": "3.9.0",
2027
- "@contractspec/lib.contracts-runtime-server-graphql": "3.8.6",
2028
- "@contractspec/lib.contracts-runtime-client-react": "3.14.0"
2067
+ "@contractspec/lib.contracts-integrations": "3.9.0",
2068
+ "@contractspec/lib.contracts-runtime-server-rest": "3.9.1",
2069
+ "@contractspec/lib.contracts-runtime-server-graphql": "3.8.7",
2070
+ "@contractspec/lib.contracts-runtime-client-react": "3.14.1"
2029
2071
  },
2030
2072
  "devDependencies": {
2031
2073
  "@types/react": "~19.2.14",
@@ -3279,6 +3321,42 @@
3279
3321
  "node": "./dist/node/components/integrations/index.js",
3280
3322
  "default": "./dist/components/integrations/index.js"
3281
3323
  },
3324
+ "./components/integrations/atoms/IntegrationCredentialFieldList": {
3325
+ "types": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.d.ts",
3326
+ "bun": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.js",
3327
+ "node": "./dist/node/components/integrations/atoms/IntegrationCredentialFieldList.js",
3328
+ "default": "./dist/components/integrations/atoms/IntegrationCredentialFieldList.js"
3329
+ },
3330
+ "./components/integrations/atoms/IntegrationCredentialModeTabs": {
3331
+ "types": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.d.ts",
3332
+ "bun": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.js",
3333
+ "node": "./dist/node/components/integrations/atoms/IntegrationCredentialModeTabs.js",
3334
+ "default": "./dist/components/integrations/atoms/IntegrationCredentialModeTabs.js"
3335
+ },
3336
+ "./components/integrations/atoms/IntegrationEnvAliasPreview": {
3337
+ "types": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.d.ts",
3338
+ "bun": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.js",
3339
+ "node": "./dist/node/components/integrations/atoms/IntegrationEnvAliasPreview.js",
3340
+ "default": "./dist/components/integrations/atoms/IntegrationEnvAliasPreview.js"
3341
+ },
3342
+ "./components/integrations/blocks/IntegrationCredentialSetupBlock": {
3343
+ "types": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.d.ts",
3344
+ "bun": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js",
3345
+ "node": "./dist/node/components/integrations/blocks/IntegrationCredentialSetupBlock.js",
3346
+ "default": "./dist/components/integrations/blocks/IntegrationCredentialSetupBlock.js"
3347
+ },
3348
+ "./components/integrations/helpers/credentialSetupAliases": {
3349
+ "types": "./dist/components/integrations/helpers/credentialSetupAliases.d.ts",
3350
+ "bun": "./dist/components/integrations/helpers/credentialSetupAliases.js",
3351
+ "node": "./dist/node/components/integrations/helpers/credentialSetupAliases.js",
3352
+ "default": "./dist/components/integrations/helpers/credentialSetupAliases.js"
3353
+ },
3354
+ "./components/integrations/helpers/credentialSetupModel": {
3355
+ "types": "./dist/components/integrations/helpers/credentialSetupModel.d.ts",
3356
+ "bun": "./dist/components/integrations/helpers/credentialSetupModel.js",
3357
+ "node": "./dist/node/components/integrations/helpers/credentialSetupModel.js",
3358
+ "default": "./dist/components/integrations/helpers/credentialSetupModel.js"
3359
+ },
3282
3360
  "./components/integrations/molecules/IntegrationCard": {
3283
3361
  "types": "./dist/components/integrations/molecules/IntegrationCard.d.ts",
3284
3362
  "bun": "./dist/components/integrations/molecules/IntegrationCard.js",
@@ -3297,6 +3375,12 @@
3297
3375
  "node": "./dist/node/components/integrations/organisms/IntegrationSettings.js",
3298
3376
  "default": "./dist/components/integrations/organisms/IntegrationSettings.js"
3299
3377
  },
3378
+ "./components/integrations/organisms/IntegrationSettingsSecretReference": {
3379
+ "types": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.d.ts",
3380
+ "bun": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.js",
3381
+ "node": "./dist/node/components/integrations/organisms/IntegrationSettingsSecretReference.js",
3382
+ "default": "./dist/components/integrations/organisms/IntegrationSettingsSecretReference.js"
3383
+ },
3300
3384
  "./components/integrations/organisms/KnowledgeSourceList": {
3301
3385
  "types": "./dist/components/integrations/organisms/KnowledgeSourceList.d.ts",
3302
3386
  "bun": "./dist/components/integrations/organisms/KnowledgeSourceList.js",
@@ -0,0 +1,51 @@
1
+ import {
2
+ HStack,
3
+ Muted,
4
+ Small,
5
+ Text,
6
+ VStack,
7
+ } from '@contractspec/lib.design-system';
8
+ import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
9
+ import type { CredentialSetupFieldRow } from '../helpers/credentialSetupModel';
10
+
11
+ export interface IntegrationCredentialFieldListProps {
12
+ fields: CredentialSetupFieldRow[];
13
+ emptyLabel?: string;
14
+ }
15
+
16
+ export function IntegrationCredentialFieldList({
17
+ fields,
18
+ emptyLabel = 'No credential fields declared.',
19
+ }: IntegrationCredentialFieldListProps) {
20
+ if (fields.length === 0) return <Muted>{emptyLabel}</Muted>;
21
+
22
+ return (
23
+ <VStack gap="sm" align="stretch">
24
+ {fields.map((field) => (
25
+ <VStack
26
+ key={`${field.kind}:${field.key}`}
27
+ gap="sm"
28
+ align="stretch"
29
+ className="rounded-xl border p-3 text-sm"
30
+ >
31
+ <HStack justify="between" align="start" gap="md">
32
+ <VStack gap="sm" align="start">
33
+ <Small>{field.label}</Small>
34
+ <Muted className="text-xs">{field.kind}</Muted>
35
+ </VStack>
36
+ <Badge
37
+ variant={field.status === 'missing' ? 'destructive' : 'secondary'}
38
+ >
39
+ <Text>{field.status}</Text>
40
+ </Badge>
41
+ </HStack>
42
+ {field.description ? <Muted>{field.description}</Muted> : null}
43
+ {field.secretRef ? (
44
+ <Muted className="font-mono text-xs">{field.secretRef}</Muted>
45
+ ) : null}
46
+ {field.required ? <Text className="text-xs">Required</Text> : null}
47
+ </VStack>
48
+ ))}
49
+ </VStack>
50
+ );
51
+ }
@@ -0,0 +1,44 @@
1
+ import { HStack, Text } from '@contractspec/lib.design-system';
2
+ import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
3
+ import { Button } from '@contractspec/lib.ui-kit-web/ui/button';
4
+ import type {
5
+ CredentialSetupModeOption,
6
+ IntegrationCredentialSetupModel,
7
+ } from '../helpers/credentialSetupModel';
8
+
9
+ export interface IntegrationCredentialModeTabsProps {
10
+ modes: CredentialSetupModeOption[];
11
+ selectedMode: IntegrationCredentialSetupModel['selectedMode'];
12
+ onModeChange?: (
13
+ mode: IntegrationCredentialSetupModel['selectedMode']
14
+ ) => void;
15
+ }
16
+
17
+ export function IntegrationCredentialModeTabs({
18
+ modes,
19
+ selectedMode,
20
+ onModeChange,
21
+ }: IntegrationCredentialModeTabsProps) {
22
+ return (
23
+ <HStack wrap="wrap" gap="sm" role="tablist" aria-label="Credential mode">
24
+ {modes.map((mode) => (
25
+ <Button
26
+ key={mode.mode}
27
+ type="button"
28
+ variant={mode.mode === selectedMode ? 'default' : 'outline'}
29
+ disabled={!mode.available}
30
+ onClick={() => onModeChange?.(mode.mode)}
31
+ role="tab"
32
+ aria-selected={mode.mode === selectedMode}
33
+ >
34
+ <Text>{mode.label}</Text>
35
+ {mode.missingRequiredCount > 0 ? (
36
+ <Badge variant="destructive">
37
+ <Text>{`${mode.missingRequiredCount} missing`}</Text>
38
+ </Badge>
39
+ ) : null}
40
+ </Button>
41
+ ))}
42
+ </HStack>
43
+ );
44
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ Code,
3
+ HStack,
4
+ Muted,
5
+ Text,
6
+ VStack,
7
+ } from '@contractspec/lib.design-system';
8
+ import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
9
+ import type {
10
+ CredentialSetupAliasRow,
11
+ CredentialSetupWarning,
12
+ } from '../helpers/credentialSetupModel';
13
+
14
+ export interface IntegrationEnvAliasPreviewProps {
15
+ aliases: CredentialSetupAliasRow[];
16
+ warnings?: CredentialSetupWarning[];
17
+ emptyLabel?: string;
18
+ }
19
+
20
+ export function IntegrationEnvAliasPreview({
21
+ aliases,
22
+ warnings = [],
23
+ emptyLabel = 'No env aliases declared.',
24
+ }: IntegrationEnvAliasPreviewProps) {
25
+ return (
26
+ <VStack gap="sm" align="stretch">
27
+ {aliases.length === 0 ? <Muted>{emptyLabel}</Muted> : null}
28
+ {aliases.map((alias) => (
29
+ <VStack
30
+ key={`${alias.logicalKey}:${alias.envName}:${alias.targetId ?? 'root'}`}
31
+ gap="sm"
32
+ align="stretch"
33
+ className="rounded-xl border p-3 text-sm"
34
+ >
35
+ <HStack wrap="wrap" gap="sm" align="center">
36
+ <Code>{alias.envName}</Code>
37
+ <Badge variant={alias.public ? 'default' : 'secondary'}>
38
+ <Text>{alias.public ? 'public' : 'server'}</Text>
39
+ </Badge>
40
+ </HStack>
41
+ <Muted className="text-xs">
42
+ {`${alias.logicalKey} → ${alias.targetLabel ?? alias.targetId ?? 'workspace root'}${alias.framework ? ` · ${alias.framework}` : ''}${alias.profile ? ` · ${alias.profile}` : ''}`}
43
+ </Muted>
44
+ {alias.warning ? (
45
+ <Text className="text-xs">{alias.warning}</Text>
46
+ ) : null}
47
+ </VStack>
48
+ ))}
49
+ {warnings.map((warning) => (
50
+ <Muted key={`${warning.level}:${warning.fieldKey ?? warning.message}`}>
51
+ {`${warning.level.toUpperCase()}: ${warning.message}`}
52
+ </Muted>
53
+ ))}
54
+ </VStack>
55
+ );
56
+ }
@@ -0,0 +1,95 @@
1
+ import { Small, Text, VStack } from '@contractspec/lib.design-system';
2
+ import { Button } from '@contractspec/lib.ui-kit-web/ui/button';
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardFooter,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@contractspec/lib.ui-kit-web/ui/card';
11
+ import { IntegrationCredentialFieldList } from '../atoms/IntegrationCredentialFieldList';
12
+ import { IntegrationCredentialModeTabs } from '../atoms/IntegrationCredentialModeTabs';
13
+ import { IntegrationEnvAliasPreview } from '../atoms/IntegrationEnvAliasPreview';
14
+ import {
15
+ type BuildIntegrationCredentialSetupModelInput,
16
+ buildIntegrationCredentialSetupModel,
17
+ type IntegrationCredentialSetupModel,
18
+ } from '../helpers/credentialSetupModel';
19
+
20
+ export interface IntegrationCredentialSetupBlockProps
21
+ extends BuildIntegrationCredentialSetupModelInput {
22
+ title?: string;
23
+ description?: string;
24
+ onModeChange?: (
25
+ mode: IntegrationCredentialSetupModel['selectedMode']
26
+ ) => void;
27
+ onTestConnection?: () => void | Promise<void>;
28
+ onSave?: () => void | Promise<void>;
29
+ isTesting?: boolean;
30
+ isSaving?: boolean;
31
+ }
32
+
33
+ export function IntegrationCredentialSetupBlock({
34
+ title = 'Credential setup',
35
+ description = 'Configure managed credentials or BYOK secret references without exposing raw secrets.',
36
+ onModeChange,
37
+ onTestConnection,
38
+ onSave,
39
+ isTesting,
40
+ isSaving,
41
+ ...input
42
+ }: IntegrationCredentialSetupBlockProps) {
43
+ const model = buildIntegrationCredentialSetupModel(input);
44
+ return (
45
+ <Card>
46
+ <CardHeader>
47
+ <CardTitle>{title}</CardTitle>
48
+ <CardDescription>{description}</CardDescription>
49
+ </CardHeader>
50
+ <CardContent>
51
+ <VStack gap="lg" align="stretch">
52
+ <IntegrationCredentialModeTabs
53
+ modes={model.modes}
54
+ selectedMode={model.selectedMode}
55
+ onModeChange={onModeChange}
56
+ />
57
+ <VStack gap="sm" align="stretch">
58
+ <Small>Required fields</Small>
59
+ <IntegrationCredentialFieldList fields={model.fields} />
60
+ </VStack>
61
+ <VStack gap="sm" align="stretch">
62
+ <Small>Environment aliases</Small>
63
+ <IntegrationEnvAliasPreview
64
+ aliases={model.aliases}
65
+ warnings={model.warnings}
66
+ />
67
+ </VStack>
68
+ </VStack>
69
+ </CardContent>
70
+ {onTestConnection || onSave ? (
71
+ <CardFooter className="flex flex-wrap justify-end gap-2">
72
+ {onTestConnection ? (
73
+ <Button
74
+ type="button"
75
+ variant="outline"
76
+ disabled={isTesting}
77
+ onClick={() => void onTestConnection()}
78
+ >
79
+ <Text>{isTesting ? 'Testing...' : 'Test connection'}</Text>
80
+ </Button>
81
+ ) : null}
82
+ {onSave ? (
83
+ <Button
84
+ type="button"
85
+ disabled={isSaving}
86
+ onClick={() => void onSave()}
87
+ >
88
+ <Text>{isSaving ? 'Saving...' : 'Save settings'}</Text>
89
+ </Button>
90
+ ) : null}
91
+ </CardFooter>
92
+ ) : null}
93
+ </Card>
94
+ );
95
+ }
@@ -0,0 +1,194 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import type { IntegrationSpec } from '@contractspec/lib.contracts-integrations';
3
+ import { renderToStaticMarkup } from 'react-dom/server';
4
+ import {
5
+ buildIntegrationCredentialSetupModel,
6
+ IntegrationCredentialSetupBlock,
7
+ } from './index';
8
+
9
+ const baseIntegration: IntegrationSpec = {
10
+ meta: {
11
+ key: 'integration.postmark',
12
+ version: '1.0.0',
13
+ title: 'Postmark',
14
+ description: 'Postmark email provider.',
15
+ stability: 'experimental',
16
+ owners: ['platform.marketplace'],
17
+ tags: ['marketplace'],
18
+ category: 'email',
19
+ },
20
+ supportedModes: ['managed', 'byok'],
21
+ capabilities: { provides: [] },
22
+ configSchema: {
23
+ schema: {
24
+ type: 'object',
25
+ required: ['region'],
26
+ properties: {
27
+ region: { type: 'string', description: 'Provider region.' },
28
+ },
29
+ },
30
+ },
31
+ secretSchema: {
32
+ schema: {
33
+ type: 'object',
34
+ required: ['apiToken'],
35
+ properties: {
36
+ apiToken: { type: 'string', description: 'API token.' },
37
+ },
38
+ },
39
+ },
40
+ byokSetup: {
41
+ keyRotationSupported: true,
42
+ provisioningSteps: ['Create an API token.'],
43
+ },
44
+ };
45
+
46
+ describe('BYOK env UI config kit', () => {
47
+ it('builds rows from legacy integration schemas', () => {
48
+ const model = buildIntegrationCredentialSetupModel({
49
+ integration: baseIntegration,
50
+ selectedMode: 'byok',
51
+ });
52
+
53
+ expect(model.fields.map((field) => field.key)).toEqual([
54
+ 'region',
55
+ 'apiToken',
56
+ ]);
57
+ expect(model.fields.find((field) => field.key === 'apiToken')?.status).toBe(
58
+ 'missing'
59
+ );
60
+ expect(
61
+ model.warnings.some((warning) => warning.message.includes('required'))
62
+ ).toBe(true);
63
+ });
64
+
65
+ it('prefers explicit credential manifests over legacy fallback', () => {
66
+ const model = buildIntegrationCredentialSetupModel({
67
+ integration: {
68
+ ...baseIntegration,
69
+ credentialManifest: {
70
+ modes: {
71
+ managed: {
72
+ mode: 'managed',
73
+ configFields: [{ key: 'workspaceId', required: true }],
74
+ },
75
+ },
76
+ },
77
+ },
78
+ selectedMode: 'managed',
79
+ });
80
+
81
+ expect(model.fields.map((field) => field.key)).toEqual(['workspaceId']);
82
+ });
83
+
84
+ it('omits raw secret-looking values from the setup model', () => {
85
+ const model = buildIntegrationCredentialSetupModel({
86
+ credentialManifest: {
87
+ modes: {
88
+ byok: {
89
+ mode: 'byok',
90
+ secretFields: [{ key: 'apiToken', required: true }],
91
+ },
92
+ },
93
+ },
94
+ selectedMode: 'byok',
95
+ secretRefs: { apiToken: 'sk_live_should_not_render' },
96
+ });
97
+
98
+ expect(JSON.stringify(model)).not.toContain('sk_live_should_not_render');
99
+ expect(
100
+ model.warnings.some((warning) => warning.message.includes('hidden'))
101
+ ).toBe(true);
102
+ });
103
+
104
+ it('renders managed mode actions and requirements', () => {
105
+ const html = renderToStaticMarkup(
106
+ <IntegrationCredentialSetupBlock
107
+ credentialManifest={{
108
+ modes: {
109
+ managed: {
110
+ mode: 'managed',
111
+ configFields: [{ key: 'workspaceId', required: true }],
112
+ },
113
+ },
114
+ }}
115
+ supportedModes={['managed']}
116
+ selectedMode="managed"
117
+ onTestConnection={() => undefined}
118
+ onSave={() => undefined}
119
+ />
120
+ );
121
+
122
+ expect(html).toContain('Managed');
123
+ expect(html).toContain('WorkspaceId');
124
+ expect(html).toContain('Test connection');
125
+ expect(html).toContain('Save settings');
126
+ });
127
+
128
+ it('renders BYOK references, app aliases, and no raw secrets', () => {
129
+ const html = renderToStaticMarkup(
130
+ <IntegrationCredentialSetupBlock
131
+ credentialManifest={{
132
+ modes: {
133
+ byok: {
134
+ mode: 'byok',
135
+ secretFields: [{ key: 'apiToken', required: true }],
136
+ },
137
+ },
138
+ }}
139
+ environment={{
140
+ targets: {
141
+ web: { id: 'web', framework: 'next', packageName: '@acme/web' },
142
+ mobile: {
143
+ id: 'mobile',
144
+ framework: 'expo',
145
+ packageName: '@acme/mobile',
146
+ },
147
+ },
148
+ variables: {
149
+ PUBLIC_API_URL: {
150
+ key: 'PUBLIC_API_URL',
151
+ sensitivity: 'public',
152
+ aliases: [
153
+ {
154
+ targetId: 'web',
155
+ framework: 'next',
156
+ name: 'NEXT_PUBLIC_API_URL',
157
+ },
158
+ {
159
+ targetId: 'mobile',
160
+ framework: 'expo',
161
+ name: 'EXPO_PUBLIC_API_URL',
162
+ },
163
+ ],
164
+ },
165
+ apiToken: {
166
+ key: 'apiToken',
167
+ sensitivity: 'secret',
168
+ aliases: [
169
+ {
170
+ targetId: 'web',
171
+ framework: 'next',
172
+ name: 'NEXT_PUBLIC_TOKEN',
173
+ },
174
+ ],
175
+ },
176
+ },
177
+ }}
178
+ secretRefs={{ apiToken: 'env://POSTMARK_TOKEN' }}
179
+ supportedModes={['byok']}
180
+ selectedMode="byok"
181
+ targetIds={['web', 'mobile']}
182
+ />
183
+ );
184
+
185
+ expect(html).toContain('BYOK');
186
+ expect(html).toContain('env://POSTMARK_TOKEN');
187
+ expect(html).toContain('NEXT_PUBLIC_API_URL');
188
+ expect(html).toContain('EXPO_PUBLIC_API_URL');
189
+ expect(html).toContain('@acme/web');
190
+ expect(html).toContain('@acme/mobile');
191
+ expect(html).toContain('Public client alias');
192
+ expect(html).not.toContain('NEXT_PUBLIC_TOKEN');
193
+ });
194
+ });