openyida 2026.5.21 → 2026.5.25-beta.0
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 +3 -0
- package/bin/yida.js +7 -1
- package/lib/app/app-list.js +20 -1
- package/lib/app/externalize-form.js +642 -0
- package/lib/core/command-manifest.js +3 -0
- package/lib/core/copy.js +50 -8
- package/lib/core/locales/ar.js +1 -0
- package/lib/core/locales/de.js +1 -0
- package/lib/core/locales/en.js +1 -0
- package/lib/core/locales/es.js +1 -0
- package/lib/core/locales/fr.js +1 -0
- package/lib/core/locales/hi.js +1 -0
- package/lib/core/locales/ja.js +1 -0
- package/lib/core/locales/ko.js +1 -0
- package/lib/core/locales/pt.js +1 -0
- package/lib/core/locales/vi.js +1 -0
- package/lib/core/locales/zh-HK.js +1 -0
- package/lib/core/locales/zh.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,6 +340,9 @@ For overseas apps, pass `--locale en_US` or `--locale ja_JP` on creation command
|
|
|
340
340
|
| `openyida verify-short-url <appType> <formUuid> <url>` | Verify a short URL |
|
|
341
341
|
| `openyida save-share-config <appType> <formUuid> <url> <isOpen> [openAuth]` | Save public access or sharing configuration |
|
|
342
342
|
| `openyida get-page-config <appType> <formUuid>` | Query public access or sharing configuration |
|
|
343
|
+
| `openyida externalize-form <appType> <formUuid> [--schema-file file]` | Assess public-access blockers and generate external-safe mirror fields |
|
|
344
|
+
|
|
345
|
+
`openyida externalize-form` is useful when a form contains fields such as `AssociationFormField`, `EmployeeField`, or `DepartmentSelectField` that depend on internal organization permissions. It produces a report plus optional `--mirror-fields-output` JSON that can be used with `openyida create-form create` to build a separate public intake form while keeping the internal form and its association fields private.
|
|
343
346
|
|
|
344
347
|
### Workflow, Reports, and Integrations
|
|
345
348
|
|
package/bin/yida.js
CHANGED
|
@@ -426,7 +426,7 @@ async function main() {
|
|
|
426
426
|
|
|
427
427
|
case 'copy': {
|
|
428
428
|
const { run } = require('../lib/core/copy');
|
|
429
|
-
run();
|
|
429
|
+
run(args);
|
|
430
430
|
break;
|
|
431
431
|
}
|
|
432
432
|
|
|
@@ -717,6 +717,12 @@ async function main() {
|
|
|
717
717
|
break;
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
case 'externalize-form': {
|
|
721
|
+
const { run } = require('../lib/app/externalize-form');
|
|
722
|
+
await run(args);
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
|
|
720
726
|
case 'update-form-config': {
|
|
721
727
|
if (args.length < 4) {
|
|
722
728
|
warn(t('cli.form_config_usage'));
|
package/lib/app/app-list.js
CHANGED
|
@@ -19,7 +19,6 @@ const {
|
|
|
19
19
|
httpGet,
|
|
20
20
|
requestWithAutoLogin,
|
|
21
21
|
} = require('../core/utils');
|
|
22
|
-
const { t } = require('../core/i18n');
|
|
23
22
|
|
|
24
23
|
const API_PATH = '/query/app/getAppList.json';
|
|
25
24
|
|
|
@@ -90,11 +89,31 @@ function formatApp(app) {
|
|
|
90
89
|
};
|
|
91
90
|
}
|
|
92
91
|
|
|
92
|
+
function hasHelpFlag(args) {
|
|
93
|
+
return (args || []).includes('--help') || (args || []).includes('-h');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function printUsage() {
|
|
97
|
+
process.stderr.write([
|
|
98
|
+
'Usage: openyida app-list [--size N]',
|
|
99
|
+
'',
|
|
100
|
+
'Options:',
|
|
101
|
+
' --size N Page size used when fetching apps, default: 20',
|
|
102
|
+
' --help, -h Show this help',
|
|
103
|
+
'',
|
|
104
|
+
].join('\n'));
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
/**
|
|
94
108
|
* app-list 命令主入口
|
|
95
109
|
* @param {string[]} args
|
|
96
110
|
*/
|
|
97
111
|
async function run(args) {
|
|
112
|
+
if (hasHelpFlag(args)) {
|
|
113
|
+
printUsage();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
const pageSizeIndex = args.indexOf('--size');
|
|
99
118
|
const pageSize = pageSizeIndex !== -1 && args[pageSizeIndex + 1]
|
|
100
119
|
? parseInt(args[pageSizeIndex + 1], 10)
|
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
loadCookieData,
|
|
7
|
+
triggerLogin,
|
|
8
|
+
resolveBaseUrl,
|
|
9
|
+
httpGet,
|
|
10
|
+
requestWithAutoLogin,
|
|
11
|
+
} = require('../core/utils');
|
|
12
|
+
|
|
13
|
+
const FIELD_COMPONENT_NAMES = new Set([
|
|
14
|
+
'TextField',
|
|
15
|
+
'TextareaField',
|
|
16
|
+
'SelectField',
|
|
17
|
+
'DateField',
|
|
18
|
+
'NumberField',
|
|
19
|
+
'RadioField',
|
|
20
|
+
'CheckboxField',
|
|
21
|
+
'EmployeeField',
|
|
22
|
+
'PhoneField',
|
|
23
|
+
'EmailField',
|
|
24
|
+
'CascadeSelectField',
|
|
25
|
+
'ImageField',
|
|
26
|
+
'AttachmentField',
|
|
27
|
+
'TableField',
|
|
28
|
+
'MultiSelectField',
|
|
29
|
+
'DepartmentSelectField',
|
|
30
|
+
'AssociationFormField',
|
|
31
|
+
'CountrySelectField',
|
|
32
|
+
'CitySelectField',
|
|
33
|
+
'RateField',
|
|
34
|
+
'SignatureField',
|
|
35
|
+
'SerialNumberField',
|
|
36
|
+
'AddressField',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const PUBLIC_BLOCKED_TYPES = new Set([
|
|
40
|
+
'AssociationFormField',
|
|
41
|
+
'EmployeeField',
|
|
42
|
+
'DepartmentSelectField',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const REVIEW_TYPES = new Set([
|
|
46
|
+
'AttachmentField',
|
|
47
|
+
'ImageField',
|
|
48
|
+
'SignatureField',
|
|
49
|
+
'TableField',
|
|
50
|
+
'CascadeSelectField',
|
|
51
|
+
'CountrySelectField',
|
|
52
|
+
'CitySelectField',
|
|
53
|
+
'AddressField',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const SAFE_CREATE_FORM_TYPES = new Set([
|
|
57
|
+
'TextField',
|
|
58
|
+
'TextareaField',
|
|
59
|
+
'SelectField',
|
|
60
|
+
'DateField',
|
|
61
|
+
'NumberField',
|
|
62
|
+
'RadioField',
|
|
63
|
+
'CheckboxField',
|
|
64
|
+
'PhoneField',
|
|
65
|
+
'EmailField',
|
|
66
|
+
'MultiSelectField',
|
|
67
|
+
'RateField',
|
|
68
|
+
'AttachmentField',
|
|
69
|
+
'ImageField',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
function readOption(args, names, fallback = '') {
|
|
73
|
+
const optionNames = Array.isArray(names) ? names : [names];
|
|
74
|
+
for (const name of optionNames) {
|
|
75
|
+
const index = args.indexOf(name);
|
|
76
|
+
if (index !== -1 && args[index + 1] && !args[index + 1].startsWith('--')) {
|
|
77
|
+
return args[index + 1];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseArgs(args) {
|
|
84
|
+
const parsed = {
|
|
85
|
+
appType: '',
|
|
86
|
+
formUuid: '',
|
|
87
|
+
schemaFile: '',
|
|
88
|
+
output: '',
|
|
89
|
+
mirrorFieldsOutput: '',
|
|
90
|
+
format: 'json',
|
|
91
|
+
target: 'open',
|
|
92
|
+
mirrorTitle: '',
|
|
93
|
+
help: false,
|
|
94
|
+
};
|
|
95
|
+
const positional = [];
|
|
96
|
+
|
|
97
|
+
for (let index = 0; index < args.length; index++) {
|
|
98
|
+
const arg = args[index];
|
|
99
|
+
if (arg === '--help' || arg === '-h') {
|
|
100
|
+
parsed.help = true;
|
|
101
|
+
} else if (arg === '--schema-file' || arg === '--schema') {
|
|
102
|
+
parsed.schemaFile = readOption(args, arg);
|
|
103
|
+
index++;
|
|
104
|
+
} else if (arg === '--output' || arg === '-o') {
|
|
105
|
+
parsed.output = readOption(args, arg);
|
|
106
|
+
index++;
|
|
107
|
+
} else if (arg === '--mirror-fields-output' || arg === '--fields-output') {
|
|
108
|
+
parsed.mirrorFieldsOutput = readOption(args, arg);
|
|
109
|
+
index++;
|
|
110
|
+
} else if (arg === '--format') {
|
|
111
|
+
parsed.format = readOption(args, arg, 'json');
|
|
112
|
+
index++;
|
|
113
|
+
} else if (arg === '--target') {
|
|
114
|
+
parsed.target = readOption(args, arg, 'open');
|
|
115
|
+
index++;
|
|
116
|
+
} else if (arg === '--mirror-title') {
|
|
117
|
+
parsed.mirrorTitle = readOption(args, arg);
|
|
118
|
+
index++;
|
|
119
|
+
} else if (arg.startsWith('--')) {
|
|
120
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
121
|
+
} else {
|
|
122
|
+
positional.push(arg);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
parsed.appType = positional[0] || '';
|
|
127
|
+
parsed.formUuid = positional[1] || '';
|
|
128
|
+
if (!['json', 'markdown'].includes(parsed.format)) {
|
|
129
|
+
throw new Error(`Unsupported format: ${parsed.format}`);
|
|
130
|
+
}
|
|
131
|
+
if (!['open', 'share'].includes(parsed.target)) {
|
|
132
|
+
throw new Error(`Unsupported target: ${parsed.target}`);
|
|
133
|
+
}
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function printUsage() {
|
|
138
|
+
const lines = [
|
|
139
|
+
'Usage: openyida externalize-form <appType> <formUuid> [options]',
|
|
140
|
+
'',
|
|
141
|
+
'Options:',
|
|
142
|
+
' --schema-file <file> Read a saved get-schema JSON file instead of fetching remotely',
|
|
143
|
+
' --output, -o <file> Write the report to a file',
|
|
144
|
+
' --mirror-fields-output <file> Write external-safe create-form fields JSON',
|
|
145
|
+
' --format json|markdown Output format, default: json',
|
|
146
|
+
' --target open|share Access target, default: open',
|
|
147
|
+
' --mirror-title <title> Title used in suggested create-form command',
|
|
148
|
+
'',
|
|
149
|
+
'Examples:',
|
|
150
|
+
' openyida externalize-form APP_XXX FORM-XXX --schema-file .cache/schema.json',
|
|
151
|
+
' openyida externalize-form APP_XXX FORM-XXX --mirror-fields-output .cache/external-fields.json',
|
|
152
|
+
];
|
|
153
|
+
process.stderr.write(`${lines.join('\n')}\n`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function extractText(value) {
|
|
157
|
+
if (value === null || value === undefined) {
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === 'string') {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
164
|
+
return String(value);
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
return value.map(extractText).filter(Boolean).join(', ');
|
|
168
|
+
}
|
|
169
|
+
if (typeof value === 'object') {
|
|
170
|
+
return extractText(
|
|
171
|
+
value.zh_CN ||
|
|
172
|
+
value.en_US ||
|
|
173
|
+
value.ja_JP ||
|
|
174
|
+
value.value ||
|
|
175
|
+
value.text ||
|
|
176
|
+
value.label ||
|
|
177
|
+
value.name ||
|
|
178
|
+
value.title ||
|
|
179
|
+
''
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isRequired(props) {
|
|
186
|
+
if (!props) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (props.required === true) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const validation = Array.isArray(props.validation) ? props.validation : [];
|
|
193
|
+
return validation.some(rule => rule && rule.type === 'required');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getPages(schemaResult) {
|
|
197
|
+
if (schemaResult && schemaResult.content && Array.isArray(schemaResult.content.pages)) {
|
|
198
|
+
return schemaResult.content.pages;
|
|
199
|
+
}
|
|
200
|
+
if (schemaResult && Array.isArray(schemaResult.pages)) {
|
|
201
|
+
return schemaResult.pages;
|
|
202
|
+
}
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function collectFieldNodes(schemaResult) {
|
|
207
|
+
const fields = [];
|
|
208
|
+
const pages = getPages(schemaResult);
|
|
209
|
+
|
|
210
|
+
function traverse(node, parents) {
|
|
211
|
+
if (!node) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const props = node.props || {};
|
|
215
|
+
const label = extractText(props.label);
|
|
216
|
+
const nextParents = FIELD_COMPONENT_NAMES.has(node.componentName) && label
|
|
217
|
+
? parents.concat(label)
|
|
218
|
+
: parents;
|
|
219
|
+
|
|
220
|
+
if (FIELD_COMPONENT_NAMES.has(node.componentName)) {
|
|
221
|
+
fields.push({
|
|
222
|
+
componentName: node.componentName,
|
|
223
|
+
props,
|
|
224
|
+
label,
|
|
225
|
+
fieldId: props.fieldId || '',
|
|
226
|
+
required: isRequired(props),
|
|
227
|
+
behavior: props.behavior || '',
|
|
228
|
+
path: nextParents.join(' > '),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
233
|
+
children.forEach(child => traverse(child, nextParents));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
pages.forEach((page) => {
|
|
237
|
+
const roots = page && page.componentsTree ? page.componentsTree : [];
|
|
238
|
+
roots.forEach(root => traverse(root, []));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return fields;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getRisk(field, target) {
|
|
245
|
+
if (target === 'share') {
|
|
246
|
+
if (field.componentName === 'AssociationFormField') {
|
|
247
|
+
return {
|
|
248
|
+
level: 'review',
|
|
249
|
+
reason: 'Association fields require internal form data permission and should be verified for org-share scenarios.',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { level: 'safe', reason: 'Internal share keeps the visitor inside the organization permission boundary.' };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (PUBLIC_BLOCKED_TYPES.has(field.componentName)) {
|
|
256
|
+
return {
|
|
257
|
+
level: 'blocked',
|
|
258
|
+
reason: `${field.componentName} depends on authenticated organization data and is not reliable for anonymous public access.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (REVIEW_TYPES.has(field.componentName)) {
|
|
262
|
+
return {
|
|
263
|
+
level: 'review',
|
|
264
|
+
reason: `${field.componentName} may need a browser verification or a simpler external intake field.`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return { level: 'safe', reason: 'Primitive input field, suitable for external intake.' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function associationTarget(props) {
|
|
271
|
+
const associationForm = props && props.associationForm ? props.associationForm : {};
|
|
272
|
+
return {
|
|
273
|
+
appType: associationForm.appType || '',
|
|
274
|
+
formUuid: associationForm.formUuid || '',
|
|
275
|
+
formTitle: associationForm.formTitle || '',
|
|
276
|
+
mainFieldId: associationForm.mainFieldId || '',
|
|
277
|
+
mainFieldLabel: extractText(associationForm.mainFieldLabel),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function planField(field, target) {
|
|
282
|
+
const risk = getRisk(field, target);
|
|
283
|
+
const plan = {
|
|
284
|
+
label: field.label || field.fieldId || field.componentName,
|
|
285
|
+
fieldId: field.fieldId,
|
|
286
|
+
componentName: field.componentName,
|
|
287
|
+
required: field.required,
|
|
288
|
+
behavior: field.behavior,
|
|
289
|
+
path: field.path,
|
|
290
|
+
riskLevel: risk.level,
|
|
291
|
+
reason: risk.reason,
|
|
292
|
+
externalStrategy: 'keep',
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (field.componentName === 'AssociationFormField') {
|
|
296
|
+
plan.associationTarget = associationTarget(field.props);
|
|
297
|
+
plan.externalStrategy = 'snapshot-and-resolve-internal';
|
|
298
|
+
} else if (field.componentName === 'EmployeeField') {
|
|
299
|
+
plan.externalStrategy = 'collect-name-or-contact';
|
|
300
|
+
} else if (field.componentName === 'DepartmentSelectField') {
|
|
301
|
+
plan.externalStrategy = 'collect-department-text';
|
|
302
|
+
} else if (risk.level === 'review') {
|
|
303
|
+
plan.externalStrategy = 'verify-or-simplify';
|
|
304
|
+
}
|
|
305
|
+
return plan;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function mirrorLabel(label, suffix) {
|
|
309
|
+
if (!label) {
|
|
310
|
+
return suffix;
|
|
311
|
+
}
|
|
312
|
+
return `${label}${suffix}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function associationSnapshotLabel(plan) {
|
|
316
|
+
const target = plan.associationTarget || {};
|
|
317
|
+
const targetMainLabel = target.mainFieldLabel || '';
|
|
318
|
+
if (targetMainLabel) {
|
|
319
|
+
return targetMainLabel;
|
|
320
|
+
}
|
|
321
|
+
if (plan.label && plan.label.endsWith('名称')) {
|
|
322
|
+
return plan.label;
|
|
323
|
+
}
|
|
324
|
+
return mirrorLabel(plan.label, '名称');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function mirrorFieldForPlan(plan) {
|
|
328
|
+
const base = {
|
|
329
|
+
label: plan.label,
|
|
330
|
+
required: plan.required,
|
|
331
|
+
sourceFieldId: plan.fieldId,
|
|
332
|
+
sourceComponentName: plan.componentName,
|
|
333
|
+
sourceStrategy: plan.externalStrategy,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
if (plan.componentName === 'AssociationFormField') {
|
|
337
|
+
const target = plan.associationTarget || {};
|
|
338
|
+
return [
|
|
339
|
+
{
|
|
340
|
+
type: 'TextField',
|
|
341
|
+
label: associationSnapshotLabel(plan),
|
|
342
|
+
required: plan.required,
|
|
343
|
+
sourceFieldId: plan.fieldId,
|
|
344
|
+
sourceComponentName: plan.componentName,
|
|
345
|
+
sourceStrategy: 'association-main-field-snapshot',
|
|
346
|
+
targetFormUuid: target.formUuid,
|
|
347
|
+
targetMainFieldId: target.mainFieldId,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
type: 'TextField',
|
|
351
|
+
label: mirrorLabel(plan.label, '业务标识'),
|
|
352
|
+
required: false,
|
|
353
|
+
sourceFieldId: plan.fieldId,
|
|
354
|
+
sourceComponentName: plan.componentName,
|
|
355
|
+
sourceStrategy: 'association-business-key-for-internal-resolution',
|
|
356
|
+
targetFormUuid: target.formUuid,
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (plan.componentName === 'EmployeeField') {
|
|
362
|
+
return [
|
|
363
|
+
{ ...base, type: 'TextField', label: mirrorLabel(plan.label, '姓名') },
|
|
364
|
+
{ ...base, type: 'TextField', label: mirrorLabel(plan.label, '联系方式'), required: false },
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (plan.componentName === 'DepartmentSelectField') {
|
|
369
|
+
return [{ ...base, type: 'TextField', label: mirrorLabel(plan.label, '名称') }];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (SAFE_CREATE_FORM_TYPES.has(plan.componentName)) {
|
|
373
|
+
return [{ ...base, type: plan.componentName }];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (plan.componentName === 'SerialNumberField') {
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return [{ ...base, type: 'TextareaField', label: mirrorLabel(plan.label, '说明'), required: false }];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function buildMirrorFields(fields) {
|
|
384
|
+
const mirrorFields = [];
|
|
385
|
+
fields.forEach((field) => {
|
|
386
|
+
mirrorFields.push(...mirrorFieldForPlan(field));
|
|
387
|
+
});
|
|
388
|
+
return mirrorFields;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function summarize(fields) {
|
|
392
|
+
const summary = {
|
|
393
|
+
totalFields: fields.length,
|
|
394
|
+
safeFields: 0,
|
|
395
|
+
reviewFields: 0,
|
|
396
|
+
blockedFields: 0,
|
|
397
|
+
associationFields: 0,
|
|
398
|
+
authBoundFields: 0,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
fields.forEach((field) => {
|
|
402
|
+
if (field.riskLevel === 'safe') {
|
|
403
|
+
summary.safeFields++;
|
|
404
|
+
} else if (field.riskLevel === 'review') {
|
|
405
|
+
summary.reviewFields++;
|
|
406
|
+
} else if (field.riskLevel === 'blocked') {
|
|
407
|
+
summary.blockedFields++;
|
|
408
|
+
}
|
|
409
|
+
if (field.componentName === 'AssociationFormField') {
|
|
410
|
+
summary.associationFields++;
|
|
411
|
+
}
|
|
412
|
+
if (PUBLIC_BLOCKED_TYPES.has(field.componentName)) {
|
|
413
|
+
summary.authBoundFields++;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
return summary;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function buildRecommendedActions(parsed, report) {
|
|
420
|
+
const actions = [
|
|
421
|
+
'Create a separate external intake form from mirrorFields instead of exposing the internal form directly.',
|
|
422
|
+
'Keep association fields in the internal form and resolve them after submission by business key or manual review.',
|
|
423
|
+
'Copy human-readable snapshots into the external form so public visitors never need target-form permissions.',
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
if (parsed.mirrorFieldsOutput) {
|
|
427
|
+
const title = parsed.mirrorTitle || `${report.formTitle || 'External Intake'} External`;
|
|
428
|
+
actions.unshift(
|
|
429
|
+
`Create mirror form: openyida create-form create ${parsed.appType} "${title}" ${parsed.mirrorFieldsOutput}`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (report.summary.blockedFields === 0 && report.summary.reviewFields === 0) {
|
|
434
|
+
actions.unshift('No blocked fields were found; public access can still be verified with save-share-config and Chrome.');
|
|
435
|
+
}
|
|
436
|
+
return actions;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function inferFormTitle(schemaResult, formUuid) {
|
|
440
|
+
const content = schemaResult && schemaResult.content ? schemaResult.content : schemaResult;
|
|
441
|
+
return extractText(
|
|
442
|
+
content && (
|
|
443
|
+
content.formName ||
|
|
444
|
+
content.formTitle ||
|
|
445
|
+
content.title ||
|
|
446
|
+
content.name
|
|
447
|
+
)
|
|
448
|
+
) || formUuid;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function analyzeSchema(schemaResult, options = {}) {
|
|
452
|
+
const target = options.target || 'open';
|
|
453
|
+
const fields = collectFieldNodes(schemaResult).map(field => planField(field, target));
|
|
454
|
+
const report = {
|
|
455
|
+
success: true,
|
|
456
|
+
appType: options.appType || '',
|
|
457
|
+
formUuid: options.formUuid || '',
|
|
458
|
+
formTitle: inferFormTitle(schemaResult, options.formUuid || ''),
|
|
459
|
+
target,
|
|
460
|
+
source: options.source || 'schema',
|
|
461
|
+
summary: summarize(fields),
|
|
462
|
+
fields,
|
|
463
|
+
mirrorFields: buildMirrorFields(fields),
|
|
464
|
+
recommendedActions: [],
|
|
465
|
+
notes: [
|
|
466
|
+
'OpenYida cannot relax Yida platform permission boundaries for anonymous visitors.',
|
|
467
|
+
'This plan keeps internal association fields private and generates external-safe snapshot fields.',
|
|
468
|
+
'For fully automated sync, add an internal automation or API worker that resolves submitted business keys.',
|
|
469
|
+
],
|
|
470
|
+
};
|
|
471
|
+
report.recommendedActions = buildRecommendedActions(options, report);
|
|
472
|
+
return report;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderMarkdown(report) {
|
|
476
|
+
const lines = [
|
|
477
|
+
`# External Access Plan: ${report.formTitle || report.formUuid}`,
|
|
478
|
+
'',
|
|
479
|
+
`- App: ${report.appType || '-'}`,
|
|
480
|
+
`- Form: ${report.formUuid || '-'}`,
|
|
481
|
+
`- Target: ${report.target}`,
|
|
482
|
+
`- Total fields: ${report.summary.totalFields}`,
|
|
483
|
+
`- Blocked fields: ${report.summary.blockedFields}`,
|
|
484
|
+
`- Review fields: ${report.summary.reviewFields}`,
|
|
485
|
+
'',
|
|
486
|
+
'## Fields',
|
|
487
|
+
'',
|
|
488
|
+
'| Label | Type | Risk | Strategy | Reason |',
|
|
489
|
+
'| --- | --- | --- | --- | --- |',
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
report.fields.forEach((field) => {
|
|
493
|
+
lines.push(
|
|
494
|
+
`| ${field.label || '-'} | ${field.componentName} | ${field.riskLevel} | ${field.externalStrategy} | ${field.reason} |`
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
lines.push('', '## Recommended Actions', '');
|
|
499
|
+
report.recommendedActions.forEach(action => lines.push(`- ${action}`));
|
|
500
|
+
lines.push('', '## Mirror Fields', '', '```json');
|
|
501
|
+
lines.push(JSON.stringify(report.mirrorFields, null, 2));
|
|
502
|
+
lines.push('```', '');
|
|
503
|
+
return lines.join('\n');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function writeJson(filePath, data) {
|
|
507
|
+
const resolved = path.resolve(filePath);
|
|
508
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
509
|
+
fs.writeFileSync(resolved, JSON.stringify(data, null, 2), 'utf-8');
|
|
510
|
+
return resolved;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function writeText(filePath, text) {
|
|
514
|
+
const resolved = path.resolve(filePath);
|
|
515
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
516
|
+
fs.writeFileSync(resolved, text, 'utf-8');
|
|
517
|
+
return resolved;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function resolveOutputPath(filePath) {
|
|
521
|
+
const resolved = path.resolve(filePath);
|
|
522
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
523
|
+
return resolved;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function loadSchemaFile(filePath) {
|
|
527
|
+
const raw = fs.readFileSync(path.resolve(filePath), 'utf-8');
|
|
528
|
+
return JSON.parse(raw);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function createAuthRef() {
|
|
532
|
+
let cookieData = loadCookieData();
|
|
533
|
+
if (!cookieData) {
|
|
534
|
+
cookieData = triggerLogin();
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
csrfToken: cookieData.csrf_token,
|
|
538
|
+
cookies: cookieData.cookies,
|
|
539
|
+
baseUrl: resolveBaseUrl(cookieData),
|
|
540
|
+
cookieData,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function fetchSchema(appType, formUuid, authRef) {
|
|
545
|
+
return requestWithAutoLogin((auth) => {
|
|
546
|
+
return httpGet(
|
|
547
|
+
auth.baseUrl,
|
|
548
|
+
`/alibaba/web/${appType}/_view/query/formdesign/getFormSchema.json`,
|
|
549
|
+
{ formUuid, schemaVersion: 'V5' },
|
|
550
|
+
auth.cookies
|
|
551
|
+
);
|
|
552
|
+
}, authRef);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function ensureSuccessfulSchema(result) {
|
|
556
|
+
if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
|
|
557
|
+
const message = result && (result.errorMsg || result.message)
|
|
558
|
+
? result.errorMsg || result.message
|
|
559
|
+
: 'Failed to fetch form schema';
|
|
560
|
+
throw new Error(message);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function loadSchema(parsed) {
|
|
565
|
+
if (parsed.schemaFile) {
|
|
566
|
+
return {
|
|
567
|
+
schema: loadSchemaFile(parsed.schemaFile),
|
|
568
|
+
source: path.resolve(parsed.schemaFile),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
const authRef = createAuthRef();
|
|
572
|
+
const schema = await fetchSchema(parsed.appType, parsed.formUuid, authRef);
|
|
573
|
+
ensureSuccessfulSchema(schema);
|
|
574
|
+
return { schema, source: 'remote' };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async function run(args) {
|
|
578
|
+
let parsed;
|
|
579
|
+
try {
|
|
580
|
+
parsed = parseArgs(args || []);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
console.error(error.message);
|
|
583
|
+
printUsage();
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (parsed.help) {
|
|
588
|
+
printUsage();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (!parsed.appType || !parsed.formUuid) {
|
|
592
|
+
printUsage();
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
try {
|
|
597
|
+
const loaded = await loadSchema(parsed);
|
|
598
|
+
const report = analyzeSchema(loaded.schema, {
|
|
599
|
+
...parsed,
|
|
600
|
+
source: loaded.source,
|
|
601
|
+
});
|
|
602
|
+
const files = {};
|
|
603
|
+
|
|
604
|
+
if (parsed.mirrorFieldsOutput) {
|
|
605
|
+
files.mirrorFields = writeJson(parsed.mirrorFieldsOutput, report.mirrorFields);
|
|
606
|
+
}
|
|
607
|
+
if (parsed.output) {
|
|
608
|
+
files.report = resolveOutputPath(parsed.output);
|
|
609
|
+
}
|
|
610
|
+
report.files = files;
|
|
611
|
+
if (parsed.output) {
|
|
612
|
+
if (parsed.format === 'markdown') {
|
|
613
|
+
writeText(files.report, renderMarkdown(report));
|
|
614
|
+
} else {
|
|
615
|
+
writeJson(files.report, report);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const finalOutput = parsed.format === 'markdown'
|
|
620
|
+
? renderMarkdown(report)
|
|
621
|
+
: JSON.stringify(report, null, 2);
|
|
622
|
+
console.log(finalOutput);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error(`externalize-form failed: ${error.message}`);
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
module.exports = {
|
|
630
|
+
parseArgs,
|
|
631
|
+
collectFieldNodes,
|
|
632
|
+
analyzeSchema,
|
|
633
|
+
renderMarkdown,
|
|
634
|
+
run,
|
|
635
|
+
_private: {
|
|
636
|
+
extractText,
|
|
637
|
+
isRequired,
|
|
638
|
+
planField,
|
|
639
|
+
buildMirrorFields,
|
|
640
|
+
summarize,
|
|
641
|
+
},
|
|
642
|
+
};
|
|
@@ -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/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: 'إضافة رسم بياني إلى تقرير موجود',
|
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',
|
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',
|
package/lib/core/locales/es.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'Verificar URL corta',
|
|
54
54
|
cmd_save_share: 'Guardar configuración de acceso público / compartir',
|
|
55
55
|
cmd_get_page_config: 'Consultar configuración de acceso público',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'Informes',
|
|
57
58
|
cmd_create_report: 'Crear informe Yida',
|
|
58
59
|
cmd_append_chart: 'Agregar gráfico a informe existente',
|
package/lib/core/locales/fr.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'Vérifier l\'URL courte',
|
|
54
54
|
cmd_save_share: 'Enregistrer la configuration d\'accès public / partage',
|
|
55
55
|
cmd_get_page_config: 'Consulter la configuration d\'accès public',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'Rapports',
|
|
57
58
|
cmd_create_report: 'Créer un rapport Yida',
|
|
58
59
|
cmd_append_chart: 'Ajouter un graphique à un rapport existant',
|
package/lib/core/locales/hi.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'शॉर्ट 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: 'मौजूदा रिपोर्ट में चार्ट जोड़ें',
|
package/lib/core/locales/ja.js
CHANGED
|
@@ -55,6 +55,7 @@ module.exports = {
|
|
|
55
55
|
cmd_verify_url: '短縮 URL を検証',
|
|
56
56
|
cmd_save_share: '公開アクセス / 共有設定を保存',
|
|
57
57
|
cmd_get_page_config: 'ページ公開アクセス設定を照会',
|
|
58
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
58
59
|
group_report: 'レポート',
|
|
59
60
|
cmd_create_report: '宜搭レポートを作成',
|
|
60
61
|
cmd_append_chart: '既存レポートにチャートを追加',
|
package/lib/core/locales/ko.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: '단축 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: '기존 보고서에 차트 추가',
|
package/lib/core/locales/pt.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'Verificar URL curta',
|
|
54
54
|
cmd_save_share: 'Salvar configuração de acesso público / compartilhamento',
|
|
55
55
|
cmd_get_page_config: 'Consultar configuração de acesso público',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'Relatórios',
|
|
57
58
|
cmd_create_report: 'Criar relatório Yida',
|
|
58
59
|
cmd_append_chart: 'Adicionar gráfico a relatório existente',
|
package/lib/core/locales/vi.js
CHANGED
|
@@ -53,6 +53,7 @@ module.exports = {
|
|
|
53
53
|
cmd_verify_url: 'Xác minh URL ngắn',
|
|
54
54
|
cmd_save_share: 'Lưu cấu hình truy cập công khai / chia sẻ',
|
|
55
55
|
cmd_get_page_config: 'Truy vấn cấu hình truy cập công khai',
|
|
56
|
+
cmd_externalize_form: 'Plan external access-safe mirror fields',
|
|
56
57
|
group_report: 'Báo cáo',
|
|
57
58
|
cmd_create_report: 'Tạo báo cáo Yida',
|
|
58
59
|
cmd_append_chart: 'Thêm biểu đồ vào báo cáo hiện có',
|
package/lib/core/locales/zh.js
CHANGED