openyida 2026.5.21 → 2026.5.25
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 +5 -1
- package/bin/yida.js +7 -1
- package/lib/app/app-list.js +20 -1
- package/lib/app/check-page.js +2 -2
- package/lib/app/compile.js +3 -2
- package/lib/app/externalize-form.js +642 -0
- package/lib/app/import-app.js +39 -11
- package/lib/app/page-compat.js +258 -2
- package/lib/app/page-compiler.js +4 -1
- package/lib/app/page-linter.js +271 -0
- package/lib/app/publish.js +3 -2
- package/lib/auth/cdp-browser-login.js +7 -3
- package/lib/auth/login.js +2 -3
- package/lib/core/command-manifest.js +3 -0
- package/lib/core/copy.js +50 -8
- package/lib/core/env-manager.js +24 -16
- package/lib/core/locales/ar.js +7 -0
- package/lib/core/locales/de.js +7 -0
- package/lib/core/locales/en.js +7 -0
- package/lib/core/locales/es.js +7 -0
- package/lib/core/locales/fr.js +7 -0
- package/lib/core/locales/hi.js +7 -0
- package/lib/core/locales/ja.js +7 -0
- package/lib/core/locales/ko.js +7 -0
- package/lib/core/locales/pt.js +7 -0
- package/lib/core/locales/vi.js +7 -0
- package/lib/core/locales/zh-HK.js +7 -0
- package/lib/core/locales/zh.js +7 -0
- package/lib/core/utils.js +2 -2
- package/lib/process/configure-process.js +552 -20
- package/package.json +1 -1
- package/project/pages/src/demo-agent-chatbox.oyd.jsx +78 -3
- package/scripts/e2e-real/full-runner.js +257 -8
- package/scripts/e2e-real/skill-coverage.js +2 -2
- package/yida-skills/SKILL.md +1 -1
- package/yida-skills/skills/yida-chart/SKILL.md +1 -1
- package/yida-skills/skills/yida-create-process/SKILL.md +3 -2
- package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
- package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +14 -12
- package/yida-skills/skills/yida-custom-page/references/attachment-upload-guide.md +3 -1
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +4 -0
- package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +31 -22
- package/yida-skills/skills/yida-dashboard/SKILL.md +10 -9
- package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +2 -0
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +13 -4
- package/yida-skills/skills/yida-dashboard/references/structure-and-layout.md +1 -1
- package/yida-skills/skills/yida-ppt-slider/SKILL.md +47 -37
- package/yida-skills/skills/yida-ppt-slider/references/examples.md +5 -4
- package/yida-skills/skills/yida-process-rule/SKILL.md +93 -3
- package/yida-skills/skills/yida-process-rule/references/official-component-nodes.md +93 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +6 -4
package/lib/app/page-linter.js
CHANGED
|
@@ -2,9 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
const { t } = require('../core/i18n');
|
|
4
4
|
const { warn, fail, hint, success } = require('../core/chalk');
|
|
5
|
+
const Babel = require('@babel/standalone');
|
|
5
6
|
|
|
6
7
|
const THEN_CALLBACK_LINE_LIMIT = 50;
|
|
7
8
|
const CALLBACK_SCAN_LINE_LIMIT = 80;
|
|
9
|
+
const parser = Babel.packages.parser;
|
|
10
|
+
const traverse = Babel.packages.traverse.default || Babel.packages.traverse;
|
|
11
|
+
|
|
12
|
+
const PARSER_OPTIONS = {
|
|
13
|
+
sourceType: 'module',
|
|
14
|
+
plugins: [
|
|
15
|
+
'jsx',
|
|
16
|
+
'objectRestSpread',
|
|
17
|
+
'classProperties',
|
|
18
|
+
'optionalChaining',
|
|
19
|
+
'nullishCoalescingOperator',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const EVENT_NAME_ALIASES = {
|
|
24
|
+
onclick: 'onClick',
|
|
25
|
+
onchange: 'onChange',
|
|
26
|
+
oninput: 'onChange',
|
|
27
|
+
onsubmit: 'onSubmit',
|
|
28
|
+
onkeydown: 'onKeyDown',
|
|
29
|
+
onkeyup: 'onKeyUp',
|
|
30
|
+
onkeypress: 'onKeyPress',
|
|
31
|
+
onfocus: 'onFocus',
|
|
32
|
+
onblur: 'onBlur',
|
|
33
|
+
onmouseenter: 'onMouseEnter',
|
|
34
|
+
onmouseleave: 'onMouseLeave',
|
|
35
|
+
onmousedown: 'onMouseDown',
|
|
36
|
+
onmouseup: 'onMouseUp',
|
|
37
|
+
onmousemove: 'onMouseMove',
|
|
38
|
+
oncompositionstart: 'onCompositionStart',
|
|
39
|
+
oncompositionend: 'onCompositionEnd',
|
|
40
|
+
};
|
|
8
41
|
|
|
9
42
|
function isInCommentOrString(line, matchIndex) {
|
|
10
43
|
const beforeMatch = line.substring(0, matchIndex);
|
|
@@ -64,6 +97,9 @@ function pushIssue(list, line, rule, message, disableMap) {
|
|
|
64
97
|
if (isRuleDisabled(disableMap, line, rule)) {
|
|
65
98
|
return;
|
|
66
99
|
}
|
|
100
|
+
if (list.some(issue => issue.line === line && issue.rule === rule && issue.message === message)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
67
103
|
list.push({ line, rule, message });
|
|
68
104
|
}
|
|
69
105
|
|
|
@@ -244,6 +280,240 @@ function detectEchartsRichLabelFormatter(sourceCode, warnings, disableMap) {
|
|
|
244
280
|
}
|
|
245
281
|
}
|
|
246
282
|
|
|
283
|
+
function getNodeLine(node) {
|
|
284
|
+
return node && node.loc && node.loc.start && node.loc.start.line ? node.loc.start.line : 1;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getJsxElementName(nameNode) {
|
|
288
|
+
if (!nameNode) {
|
|
289
|
+
return '';
|
|
290
|
+
}
|
|
291
|
+
if (nameNode.type === 'JSXIdentifier') {
|
|
292
|
+
return nameNode.name;
|
|
293
|
+
}
|
|
294
|
+
if (nameNode.type === 'JSXMemberExpression') {
|
|
295
|
+
return getJsxElementName(nameNode.property);
|
|
296
|
+
}
|
|
297
|
+
return '';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function getJsxAttributeName(nameNode) {
|
|
301
|
+
if (!nameNode || nameNode.type !== 'JSXIdentifier') {
|
|
302
|
+
return '';
|
|
303
|
+
}
|
|
304
|
+
return nameNode.name;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function hasJsxAttribute(attrs, name) {
|
|
308
|
+
return attrs.some(attr => getJsxAttributeName(attr.name) === name);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function getStaticStringAttribute(attrs, name) {
|
|
312
|
+
const attr = attrs.find(item => getJsxAttributeName(item.name) === name);
|
|
313
|
+
if (!attr || !attr.value) {
|
|
314
|
+
return '';
|
|
315
|
+
}
|
|
316
|
+
if (attr.value.type === 'StringLiteral') {
|
|
317
|
+
return attr.value.value;
|
|
318
|
+
}
|
|
319
|
+
return '';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isThisOrSelfMember(expression) {
|
|
323
|
+
return !!(
|
|
324
|
+
expression &&
|
|
325
|
+
expression.type === 'MemberExpression' &&
|
|
326
|
+
(
|
|
327
|
+
expression.object.type === 'ThisExpression' ||
|
|
328
|
+
(expression.object.type === 'Identifier' && expression.object.name === 'self')
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isBindThisCall(expression) {
|
|
334
|
+
return !!(
|
|
335
|
+
expression &&
|
|
336
|
+
expression.type === 'CallExpression' &&
|
|
337
|
+
expression.callee &&
|
|
338
|
+
expression.callee.type === 'MemberExpression' &&
|
|
339
|
+
expression.callee.property &&
|
|
340
|
+
expression.callee.property.type === 'Identifier' &&
|
|
341
|
+
expression.callee.property.name === 'bind' &&
|
|
342
|
+
expression.arguments &&
|
|
343
|
+
expression.arguments[0] &&
|
|
344
|
+
expression.arguments[0].type === 'ThisExpression'
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function expressionIsBareHandlerReference(expression) {
|
|
349
|
+
if (!expression) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (isThisOrSelfMember(expression)) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (expression.type === 'Identifier') {
|
|
356
|
+
return /^handle[A-Z]|^on[A-Z]/.test(expression.name);
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function blockContainsBareHandlerReference(blockStatement) {
|
|
362
|
+
return blockStatement.body.some((statement) => {
|
|
363
|
+
return statement.type === 'ExpressionStatement' &&
|
|
364
|
+
expressionIsBareHandlerReference(statement.expression);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isEventAttribute(name) {
|
|
369
|
+
return /^on[A-Z]/.test(name || '');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function isLowercaseEventAttribute(name) {
|
|
373
|
+
return /^on[a-z]/.test(name || '');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isInteractiveButton(attrs) {
|
|
377
|
+
if (hasJsxAttribute(attrs, 'disabled') || hasJsxAttribute(attrs, 'aria-disabled')) {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const buttonType = getStaticStringAttribute(attrs, 'type').toLowerCase();
|
|
382
|
+
if (buttonType === 'submit') {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return attrs.some((attr) => {
|
|
387
|
+
const attrName = getJsxAttributeName(attr.name);
|
|
388
|
+
return attrName === 'onClick' || attrName === 'onMouseDown' || attrName === 'onKeyDown';
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getCanonicalEventName(name) {
|
|
393
|
+
if (EVENT_NAME_ALIASES[name]) {
|
|
394
|
+
return EVENT_NAME_ALIASES[name];
|
|
395
|
+
}
|
|
396
|
+
if (!name || name.length <= 2) {
|
|
397
|
+
return name;
|
|
398
|
+
}
|
|
399
|
+
return 'on' + name.charAt(2).toUpperCase() + name.slice(3);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function collectAstLintIssues(sourceCode, errors, warnings, disableMap) {
|
|
403
|
+
let ast;
|
|
404
|
+
try {
|
|
405
|
+
ast = parser.parse(sourceCode, PARSER_OPTIONS);
|
|
406
|
+
} catch {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
traverse(ast, {
|
|
411
|
+
ExportNamedDeclaration(pathRef) {
|
|
412
|
+
const declaration = pathRef.node.declaration;
|
|
413
|
+
if (!declaration || declaration.type !== 'FunctionDeclaration' || !declaration.id) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const name = declaration.id.name;
|
|
418
|
+
if (/^didmount$/i.test(name) && name !== 'didMount') {
|
|
419
|
+
pushIssue(errors, getNodeLine(declaration), 'lifecycle-case', t('publish.lint_lifecycle_case', name, 'didMount'), disableMap);
|
|
420
|
+
}
|
|
421
|
+
if (/^didunmount$/i.test(name) && name !== 'didUnmount') {
|
|
422
|
+
pushIssue(errors, getNodeLine(declaration), 'lifecycle-case', t('publish.lint_lifecycle_case', name, 'didUnmount'), disableMap);
|
|
423
|
+
}
|
|
424
|
+
if (name === 'componentDidMount' || name === 'componentWillUnmount') {
|
|
425
|
+
const expected = name === 'componentDidMount' ? 'didMount' : 'didUnmount';
|
|
426
|
+
pushIssue(errors, getNodeLine(declaration), 'react-lifecycle-method', t('publish.lint_react_lifecycle_method', name, expected), disableMap);
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
ClassMethod(pathRef) {
|
|
430
|
+
const key = pathRef.node.key;
|
|
431
|
+
const name = key && key.type === 'Identifier' ? key.name : '';
|
|
432
|
+
if (name === 'componentDidMount' || name === 'componentWillUnmount') {
|
|
433
|
+
const expected = name === 'componentDidMount' ? 'didMount' : 'didUnmount';
|
|
434
|
+
pushIssue(errors, getNodeLine(pathRef.node), 'react-lifecycle-method', t('publish.lint_react_lifecycle_method', name, expected), disableMap);
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
ObjectMethod(pathRef) {
|
|
438
|
+
const key = pathRef.node.key;
|
|
439
|
+
const name = key && key.type === 'Identifier' ? key.name : '';
|
|
440
|
+
if (name === 'componentDidMount' || name === 'componentWillUnmount') {
|
|
441
|
+
const expected = name === 'componentDidMount' ? 'didMount' : 'didUnmount';
|
|
442
|
+
pushIssue(errors, getNodeLine(pathRef.node), 'react-lifecycle-method', t('publish.lint_react_lifecycle_method', name, expected), disableMap);
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
ObjectProperty(pathRef) {
|
|
446
|
+
if (pathRef.node.computed) {
|
|
447
|
+
pushIssue(errors, getNodeLine(pathRef.node), 'computed-property', t('publish.lint_computed_property'), disableMap);
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
JSXOpeningElement(pathRef) {
|
|
451
|
+
const elementName = getJsxElementName(pathRef.node.name);
|
|
452
|
+
const attrs = pathRef.node.attributes || [];
|
|
453
|
+
|
|
454
|
+
if (elementName === 'input' && attrs.some(attr => getJsxAttributeName(attr.name) === 'value')) {
|
|
455
|
+
pushIssue(errors, getNodeLine(pathRef.node), 'controlled-input', t('publish.lint_controlled_input'), disableMap);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (elementName === 'select') {
|
|
459
|
+
pushIssue(warnings, getNodeLine(pathRef.node), 'native-select-ui', t('publish.lint_native_select_ui'), disableMap);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (elementName === 'button' && !isInteractiveButton(attrs)) {
|
|
463
|
+
pushIssue(errors, getNodeLine(pathRef.node), 'button-missing-handler', t('publish.lint_button_missing_handler'), disableMap);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
attrs.forEach((attr) => {
|
|
467
|
+
const attrName = getJsxAttributeName(attr.name);
|
|
468
|
+
if (!attrName) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (isLowercaseEventAttribute(attrName)) {
|
|
473
|
+
pushIssue(errors, getNodeLine(attr), 'event-lowercase', t('publish.lint_event_lowercase', attrName, getCanonicalEventName(attrName)), disableMap);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!isEventAttribute(attrName)) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
|
|
482
|
+
pushIssue(errors, getNodeLine(attr), 'event-call-result', t('publish.lint_event_call_result'), disableMap);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const expression = attr.value.expression;
|
|
487
|
+
|
|
488
|
+
if (isThisOrSelfMember(expression)) {
|
|
489
|
+
pushIssue(errors, getNodeLine(attr), 'event-direct-method', t('publish.lint_event_direct_method'), disableMap);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (isBindThisCall(expression)) {
|
|
494
|
+
pushIssue(errors, getNodeLine(attr), 'event-bind-this', t('publish.lint_event_bind_this'), disableMap);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (expression && expression.type === 'CallExpression') {
|
|
499
|
+
pushIssue(errors, getNodeLine(attr), 'event-call-result', t('publish.lint_event_call_result'), disableMap);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (expression && expression.type === 'ArrowFunctionExpression') {
|
|
504
|
+
if (expressionIsBareHandlerReference(expression.body)) {
|
|
505
|
+
pushIssue(errors, getNodeLine(attr), 'event-noop-arrow', t('publish.lint_event_noop_arrow'), disableMap);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (expression.body && expression.body.type === 'BlockStatement' && blockContainsBareHandlerReference(expression.body)) {
|
|
509
|
+
pushIssue(errors, getNodeLine(attr), 'event-noop-arrow', t('publish.lint_event_noop_arrow'), disableMap);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
247
517
|
function lintYidaSource(sourceCode, filePath) {
|
|
248
518
|
const errors = [];
|
|
249
519
|
const warnings = [];
|
|
@@ -346,6 +616,7 @@ function lintYidaSource(sourceCode, filePath) {
|
|
|
346
616
|
|
|
347
617
|
detectYidaCallsWithoutCatch(sourceCode, warnings, disableMap);
|
|
348
618
|
detectEchartsRichLabelFormatter(sourceCode, warnings, disableMap);
|
|
619
|
+
collectAstLintIssues(sourceCode, errors, warnings, disableMap);
|
|
349
620
|
|
|
350
621
|
return { errors, warnings };
|
|
351
622
|
}
|
package/lib/app/publish.js
CHANGED
|
@@ -34,7 +34,7 @@ const { t } = require('../core/i18n');
|
|
|
34
34
|
const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
|
|
35
35
|
const { compileSource } = require('./page-compiler');
|
|
36
36
|
const { runLintCheck } = require('./page-linter');
|
|
37
|
-
const { buildPageFile,
|
|
37
|
+
const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
|
|
38
38
|
const { fetchFormPageList } = require('./form-navigation');
|
|
39
39
|
const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
|
|
40
40
|
|
|
@@ -868,7 +868,8 @@ async function main(argv) {
|
|
|
868
868
|
error(t('publish.source_not_found', sourcePath));
|
|
869
869
|
}
|
|
870
870
|
|
|
871
|
-
|
|
871
|
+
const initialSourceCode = fs.readFileSync(sourcePath, 'utf-8');
|
|
872
|
+
if (shouldBuildPageSource(initialSourceCode, sourcePath, { modern: compat })) {
|
|
872
873
|
step(0, t('build_page.step'));
|
|
873
874
|
const buildResult = buildPageFile(sourcePath, { modern: compat });
|
|
874
875
|
if (!buildResult.ok) {
|
|
@@ -13,7 +13,7 @@ const path = require('path');
|
|
|
13
13
|
const net = require('net');
|
|
14
14
|
const crypto = require('crypto');
|
|
15
15
|
const { execFileSync, spawn } = require('child_process');
|
|
16
|
-
const { deriveBaseUrlFromCookies,
|
|
16
|
+
const { deriveBaseUrlFromCookies, deriveBaseUrlFromLoginState } = require('../core/env-manager');
|
|
17
17
|
|
|
18
18
|
function findBrowserExecutable() {
|
|
19
19
|
if (process.env.OPENYIDA_CHROME_PATH && fs.existsSync(process.env.OPENYIDA_CHROME_PATH)) {
|
|
@@ -284,6 +284,10 @@ async function getCurrentPageUrl(client, fallbackUrl) {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
function deriveBaseUrlFromBrowserState(cookies, loginUrl, currentPageUrl) {
|
|
288
|
+
return deriveBaseUrlFromLoginState(cookies, loginUrl, currentPageUrl);
|
|
289
|
+
}
|
|
290
|
+
|
|
287
291
|
async function runCdpBrowserLogin(options = {}) {
|
|
288
292
|
const browserPath = options.browserPath || findBrowserExecutable();
|
|
289
293
|
if (!browserPath) {
|
|
@@ -332,10 +336,9 @@ async function runCdpBrowserLogin(options = {}) {
|
|
|
332
336
|
const cookies = Array.isArray(result.cookies) ? result.cookies : [];
|
|
333
337
|
if (cookies.some((cookie) => cookie.name === 'tianshu_csrf_token' && cookie.value)) {
|
|
334
338
|
const currentPageUrl = await getCurrentPageUrl(client, target.url || loginUrl);
|
|
335
|
-
const fallbackBaseUrl = deriveBaseUrlFromUrl(loginUrl, currentPageUrl);
|
|
336
339
|
return {
|
|
337
340
|
cookies,
|
|
338
|
-
base_url:
|
|
341
|
+
base_url: deriveBaseUrlFromBrowserState(cookies, loginUrl, currentPageUrl),
|
|
339
342
|
};
|
|
340
343
|
}
|
|
341
344
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -381,6 +384,7 @@ module.exports = {
|
|
|
381
384
|
cdpBrowserLogin,
|
|
382
385
|
createCdpClient,
|
|
383
386
|
deriveBaseUrl,
|
|
387
|
+
deriveBaseUrlFromBrowserState,
|
|
384
388
|
findBrowserExecutable,
|
|
385
389
|
runCdpBrowserLogin,
|
|
386
390
|
};
|
package/lib/auth/login.js
CHANGED
|
@@ -311,7 +311,7 @@ function playwrightInteractiveLogin(loginUrl, playwrightPath) {
|
|
|
311
311
|
const scriptContent = `
|
|
312
312
|
const playwright = require(${JSON.stringify(playwrightPath)});
|
|
313
313
|
const { chromium } = playwright;
|
|
314
|
-
const {
|
|
314
|
+
const { deriveBaseUrlFromLoginState } = require(${JSON.stringify(envManagerPath)});
|
|
315
315
|
|
|
316
316
|
(async () => {
|
|
317
317
|
// 优先使用本地已安装的 Chrome,避免下载 Playwright 内置 Chromium
|
|
@@ -349,8 +349,7 @@ const { deriveBaseUrlFromCookies, deriveBaseUrlFromUrl } = require(${JSON.string
|
|
|
349
349
|
|
|
350
350
|
const currentUrl = page.url();
|
|
351
351
|
const cookies = await context.cookies();
|
|
352
|
-
const
|
|
353
|
-
const baseUrl = deriveBaseUrlFromCookies(cookies, fallbackBaseUrl);
|
|
352
|
+
const baseUrl = deriveBaseUrlFromLoginState(cookies, ${JSON.stringify(loginUrl)}, currentUrl);
|
|
354
353
|
await browser.close();
|
|
355
354
|
|
|
356
355
|
console.log(JSON.stringify({ cookies, base_url: baseUrl }));
|
|
@@ -110,6 +110,9 @@ const COMMAND_GROUPS = [
|
|
|
110
110
|
command('verify-short-url', ['verify-short-url'], 'verify-short-url <appType> ...', 'help.cmd_verify_url'),
|
|
111
111
|
command('save-share-config', ['save-share-config'], 'save-share-config <appType> ...', 'help.cmd_save_share'),
|
|
112
112
|
command('get-page-config', ['get-page-config'], 'get-page-config <appType> <formUuid>', 'help.cmd_get_page_config'),
|
|
113
|
+
command('externalize-form', ['externalize-form'], 'externalize-form <appType> <formUuid> [--schema-file file]', 'help.cmd_externalize_form', {
|
|
114
|
+
output: 'json|markdown',
|
|
115
|
+
}),
|
|
113
116
|
],
|
|
114
117
|
},
|
|
115
118
|
{
|
package/lib/core/copy.js
CHANGED
|
@@ -81,6 +81,30 @@ function mergeCopyDir(sourceDir, destDir) {
|
|
|
81
81
|
return copiedCount;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function resolveExistingPath(targetPath) {
|
|
85
|
+
try {
|
|
86
|
+
return fs.realpathSync(targetPath);
|
|
87
|
+
} catch {
|
|
88
|
+
return path.resolve(targetPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isSameDirectory(a, b) {
|
|
93
|
+
const pathA = resolveExistingPath(a);
|
|
94
|
+
const pathB = resolveExistingPath(b);
|
|
95
|
+
if (process.platform === 'win32') {
|
|
96
|
+
return pathA.toLowerCase() === pathB.toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
return pathA === pathB;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function clearDirectoryContents(dir) {
|
|
102
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
fs.rmSync(path.join(dir, entry.name), { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
84
108
|
/**
|
|
85
109
|
* 强制复制目录:先清空目标目录,再完整复制。
|
|
86
110
|
* @returns {number} 复制的文件数量
|
|
@@ -89,7 +113,11 @@ function forceCopyDir(sourceDir, destDir) {
|
|
|
89
113
|
if (!fs.existsSync(sourceDir)) {return 0;}
|
|
90
114
|
|
|
91
115
|
if (fs.existsSync(destDir)) {
|
|
92
|
-
|
|
116
|
+
if (isSameDirectory(destDir, process.cwd())) {
|
|
117
|
+
clearDirectoryContents(destDir);
|
|
118
|
+
} else {
|
|
119
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
120
|
+
}
|
|
93
121
|
console.log(t('copy.cleared', destDir));
|
|
94
122
|
}
|
|
95
123
|
|
|
@@ -182,9 +210,11 @@ function createSymlink(sourceDir, destLink) {
|
|
|
182
210
|
* @param {string|null} activeToolName
|
|
183
211
|
* @param {string|null} activeProjectRoot
|
|
184
212
|
* @param {Array} envResults
|
|
213
|
+
* @param {object} [options]
|
|
214
|
+
* @param {boolean} [options.allowCurrentDir=false] - 未检测到活跃 AI 工具时,是否允许使用当前目录
|
|
185
215
|
* @returns {string} 目标根目录路径
|
|
186
216
|
*/
|
|
187
|
-
function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults) {
|
|
217
|
+
function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults, options = {}) {
|
|
188
218
|
const activeResult = envResults.find((r) => r.displayName === activeToolName);
|
|
189
219
|
const isWukong = activeResult && activeResult.dirName === '.real';
|
|
190
220
|
|
|
@@ -201,6 +231,10 @@ function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults) {
|
|
|
201
231
|
return process.cwd();
|
|
202
232
|
}
|
|
203
233
|
|
|
234
|
+
if (options.allowCurrentDir) {
|
|
235
|
+
return process.cwd();
|
|
236
|
+
}
|
|
237
|
+
|
|
204
238
|
// 未检测到活跃工具
|
|
205
239
|
warn(t('copy.no_ai_tool'));
|
|
206
240
|
envResults.forEach((r) => {
|
|
@@ -223,13 +257,13 @@ function copyItem(label, sourceDir, destDir, isForce) {
|
|
|
223
257
|
|
|
224
258
|
/**
|
|
225
259
|
* 执行 copy 命令主逻辑。
|
|
260
|
+
* @param {string[]} [args=process.argv.slice(3)] 命令参数
|
|
226
261
|
*/
|
|
227
|
-
function run() {
|
|
262
|
+
function run(args = process.argv.slice(3)) {
|
|
228
263
|
const { c, sep, banner, info, success, hint, label, fail: chalkFail, listItem } = require('./chalk');
|
|
229
264
|
|
|
230
265
|
banner(t('copy.title'), { stderr: false });
|
|
231
266
|
|
|
232
|
-
const args = process.argv.slice(3);
|
|
233
267
|
const isForce = args.includes('--force');
|
|
234
268
|
const wantsSkills = args.includes('-skills');
|
|
235
269
|
const wantsProject = args.includes('-project');
|
|
@@ -250,7 +284,9 @@ function run() {
|
|
|
250
284
|
const { activeToolName, activeProjectRoot, results: envResults } = detectEnvironment();
|
|
251
285
|
const activeEnvResult = envResults.find((r) => r.isActive);
|
|
252
286
|
const isWukong = activeEnvResult && activeEnvResult.dirName === '.real';
|
|
253
|
-
const destBase = resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults
|
|
287
|
+
const destBase = resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults, {
|
|
288
|
+
allowCurrentDir: isForce,
|
|
289
|
+
});
|
|
254
290
|
label('Target', destBase, { stderr: false });
|
|
255
291
|
if (isForce) {
|
|
256
292
|
warn(t('copy.force_mode'), false);
|
|
@@ -345,8 +381,8 @@ function run() {
|
|
|
345
381
|
}
|
|
346
382
|
|
|
347
383
|
// 4. 打印汇总
|
|
348
|
-
const copyCount = results.filter(r => r.type === 'copy').reduce((sum, r) => sum + r.count, 0);
|
|
349
|
-
const linkCount = results.filter(r => r.type === 'symlink').length;
|
|
384
|
+
const copyCount = results.filter((r) => r.type === 'copy').reduce((sum, r) => sum + r.count, 0);
|
|
385
|
+
const linkCount = results.filter((r) => r.type === 'symlink').length;
|
|
350
386
|
console.log('');
|
|
351
387
|
console.log(` ${sep()}`);
|
|
352
388
|
success(t('copy.done'), false);
|
|
@@ -369,4 +405,10 @@ function run() {
|
|
|
369
405
|
console.log(` ${sep()}\n`);
|
|
370
406
|
}
|
|
371
407
|
|
|
372
|
-
module.exports = {
|
|
408
|
+
module.exports = {
|
|
409
|
+
run,
|
|
410
|
+
_internal: {
|
|
411
|
+
forceCopyDir,
|
|
412
|
+
resolveDestBaseFromEnv,
|
|
413
|
+
},
|
|
414
|
+
};
|
package/lib/core/env-manager.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 优先级(高 → 低):
|
|
10
10
|
* 1. 环境变量 OPENYIDA_ENDPOINT
|
|
11
|
-
* 2.
|
|
12
|
-
* 3.
|
|
13
|
-
* 4.
|
|
11
|
+
* 2. cookieData.base_url(登录后实际跳转域名)
|
|
12
|
+
* 3. 环境变量 OPENYIDA_ENV 指定的环境配置
|
|
13
|
+
* 4. 当前激活的环境配置(openyida-envs.json current 字段)
|
|
14
14
|
* 5. 默认公有云 https://www.aliwork.com
|
|
15
15
|
*
|
|
16
16
|
* 导出函数:
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* migrateOldCookieFile() - 迁移旧版 cookies.json → cookies-public.json
|
|
22
22
|
* resolveEndpoint() - 解析最终 baseUrl(含完整优先级)
|
|
23
23
|
* resolveLoginUrl() - 解析最终登录 URL
|
|
24
|
+
* deriveBaseUrlFromLoginState() - 从 Cookie + 浏览器当前 URL 解析实际 baseUrl
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
'use strict';
|
|
@@ -329,6 +330,20 @@ function deriveBaseUrlFromCookies(cookies = [], fallbackUrl = DEFAULT_BASE_URL)
|
|
|
329
330
|
return fallbackOrigin;
|
|
330
331
|
}
|
|
331
332
|
|
|
333
|
+
function deriveBaseUrlFromLoginState(cookies = [], loginUrl = DEFAULT_LOGIN_URL, currentUrl = null) {
|
|
334
|
+
const fallbackBaseUrl = deriveBaseUrlFromUrl(loginUrl, currentUrl);
|
|
335
|
+
const cookieBaseUrl = deriveBaseUrlFromCookies(cookies, fallbackBaseUrl);
|
|
336
|
+
const currentOrigin = normalizeBaseUrl(currentUrl, null);
|
|
337
|
+
const currentHost = normalizeHostname(currentOrigin);
|
|
338
|
+
const loginHost = normalizeHostname(normalizeBaseUrl(loginUrl, null));
|
|
339
|
+
|
|
340
|
+
if (currentOrigin && isYidaServiceHost(currentHost) && currentHost !== loginHost) {
|
|
341
|
+
return currentOrigin;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return cookieBaseUrl;
|
|
345
|
+
}
|
|
346
|
+
|
|
332
347
|
function deriveBaseUrlFromUrl(fallbackBaseUrl, candidateUrl) {
|
|
333
348
|
let fallbackOrigin = normalizeBaseUrl(fallbackBaseUrl, DEFAULT_BASE_URL);
|
|
334
349
|
const fallbackHost = normalizeHostname(fallbackOrigin);
|
|
@@ -470,8 +485,8 @@ function migrateOldCookieFile(projectRoot) {
|
|
|
470
485
|
/**
|
|
471
486
|
* 解析最终的 baseUrl,按优先级:
|
|
472
487
|
* 1. OPENYIDA_ENDPOINT 环境变量
|
|
473
|
-
* 2.
|
|
474
|
-
* 3.
|
|
488
|
+
* 2. cookieData.base_url(登录后实际跳转域名)
|
|
489
|
+
* 3. 当前激活环境配置的 baseUrl
|
|
475
490
|
* 4. 默认公有云
|
|
476
491
|
* @param {object} [cookieData]
|
|
477
492
|
* @param {string} [projectRoot]
|
|
@@ -483,21 +498,13 @@ function resolveEndpoint(cookieData, projectRoot) {
|
|
|
483
498
|
return normalizeBaseUrl(process.env.OPENYIDA_ENDPOINT, DEFAULT_BASE_URL);
|
|
484
499
|
}
|
|
485
500
|
|
|
486
|
-
// 优先级 2
|
|
487
|
-
const { config: envConfig } = getCurrentEnvConfig(projectRoot);
|
|
488
|
-
// 只有当环境配置不是默认公有云,或者没有 cookieData 时才使用环境配置
|
|
489
|
-
// 这样可以兼容:用户没有配置多环境时,仍从 Cookie 中提取专属域名
|
|
490
|
-
const isDefaultPublic = envConfig.baseUrl === DEFAULT_BASE_URL;
|
|
491
|
-
if (!isDefaultPublic && envConfig.baseUrl) {
|
|
492
|
-
return normalizeBaseUrl(envConfig.baseUrl, DEFAULT_BASE_URL);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// 优先级 3:从 Cookie 历史提取(兼容专属域名)
|
|
501
|
+
// 优先级 2:登录缓存中记录的实际服务域名
|
|
496
502
|
if (cookieData && cookieData.base_url) {
|
|
497
503
|
return normalizeBaseUrl(cookieData.base_url, DEFAULT_BASE_URL);
|
|
498
504
|
}
|
|
499
505
|
|
|
500
|
-
// 优先级
|
|
506
|
+
// 优先级 3:当前激活环境配置
|
|
507
|
+
const { config: envConfig } = getCurrentEnvConfig(projectRoot);
|
|
501
508
|
if (envConfig.baseUrl) {
|
|
502
509
|
return normalizeBaseUrl(envConfig.baseUrl, DEFAULT_BASE_URL);
|
|
503
510
|
}
|
|
@@ -560,5 +567,6 @@ module.exports = {
|
|
|
560
567
|
inferLoginUrlForBaseUrl,
|
|
561
568
|
deriveBaseUrlFromDingtalkOAuthUrl,
|
|
562
569
|
deriveBaseUrlFromCookies,
|
|
570
|
+
deriveBaseUrlFromLoginState,
|
|
563
571
|
deriveBaseUrlFromUrl,
|
|
564
572
|
};
|
package/lib/core/locales/ar.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'التحقق من الرابط المختصر',
|
|
54
54
|
cmd_save_share: 'حفظ إعدادات الوصول العام / المشاركة',
|
|
55
55
|
cmd_get_page_config: 'استعلام إعدادات الوصول العام للصفحة',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'التقارير',
|
|
57
58
|
cmd_create_report: 'إنشاء تقرير Yida',
|
|
58
59
|
cmd_append_chart: 'إضافة رسم بياني إلى تقرير موجود',
|
|
@@ -451,6 +452,12 @@ module.exports = {
|
|
|
451
452
|
lint_jsx_extension: 'This .js file contains JSX; prefer .jsx for source files. The compiled output is still .js',
|
|
452
453
|
lint_event_direct_method: 'Event binding passes this.xxx directly and will lose this; use (e) => { this.xxx(e); }',
|
|
453
454
|
lint_event_bind_this: 'Event binding uses .bind(this); use (e) => { this.xxx(e); } instead',
|
|
455
|
+
lint_lifecycle_case: 'Detected lifecycle export {0}; Yida only recognizes {1} (case-sensitive)',
|
|
456
|
+
lint_react_lifecycle_method: 'Detected React lifecycle {0}; export {1} for Yida custom pages',
|
|
457
|
+
lint_event_lowercase: 'Detected lowercase event prop {0}; React/Yida will not bind it. Use {1}',
|
|
458
|
+
lint_event_call_result: 'Event binding invokes a function or is not an expression handler; use (e) => { self.xxx(e); }',
|
|
459
|
+
lint_event_noop_arrow: 'Event arrow function references a handler without calling it; use (e) => { self.xxx(e); }',
|
|
460
|
+
lint_button_missing_handler: 'Visible <button> has no onClick/onMouseDown/onKeyDown or disabled state; bind an event handler or use span/div for static labels',
|
|
454
461
|
lint_array_callback_function: '.{0}(function ...) may lose this in callbacks; use .{0}((item) => ...) instead',
|
|
455
462
|
lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
|
|
456
463
|
lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
|
package/lib/core/locales/de.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'Kurz-URL überprüfen',
|
|
54
54
|
cmd_save_share: 'Öffentlichen Zugang / Freigabe speichern',
|
|
55
55
|
cmd_get_page_config: 'Öffentliche Zugangskonfiguration abfragen',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'Berichte',
|
|
57
58
|
cmd_create_report: 'Yida-Bericht erstellen',
|
|
58
59
|
cmd_append_chart: 'Diagramm zu bestehendem Bericht hinzufügen',
|
|
@@ -451,6 +452,12 @@ module.exports = {
|
|
|
451
452
|
lint_jsx_extension: 'This .js file contains JSX; prefer .jsx for source files. The compiled output is still .js',
|
|
452
453
|
lint_event_direct_method: 'Event binding passes this.xxx directly and will lose this; use (e) => { this.xxx(e); }',
|
|
453
454
|
lint_event_bind_this: 'Event binding uses .bind(this); use (e) => { this.xxx(e); } instead',
|
|
455
|
+
lint_lifecycle_case: 'Detected lifecycle export {0}; Yida only recognizes {1} (case-sensitive)',
|
|
456
|
+
lint_react_lifecycle_method: 'Detected React lifecycle {0}; export {1} for Yida custom pages',
|
|
457
|
+
lint_event_lowercase: 'Detected lowercase event prop {0}; React/Yida will not bind it. Use {1}',
|
|
458
|
+
lint_event_call_result: 'Event binding invokes a function or is not an expression handler; use (e) => { self.xxx(e); }',
|
|
459
|
+
lint_event_noop_arrow: 'Event arrow function references a handler without calling it; use (e) => { self.xxx(e); }',
|
|
460
|
+
lint_button_missing_handler: 'Visible <button> has no onClick/onMouseDown/onKeyDown or disabled state; bind an event handler or use span/div for static labels',
|
|
454
461
|
lint_array_callback_function: '.{0}(function ...) may lose this in callbacks; use .{0}((item) => ...) instead',
|
|
455
462
|
lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
|
|
456
463
|
lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -56,6 +56,7 @@ module.exports = {
|
|
|
56
56
|
cmd_verify_url: 'Verify short URL',
|
|
57
57
|
cmd_save_share: 'Save public access / share config',
|
|
58
58
|
cmd_get_page_config: 'Query page public access config',
|
|
59
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
59
60
|
group_report: 'Reports',
|
|
60
61
|
cmd_create_report: 'Create a Yida report',
|
|
61
62
|
cmd_append_chart: 'Append chart to existing report',
|
|
@@ -949,6 +950,12 @@ Examples:
|
|
|
949
950
|
lint_jsx_extension: 'This .js file contains JSX; prefer .jsx for source files. The compiled output is still .js',
|
|
950
951
|
lint_event_direct_method: 'Event binding passes this.xxx directly and will lose this; use (e) => { this.xxx(e); }',
|
|
951
952
|
lint_event_bind_this: 'Event binding uses .bind(this); use (e) => { this.xxx(e); } instead',
|
|
953
|
+
lint_lifecycle_case: 'Detected lifecycle export {0}; Yida only recognizes {1} (case-sensitive)',
|
|
954
|
+
lint_react_lifecycle_method: 'Detected React lifecycle {0}; export {1} for Yida custom pages',
|
|
955
|
+
lint_event_lowercase: 'Detected lowercase event prop {0}; React/Yida will not bind it. Use {1}',
|
|
956
|
+
lint_event_call_result: 'Event binding invokes a function or is not an expression handler; use (e) => { self.xxx(e); }',
|
|
957
|
+
lint_event_noop_arrow: 'Event arrow function references a handler without calling it; use (e) => { self.xxx(e); }',
|
|
958
|
+
lint_button_missing_handler: 'Visible <button> has no onClick/onMouseDown/onKeyDown or disabled state; bind an event handler or use span/div for static labels',
|
|
952
959
|
lint_array_callback_function: '.{0}(function ...) may lose this in callbacks; use .{0}((item) => ...) instead',
|
|
953
960
|
lint_foreach_callback_function: '.forEach(function ...) may lose this in callbacks; prefer .forEach((item) => ...)',
|
|
954
961
|
lint_controlled_input: 'input uses controlled value mode. Yida pages should use defaultValue + onChange to update _customState',
|