@adminide-stack/form-builder-core 5.1.4-alpha.331 → 5.1.4-alpha.332

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.
@@ -0,0 +1,1559 @@
1
+ import {CONTRIBUTION_POINTS,ContributeDefaultValueSourceType}from'common';import {resolveConnectorFieldTokensInValue}from'../config/connectorFieldTokenResolver.js';import {parseSampleNodes,flattenSchemas}from'./schemaHelpers.js';const parseScopes = raw => raw.split(',').map(s => s.trim()).filter(Boolean);
2
+ const parseEnumValues = raw => {
3
+ if (!raw || !raw.trim()) return [];
4
+ try {
5
+ const parsed = JSON.parse(raw);
6
+ return Array.isArray(parsed) ? parsed : [];
7
+ } catch (_) {
8
+ return raw.split('\n').map(line => line.trim()).filter(Boolean).map(line => {
9
+ const [value, label] = line.split(':');
10
+ return {
11
+ value: (value || '').trim(),
12
+ label: (label || value || '').trim()
13
+ };
14
+ });
15
+ }
16
+ };
17
+ const mapProviderFields = fields => fields.map(field => ({
18
+ name: field.name,
19
+ label: field.label,
20
+ type: field.type,
21
+ placeholder: field.placeholder,
22
+ description: field.description,
23
+ required: field.required,
24
+ defaultValue: field.defaultValue ?? '',
25
+ isConfigField: field.isConfigField,
26
+ isSecret: field.isSecret,
27
+ secretSource: field.isSecret ? {
28
+ type: 'vault',
29
+ secretType: field.secretType,
30
+ secretKey: field.secretKey
31
+ } : null,
32
+ uiWidget: field.uiWidget,
33
+ enumValues: parseEnumValues(field.enumValues),
34
+ readOnly: field.readOnly,
35
+ iconType: field.iconType,
36
+ genericType: field.genericType || undefined,
37
+ placement: field.placement || undefined,
38
+ placementKey: field.placementKey || undefined,
39
+ scope: field.scope || undefined
40
+ }));
41
+ const buildProviderConfig = (providerConfig, connectorType) => {
42
+ const base = {
43
+ id: providerConfig.id,
44
+ title: providerConfig.title,
45
+ description: providerConfig.description,
46
+ icon: {
47
+ name: providerConfig.iconName,
48
+ style: providerConfig.iconFontSize ? {
49
+ fontSize: providerConfig.iconFontSize
50
+ } : undefined
51
+ },
52
+ logo: providerConfig.logo || undefined,
53
+ color: providerConfig.color || undefined,
54
+ enabled: providerConfig.enabled,
55
+ scopes: parseScopes(providerConfig.scopes),
56
+ authMethods: providerConfig.authMethods.length > 0 ? providerConfig.authMethods : ['api_key'],
57
+ categories: [providerConfig.category],
58
+ fields: mapProviderFields(providerConfig.fields)
59
+ };
60
+ if (connectorType === 'mcp') {
61
+ return {
62
+ ...base,
63
+ providerUrl: providerConfig.providerUrl || undefined,
64
+ mcpUrl: providerConfig.mcpUrl
65
+ };
66
+ }
67
+ return {
68
+ ...base,
69
+ providerUrl: providerConfig.providerUrl,
70
+ useCustomOAuthEndpoints: providerConfig.useCustomOAuthEndpoints,
71
+ usePKCE: providerConfig.usePKCE,
72
+ clientAuthMethod: providerConfig.clientAuthMethod,
73
+ authorizationUrl: providerConfig.authorizationUrl,
74
+ tokenUrl: providerConfig.tokenUrl,
75
+ revocationUrl: providerConfig.revocationUrl,
76
+ useBasicAuth: providerConfig.useBasicAuth
77
+ };
78
+ };
79
+ const buildAppSeed = p => {
80
+ const authMethods = p.authMethods ? [...p.authMethods] : ['oauth2'];
81
+ return {
82
+ key: `${p.id}:app`,
83
+ providerId: p.id,
84
+ title: p.title,
85
+ description: p.description || '',
86
+ scopes: p.scopes || [],
87
+ providerUrl: p.providerUrl || '',
88
+ useCustomOAuthEndpoints: p.useCustomOAuthEndpoints,
89
+ usePKCE: p.usePKCE,
90
+ clientAuthMethod: p.clientAuthMethod,
91
+ authorizationUrl: p.authorizationUrl,
92
+ tokenUrl: p.tokenUrl,
93
+ revocationUrl: p.revocationUrl,
94
+ useBasicAuth: p.useBasicAuth,
95
+ mcpUrl: p.mcpUrl || '',
96
+ connectorType: 'app',
97
+ authMethods,
98
+ fields: p.fields || [],
99
+ resources: p.resources || [],
100
+ customButtons: p.customButtons || [],
101
+ formIcon: p.logo || '',
102
+ categories: p.categories
103
+ };
104
+ };
105
+ const buildMcpSeed = p => ({
106
+ key: `${p.id}:mcp`,
107
+ providerId: p.id,
108
+ title: p.title,
109
+ description: p.description || '',
110
+ scopes: p.scopes || [],
111
+ providerUrl: p.mcpUrl || '',
112
+ mcpUrl: p.mcpUrl || '',
113
+ connectorType: 'mcp',
114
+ authMethods: [...p.authMethods],
115
+ fields: p.fields || [],
116
+ resources: [],
117
+ customButtons: [],
118
+ formIcon: p.logo || '',
119
+ categories: p.categories
120
+ });
121
+ function buildProviderSeedFromConfig(provider, connectorType) {
122
+ if (connectorType === 'mcp') {
123
+ return buildMcpSeed(provider);
124
+ }
125
+ return buildAppSeed(provider);
126
+ }
127
+ // Auth seed generation
128
+ const sanitizeFieldKey = value => value.replace(/[^a-zA-Z0-9_]/g, '_');
129
+ const seedIsApiKeyOnly = seed => seed.connectorType !== 'mcp' && !seed.authMethods.includes('oauth2') && !seed.authMethods.includes('bearer_token');
130
+ const STEP_KEY = 'step1';
131
+ const seedTemplateType = seed => seed.connectorType === 'mcp' ? 'mcp' : seed.authMethods.includes('oauth2') ? 'oauth' : 'api';
132
+ function buildResourcesNode(seed) {
133
+ if (seed.resources.length === 0) return null;
134
+ const properties = {};
135
+ for (const originalResource of seed.resources) {
136
+ const resource = resolveConnectorFieldTokensInValue(originalResource, {
137
+ fields: seed.fields,
138
+ basePath: `${STEP_KEY}.oauthConnector`,
139
+ context: `${seed.key}.${originalResource.name}`
140
+ });
141
+ const prefix = resource.name;
142
+ const selectedSource = resource.type === 'jsCode' ? 'js-code' : resource.type;
143
+ properties[`${prefix}.id`] = resource.name;
144
+ properties[`${prefix}.name`] = resource.label;
145
+ properties[`${prefix}.type`] = resource.type === 'rest-api' ? 'apiRequest' : resource.type;
146
+ properties[`${prefix}.selectedSource`] = selectedSource;
147
+ properties[`${prefix}.description`] = '';
148
+ properties[`${prefix}.key`] = resource.name;
149
+ if (resource.type === 'rest-api' && resource.config.restApi) {
150
+ const r = resource.config.restApi;
151
+ const rp = `${prefix}.config.restApi`;
152
+ properties[`${rp}.httpMethod`] = r.httpMethod;
153
+ properties[`${rp}.url`] = r.url;
154
+ properties[`${rp}.urlParams`] = r.urlParams;
155
+ r.headers.forEach((h, i) => {
156
+ properties[`${rp}.headers.${i}.key`] = h.key;
157
+ properties[`${rp}.headers.${i}.value`] = h.value;
158
+ });
159
+ properties[`${rp}.body.format`] = r.body.format;
160
+ properties[`${rp}.body.data`] = r.body.data;
161
+ properties[`${rp}.body.rawData`] = r.body.rawData;
162
+ properties[`${rp}.cookies`] = r.cookies;
163
+ properties[`${rp}.runBehavior`] = r.runBehavior;
164
+ properties[`${rp}.transformResults`] = r.transformResults;
165
+ }
166
+ if (resource.type === 'jsCode' && resource.config.jsCode) {
167
+ const j = resource.config.jsCode;
168
+ const jp = `${prefix}.config.jsCode`;
169
+ properties[`${jp}.query`] = j.query;
170
+ properties[`${jp}.transformResults`] = j.transformResults;
171
+ }
172
+ if (resource.type === 'graphql' && resource.config.graphql) {
173
+ const g = resource.config.graphql;
174
+ const gp = `${prefix}.config.graphql`;
175
+ properties[`${gp}.query`] = g.query;
176
+ properties[`${gp}.useInternalLink`] = g.useInternalLink;
177
+ properties[`${gp}.transformResults`] = g.transformResults;
178
+ properties[`${gp}.variables`] = g.variables;
179
+ }
180
+ }
181
+ return {
182
+ type: 'resources',
183
+ properties
184
+ };
185
+ }
186
+ function buildCustomButtonsNode(seed) {
187
+ if (seed.customButtons.length === 0) return null;
188
+ const node = {
189
+ type: 'customButtons'
190
+ };
191
+ seed.customButtons.forEach((btn, idx) => {
192
+ const base = `properties.${STEP_KEY}.${idx}`;
193
+ node[`${base}.id`] = btn.id || `btn-${Math.random().toString(36).substring(2, 15)}`;
194
+ node[`${base}.title`] = btn.title;
195
+ node[`${base}.action`] = btn.action;
196
+ if (btn.resourceId) node[`${base}.resourceId`] = btn.resourceId;
197
+ if (btn.inngestId) node[`${base}.inngestId`] = btn.inngestId;
198
+ });
199
+ return node;
200
+ }
201
+ function buildInngestNode() {
202
+ return {
203
+ type: 'inngest',
204
+ 'properties.generatedCode.submit': `async ({ event, step }) => {\nreturn {\n functionId: 'new-function',\n timestamp: new Date().toISOString()\n};\n}`,
205
+ 'properties.raw.submit.functionID': 'new-function',
206
+ 'properties.raw.submit.functionName': 'New Workflow Function',
207
+ 'properties.raw.submit.functionDescription': 'A new Workflow function',
208
+ 'properties.raw.submit.enabled': false,
209
+ 'properties.raw.submit.displaySubmitButton': false,
210
+ 'properties.raw.submit.events.0': 'app.event',
211
+ 'properties.raw.submit.metadata.version': '1.0.0',
212
+ 'properties.raw.submit.selectedStep': null,
213
+ 'properties.raw.submit.activeTab': 'config',
214
+ 'properties.raw.submit.editorSettings.theme': 'vs-dark',
215
+ 'properties.raw.submit.editorSettings.fontSize': 14,
216
+ 'properties.raw.submit.editorSettings.tabSize': 2,
217
+ 'properties.raw.submit.editorSettings.wordWrap': true,
218
+ 'properties.raw.submit.generatedCode': `async ({ event, step }) => {\nreturn {\n functionId: 'new-function',\n timestamp: new Date().toISOString()\n};\n}`,
219
+ 'properties.raw.submit.isCodeManuallyEdited': false,
220
+ 'properties.raw.submit.stepEditorStates.activeTab': 'config',
221
+ 'properties.raw.submit.version': '1.0.0'
222
+ };
223
+ }
224
+ const seedCategory = seed => seed.categories[0];
225
+ const toVaultPrefix = value => value.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
226
+ const seedAuthMethodDefault = seed => seed.authMethods.includes('oauth2') ? 'oauth' : seed.connectorType === 'mcp' ? 'mcp' : 'api_key';
227
+ function buildPreGeneratedFormSteps(seed, namespace) {
228
+ const vaultPrefix = toVaultPrefix(seed.providerId);
229
+ const isApp = seed.connectorType === 'app';
230
+ const isApiKeyOnly = seedIsApiKeyOnly(seed);
231
+ const templateType = seedTemplateType(seed);
232
+ const fullPath = `integrationSettings.settings.${templateType}.${namespace}.oauthConnector`;
233
+ if (isApiKeyOnly) {
234
+ // api_key-only: oauthConnector element with minimal children (so widget renders),
235
+ // plus custom field elements with full secret/config metadata.
236
+ const minimalChildren = [{
237
+ id: 'provider',
238
+ type: 'string',
239
+ label: 'Provider',
240
+ default: seed.providerId,
241
+ scope: 2 /* ConfigurationScope.MACHINE */,
242
+ readOnly: true
243
+ }, {
244
+ id: 'providerTitle',
245
+ type: 'string',
246
+ label: 'Provider Title',
247
+ default: seed.title,
248
+ scope: 2 /* ConfigurationScope.MACHINE */,
249
+ readOnly: true
250
+ }, {
251
+ id: 'providerUrl',
252
+ type: 'string',
253
+ label: 'Provider URL',
254
+ default: seed.providerUrl,
255
+ scope: 2 /* ConfigurationScope.MACHINE */,
256
+ readOnly: true
257
+ }, {
258
+ id: 'description',
259
+ type: 'string',
260
+ label: 'Description',
261
+ scope: 2 /* ConfigurationScope.MACHINE */,
262
+ default: seed.description,
263
+ readOnly: true
264
+ }, {
265
+ id: 'connectorType',
266
+ type: 'string',
267
+ label: 'Connector Type',
268
+ default: seed.connectorType,
269
+ scope: 2 /* ConfigurationScope.MACHINE */,
270
+ readOnly: true
271
+ }, {
272
+ id: 'authMethod',
273
+ type: 'string',
274
+ label: 'Auth Method',
275
+ default: 'api_key',
276
+ scope: 2 /* ConfigurationScope.MACHINE */,
277
+ readOnly: true
278
+ }];
279
+ const minimalUiOverrides = {
280
+ provider: {
281
+ 'ui:readonly': true
282
+ },
283
+ providerTitle: {
284
+ 'ui:readonly': true
285
+ },
286
+ providerUrl: {
287
+ 'ui:readonly': true
288
+ },
289
+ description: {
290
+ 'ui:readonly': true
291
+ },
292
+ connectorType: {
293
+ 'ui:widget': 'hidden'
294
+ },
295
+ authMethod: {}
296
+ };
297
+ const elements = [{
298
+ id: 'oauthConnector',
299
+ type: 'object',
300
+ label: 'OAuth Connector',
301
+ required: false,
302
+ description: seed.description || 'Connect to external providers',
303
+ connectorType: seed.connectorType,
304
+ widget: 'OAuthConnectorWidget',
305
+ uiField: 'OAuthConnectorField',
306
+ children: minimalChildren,
307
+ _widgetUiOverrides: minimalUiOverrides,
308
+ connectorProvider: seed.providerId,
309
+ connectorProviderTitle: seed.title,
310
+ connectorDescription: seed.description,
311
+ connectorCategory: seedCategory(seed),
312
+ ...(seed.connectorType === 'mcp' ? {
313
+ connectorMcpUrl: seed.providerUrl
314
+ } : {
315
+ connectorProviderUrl: seed.providerUrl
316
+ }),
317
+ connectorScopes: seed.scopes,
318
+ useCustomOAuthEndpoints: seed.useCustomOAuthEndpoints ?? false,
319
+ connectorAuthorizationUrl: seed.authorizationUrl || '',
320
+ connectorTokenUrl: seed.tokenUrl || '',
321
+ connectorRevocationUrl: seed.revocationUrl || '',
322
+ useBasicAuth: seed.useBasicAuth ?? false,
323
+ connectorRedirectUri: '{{CLIENT_URL}}/{{CONNECTOR_ENDPOINT}}',
324
+ // connectorAuthMethod — present only for non-oauth connectors (matches reference forms)
325
+ ...(!seed.authMethods.includes('oauth2') ? {
326
+ connectorAuthMethod: seedAuthMethodDefault(seed)
327
+ } : {}),
328
+ ...(seed.fields.length > 0 ? {
329
+ connectorProviderFields: seed.fields
330
+ } : {}),
331
+ ...(seed.resources.length > 0 ? {
332
+ connectorProviderResources: seed.resources
333
+ } : {}),
334
+ ...(seed.customButtons.length > 0 ? {
335
+ connectorProviderCustomButtons: seed.customButtons
336
+ } : {}),
337
+ addedFromStepperConfigToggle: true,
338
+ isSchemaElement: true,
339
+ sourceSchemaId: 'configuration',
340
+ fullPath,
341
+ contributionPoint: 'configuration.integrationSettings',
342
+ templateType,
343
+ namespace
344
+ }];
345
+ return [{
346
+ id: STEP_KEY,
347
+ title: 'Step 1',
348
+ elements
349
+ }];
350
+ }
351
+ // Full OAuth children
352
+ const children = [{
353
+ id: 'clientId',
354
+ type: 'string',
355
+ label: 'Client ID',
356
+ description: 'OAuth application client identifier',
357
+ format: 'secret',
358
+ readOnly: true,
359
+ default: '',
360
+ scope: isApp ? 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */ : 1 /* ConfigurationScope.APPLICATION */,
361
+ schemaExtensions: {
362
+ defaultValueSource: {
363
+ type: ContributeDefaultValueSourceType.Vault,
364
+ secretType: 'OAUTH_CLIENT',
365
+ secretKey: `${vaultPrefix}_CLIENT_ID`,
366
+ ...(isApp ? {
367
+ requiredSignature: true
368
+ } : {})
369
+ }
370
+ }
371
+ }, {
372
+ id: 'clientSecret',
373
+ type: 'string',
374
+ label: 'Client Secret',
375
+ description: 'OAuth application client secret',
376
+ format: 'secret',
377
+ readOnly: true,
378
+ default: '',
379
+ scope: isApp ? 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */ : 1 /* ConfigurationScope.APPLICATION */,
380
+ schemaExtensions: {
381
+ defaultValueSource: {
382
+ type: ContributeDefaultValueSourceType.Vault,
383
+ secretType: 'OAUTH_CLIENT',
384
+ secretKey: `${vaultPrefix}_CLIENT_SECRET`,
385
+ ...(isApp ? {
386
+ requiredSignature: true
387
+ } : {})
388
+ }
389
+ }
390
+ }, {
391
+ id: 'apiKey',
392
+ type: 'string',
393
+ label: 'API Key',
394
+ description: 'API Key for MCP integration',
395
+ format: 'secret',
396
+ writeOnly: true,
397
+ default: '',
398
+ scope: 4 /* ConfigurationScope.RESOURCE */,
399
+ schemaExtensions: {
400
+ defaultValueSource: {
401
+ type: ContributeDefaultValueSourceType.Vault,
402
+ secretType: 'API_KEY',
403
+ secretKey: `${vaultPrefix}_API_KEY`
404
+ }
405
+ }
406
+ }, {
407
+ id: 'accessToken',
408
+ type: 'string',
409
+ label: 'Access Token',
410
+ format: 'secret',
411
+ writeOnly: true,
412
+ default: '',
413
+ readOnly: true,
414
+ scope: 1 /* ConfigurationScope.APPLICATION */,
415
+ schemaExtensions: {
416
+ defaultValueSource: {
417
+ type: ContributeDefaultValueSourceType.Vault,
418
+ secretType: 'OAUTH_CLIENT',
419
+ secretKey: `${vaultPrefix}_ACCESS_TOKEN`
420
+ }
421
+ }
422
+ }, {
423
+ id: 'refreshToken',
424
+ type: 'string',
425
+ label: 'Refresh Token',
426
+ format: 'secret',
427
+ writeOnly: true,
428
+ default: '',
429
+ readOnly: true,
430
+ scope: 1 /* ConfigurationScope.APPLICATION */,
431
+ schemaExtensions: {
432
+ defaultValueSource: {
433
+ type: ContributeDefaultValueSourceType.Vault,
434
+ secretType: 'OAUTH_CLIENT',
435
+ secretKey: `${vaultPrefix}_REFRESH_TOKEN`
436
+ }
437
+ }
438
+ }, {
439
+ id: 'expiresAt',
440
+ type: 'number',
441
+ label: 'Expires At',
442
+ description: 'Access token expiry as Unix timestamp in milliseconds; 0 if unset',
443
+ default: 0,
444
+ readOnly: true,
445
+ scope: 1 /* ConfigurationScope.APPLICATION */
446
+ }, {
447
+ id: 'urlTemplateKeyValues',
448
+ type: 'string',
449
+ label: 'URL Template Key Values',
450
+ description: 'Stringified JSON of resolved values for URL template placeholders (e.g. {"zoho-region":"eu"})',
451
+ default: '{}',
452
+ readOnly: true,
453
+ scope: 1 /* ConfigurationScope.APPLICATION */
454
+ }, {
455
+ id: 'authRequestMeta',
456
+ type: 'string',
457
+ label: 'Auth Request Metadata',
458
+ description: 'Serialized OAuth authorization-request metadata (e.g. PKCE verifier, state) for session continuity',
459
+ default: '',
460
+ readOnly: true,
461
+ scope: 1 /* ConfigurationScope.APPLICATION */
462
+ }, {
463
+ id: 'provider',
464
+ type: 'string',
465
+ label: 'Provider',
466
+ default: seed.providerId,
467
+ scope: 2 /* ConfigurationScope.MACHINE */,
468
+ readOnly: true
469
+ }, {
470
+ id: 'providerTitle',
471
+ type: 'string',
472
+ label: 'Provider Title',
473
+ default: seed.title,
474
+ scope: 2 /* ConfigurationScope.MACHINE */,
475
+ readOnly: true
476
+ }, {
477
+ id: 'providerUrl',
478
+ type: 'string',
479
+ label: 'Provider URL',
480
+ default: seed.providerUrl,
481
+ scope: 2 /* ConfigurationScope.MACHINE */,
482
+ readOnly: true
483
+ }, {
484
+ id: 'useCustomOAuthEndpoints',
485
+ type: 'boolean',
486
+ label: 'Use Custom OAuth Endpoints',
487
+ default: seed.useCustomOAuthEndpoints ?? false,
488
+ scope: 2 /* ConfigurationScope.MACHINE */,
489
+ readOnly: true
490
+ }, {
491
+ id: 'authorizationUrl',
492
+ type: 'string',
493
+ label: 'Authorization URL',
494
+ default: seed.authorizationUrl || '',
495
+ scope: 2 /* ConfigurationScope.MACHINE */,
496
+ readOnly: true
497
+ }, {
498
+ id: 'tokenUrl',
499
+ type: 'string',
500
+ label: 'Token URL',
501
+ default: seed.tokenUrl || '',
502
+ scope: 2 /* ConfigurationScope.MACHINE */,
503
+ readOnly: true
504
+ }, {
505
+ id: 'revocationUrl',
506
+ type: 'string',
507
+ label: 'Revocation URL',
508
+ default: seed.revocationUrl || '',
509
+ scope: 2 /* ConfigurationScope.MACHINE */,
510
+ readOnly: true
511
+ }, {
512
+ id: 'useBasicAuth',
513
+ type: 'boolean',
514
+ label: 'Use Basic Auth',
515
+ default: seed.useBasicAuth ?? false,
516
+ scope: 2 /* ConfigurationScope.MACHINE */,
517
+ readOnly: true
518
+ }, {
519
+ id: 'redirectUri',
520
+ type: 'string',
521
+ label: 'Redirect URI',
522
+ scope: 2 /* ConfigurationScope.MACHINE */,
523
+ default: '{{CLIENT_URL}}/{{CONNECTOR_ENDPOINT}}',
524
+ readOnly: true,
525
+ defaultValueSource: {
526
+ type: ContributeDefaultValueSourceType.Environment
527
+ }
528
+ }, {
529
+ id: 'scopes',
530
+ type: 'array',
531
+ label: 'Scopes',
532
+ scope: 2 /* ConfigurationScope.MACHINE */,
533
+ default: seed.scopes,
534
+ readOnly: true
535
+ }, {
536
+ id: 'description',
537
+ type: 'string',
538
+ label: 'Description',
539
+ scope: 2 /* ConfigurationScope.MACHINE */,
540
+ default: seed.description,
541
+ readOnly: true
542
+ }, {
543
+ id: 'connectorType',
544
+ type: 'string',
545
+ label: 'Connector Type',
546
+ default: seed.connectorType,
547
+ scope: 2 /* ConfigurationScope.MACHINE */,
548
+ readOnly: true
549
+ }, {
550
+ id: 'mcpUrl',
551
+ type: 'string',
552
+ label: 'MCP URL',
553
+ default: seed.mcpUrl || '',
554
+ scope: 2 /* ConfigurationScope.MACHINE */,
555
+ readOnly: true
556
+ }, {
557
+ id: 'authMethod',
558
+ type: 'string',
559
+ label: 'Auth Method',
560
+ default: seedAuthMethodDefault(seed),
561
+ scope: 2 /* ConfigurationScope.MACHINE */,
562
+ readOnly: true
563
+ }, {
564
+ id: 'mcpScopes',
565
+ type: 'array',
566
+ label: 'MCP Scopes',
567
+ default: seed.connectorType === 'mcp' ? seed.scopes : [],
568
+ scope: 2 /* ConfigurationScope.MACHINE */,
569
+ readOnly: true
570
+ }, {
571
+ id: 'hideCredentials',
572
+ type: 'boolean',
573
+ label: 'Hide Credentials',
574
+ default: false,
575
+ scope: 2 /* ConfigurationScope.MACHINE */
576
+ }, {
577
+ id: 'isConnected',
578
+ type: 'boolean',
579
+ label: 'Connected',
580
+ default: false,
581
+ scope: 2 /* ConfigurationScope.MACHINE */,
582
+ readOnly: true
583
+ }];
584
+ const widgetUiOverrides = {
585
+ clientId: {
586
+ 'ui:widget': 'hidden',
587
+ 'ui:placeholder': 'Enter OAuth Client ID'
588
+ },
589
+ clientSecret: {
590
+ 'ui:widget': 'hidden',
591
+ 'ui:placeholder': 'Enter OAuth Client Secret'
592
+ },
593
+ apiKey: {
594
+ 'ui:widget': seed.connectorType === 'mcp' || !seed.authMethods.includes('oauth2') ? 'password' : 'hidden',
595
+ 'ui:placeholder': 'Enter API Key'
596
+ },
597
+ accessToken: {
598
+ 'ui:widget': 'hidden'
599
+ },
600
+ refreshToken: {
601
+ 'ui:widget': 'hidden'
602
+ },
603
+ expiresAt: {
604
+ 'ui:widget': 'hidden'
605
+ },
606
+ urlTemplateKeyValues: {
607
+ 'ui:widget': 'hidden'
608
+ },
609
+ authRequestMeta: {
610
+ 'ui:widget': 'hidden'
611
+ },
612
+ provider: {
613
+ 'ui:readonly': true
614
+ },
615
+ providerTitle: {
616
+ 'ui:readonly': true
617
+ },
618
+ providerUrl: {
619
+ 'ui:readonly': true
620
+ },
621
+ useCustomOAuthEndpoints: {
622
+ 'ui:readonly': true
623
+ },
624
+ authorizationUrl: {
625
+ 'ui:readonly': true
626
+ },
627
+ tokenUrl: {
628
+ 'ui:readonly': true
629
+ },
630
+ revocationUrl: {
631
+ 'ui:readonly': true
632
+ },
633
+ redirectUri: {
634
+ 'ui:readonly': true
635
+ },
636
+ scopes: {
637
+ 'ui:readonly': true,
638
+ 'ui:widget': 'chips'
639
+ },
640
+ description: {
641
+ 'ui:readonly': true
642
+ },
643
+ connectorType: {
644
+ 'ui:widget': 'hidden'
645
+ },
646
+ mcpUrl: {
647
+ 'ui:readonly': true
648
+ },
649
+ authMethod: {},
650
+ mcpScopes: {
651
+ 'ui:readonly': true,
652
+ 'ui:widget': 'chips'
653
+ },
654
+ hideCredentials: {
655
+ 'ui:widget': 'hidden'
656
+ },
657
+ isConnected: {
658
+ 'ui:widget': 'hidden'
659
+ }
660
+ };
661
+ return [{
662
+ id: STEP_KEY,
663
+ title: 'Step 1',
664
+ elements: [{
665
+ id: 'oauthConnector',
666
+ type: 'object',
667
+ label: 'OAuth Connector',
668
+ required: false,
669
+ description: seed.description || 'Connect to external OAuth providers',
670
+ connectorType: seed.connectorType,
671
+ widget: 'OAuthConnectorWidget',
672
+ uiField: 'OAuthConnectorField',
673
+ children,
674
+ _widgetUiOverrides: widgetUiOverrides,
675
+ connectorProvider: seed.providerId,
676
+ connectorProviderTitle: seed.title,
677
+ connectorDescription: seed.description,
678
+ connectorCategory: seedCategory(seed),
679
+ ...(seed.connectorType === 'mcp' ? {
680
+ connectorMcpUrl: seed.providerUrl
681
+ } : {
682
+ connectorProviderUrl: seed.providerUrl
683
+ }),
684
+ connectorScopes: seed.scopes,
685
+ useCustomOAuthEndpoints: seed.useCustomOAuthEndpoints ?? false,
686
+ connectorAuthorizationUrl: seed.authorizationUrl || '',
687
+ connectorTokenUrl: seed.tokenUrl || '',
688
+ connectorRevocationUrl: seed.revocationUrl || '',
689
+ useBasicAuth: seed.useBasicAuth ?? false,
690
+ connectorRedirectUri: '{{CLIENT_URL}}/{{CONNECTOR_ENDPOINT}}',
691
+ // connectorAuthMethod — present only for non-oauth connectors (matches reference forms)
692
+ ...(!seed.authMethods.includes('oauth2') ? {
693
+ connectorAuthMethod: seedAuthMethodDefault(seed)
694
+ } : {}),
695
+ ...(seed.fields.length > 0 ? {
696
+ connectorProviderFields: seed.fields
697
+ } : {}),
698
+ ...(seed.resources.length > 0 ? {
699
+ connectorProviderResources: seed.resources
700
+ } : {}),
701
+ ...(seed.customButtons.length > 0 ? {
702
+ connectorProviderCustomButtons: seed.customButtons
703
+ } : {}),
704
+ addedFromStepperConfigToggle: true,
705
+ isSchemaElement: true,
706
+ sourceSchemaId: 'configuration',
707
+ fullPath,
708
+ contributionPoint: 'configuration.integrationSettings',
709
+ templateType,
710
+ namespace
711
+ }]
712
+ }];
713
+ }
714
+ const buildCategoryKeyLookup = () => {
715
+ const lookup = new Map();
716
+ const prefix = 'uiLayout.credential.templates.';
717
+ const suffix = '.value';
718
+ for (const [key, value] of Object.entries(CONTRIBUTION_POINTS)) {
719
+ if (key.startsWith(prefix) && key.endsWith(suffix)) {
720
+ lookup.set(value, key.slice(prefix.length, -suffix.length));
721
+ }
722
+ }
723
+ return lookup;
724
+ };
725
+ const CATEGORY_KEY_LOOKUP = buildCategoryKeyLookup();
726
+ const categoryToTemplateTypeKey = category => {
727
+ const key = CATEGORY_KEY_LOOKUP.get(category);
728
+ if (!key) {
729
+ throw new Error(`No contribution-point template type found for category "${category}"`);
730
+ }
731
+ return key;
732
+ };
733
+ function buildOAuthConfigurationContribution(seedsWithNamespaces) {
734
+ const formProperties = {
735
+ required: []
736
+ };
737
+ const formUIProperties = {};
738
+ const metaPaths = new Set();
739
+ for (const {
740
+ seed,
741
+ namespace
742
+ } of seedsWithNamespaces) {
743
+ const vaultPrefix = toVaultPrefix(seed.providerId);
744
+ const isApp = seed.connectorType === 'app';
745
+ const isApiKeyOnly = seedIsApiKeyOnly(seed);
746
+ const templateType = seedTemplateType(seed);
747
+ const basePath = `integrationSettings.settings.${templateType}.${namespace}.oauthConnector`;
748
+ // Helper to add a config field under the oauthConnector basePath
749
+ const addField = (fieldName, def, uiDef) => {
750
+ formProperties[`${basePath}.${fieldName}`] = {
751
+ ...def,
752
+ fieldMeta: {
753
+ addedFromStepperConfigToggle: true
754
+ }
755
+ };
756
+ if (uiDef && Object.keys(uiDef).length > 0) {
757
+ formUIProperties[`${basePath}.${fieldName}`] = uiDef;
758
+ }
759
+ };
760
+ // Register oauthConnector path + all parents for meta entries (always, so widget renders)
761
+ const baseParts = basePath.split('.');
762
+ for (let i = 1; i < baseParts.length; i++) {
763
+ metaPaths.add(baseParts.slice(0, i).join('.'));
764
+ }
765
+ metaPaths.add(basePath);
766
+ // Root OAuthConnector UI entry (always, so widget renders)
767
+ // Include ui:options so providerFields / authMethods survive the $ref resolution
768
+ const uiOptions = {
769
+ provider: seed.providerId,
770
+ connectorType: seed.connectorType,
771
+ defaultAuthMethod: seedAuthMethodDefault(seed)
772
+ };
773
+ if (seed.authMethods.length > 0) uiOptions.authMethods = seed.authMethods;
774
+ if (seed.clientAuthMethod) uiOptions.defaultClientAuthMethod = seed.clientAuthMethod;
775
+ if (seed.usePKCE !== undefined) uiOptions.usePKCE = seed.usePKCE;
776
+ uiOptions.category = seedCategory(seed);
777
+ if (seed.fields.length > 0) {
778
+ uiOptions.providerFields = seed.fields.map(field => field.isConfigField ? {
779
+ ...field,
780
+ keyReference: `${basePath}.${field.genericType ?? field.name}`,
781
+ schemaReference: 'configuration'
782
+ } : field);
783
+ }
784
+ if (seed.resources.length > 0) uiOptions.providerResources = seed.resources;
785
+ if (seed.customButtons.length > 0) uiOptions.providerCustomButtons = seed.customButtons;
786
+ formUIProperties[basePath] = {
787
+ 'ui:widget': 'OAuthConnectorWidget',
788
+ 'ui:field': 'OAuthConnectorField',
789
+ 'ui:options': uiOptions
790
+ };
791
+ // ── OAuth sub-fields — only for non-api_key-only connectors ─────
792
+ if (!isApiKeyOnly) {
793
+ addField('clientId', {
794
+ type: 'string',
795
+ title: 'Client ID',
796
+ description: 'OAuth application client identifier',
797
+ format: 'secret',
798
+ default: '',
799
+ readOnly: true,
800
+ scope: isApp ? 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */ : 1 /* ConfigurationScope.APPLICATION */,
801
+ defaultValueSource: {
802
+ type: ContributeDefaultValueSourceType.Vault,
803
+ secretType: 'OAUTH_CLIENT',
804
+ secretKey: `${vaultPrefix}_CLIENT_ID`,
805
+ ...(isApp ? {
806
+ requiredSignature: true
807
+ } : {})
808
+ }
809
+ }, {
810
+ 'ui:widget': 'hidden',
811
+ 'ui:description': 'OAuth application client identifier',
812
+ 'ui:placeholder': 'Enter OAuth Client ID'
813
+ });
814
+ addField('clientSecret', {
815
+ type: 'string',
816
+ title: 'Client Secret',
817
+ description: 'OAuth application client secret',
818
+ format: 'secret',
819
+ default: '',
820
+ readOnly: true,
821
+ scope: isApp ? 6 /* ConfigurationScope.MACHINE_OVERRIDABLE */ : 1 /* ConfigurationScope.APPLICATION */,
822
+ defaultValueSource: {
823
+ type: ContributeDefaultValueSourceType.Vault,
824
+ secretType: 'OAUTH_CLIENT',
825
+ secretKey: `${vaultPrefix}_CLIENT_SECRET`,
826
+ ...(isApp ? {
827
+ requiredSignature: true
828
+ } : {})
829
+ }
830
+ }, {
831
+ 'ui:widget': 'hidden',
832
+ 'ui:description': 'OAuth application client secret',
833
+ 'ui:placeholder': 'Enter OAuth Client Secret'
834
+ });
835
+ addField('apiKey', {
836
+ type: 'string',
837
+ title: 'API Key',
838
+ description: 'API Key for MCP integration',
839
+ format: 'secret',
840
+ default: '',
841
+ writeOnly: true,
842
+ scope: 4 /* ConfigurationScope.RESOURCE */,
843
+ defaultValueSource: {
844
+ type: ContributeDefaultValueSourceType.Vault,
845
+ secretType: 'API_KEY',
846
+ secretKey: `${vaultPrefix}_API_KEY`
847
+ }
848
+ }, {
849
+ 'ui:widget': !seed.authMethods.includes('oauth2') ? 'password' : 'hidden',
850
+ 'ui:description': 'API Key for MCP integration',
851
+ 'ui:placeholder': 'Enter API Key'
852
+ });
853
+ addField('accessToken', {
854
+ type: 'string',
855
+ title: 'Access Token',
856
+ description: '',
857
+ format: 'secret',
858
+ default: '',
859
+ readOnly: true,
860
+ writeOnly: true,
861
+ scope: 1 /* ConfigurationScope.APPLICATION */,
862
+ defaultValueSource: {
863
+ type: ContributeDefaultValueSourceType.Vault,
864
+ secretType: 'OAUTH_CLIENT',
865
+ secretKey: `${vaultPrefix}_ACCESS_TOKEN`
866
+ }
867
+ }, {
868
+ 'ui:widget': 'hidden'
869
+ });
870
+ addField('refreshToken', {
871
+ type: 'string',
872
+ title: 'Refresh Token',
873
+ description: '',
874
+ format: 'secret',
875
+ default: '',
876
+ readOnly: true,
877
+ writeOnly: true,
878
+ scope: 1 /* ConfigurationScope.APPLICATION */,
879
+ defaultValueSource: {
880
+ type: ContributeDefaultValueSourceType.Vault,
881
+ secretType: 'OAUTH_CLIENT',
882
+ secretKey: `${vaultPrefix}_REFRESH_TOKEN`
883
+ }
884
+ }, {
885
+ 'ui:widget': 'hidden'
886
+ });
887
+ addField('expiresAt', {
888
+ type: 'number',
889
+ title: 'Expires At',
890
+ description: 'Access token expiry as Unix timestamp in milliseconds; 0 if unset',
891
+ default: 0,
892
+ readOnly: true,
893
+ scope: 1 /* ConfigurationScope.APPLICATION */
894
+ }, {
895
+ 'ui:widget': 'hidden'
896
+ });
897
+ addField('urlTemplateKeyValues', {
898
+ type: 'string',
899
+ title: 'URL Template Key Values',
900
+ description: 'Stringified JSON of resolved values for URL template placeholders (e.g. {"zoho-region":"eu"})',
901
+ default: '{}',
902
+ readOnly: true,
903
+ scope: 1 /* ConfigurationScope.APPLICATION */
904
+ }, {
905
+ 'ui:widget': 'hidden'
906
+ });
907
+ addField('authRequestMeta', {
908
+ type: 'string',
909
+ title: 'Auth Request Metadata',
910
+ description: 'Serialized OAuth authorization-request metadata (e.g. PKCE verifier, state) for session continuity',
911
+ default: '',
912
+ readOnly: true,
913
+ scope: 1 /* ConfigurationScope.APPLICATION */
914
+ }, {
915
+ 'ui:widget': 'hidden'
916
+ });
917
+ addField('provider', {
918
+ type: 'string',
919
+ title: 'Provider',
920
+ default: seed.providerId,
921
+ readOnly: true,
922
+ scope: 2 /* ConfigurationScope.MACHINE */
923
+ }, {
924
+ 'ui:readonly': true
925
+ });
926
+ addField('providerTitle', {
927
+ type: 'string',
928
+ title: 'Provider Title',
929
+ default: seed.title,
930
+ readOnly: true,
931
+ scope: 2 /* ConfigurationScope.MACHINE */
932
+ }, {
933
+ 'ui:readonly': true
934
+ });
935
+ addField('providerUrl', {
936
+ type: 'string',
937
+ title: 'Provider URL',
938
+ default: seed.providerUrl,
939
+ readOnly: true,
940
+ scope: 2 /* ConfigurationScope.MACHINE */
941
+ }, {
942
+ 'ui:readonly': true
943
+ });
944
+ addField('useCustomOAuthEndpoints', {
945
+ type: 'boolean',
946
+ title: 'Use Custom OAuth Endpoints',
947
+ default: seed.useCustomOAuthEndpoints ?? false,
948
+ readOnly: true,
949
+ scope: 2 /* ConfigurationScope.MACHINE */
950
+ }, {
951
+ 'ui:readonly': true
952
+ });
953
+ addField('authorizationUrl', {
954
+ type: 'string',
955
+ title: 'Authorization URL',
956
+ default: seed.authorizationUrl || '',
957
+ readOnly: true,
958
+ scope: 2 /* ConfigurationScope.MACHINE */
959
+ }, {
960
+ 'ui:readonly': true
961
+ });
962
+ addField('tokenUrl', {
963
+ type: 'string',
964
+ title: 'Token URL',
965
+ default: seed.tokenUrl || '',
966
+ readOnly: true,
967
+ scope: 2 /* ConfigurationScope.MACHINE */
968
+ }, {
969
+ 'ui:readonly': true
970
+ });
971
+ addField('revocationUrl', {
972
+ type: 'string',
973
+ title: 'Revocation URL',
974
+ default: seed.revocationUrl || '',
975
+ readOnly: true,
976
+ scope: 2 /* ConfigurationScope.MACHINE */
977
+ }, {
978
+ 'ui:readonly': true
979
+ });
980
+ addField('useBasicAuth', {
981
+ type: 'boolean',
982
+ title: 'Use Basic Auth',
983
+ default: seed.useBasicAuth ?? false,
984
+ readOnly: true,
985
+ scope: 2 /* ConfigurationScope.MACHINE */
986
+ }, {
987
+ 'ui:widget': 'hidden'
988
+ });
989
+ if (seed.clientAuthMethod) {
990
+ addField('clientAuthMethod', {
991
+ type: 'string',
992
+ title: 'Client Auth Method',
993
+ default: seed.clientAuthMethod,
994
+ readOnly: true,
995
+ scope: 2 /* ConfigurationScope.MACHINE */
996
+ }, {
997
+ 'ui:widget': 'hidden'
998
+ });
999
+ }
1000
+ addField('redirectUri', {
1001
+ type: 'string',
1002
+ title: 'Redirect URI',
1003
+ default: '{{CLIENT_URL}}/{{CONNECTOR_ENDPOINT}}',
1004
+ readOnly: true,
1005
+ scope: 2 /* ConfigurationScope.MACHINE */,
1006
+ defaultValueSource: {
1007
+ type: ContributeDefaultValueSourceType.Environment
1008
+ }
1009
+ }, {
1010
+ 'ui:readonly': true
1011
+ });
1012
+ addField('scopes', {
1013
+ type: 'string',
1014
+ title: 'Scopes',
1015
+ default: seed.scopes,
1016
+ readOnly: true,
1017
+ scope: 2 /* ConfigurationScope.MACHINE */
1018
+ }, {
1019
+ 'ui:readonly': true,
1020
+ 'ui:widget': 'chips'
1021
+ });
1022
+ addField('description', {
1023
+ type: 'string',
1024
+ title: 'Description',
1025
+ default: seed.description,
1026
+ readOnly: true,
1027
+ scope: 2 /* ConfigurationScope.MACHINE */
1028
+ }, {
1029
+ 'ui:readonly': true
1030
+ });
1031
+ addField('connectorType', {
1032
+ type: 'string',
1033
+ title: 'Connector Type',
1034
+ default: seed.connectorType,
1035
+ readOnly: true,
1036
+ scope: 2 /* ConfigurationScope.MACHINE */
1037
+ }, {
1038
+ 'ui:widget': 'hidden'
1039
+ });
1040
+ addField('mcpUrl', {
1041
+ type: 'string',
1042
+ title: 'MCP URL',
1043
+ default: seed.mcpUrl || '',
1044
+ readOnly: true,
1045
+ scope: 2 /* ConfigurationScope.MACHINE */
1046
+ }, {
1047
+ 'ui:readonly': true
1048
+ });
1049
+ addField('authMethod', {
1050
+ type: 'string',
1051
+ title: 'Auth Method',
1052
+ default: seedAuthMethodDefault(seed),
1053
+ readOnly: true,
1054
+ scope: 2 /* ConfigurationScope.MACHINE */
1055
+ }, {});
1056
+ addField('mcpScopes', {
1057
+ type: 'string',
1058
+ title: 'MCP Scopes',
1059
+ default: seed.connectorType === 'mcp' ? seed.scopes : [],
1060
+ readOnly: true,
1061
+ scope: 2 /* ConfigurationScope.MACHINE */
1062
+ }, {
1063
+ 'ui:readonly': true,
1064
+ 'ui:widget': 'chips'
1065
+ });
1066
+ addField('hideCredentials', {
1067
+ type: 'string',
1068
+ title: 'Hide Credentials',
1069
+ default: false,
1070
+ scope: 2 /* ConfigurationScope.MACHINE */
1071
+ }, {
1072
+ 'ui:widget': 'hidden'
1073
+ });
1074
+ addField('isConnected', {
1075
+ type: 'string',
1076
+ title: 'Connected',
1077
+ default: false,
1078
+ readOnly: true,
1079
+ scope: 2 /* ConfigurationScope.MACHINE */
1080
+ }, {
1081
+ 'ui:widget': 'hidden'
1082
+ });
1083
+ } else {
1084
+ // api_key-only: still need minimal sub-fields so the widget can render
1085
+ addField('provider', {
1086
+ type: 'string',
1087
+ title: 'Provider',
1088
+ default: seed.providerId,
1089
+ readOnly: true,
1090
+ scope: 2 /* ConfigurationScope.MACHINE */
1091
+ }, {
1092
+ 'ui:readonly': true
1093
+ });
1094
+ addField('providerTitle', {
1095
+ type: 'string',
1096
+ title: 'Provider Title',
1097
+ default: seed.title,
1098
+ readOnly: true,
1099
+ scope: 2 /* ConfigurationScope.MACHINE */
1100
+ }, {
1101
+ 'ui:readonly': true
1102
+ });
1103
+ addField('providerUrl', {
1104
+ type: 'string',
1105
+ title: 'Provider URL',
1106
+ default: seed.providerUrl,
1107
+ readOnly: true,
1108
+ scope: 2 /* ConfigurationScope.MACHINE */
1109
+ }, {
1110
+ 'ui:readonly': true
1111
+ });
1112
+ addField('description', {
1113
+ type: 'string',
1114
+ title: 'Description',
1115
+ default: seed.description,
1116
+ readOnly: true,
1117
+ scope: 2 /* ConfigurationScope.MACHINE */
1118
+ }, {
1119
+ 'ui:readonly': true
1120
+ });
1121
+ addField('connectorType', {
1122
+ type: 'string',
1123
+ title: 'Connector Type',
1124
+ default: seed.connectorType,
1125
+ readOnly: true,
1126
+ scope: 2 /* ConfigurationScope.MACHINE */
1127
+ }, {
1128
+ 'ui:widget': 'hidden'
1129
+ });
1130
+ addField('authMethod', {
1131
+ type: 'string',
1132
+ title: 'Auth Method',
1133
+ default: 'api_key',
1134
+ readOnly: true,
1135
+ scope: 2 /* ConfigurationScope.MACHINE */
1136
+ }, {});
1137
+ }
1138
+ // Custom isConfigField fields should also be written into configuration schema.
1139
+ for (const field of seed.fields) {
1140
+ if (!field.isConfigField) continue;
1141
+ const fieldPath = `${basePath}.${field.genericType ?? field.name}`;
1142
+ const fieldDef = {
1143
+ type: field.type,
1144
+ title: field.label,
1145
+ description: field.description || '',
1146
+ fieldMeta: {
1147
+ addedFromStepperConfigToggle: true
1148
+ },
1149
+ required: field.required,
1150
+ scope: field.scope || 4 /* ConfigurationScope.RESOURCE */
1151
+ };
1152
+ if (field.isSecret && field.secretSource) {
1153
+ fieldDef.format = 'secret';
1154
+ fieldDef.writeOnly = true;
1155
+ fieldDef['x-env'] = {
1156
+ isEnvironmentVariable: true,
1157
+ vaultKeyName: field.secretSource.secretKey,
1158
+ secretType: field.secretSource.secretType
1159
+ };
1160
+ fieldDef.defaultValueSource = {
1161
+ type: field.secretSource.type,
1162
+ secretType: field.secretSource.secretType,
1163
+ secretKey: field.secretSource.secretKey
1164
+ };
1165
+ fieldDef.isEnvironmentVariable = true;
1166
+ fieldDef.secretType = field.secretSource.secretType;
1167
+ fieldDef.vaultKeyName = field.secretSource.secretKey;
1168
+ if (field.placeholder) fieldDef.placeholder = field.placeholder;
1169
+ }
1170
+ formProperties[fieldPath] = fieldDef;
1171
+ const uiEntry = {};
1172
+ if (field.uiWidget && field.uiWidget !== 'text') {
1173
+ uiEntry['ui:widget'] = field.uiWidget;
1174
+ }
1175
+ if (field.placeholder) {
1176
+ uiEntry['ui:placeholder'] = field.placeholder;
1177
+ }
1178
+ if (Object.keys(uiEntry).length > 0) {
1179
+ formUIProperties[fieldPath] = uiEntry;
1180
+ }
1181
+ // Ensure meta paths include this custom field path.
1182
+ const parts = fieldPath.split('.');
1183
+ for (let i = 1; i < parts.length; i++) {
1184
+ metaPaths.add(parts.slice(0, i).join('.'));
1185
+ }
1186
+ }
1187
+ }
1188
+ // Build meta entries for all parent paths
1189
+ const sortedMeta = Array.from(metaPaths).sort();
1190
+ for (const path of sortedMeta) {
1191
+ const parts = path.split('.');
1192
+ formProperties[`${path}.meta`] = {
1193
+ title: parts[parts.length - 1],
1194
+ description: '',
1195
+ type: 'object',
1196
+ position: 0
1197
+ };
1198
+ formProperties[`${path}.required`] = [];
1199
+ }
1200
+ return [{
1201
+ type: 'form-metadata',
1202
+ properties: {
1203
+ schemaId: 'configuration'
1204
+ }
1205
+ }, {
1206
+ type: 'form',
1207
+ properties: formProperties
1208
+ }, {
1209
+ type: 'formUI',
1210
+ properties: formUIProperties
1211
+ }];
1212
+ }
1213
+ function generateFromSeeds(seeds, title, formIcon, formExtensionId) {
1214
+ const allNodes = [];
1215
+ const mergedUiLayoutTemplates = {};
1216
+ const mergedUiLayoutUi = {};
1217
+ let uiLayoutFormMetadataNode = null;
1218
+ const seedsWithNamespaces = [];
1219
+ seeds.forEach((seed, idx) => {
1220
+ const generatedFormId = `${sanitizeFieldKey(seed.providerId)}-${seed.connectorType}-${Math.random().toString(36).substring(2, 10)}`;
1221
+ const providerTitle = `${seed.title} (${seed.connectorType.toUpperCase()})`;
1222
+ const namespace = generatedFormId;
1223
+ seedsWithNamespaces.push({
1224
+ seed,
1225
+ namespace
1226
+ });
1227
+ // ── Stepper-form: minimal form node with $ref to configuration ──
1228
+ seedIsApiKeyOnly(seed);
1229
+ const formProperties = {
1230
+ required: [],
1231
+ [`${STEP_KEY}.meta`]: {
1232
+ title: 'Step 1',
1233
+ description: '',
1234
+ type: 'object',
1235
+ position: 0
1236
+ },
1237
+ [`${STEP_KEY}.required`]: []
1238
+ };
1239
+ // oauthConnector $ref — always present so the widget renders for all forms
1240
+ formProperties[`${STEP_KEY}.oauthConnector`] = {
1241
+ $ref: `configuration#/properties/integrationSettings.settings.${seedTemplateType(seed)}.${namespace}.oauthConnector`
1242
+ };
1243
+ // Custom isConfigField fields are rendered inside the OAuthConnectorWidget
1244
+ // via connectorProviderFields — no standalone $refs needed in the stepper form.
1245
+ const formNode = {
1246
+ type: 'form',
1247
+ properties: formProperties
1248
+ };
1249
+ const formUINode = {
1250
+ type: 'formUI',
1251
+ properties: {
1252
+ [STEP_KEY]: {
1253
+ 'ui:options': {}
1254
+ }
1255
+ }
1256
+ };
1257
+ const definitionsNode = {
1258
+ type: 'definitions',
1259
+ properties: {}
1260
+ };
1261
+ const connectionsNode = {
1262
+ type: 'connections',
1263
+ properties: {
1264
+ type: 'connections',
1265
+ 'properties.steps.0': STEP_KEY,
1266
+ [`properties.transitions.${STEP_KEY}`]: {}
1267
+ }
1268
+ };
1269
+ const buttonsNode = buildCustomButtonsNode(seed);
1270
+ const resourcesNode = buildResourcesNode(seed);
1271
+ const inngestNode = buildInngestNode();
1272
+ const formDataNode = {
1273
+ type: 'formData',
1274
+ properties: {}
1275
+ };
1276
+ // form-metadata goes LAST (matches manual GUI pattern)
1277
+ const formMetadataNode = {
1278
+ type: 'form-metadata',
1279
+ properties: {
1280
+ title: providerTitle,
1281
+ formIcon: seed.formIcon || '',
1282
+ id: generatedFormId,
1283
+ category: seedCategory(seed),
1284
+ preGeneratedFormSteps: buildPreGeneratedFormSteps(seed, namespace)
1285
+ }
1286
+ };
1287
+ // Emit nodes: form-metadata FIRST (parseSampleNodes splits groups at form-metadata boundaries)
1288
+ allNodes.push(formMetadataNode, formNode, formUINode);
1289
+ allNodes.push(definitionsNode, connectionsNode);
1290
+ allNodes.push(buttonsNode || {
1291
+ type: 'customButtons',
1292
+ properties: {}
1293
+ });
1294
+ allNodes.push(resourcesNode || {
1295
+ type: 'resources',
1296
+ properties: {}
1297
+ });
1298
+ allNodes.push(inngestNode);
1299
+ allNodes.push(formDataNode);
1300
+ // Build merged uiLayout (single record)
1301
+ // Only build the metadata node once
1302
+ if (!uiLayoutFormMetadataNode) {
1303
+ uiLayoutFormMetadataNode = {
1304
+ type: 'form-metadata',
1305
+ properties: {
1306
+ id: 'uilayout-oauth',
1307
+ title: 'uiLayout',
1308
+ schemaId: 'uiLayout',
1309
+ contributionPoint: 'credential',
1310
+ namespace: 'templates'
1311
+ }
1312
+ };
1313
+ }
1314
+ // Add to merged templates — key must match formId (same pattern as commonApiSample)
1315
+ const hasDualAuth = seed.connectorType === 'app' && seed.authMethods.includes('api_key') && seed.authMethods.includes('oauth2');
1316
+ const templateProperties = {
1317
+ templateName: {
1318
+ type: 'string',
1319
+ title: 'templateName',
1320
+ default: providerTitle,
1321
+ scope: 3 /* ConfigurationScope.WINDOW */
1322
+ },
1323
+ projectType: {
1324
+ type: 'string',
1325
+ title: 'projectType',
1326
+ default: seed.connectorType === 'mcp' ? 'MCP' : !seed.authMethods.includes('oauth2') && (seed.authMethods.includes('api_key') || seed.authMethods.includes('bearer_token')) ? 'API' : 'OAuth',
1327
+ scope: 3 /* ConfigurationScope.WINDOW */
1328
+ },
1329
+ description: {
1330
+ type: 'string',
1331
+ title: 'description',
1332
+ default: seed.description || `${seed.title} connector template`,
1333
+ scope: 3 /* ConfigurationScope.WINDOW */
1334
+ },
1335
+ tags: {
1336
+ type: 'array',
1337
+ title: 'tags',
1338
+ items: {
1339
+ type: 'string'
1340
+ },
1341
+ uniqueItems: true,
1342
+ default: [seed.providerId, seed.connectorType, 'connector'],
1343
+ scope: 3 /* ConfigurationScope.WINDOW */
1344
+ },
1345
+ keywords: {
1346
+ type: 'array',
1347
+ title: 'keywords',
1348
+ items: {
1349
+ type: 'string'
1350
+ },
1351
+ uniqueItems: true,
1352
+ default: seed.connectorType === 'mcp' ? [seed.providerId, 'mcp', seed.authMethods.includes('oauth2') ? 'oauth' : 'api-key'] : !seed.authMethods.includes('oauth2') && seed.authMethods.includes('api_key') ? [seed.providerId, 'api', 'api-key'] : [seed.providerId, seed.connectorType, 'oauth widget'],
1353
+ scope: 3 /* ConfigurationScope.WINDOW */
1354
+ },
1355
+ formId: {
1356
+ type: 'string',
1357
+ title: 'formId',
1358
+ default: generatedFormId,
1359
+ scope: 3 /* ConfigurationScope.WINDOW */
1360
+ },
1361
+ formExtensionId: {
1362
+ type: 'string',
1363
+ title: 'formExtensionId',
1364
+ default: formExtensionId,
1365
+ scope: 3 /* ConfigurationScope.WINDOW */
1366
+ },
1367
+ formIcon: {
1368
+ type: 'string',
1369
+ title: 'formIcon',
1370
+ default: seed.formIcon || '',
1371
+ scope: 3 /* ConfigurationScope.WINDOW */
1372
+ }
1373
+ };
1374
+ if (hasDualAuth) {
1375
+ templateProperties.apiKeyEnabled = {
1376
+ type: 'boolean',
1377
+ title: 'apiKeyEnabled',
1378
+ default: true,
1379
+ scope: 3 /* ConfigurationScope.WINDOW */
1380
+ };
1381
+ templateProperties.oAuthEnabled = {
1382
+ type: 'boolean',
1383
+ title: 'oAuthEnabled',
1384
+ default: false,
1385
+ scope: 3 /* ConfigurationScope.WINDOW */
1386
+ };
1387
+ } else {
1388
+ // If connectorType is 'app' and only oauth2 is present, enabled should be false
1389
+ const isAppOnlyOauth = seed.connectorType === 'app' && seed.authMethods.length === 1 && seed.authMethods[0] === 'oauth2';
1390
+ templateProperties.enabled = {
1391
+ type: 'boolean',
1392
+ title: 'enabled',
1393
+ default: !isAppOnlyOauth,
1394
+ scope: 3 /* ConfigurationScope.WINDOW */
1395
+ };
1396
+ }
1397
+ mergedUiLayoutTemplates[generatedFormId] = {
1398
+ type: 'object',
1399
+ title: providerTitle,
1400
+ properties: templateProperties,
1401
+ required: []
1402
+ };
1403
+ const templateUi = {
1404
+ templateName: {
1405
+ 'ui:widget': 'text'
1406
+ },
1407
+ projectType: {
1408
+ 'ui:widget': 'text'
1409
+ },
1410
+ description: {
1411
+ 'ui:widget': 'textarea',
1412
+ 'ui:options': {
1413
+ rows: 2
1414
+ }
1415
+ },
1416
+ tags: {
1417
+ 'ui:widget': 'chips',
1418
+ 'ui:options': {
1419
+ label: false
1420
+ }
1421
+ },
1422
+ keywords: {
1423
+ 'ui:widget': 'chips',
1424
+ 'ui:options': {
1425
+ label: false
1426
+ }
1427
+ },
1428
+ formId: {
1429
+ 'ui:readonly': true
1430
+ },
1431
+ formExtensionId: {
1432
+ 'ui:readonly': true
1433
+ },
1434
+ formIcon: {
1435
+ 'ui:widget': 'text'
1436
+ }
1437
+ };
1438
+ if (hasDualAuth) {
1439
+ templateUi.apiKeyEnabled = {
1440
+ 'ui:widget': 'switch'
1441
+ };
1442
+ templateUi.oAuthEnabled = {
1443
+ 'ui:widget': 'switch'
1444
+ };
1445
+ } else {
1446
+ templateUi.enabled = {
1447
+ 'ui:widget': 'switch'
1448
+ };
1449
+ }
1450
+ mergedUiLayoutUi[generatedFormId] = templateUi;
1451
+ });
1452
+ // Compose a single flattened uiLayout schema record for all providers, with '{credentialPath}.{category}.' prefix
1453
+ // Group by connector category so each provider lands under its actual category key
1454
+ const credentialBasePath = CONTRIBUTION_POINTS['uiLayout.credential.path'];
1455
+ const prefixedTemplates = {};
1456
+ const prefixedUi = {};
1457
+ // Build a namespace→category lookup from seeds using contribution-point template type keys
1458
+ const nsToCategoryMap = new Map();
1459
+ seedsWithNamespaces.forEach(({
1460
+ seed,
1461
+ namespace
1462
+ }) => {
1463
+ nsToCategoryMap.set(namespace, categoryToTemplateTypeKey(seedCategory(seed)));
1464
+ });
1465
+ Object.keys(mergedUiLayoutTemplates).forEach(key => {
1466
+ const categoryKey = nsToCategoryMap.get(key);
1467
+ if (!categoryKey) throw new Error(`Missing category mapping for namespace "${key}"`);
1468
+ prefixedTemplates[`${credentialBasePath}.${categoryKey}.${key}`] = mergedUiLayoutTemplates[key];
1469
+ });
1470
+ Object.keys(mergedUiLayoutUi).forEach(key => {
1471
+ const categoryKey = nsToCategoryMap.get(key);
1472
+ if (!categoryKey) throw new Error(`Missing category mapping for namespace "${key}"`);
1473
+ prefixedUi[`${credentialBasePath}.${categoryKey}.${key}`] = mergedUiLayoutUi[key];
1474
+ });
1475
+ const uiLayoutSchema = {
1476
+ type: 'object',
1477
+ properties: {
1478
+ ...prefixedTemplates
1479
+ }
1480
+ };
1481
+ const uiLayoutUiSchema = {
1482
+ ...prefixedUi
1483
+ };
1484
+ const flattenedUiLayout = flattenSchemas({
1485
+ formSchema: uiLayoutSchema,
1486
+ uiSchema: uiLayoutUiSchema
1487
+ });
1488
+ const uiLayoutFormNode = {
1489
+ type: 'form',
1490
+ properties: flattenedUiLayout.formSchema || {}
1491
+ };
1492
+ const uiLayoutFormUINode = {
1493
+ type: 'formUI',
1494
+ properties: flattenedUiLayout.uiSchema || {}
1495
+ };
1496
+ // Merged configuration contribution (ALWAYS emitted)
1497
+ allNodes.push(...buildOAuthConfigurationContribution(seedsWithNamespaces));
1498
+ if (!uiLayoutFormMetadataNode) return allNodes;
1499
+ return [...allNodes, uiLayoutFormMetadataNode, uiLayoutFormNode, uiLayoutFormUINode];
1500
+ }
1501
+ function createOAuthSeedGenerator(seeds) {
1502
+ return (title = '', formIcon = '', formExtensionId = '') => generateFromSeeds(seeds, title, formIcon, formExtensionId);
1503
+ }
1504
+ // create provider refs
1505
+ const findRefPath = node => {
1506
+ if (!node || typeof node !== 'object') return null;
1507
+ if (typeof node.$ref === 'string') return node.$ref;
1508
+ for (const value of Object.values(node)) {
1509
+ if (Array.isArray(value)) {
1510
+ for (const entry of value) {
1511
+ const found = findRefPath(entry);
1512
+ if (found) return found;
1513
+ }
1514
+ } else if (value && typeof value === 'object') {
1515
+ const found = findRefPath(value);
1516
+ if (found) return found;
1517
+ }
1518
+ }
1519
+ return null;
1520
+ };
1521
+ const buildProviderWithRefs = (provider, sampleNodes) => {
1522
+ const refPath = findRefPath({
1523
+ nodes: sampleNodes
1524
+ });
1525
+ const refMatch = refPath && /integrationSettings\.settings\.([^.]+)\.([^.]+)\.oauthConnector/.exec(refPath);
1526
+ const templateType = refMatch?.[1];
1527
+ const namespace = refMatch?.[2];
1528
+ const basePath = templateType && namespace ? `integrationSettings.settings.${templateType}.${namespace}.oauthConnector` : null;
1529
+ if (!basePath) return provider;
1530
+ return {
1531
+ ...provider,
1532
+ fields: provider.fields.map(field => {
1533
+ if (!field.isConfigField) return field;
1534
+ return {
1535
+ ...field,
1536
+ keyReference: `${basePath}.${field.genericType ?? field.name}`,
1537
+ schemaReference: 'configuration'
1538
+ };
1539
+ })
1540
+ };
1541
+ };
1542
+ const buildMetadataSchema = sampleNodes => {
1543
+ const parsed = parseSampleNodes(sampleNodes, 'stepper-form', 'Multi Form');
1544
+ const mainNodes = parsed.mainMetadataNode ? [parsed.mainMetadataNode, ...parsed.mainNodes] : parsed.mainNodes;
1545
+ return [{
1546
+ schemaId: 'stepper-form',
1547
+ formId: parsed.mainMetadataNode?.properties?.id || '',
1548
+ integrationConfigurationId: null,
1549
+ type: 'Multi Form',
1550
+ configurationNodes: mainNodes
1551
+ }, ...parsed.additionalSchemas.map(entry => ({
1552
+ schemaId: entry.schemaId,
1553
+ formId: entry.formId || '',
1554
+ integrationConfigurationId: null,
1555
+ type: entry.type || 'Contribution',
1556
+ configurationNodes: entry.configurationNodes,
1557
+ contributes: {}
1558
+ }))];
1559
+ };export{buildMetadataSchema,buildProviderConfig,buildProviderSeedFromConfig,buildProviderWithRefs,createOAuthSeedGenerator};//# sourceMappingURL=authMetaDataSchema.js.map