@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/app/lpk_devshell.js
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
// lzc-cli app devshell
|
|
2
|
-
import path from
|
|
3
|
-
import fs from
|
|
4
|
-
import logger from
|
|
5
|
-
import spawn from
|
|
6
|
-
import { LpkInstaller } from
|
|
7
|
-
import debounce from
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import logger from 'loglevel';
|
|
5
|
+
import spawn from 'cross-spawn';
|
|
6
|
+
import { LpkInstaller } from './lpk_installer.js';
|
|
7
|
+
import debounce from 'lodash.debounce';
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from
|
|
26
|
-
import os from
|
|
27
|
-
import chokidar from
|
|
28
|
-
import _ from
|
|
29
|
-
import { DebugBridge } from
|
|
30
|
-
import shellApi from
|
|
31
|
-
import {
|
|
9
|
+
mergeYamlInMemory,
|
|
10
|
+
contextDirname,
|
|
11
|
+
ensureDir,
|
|
12
|
+
isFileExist,
|
|
13
|
+
GitIgnore,
|
|
14
|
+
md5String,
|
|
15
|
+
md5File,
|
|
16
|
+
loadFromYaml,
|
|
17
|
+
isUserApp,
|
|
18
|
+
createTemplateFileCommon,
|
|
19
|
+
isDebugMode,
|
|
20
|
+
resolveDomain,
|
|
21
|
+
isWindows,
|
|
22
|
+
isMacOs,
|
|
23
|
+
isLinux,
|
|
24
|
+
pkgInfo,
|
|
25
|
+
} from '../utils.js';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import chokidar from 'chokidar';
|
|
28
|
+
import _ from 'lodash';
|
|
29
|
+
import { DebugBridge } from '../debug_bridge.js';
|
|
30
|
+
import shellApi from '../shellapi.js';
|
|
31
|
+
import { t } from '../i18n/index.js';
|
|
32
|
+
import { collectContextFromDockerFile } from './lpk_devshell_docker.js';
|
|
32
33
|
|
|
33
34
|
// 判断是否需要重新构建
|
|
34
35
|
// - 先判断 lzc-build.yml 是否发生改变
|
|
@@ -37,514 +38,424 @@ import { collectContextFromDockerFile } from "./lpk_devshell_docker.js"
|
|
|
37
38
|
// - 根据 backend api 判断一个 appid 是否属于 running
|
|
38
39
|
// - 根据在 backend api 中判断同步的目录下是否存在文件 判断当前运行的 app 是否已经有一个挂载的实例,避免重复挂载
|
|
39
40
|
class AppDevShellMonitor {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
? JSON.parse(fs.readFileSync(this.cacheFilePath))
|
|
94
|
-
: {}
|
|
95
|
-
const buildHash = isFileExist(this.optionsFilePath)
|
|
96
|
-
? await md5File(this.optionsFilePath)
|
|
97
|
-
: ""
|
|
98
|
-
const manifestHash = isFileExist(this.manifestFilePath)
|
|
99
|
-
? await md5File(this.manifestFilePath)
|
|
100
|
-
: ""
|
|
101
|
-
this.newHash = {
|
|
102
|
-
build: buildHash,
|
|
103
|
-
manifest: manifestHash,
|
|
104
|
-
lzcVersion: pkgInfo.version
|
|
105
|
-
}
|
|
106
|
-
if (!_.isEqual(this.oldHash, this.newHash)) {
|
|
107
|
-
fs.writeFileSync(this.cacheFilePath, JSON.stringify(this.newHash))
|
|
108
|
-
}
|
|
109
|
-
}
|
|
41
|
+
constructor(cwd, pkgId, buildConfigFile) {
|
|
42
|
+
this.pwd = cwd ? path.resolve(cwd) : process.cwd();
|
|
43
|
+
this.pkgId = pkgId;
|
|
44
|
+
|
|
45
|
+
this.optionsFilePath = path.join(this.pwd, buildConfigFile);
|
|
46
|
+
this.options = loadFromYaml(this.optionsFilePath);
|
|
47
|
+
|
|
48
|
+
this.manifestFilePath = this.options['manifest'] ? path.join(this.pwd, this.options['manifest']) : path.join(this.pwd, 'lzc-manifest.yml');
|
|
49
|
+
|
|
50
|
+
this.hashObject = {
|
|
51
|
+
build: '',
|
|
52
|
+
manifest: '',
|
|
53
|
+
};
|
|
54
|
+
this.cacheFilePath = undefined;
|
|
55
|
+
this.oldHash = undefined;
|
|
56
|
+
this.newHash = undefined;
|
|
57
|
+
this.bridge = new DebugBridge();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async init() {
|
|
61
|
+
const pathId = await md5String(this.pwd);
|
|
62
|
+
this.cacheFilePath = path.resolve(os.tmpdir(), 'lzc-cli-devshell', pathId, 'hash');
|
|
63
|
+
ensureDir(this.cacheFilePath);
|
|
64
|
+
|
|
65
|
+
await this.updateHash();
|
|
66
|
+
await this.bridge.init();
|
|
67
|
+
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async shouldBuild() {
|
|
72
|
+
return this.change() || (await this.bridge.status(this.pkgId)) === 'NotInstalled' || !(await this.bridge.isDevshell(this.pkgId));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
change() {
|
|
76
|
+
logger.debug('oldHash', this.oldHash);
|
|
77
|
+
logger.debug('newHash', this.newHash);
|
|
78
|
+
return !_.isEqual(this.oldHash, this.newHash);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async updateHash() {
|
|
82
|
+
this.oldHash = isFileExist(this.cacheFilePath) ? JSON.parse(fs.readFileSync(this.cacheFilePath)) : {};
|
|
83
|
+
const buildHash = isFileExist(this.optionsFilePath) ? await md5File(this.optionsFilePath) : '';
|
|
84
|
+
const manifestHash = isFileExist(this.manifestFilePath) ? await md5File(this.manifestFilePath) : '';
|
|
85
|
+
this.newHash = {
|
|
86
|
+
build: buildHash,
|
|
87
|
+
manifest: manifestHash,
|
|
88
|
+
lzcVersion: pkgInfo.version,
|
|
89
|
+
};
|
|
90
|
+
if (!_.isEqual(this.oldHash, this.newHash)) {
|
|
91
|
+
fs.writeFileSync(this.cacheFilePath, JSON.stringify(this.newHash));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
110
94
|
}
|
|
111
95
|
|
|
112
96
|
export class AppDevShell {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
manifest["application"]["image"] = config["devshell"]["image"]
|
|
374
|
-
delete manifest["application"]["devshell"]
|
|
375
|
-
}
|
|
376
|
-
return manifest
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// 如果没有找到 devshell 中没有指定 image 不存在,将默认使用的 lzc-cli/devshell 容器
|
|
380
|
-
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
381
|
-
delete manifest["application"]["devshell"]
|
|
382
|
-
|
|
383
|
-
const config = manifest["application"]
|
|
384
|
-
if (config["image"]) {
|
|
385
|
-
return manifest
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
logger.debug("use default lzc-cli/devshell image")
|
|
389
|
-
manifest["application"]["image"] =
|
|
390
|
-
`registry.lazycat.cloud/lzc-cli/devshell:v0.0.5`
|
|
391
|
-
return manifest
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
// devshell 模式下,默认打开后台常驻
|
|
395
|
-
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
396
|
-
manifest["application"]["background_task"] = true
|
|
397
|
-
return manifest
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
// 添加一个 devshell 的标记在 lpk 中,标记当前 lpk 为一个 debug 版本
|
|
401
|
-
this.lpkBuild.onBeforeDumpLpk(async (options, cwd, destDir) => {
|
|
402
|
-
fs.writeFileSync(path.resolve(destDir, "devshell"), "")
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
// 在构建生成 lpk 包后,调用 deploy 进行部署
|
|
406
|
-
let installer = new LpkInstaller()
|
|
407
|
-
await installer.init()
|
|
408
|
-
await installer.deploy(this.lpkBuild, true)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async rsyncShell() {
|
|
412
|
-
const manifest = await this.lpkBuild.getManifest()
|
|
413
|
-
const pkgId = manifest["package"]
|
|
414
|
-
const devshell = new DevShell(pkgId, this.isUserApp)
|
|
415
|
-
try {
|
|
416
|
-
await devshell.shell()
|
|
417
|
-
} catch (e) {
|
|
418
|
-
logger.error(`devshell 错误: ${e}`)
|
|
419
|
-
}
|
|
420
|
-
logger.debug("exit shell")
|
|
421
|
-
// TODO: shell 在正常情况下,按 Ctrl-D 就会退出,回到原来的本地的 shell ,但
|
|
422
|
-
// 现在会一直卡在退出状态后,必须要另外手动的指定 pkill node
|
|
423
|
-
process.exit(0)
|
|
424
|
-
}
|
|
97
|
+
constructor(cwd, lpkBuild, forceBuild, buildConfigFile) {
|
|
98
|
+
this.cwd = cwd;
|
|
99
|
+
this.lpkBuild = lpkBuild;
|
|
100
|
+
this.forceBuild = forceBuild;
|
|
101
|
+
this.buildConfigFile = buildConfigFile;
|
|
102
|
+
this.isUserApp = false;
|
|
103
|
+
this.monitor = undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async init() {
|
|
107
|
+
const manifest = await this.lpkBuild.getManifest();
|
|
108
|
+
this.monitor = await new AppDevShellMonitor(this.cwd, manifest['package'], this.buildConfigFile).init();
|
|
109
|
+
this.isUserApp = isUserApp(manifest);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async build() {
|
|
113
|
+
// 先判断是否需要重新构建
|
|
114
|
+
if (this.forceBuild || (await this.monitor.shouldBuild())) {
|
|
115
|
+
logger.debug('build...');
|
|
116
|
+
await this.devshellBuild();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async devshellBuild() {
|
|
121
|
+
this.lpkBuild.onBeforeBuildPackage(async (options) => {
|
|
122
|
+
const devshell = options['devshell'];
|
|
123
|
+
if (!devshell) {
|
|
124
|
+
throw t('lzc_cli.lib.app.lpk_devshell.devshell_build_field_fail', 'devshell 模式下,devshell 字段必须要指定');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const routes = devshell['routes'];
|
|
128
|
+
if (!routes || routes.length == 0) {
|
|
129
|
+
throw t('lzc_cli.lib.app.lpk_devshell.devshell_build_routes_not_exists_fail', 'devshell 模式下,必须要指定 routes 内容');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return options;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 复制 busybox 到 devshell 中去
|
|
136
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
137
|
+
const busyboxPath = path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'busybox-1.35.0');
|
|
138
|
+
let dest = path.join(contentdir, 'devshell', 'busybox');
|
|
139
|
+
ensureDir(dest);
|
|
140
|
+
fs.copyFileSync(busyboxPath, dest);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 复制 init_debug_bridge.sh 到 devshell 中去
|
|
144
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
145
|
+
const initPath = path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'init_debug_bridge.sh');
|
|
146
|
+
let dest = path.join(contentdir, 'devshell', 'init_debug_bridge.sh');
|
|
147
|
+
ensureDir(dest);
|
|
148
|
+
fs.copyFileSync(initPath, dest);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 复制 exec.sh 到 devshell 中去
|
|
152
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
153
|
+
const execScriptPath = path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'exec.sh');
|
|
154
|
+
let dest = path.join(contentdir, 'devshell', 'exec.sh');
|
|
155
|
+
ensureDir(dest);
|
|
156
|
+
fs.copyFileSync(execScriptPath, dest);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 复制 rsyncd.conf 到 devshell 中去
|
|
160
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir) => {
|
|
161
|
+
const execScriptPath = path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'rsyncd.conf');
|
|
162
|
+
let dest = path.join(contentdir, 'devshell', 'rsyncd.conf');
|
|
163
|
+
ensureDir(dest);
|
|
164
|
+
fs.copyFileSync(execScriptPath, dest);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 复制 setupscript 脚本
|
|
168
|
+
this.lpkBuild.onBeforeTarContent(async (contentdir, options) => {
|
|
169
|
+
const devshell = options['devshell'];
|
|
170
|
+
if (!devshell['setupscript']) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.debug('process setupscript');
|
|
175
|
+
const dest = path.join(contentdir, 'devshell', 'setupscript');
|
|
176
|
+
ensureDir(dest);
|
|
177
|
+
|
|
178
|
+
// 先判断是否文件
|
|
179
|
+
const filePath = path.resolve(devshell['setupscript']);
|
|
180
|
+
if (isFileExist(filePath)) {
|
|
181
|
+
fs.copyFileSync(filePath, dest);
|
|
182
|
+
} else {
|
|
183
|
+
fs.writeFileSync(dest, `#!/bin/sh\nset -ex\n${devshell['setupscript']}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// 在生成 manifest.yml 之前合并 devshell manifest 模板字段
|
|
188
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest, options) => {
|
|
189
|
+
logger.debug('merge lzc-build.yml devshell routes field');
|
|
190
|
+
const devshell = options['devshell'];
|
|
191
|
+
|
|
192
|
+
const routes = devshell['routes'];
|
|
193
|
+
logger.debug("options devshell delete 'routes' field");
|
|
194
|
+
delete options['devshell']['routes'];
|
|
195
|
+
|
|
196
|
+
// 添加 devshell 必要路由,这里需要使用 /bin/sh 启动后面的脚本,因为在
|
|
197
|
+
// Windows 上打包的文件将会丢失可执行权限
|
|
198
|
+
routes.push('/__debug.bridge=exec://80,/bin/sh /lzcapp/pkg/content/devshell/init_debug_bridge.sh');
|
|
199
|
+
routes.push('/__isdevshell=file:///lzcapp/pkg/devshell');
|
|
200
|
+
|
|
201
|
+
// 如果 devshell 中的 router 和 manifest 中的 prefix 出现冲突
|
|
202
|
+
// 优先使用 devshell 中的。
|
|
203
|
+
routes.forEach((r) => {
|
|
204
|
+
if (!r) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let prefix = r.split('=')[0];
|
|
209
|
+
let index = manifest['application']['routes'].findIndex((mr) => {
|
|
210
|
+
if (!mr) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return mr.split('=')[0] == prefix;
|
|
214
|
+
});
|
|
215
|
+
if (index > -1) {
|
|
216
|
+
manifest['application']['routes'].splice(index, 1);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
const application = { routes };
|
|
220
|
+
return mergeYamlInMemory([manifest, { application }]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// 在生成 manifest.yml 之前合并 lzc-build.yml devshell 字段的值
|
|
224
|
+
// 并加上 health_check 字段, 当处于 devshell 的情况时,禁用 health_check
|
|
225
|
+
// 避免应用永远处于 unhealth 导致状态卡在 starting
|
|
226
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest, options) => {
|
|
227
|
+
logger.debug('merge lzc-build.yml devshell services\n', options);
|
|
228
|
+
const userapp = this.isUserApp ? shellApi.uid + '.' : '';
|
|
229
|
+
const devshell = {
|
|
230
|
+
application: {
|
|
231
|
+
devshell: options['devshell'],
|
|
232
|
+
health_check: {
|
|
233
|
+
disable: true,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
return mergeYamlInMemory([manifest, devshell]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// 如果 services/devshell 中有 dependencies 字段,优先使用
|
|
241
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
242
|
+
const config = manifest['application']['devshell'];
|
|
243
|
+
if (!config || !config['dependencies']) {
|
|
244
|
+
return manifest;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const deps = config['dependencies'];
|
|
248
|
+
if (deps.length == 0) {
|
|
249
|
+
logger.warn(t('lzc_cli.lib.app.lpk_devshell.devshell_build_skip_dependencies_tips', 'dependencies 内容为空,跳过 dependencies'));
|
|
250
|
+
delete manifest['application']['devshell']['dependencies'];
|
|
251
|
+
return manifest;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const depsStr = deps.sort().join(' ');
|
|
255
|
+
logger.debug(t('lzc_cli.lib.app.lpk_devshell.devshell_build_dockerfile_tips', '开始创建 Dockerfile 文件'));
|
|
256
|
+
|
|
257
|
+
const tempDir = fs.mkdtempSync('.lzc-cli-build-dependencies');
|
|
258
|
+
try {
|
|
259
|
+
const dockerfilePath = path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'Dockerfile.in');
|
|
260
|
+
await createTemplateFileCommon(dockerfilePath, path.join(tempDir, 'Dockerfile'), { dependencies: depsStr });
|
|
261
|
+
|
|
262
|
+
const label = `${await md5String(depsStr)}:latest`;
|
|
263
|
+
logger.debug(t('lzc_cli.lib.app.lpk_devshell.devshell_build_image_for_box_tips', `开始在盒子中构建 {{label}} 镜像 from {{tempDir}}`, { label, tempDir }));
|
|
264
|
+
|
|
265
|
+
const contextTar = await collectContextFromDockerFile(tempDir, path.resolve(tempDir, 'Dockerfile'));
|
|
266
|
+
const bridge = new DebugBridge();
|
|
267
|
+
await bridge.init();
|
|
268
|
+
const tag = await bridge.buildImage(label, contextTar);
|
|
269
|
+
delete manifest['application']['devshell'];
|
|
270
|
+
manifest['application']['image'] = tag;
|
|
271
|
+
} finally {
|
|
272
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
273
|
+
}
|
|
274
|
+
return manifest;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// 如果 services 中有 devshell 的字段,需要检测是否需要提前构建
|
|
278
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
279
|
+
const application = manifest['application'];
|
|
280
|
+
if (!application || !application['devshell']) {
|
|
281
|
+
return manifest;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const config = manifest['application']['devshell'];
|
|
285
|
+
if (!config || !config['build']) {
|
|
286
|
+
return manifest;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const label = `${manifest['package']}-devshell:${manifest['version']}`;
|
|
290
|
+
logger.debug(t('lzc_cli.lib.app.lpk_devshell.devshell_build_label_image_box_tips', `开始在盒子中构建 {{label}} 镜像`, { label }));
|
|
291
|
+
|
|
292
|
+
const contextTar = await collectContextFromDockerFile(process.cwd(), path.resolve(process.cwd(), config['build'], 'Dockerfile'));
|
|
293
|
+
|
|
294
|
+
const bridge = new DebugBridge();
|
|
295
|
+
await bridge.init();
|
|
296
|
+
const tag = await bridge.buildImage(label, contextTar);
|
|
297
|
+
delete manifest['application']['devshell'];
|
|
298
|
+
manifest['application']['image'] = tag;
|
|
299
|
+
return manifest;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// 如果 devshell 中指定了 image 字段将使用 image 字段
|
|
303
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
304
|
+
const config = manifest['application'];
|
|
305
|
+
if (config['devshell'] && config['devshell']['image']) {
|
|
306
|
+
manifest['application']['image'] = config['devshell']['image'];
|
|
307
|
+
delete manifest['application']['devshell'];
|
|
308
|
+
}
|
|
309
|
+
return manifest;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// 如果没有找到 devshell 中没有指定 image 不存在,将默认使用的 lzc-cli/devshell 容器
|
|
313
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
314
|
+
delete manifest['application']['devshell'];
|
|
315
|
+
|
|
316
|
+
const config = manifest['application'];
|
|
317
|
+
if (config['image']) {
|
|
318
|
+
return manifest;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
logger.debug('use default lzc-cli/devshell image');
|
|
322
|
+
manifest['application']['image'] = `registry.lazycat.cloud/lzc-cli/devshell:v0.0.5`;
|
|
323
|
+
return manifest;
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// devshell 模式下,默认打开后台常驻
|
|
327
|
+
this.lpkBuild.onBeforeDumpYaml(async (manifest) => {
|
|
328
|
+
manifest['application']['background_task'] = true;
|
|
329
|
+
return manifest;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 添加一个 devshell 的标记在 lpk 中,标记当前 lpk 为一个 debug 版本
|
|
333
|
+
this.lpkBuild.onBeforeDumpLpk(async (options, cwd, destDir) => {
|
|
334
|
+
fs.writeFileSync(path.resolve(destDir, 'devshell'), '');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// 在构建生成 lpk 包后,调用 deploy 进行部署
|
|
338
|
+
let installer = new LpkInstaller();
|
|
339
|
+
await installer.init();
|
|
340
|
+
await installer.deploy(this.lpkBuild, true);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async rsyncShell() {
|
|
344
|
+
const manifest = await this.lpkBuild.getManifest();
|
|
345
|
+
const pkgId = manifest['package'];
|
|
346
|
+
const devshell = new DevShell(pkgId, this.isUserApp);
|
|
347
|
+
try {
|
|
348
|
+
await devshell.shell();
|
|
349
|
+
} catch (e) {
|
|
350
|
+
logger.error(`devshell fail: ${e}`);
|
|
351
|
+
}
|
|
352
|
+
logger.debug('exit shell');
|
|
353
|
+
// TODO: shell 在正常情况下,按 Ctrl-D 就会退出,回到原来的本地的 shell ,但
|
|
354
|
+
// 现在会一直卡在退出状态后,必须要另外手动的指定 pkill node
|
|
355
|
+
process.exit(0);
|
|
356
|
+
}
|
|
425
357
|
}
|
|
426
358
|
|
|
427
359
|
class DevShell {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
)
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async shell() {
|
|
533
|
-
try {
|
|
534
|
-
// 监听文件
|
|
535
|
-
await this.watchFile(this.appId)
|
|
536
|
-
await this.connectShell(async () => {
|
|
537
|
-
// 在连接成功的时候,同步一次文件
|
|
538
|
-
await this.syncProject(this.appId)
|
|
539
|
-
})
|
|
540
|
-
} catch (e) {
|
|
541
|
-
return Promise.reject(e)
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
async connectShell(onconnect = null) {
|
|
546
|
-
const bridge = new DebugBridge()
|
|
547
|
-
await bridge.init()
|
|
548
|
-
await bridge.devshell(this.appId, this.isUserApp, onconnect)
|
|
549
|
-
}
|
|
360
|
+
constructor(appId, isUserApp) {
|
|
361
|
+
this.appId = appId;
|
|
362
|
+
this.isUserApp = isUserApp;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async syncProject(appId) {
|
|
366
|
+
const resolvedIp = await resolveDomain(`dev.${shellApi.boxname}.heiyu.space`);
|
|
367
|
+
const rsyncDebug = isDebugMode() ? '-P' : '';
|
|
368
|
+
const destDir = `${appId}${this.isUserApp ? '/' + shellApi.uid : ''}`;
|
|
369
|
+
const dest = `rsync://${shellApi.uid}@[${resolvedIp}]:874/lzcapp_cache/${destDir}/devshell`;
|
|
370
|
+
const rsyncCmd = isWindows ? path.join(contextDirname(import.meta.url), '..', '..', 'template', '_lpk', 'win-rsync', 'rsync.exe') : `rsync`;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const rsyncArgs = [`${rsyncDebug}`, `--recursive`, `--relative`, `--perms`, `--update`, `-F --filter=':- .gitignore'`, `--ignore-errors`, `. ${dest}`];
|
|
374
|
+
logger.debug(t('lzc_cli.lib.app.lpk_devshell.sync_project_code_tips', '同步代码: '), rsyncCmd, rsyncArgs.join(' '));
|
|
375
|
+
const rsyncStream = spawn.sync(rsyncCmd, rsyncArgs, {
|
|
376
|
+
env: { ...process.env, RSYNC_PASSWORD: 'fakefakefake' },
|
|
377
|
+
shell: true,
|
|
378
|
+
stdio: ['ignore', isDebugMode() ? 'inherit' : 'ignore', 'inherit'],
|
|
379
|
+
});
|
|
380
|
+
} catch (err) {
|
|
381
|
+
logger.error(t('lzc_cli.lib.app.lpk_devshell.sync_project_rsync_tips', 'rsync 同步失败'));
|
|
382
|
+
logger.debug(err);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// fallback fs.watch on not darwin and windows platform
|
|
387
|
+
// FILEPATH directory or file path
|
|
388
|
+
// CALLBACK => function(eventType, filename)
|
|
389
|
+
// fs.watch 虽然不支持递归,但可以直接监听整个文件夹的变动
|
|
390
|
+
async fallbackWatch(filepath, gitignore, callback) {
|
|
391
|
+
if (gitignore.contain(filepath)) {
|
|
392
|
+
return Promise.resolve();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (filepath.endsWith('.git') || filepath.endsWith('.lazycat')) {
|
|
396
|
+
return Promise.resolve();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fs.watch(filepath, callback(filepath));
|
|
400
|
+
|
|
401
|
+
// 如果为一个文件夹,则扫描当中是否含有子文件夹
|
|
402
|
+
if (isDirSync(filepath)) {
|
|
403
|
+
return gitignore.readdir(filepath, (err, files) => {
|
|
404
|
+
if (err) {
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (files.length <= 0) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
files.forEach((f) => {
|
|
413
|
+
if (f.isDirectory()) {
|
|
414
|
+
this.fallbackWatch(path.join(filepath, f.name), gitignore, callback);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 监听非.gitignore文件
|
|
422
|
+
// TODO: 目前仅仅监听process.cwd()以下的文件
|
|
423
|
+
async watchFile(appId) {
|
|
424
|
+
const ignore = new GitIgnore(process.cwd());
|
|
425
|
+
await ignore.collect();
|
|
426
|
+
chokidar
|
|
427
|
+
.watch('.', {
|
|
428
|
+
ignored: (path) => {
|
|
429
|
+
if (['.git', '.lazycat'].some((p) => path.startsWith(p))) return true;
|
|
430
|
+
|
|
431
|
+
return ignore.contain(path);
|
|
432
|
+
},
|
|
433
|
+
ignoreInitial: true,
|
|
434
|
+
})
|
|
435
|
+
.on(
|
|
436
|
+
'all',
|
|
437
|
+
debounce(() => {
|
|
438
|
+
this.syncProject(appId);
|
|
439
|
+
}, 1000),
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async shell() {
|
|
444
|
+
try {
|
|
445
|
+
// 监听文件
|
|
446
|
+
await this.watchFile(this.appId);
|
|
447
|
+
await this.connectShell(async () => {
|
|
448
|
+
// 在连接成功的时候,同步一次文件
|
|
449
|
+
await this.syncProject(this.appId);
|
|
450
|
+
});
|
|
451
|
+
} catch (e) {
|
|
452
|
+
return Promise.reject(e);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async connectShell(onconnect = null) {
|
|
457
|
+
const bridge = new DebugBridge();
|
|
458
|
+
await bridge.init();
|
|
459
|
+
await bridge.devshell(this.appId, this.isUserApp, onconnect);
|
|
460
|
+
}
|
|
550
461
|
}
|