openyida 2026.5.27-beta.0 → 2026.5.27

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 CHANGED
@@ -312,9 +312,12 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
312
312
  | `openyida create-form update <appType> <formUuid> <changes.json> [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
313
313
  | `openyida create-form patch <appType> <formUuid> <patch.json> [--open\|--no-open]` | Apply controlled schema patches for designer-only form settings |
314
314
  | `openyida create-form rule <appType> <formUuid> <rules.json> [--open\|--no-open]` | Configure field show/hide linkage and onChange auto-assignment rules |
315
+ | `openyida create-form validation <appType> <formUuid> <validations.json> [--open\|--no-open]` | Configure form validations using Yida-visible field `props.validation` rules and `customValidate` JSExpression functions for regex, bank card, cross-field, conditional, or async checks |
316
+ | `openyida add-validation <appType> <formUuid> --field <labelOrId> --type <phone\|regex\|idCard\|email\|...> [--message <text>]` | Add one smart validation rule without writing a JSON file |
315
317
  | `openyida create-form bind-datasource <appType> <formUuid> <fieldLabelOrId> <datasource.json> [--open\|--no-open]` | Bind URL/search data sources to SelectField/MultiSelectField-style option fields |
316
318
  | `openyida create-form add-option <appType> <formUuid> <fieldLabel> <option1> [option2] ...` | Append options to a SelectField/RadioField/CheckboxField/MultiSelectField |
317
319
  | `openyida list-forms <appType> [--keyword <text>]` | List forms in an application |
320
+ | `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage Yida aggregate tables (`virtualView`) |
318
321
  | `openyida get-schema <appType> <formUuid\|--all> [--field <labelOrFieldId>]` | Fetch one form schema, batch export all, or pick a single field's full props |
319
322
  | `openyida create-page <appType> "<name>" [--mode dashboard] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a custom display page; dashboard mode hides top/workbench chrome |
320
323
  | `openyida generate-page <template> [--spec file]` | Generate custom page source from templates (`product-homepage`, `todo-mvc`) |
@@ -324,13 +327,15 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
324
327
  | `openyida publish <sourceFile> <appType> <formUuid> [--compat] [--health-check] [--force] [--open\|--no-open]` | Compile and publish a custom display page; by default the target must be `formType=display` |
325
328
  | `openyida update-form-config <appType> <formUuid> <isRenderNav> <title> [--locale zh_CN\|en_US\|ja_JP]` | Update page/form display configuration |
326
329
 
330
+ Form field definitions can include `alias` or `componentAlias` to populate Yida designer component aliases, stored as `pages[0].componentAlias.items`. Yida runtime resolves these aliases in page JS, so `this.$('phone')` can be used instead of `this.$('textField_xxx')`; OpenYida form rules, validations, and `openyida data ... --resolve-aliases` JSON inputs also accept aliases as field references. For server-side DingTalk OpenAPI calls, use `GET /v2.0/yida/forms/component/alias/{appType}/{formUuid}` to read the `{ fieldId, alias }` mapping, then translate aliases before sending form data/search JSON. That endpoint requires `systemToken`, `userId`, an access token, and the Yida form data read permission; grant that permission in DingTalk developer console API permissions and publish the DingTalk app. Yida app code and app secret are available under app settings > deployment/maintenance.
331
+
327
332
  `openyida publish` preserves existing custom page data sources by default. Before saving the new compiled JSX Schema, it reads the current page Schema and merges the Page-level `dataSource` with the built-in `urlParams` and `timestamp` sources, so manually configured data sources are not deleted during republish.
328
333
 
329
334
  ### Data, Permissions, and Sharing
330
335
 
331
336
  | Command | Description |
332
337
  |---------|-------------|
333
- | `openyida data <action> <resource> [args]` | Unified data management for forms, processes, tasks, and subforms; form queries support `--all` and `--form-uuid` subform hydration |
338
+ | `openyida data <action> <resource> [args]` | Unified data management for forms, processes, tasks, and subforms; form queries support `--all`, `--form-uuid` subform hydration, and `--resolve-aliases` for component alias JSON keys |
334
339
  | `openyida data check <appType> <formUuid> <rules.json>` | Detect anomalous process-form records |
335
340
  | `openyida task-center <type> [options]` | Query todo, created, processed, CC, or proxy-submitted tasks |
336
341
  | `openyida agent-center <sub-command>` | Manage Yida process delegation and departure delegation |
@@ -351,6 +356,7 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
351
356
  |---------|-------------|
352
357
  | `openyida create-process <appType> ...` | Create a process form and configure workflow |
353
358
  | `openyida configure-process <appType> ...` | Configure and publish process rules |
359
+ | `openyida ai-form-setting <get\|fields\|models\|enable\|disable\|save> <appType> ...` | Manage process-form AI approval prompts from `/settings/aiFormSetting` |
354
360
  | `openyida process preview <appType> <processInstanceId> [--output <path>]` | Generate a visual process preview |
355
361
  | `openyida create-report <appType> "<name>" <charts.json> [--open\|--no-open]` | Create a Yida report |
356
362
  | `openyida append-chart <appType> <reportId> <charts.json> [--open\|--no-open]` | Append a chart to an existing report |
package/bin/yida.js CHANGED
@@ -642,12 +642,24 @@ async function main() {
642
642
  break;
643
643
  }
644
644
 
645
+ case 'add-validation': {
646
+ process.argv = [process.argv[0], process.argv[1], 'validation', ...args];
647
+ require('../lib/app/create-form');
648
+ break;
649
+ }
650
+
645
651
  case 'list-forms': {
646
652
  const { run } = require('../lib/app/list-forms');
647
653
  await run(args);
648
654
  break;
649
655
  }
650
656
 
657
+ case 'aggregate-table': {
658
+ const { run } = require('../lib/aggregate-table/aggregate-table');
659
+ await run(args);
660
+ break;
661
+ }
662
+
651
663
  case 'get-schema': {
652
664
  const { run } = require('../lib/app/get-schema');
653
665
  await run(args);
@@ -881,6 +893,14 @@ async function main() {
881
893
  break;
882
894
  }
883
895
 
896
+ case 'ai-form-setting':
897
+ case 'ai-approve':
898
+ case 'aiFormSetting': {
899
+ const { run: runAIFormSetting } = require('../lib/process/ai-form-setting');
900
+ await runAIFormSetting(args);
901
+ break;
902
+ }
903
+
884
904
  case 'process': {
885
905
  const subCommand = args[0];
886
906
  const subArgs = args.slice(1);
@@ -0,0 +1,594 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const querystring = require('querystring');
5
+
6
+ const {
7
+ loadCookieData,
8
+ triggerLogin,
9
+ resolveBaseUrl,
10
+ httpGet,
11
+ httpPost,
12
+ requestWithAutoLogin,
13
+ } = require('../core/utils');
14
+ const { t } = require('../core/i18n');
15
+ const { buildYidaTitleI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
16
+ const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
17
+ const { fetchFormPageList, resolveLocalizedText } = require('../app/form-navigation');
18
+
19
+ const FORM_TYPE_VIRTUAL_VIEW = 'virtualView';
20
+ const DESIGN_KEYS = [
21
+ 'relationForms',
22
+ 'relationships',
23
+ 'aggregatedFields',
24
+ 'auxFields',
25
+ 'formulaFields',
26
+ 'validators',
27
+ ];
28
+
29
+ function hasHelpFlag(args) {
30
+ return (args || []).includes('--help') || (args || []).includes('-h');
31
+ }
32
+
33
+ function printUsage() {
34
+ process.stderr.write([
35
+ t('aggregate_table.usage'),
36
+ '',
37
+ t('aggregate_table.commands_title'),
38
+ ` ${t('aggregate_table.cmd_list')}`,
39
+ ` ${t('aggregate_table.cmd_create_empty')}`,
40
+ ` ${t('aggregate_table.cmd_inspect')}`,
41
+ ` ${t('aggregate_table.cmd_preview')}`,
42
+ ` ${t('aggregate_table.cmd_save')}`,
43
+ ` ${t('aggregate_table.cmd_publish')}`,
44
+ ` ${t('aggregate_table.cmd_status')}`,
45
+ '',
46
+ t('aggregate_table.examples_title'),
47
+ ` ${t('aggregate_table.example_list')}`,
48
+ ` ${t('aggregate_table.example_create_empty')}`,
49
+ ` ${t('aggregate_table.example_inspect')}`,
50
+ '',
51
+ ].join('\n'));
52
+ }
53
+
54
+ function parseFlags(args) {
55
+ const openOption = parseOpenOption(args || []);
56
+ const positional = [];
57
+ const flags = {
58
+ json: false,
59
+ keyword: '',
60
+ locale: null,
61
+ };
62
+
63
+ for (let index = 0; index < openOption.args.length; index++) {
64
+ const arg = openOption.args[index];
65
+ if (arg === '--json') {
66
+ flags.json = true;
67
+ continue;
68
+ }
69
+ if (arg === '--keyword' && openOption.args[index + 1]) {
70
+ flags.keyword = openOption.args[++index];
71
+ continue;
72
+ }
73
+ if ((arg === '--locale' || arg === '--content-locale' || arg === '--lang') && openOption.args[index + 1]) {
74
+ const locale = openOption.args[++index];
75
+ if (!normalizeYidaLocale(locale)) {
76
+ throw new Error(t('aggregate_table.unsupported_locale', locale));
77
+ }
78
+ flags.locale = locale;
79
+ continue;
80
+ }
81
+ positional.push(arg);
82
+ }
83
+
84
+ flags.openMode = openOption.mode;
85
+ return { positional, flags };
86
+ }
87
+
88
+ function unwrapContent(result) {
89
+ if (result && Object.prototype.hasOwnProperty.call(result, 'content')) {
90
+ return result.content;
91
+ }
92
+ return result;
93
+ }
94
+
95
+ function assertSuccess(result, action) {
96
+ if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
97
+ const message = result
98
+ ? result.errorMsg || result.error || result.message || t('common.unknown_error')
99
+ : t('common.request_failed');
100
+ throw new Error(`${action}: ${message}`);
101
+ }
102
+ return unwrapContent(result);
103
+ }
104
+
105
+ async function createAuthRef() {
106
+ let cookieData = loadCookieData();
107
+ if (!cookieData) {
108
+ cookieData = await triggerLogin();
109
+ }
110
+ if (!cookieData || !cookieData.cookies) {
111
+ throw new Error(t('aggregate_table.no_login'));
112
+ }
113
+ return {
114
+ csrfToken: cookieData.csrf_token,
115
+ cookies: cookieData.cookies,
116
+ baseUrl: resolveBaseUrl(cookieData),
117
+ cookieData,
118
+ };
119
+ }
120
+
121
+ function buildCreateEmptyPostData(csrfToken, title) {
122
+ return querystring.stringify({
123
+ _csrf_token: csrfToken,
124
+ formType: 'receipt',
125
+ isVirtualView: 'y',
126
+ title: JSON.stringify(buildYidaTitleI18n(title, {
127
+ en_US: title,
128
+ ja_JP: title,
129
+ })),
130
+ });
131
+ }
132
+
133
+ function buildDesignPostData(csrfToken, formUuid, designInfo, gmtModified) {
134
+ const payload = {
135
+ _csrf_token: csrfToken,
136
+ formUuid,
137
+ designInfo: JSON.stringify(designInfo),
138
+ };
139
+ if (gmtModified !== undefined) {
140
+ payload.gmtModified = gmtModified === null ? '' : gmtModified;
141
+ }
142
+ return querystring.stringify(payload);
143
+ }
144
+
145
+ async function checkVirtualViewFeature(authRef, appType) {
146
+ return requestWithAutoLogin((auth) => {
147
+ return httpPost(
148
+ auth.baseUrl,
149
+ `/dingtalk/web/${appType}/query/virtualview/show.json`,
150
+ querystring.stringify({ _csrf_token: auth.csrfToken }),
151
+ auth.cookies
152
+ );
153
+ }, authRef);
154
+ }
155
+
156
+ async function createEmptyVirtualView(authRef, appType, title) {
157
+ return requestWithAutoLogin((auth) => {
158
+ return httpPost(
159
+ auth.baseUrl,
160
+ `/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`,
161
+ buildCreateEmptyPostData(auth.csrfToken, title),
162
+ auth.cookies
163
+ );
164
+ }, authRef);
165
+ }
166
+
167
+ async function getVirtualViewConfig(authRef, appType, formUuid) {
168
+ return requestWithAutoLogin((auth) => {
169
+ return httpGet(
170
+ auth.baseUrl,
171
+ `/alibaba/web/${appType}/query/virtualview/get.json`,
172
+ { formUuid },
173
+ auth.cookies
174
+ );
175
+ }, authRef);
176
+ }
177
+
178
+ async function postVirtualViewDesign(authRef, appType, formUuid, designInfo, action, gmtModified) {
179
+ const endpointMap = {
180
+ preview: 'preview.json',
181
+ save: 'saveStashConfig.json',
182
+ publish: 'update.json',
183
+ };
184
+ const endpoint = endpointMap[action];
185
+ if (!endpoint) {
186
+ throw new Error(`Unknown aggregate table action: ${action}`);
187
+ }
188
+
189
+ return requestWithAutoLogin((auth) => {
190
+ return httpPost(
191
+ auth.baseUrl,
192
+ `/alibaba/web/${appType}/query/virtualview/${endpoint}`,
193
+ buildDesignPostData(auth.csrfToken, formUuid, designInfo, gmtModified),
194
+ auth.cookies
195
+ );
196
+ }, authRef);
197
+ }
198
+
199
+ async function queryBuildState(authRef, appType, formUuid) {
200
+ return requestWithAutoLogin((auth) => {
201
+ return httpGet(
202
+ auth.baseUrl,
203
+ `/alibaba/web/${appType}/query/virtualview/queryBuildState.json`,
204
+ { formUuid },
205
+ auth.cookies
206
+ );
207
+ }, authRef);
208
+ }
209
+
210
+ function isVirtualViewNode(node) {
211
+ return node && node.formType === FORM_TYPE_VIRTUAL_VIEW;
212
+ }
213
+
214
+ function filterAggregateTables(items, keyword) {
215
+ const aggregateTables = (items || []).filter(isVirtualViewNode);
216
+ if (!keyword) {
217
+ return aggregateTables;
218
+ }
219
+ const normalized = String(keyword).toLowerCase();
220
+ return aggregateTables.filter((item) => {
221
+ return [item.formName, item.formUuid, item.pathName]
222
+ .filter(Boolean)
223
+ .some((value) => String(value).toLowerCase().includes(normalized));
224
+ });
225
+ }
226
+
227
+ function normalizeAggregateTableNode(node) {
228
+ return {
229
+ formUuid: node.formUuid,
230
+ aggregateTableId: node.formUuid,
231
+ name: node.formName || '',
232
+ formType: FORM_TYPE_VIRTUAL_VIEW,
233
+ pathName: node.pathName || '',
234
+ };
235
+ }
236
+
237
+ function readJsonInput(input) {
238
+ if (!input) {
239
+ throw new Error(t('aggregate_table.design_required'));
240
+ }
241
+
242
+ let raw = input;
243
+ if (fs.existsSync(input)) {
244
+ raw = fs.readFileSync(input, 'utf8');
245
+ }
246
+
247
+ try {
248
+ return JSON.parse(raw);
249
+ } catch (err) {
250
+ throw new Error(t('aggregate_table.invalid_json', err.message));
251
+ }
252
+ }
253
+
254
+ function normalizeDesignConfig(rawConfig, formUuid) {
255
+ let config = rawConfig;
256
+
257
+ if (config && typeof config.designInfo === 'string') {
258
+ config = JSON.parse(config.designInfo);
259
+ } else if (config && config.designInfo && typeof config.designInfo === 'object') {
260
+ config = config.designInfo;
261
+ } else if (config && config.viewDesignConfig) {
262
+ config = config.viewDesignConfig;
263
+ } else if (config && Object.prototype.hasOwnProperty.call(config, 'content')) {
264
+ config = config.content && config.content.viewDesignConfig
265
+ ? config.content.viewDesignConfig
266
+ : config.content;
267
+ }
268
+
269
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
270
+ throw new Error(t('aggregate_table.design_object_required'));
271
+ }
272
+
273
+ const normalized = { formUuid };
274
+ for (const key of DESIGN_KEYS) {
275
+ normalized[key] = Array.isArray(config[key]) ? config[key] : [];
276
+ }
277
+ return normalized;
278
+ }
279
+
280
+ function summarizeDesignConfig(config) {
281
+ const safeConfig = config || {};
282
+ return {
283
+ formUuid: safeConfig.formUuid || '',
284
+ title: resolveLocalizedText(safeConfig.title, ''),
285
+ isStashConfig: safeConfig.isStashConfig || '',
286
+ gmtModified: safeConfig.gmtModified || null,
287
+ stashGmtModified: safeConfig.stashGmtModified || null,
288
+ counts: {
289
+ relationForms: Array.isArray(safeConfig.relationForms) ? safeConfig.relationForms.length : 0,
290
+ relationships: Array.isArray(safeConfig.relationships) ? safeConfig.relationships.length : 0,
291
+ aggregatedFields: Array.isArray(safeConfig.aggregatedFields) ? safeConfig.aggregatedFields.length : 0,
292
+ auxFields: Array.isArray(safeConfig.auxFields) ? safeConfig.auxFields.length : 0,
293
+ formulaFields: Array.isArray(safeConfig.formulaFields) ? safeConfig.formulaFields.length : 0,
294
+ validators: Array.isArray(safeConfig.validators) ? safeConfig.validators.length : 0,
295
+ },
296
+ };
297
+ }
298
+
299
+ function buildDesignUrl(baseUrl, appType, formUuid, options = {}) {
300
+ const suffix = options.fromNew ? '&fromNew=true' : '';
301
+ return `${baseUrl}/alibaba/web/${appType}/design/virtualViewDesigner.html?formUuid=${formUuid}${suffix}`;
302
+ }
303
+
304
+ function buildWorkbenchUrl(baseUrl, appType, formUuid) {
305
+ return `${baseUrl}/${appType}/workbench/${formUuid}`;
306
+ }
307
+
308
+ function printSummary(title, rows) {
309
+ const { result } = require('../core/chalk');
310
+ result(true, title, rows);
311
+ }
312
+
313
+ async function runList(args) {
314
+ const { positional, flags } = parseFlags(args);
315
+ const appType = positional[0];
316
+ if (!appType) {
317
+ const { error } = require('../core/chalk');
318
+ error(t('aggregate_table.list_usage'), { hint: t('aggregate_table.example_list') });
319
+ return;
320
+ }
321
+
322
+ const authRef = await createAuthRef();
323
+ const items = await fetchFormPageList(appType, authRef);
324
+ const aggregateTables = filterAggregateTables(items, flags.keyword).map(normalizeAggregateTableNode);
325
+
326
+ if (!flags.json) {
327
+ printSummary(t('aggregate_table.list_success'), [
328
+ ['App', appType],
329
+ ['Count', String(aggregateTables.length)],
330
+ ]);
331
+ }
332
+
333
+ console.log(JSON.stringify(aggregateTables, null, 2));
334
+ }
335
+
336
+ async function runCreateEmpty(args) {
337
+ const { positional, flags } = parseFlags(args);
338
+ const appType = positional[0];
339
+ const name = positional[1];
340
+ if (!appType || !name) {
341
+ const { error } = require('../core/chalk');
342
+ error(t('aggregate_table.create_empty_usage'), { hint: t('aggregate_table.example_create_empty') });
343
+ return;
344
+ }
345
+
346
+ const authRef = await createAuthRef();
347
+ const contentLocale = resolveContentLocale({ locale: flags.locale, baseUrl: authRef.baseUrl });
348
+
349
+ const featureResult = await checkVirtualViewFeature(authRef, appType);
350
+ const featureEnabled = assertSuccess(featureResult, t('aggregate_table.check_feature'));
351
+ if (featureEnabled !== true) {
352
+ throw new Error(t('aggregate_table.feature_disabled'));
353
+ }
354
+
355
+ const createResult = await createEmptyVirtualView(authRef, appType, name);
356
+ const created = assertSuccess(createResult, t('aggregate_table.create_empty'));
357
+ const formUuid = (created && created.formUuid) || created;
358
+ const designUrl = buildDesignUrl(authRef.baseUrl, appType, formUuid, { fromNew: true });
359
+ const workbenchUrl = buildWorkbenchUrl(authRef.baseUrl, appType, formUuid);
360
+
361
+ printSummary(t('aggregate_table.create_empty_success'), [
362
+ ['App', appType],
363
+ ['Aggregate Table ID', formUuid],
364
+ ['Locale', contentLocale],
365
+ ['Design URL', designUrl],
366
+ ]);
367
+
368
+ console.log(JSON.stringify(withBrowserHandoff({
369
+ success: true,
370
+ appType,
371
+ aggregateTableId: formUuid,
372
+ formUuid,
373
+ name,
374
+ formType: FORM_TYPE_VIRTUAL_VIEW,
375
+ locale: contentLocale,
376
+ designUrl,
377
+ workbenchUrl,
378
+ }, designUrl, { stage: 'aggregate_table_create_empty_success', title: name }, flags.openMode)));
379
+ }
380
+
381
+ async function runInspect(args) {
382
+ const { positional, flags } = parseFlags(args);
383
+ const appType = positional[0];
384
+ const formUuid = positional[1];
385
+ if (!appType || !formUuid) {
386
+ const { error } = require('../core/chalk');
387
+ error(t('aggregate_table.inspect_usage'), { hint: t('aggregate_table.example_inspect') });
388
+ return;
389
+ }
390
+
391
+ const authRef = await createAuthRef();
392
+ const result = await getVirtualViewConfig(authRef, appType, formUuid);
393
+ const config = assertSuccess(result, t('aggregate_table.inspect'));
394
+ const summary = summarizeDesignConfig(config);
395
+
396
+ if (!flags.json) {
397
+ printSummary(t('aggregate_table.inspect_success'), [
398
+ ['App', appType],
399
+ ['Aggregate Table ID', formUuid],
400
+ ['Data Sources', String(summary.counts.relationForms)],
401
+ ['Relationships', String(summary.counts.relationships)],
402
+ ['Metrics', String(summary.counts.formulaFields)],
403
+ ['Validators', String(summary.counts.validators)],
404
+ ]);
405
+ }
406
+
407
+ console.log(JSON.stringify({
408
+ success: true,
409
+ appType,
410
+ aggregateTableId: formUuid,
411
+ formUuid,
412
+ summary,
413
+ config,
414
+ }, null, 2));
415
+ }
416
+
417
+ async function loadDesignForMutation(authRef, appType, formUuid, input, action) {
418
+ const rawConfig = readJsonInput(input);
419
+ const designInfo = normalizeDesignConfig(rawConfig, formUuid);
420
+ const currentResult = await getVirtualViewConfig(authRef, appType, formUuid);
421
+ const currentConfig = assertSuccess(currentResult, t('aggregate_table.inspect'));
422
+ const gmtModified = action === 'save'
423
+ ? currentConfig.stashGmtModified
424
+ : currentConfig.gmtModified;
425
+ return { designInfo, currentConfig, gmtModified };
426
+ }
427
+
428
+ async function runPreview(args) {
429
+ const { positional, flags } = parseFlags(args);
430
+ const appType = positional[0];
431
+ const formUuid = positional[1];
432
+ const input = positional[2];
433
+ if (!appType || !formUuid || !input) {
434
+ const { error } = require('../core/chalk');
435
+ error(t('aggregate_table.preview_usage'), { hint: t('aggregate_table.example_preview') });
436
+ return;
437
+ }
438
+
439
+ const authRef = await createAuthRef();
440
+ const designInfo = normalizeDesignConfig(readJsonInput(input), formUuid);
441
+ const result = await postVirtualViewDesign(authRef, appType, formUuid, designInfo, 'preview');
442
+ const rows = assertSuccess(result, t('aggregate_table.preview'));
443
+ const rowCount = Array.isArray(rows) ? rows.length : 0;
444
+
445
+ if (!flags.json) {
446
+ printSummary(t('aggregate_table.preview_success'), [
447
+ ['App', appType],
448
+ ['Aggregate Table ID', formUuid],
449
+ ['Rows', String(rowCount)],
450
+ ]);
451
+ }
452
+
453
+ console.log(JSON.stringify({
454
+ success: true,
455
+ appType,
456
+ aggregateTableId: formUuid,
457
+ formUuid,
458
+ rowCount,
459
+ rows,
460
+ }, null, 2));
461
+ }
462
+
463
+ async function runSaveOrPublish(args, action) {
464
+ const { positional, flags } = parseFlags(args);
465
+ const appType = positional[0];
466
+ const formUuid = positional[1];
467
+ const input = positional[2];
468
+ const isPublish = action === 'publish';
469
+ if (!appType || !formUuid || !input) {
470
+ const { error } = require('../core/chalk');
471
+ error(
472
+ isPublish ? t('aggregate_table.publish_usage') : t('aggregate_table.save_usage'),
473
+ { hint: isPublish ? t('aggregate_table.example_publish') : t('aggregate_table.example_save') }
474
+ );
475
+ return;
476
+ }
477
+
478
+ const authRef = await createAuthRef();
479
+ const { designInfo, gmtModified } = await loadDesignForMutation(authRef, appType, formUuid, input, action);
480
+ if (isPublish && designInfo.relationForms.length === 0) {
481
+ throw new Error(t('aggregate_table.publish_requires_source'));
482
+ }
483
+
484
+ const result = await postVirtualViewDesign(authRef, appType, formUuid, designInfo, action, gmtModified);
485
+ const content = assertSuccess(result, isPublish ? t('aggregate_table.publish') : t('aggregate_table.save'));
486
+ const designUrl = buildDesignUrl(authRef.baseUrl, appType, formUuid);
487
+ const workbenchUrl = buildWorkbenchUrl(authRef.baseUrl, appType, formUuid);
488
+
489
+ if (!flags.json) {
490
+ printSummary(isPublish ? t('aggregate_table.publish_success') : t('aggregate_table.save_success'), [
491
+ ['App', appType],
492
+ ['Aggregate Table ID', formUuid],
493
+ ['Design URL', designUrl],
494
+ ]);
495
+ }
496
+
497
+ console.log(JSON.stringify(withBrowserHandoff({
498
+ success: true,
499
+ action,
500
+ appType,
501
+ aggregateTableId: formUuid,
502
+ formUuid,
503
+ gmtModified: content && content.gmtModified,
504
+ response: content,
505
+ designUrl,
506
+ workbenchUrl,
507
+ }, designUrl, { stage: `aggregate_table_${action}_success`, title: formUuid }, flags.openMode), null, 2));
508
+ }
509
+
510
+ async function runStatus(args) {
511
+ const { positional, flags } = parseFlags(args);
512
+ const appType = positional[0];
513
+ const formUuid = positional[1];
514
+ if (!appType || !formUuid) {
515
+ const { error } = require('../core/chalk');
516
+ error(t('aggregate_table.status_usage'), { hint: t('aggregate_table.example_status') });
517
+ return;
518
+ }
519
+
520
+ const authRef = await createAuthRef();
521
+ const result = await queryBuildState(authRef, appType, formUuid);
522
+ const content = assertSuccess(result, t('aggregate_table.status'));
523
+
524
+ if (!flags.json) {
525
+ printSummary(t('aggregate_table.status_success'), [
526
+ ['App', appType],
527
+ ['Aggregate Table ID', formUuid],
528
+ ['Status', (content && content.status) || String(content || '')],
529
+ ]);
530
+ }
531
+
532
+ console.log(JSON.stringify({
533
+ success: true,
534
+ appType,
535
+ aggregateTableId: formUuid,
536
+ formUuid,
537
+ status: content && content.status,
538
+ result: content,
539
+ }, null, 2));
540
+ }
541
+
542
+ async function run(args) {
543
+ if (!args || args.length === 0 || hasHelpFlag(args)) {
544
+ printUsage();
545
+ return;
546
+ }
547
+
548
+ const subCommand = args[0];
549
+ const subArgs = args.slice(1);
550
+ switch (subCommand) {
551
+ case 'list':
552
+ return runList(subArgs);
553
+ case 'create':
554
+ case 'create-empty':
555
+ return runCreateEmpty(subArgs);
556
+ case 'inspect':
557
+ case 'get':
558
+ return runInspect(subArgs);
559
+ case 'preview':
560
+ return runPreview(subArgs);
561
+ case 'save':
562
+ return runSaveOrPublish(subArgs, 'save');
563
+ case 'publish':
564
+ return runSaveOrPublish(subArgs, 'publish');
565
+ case 'status':
566
+ return runStatus(subArgs);
567
+ default: {
568
+ const { error } = require('../core/chalk');
569
+ error(t('aggregate_table.unknown_subcommand', subCommand), { hint: t('aggregate_table.usage') });
570
+ return null;
571
+ }
572
+ }
573
+ }
574
+
575
+ module.exports = {
576
+ DESIGN_KEYS,
577
+ FORM_TYPE_VIRTUAL_VIEW,
578
+ buildCreateEmptyPostData,
579
+ buildDesignPostData,
580
+ filterAggregateTables,
581
+ normalizeAggregateTableNode,
582
+ normalizeDesignConfig,
583
+ parseFlags,
584
+ readJsonInput,
585
+ summarizeDesignConfig,
586
+ run,
587
+ __api__: {
588
+ checkVirtualViewFeature,
589
+ createEmptyVirtualView,
590
+ getVirtualViewConfig,
591
+ postVirtualViewDesign,
592
+ queryBuildState,
593
+ },
594
+ };