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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +383 -0
  3. package/bin/yida.js +670 -0
  4. package/lib/app/form-navigation.js +58 -0
  5. package/lib/app/get-schema.js +538 -0
  6. package/lib/auth/auth.js +294 -0
  7. package/lib/auth/cdp-browser-login.js +390 -0
  8. package/lib/auth/codex-login.js +71 -0
  9. package/lib/auth/login.js +475 -0
  10. package/lib/auth/org.js +363 -0
  11. package/lib/auth/qr-login.js +1563 -0
  12. package/lib/core/chalk.js +384 -0
  13. package/lib/core/check-update.js +82 -0
  14. package/lib/core/cli-error.js +39 -0
  15. package/lib/core/command-manifest.js +106 -0
  16. package/lib/core/env-cmd.js +545 -0
  17. package/lib/core/env-manager.js +601 -0
  18. package/lib/core/env.js +287 -0
  19. package/lib/core/i18n.js +177 -0
  20. package/lib/core/locales/ar.js +805 -0
  21. package/lib/core/locales/de.js +805 -0
  22. package/lib/core/locales/en.js +1623 -0
  23. package/lib/core/locales/es.js +805 -0
  24. package/lib/core/locales/fr.js +805 -0
  25. package/lib/core/locales/hi.js +805 -0
  26. package/lib/core/locales/ja.js +1197 -0
  27. package/lib/core/locales/ko.js +807 -0
  28. package/lib/core/locales/pt.js +805 -0
  29. package/lib/core/locales/vi.js +805 -0
  30. package/lib/core/locales/zh-HK.js +1233 -0
  31. package/lib/core/locales/zh.js +1584 -0
  32. package/lib/core/query-data.js +781 -0
  33. package/lib/core/redact.js +100 -0
  34. package/lib/core/utils.js +799 -0
  35. package/lib/core/yida-client.js +117 -0
  36. package/package.json +94 -0
  37. package/project/config.json +4 -0
  38. package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
  39. package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
  40. package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
  41. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
  42. package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
  43. package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
  44. package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
  45. package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
  46. package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
  47. package/project/prd/demo-birthday-game.md +39 -0
  48. package/project/prd/demo-crm.md +463 -0
  49. package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
  50. package/project/prd/demo-future-vision-2026.md +78 -0
  51. package/project/prd/demo-salary-calculator.md +101 -0
  52. package/scripts/build-skills-package.js +406 -0
  53. package/scripts/check-syntax.js +59 -0
  54. package/scripts/demo-dws.sh +106 -0
  55. package/scripts/e2e-real/cleanup.js +67 -0
  56. package/scripts/e2e-real/fixtures/form-fields.json +18 -0
  57. package/scripts/e2e-real/full-runner.js +1566 -0
  58. package/scripts/e2e-real/runner.js +293 -0
  59. package/scripts/e2e-real/skill-coverage.js +115 -0
  60. package/scripts/generate-command-docs.js +109 -0
  61. package/scripts/nightly-smoke.js +134 -0
  62. package/scripts/postinstall.js +545 -0
  63. package/scripts/solution-center-runner.js +368 -0
  64. package/scripts/validate-ci.sh +50 -0
  65. package/scripts/validate-command-manifest.js +119 -0
  66. package/scripts/validate-package-size.js +78 -0
  67. package/scripts/validate-skills.js +247 -0
  68. package/scripts/validate-structure.js +66 -0
  69. package/yida-skills/SKILL.md +163 -0
  70. package/yida-skills/references/yida-api.md +1309 -0
  71. package/yida-skills/skills/large-file-write/SKILL.md +91 -0
  72. package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
  73. package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
  74. package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
  75. package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
  76. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
  77. package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
  78. package/yida-skills/skills/yida-login/SKILL.md +159 -0
  79. package/yida-skills/skills/yida-logout/SKILL.md +67 -0
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const ROOT = path.resolve(__dirname, '..', '..');
10
+ const BIN = path.join(ROOT, 'bin', 'yida.js');
11
+ const DEFAULT_REGISTRY_DIR = path.join(ROOT, 'project', '.cache', 'e2e-real');
12
+ const DEFAULT_FIELDS_FILE = path.join(__dirname, 'fixtures', 'form-fields.json');
13
+ const DEFAULT_PAGE_SOURCE = path.join(ROOT, 'project', 'pages', 'src', 'demo-compat-smoke.oyd.jsx');
14
+
15
+ function nowStamp(date = new Date()) {
16
+ return date.toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
17
+ }
18
+
19
+ function getConfig(env = process.env, date = new Date()) {
20
+ const prefix = env.YIDACONNECTOR_E2E_PREFIX || `OY_E2E_${nowStamp(date)}`;
21
+ return {
22
+ enabled: env.YIDACONNECTOR_E2E === '1',
23
+ missing: env.YIDACONNECTOR_E2E === '1' ? [] : ['YIDACONNECTOR_E2E=1'],
24
+ prefix,
25
+ appName: env.YIDACONNECTOR_E2E_APP_NAME || `${prefix}_App`,
26
+ formName: env.YIDACONNECTOR_E2E_FORM_NAME || `${prefix}_Form`,
27
+ pageName: env.YIDACONNECTOR_E2E_PAGE_NAME || `${prefix}_Page`,
28
+ fieldsFile: env.YIDACONNECTOR_E2E_FIELDS_FILE || DEFAULT_FIELDS_FILE,
29
+ pageSource: env.YIDACONNECTOR_E2E_PAGE_SOURCE || DEFAULT_PAGE_SOURCE,
30
+ registryDir: env.YIDACONNECTOR_E2E_REGISTRY_DIR || DEFAULT_REGISTRY_DIR,
31
+ baseUrl: env.YIDACONNECTOR_E2E_BASE_URL,
32
+ cookiesBase64: env.YIDACONNECTOR_E2E_COOKIES_BASE64,
33
+ corpId: env.YIDACONNECTOR_E2E_CORP_ID,
34
+ skipPublish: env.YIDACONNECTOR_E2E_SKIP_PUBLISH === '1',
35
+ };
36
+ }
37
+
38
+ function ensureEnabled(config) {
39
+ if (!config.enabled) {
40
+ console.log(`Skipping real E2E; missing: ${config.missing.join(', ')}`);
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function decodeCookieData(config) {
47
+ if (!config.cookiesBase64) {return null;}
48
+ const raw = Buffer.from(config.cookiesBase64, 'base64').toString('utf8');
49
+ const parsed = JSON.parse(raw);
50
+ const cookieData = Array.isArray(parsed) ? { cookies: parsed } : parsed;
51
+ if (!Array.isArray(cookieData.cookies) || cookieData.cookies.length === 0) {
52
+ throw new Error('YIDACONNECTOR_E2E_COOKIES_BASE64 must decode to a cookie array or an object with cookies');
53
+ }
54
+ cookieData.base_url = config.baseUrl || cookieData.base_url || 'https://www.aliwork.com';
55
+ return cookieData;
56
+ }
57
+
58
+ function writeCookieCache(cookieData) {
59
+ if (!cookieData) {return;}
60
+ const cacheDir = path.join(ROOT, 'project', '.cache');
61
+ fs.mkdirSync(cacheDir, { recursive: true });
62
+ fs.writeFileSync(
63
+ path.join(cacheDir, 'cookies-public.json'),
64
+ JSON.stringify(cookieData, null, 2),
65
+ 'utf8'
66
+ );
67
+ }
68
+
69
+ function createRegistry(config) {
70
+ fs.mkdirSync(config.registryDir, { recursive: true });
71
+ const registry = {
72
+ runId: config.prefix,
73
+ startedAt: new Date().toISOString(),
74
+ status: 'running',
75
+ targetCorpId: config.corpId || null,
76
+ resources: [],
77
+ commands: [],
78
+ };
79
+ const registryPath = path.join(config.registryDir, `${config.prefix}.json`);
80
+ writeRegistry(registryPath, registry);
81
+ return { registry, registryPath };
82
+ }
83
+
84
+ function writeRegistry(registryPath, registry) {
85
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true });
86
+ fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`, 'utf8');
87
+ }
88
+
89
+ function addResource(registry, registryPath, resource) {
90
+ registry.resources.push({
91
+ createdAt: new Date().toISOString(),
92
+ ...resource,
93
+ });
94
+ writeRegistry(registryPath, registry);
95
+ }
96
+
97
+ function extractJsonObjects(output) {
98
+ const text = output || '';
99
+ const results = [];
100
+ for (let index = 0; index < text.length; index += 1) {
101
+ if (text[index] !== '{') {continue;}
102
+ let depth = 0;
103
+ let inString = false;
104
+ let escaped = false;
105
+ for (let cursor = index; cursor < text.length; cursor += 1) {
106
+ const char = text[cursor];
107
+ if (inString) {
108
+ if (escaped) {
109
+ escaped = false;
110
+ } else if (char === '\\') {
111
+ escaped = true;
112
+ } else if (char === '"') {
113
+ inString = false;
114
+ }
115
+ continue;
116
+ }
117
+ if (char === '"') {
118
+ inString = true;
119
+ } else if (char === '{') {
120
+ depth += 1;
121
+ } else if (char === '}') {
122
+ depth -= 1;
123
+ if (depth === 0) {
124
+ const candidate = text.slice(index, cursor + 1);
125
+ try {
126
+ results.push(JSON.parse(candidate));
127
+ } catch {
128
+ // Keep scanning; CLI output may contain braces in non-JSON text.
129
+ }
130
+ index = cursor;
131
+ break;
132
+ }
133
+ }
134
+ }
135
+ }
136
+ return results;
137
+ }
138
+
139
+ function parseLastJson(output) {
140
+ const parsed = extractJsonObjects(output);
141
+ return parsed.length > 0 ? parsed[parsed.length - 1] : null;
142
+ }
143
+
144
+ function runCli(args, env = process.env) {
145
+ console.log(`Running: yidaconnector ${args.join(' ')}`);
146
+ const result = spawnSync(process.execPath, [BIN, ...args], {
147
+ cwd: ROOT,
148
+ encoding: 'utf8',
149
+ env: {
150
+ ...env,
151
+ YIDACONNECTOR_LANG: 'zh',
152
+ CI: '1',
153
+ },
154
+ stdio: ['ignore', 'pipe', 'pipe'],
155
+ timeout: 120000,
156
+ });
157
+ const stdout = result.stdout || '';
158
+ const stderr = result.stderr || '';
159
+ if (result.status !== 0) {
160
+ const details = (stderr.trim() || stdout.trim()).slice(0, 1600);
161
+ throw new Error(`Command failed: yidaconnector ${args.join(' ')}\n${details}`);
162
+ }
163
+ return {
164
+ stdout,
165
+ stderr,
166
+ json: parseLastJson(stdout),
167
+ };
168
+ }
169
+
170
+ function requireSuccess(stepName, commandResult) {
171
+ if (!commandResult.json) {
172
+ throw new Error(`${stepName} did not emit a JSON result`);
173
+ }
174
+ if (commandResult.json.success === false || commandResult.json.status === 'error') {
175
+ throw new Error(`${stepName} failed: ${JSON.stringify(commandResult.json)}`);
176
+ }
177
+ return commandResult.json;
178
+ }
179
+
180
+ function run(options = {}) {
181
+ const env = options.env || process.env;
182
+ const config = options.config || getConfig(env);
183
+ const executeCli = options.runCli || runCli;
184
+ const persistCookieCache = options.writeCookieCache || writeCookieCache;
185
+ const registryFactory = options.createRegistry || createRegistry;
186
+ const persistRegistry = options.writeRegistry || writeRegistry;
187
+ const trackResource = options.addResource || addResource;
188
+
189
+ if (!ensureEnabled(config)) {
190
+ return { skipped: true, missing: config.missing };
191
+ }
192
+
193
+ if (!fs.existsSync(config.fieldsFile)) {
194
+ throw new Error(`E2E fields file not found: ${config.fieldsFile}`);
195
+ }
196
+ if (!config.skipPublish && !fs.existsSync(config.pageSource)) {
197
+ throw new Error(`E2E page source not found: ${config.pageSource}`);
198
+ }
199
+
200
+ persistCookieCache(decodeCookieData(config));
201
+ const { registry, registryPath } = registryFactory(config);
202
+
203
+ function runStep(name, args) {
204
+ const commandResult = executeCli([...args, '--quiet'], env);
205
+ registry.commands.push({ name, args, completedAt: new Date().toISOString() });
206
+ persistRegistry(registryPath, registry);
207
+ return commandResult;
208
+ }
209
+
210
+ try {
211
+ requireSuccess('login check', runStep('login', ['login', '--check-only', '--json']));
212
+ if (config.corpId) {
213
+ runStep('org-switch', ['org', 'switch', '--corp-id', config.corpId]);
214
+ }
215
+ runStep('app-list', ['app-list', '--size', '1']);
216
+
217
+ const app = requireSuccess('create app', runStep('create-app', [
218
+ 'create-app',
219
+ config.appName,
220
+ '--desc',
221
+ 'YidaConnector real E2E disposable app',
222
+ '--no-open',
223
+ ]));
224
+ trackResource(registry, registryPath, { type: 'app', appType: app.appType, name: config.appName, url: app.url });
225
+
226
+ const form = requireSuccess('create form', runStep('create-form', [
227
+ 'create-form',
228
+ 'create',
229
+ app.appType,
230
+ config.formName,
231
+ config.fieldsFile,
232
+ '--no-open',
233
+ ]));
234
+ trackResource(registry, registryPath, { type: 'form', appType: app.appType, formUuid: form.formUuid, name: config.formName, url: form.url });
235
+
236
+ requireSuccess('get schema', runStep('get-schema', ['get-schema', app.appType, form.formUuid, '--json']));
237
+ requireSuccess('query data', runStep('query-data', ['data', 'query', 'form', app.appType, form.formUuid, '--size', '1']));
238
+
239
+ if (!config.skipPublish) {
240
+ const page = requireSuccess('create page', runStep('create-page', [
241
+ 'create-page',
242
+ app.appType,
243
+ config.pageName,
244
+ '--mode',
245
+ 'dashboard',
246
+ '--no-open',
247
+ ]));
248
+ trackResource(registry, registryPath, { type: 'page', appType: app.appType, pageId: page.pageId, name: config.pageName, url: page.url });
249
+ requireSuccess('publish page', runStep('publish', [
250
+ 'publish',
251
+ config.pageSource,
252
+ app.appType,
253
+ page.pageId,
254
+ '--health-check',
255
+ '--no-open',
256
+ ]));
257
+ }
258
+
259
+ registry.status = 'passed';
260
+ registry.finishedAt = new Date().toISOString();
261
+ persistRegistry(registryPath, registry);
262
+ console.log(`Real E2E passed. Registry: ${registryPath}`);
263
+ return { skipped: false, registryPath, registry };
264
+ } catch (error) {
265
+ registry.status = 'failed';
266
+ registry.finishedAt = new Date().toISOString();
267
+ registry.error = error.message;
268
+ persistRegistry(registryPath, registry);
269
+ throw error;
270
+ }
271
+ }
272
+
273
+ if (require.main === module) {
274
+ try {
275
+ run();
276
+ } catch (error) {
277
+ console.error(error.message);
278
+ process.exit(1);
279
+ }
280
+ }
281
+
282
+ module.exports = {
283
+ addResource,
284
+ createRegistry,
285
+ decodeCookieData,
286
+ extractJsonObjects,
287
+ getConfig,
288
+ parseLastJson,
289
+ run,
290
+ runCli,
291
+ writeRegistry,
292
+ writeCookieCache,
293
+ };
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const ROOT = path.resolve(__dirname, '..', '..');
9
+ const SKILLS_DIR = path.join(ROOT, 'yida-skills', 'skills');
10
+
11
+ const SKILL_COVERAGE = {
12
+ 'large-file-write': { level: 'offline', tests: ['write fixture generation uses fs APIs in runner tests'] },
13
+ 'sls-log-workbench': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'internal support-only SLS tooling is guarded by passphrase and corp whitelist; shared real E2E must not query production logs' },
14
+ 'yida-app': { level: 'real-e2e', stages: ['app', 'form', 'page', 'data', 'report', 'dashboard'] },
15
+ 'yida-app-permission': { level: 'offline-unit', tests: ['tests/app-permission.test.js'], reason: 'app admin mutations affect real application access; shared real E2E only validates safe read paths' },
16
+ 'yida-basic-info': { level: 'offline-unit', tests: ['tests/basic-info.test.js'], reason: 'basic-info reads org admin metadata and can update domains; unit coverage avoids mutating shared real org settings' },
17
+ 'yida-business-rule': { level: 'opt-in', reason: 'business association rules mutate form event configuration; validate in a dedicated real-form/UI stage before adding to deterministic shared E2E' },
18
+ 'yida-chart': { level: 'real-e2e', stages: ['report', 'dashboard'], tests: ['report chart config generation'] },
19
+ 'yida-connector': { level: 'offline', stages: ['connector-local'], commands: ['connector gen-template', 'connector parse-api'] },
20
+ 'yida-connector-safe-actions': { level: 'offline', stages: ['connector-local'], commands: ['connector parse-api', 'connector test --action <operationId>'], reason: 'skill documents conservative HTTP connector action generation and repair workflow; shared E2E should validate local parsing without mutating tenant connectors' },
21
+ 'yida-corp-efficiency': { level: 'offline-unit', tests: ['tests/corp-efficiency.test.js'], reason: 'enterprise efficiency queries and notify mutations are not safe for shared real org E2E' },
22
+ 'yida-corp-manager': { level: 'offline-unit', tests: ['tests/corp-manager.test.js'], reason: 'enterprise admin mutations are not safe for shared real org E2E' },
23
+ 'yida-create-app': { level: 'real-e2e', stages: ['app'], commands: ['create-app'] },
24
+ 'yida-create-form-page': { level: 'real-e2e', stages: ['form'], commands: ['create-form create', 'create-form update', 'create-form add-option'] },
25
+ 'yida-create-page': { level: 'real-e2e', stages: ['page', 'dashboard'], commands: ['create-page --mode dashboard'] },
26
+ 'yida-create-process': { level: 'opt-in-real-e2e', stages: ['process'], commands: ['create-process --formUuid'], reason: 'process stage mutates workflow definitions and is excluded from default full E2E unless explicitly requested' },
27
+ 'yida-custom-page': { level: 'real-e2e', stages: ['page'], commands: ['check-page', 'build-page', 'compile', 'publish'] },
28
+ 'yida-dashboard': { level: 'real-e2e', stages: ['dashboard'], commands: ['create-page --mode dashboard', 'publish dashboard skill page'] },
29
+ 'yida-data-source-connectors': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'skill defines a page Schema/dataSource authoring guardrail; real connector execution mutates tenant-specific connector/data source configuration and should be validated in dedicated page publish scenarios' },
30
+ 'yida-data-management': { level: 'real-e2e', stages: ['data', 'task'], commands: ['data create/query/update form', 'data query tasks'] },
31
+ 'yida-db-seq-fix': { level: 'offline-unit', tests: ['tests/db-seq-fix.test.js'], reason: 'PostgreSQL admin repair is not safe for shared real org E2E' },
32
+ 'yida-density': { level: 'offline-unit', tests: ['sample/check-page coverage'], reason: 'visual density template is validated through page build/lint rather than real data mutation' },
33
+ 'yida-export-conversation': { level: 'offline-unit', tests: ['conversation exporter unit coverage'], reason: 'depends on local conversation artifacts, not Yida API' },
34
+ 'yida-flash-note-to-prd': { level: 'opt-in', stages: ['ai'], commands: ['flash-to-prd'], reason: 'remote AI service can timeout; excluded from deterministic default full run' },
35
+ 'yida-form-detail': { level: 'offline-unit', reason: 'form detail styling skill has no standalone CLI command yet' },
36
+ 'yida-form-permission': { level: 'real-e2e', stages: ['permission'], commands: ['get-permission'] },
37
+ 'yida-formula': { level: 'offline', stages: ['offline'], commands: ['formula evaluate'] },
38
+ 'yida-formula-evaluate': { level: 'offline', stages: ['offline'], commands: ['formula evaluate --json'] },
39
+ 'yida-get-schema': { level: 'real-e2e', stages: ['form'], commands: ['get-schema', 'get-schema --all', 'get-schema --field'] },
40
+ 'yida-i18n': { level: 'offline-unit', tests: ['tests/i18n-management.test.js'], reason: 'multilingual management writes app language config and copy entries; shared real E2E should only run read-only overview on dedicated intl apps' },
41
+ 'yida-integration': { level: 'opt-in', reason: 'creates backend automation flows; should run in a separate integration stage with cleanup/audit controls' },
42
+ 'yida-agent-center': { level: 'offline-unit', tests: ['tests/agent-center.test.js'], reason: 'process delegation mutates real user-agent relationships; shared real E2E must avoid changing organization delegation settings' },
43
+ 'yida-login': { level: 'real-e2e', stages: ['auth'], commands: ['login --check-only --json'] },
44
+ 'yida-logout': { level: 'offline-unit', tests: ['login/auth unit coverage'], reason: 'real logout would destroy the shared E2E session' },
45
+ 'yida-nav-group': { level: 'offline-unit', tests: ['tests/nav-group.test.js'], reason: 'navigation grouping mutates app sidebar order; unit coverage validates payloads and tree operations until a dedicated cleanup-safe nav stage exists' },
46
+ 'yida-page-config': { level: 'real-e2e', stages: ['share'], commands: ['get-page-config', 'verify-short-url', 'save-share-config'] },
47
+ 'yida-ppt': { level: 'deprecated', reason: 'skill is deprecated in favor of yida-ppt-slider' },
48
+ 'yida-ppt-slider': { level: 'offline-unit', reason: 'presentation-style custom page skill should be validated by page generation/check-page fixtures' },
49
+ 'yida-process-rule': { level: 'opt-in-real-e2e', stages: ['process'], commands: ['configure-process'], reason: 'process stage publishes workflow rules on the disposable E2E form and is excluded from default full E2E unless explicitly requested' },
50
+ 'yida-publish-page': { level: 'real-e2e', stages: ['page', 'dashboard'], commands: ['publish --health-check'] },
51
+ 'yida-report': { level: 'real-e2e', stages: ['report'], commands: ['create-report', 'append-chart'] },
52
+ 'yida-table-form': { level: 'offline-unit', reason: 'table-form custom page template should be validated with check-page fixture before real publish stage is added' },
53
+ 'yida-voc': { level: 'offline-unit', reason: 'VOC formatting skill is local text transformation, not Yida API mutation' },
54
+ };
55
+
56
+ function listSkillNames(skillsDir = SKILLS_DIR) {
57
+ return fs.readdirSync(skillsDir)
58
+ .filter((name) => fs.statSync(path.join(skillsDir, name)).isDirectory())
59
+ .sort();
60
+ }
61
+
62
+ function validateSkillCoverage(options = {}) {
63
+ const skillsDir = options.skillsDir || SKILLS_DIR;
64
+ const coverage = options.coverage || SKILL_COVERAGE;
65
+ const skillNames = options.skillNames || listSkillNames(skillsDir);
66
+ const missing = skillNames.filter((name) => !coverage[name]);
67
+ const extra = Object.keys(coverage).filter((name) => !skillNames.includes(name)).sort();
68
+ const invalid = Object.entries(coverage)
69
+ .filter(([, entry]) => !entry || !entry.level)
70
+ .map(([name]) => name);
71
+
72
+ return {
73
+ ok: missing.length === 0 && extra.length === 0 && invalid.length === 0,
74
+ checked: skillNames.length,
75
+ missing,
76
+ extra,
77
+ invalid,
78
+ coverage,
79
+ };
80
+ }
81
+
82
+ function run(options = {}) {
83
+ const result = validateSkillCoverage(options);
84
+ if (options.json) {
85
+ console.log(JSON.stringify(result, null, 2));
86
+ } else {
87
+ console.log(`Skill E2E coverage: checked ${result.checked} skills`);
88
+ for (const name of Object.keys(result.coverage).sort()) {
89
+ const entry = result.coverage[name];
90
+ console.log(`- ${name}: ${entry.level}`);
91
+ }
92
+ }
93
+
94
+ if (!result.ok) {
95
+ if (!options.json) {
96
+ if (result.missing.length) {console.error(`Missing coverage: ${result.missing.join(', ')}`);}
97
+ if (result.extra.length) {console.error(`Coverage entries without skill: ${result.extra.join(', ')}`);}
98
+ if (result.invalid.length) {console.error(`Invalid coverage entries: ${result.invalid.join(', ')}`);}
99
+ }
100
+ process.exit(1);
101
+ }
102
+
103
+ return result;
104
+ }
105
+
106
+ if (require.main === module) {
107
+ run({ json: process.argv.includes('--json') });
108
+ }
109
+
110
+ module.exports = {
111
+ SKILL_COVERAGE,
112
+ listSkillNames,
113
+ run,
114
+ validateSkillCoverage,
115
+ };
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { COMMAND_GROUPS } = require('../lib/core/command-manifest');
8
+ const en = require('../lib/core/locales/en');
9
+
10
+ const ROOT = path.resolve(__dirname, '..');
11
+ const README_FILE = path.join(ROOT, 'README.md');
12
+ const START = '<!-- YIDACONNECTOR_COMMANDS_START -->';
13
+ const END = '<!-- YIDACONNECTOR_COMMANDS_END -->';
14
+
15
+ function getByPath(object, keyPath) {
16
+ return String(keyPath || '').split('.').reduce((current, key) => {
17
+ if (!current || typeof current !== 'object') {
18
+ return undefined;
19
+ }
20
+ return current[key];
21
+ }, object);
22
+ }
23
+
24
+ function translate(key) {
25
+ return getByPath(en, key) || key;
26
+ }
27
+
28
+ function escapeTableCell(value) {
29
+ return String(value || '')
30
+ .replace(/\|/g, '\\|')
31
+ .replace(/\n/g, '<br>');
32
+ }
33
+
34
+ function commandRow(entry) {
35
+ return `| \`yidaconnector ${escapeTableCell(entry.usage)}\` | ${escapeTableCell(translate(entry.descriptionKey))} |`;
36
+ }
37
+
38
+ function renderGroup(group) {
39
+ const commands = group.commands.filter(entry => !entry.hidden);
40
+ if (commands.length === 0) {
41
+ return '';
42
+ }
43
+
44
+ return [
45
+ `### ${translate(group.titleKey)}`,
46
+ '',
47
+ '| Command | Description |',
48
+ '|---------|-------------|',
49
+ ...commands.map(commandRow),
50
+ '',
51
+ ].join('\n');
52
+ }
53
+
54
+ function renderCommandDocs() {
55
+ return [
56
+ START,
57
+ '<!-- This section is generated by `npm run docs:commands`. Do not edit command rows by hand. -->',
58
+ '',
59
+ ...COMMAND_GROUPS.map(renderGroup).filter(Boolean),
60
+ END,
61
+ ].join('\n');
62
+ }
63
+
64
+ function replaceGeneratedBlock(readme, generated) {
65
+ const startIndex = readme.indexOf(START);
66
+ const endIndex = readme.indexOf(END);
67
+
68
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
69
+ return readme.slice(0, startIndex) + generated + readme.slice(endIndex + END.length);
70
+ }
71
+
72
+ const anchor = 'Run `yidaconnector --help` or `yidaconnector <command> --help` for detailed usage.\n';
73
+ const anchorIndex = readme.indexOf(anchor);
74
+ if (anchorIndex === -1) {
75
+ throw new Error('README CLI reference anchor not found');
76
+ }
77
+
78
+ const insertIndex = anchorIndex + anchor.length;
79
+ return `${readme.slice(0, insertIndex)}\n${generated}\n${readme.slice(insertIndex)}`;
80
+ }
81
+
82
+ function run() {
83
+ const args = process.argv.slice(2);
84
+ const checkOnly = args.includes('--check');
85
+ const printOnly = args.includes('--print');
86
+ const generated = renderCommandDocs();
87
+
88
+ if (printOnly) {
89
+ process.stdout.write(generated + '\n');
90
+ return;
91
+ }
92
+
93
+ const current = fs.readFileSync(README_FILE, 'utf8');
94
+ const next = replaceGeneratedBlock(current, generated);
95
+
96
+ if (checkOnly) {
97
+ if (next !== current) {
98
+ console.error('README command docs are out of date. Run `npm run docs:commands`.');
99
+ process.exit(1);
100
+ }
101
+ console.log('README command docs OK');
102
+ return;
103
+ }
104
+
105
+ fs.writeFileSync(README_FILE, next);
106
+ console.log('Updated README command docs');
107
+ }
108
+
109
+ run();
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { spawnSync } = require('child_process');
8
+
9
+ const ROOT = path.resolve(__dirname, '..');
10
+ const BIN = path.join(ROOT, 'bin', 'yida.js');
11
+ const BASE_REQUIRED_ENV = [
12
+ 'YIDACONNECTOR_SMOKE_COOKIES_BASE64',
13
+ 'YIDACONNECTOR_SMOKE_APP_TYPE',
14
+ ];
15
+
16
+ function getSmokeConfig(env = process.env) {
17
+ const missing = BASE_REQUIRED_ENV.filter((name) => !env[name]);
18
+ const hasFormSmoke = Boolean(env.YIDACONNECTOR_SMOKE_FORM_UUID);
19
+ const hasPageSmoke = Boolean(env.YIDACONNECTOR_SMOKE_PAGE_UUID);
20
+ if (!hasFormSmoke && !hasPageSmoke) {
21
+ missing.push('YIDACONNECTOR_SMOKE_FORM_UUID or YIDACONNECTOR_SMOKE_PAGE_UUID');
22
+ }
23
+ if (env.YIDACONNECTOR_SMOKE_PAGE_SOURCE && !hasPageSmoke) {
24
+ missing.push('YIDACONNECTOR_SMOKE_PAGE_UUID');
25
+ }
26
+ return {
27
+ missing,
28
+ appType: env.YIDACONNECTOR_SMOKE_APP_TYPE,
29
+ formUuid: env.YIDACONNECTOR_SMOKE_FORM_UUID,
30
+ pageUuid: env.YIDACONNECTOR_SMOKE_PAGE_UUID,
31
+ pageSource: env.YIDACONNECTOR_SMOKE_PAGE_SOURCE,
32
+ };
33
+ }
34
+
35
+ function hasRequiredConfig(env = process.env) {
36
+ const { missing } = getSmokeConfig(env);
37
+ if (missing.length > 0) {
38
+ console.log(`Skipping real-environment smoke; missing: ${missing.join(', ')}`);
39
+ return false;
40
+ }
41
+ return true;
42
+ }
43
+
44
+ function decodeCookieData(env = process.env) {
45
+ const raw = Buffer.from(env.YIDACONNECTOR_SMOKE_COOKIES_BASE64, 'base64').toString('utf8');
46
+ const parsed = JSON.parse(raw);
47
+ const cookieData = Array.isArray(parsed) ? { cookies: parsed } : parsed;
48
+ if (!Array.isArray(cookieData.cookies) || cookieData.cookies.length === 0) {
49
+ throw new Error('YIDACONNECTOR_SMOKE_COOKIES_BASE64 must decode to a cookie array or an object with cookies');
50
+ }
51
+ cookieData.base_url = env.YIDACONNECTOR_SMOKE_BASE_URL || cookieData.base_url || 'https://www.aliwork.com';
52
+ return cookieData;
53
+ }
54
+
55
+ function writeCookieCache(cookieData) {
56
+ const cacheDir = path.join(ROOT, 'project', '.cache');
57
+ fs.mkdirSync(cacheDir, { recursive: true });
58
+ fs.writeFileSync(
59
+ path.join(cacheDir, 'cookies-public.json'),
60
+ JSON.stringify(cookieData, null, 2),
61
+ 'utf8'
62
+ );
63
+ }
64
+
65
+ function runCli(args, env = process.env) {
66
+ console.log(`Running: yidaconnector ${args.join(' ')}`);
67
+ const result = spawnSync(process.execPath, [BIN, ...args], {
68
+ cwd: ROOT,
69
+ encoding: 'utf8',
70
+ env: {
71
+ ...env,
72
+ YIDACONNECTOR_LANG: 'zh',
73
+ CI: '1',
74
+ },
75
+ stdio: ['ignore', 'pipe', 'pipe'],
76
+ timeout: 60000,
77
+ });
78
+ if (result.status !== 0) {
79
+ const stderr = (result.stderr || '').trim();
80
+ const stdout = (result.stdout || '').trim();
81
+ const details = (stderr || stdout).slice(0, 1000);
82
+ throw new Error(`Command failed: yidaconnector ${args.join(' ')}\n${details}`);
83
+ }
84
+ const outputLength = (result.stdout || '').trim().length;
85
+ console.log(`OK: yidaconnector ${args[0]} (${outputLength} stdout chars)`);
86
+ }
87
+
88
+ function run(options = {}) {
89
+ const env = options.env || process.env;
90
+ const executeCli = options.runCli || runCli;
91
+ const persistCookieCache = options.writeCookieCache || writeCookieCache;
92
+ if (!hasRequiredConfig(env)) {
93
+ return;
94
+ }
95
+
96
+ const config = getSmokeConfig(env);
97
+ const cookieData = decodeCookieData(env);
98
+ persistCookieCache(cookieData);
99
+
100
+ executeCli(['login', '--check-only'], env);
101
+ executeCli(['app-list', '--size', '1'], env);
102
+
103
+ if (config.pageUuid) {
104
+ executeCli(['get-schema', config.appType, config.pageUuid], env);
105
+ if (config.pageSource) {
106
+ executeCli(['publish', config.pageSource, config.appType, config.pageUuid, '--health-check', '--no-open'], env);
107
+ }
108
+ }
109
+
110
+ if (config.formUuid) {
111
+ executeCli(['get-schema', config.appType, config.formUuid], env);
112
+ executeCli(['data', 'query', 'form', config.appType, config.formUuid, '--size', '1'], env);
113
+ }
114
+
115
+ console.log('Nightly real-environment smoke passed');
116
+ }
117
+
118
+ if (require.main === module) {
119
+ try {
120
+ run();
121
+ } catch (error) {
122
+ console.error(error.message);
123
+ process.exit(1);
124
+ }
125
+ }
126
+
127
+ module.exports = {
128
+ decodeCookieData,
129
+ getSmokeConfig,
130
+ hasRequiredConfig,
131
+ run,
132
+ runCli,
133
+ writeCookieCache,
134
+ };