yidaconnector 2026.6.11
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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yida.js +670 -0
- package/lib/app/form-navigation.js +58 -0
- package/lib/app/get-schema.js +538 -0
- package/lib/auth/auth.js +294 -0
- package/lib/auth/cdp-browser-login.js +390 -0
- package/lib/auth/codex-login.js +71 -0
- package/lib/auth/login.js +475 -0
- package/lib/auth/org.js +363 -0
- package/lib/auth/qr-login.js +1563 -0
- package/lib/core/chalk.js +384 -0
- package/lib/core/check-update.js +82 -0
- package/lib/core/cli-error.js +39 -0
- package/lib/core/command-manifest.js +106 -0
- package/lib/core/env-cmd.js +545 -0
- package/lib/core/env-manager.js +601 -0
- package/lib/core/env.js +287 -0
- package/lib/core/i18n.js +177 -0
- package/lib/core/locales/ar.js +805 -0
- package/lib/core/locales/de.js +805 -0
- package/lib/core/locales/en.js +1623 -0
- package/lib/core/locales/es.js +805 -0
- package/lib/core/locales/fr.js +805 -0
- package/lib/core/locales/hi.js +805 -0
- package/lib/core/locales/ja.js +1197 -0
- package/lib/core/locales/ko.js +807 -0
- package/lib/core/locales/pt.js +805 -0
- package/lib/core/locales/vi.js +805 -0
- package/lib/core/locales/zh-HK.js +1233 -0
- package/lib/core/locales/zh.js +1584 -0
- package/lib/core/query-data.js +781 -0
- package/lib/core/redact.js +100 -0
- package/lib/core/utils.js +799 -0
- package/lib/core/yida-client.js +117 -0
- package/package.json +94 -0
- package/project/config.json +4 -0
- package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
- package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
- package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
- package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
- package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
- package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-crm.md +463 -0
- package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/build-skills-package.js +406 -0
- package/scripts/check-syntax.js +59 -0
- package/scripts/demo-dws.sh +106 -0
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1566 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +115 -0
- package/scripts/generate-command-docs.js +109 -0
- package/scripts/nightly-smoke.js +134 -0
- package/scripts/postinstall.js +545 -0
- package/scripts/solution-center-runner.js +368 -0
- package/scripts/validate-ci.sh +50 -0
- package/scripts/validate-command-manifest.js +119 -0
- package/scripts/validate-package-size.js +78 -0
- package/scripts/validate-skills.js +247 -0
- package/scripts/validate-structure.js +66 -0
- package/yida-skills/SKILL.md +163 -0
- package/yida-skills/references/yida-api.md +1309 -0
- package/yida-skills/skills/large-file-write/SKILL.md +91 -0
- package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
- package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
- package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
- package/yida-skills/skills/yida-login/SKILL.md +159 -0
- package/yida-skills/skills/yida-logout/SKILL.md +67 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Execute YidaConnector build tasks generated by the DingTalk AI Solution Center.
|
|
4
|
+
*
|
|
5
|
+
* Default mode is dry-run. Use --execute to create real Yida resources.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { spawnSync } = require('child_process');
|
|
13
|
+
|
|
14
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const YIDA_BIN = path.join(ROOT, 'bin', 'yida.js');
|
|
16
|
+
const CACHE_DIR = path.join(ROOT, 'project', '.cache', 'solution-center-runner');
|
|
17
|
+
|
|
18
|
+
const SOURCE_APP_TYPE = 'APP_WXXZPD6QF8B2NNWGJG3J';
|
|
19
|
+
const DEMO_FORM_UUID = 'FORM-88DD6EE845464D43A2C18A85B895245AK576';
|
|
20
|
+
|
|
21
|
+
const FIELDS = {
|
|
22
|
+
demoName: 'textField_ajhc2hnzf',
|
|
23
|
+
customerName: 'textField_ajhc3eszd',
|
|
24
|
+
solution: 'textField_ajhc4ffn8',
|
|
25
|
+
status: 'selectField_ajhd60inc',
|
|
26
|
+
appType: 'textField_ajhd7khim',
|
|
27
|
+
pageUrl: 'textField_ajhd88lv6',
|
|
28
|
+
duration: 'numberField_ajhd9sef0',
|
|
29
|
+
feedback: 'textareaField_ajhdcpp4h',
|
|
30
|
+
buildSpec: 'textareaField_bx6z13bfq',
|
|
31
|
+
buildPrompt: 'textareaField_bx6z2j4v4',
|
|
32
|
+
tasks: 'tableField_ajhddqugc',
|
|
33
|
+
taskName: 'textField_ajhde8pga',
|
|
34
|
+
taskStatus: 'selectField_ajhdfogqg',
|
|
35
|
+
taskOutput: 'textareaField_ajhdgsmj5',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function printUsage() {
|
|
39
|
+
console.log(`Usage:
|
|
40
|
+
node scripts/solution-center-runner.js [--dry-run]
|
|
41
|
+
node scripts/solution-center-runner.js --execute [--limit 1] [--inst-id FINST-xxx]
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--dry-run Preview pending tasks without creating resources (default)
|
|
45
|
+
--execute Create Yida app/forms/page and update source Demo record
|
|
46
|
+
--limit <n> Max tasks to process, default 1
|
|
47
|
+
--inst-id <id> Process one specific Demo instance
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseArgs(argv) {
|
|
52
|
+
const options = {
|
|
53
|
+
execute: false,
|
|
54
|
+
limit: 1,
|
|
55
|
+
instId: '',
|
|
56
|
+
help: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
60
|
+
const arg = argv[index];
|
|
61
|
+
if (arg === '--help' || arg === '-h') {
|
|
62
|
+
options.help = true;
|
|
63
|
+
} else if (arg === '--execute') {
|
|
64
|
+
options.execute = true;
|
|
65
|
+
} else if (arg === '--dry-run') {
|
|
66
|
+
options.execute = false;
|
|
67
|
+
} else if (arg === '--limit' && argv[index + 1]) {
|
|
68
|
+
options.limit = Math.max(1, Number.parseInt(argv[++index], 10) || 1);
|
|
69
|
+
} else if (arg === '--inst-id' && argv[index + 1]) {
|
|
70
|
+
options.instId = argv[++index];
|
|
71
|
+
} else {
|
|
72
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return options;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stripAnsi(value) {
|
|
80
|
+
const escapeChar = String.fromCharCode(27);
|
|
81
|
+
return String(value || '').replace(new RegExp(`${escapeChar}\\[[0-9;]*m`, 'g'), '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseLastJson(output) {
|
|
85
|
+
const text = stripAnsi(output).trim();
|
|
86
|
+
const lines = text.split(/\r?\n/);
|
|
87
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
88
|
+
const line = lines[index].trim();
|
|
89
|
+
if (line !== '{' && !line.startsWith('{"')) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(lines.slice(index).join('\n'));
|
|
94
|
+
} catch {
|
|
95
|
+
// keep scanning for the next JSON-looking line
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw new Error('Command did not print a JSON object');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function runYida(args, options = {}) {
|
|
102
|
+
const result = spawnSync(process.execPath, [YIDA_BIN].concat(args), {
|
|
103
|
+
cwd: ROOT,
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
106
|
+
});
|
|
107
|
+
const output = `${result.stdout || ''}${result.stderr || ''}`;
|
|
108
|
+
if (options.echo) {
|
|
109
|
+
process.stdout.write(output);
|
|
110
|
+
}
|
|
111
|
+
if (result.status !== 0) {
|
|
112
|
+
throw new Error(stripAnsi(output).trim() || `yidaconnector ${args.join(' ')} failed`);
|
|
113
|
+
}
|
|
114
|
+
if (options.json) {
|
|
115
|
+
try {
|
|
116
|
+
return parseLastJson(result.stdout || '');
|
|
117
|
+
} catch {
|
|
118
|
+
return parseLastJson(output);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return output;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function field(record, fieldId) {
|
|
125
|
+
const data = record.formData || {};
|
|
126
|
+
if (data[fieldId] !== undefined && data[fieldId] !== null) {
|
|
127
|
+
return data[fieldId];
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function queryDemoInstances(options) {
|
|
133
|
+
const args = ['data', 'query', 'form', SOURCE_APP_TYPE, DEMO_FORM_UUID, '--page', '1', '--size', '100'];
|
|
134
|
+
if (options.instId) {
|
|
135
|
+
args.push('--inst-id', options.instId);
|
|
136
|
+
}
|
|
137
|
+
const result = runYida(args, { json: true });
|
|
138
|
+
return Array.isArray(result.data) ? result.data : [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseBuildSpec(record) {
|
|
142
|
+
const raw = field(record, FIELDS.buildSpec);
|
|
143
|
+
if (!raw) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return JSON.parse(raw);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function pickPending(records, options) {
|
|
150
|
+
const candidates = [];
|
|
151
|
+
records.forEach((record) => {
|
|
152
|
+
let spec = null;
|
|
153
|
+
try {
|
|
154
|
+
spec = parseBuildSpec(record);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
candidates.push({ record, spec: null, skipReason: `buildSpec JSON 解析失败: ${err.message}` });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!spec) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const status = field(record, FIELDS.status);
|
|
163
|
+
const targetAppType = field(record, FIELDS.appType);
|
|
164
|
+
if (options.instId || (!targetAppType && status !== '已创建')) {
|
|
165
|
+
candidates.push({ record, spec, skipReason: '' });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return candidates.slice(0, options.limit);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ensureCacheDir() {
|
|
172
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function sanitizeFileName(value) {
|
|
176
|
+
return String(value || 'form').replace(/[^\w\u4e00-\u9fa5-]+/g, '_').slice(0, 40);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function fieldSetForForm(formName) {
|
|
180
|
+
const subject = String(formName || '业务').replace(/表$/, '');
|
|
181
|
+
return [
|
|
182
|
+
{ type: 'TextField', label: `${subject}名称`, required: true },
|
|
183
|
+
{ type: 'TextareaField', label: '业务说明' },
|
|
184
|
+
{ type: 'SelectField', label: '状态', options: ['未开始', '进行中', '已完成', '异常'] },
|
|
185
|
+
{ type: 'EmployeeField', label: '负责人' },
|
|
186
|
+
{ type: 'DateField', label: '发生时间' },
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function writeFieldsFile(taskId, formName) {
|
|
191
|
+
ensureCacheDir();
|
|
192
|
+
const filePath = path.join(CACHE_DIR, `${taskId}-${sanitizeFileName(formName)}.fields.json`);
|
|
193
|
+
fs.writeFileSync(filePath, JSON.stringify(fieldSetForForm(formName), null, 2), 'utf8');
|
|
194
|
+
return filePath;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function updateDemoRecord(formInstId, patch) {
|
|
198
|
+
runYida([
|
|
199
|
+
'data', 'update', 'form', SOURCE_APP_TYPE,
|
|
200
|
+
'--inst-id', formInstId,
|
|
201
|
+
'--data-json', JSON.stringify(patch),
|
|
202
|
+
'--use-latest-version', 'y',
|
|
203
|
+
], { json: true });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildTaskRows(spec, status, extraOutput) {
|
|
207
|
+
return (spec.tasks || []).map((task) => {
|
|
208
|
+
const row = {};
|
|
209
|
+
row[FIELDS.taskName] = String(task.name || '').slice(0, 190);
|
|
210
|
+
row[FIELDS.taskStatus] = status;
|
|
211
|
+
row[FIELDS.taskOutput] = String(extraOutput || task.output || '').slice(0, 190);
|
|
212
|
+
return row;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function updateSourceSuccess(record, spec, result, elapsedMinutes) {
|
|
217
|
+
const patch = {};
|
|
218
|
+
patch[FIELDS.status] = '已创建';
|
|
219
|
+
patch[FIELDS.appType] = result.appType;
|
|
220
|
+
patch[FIELDS.pageUrl] = result.pageUrl || result.appUrl || '';
|
|
221
|
+
patch[FIELDS.duration] = elapsedMinutes;
|
|
222
|
+
patch[FIELDS.feedback] = [
|
|
223
|
+
'YidaConnector runner 已完成搭建。',
|
|
224
|
+
`创建应用: ${result.appType}`,
|
|
225
|
+
`表单: ${result.forms.map((item) => item.title).join('、')}`,
|
|
226
|
+
`页面: ${result.pageName || 'SA 演示首页'}`,
|
|
227
|
+
].join('\n');
|
|
228
|
+
patch[FIELDS.tasks] = buildTaskRows(spec, '已完成');
|
|
229
|
+
updateDemoRecord(record.formInstId, patch);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function updateSourceFailure(record, spec, error) {
|
|
233
|
+
const patch = {};
|
|
234
|
+
patch[FIELDS.status] = '需改版';
|
|
235
|
+
patch[FIELDS.feedback] = `YidaConnector runner 执行失败:${String(error.message || error).slice(0, 500)}`;
|
|
236
|
+
patch[FIELDS.tasks] = buildTaskRows(spec, '失败', patch[FIELDS.feedback]);
|
|
237
|
+
updateDemoRecord(record.formInstId, patch);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function createYidaApp(spec) {
|
|
241
|
+
const appName = spec.appName || `${spec.customerName || '客户'} Demo`;
|
|
242
|
+
const result = runYida([
|
|
243
|
+
'create-app',
|
|
244
|
+
'--name', appName,
|
|
245
|
+
'--desc', `${spec.industryName || ''}${spec.solutionTitle || ''} YidaConnector Demo`,
|
|
246
|
+
'--theme', 'greyBlue',
|
|
247
|
+
'--nav-theme', 'dark',
|
|
248
|
+
'--layout', 'slide',
|
|
249
|
+
], { json: true });
|
|
250
|
+
if (!result.success || !result.appType) {
|
|
251
|
+
throw new Error(`创建应用失败: ${JSON.stringify(result)}`);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createYidaForms(appType, spec) {
|
|
257
|
+
const forms = [];
|
|
258
|
+
(spec.forms || []).slice(0, 8).forEach((formName, index) => {
|
|
259
|
+
const fieldsFile = writeFieldsFile(`${Date.now()}-${index}`, formName);
|
|
260
|
+
const result = runYida([
|
|
261
|
+
'create-form', 'create', appType, formName, fieldsFile,
|
|
262
|
+
'--layout', 'double',
|
|
263
|
+
'--theme', 'compact',
|
|
264
|
+
'--label-align', 'top',
|
|
265
|
+
'--no-open',
|
|
266
|
+
], { json: true });
|
|
267
|
+
if (!result.success || !result.formUuid) {
|
|
268
|
+
throw new Error(`创建表单失败 ${formName}: ${JSON.stringify(result)}`);
|
|
269
|
+
}
|
|
270
|
+
forms.push({ title: formName, formUuid: result.formUuid, url: result.url || '' });
|
|
271
|
+
});
|
|
272
|
+
return forms;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function createYidaPage(appType, spec) {
|
|
276
|
+
const pageName = (spec.pages && spec.pages[0]) || 'SA 演示首页';
|
|
277
|
+
const result = runYida([
|
|
278
|
+
'create-page', appType, pageName,
|
|
279
|
+
'--mode', 'dashboard',
|
|
280
|
+
'--no-open',
|
|
281
|
+
], { json: true });
|
|
282
|
+
if (!result.success || !result.pageId) {
|
|
283
|
+
throw new Error(`创建页面失败: ${JSON.stringify(result)}`);
|
|
284
|
+
}
|
|
285
|
+
return { pageName, pageId: result.pageId, url: result.url || result.workbenchUrl || '' };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function executeBuild(record, spec) {
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
const app = createYidaApp(spec);
|
|
291
|
+
const forms = createYidaForms(app.appType, spec);
|
|
292
|
+
const page = createYidaPage(app.appType, spec);
|
|
293
|
+
const elapsedMinutes = Math.max(1, Math.round((Date.now() - start) / 60000));
|
|
294
|
+
const result = {
|
|
295
|
+
appType: app.appType,
|
|
296
|
+
appUrl: app.url || '',
|
|
297
|
+
forms,
|
|
298
|
+
pageName: page.pageName,
|
|
299
|
+
pageId: page.pageId,
|
|
300
|
+
pageUrl: page.url,
|
|
301
|
+
};
|
|
302
|
+
updateSourceSuccess(record, spec, result, elapsedMinutes);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function printTaskPreview(task) {
|
|
307
|
+
const record = task.record;
|
|
308
|
+
const spec = task.spec;
|
|
309
|
+
if (task.skipReason) {
|
|
310
|
+
console.log(`- ${record.formInstId}: 跳过,${task.skipReason}`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
console.log(`- ${record.formInstId}`);
|
|
314
|
+
console.log(` 客户: ${field(record, FIELDS.customerName) || spec.customerName || '-'}`);
|
|
315
|
+
console.log(` Demo: ${field(record, FIELDS.demoName) || spec.appName || '-'}`);
|
|
316
|
+
console.log(` 方案: ${spec.solutionTitle || '-'}`);
|
|
317
|
+
console.log(` 表单: ${(spec.forms || []).join('、') || '-'}`);
|
|
318
|
+
console.log(` 页面: ${(spec.pages || []).join('、') || '-'}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function main() {
|
|
322
|
+
const options = parseArgs(process.argv.slice(2));
|
|
323
|
+
if (options.help) {
|
|
324
|
+
printUsage();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const records = queryDemoInstances(options);
|
|
329
|
+
const tasks = pickPending(records, options);
|
|
330
|
+
if (tasks.length === 0) {
|
|
331
|
+
console.log('未找到可执行的 YidaConnector 搭建任务。请先在解决方案中心点击「YidaConnector 搭建」生成任务。');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(options.execute ? '将执行以下 YidaConnector 搭建任务:' : 'Dry run: 将预览以下 YidaConnector 搭建任务:');
|
|
336
|
+
tasks.forEach(printTaskPreview);
|
|
337
|
+
|
|
338
|
+
if (!options.execute) {
|
|
339
|
+
console.log('\n未创建任何资源。确认无误后执行:node scripts/solution-center-runner.js --execute --limit 1');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
tasks.forEach((task) => {
|
|
344
|
+
if (task.skipReason) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
console.log(`\n开始执行 ${task.record.formInstId}...`);
|
|
349
|
+
const result = executeBuild(task.record, task.spec);
|
|
350
|
+
console.log(`完成: ${result.appType} ${result.pageUrl || result.appUrl}`);
|
|
351
|
+
} catch (err) {
|
|
352
|
+
console.error(`执行失败: ${err.message}`);
|
|
353
|
+
try {
|
|
354
|
+
updateSourceFailure(task.record, task.spec || {}, err);
|
|
355
|
+
} catch (updateErr) {
|
|
356
|
+
console.error(`回填失败状态也失败了: ${updateErr.message}`);
|
|
357
|
+
}
|
|
358
|
+
process.exitCode = 1;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
main();
|
|
365
|
+
} catch (err) {
|
|
366
|
+
console.error(err.message);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
cd "$(dirname "$0")/.."
|
|
5
|
+
|
|
6
|
+
echo "=== Step 1: Install dependencies ==="
|
|
7
|
+
npm ci --ignore-scripts
|
|
8
|
+
|
|
9
|
+
echo ""
|
|
10
|
+
echo "=== Step 2: Validate project structure ==="
|
|
11
|
+
npm run check:structure
|
|
12
|
+
|
|
13
|
+
echo ""
|
|
14
|
+
echo "=== Step 3: Validate skills ==="
|
|
15
|
+
npm run check:skills
|
|
16
|
+
|
|
17
|
+
echo ""
|
|
18
|
+
echo "=== Step 4: Validate command manifest ==="
|
|
19
|
+
npm run check:commands
|
|
20
|
+
|
|
21
|
+
echo ""
|
|
22
|
+
echo "=== Step 5: Validate generated command docs ==="
|
|
23
|
+
npm run check:docs
|
|
24
|
+
|
|
25
|
+
echo ""
|
|
26
|
+
echo "=== Step 6: Build Wukong skills package ==="
|
|
27
|
+
npm run build:skills
|
|
28
|
+
|
|
29
|
+
echo ""
|
|
30
|
+
echo "=== Step 7: Check JavaScript syntax ==="
|
|
31
|
+
npm run check:syntax
|
|
32
|
+
|
|
33
|
+
echo ""
|
|
34
|
+
echo "=== Step 8: Run lint ==="
|
|
35
|
+
npm run lint
|
|
36
|
+
|
|
37
|
+
echo ""
|
|
38
|
+
echo "=== Step 9: Run tests ==="
|
|
39
|
+
npm run test:unit -- --runInBand
|
|
40
|
+
|
|
41
|
+
echo ""
|
|
42
|
+
echo "=== Step 10: Validate npm package size budget ==="
|
|
43
|
+
npm run check:package-size
|
|
44
|
+
|
|
45
|
+
echo ""
|
|
46
|
+
echo "=== Step 11: Validate npm package contents ==="
|
|
47
|
+
npm run check:package
|
|
48
|
+
|
|
49
|
+
echo ""
|
|
50
|
+
echo "=== All checks passed! ==="
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { COMMAND_GROUPS, flattenCommandManifest } = require('../lib/core/command-manifest');
|
|
8
|
+
|
|
9
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
const ROUTER_FILE = path.join(ROOT, 'bin/yida.js');
|
|
11
|
+
const README_FILE = path.join(ROOT, 'README.md');
|
|
12
|
+
|
|
13
|
+
const errors = [];
|
|
14
|
+
|
|
15
|
+
function toRelative(filePath) {
|
|
16
|
+
return path.relative(ROOT, filePath).split(path.sep).join('/');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function escapeRegExp(value) {
|
|
20
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function commandRoot(value) {
|
|
24
|
+
return String(value || '').trim().split(/\s+/)[0];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectRouterCases() {
|
|
28
|
+
const text = fs.readFileSync(ROUTER_FILE, 'utf8');
|
|
29
|
+
const cases = new Set();
|
|
30
|
+
const pattern = /case '([^']+)'\s*:/g;
|
|
31
|
+
let match = pattern.exec(text);
|
|
32
|
+
while (match) {
|
|
33
|
+
cases.add(match[1]);
|
|
34
|
+
match = pattern.exec(text);
|
|
35
|
+
}
|
|
36
|
+
return cases;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function collectManifestRoots(commands) {
|
|
40
|
+
const roots = new Set();
|
|
41
|
+
for (const entry of commands) {
|
|
42
|
+
roots.add(entry.path[0]);
|
|
43
|
+
for (const alias of entry.aliases || []) {
|
|
44
|
+
roots.add(commandRoot(alias));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return roots;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function validateUniqueIds(commands) {
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const entry of commands) {
|
|
53
|
+
if (seen.has(entry.id)) {
|
|
54
|
+
errors.push(`Duplicate command manifest id: ${entry.id}`);
|
|
55
|
+
}
|
|
56
|
+
seen.add(entry.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateRouterCoverage(commands) {
|
|
61
|
+
const routerCases = collectRouterCases();
|
|
62
|
+
const manifestRoots = collectManifestRoots(commands);
|
|
63
|
+
|
|
64
|
+
for (const routerCase of [...routerCases].sort()) {
|
|
65
|
+
if (!manifestRoots.has(routerCase)) {
|
|
66
|
+
errors.push(`${toRelative(ROUTER_FILE)}: route case "${routerCase}" is missing from command manifest roots or aliases`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const root of [...manifestRoots].sort()) {
|
|
71
|
+
if (!routerCases.has(root)) {
|
|
72
|
+
errors.push(`${toRelative(ROUTER_FILE)}: command manifest root/alias "${root}" has no route case`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateReadmeCoverage(commands) {
|
|
78
|
+
const readme = fs.readFileSync(README_FILE, 'utf8');
|
|
79
|
+
const visibleRoots = new Set(commands.filter(entry => !entry.hidden).map(entry => entry.path[0]));
|
|
80
|
+
|
|
81
|
+
for (const root of [...visibleRoots].sort()) {
|
|
82
|
+
const pattern = new RegExp(`yidaconnector\\s+${escapeRegExp(root)}(\\s|\`|$)`);
|
|
83
|
+
if (!pattern.test(readme)) {
|
|
84
|
+
errors.push(`${toRelative(README_FILE)}: visible command root "${root}" is missing from CLI reference`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateGroupReferences() {
|
|
90
|
+
const commands = new Set(flattenCommandManifest().map(entry => entry.id));
|
|
91
|
+
for (const group of COMMAND_GROUPS) {
|
|
92
|
+
for (const commandId of group.commands.map(entry => entry.id)) {
|
|
93
|
+
if (!commands.has(commandId)) {
|
|
94
|
+
errors.push(`Command group "${group.id}" references unknown command id "${commandId}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function run() {
|
|
101
|
+
const commands = flattenCommandManifest();
|
|
102
|
+
|
|
103
|
+
validateUniqueIds(commands);
|
|
104
|
+
validateGroupReferences();
|
|
105
|
+
validateRouterCoverage(commands);
|
|
106
|
+
validateReadmeCoverage(commands);
|
|
107
|
+
|
|
108
|
+
if (errors.length > 0) {
|
|
109
|
+
console.error('Command manifest validation failed:');
|
|
110
|
+
for (const error of errors) {
|
|
111
|
+
console.error(' error ' + error);
|
|
112
|
+
}
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(`Command manifest OK: ${commands.length} entries aligned with router and README`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
run();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const MAX_TARBALL_BYTES = 1536 * 1024;
|
|
8
|
+
const MAX_UNPACKED_BYTES = 4608 * 1024;
|
|
9
|
+
const MAX_ENTRY_COUNT = 300;
|
|
10
|
+
const MAX_SINGLE_FILE_BYTES = 512 * 1024;
|
|
11
|
+
|
|
12
|
+
function formatBytes(bytes) {
|
|
13
|
+
if (bytes < 1024) {
|
|
14
|
+
return `${bytes} B`;
|
|
15
|
+
}
|
|
16
|
+
if (bytes < 1024 * 1024) {
|
|
17
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
18
|
+
}
|
|
19
|
+
return `${(bytes / 1024 / 1024).toFixed(2)} MiB`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function fail(message) {
|
|
23
|
+
console.error('Package size validation failed:');
|
|
24
|
+
console.error(' error ' + message);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runNpmPackDryRun() {
|
|
29
|
+
const result = spawnSync('npm', ['pack', '--dry-run', '--json'], {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
stdio: 'pipe',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
process.stderr.write(result.stderr || result.stdout);
|
|
36
|
+
process.exit(result.status || 1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(result.stdout);
|
|
41
|
+
return parsed[0];
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
process.stderr.write(result.stdout);
|
|
44
|
+
fail('npm pack --dry-run --json did not return valid JSON');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateLargestFiles(files) {
|
|
49
|
+
const sorted = [...files].sort((a, b) => b.size - a.size);
|
|
50
|
+
const oversized = sorted.find(file => file.size > MAX_SINGLE_FILE_BYTES);
|
|
51
|
+
if (oversized) {
|
|
52
|
+
fail(`${oversized.path} is ${formatBytes(oversized.size)}, above ${formatBytes(MAX_SINGLE_FILE_BYTES)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return sorted.slice(0, 5).map(file => `${file.path} (${formatBytes(file.size)})`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function run() {
|
|
59
|
+
const pack = runNpmPackDryRun();
|
|
60
|
+
const largestFiles = validateLargestFiles(pack.files || []);
|
|
61
|
+
|
|
62
|
+
if (pack.size > MAX_TARBALL_BYTES) {
|
|
63
|
+
fail(`tarball is ${formatBytes(pack.size)}, above ${formatBytes(MAX_TARBALL_BYTES)}`);
|
|
64
|
+
}
|
|
65
|
+
if (pack.unpackedSize > MAX_UNPACKED_BYTES) {
|
|
66
|
+
fail(`unpacked package is ${formatBytes(pack.unpackedSize)}, above ${formatBytes(MAX_UNPACKED_BYTES)}`);
|
|
67
|
+
}
|
|
68
|
+
if (pack.entryCount > MAX_ENTRY_COUNT) {
|
|
69
|
+
fail(`package has ${pack.entryCount} files, above ${MAX_ENTRY_COUNT}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(
|
|
73
|
+
`Package size OK: ${formatBytes(pack.size)} tarball, ${formatBytes(pack.unpackedSize)} unpacked, ${pack.entryCount} files`
|
|
74
|
+
);
|
|
75
|
+
console.log('Largest files: ' + largestFiles.join(', '));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
run();
|