@lazycatcloud/lzc-cli 1.3.12 → 1.3.13
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/changelog.md +9 -0
- package/lib/app/apkshell.js +37 -40
- package/lib/app/index.js +187 -186
- package/lib/app/lpk_build.js +341 -358
- package/lib/app/lpk_create.js +135 -155
- package/lib/app/lpk_create_generator.js +74 -66
- package/lib/app/lpk_devshell.js +444 -533
- package/lib/app/lpk_devshell_docker.js +48 -47
- package/lib/app/lpk_installer.js +119 -123
- package/lib/appstore/index.js +205 -214
- package/lib/appstore/login.js +146 -143
- package/lib/appstore/prePublish.js +101 -100
- package/lib/appstore/publish.js +253 -256
- package/lib/box/index.js +82 -77
- package/lib/config/index.js +58 -54
- package/lib/debug_bridge.js +280 -330
- package/lib/docker/index.js +84 -86
- package/lib/i18n/README.md +25 -0
- package/lib/i18n/index.js +37 -0
- package/lib/i18n/locales/en/translation.json +251 -0
- package/lib/i18n/locales/zh/translation.json +251 -0
- package/lib/shellapi.js +122 -146
- package/lib/utils.js +536 -552
- package/package.json +6 -8
- package/scripts/cli.js +81 -77
- package/scripts/lzc-docker-compose.js +34 -33
- package/scripts/lzc-docker.js +34 -33
package/lib/appstore/publish.js
CHANGED
|
@@ -1,145 +1,139 @@
|
|
|
1
|
-
import { request, autoLogin } from
|
|
2
|
-
import logger from
|
|
3
|
-
import FormData from
|
|
4
|
-
import fs from
|
|
5
|
-
import inquirer from
|
|
6
|
-
import path from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
unzipSync,
|
|
11
|
-
loadFromYaml,
|
|
12
|
-
isValidPackageName,
|
|
13
|
-
getLanguageForLocale
|
|
14
|
-
} from "../utils.js"
|
|
15
|
-
import { appStoreServerUrl } from "./env.js"
|
|
1
|
+
import { request, autoLogin } from './login.js';
|
|
2
|
+
import logger from 'loglevel';
|
|
3
|
+
import FormData from 'form-data';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { isFileExist, isPngWithFile, unzipSync, loadFromYaml, isValidPackageName, getLanguageForLocale } from '../utils.js';
|
|
8
|
+
import { t } from '../i18n/index.js';
|
|
9
|
+
import { appStoreServerUrl } from './env.js';
|
|
16
10
|
|
|
17
11
|
async function askChangeLog(locale) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
const noEmpty = (value) => value != '';
|
|
13
|
+
return await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
name: 'changelog',
|
|
16
|
+
type: 'editor',
|
|
17
|
+
message: t('lzc_cli.lib.publish.ask_changelog_prompt', `填写 changelog 内容 ({{locale}})`, { locale }),
|
|
18
|
+
validate: noEmpty,
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
async function getCategories(baseUrl, locale =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
23
|
+
async function getCategories(baseUrl, locale = 'zh') {
|
|
24
|
+
const response = await request(`${baseUrl}/app/category?size=100`, {
|
|
25
|
+
method: 'GET',
|
|
26
|
+
});
|
|
27
|
+
const langKey = getLanguageForLocale(locale);
|
|
28
|
+
const { items: data } = await response.json();
|
|
29
|
+
console.log('data', data);
|
|
30
|
+
return data
|
|
31
|
+
.sort((a, b) => a.index - b.index)
|
|
32
|
+
.map((cat) => {
|
|
33
|
+
let localize_name = (cat?.localize_name ?? {})[langKey] ?? undefined;
|
|
34
|
+
return {
|
|
35
|
+
name: localize_name ?? cat.name,
|
|
36
|
+
value: cat.id,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
async function askPublishAppInfo(baseUrl, manifest, locale) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
// 获取分类列表
|
|
43
|
+
// const categories = await getCategories(baseUrl, locale)
|
|
44
|
+
const langKey = getLanguageForLocale(locale);
|
|
51
45
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
]
|
|
46
|
+
const questions = [
|
|
47
|
+
{
|
|
48
|
+
type: 'input',
|
|
49
|
+
name: 'name',
|
|
50
|
+
message: t('lzc_cli.lib.publish.ask_publish_app_info_name_prompt', '请输入应用名称:'),
|
|
51
|
+
default: manifest['name'],
|
|
52
|
+
validate: (input) => (input.trim() !== '' ? true : t('lzc_cli.lib.publish.ask_publish_app_info_name_validate', '应用名称不能为空')),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'package',
|
|
57
|
+
message: t('lzc_cli.lib.publish.ask_publish_app_info_package_prompt', '请输入应用包标识符:'),
|
|
58
|
+
default: manifest['package'],
|
|
59
|
+
validate: (input) =>
|
|
60
|
+
isValidPackageName(input) ? true : t('lzc_cli.lib.publish.ask_publish_app_info_package_validate', '应用包标识符不符合规范,建议使用反向域名表示法'),
|
|
61
|
+
},
|
|
62
|
+
// {
|
|
63
|
+
// type: "input",
|
|
64
|
+
// name: "description",
|
|
65
|
+
// message: "请输入应用描述:",
|
|
66
|
+
// default: manifest["description"] || ""
|
|
67
|
+
// },
|
|
68
|
+
// {
|
|
69
|
+
// type: "input",
|
|
70
|
+
// name: "brief",
|
|
71
|
+
// message: "请输入应用简介(简单的概述):",
|
|
72
|
+
// default: ""
|
|
73
|
+
// },
|
|
74
|
+
// 开发者暂时不能配置应用分类
|
|
75
|
+
// {
|
|
76
|
+
// type: "checkbox",
|
|
77
|
+
// name: "category",
|
|
78
|
+
// message: "请选择应用分类:",
|
|
79
|
+
// choices: categories,
|
|
80
|
+
// validate: (input) => (input.length > 0 ? true : "请至少选择一个分类")
|
|
81
|
+
// },
|
|
82
|
+
// {
|
|
83
|
+
// type: "input",
|
|
84
|
+
// name: "keywords",
|
|
85
|
+
// message: "请输入关键词(用逗号分隔):",
|
|
86
|
+
// default: ""
|
|
87
|
+
// },
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'source',
|
|
91
|
+
message: t('lzc_cli.lib.publish.ask_publish_app_info_source_prompt', '请输入应用来源(原创应用可不填):'),
|
|
92
|
+
default: manifest['homepage'] || '',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'author',
|
|
97
|
+
message: t('lzc_cli.lib.publish.ask_publish_app_info_author_prompt', '请输入作者名称(原创应用可不填):'),
|
|
98
|
+
default: manifest['author'] || '',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
109
101
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
const answers = await inquirer.prompt(questions);
|
|
103
|
+
return {
|
|
104
|
+
language: langKey, // 应用主要语言(后续需要做成可以让用户选择)
|
|
105
|
+
...answers,
|
|
106
|
+
};
|
|
115
107
|
}
|
|
116
108
|
|
|
117
109
|
async function askWhetherCreateLPK(baseUrl, manifest, locale) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
110
|
+
const answers = await inquirer.prompt([
|
|
111
|
+
{
|
|
112
|
+
name: 'continue',
|
|
113
|
+
type: 'input',
|
|
114
|
+
message: t('lzc_cli.lib.publish.ask_whether_create_lpk_continue_prompt', '检测到您当前的应用,还没有在懒猫微服中创建,是否使用当前的安装包中的信息进行创建? [y/n]'),
|
|
115
|
+
default: 'y',
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
if (answers.continue.toLowerCase() === 'y') {
|
|
119
|
+
const appInfo = await askPublishAppInfo(baseUrl, manifest, locale);
|
|
120
|
+
const crateAppRes = await request(`${baseUrl}/app/create`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
package: appInfo.package,
|
|
124
|
+
language: appInfo.language,
|
|
125
|
+
name: appInfo.name,
|
|
126
|
+
source: appInfo.source,
|
|
127
|
+
source_author: appInfo.author,
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
logger.debug('create app res: ', await crateAppRes.text());
|
|
131
|
+
logger.info(t('lzc_cli.lib.publish.ask_whether_create_lpk_success_tips', `创建 {{package}} 应用成功!`, { package: manifest['package'] }));
|
|
132
|
+
} else {
|
|
133
|
+
logger.info(
|
|
134
|
+
t(
|
|
135
|
+
'lzc_cli.lib.publish.ask_whether_create_lpk_fail_tips',
|
|
136
|
+
`当前想发布的应用没有在 '懒猫微服的开发者中心' 创建,请按照以下步骤创建:
|
|
143
137
|
|
|
144
138
|
1. 浏览器打开 https://developer.lazycat.cloud/manage
|
|
145
139
|
2. 登录您的开发者帐号
|
|
@@ -147,149 +141,152 @@ async function askWhetherCreateLPK(baseUrl, manifest, locale) {
|
|
|
147
141
|
4. 点击 '新增',根据您的应用信息进行填写
|
|
148
142
|
5. 填写完成后,点击创建即可
|
|
149
143
|
6. 创建成功后,您可以通过 'lzc-cli appstore publish' 来发布应用了
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
`,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
153
149
|
}
|
|
154
150
|
|
|
155
151
|
export class Publish {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
constructor(baseUrl = `${appStoreServerUrl}/api/v3/developer`) {
|
|
153
|
+
this.baseUrl = baseUrl;
|
|
154
|
+
}
|
|
159
155
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} raw
|
|
158
|
+
*/
|
|
159
|
+
static isJSON(raw) {
|
|
160
|
+
const ml = raw.length;
|
|
161
|
+
if (ml <= 1) return false;
|
|
162
|
+
return raw[0] == '{' && raw[ml - 1] == '}';
|
|
163
|
+
}
|
|
168
164
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
}
|
|
165
|
+
static preCheck(pkgPath) {
|
|
166
|
+
const tempDir = fs.mkdtempSync('.lzc-cli-publish');
|
|
167
|
+
try {
|
|
168
|
+
unzipSync(pkgPath, tempDir, ['devshell', 'icon.png']);
|
|
169
|
+
if (isFileExist(path.join(tempDir, 'devshell'))) {
|
|
170
|
+
logger.error(t('lzc_cli.lib.publish.pre_check_fail_tips', '不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建'));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const tempIcon = path.join(tempDir, 'icon.png');
|
|
174
|
+
if (!isFileExist(tempIcon) || !isPngWithFile(tempIcon)) {
|
|
175
|
+
logger.error(t('lzc_cli.lib.publish.pre_check_icon_not_exist_tips', 'icon 必须存在且要求是 png 格式'));
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
} finally {
|
|
180
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
189
183
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
184
|
+
async checkAppIdExist(pkgPath) {
|
|
185
|
+
const tempDir = fs.mkdtempSync('.lzc-cli-publish');
|
|
186
|
+
try {
|
|
187
|
+
unzipSync(pkgPath, tempDir, ['manifest.yml']);
|
|
188
|
+
const manifest = loadFromYaml(path.join(tempDir, 'manifest.yml'));
|
|
189
|
+
const checkUrl = this.baseUrl + `/app/check/exist?package=${manifest['package']}`;
|
|
190
|
+
const res = await request(checkUrl, { method: 'GET' });
|
|
191
|
+
if (res.status >= 400) {
|
|
192
|
+
logger.error(t('lzc_cli.lib.publish.check_app_id_exist_fail_tips', '检测应用是否存在出错, 错误状态码为: '), res.status);
|
|
193
|
+
logger.error(await res.json());
|
|
194
|
+
return { manifest, appIdExisted: true }; // 默认认为已经存在
|
|
195
|
+
} else {
|
|
196
|
+
const { exist = false } = await res.json();
|
|
197
|
+
logger.debug(`check appId[${manifest['package']}] exist: ${exist}`);
|
|
198
|
+
return { manifest, appIdExisted: exist };
|
|
199
|
+
}
|
|
200
|
+
} finally {
|
|
201
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
211
204
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
205
|
+
/**
|
|
206
|
+
* @param {string} pkgPath
|
|
207
|
+
* @param {string} changelog
|
|
208
|
+
* @param {string} locale
|
|
209
|
+
*/
|
|
210
|
+
async publish(pkgPath, changelog, locale = 'zh') {
|
|
211
|
+
if (!Publish.preCheck(pkgPath)) return;
|
|
219
212
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
213
|
+
const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath);
|
|
214
|
+
if (!appIdExisted) {
|
|
215
|
+
await askWhetherCreateLPK(this.baseUrl, manifest, locale);
|
|
216
|
+
}
|
|
224
217
|
|
|
225
|
-
|
|
218
|
+
await autoLogin();
|
|
226
219
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
220
|
+
logger.info(t('lzc_cli.lib.publish.publish_pending_tips', '正在提交审核...'));
|
|
221
|
+
const form = new FormData();
|
|
222
|
+
form.append('file', fs.createReadStream(pkgPath));
|
|
230
223
|
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
const uploadURL = this.baseUrl + '/app/lpk/upload';
|
|
225
|
+
logger.debug('upload url is', uploadURL);
|
|
233
226
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
227
|
+
const res = await request(uploadURL, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
body: form,
|
|
230
|
+
});
|
|
231
|
+
const text = (await res.text()).trim();
|
|
232
|
+
if (!Publish.isJSON(text)) {
|
|
233
|
+
logger.info('upload lpk fail', text);
|
|
234
|
+
throw new Error(text);
|
|
235
|
+
}
|
|
236
|
+
const lpkInfo = await JSON.parse(text);
|
|
237
|
+
logger.debug('upload lpk response', lpkInfo);
|
|
238
|
+
if (res.status >= 400) {
|
|
239
|
+
logger.error(
|
|
240
|
+
t('lzc_cli.lib.publish.publish_lpk_fail_tips', `LPK 文件上传失败,err: {{message}}`, {
|
|
241
|
+
message: lpkInfo?.message ?? lpkInfo,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
throw new Error(lpkInfo?.message ?? lpkInfo);
|
|
245
|
+
}
|
|
249
246
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
247
|
+
// 填写更新日志
|
|
248
|
+
if (!changelog) {
|
|
249
|
+
const answer = await askChangeLog(locale);
|
|
250
|
+
changelog = answer.changelog;
|
|
251
|
+
}
|
|
252
|
+
changelog = changelog.trim(); // clean space ^:)
|
|
256
253
|
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
const sendURL = this.baseUrl + `/app/${lpkInfo.package}/review/create`;
|
|
255
|
+
logger.debug('publish url is', sendURL);
|
|
259
256
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
257
|
+
const formData = {
|
|
258
|
+
version: {
|
|
259
|
+
// supportPC: lpkInfo.supportPC,
|
|
260
|
+
// supportMobile: lpkInfo.supportMobile,
|
|
261
|
+
package: lpkInfo.package,
|
|
262
|
+
name: lpkInfo.version,
|
|
263
|
+
icon_path: lpkInfo.iconPath,
|
|
264
|
+
pkg_path: lpkInfo.url,
|
|
265
|
+
unsupported_platforms: lpkInfo.unsupportedPlatforms,
|
|
266
|
+
min_os_version: lpkInfo.minOsVersion,
|
|
267
|
+
changelogs: {},
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
// changelogs 本地化
|
|
271
|
+
const langKey = getLanguageForLocale(locale);
|
|
272
|
+
formData.version.changelogs[langKey] = changelog;
|
|
276
273
|
|
|
277
|
-
|
|
274
|
+
logger.debug('form data is', formData);
|
|
278
275
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
276
|
+
const publishRes = await request(sendURL, {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
body: JSON.stringify(formData),
|
|
279
|
+
});
|
|
283
280
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
281
|
+
const publishResMsg = (await publishRes.text()).trim();
|
|
282
|
+
if (publishRes.status > 399) {
|
|
283
|
+
logger.error(t('lzc_cli.lib.publish.publish_fail_tips', '发布应用出错,错误状态码为: '), publishRes.status);
|
|
284
|
+
logger.error(publishResMsg);
|
|
285
|
+
throw new Error(publishResMsg);
|
|
286
|
+
}
|
|
290
287
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
logger.info(t('lzc_cli.lib.publish.publish_done_tips', '应用提交成功! 请等待审核结果'));
|
|
289
|
+
logger.debug('publish response', publishResMsg);
|
|
290
|
+
return publishRes;
|
|
291
|
+
}
|
|
295
292
|
}
|