@lark-apaas/miaoda-cli 0.1.3 → 0.1.4
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/dist/api/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +398 -55
- package/dist/api/db/client.js +155 -28
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/schemas.js +32 -32
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +12 -12
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_destructive.js +67 -0
- package/dist/cli/handlers/db/_env.js +26 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +32 -31
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +234 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +413 -0
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +32 -30
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +12 -12
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +19 -19
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
|
@@ -4,13 +4,13 @@ exports.registerPluginCommands = registerPluginCommands;
|
|
|
4
4
|
const index_1 = require("../../../cli/handlers/plugin/index");
|
|
5
5
|
function registerPluginCommands(program) {
|
|
6
6
|
const pluginCmd = program
|
|
7
|
-
.command(
|
|
8
|
-
.description(
|
|
9
|
-
.usage(
|
|
7
|
+
.command('plugin')
|
|
8
|
+
.description('插件管理:安装/更新/移除插件包,查询 capability 实例')
|
|
9
|
+
.usage('<command> [flags]');
|
|
10
10
|
pluginCmd.action(() => {
|
|
11
11
|
pluginCmd.outputHelp();
|
|
12
12
|
});
|
|
13
|
-
pluginCmd.addHelpText(
|
|
13
|
+
pluginCmd.addHelpText('after', `
|
|
14
14
|
概念
|
|
15
15
|
- 插件包 (plugin package):.tgz 包,释放到 ./node_modules/<name>
|
|
16
16
|
- actionPlugins:./package.json 里的字段,记录 { "@scope/name": "version" },作为
|
|
@@ -26,10 +26,10 @@ function registerPluginCommands(program) {
|
|
|
26
26
|
- remove / list / list-packages:纯本地 FS,不走网络
|
|
27
27
|
`);
|
|
28
28
|
pluginCmd
|
|
29
|
-
.command(
|
|
30
|
-
.description(
|
|
31
|
-
.argument(
|
|
32
|
-
.addHelpText(
|
|
29
|
+
.command('install')
|
|
30
|
+
.description('拉取插件包并安装到当前应用项目')
|
|
31
|
+
.argument('<names...>', '插件名,格式 @scope/name 或 @scope/name@version;未带版本默认 latest')
|
|
32
|
+
.addHelpText('after', `
|
|
33
33
|
行为
|
|
34
34
|
- 拉取 .tgz 到本地缓存,解压到 ./node_modules/<name>
|
|
35
35
|
- 在 ./package.json 的 actionPlugins 字段记录 <name>: <version>
|
|
@@ -62,10 +62,10 @@ JSON 输出
|
|
|
62
62
|
await (0, index_1.handlePluginInstall)({ names });
|
|
63
63
|
});
|
|
64
64
|
pluginCmd
|
|
65
|
-
.command(
|
|
66
|
-
.description(
|
|
67
|
-
.argument(
|
|
68
|
-
.addHelpText(
|
|
65
|
+
.command('update')
|
|
66
|
+
.description('把已安装插件升级到 latest 版本')
|
|
67
|
+
.argument('<names...>', '插件名,格式 @scope/name;带 @version 也只取 name 部分')
|
|
68
|
+
.addHelpText('after', `
|
|
69
69
|
行为
|
|
70
70
|
- 仅对已在 actionPlugins 里的插件生效;未安装的插件返回 notInstalled
|
|
71
71
|
- 拉取 latest 版本 .tgz,覆盖安装到 ./node_modules/<name>
|
|
@@ -97,10 +97,10 @@ JSON 输出
|
|
|
97
97
|
await (0, index_1.handlePluginUpdate)({ names });
|
|
98
98
|
});
|
|
99
99
|
pluginCmd
|
|
100
|
-
.command(
|
|
101
|
-
.description(
|
|
102
|
-
.argument(
|
|
103
|
-
.addHelpText(
|
|
100
|
+
.command('remove')
|
|
101
|
+
.description('从当前项目移除一个已安装的插件')
|
|
102
|
+
.argument('<name>', '插件名,格式 @scope/name')
|
|
103
|
+
.addHelpText('after', `
|
|
104
104
|
行为
|
|
105
105
|
- 删除 ./node_modules/<name> 目录
|
|
106
106
|
- 从 ./package.json 的 actionPlugins 字段移除对应条目
|
|
@@ -123,9 +123,9 @@ JSON 输出
|
|
|
123
123
|
(0, index_1.handlePluginRemove)({ name });
|
|
124
124
|
});
|
|
125
125
|
pluginCmd
|
|
126
|
-
.command(
|
|
127
|
-
.description(
|
|
128
|
-
.addHelpText(
|
|
126
|
+
.command('init')
|
|
127
|
+
.description('按 package.json 的 actionPlugins 批量安装所有插件')
|
|
128
|
+
.addHelpText('after', `
|
|
129
129
|
行为
|
|
130
130
|
- 读取 ./package.json 的 actionPlugins,对每个条目按固定版本安装
|
|
131
131
|
- 已经是目标版本的跳过(幂等),用于 clone 项目后首次同步 / CI
|
|
@@ -156,11 +156,11 @@ JSON 输出
|
|
|
156
156
|
await (0, index_1.handlePluginInit)();
|
|
157
157
|
});
|
|
158
158
|
pluginCmd
|
|
159
|
-
.command(
|
|
160
|
-
.description(
|
|
161
|
-
.option(
|
|
162
|
-
.option(
|
|
163
|
-
.addHelpText(
|
|
159
|
+
.command('list')
|
|
160
|
+
.description('列出当前项目的 capability 实例(./server/capabilities/*.json)')
|
|
161
|
+
.option('--summary', '不 hydrate(不充血),直接返回 capability 文件的原始 JSON')
|
|
162
|
+
.option('--id <id>', '只查询指定 id 的单个 capability')
|
|
163
|
+
.addHelpText('after', `
|
|
164
164
|
行为
|
|
165
165
|
- 扫描 ./server/capabilities/*.json(排除 capabilities.json)
|
|
166
166
|
- 默认对每条 capability 做 hydrate:读取 ./node_modules/<pluginKey>/manifest.json,
|
|
@@ -190,9 +190,9 @@ JSON 输出
|
|
|
190
190
|
await (0, index_1.handlePluginList)(opts);
|
|
191
191
|
});
|
|
192
192
|
pluginCmd
|
|
193
|
-
.command(
|
|
194
|
-
.description(
|
|
195
|
-
.addHelpText(
|
|
193
|
+
.command('list-packages')
|
|
194
|
+
.description('列出 package.json actionPlugins 里已声明的插件包')
|
|
195
|
+
.addHelpText('after', `
|
|
196
196
|
行为
|
|
197
197
|
- 读取 ./package.json 的 actionPlugins 字段
|
|
198
198
|
- 只看声明,不校验 ./node_modules 下是否实际存在
|
|
@@ -16,10 +16,10 @@ const args_1 = require("../../utils/args");
|
|
|
16
16
|
Object.defineProperty(exports, "failArgs", { enumerable: true, get: function () { return args_1.failArgs; } });
|
|
17
17
|
/** --app-id option,需要应用上下文的命令自行 .addOption(appIdOption()) */
|
|
18
18
|
function appIdOption() {
|
|
19
|
-
return new commander_1.Option(
|
|
19
|
+
return new commander_1.Option('--app-id <id>', '指定目标应用').env('MIAODA_APP_ID');
|
|
20
20
|
}
|
|
21
21
|
function branchOption() {
|
|
22
|
-
return new commander_1.Option(
|
|
22
|
+
return new commander_1.Option('--branch <branch>', '分支(优先级:--branch > 当前仓库 HEAD 分支;非 git 目录回退应用默认分支)').env('LOCAL_MOCK_MIAODA_DEPLOY_BRANCH');
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* soft-required: Commander 类型上 optional,runtime 校验必填。
|
|
@@ -36,8 +36,8 @@ function softRequiredOption(name, desc) {
|
|
|
36
36
|
function resolveAppId(opts) {
|
|
37
37
|
const id = opts.appId ?? process.env.MIAODA_APP_ID ?? process.env.app_id;
|
|
38
38
|
if (!id) {
|
|
39
|
-
throw new error_1.AppError(
|
|
40
|
-
next_actions: [
|
|
39
|
+
throw new error_1.AppError('APP_ID_MISSING', '未指定应用', {
|
|
40
|
+
next_actions: ['设置 export MIAODA_APP_ID=<id>'],
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
return id;
|
|
@@ -53,7 +53,7 @@ function withHelp(cmd, handler) {
|
|
|
53
53
|
await handler(...args);
|
|
54
54
|
}
|
|
55
55
|
catch (err) {
|
|
56
|
-
if (err instanceof error_1.AppError && err.code ===
|
|
56
|
+
if (err instanceof error_1.AppError && err.code === 'ARGS_INVALID') {
|
|
57
57
|
process.stderr.write(`Error: ${err.message}\n`);
|
|
58
58
|
cmd.outputHelp();
|
|
59
59
|
process.exitCode = 2;
|
|
@@ -81,7 +81,7 @@ function caseInsensitiveChoice(canonical) {
|
|
|
81
81
|
return (raw) => {
|
|
82
82
|
const hit = map.get(raw.toLowerCase());
|
|
83
83
|
if (hit === undefined) {
|
|
84
|
-
throw new commander_1.InvalidArgumentError(`Allowed choices are ${canonical.join(
|
|
84
|
+
throw new commander_1.InvalidArgumentError(`Allowed choices are ${canonical.join(', ')} (case-insensitive).`);
|
|
85
85
|
}
|
|
86
86
|
return hit;
|
|
87
87
|
};
|
|
@@ -98,8 +98,8 @@ function validateTimeOptions(opts, ...names) {
|
|
|
98
98
|
const value = opts[name];
|
|
99
99
|
if (value === undefined)
|
|
100
100
|
continue;
|
|
101
|
-
if (typeof value !==
|
|
102
|
-
(0, args_1.failArgs)(`--${name.replace(/([A-Z])/g,
|
|
101
|
+
if (typeof value !== 'string') {
|
|
102
|
+
(0, args_1.failArgs)(`--${name.replace(/([A-Z])/g, '-$1').toLowerCase()} 必须是时间字符串`);
|
|
103
103
|
}
|
|
104
104
|
(0, time_1.parseTimeToMs)(value);
|
|
105
105
|
}
|
|
@@ -118,8 +118,8 @@ function validateTimeOptions(opts, ...names) {
|
|
|
118
118
|
*/
|
|
119
119
|
function rejectCliOverride(cmd, ...optNames) {
|
|
120
120
|
for (const name of optNames) {
|
|
121
|
-
if (cmd.getOptionValueSource(name) ===
|
|
122
|
-
const flag =
|
|
121
|
+
if (cmd.getOptionValueSource(name) === 'cli') {
|
|
122
|
+
const flag = '--' + name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
123
123
|
(0, args_1.failArgs)(`${flag} 由沙箱/本地配置注入,不允许显式传递`);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
@@ -41,7 +41,7 @@ const index_1 = require("../../../api/app/index");
|
|
|
41
41
|
/** miaoda app update [--app-id <id>] [--name <n>] [--description <d>] */
|
|
42
42
|
async function handleAppUpdate(opts) {
|
|
43
43
|
if (opts.name === undefined && opts.description === undefined) {
|
|
44
|
-
(0, args_1.failArgs)(
|
|
44
|
+
(0, args_1.failArgs)('至少需要 --name 或 --description 中的一个');
|
|
45
45
|
}
|
|
46
46
|
const appID = opts.appId;
|
|
47
47
|
await api.app.updateAppMeta({
|
|
@@ -53,7 +53,7 @@ async function handleAppUpdate(opts) {
|
|
|
53
53
|
const resp = await api.app.getAppInfo(appID);
|
|
54
54
|
const meta = resp.appInfo?.appMeta ?? {};
|
|
55
55
|
if (!(0, output_1.isJsonMode)()) {
|
|
56
|
-
process.stdout.write(
|
|
56
|
+
process.stdout.write('✓ App updated successfully\n');
|
|
57
57
|
}
|
|
58
58
|
(0, output_1.emit)({ data: meta }, index_1.appMetaSchema);
|
|
59
59
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// db 域内不可逆 / destructive 操作的统一确认门。
|
|
3
|
+
//
|
|
4
|
+
// 设计要点:
|
|
5
|
+
// 1. --yes:放行(CI / 脚本显式确认场景)
|
|
6
|
+
// 2. 非 TTY 且无 --yes:抛 DESTRUCTIVE_REQUIRES_CONFIRM,不要默认放行。
|
|
7
|
+
// 非 TTY 通常是 CI / agent / 管道场景,无人能交互回答 y/N,必须靠 --yes 显式确认。
|
|
8
|
+
// 3. TTY 且无 --yes:交互式 y/N(prompt 写 stderr,避免污染 --json 模式 stdout)
|
|
9
|
+
//
|
|
10
|
+
// 反例(已修复,见这次 commit):把 !isJsonMode() 拿来做确认门 —— --json 只改输出
|
|
11
|
+
// 格式,跟"用户已确认"不是同一语义。CI / agent 几乎总会带 --json 解析输出,等价于
|
|
12
|
+
// 把不可逆操作的确认变成了空门,跟 file rm 的模型也不一致。
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.confirmDestructive = confirmDestructive;
|
|
18
|
+
exports.assertDestructiveAllowedInTty = assertDestructiveAllowedInTty;
|
|
19
|
+
exports.askYesNo = askYesNo;
|
|
20
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
21
|
+
const render_1 = require("../../../utils/render");
|
|
22
|
+
const error_1 = require("../../../utils/error");
|
|
23
|
+
const DESTRUCTIVE_REQUIRES_CONFIRM_MSG = 'This operation is destructive. Rerun with --yes to confirm.';
|
|
24
|
+
/**
|
|
25
|
+
* 完整的"yes/tty/交互"决策门。直接用于无需 fetch 预览的简单确认场景(如 migration init)。
|
|
26
|
+
*
|
|
27
|
+
* 返回值:
|
|
28
|
+
* - true → 已确认,可执行
|
|
29
|
+
* - false → TTY 下用户输了 n,调用方应渲染 Aborted 并退出
|
|
30
|
+
*
|
|
31
|
+
* 异常:
|
|
32
|
+
* - DESTRUCTIVE_REQUIRES_CONFIRM:非 TTY 且无 --yes
|
|
33
|
+
*/
|
|
34
|
+
async function confirmDestructive(prompt, yes) {
|
|
35
|
+
if (yes === true)
|
|
36
|
+
return true;
|
|
37
|
+
if (!(0, render_1.isStdoutTty)()) {
|
|
38
|
+
throw new error_1.AppError('DESTRUCTIVE_REQUIRES_CONFIRM', DESTRUCTIVE_REQUIRES_CONFIRM_MSG);
|
|
39
|
+
}
|
|
40
|
+
return askYesNo(prompt);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 给"需要在 confirm 之前先 fetch 预览"的场景(migration apply / recovery apply 可选优化)用:
|
|
44
|
+
* 在非 TTY 无 --yes 时提前抛错,避免无意义触发后端 dry-run RPC。
|
|
45
|
+
*
|
|
46
|
+
* 调用后 TTY 路径仍需自行调 askYesNo / confirmDestructive 完成交互。
|
|
47
|
+
*/
|
|
48
|
+
function assertDestructiveAllowedInTty(yes) {
|
|
49
|
+
if (yes === true)
|
|
50
|
+
return;
|
|
51
|
+
if (!(0, render_1.isStdoutTty)()) {
|
|
52
|
+
throw new error_1.AppError('DESTRUCTIVE_REQUIRES_CONFIRM', DESTRUCTIVE_REQUIRES_CONFIRM_MSG);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 交互式 y/N。输入读 stdin,prompt 写 stderr —— stderr 避免污染 --json 模式 stdout 的 envelope。
|
|
57
|
+
* 仅 'y'(忽略大小写)算确认;其余一律视为否决。
|
|
58
|
+
*/
|
|
59
|
+
async function askYesNo(prompt) {
|
|
60
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
rl.question(prompt, (answer) => {
|
|
63
|
+
rl.close();
|
|
64
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// db 域内 --env 参数的 CLI vocab 校验。
|
|
3
|
+
//
|
|
4
|
+
// 用户视角的 vocab:
|
|
5
|
+
// - dev → 多环境沙箱分支
|
|
6
|
+
// - online → 生产分支(向后端发请求时映射成 main,client.ts/api.ts 已处理)
|
|
7
|
+
// - main → online 的别名,兼容历史脚本
|
|
8
|
+
//
|
|
9
|
+
// 配合 client.ts::buildUnknownEnvError,把 vocab 错(这里)跟 backend state 错
|
|
10
|
+
// (client.ts 处理)统一成同一类 UNKNOWN_ENV_VALUE,避免用户在两种失败之间困惑。
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.validateEnv = validateEnv;
|
|
13
|
+
const client_1 = require("../../../api/db/client");
|
|
14
|
+
const VOCAB = ['dev', 'main', 'online'];
|
|
15
|
+
const VOCAB_SET = new Set(VOCAB);
|
|
16
|
+
/**
|
|
17
|
+
* CLI vocab 校验。空值(未传 --env)放行,由后端按 workspace 默认 branch 处理。
|
|
18
|
+
* 不在 vocab 内 → 抛 UNKNOWN_ENV_VALUE,preAction hook 兜底,避免无意义触发 RPC。
|
|
19
|
+
*/
|
|
20
|
+
function validateEnv(env) {
|
|
21
|
+
if (env === undefined || env === '')
|
|
22
|
+
return;
|
|
23
|
+
if (VOCAB_SET.has(env))
|
|
24
|
+
return;
|
|
25
|
+
throw (0, client_1.buildUnknownEnvError)(env);
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// changelog / audit 共用:把后端透传的 operator 字符串还原成 {id, name}。
|
|
3
|
+
//
|
|
4
|
+
// 后端约定:Operator 字段值是 `{"id":"<user_id>","name":"<resolved_name>"}` JSON
|
|
5
|
+
// 字符串(dataloom inner_service.encodeOperator 生成),用同一字段同时承载机器可
|
|
6
|
+
// 识别的 user_id 与人类可读 name。
|
|
7
|
+
//
|
|
8
|
+
// 选择"字符串内嵌 JSON"而不是 IDL struct,是为了避免 dataloom_inner.thrift +
|
|
9
|
+
// kitex_gen 跨仓库改造;CLI 端 JSON.parse 拆开即可分场景渲染:
|
|
10
|
+
// - --json 模式:返 {id, name} 对象(agent / 下游能区分同名用户)
|
|
11
|
+
// - pretty 模式:只取 name(兼容 PRD 原始 string 形态)
|
|
12
|
+
//
|
|
13
|
+
// 历史数据 / 旧版后端没接这层时,operator 仍是纯字符串。该函数兼容:
|
|
14
|
+
// - 解析失败 → 当作 {id: raw, name: raw}
|
|
15
|
+
// - 解析成功但缺字段 → 用现有字段兜底另一个
|
|
16
|
+
// 解析失败默认不报错,保留 raw 作为兜底显示文本。
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.parseOperator = parseOperator;
|
|
19
|
+
function parseOperator(raw) {
|
|
20
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
21
|
+
return { id: '', name: '' };
|
|
22
|
+
}
|
|
23
|
+
if (!raw.startsWith('{')) {
|
|
24
|
+
return { id: raw, name: raw };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const obj = JSON.parse(raw);
|
|
28
|
+
const id = typeof obj.id === 'string' ? obj.id : '';
|
|
29
|
+
const name = typeof obj.name === 'string' && obj.name !== '' ? obj.name : id;
|
|
30
|
+
return { id, name };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { id: raw, name: raw };
|
|
34
|
+
}
|
|
35
|
+
}
|