openyida 2026.9.4 → 2026.9.7
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 +7 -6
- package/bin/yida.js +1 -1
- package/lib/app/app-list.js +78 -53
- package/lib/app/canvas-compile.js +147 -0
- package/lib/app/page-linter.js +24 -0
- package/lib/app/publish.js +14 -1
- package/lib/app/services/canvas-page-schema-builder.js +175 -0
- package/lib/app/update-app.js +8 -2
- package/lib/bridge/bridge.js +15 -3
- package/lib/connector/api.js +3 -1
- package/lib/connector/connector-add-action.js +2 -0
- package/lib/connector/connector-create-connection.js +119 -16
- package/lib/connector/connector-create.js +40 -32
- package/lib/connector/connector-detail.js +2 -1
- package/lib/connector/connector-gen-template.js +5 -17
- package/lib/connector/connector-list-connections.js +5 -4
- package/lib/connector/connector-test.js +48 -2
- package/lib/connector/contract.js +132 -10
- package/lib/connector/operation-normalizer.js +20 -4
- package/lib/connector/yida-system-token.js +280 -0
- package/lib/core/agent-capabilities.js +24 -2
- package/lib/core/command-manifest.js +8 -8
- package/lib/core/locales/en.js +43 -2
- package/lib/core/locales/zh.js +43 -2
- package/lib/core/redact.js +4 -3
- package/lib/integration/connector-presets.js +95 -9
- package/lib/integration/integration-connector-schema.js +79 -15
- package/lib/integration/integration-create.js +131 -5
- package/lib/integration/integration-readback.js +136 -0
- package/lib/process/configure-process.js +69 -16
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/yida-skills/SKILL.md +3 -1
- package/yida-skills/references/resource-context.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -3
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +19 -3
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +25 -22
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +19 -18
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +41 -37
- package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +2 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +1 -1
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +3 -3
- package/yida-skills/skills/yida-canvas-data-binding/references/connector-binding.md +23 -0
- package/yida-skills/skills/yida-connector/SKILL.md +15 -8
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +2 -2
- package/yida-skills/skills/yida-connector/references/connector-action-format.md +9 -0
- package/yida-skills/skills/yida-connector/templates/api-document-template.md +6 -19
- package/yida-skills/skills/yida-data-management/SKILL.md +8 -4
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-design.md +2 -0
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +4 -3
- package/yida-skills/skills/yida-dingtalk-openapi/SKILL.md +75 -0
- package/yida-skills/skills/yida-dingtalk-openapi/references/api-contract.md +98 -0
- package/yida-skills/skills/yida-dingtalk-openapi/references/connector-mapping.md +27 -0
- package/yida-skills/skills/yida-integration/SKILL.md +29 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +3 -2
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +2 -1
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +3 -0
- package/yida-skills/skills-index.json +54 -0
|
@@ -269,14 +269,187 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
269
269
|
return bridge;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
function openyidaConnectorCsrfToken() {
|
|
273
|
+
var targets = openyidaYidaApiBridgeTargets();
|
|
274
|
+
for (var i = 0; i < targets.length; i += 1) {
|
|
275
|
+
try {
|
|
276
|
+
var config = targets[i] && targets[i].g_config;
|
|
277
|
+
if (config && config._csrf_token) {
|
|
278
|
+
return String(config._csrf_token);
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {}
|
|
281
|
+
}
|
|
282
|
+
return '';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function openyidaConnectorBridgeError(code, message) {
|
|
286
|
+
var error = new Error(message);
|
|
287
|
+
error.code = code;
|
|
288
|
+
return error;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function openyidaResolveConnectorName(binding) {
|
|
292
|
+
var connectorName = binding && binding.connectorName ? String(binding.connectorName) : '';
|
|
293
|
+
var legacyConnectorId = binding && binding.connectorId ? String(binding.connectorId) : '';
|
|
294
|
+
if (!connectorName && legacyConnectorId.indexOf('Http_') === 0) {
|
|
295
|
+
connectorName = legacyConnectorId;
|
|
296
|
+
}
|
|
297
|
+
if (!connectorName || connectorName.indexOf('Http_') !== 0) {
|
|
298
|
+
throw openyidaConnectorBridgeError(
|
|
299
|
+
'CONNECTOR_RUNTIME_NAME_REQUIRED',
|
|
300
|
+
'connectorName must be the internal Http_* connector name; numeric connectorId is only for CLI management'
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (legacyConnectorId.indexOf('Http_') === 0 && legacyConnectorId !== connectorName) {
|
|
304
|
+
throw openyidaConnectorBridgeError(
|
|
305
|
+
'CONNECTOR_RUNTIME_IDENTITY_MISMATCH',
|
|
306
|
+
'connectorName and legacy connectorId refer to different connectors'
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return connectorName;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function openyidaParseConnectorJson(value) {
|
|
313
|
+
if (typeof value !== 'string') { return value; }
|
|
314
|
+
try { return JSON.parse(value); } catch (err) { return value; }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function openyidaConnectorErrorMessage(value, depth) {
|
|
318
|
+
if (depth > 4 || value === null || value === undefined) { return ''; }
|
|
319
|
+
var parsed = openyidaParseConnectorJson(value);
|
|
320
|
+
if (typeof parsed === 'string') { return parsed; }
|
|
321
|
+
if (typeof parsed !== 'object') { return ''; }
|
|
322
|
+
var directKeys = ['errorMsg', 'errorMessage', 'message'];
|
|
323
|
+
for (var i = 0; i < directKeys.length; i += 1) {
|
|
324
|
+
var direct = parsed[directKeys[i]];
|
|
325
|
+
if (direct !== undefined && direct !== null && String(direct).trim()) {
|
|
326
|
+
return String(direct);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (parsed.error && typeof parsed.error === 'object') {
|
|
330
|
+
var nestedError = openyidaConnectorErrorMessage(parsed.error, depth + 1);
|
|
331
|
+
if (nestedError) { return nestedError; }
|
|
332
|
+
}
|
|
333
|
+
if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
|
|
334
|
+
var listedError = openyidaConnectorErrorMessage(parsed.errors[0], depth + 1);
|
|
335
|
+
if (listedError) { return listedError; }
|
|
336
|
+
}
|
|
337
|
+
var nestedKeys = ['content', 'serviceReturnValue', 'result', 'data'];
|
|
338
|
+
for (var j = 0; j < nestedKeys.length; j += 1) {
|
|
339
|
+
if (parsed[nestedKeys[j]] !== undefined && parsed[nestedKeys[j]] !== null) {
|
|
340
|
+
var nested = openyidaConnectorErrorMessage(parsed[nestedKeys[j]], depth + 1);
|
|
341
|
+
if (nested) { return nested; }
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return '';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function openyidaConnectorPayloadFailed(value) {
|
|
348
|
+
var parsed = openyidaParseConnectorJson(value);
|
|
349
|
+
return !!(parsed && typeof parsed === 'object'
|
|
350
|
+
&& (parsed.success === false || parsed.hasError === true));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function openyidaNormalizeConnectorInputs(inputs) {
|
|
354
|
+
var source = inputs && typeof inputs === 'object' ? inputs : {};
|
|
355
|
+
var body = Object.prototype.hasOwnProperty.call(source, 'body') ? source.body : {};
|
|
356
|
+
if (body === null || body === undefined) { body = {}; }
|
|
357
|
+
if (typeof body !== 'object' || Array.isArray(body)) {
|
|
358
|
+
throw openyidaConnectorBridgeError(
|
|
359
|
+
'CONNECTOR_BODY_OBJECT_REQUIRED',
|
|
360
|
+
'connector inputs.body must be an object; pass the value directly instead of JSON.stringify(...)'
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
path: source.path && typeof source.path === 'object' ? source.path : {},
|
|
365
|
+
query: source.query && typeof source.query === 'object' ? source.query : {},
|
|
366
|
+
header: source.header && typeof source.header === 'object' ? source.header : {},
|
|
367
|
+
body: body
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function openyidaCreateConnectorApiBridge() {
|
|
372
|
+
function invoke(binding, inputs) {
|
|
373
|
+
if (!binding || !binding.operationId) {
|
|
374
|
+
return Promise.reject(openyidaConnectorBridgeError(
|
|
375
|
+
'CONNECTOR_RUNTIME_IDENTITY_REQUIRED',
|
|
376
|
+
'connectorName and operationId are required'
|
|
377
|
+
));
|
|
378
|
+
}
|
|
379
|
+
var connectorName;
|
|
380
|
+
var normalizedInputs;
|
|
381
|
+
try {
|
|
382
|
+
connectorName = openyidaResolveConnectorName(binding);
|
|
383
|
+
normalizedInputs = openyidaNormalizeConnectorInputs(inputs);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return Promise.reject(error);
|
|
386
|
+
}
|
|
387
|
+
var connectorInfo = {
|
|
388
|
+
connectorId: connectorName,
|
|
389
|
+
actionId: String(binding.operationId),
|
|
390
|
+
type: 'httpConnector'
|
|
391
|
+
};
|
|
392
|
+
if (binding.connectionId !== undefined && binding.connectionId !== null && binding.connectionId !== '') {
|
|
393
|
+
connectorInfo.connection = String(binding.connectionId);
|
|
394
|
+
}
|
|
395
|
+
var csrfToken = openyidaConnectorCsrfToken();
|
|
396
|
+
var endpoint = '/query/publicService/invokeService.json';
|
|
397
|
+
if (csrfToken) {
|
|
398
|
+
endpoint += '?_csrf_token=' + encodeURIComponent(csrfToken);
|
|
399
|
+
}
|
|
400
|
+
var body = 'inputs=' + encodeURIComponent(JSON.stringify(normalizedInputs))
|
|
401
|
+
+ '&serviceInfo=' + encodeURIComponent(JSON.stringify({ connectorInfo: connectorInfo }));
|
|
402
|
+
var headers = { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' };
|
|
403
|
+
if (csrfToken) {
|
|
404
|
+
headers.global_csrf_token = csrfToken;
|
|
405
|
+
}
|
|
406
|
+
if (typeof window === 'undefined' || typeof window.fetch !== 'function') {
|
|
407
|
+
return Promise.reject(new Error('window.fetch is not available'));
|
|
408
|
+
}
|
|
409
|
+
return window.fetch(endpoint, {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
credentials: 'include',
|
|
412
|
+
headers: headers,
|
|
413
|
+
body: body
|
|
414
|
+
}).then(function(response) {
|
|
415
|
+
if (!response || response.ok === false || typeof response.json !== 'function') {
|
|
416
|
+
throw new Error('connector invocation HTTP request failed');
|
|
417
|
+
}
|
|
418
|
+
return response.json();
|
|
419
|
+
}).then(function(payload) {
|
|
420
|
+
if (!payload || openyidaConnectorPayloadFailed(payload)) {
|
|
421
|
+
throw openyidaConnectorBridgeError(
|
|
422
|
+
'CONNECTOR_INVOCATION_FAILED',
|
|
423
|
+
openyidaConnectorErrorMessage(payload, 0) || 'connector invocation failed'
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
var value = payload.content && Object.prototype.hasOwnProperty.call(payload.content, 'serviceReturnValue')
|
|
427
|
+
? payload.content.serviceReturnValue
|
|
428
|
+
: payload.content;
|
|
429
|
+
value = openyidaParseConnectorJson(value);
|
|
430
|
+
if (openyidaConnectorPayloadFailed(value)) {
|
|
431
|
+
throw openyidaConnectorBridgeError(
|
|
432
|
+
'CONNECTOR_SERVICE_FAILED',
|
|
433
|
+
openyidaConnectorErrorMessage(value, 0) || 'connector service returned failure'
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return value;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return { ready: true, invoke: invoke };
|
|
440
|
+
}
|
|
441
|
+
|
|
272
442
|
function openyidaInstallYidaApiBridge() {
|
|
273
443
|
var bridge = openyidaCreateYidaApiBridge(this);
|
|
274
444
|
var utilsBridge = openyidaCreateUtilsBridge(this, bridge);
|
|
445
|
+
var connectorBridge = openyidaCreateConnectorApiBridge();
|
|
275
446
|
try {
|
|
276
447
|
window.__OPENYIDA_YIDA_API__ = bridge;
|
|
277
448
|
window.openyidaYidaApi = bridge;
|
|
278
449
|
window.__OPENYIDA_UTILS__ = utilsBridge;
|
|
279
450
|
window.openyidaUtils = utilsBridge;
|
|
451
|
+
window.__OPENYIDA_CONNECTOR_API__ = connectorBridge;
|
|
452
|
+
window.openyidaConnectorApi = connectorBridge;
|
|
280
453
|
} catch (err) {}
|
|
281
454
|
openyidaYidaApiBridgeTargets().forEach(function(target) {
|
|
282
455
|
try {
|
|
@@ -284,6 +457,8 @@ function openyidaInstallYidaApiBridge() {
|
|
|
284
457
|
target.openyidaYidaApi = bridge;
|
|
285
458
|
target.__OPENYIDA_UTILS__ = utilsBridge;
|
|
286
459
|
target.openyidaUtils = utilsBridge;
|
|
460
|
+
target.__OPENYIDA_CONNECTOR_API__ = connectorBridge;
|
|
461
|
+
target.openyidaConnectorApi = connectorBridge;
|
|
287
462
|
} catch (err) {}
|
|
288
463
|
});
|
|
289
464
|
return bridge;
|
package/lib/app/update-app.js
CHANGED
|
@@ -294,6 +294,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
294
294
|
|
|
295
295
|
const currentThemeColor = pickAppField(currentApp, 'themeColor', 'THEME_COLOR');
|
|
296
296
|
const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
|
|
297
|
+
const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
|
|
298
|
+
const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
|
|
297
299
|
if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
|
|
298
300
|
throw new Error(t('update_app.custom_theme_color_required'));
|
|
299
301
|
}
|
|
@@ -342,10 +344,14 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
|
|
|
342
344
|
navigation: currentApp.navigation || (currentApp.config && currentApp.config.NAVIGATION) || 'TODO,DONE,SUBMIT',
|
|
343
345
|
pageHeader: currentApp.pageHeader || '',
|
|
344
346
|
pageFooter: currentApp.pageFooter || '',
|
|
345
|
-
addWaterMark: currentApp.addWaterMark || (currentApp.config && currentApp.config.ADDWATERMARK) || 'y',
|
|
346
|
-
sentryMode: currentApp.sentryMode || (currentApp.config && currentApp.config.SENTRY_MODE) || 'y',
|
|
347
347
|
};
|
|
348
348
|
|
|
349
|
+
if (addWaterMark !== undefined && addWaterMark !== null) {
|
|
350
|
+
postDataObj.addWaterMark = addWaterMark;
|
|
351
|
+
}
|
|
352
|
+
if (sentryMode !== undefined && sentryMode !== null) {
|
|
353
|
+
postDataObj.sentryMode = sentryMode;
|
|
354
|
+
}
|
|
349
355
|
if (params.hideAppNav !== null && params.hideAppNav !== undefined) {
|
|
350
356
|
postDataObj.hideAppNav = params.hideAppNav;
|
|
351
357
|
} else {
|
package/lib/bridge/bridge.js
CHANGED
|
@@ -389,11 +389,19 @@ function normalizeCreateAppInput(body = {}) {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
function normalizeAppListInput(body = {}) {
|
|
392
|
-
const
|
|
392
|
+
const type = String(body.type ?? 'managed').trim();
|
|
393
|
+
const page = Number(body.page ?? body.pageIndex ?? 1);
|
|
394
|
+
const size = Number(body.size ?? body.pageSize ?? 16);
|
|
395
|
+
if (!['managed', 'created'].includes(type)) {
|
|
396
|
+
throw new Error('type must be managed or created');
|
|
397
|
+
}
|
|
398
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
399
|
+
throw new Error('page must be a positive integer');
|
|
400
|
+
}
|
|
393
401
|
if (!Number.isInteger(size) || size < 1 || size > 100) {
|
|
394
402
|
throw new Error('size must be an integer between 1 and 100');
|
|
395
403
|
}
|
|
396
|
-
return { size };
|
|
404
|
+
return { type, page, size };
|
|
397
405
|
}
|
|
398
406
|
|
|
399
407
|
function parseCreateAppJsonOutput(stdout) {
|
|
@@ -723,8 +731,12 @@ function defaultListApps(params = {}) {
|
|
|
723
731
|
const args = [
|
|
724
732
|
cliPath,
|
|
725
733
|
'app-list',
|
|
734
|
+
'--type',
|
|
735
|
+
params.type ?? 'managed',
|
|
736
|
+
'--page',
|
|
737
|
+
String(params.page ?? 1),
|
|
726
738
|
'--size',
|
|
727
|
-
String(params.size
|
|
739
|
+
String(params.size ?? 16),
|
|
728
740
|
];
|
|
729
741
|
|
|
730
742
|
return new Promise((resolve, reject) => {
|
package/lib/connector/api.js
CHANGED
|
@@ -322,9 +322,11 @@ async function saveConnector(params, authRef) {
|
|
|
322
322
|
}, authRef);
|
|
323
323
|
assertConnectorReadback(params, detail);
|
|
324
324
|
|
|
325
|
+
const detailConnectorId = detail && (detail.id || detail.connectorId);
|
|
326
|
+
|
|
325
327
|
return {
|
|
326
328
|
...result,
|
|
327
|
-
connectorId,
|
|
329
|
+
connectorId: detailConnectorId || connectorId,
|
|
328
330
|
connectorName,
|
|
329
331
|
detail,
|
|
330
332
|
readbackVerified: true,
|
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
createActionSnapshot,
|
|
29
29
|
mergeAddedOperations,
|
|
30
30
|
} = require('./connector-action-update');
|
|
31
|
+
const { assertNoExplicitSystemToken } = require('./yida-system-token');
|
|
31
32
|
|
|
32
33
|
function showUsage() {
|
|
33
34
|
console.log(`
|
|
@@ -105,6 +106,7 @@ async function run(args) {
|
|
|
105
106
|
newOperations = [newOperations];
|
|
106
107
|
}
|
|
107
108
|
newOperations = normalizeOperations(newOperations);
|
|
109
|
+
assertNoExplicitSystemToken(newOperations, { source: '--operations' });
|
|
108
110
|
console.log(`✓ 已加载 ${newOperations.length} 个执行动作`);
|
|
109
111
|
newOperations.forEach((operation, index) => {
|
|
110
112
|
console.log(` [${index + 1}] ${operation.operationId}: ${operation.summary}`);
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
* --username <username> 基本身份验证 - 用户名
|
|
8
8
|
* --password <password> 基本身份验证 - 密码
|
|
9
9
|
* --api-key <key> API 密钥 - 密钥值
|
|
10
|
-
* --app-key <key> 钉钉开放平台 - App Key
|
|
11
|
-
* --app-secret <secret> 钉钉开放平台 - App Secret
|
|
12
10
|
* --app-code <code> 阿里云 API 网关 - AppCode
|
|
11
|
+
* --interactive 在当前终端隐藏输入钉钉 App Key / App Secret
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
'use strict';
|
|
16
15
|
const { fail } = require('../core/chalk');
|
|
17
16
|
const { safeJsonStringify } = require('../core/redact');
|
|
17
|
+
const { CliError } = require('../core/cli-error');
|
|
18
|
+
const { t } = require('../core/i18n');
|
|
18
19
|
|
|
19
20
|
const {
|
|
20
21
|
getAuthRef,
|
|
@@ -36,14 +37,13 @@ function showUsage() {
|
|
|
36
37
|
--username <username> 基本身份验证 - 用户名
|
|
37
38
|
--password <password> 基本身份验证 - 密码
|
|
38
39
|
--api-key <key> API 密钥 - 密钥值
|
|
39
|
-
--app-key <key> 钉钉开放平台 - App Key
|
|
40
|
-
--app-secret <secret> 钉钉开放平台 - App Secret
|
|
41
40
|
--app-code <code> 阿里云 API 网关 - AppCode
|
|
41
|
+
--interactive 在 TTY 终端隐藏输入钉钉 App Key / App Secret(推荐)
|
|
42
42
|
|
|
43
43
|
示例:
|
|
44
44
|
openyida connector create-connection 910264 "测试账号" --username "admin" --password "123456"
|
|
45
45
|
openyida connector create-connection 910258 "生产密钥" --api-key "sk-xxxxxxxx"
|
|
46
|
-
openyida connector create-connection 910244 "钉钉账号" --
|
|
46
|
+
openyida connector create-connection 910244 "钉钉账号" --interactive
|
|
47
47
|
openyida connector create-connection 910264 "阿里云账号" --app-code "your-app-code"
|
|
48
48
|
`);
|
|
49
49
|
}
|
|
@@ -85,6 +85,98 @@ function buildSecurityValue(options, authType) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
function secretInputError(code, message, details = {}) {
|
|
89
|
+
return new CliError(message, {
|
|
90
|
+
code,
|
|
91
|
+
details: {
|
|
92
|
+
retrySafe: true,
|
|
93
|
+
sideEffectState: 'none',
|
|
94
|
+
...details,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readHiddenLine(prompt, input = process.stdin, output = process.stderr) {
|
|
100
|
+
if (!input || !output || input.isTTY !== true || typeof input.setRawMode !== 'function') {
|
|
101
|
+
return Promise.reject(secretInputError(
|
|
102
|
+
'CONNECTOR_SECRET_INPUT_TTY_REQUIRED',
|
|
103
|
+
t('connector_auth.tty_required'),
|
|
104
|
+
{ nextStep: t('connector_auth.tty_next_step') }
|
|
105
|
+
));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
let value = '';
|
|
110
|
+
const wasRaw = !!input.isRaw;
|
|
111
|
+
const cleanup = () => {
|
|
112
|
+
input.removeListener('data', onData);
|
|
113
|
+
if (!wasRaw) {input.setRawMode(false);}
|
|
114
|
+
input.pause();
|
|
115
|
+
};
|
|
116
|
+
const finish = () => {
|
|
117
|
+
cleanup();
|
|
118
|
+
output.write('\n');
|
|
119
|
+
resolve(value);
|
|
120
|
+
};
|
|
121
|
+
const abort = () => {
|
|
122
|
+
cleanup();
|
|
123
|
+
output.write('\n');
|
|
124
|
+
reject(secretInputError('CONNECTOR_SECRET_INPUT_CANCELLED', t('connector_auth.input_cancelled')));
|
|
125
|
+
};
|
|
126
|
+
const onData = (chunk) => {
|
|
127
|
+
const text = String(chunk);
|
|
128
|
+
for (const char of text) {
|
|
129
|
+
if (char === '\u0003') {abort(); return;}
|
|
130
|
+
if (char === '\r' || char === '\n') {finish(); return;}
|
|
131
|
+
if (char === '\u007f' || char === '\b') {
|
|
132
|
+
value = Array.from(value).slice(0, -1).join('');
|
|
133
|
+
} else if (char >= ' ') {
|
|
134
|
+
value += char;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
output.write(prompt);
|
|
140
|
+
input.setEncoding('utf8');
|
|
141
|
+
input.setRawMode(true);
|
|
142
|
+
input.resume();
|
|
143
|
+
input.on('data', onData);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function collectInteractiveCredentials(options, authType, deps = {}) {
|
|
148
|
+
if (!options.interactive) {return options;}
|
|
149
|
+
if (options.appKey || options.appSecret) {
|
|
150
|
+
throw secretInputError(
|
|
151
|
+
'CONNECTOR_SECRET_INPUT_CONFLICT',
|
|
152
|
+
t('connector_auth.interactive_conflict')
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (authType !== 'DingAuth' && authType !== 'DingTrustGW') {
|
|
156
|
+
throw secretInputError(
|
|
157
|
+
'CONNECTOR_INTERACTIVE_AUTH_UNSUPPORTED',
|
|
158
|
+
t('connector_auth.interactive_auth_unsupported', authType)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const readSecret = deps.readSecret || readHiddenLine;
|
|
163
|
+
const input = deps.input || process.stdin;
|
|
164
|
+
const output = deps.output || process.stderr;
|
|
165
|
+
if (input.isTTY !== true || typeof input.setRawMode !== 'function') {
|
|
166
|
+
throw secretInputError(
|
|
167
|
+
'CONNECTOR_SECRET_INPUT_TTY_REQUIRED',
|
|
168
|
+
t('connector_auth.tty_required'),
|
|
169
|
+
{ nextStep: t('connector_auth.tty_next_step') }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const appKey = await readSecret(t('connector_auth.prompt_app_key'), input, output);
|
|
173
|
+
const appSecret = await readSecret(t('connector_auth.prompt_app_secret'), input, output);
|
|
174
|
+
if (!appKey || !appSecret) {
|
|
175
|
+
throw secretInputError('CONNECTOR_SECRET_INPUT_EMPTY', t('connector_auth.input_empty'));
|
|
176
|
+
}
|
|
177
|
+
return { ...options, appKey, appSecret };
|
|
178
|
+
}
|
|
179
|
+
|
|
88
180
|
function parseArgs(args) {
|
|
89
181
|
const options = {
|
|
90
182
|
connectorId: args[0],
|
|
@@ -111,6 +203,9 @@ function parseArgs(args) {
|
|
|
111
203
|
case '--app-code':
|
|
112
204
|
options.appCode = args[++i];
|
|
113
205
|
break;
|
|
206
|
+
case '--interactive':
|
|
207
|
+
options.interactive = true;
|
|
208
|
+
break;
|
|
114
209
|
case '--json':
|
|
115
210
|
options.json = true;
|
|
116
211
|
break;
|
|
@@ -120,18 +215,19 @@ function parseArgs(args) {
|
|
|
120
215
|
return options;
|
|
121
216
|
}
|
|
122
217
|
|
|
123
|
-
async function run(args) {
|
|
218
|
+
async function run(args, deps = {}) {
|
|
124
219
|
if (!args || args.length < 2 || args[0] === '--help' || args[0] === '-h') {
|
|
125
220
|
showUsage();
|
|
126
221
|
process.exit(0);
|
|
127
222
|
}
|
|
128
223
|
|
|
129
224
|
const options = parseArgs(args);
|
|
225
|
+
const log = options.json ? () => {} : console.log;
|
|
130
226
|
const authRef = getAuthRef();
|
|
131
227
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
228
|
+
log('🔧 正在创建鉴权账号...\n');
|
|
229
|
+
log(`连接器 ID: ${options.connectorId}`);
|
|
230
|
+
log(`账号名称: ${options.connectionName}\n`);
|
|
135
231
|
|
|
136
232
|
const connector = await findConnectorById(options.connectorId, authRef);
|
|
137
233
|
if (!connector) {
|
|
@@ -139,8 +235,8 @@ async function run(args) {
|
|
|
139
235
|
process.exit(1);
|
|
140
236
|
}
|
|
141
237
|
|
|
142
|
-
|
|
143
|
-
|
|
238
|
+
log(`连接器: ${connector.displayName}`);
|
|
239
|
+
log(`连接器名: ${connector.connectorName}\n`);
|
|
144
240
|
|
|
145
241
|
const detail = await getConnectorDetail(connector.connectorName, authRef);
|
|
146
242
|
const securitySchemes = JSON.parse(detail.securitySchemes || '{}');
|
|
@@ -151,9 +247,10 @@ async function run(args) {
|
|
|
151
247
|
process.exit(1);
|
|
152
248
|
}
|
|
153
249
|
|
|
154
|
-
|
|
250
|
+
log(`鉴权类型: ${authType}\n`);
|
|
155
251
|
|
|
156
|
-
const
|
|
252
|
+
const credentialOptions = await collectInteractiveCredentials(options, authType, deps);
|
|
253
|
+
const securityValue = buildSecurityValue(credentialOptions, authType);
|
|
157
254
|
|
|
158
255
|
const result = await createConnection({
|
|
159
256
|
connectionName: options.connectionName,
|
|
@@ -163,8 +260,8 @@ async function run(args) {
|
|
|
163
260
|
authType: AUTH_TYPE_CODES[authType] ?? AUTH_TYPE_CODES.NONE,
|
|
164
261
|
}, authRef);
|
|
165
262
|
|
|
166
|
-
|
|
167
|
-
|
|
263
|
+
log('✅ 鉴权账号创建成功!');
|
|
264
|
+
log(`账号 ID: ${result.id || result.connectionId || '-'}`);
|
|
168
265
|
const output = {
|
|
169
266
|
success: true,
|
|
170
267
|
connectionId: result.id || result.connectionId,
|
|
@@ -177,4 +274,10 @@ async function run(args) {
|
|
|
177
274
|
return output;
|
|
178
275
|
}
|
|
179
276
|
|
|
180
|
-
module.exports = {
|
|
277
|
+
module.exports = {
|
|
278
|
+
buildSecurityValue,
|
|
279
|
+
collectInteractiveCredentials,
|
|
280
|
+
parseArgs,
|
|
281
|
+
readHiddenLine,
|
|
282
|
+
run,
|
|
283
|
+
};
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
* --api-key-label <label> API Key 显示标签
|
|
14
14
|
* --api-key-name <name> API Key 参数名
|
|
15
15
|
* --api-key-location <loc> API Key 位置: HEADER 或 QUERY
|
|
16
|
-
* --app-key <key> 钉钉 App Key
|
|
17
|
-
* --app-secret <secret> 钉钉 App Secret
|
|
18
16
|
* --desc <description> 连接器描述
|
|
19
17
|
* --icon <icon> 图标代码
|
|
20
18
|
* --operations <file> 执行动作配置文件 (JSON 格式,创建时必填)
|
|
@@ -33,8 +31,11 @@ const {
|
|
|
33
31
|
} = require('./api');
|
|
34
32
|
const { normalizeOperations } = require('./operation-normalizer');
|
|
35
33
|
const { safeJsonStringify } = require('../core/redact');
|
|
34
|
+
const { CliError } = require('../core/cli-error');
|
|
35
|
+
const { t } = require('../core/i18n');
|
|
36
36
|
const { HTTP_CONNECTOR_MODE } = require('./contract');
|
|
37
37
|
const { buildConnectorSaveParams, createActionSnapshot } = require('./connector-action-update');
|
|
38
|
+
const { assertNoExplicitSystemToken } = require('./yida-system-token');
|
|
38
39
|
|
|
39
40
|
function showUsage() {
|
|
40
41
|
console.log(`
|
|
@@ -53,8 +54,6 @@ function showUsage() {
|
|
|
53
54
|
--api-key-label <label> API Key 显示标签 (默认: API Key)
|
|
54
55
|
--api-key-name <name> API Key 参数名 (默认: X-API-Key)
|
|
55
56
|
--api-key-location <loc> API Key 位置: HEADER 或 QUERY (默认: HEADER)
|
|
56
|
-
--app-key <key> 钉钉 App Key
|
|
57
|
-
--app-secret <secret> 钉钉 App Secret
|
|
58
57
|
--desc <description> 连接器描述
|
|
59
58
|
--icon <icon> 图标代码 (默认: chaxun%%#FFA200)
|
|
60
59
|
--operations <file> 执行动作配置文件 (JSON 格式,创建时必填)
|
|
@@ -136,11 +135,15 @@ function parseArgs(args) {
|
|
|
136
135
|
options.apiKeyLocation = args[++i]?.toUpperCase();
|
|
137
136
|
break;
|
|
138
137
|
case '--app-key':
|
|
139
|
-
options.appKey = args[++i];
|
|
140
|
-
break;
|
|
141
138
|
case '--app-secret':
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
throw new CliError(t('connector_auth.create_rejects_credentials'), {
|
|
140
|
+
code: 'CONNECTOR_CREDENTIALS_NOT_ACCEPTED_ON_CREATE',
|
|
141
|
+
details: {
|
|
142
|
+
retrySafe: true,
|
|
143
|
+
sideEffectState: 'none',
|
|
144
|
+
nextStep: t('connector_auth.create_credentials_next_step'),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
144
147
|
case '--desc':
|
|
145
148
|
options.description = args[++i];
|
|
146
149
|
break;
|
|
@@ -179,9 +182,6 @@ function buildSecuritySchemes(options) {
|
|
|
179
182
|
},
|
|
180
183
|
});
|
|
181
184
|
case 'DINGTALK':
|
|
182
|
-
if (!options.appKey || !options.appSecret) {
|
|
183
|
-
throw new Error('钉钉鉴权需要提供 --app-key 和 --app-secret');
|
|
184
|
-
}
|
|
185
185
|
return JSON.stringify({ DingAuth: {} });
|
|
186
186
|
case 'ALIYUN':
|
|
187
187
|
return JSON.stringify({ AliyunApiGateway: {} });
|
|
@@ -222,12 +222,13 @@ async function run(args) {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const options = parseArgs(args);
|
|
225
|
+
const log = options.json ? () => {} : console.log;
|
|
225
226
|
const authRef = getAuthRef();
|
|
226
227
|
|
|
227
228
|
// 更新模式
|
|
228
229
|
if (options.isUpdateMode) {
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
log('🔧 正在更新连接器...\n');
|
|
231
|
+
log(`连接器 ID: ${options.connectorId}\n`);
|
|
231
232
|
|
|
232
233
|
const connector = await findConnectorById(options.connectorId, authRef);
|
|
233
234
|
if (!connector) {
|
|
@@ -235,8 +236,8 @@ async function run(args) {
|
|
|
235
236
|
process.exit(1);
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
log(`找到连接器: ${connector.displayName}`);
|
|
240
|
+
log(`当前描述: ${connector.connectorDesc || '(空)'}\n`);
|
|
240
241
|
|
|
241
242
|
const detail = await getConnectorDetail(connector.connectorName, authRef);
|
|
242
243
|
const snapshot = createActionSnapshot({
|
|
@@ -251,17 +252,17 @@ async function run(args) {
|
|
|
251
252
|
connectorDesc: newDesc,
|
|
252
253
|
}, authRef);
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
log('✅ 连接器更新成功!');
|
|
256
|
+
log('\n新描述:');
|
|
257
|
+
log(newDesc);
|
|
257
258
|
return;
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
// 创建模式
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
log('🔧 正在创建连接器...\n');
|
|
263
|
+
log(`名称: ${options.name}`);
|
|
264
|
+
log(`基础域名: ${options.baseUrl}`);
|
|
265
|
+
log(`鉴权方式: ${options.authType}`);
|
|
265
266
|
|
|
266
267
|
if (!options.operationsFile) {
|
|
267
268
|
fail('错误: 创建连接器必须提供 --operations 参数');
|
|
@@ -278,7 +279,8 @@ async function run(args) {
|
|
|
278
279
|
process.exit(1);
|
|
279
280
|
}
|
|
280
281
|
operations = normalizeOperations(operations);
|
|
281
|
-
|
|
282
|
+
assertNoExplicitSystemToken(operations, { source: '--operations' });
|
|
283
|
+
log(`✓ 已加载 ${operations.length} 个执行动作`);
|
|
282
284
|
} catch (e) {
|
|
283
285
|
fail(`读取执行动作配置文件失败: ${e.message}`);
|
|
284
286
|
process.exit(1);
|
|
@@ -309,25 +311,31 @@ async function run(args) {
|
|
|
309
311
|
const detailUrl = connectorId
|
|
310
312
|
? `https://yidalogin.aliwork.com/platformManage/customConnectorFactory/update?id=${connectorId}&connectorName=${connectorName}&mode=http`
|
|
311
313
|
: null;
|
|
314
|
+
const accountManageUrl = connectorName
|
|
315
|
+
? `https://yidalogin.aliwork.com/platformManage/customConnectorFactory?connectorName=${encodeURIComponent(connectorName)}&action=accountManage`
|
|
316
|
+
: null;
|
|
312
317
|
|
|
313
|
-
|
|
318
|
+
log('\n✅ 连接器创建成功!');
|
|
314
319
|
if (connectorId) {
|
|
315
|
-
|
|
320
|
+
log(`\nID: ${connectorId}`);
|
|
316
321
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
log(`连接器名称: ${connectorName}`);
|
|
323
|
+
log(`显示名称: ${options.name}`);
|
|
324
|
+
log(`基础域名: ${options.baseUrl}`);
|
|
320
325
|
if (detailUrl) {
|
|
321
|
-
|
|
326
|
+
log(`\n🔗 查看连接器:\n ${detailUrl}`);
|
|
327
|
+
log(`\n🔐 添加授权账号:\n ${accountManageUrl}`);
|
|
322
328
|
} else {
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
log('\n💡 下一步: 查看连接器列表获取 ID');
|
|
330
|
+
log(' openyida connector list');
|
|
325
331
|
}
|
|
326
332
|
const result = {
|
|
327
333
|
success: true,
|
|
328
334
|
connectorId,
|
|
329
335
|
connectorName,
|
|
330
336
|
displayName: options.name,
|
|
337
|
+
detailUrl,
|
|
338
|
+
accountManageUrl,
|
|
331
339
|
readbackVerified: saveResult.readbackVerified === true,
|
|
332
340
|
};
|
|
333
341
|
if (options.json) {
|
|
@@ -336,4 +344,4 @@ async function run(args) {
|
|
|
336
344
|
return result;
|
|
337
345
|
}
|
|
338
346
|
|
|
339
|
-
module.exports = { buildSecuritySchemes, parseBaseUrl, run };
|
|
347
|
+
module.exports = { buildSecuritySchemes, parseArgs, parseBaseUrl, run };
|
|
@@ -42,6 +42,7 @@ async function run(args) {
|
|
|
42
42
|
const result = {
|
|
43
43
|
success: true,
|
|
44
44
|
connectorId: String(connectorId),
|
|
45
|
+
connectorName: detail.connectorName || connector.connectorName,
|
|
45
46
|
detail: { ...detail, id: detail.id === undefined ? connector.id : detail.id },
|
|
46
47
|
};
|
|
47
48
|
if (json) {
|
|
@@ -53,7 +54,7 @@ async function run(args) {
|
|
|
53
54
|
console.log('📋 基本信息');
|
|
54
55
|
console.log('─'.repeat(50));
|
|
55
56
|
console.log(`显示名称: ${detail.displayName || '-'}`);
|
|
56
|
-
console.log(`连接器名: ${
|
|
57
|
+
console.log(`连接器名: ${result.connectorName || '-'}`);
|
|
57
58
|
console.log(`描述: ${detail.connectorDesc ? detail.connectorDesc.split('---')[0].trim() : '-'}`);
|
|
58
59
|
console.log(`域名: ${detail.host || '-'}`);
|
|
59
60
|
console.log(`基础路径: ${detail.baseUrl || '-'}`);
|