@lazycatcloud/lzc-cli 1.1.1 → 1.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.
Files changed (67) hide show
  1. package/cmds/app.js +137 -0
  2. package/cmds/config.js +55 -0
  3. package/cmds/create.js +55 -0
  4. package/cmds/dev.js +122 -0
  5. package/cmds/init.js +125 -0
  6. package/cmds/log.js +103 -0
  7. package/cmds/publish.js +116 -0
  8. package/lib/api.js +34 -36
  9. package/lib/archiver.js +50 -31
  10. package/lib/box/check_qemu.js +27 -0
  11. package/lib/box/hportal.js +114 -0
  12. package/lib/box/index.js +152 -0
  13. package/lib/box/qemu_vm_mgr.js +625 -0
  14. package/lib/box/schemes/vm_box_system_debian.json +47 -0
  15. package/lib/builder.js +154 -35
  16. package/lib/dev.js +51 -32
  17. package/lib/env.js +276 -57
  18. package/lib/generator.js +31 -0
  19. package/lib/git/git-commit.sh +7 -0
  20. package/lib/git/git-reset.sh +15 -0
  21. package/lib/key.js +14 -11
  22. package/lib/sdk.js +7 -10
  23. package/lib/utils.js +149 -53
  24. package/package.json +18 -5
  25. package/scripts/cli.js +134 -70
  26. package/template/_lazycat/app-config +1 -0
  27. package/template/_lazycat/docker-compose.yml.in +3 -5
  28. package/template/golang/README.md +3 -4
  29. package/template/golang/assets/css/bootstrap-responsive.css +26 -23
  30. package/template/golang/assets/css/bootstrap-responsive.min.css +1065 -1
  31. package/template/golang/assets/css/bootstrap.css +733 -362
  32. package/template/golang/assets/css/bootstrap.min.css +5299 -1
  33. package/template/golang/assets/css/rego.css +17 -17
  34. package/template/golang/assets/js/bootstrap.js +1340 -1311
  35. package/template/golang/assets/js/bootstrap.min.js +1240 -5
  36. package/template/golang/assets/js/rego.js +80 -69
  37. package/template/golang/index.html +61 -59
  38. package/template/ionic_vue3/README.md +46 -0
  39. package/template/ionic_vue3/_eslintrc.cjs +24 -0
  40. package/template/ionic_vue3/_gitignore +29 -0
  41. package/template/ionic_vue3/_vscode/extensions.json +6 -0
  42. package/template/ionic_vue3/capacitor.config.ts +10 -0
  43. package/template/ionic_vue3/env.d.ts +1 -0
  44. package/template/ionic_vue3/index.html +13 -0
  45. package/template/ionic_vue3/ionic.config.json +7 -0
  46. package/template/ionic_vue3/package.json +52 -0
  47. package/template/ionic_vue3/postcss.config.js +6 -0
  48. package/template/ionic_vue3/public/favicon.ico +0 -0
  49. package/template/ionic_vue3/src/App.vue +11 -0
  50. package/template/ionic_vue3/src/assets/logo.svg +1 -0
  51. package/template/ionic_vue3/src/index.css +3 -0
  52. package/template/ionic_vue3/src/main.ts +35 -0
  53. package/template/ionic_vue3/src/router/index.ts +15 -0
  54. package/template/ionic_vue3/src/theme/variables.css +231 -0
  55. package/template/ionic_vue3/src/views/Home.vue +38 -0
  56. package/template/ionic_vue3/tailwind.config.js +7 -0
  57. package/template/ionic_vue3/tsconfig.json +16 -0
  58. package/template/ionic_vue3/tsconfig.vite-config.json +8 -0
  59. package/template/ionic_vue3/vite.config.ts +28 -0
  60. package/template/release/golang/build.sh +1 -2
  61. package/template/release/ionic_vue3/Dockerfile +10 -0
  62. package/template/release/ionic_vue3/build.sh +9 -0
  63. package/template/release/ionic_vue3/docker-compose.yml.in +8 -0
  64. package/template/release/vue/Dockerfile +3 -2
  65. package/template/release/vue/build.sh +4 -2
  66. package/template/vue/README.md +5 -0
  67. package/template/vue/babel.config.js +2 -4
package/lib/builder.js CHANGED
@@ -9,15 +9,17 @@ import { DockerfileParser } from "dockerfile-ast";
9
9
  import glob from "fast-glob";
10
10
  import ignore from "@balena/dockerignore";
11
11
  import { createLogUpdate } from "log-update";
12
+ import env, { sdkEnv } from "./env.js";
13
+ import Dockerode from "dockerode";
12
14
 
13
15
  export const PRE_BUILD_FILE = "build.sh";
14
16
 
15
- export async function execPreBuild(targetDir, env = {}) {
17
+ export async function execPreBuild(targetDir, context, env = {}) {
16
18
  const buildFile = path.join(targetDir, PRE_BUILD_FILE);
17
19
  if (!fs.existsSync(buildFile)) return;
18
20
 
19
21
  const subproces = execa(buildFile, {
20
- cwd: targetDir,
22
+ cwd: context,
21
23
  env: env,
22
24
  stdio: ["ignore", "inherit", "inherit"],
23
25
  });
@@ -97,48 +99,135 @@ export default class Builder {
97
99
  return src;
98
100
  }
99
101
 
100
- // v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
101
- async dockerRemoteBuildV2(contextDir, dockerfile) {
102
- const env = this.env;
102
+ async dockerLocalBuild(contextDir, dockerfile, tag = env.get("APP_ID")) {
103
103
  const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
104
- const host = new URL(env["SDK_URL"]).host;
105
- const docker = await new DockerClient(host).init();
104
+
105
+ const docker = await new DockerClient().init();
106
106
  try {
107
107
  console.log(chalk.green("开始在设备中构建镜像"));
108
- let stream = await docker.buildImage(
108
+
109
+ const stream = await docker.buildImage(
109
110
  {
110
111
  context: contextDir,
111
112
  src: src,
112
113
  },
113
114
  {
114
- buildargs: env,
115
+ buildargs: env.all,
115
116
  dockerfile: src[0],
116
- t: env["APP_ID"],
117
+ t: tag,
117
118
  }
118
119
  );
119
- await new Promise((resolve, reject) => {
120
- let pulls = [];
121
- let logUpdate;
120
+ const finished = await new Promise((resolve, reject) => {
121
+ let refresher = new StatusRefresher();
122
122
 
123
- function refresh() {
124
- logUpdate(pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
125
- }
123
+ docker.modem.followProgress(
124
+ stream,
125
+ (err, res) => {
126
+ err ? reject(err) : resolve(res);
127
+ },
128
+ (res) => {
129
+ if (res.status) {
130
+ if (res.id) {
131
+ if (["Downloading", "Extracting"].includes(res.status)) {
132
+ refresher.updateStatus({
133
+ id: res.id,
134
+ content: `${res.status} ${res.progress}`,
135
+ });
136
+ } else {
137
+ refresher.updateStatus({ id: res.id, content: res.status });
138
+ }
139
+ } else {
140
+ process.stdout.write(res.status);
141
+ process.stdout.write("\n");
142
+ }
143
+ }
126
144
 
127
- function updatePull({ id, content }) {
128
- let index = pulls.findIndex((p) => p.id == id);
129
- if (index > -1) {
130
- pulls[index] = {
131
- id: id,
132
- content: content,
133
- };
134
- } else {
135
- pulls.push({
136
- id: id,
137
- content: content,
138
- });
145
+ if (res.stream) {
146
+ if (res.stream.startsWith("Step")) {
147
+ refresher.reset();
148
+ }
149
+ process.stdout.write(res.stream);
150
+ }
151
+ if (res.error) {
152
+ reject(res.error);
153
+ }
139
154
  }
140
- }
155
+ );
156
+ });
157
+ await this.pushImage(tag);
158
+ } catch (err) {
159
+ throw err;
160
+ }
161
+ }
162
+
163
+ async pushImage(tag) {
164
+ console.log(chalk.green(`开始推送镜像`));
165
+ const docker = await new DockerClient().init();
166
+
167
+ const image = new Dockerode.Image(docker.modem, tag);
168
+
169
+ // TODO hardcode 未来开发者需要获取推送至 lazycat 的token
170
+ return new Promise((resolve, reject) => {
171
+ image
172
+ .push({
173
+ authconfig: { auth: "bG5rczpsbmtzMjAyMA==" },
174
+ })
175
+ .then((stream) => {
176
+ let refresher = new StatusRefresher();
177
+ docker.modem.followProgress(
178
+ stream,
179
+ (err, res) => {
180
+ err ? reject(err) : resolve(res);
181
+ },
182
+ (res) => {
183
+ if (res.status) {
184
+ if (res.id) {
185
+ refresher.updateStatus({ id: res.id, content: res.status });
186
+ } else {
187
+ process.stdout.write(res.status);
188
+ process.stdout.write("\n");
189
+ }
190
+ }
141
191
 
192
+ if (res.stream) {
193
+ process.stdout.write(res.stream);
194
+ }
195
+ if (res.error) {
196
+ reject(res.error);
197
+ }
198
+ }
199
+ );
200
+ });
201
+ });
202
+ }
203
+
204
+ /**
205
+ * v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
206
+ * @param contextDir
207
+ * @param dockerfile
208
+ * @param tag
209
+ */
210
+ async dockerRemoteBuildV2(contextDir, dockerfile, tag = env.get("APP_ID")) {
211
+ const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
212
+ const host = new URL(sdkEnv.sdkUrl).host;
213
+ console.log(host);
214
+ const docker = await new DockerClient(host).init();
215
+ try {
216
+ return new Promise(async (resolve, reject) => {
217
+ let refresher = new StatusRefresher();
218
+
219
+ console.log(chalk.green("开始在设备中构建镜像"));
220
+ const stream = await docker.buildImage(
221
+ {
222
+ context: contextDir,
223
+ src: src,
224
+ },
225
+ {
226
+ buildargs: this.env,
227
+ dockerfile: src[0],
228
+ t: tag,
229
+ }
230
+ );
142
231
  docker.modem.followProgress(
143
232
  stream,
144
233
  (err, res) => {
@@ -148,15 +237,16 @@ export default class Builder {
148
237
  if (res.status) {
149
238
  if (res.id) {
150
239
  if (["Downloading", "Extracting"].includes(res.status)) {
151
- updatePull({
240
+ refresher.updateStatus({
152
241
  id: res.id,
153
242
  content: `${res.status} ${res.progress}`,
154
243
  });
155
244
  } else {
156
- updatePull({ id: res.id, content: res.status });
245
+ refresher.updateStatus({
246
+ id: res.id,
247
+ content: res.status,
248
+ });
157
249
  }
158
-
159
- refresh();
160
250
  } else {
161
251
  process.stdout.write(res.status);
162
252
  process.stdout.write("\n");
@@ -165,8 +255,7 @@ export default class Builder {
165
255
 
166
256
  if (res.stream) {
167
257
  if (res.stream.startsWith("Step")) {
168
- pulls = [];
169
- logUpdate = createLogUpdate(process.stdout);
258
+ refresher.reset();
170
259
  }
171
260
  process.stdout.write(res.stream);
172
261
  }
@@ -181,3 +270,33 @@ export default class Builder {
181
270
  }
182
271
  }
183
272
  }
273
+
274
+ class StatusRefresher {
275
+ constructor() {
276
+ this.reset();
277
+ }
278
+ refresh() {
279
+ this.logUpdate(this.pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
280
+ }
281
+
282
+ reset() {
283
+ this.pulls = [];
284
+ this.logUpdate = createLogUpdate(process.stdout);
285
+ }
286
+
287
+ updateStatus({ id, content }) {
288
+ let index = this.pulls.findIndex((p) => p.id == id);
289
+ if (index > -1) {
290
+ this.pulls[index] = {
291
+ id: id,
292
+ content: content,
293
+ };
294
+ } else {
295
+ this.pulls.push({
296
+ id: id,
297
+ content: content,
298
+ });
299
+ }
300
+ this.refresh();
301
+ }
302
+ }
package/lib/dev.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import execa from "execa";
2
2
  import { execSync } from "child_process";
3
3
  import fs from "fs";
4
+ import chalk from "chalk";
4
5
  import {
5
6
  GitIgnore,
6
7
  isDirSync,
@@ -9,15 +10,16 @@ import {
9
10
  APP_SDK_HOSTNAME,
10
11
  } from "./utils.js";
11
12
  import path from "path";
12
- import os from "os";
13
- import Env from "./env.js";
13
+ import env, { sdkEnv } from "./env.js";
14
14
  import _get from "lodash.get";
15
15
  import { execPreBuild } from "./builder.js";
16
+ import Key from "./key.js";
17
+ import chokidar from "chokidar";
16
18
 
17
19
  import Archiver from "../lib/archiver.js";
18
20
 
19
21
  async function sdkSSHAddr(env) {
20
- let url = await env.get("SDK_URL");
22
+ let url = sdkEnv.sdkUrl;
21
23
  return `${APP_SDK_HOSTNAME}@${urlHostname(url)}:2222`;
22
24
  }
23
25
 
@@ -30,7 +32,7 @@ class DevModule {
30
32
  this.appId = appId;
31
33
  this.tempDir = path.join(cwd, "output/debug");
32
34
  this.cwd = cwd;
33
- this.env = Env(cwd);
35
+ // this.env = Env(cwd);
34
36
  }
35
37
 
36
38
  reset() {
@@ -44,12 +46,12 @@ class DevModule {
44
46
  const buildDir = path.join(this.cwd, `debug/${command}`);
45
47
  try {
46
48
  const allEnv = {
47
- ...this.env.all,
48
- APP_IMAGE_NAME: this.env.get("APP_ID"),
49
+ ...env.all,
50
+ APP_IMAGE_NAME: env.get("APP_ID"),
49
51
  };
50
- await execPreBuild(buildDir, allEnv);
52
+ await execPreBuild(buildDir, buildDir, allEnv);
51
53
  let arch = new Archiver(this.tempDir);
52
- await arch.load(this.cwd);
54
+ await arch.load(this.cwd, allEnv);
53
55
  await arch.add(buildDir);
54
56
  return await arch.finalize();
55
57
  } catch (e) {
@@ -67,17 +69,17 @@ class DevModule {
67
69
 
68
70
  try {
69
71
  if (!localaddr) {
70
- let port = this.env.get("DEV_PORT");
72
+ let port = env.get("DEV_PORT");
71
73
  localaddr = `0.0.0.0:${port}`;
72
74
  }
73
75
 
74
- localServer = execa.command(this.env.get("DEV_CMD"), {
76
+ localServer = execa.command(env.get("DEV_CMD"), {
75
77
  stdio: "inherit",
76
78
  });
77
79
 
78
80
  const appAddr = this.getAddress();
79
81
 
80
- let jump = await sdkSSHAddr(this.env);
82
+ let jump = await sdkSSHAddr(env);
81
83
  const subproces = execa(
82
84
  "ssh",
83
85
  [
@@ -117,7 +119,7 @@ class DevModule {
117
119
  }
118
120
 
119
121
  async syncProject(keyFile, appAddr) {
120
- let jump = await sdkSSHAddr(this.env);
122
+ let jump = await sdkSSHAddr(env);
121
123
  let rsh = [
122
124
  "ssh",
123
125
  "-J",
@@ -135,6 +137,15 @@ class DevModule {
135
137
  "-i",
136
138
  keyFile,
137
139
  ].join(" ");
140
+ // 检查rsync工具是否存在:提示用户
141
+ try {
142
+ execSync("rsync -V", {
143
+ stdio: "ignore",
144
+ });
145
+ } catch {
146
+ console.log(chalk.red("请检查 rsync 是否安装,路径是否正确!"));
147
+ process.exit();
148
+ }
138
149
  execSync(
139
150
  `rsync --rsh='${rsh}' -czrPR . root@${appAddr}:/root/${this.appId} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --delete`,
140
151
  { stdio: ["ignore", "ignore", "inherit"] }
@@ -144,13 +155,17 @@ class DevModule {
144
155
  // fallback fs.watch on not darwin and windows platform
145
156
  // FILEPATH directory or file path
146
157
  // CALLBACK => function(eventType, filename)
147
- // fs.watch 虽然不支持递归,当可以直接监听整个文件夹的变动
158
+ // fs.watch 虽然不支持递归,但可以直接监听整个文件夹的变动
148
159
  async fallbackWatch(filepath, gitignore, callback) {
149
160
  if (gitignore.contain(filepath)) {
150
161
  return Promise.resolve();
151
162
  }
152
163
 
153
- fs.watch(filepath, callback);
164
+ if (filepath.endsWith(".git") || filepath.endsWith(".lazycat")) {
165
+ return Promise.resolve();
166
+ }
167
+
168
+ fs.watch(filepath, callback(filepath));
154
169
 
155
170
  // 如果为一个文件夹,则扫描当中是否含有子文件夹
156
171
  if (isDirSync(filepath)) {
@@ -179,22 +194,21 @@ class DevModule {
179
194
  // 监听非.gitignore文件
180
195
  // TODO: 目前仅仅监听process.cwd()以下的文件
181
196
  async watchFile(keyFile, appAddr) {
182
- let callback = (eventType, filename) => {
183
- this.syncProject(keyFile, appAddr);
184
- };
185
-
186
- try {
187
- if (["darwin", "win32"].includes(os.platform())) {
188
- // onle macos and window platform support recursive options
189
- fs.watch(process.cwd(), { recursive: true }, callback);
190
- } else {
191
- // fallback to other platform
192
- let gitignore = new GitIgnore();
193
- this.fallbackWatch(process.cwd(), gitignore, callback);
194
- }
195
- } catch (e) {
196
- return Promise.reject(e);
197
- }
197
+ const ignore = new GitIgnore(process.cwd());
198
+ await ignore.collect();
199
+ chokidar
200
+ .watch(".", {
201
+ ignored: (path) => {
202
+ if ([".git", ".lazycat"].some((p) => path.startsWith(p))) return true;
203
+
204
+ return ignore.contain(path);
205
+ },
206
+ ignoreInitial: true,
207
+ })
208
+ .on("all", (event, path) => {
209
+ // console.log(event, path);
210
+ this.syncProject(keyFile, appAddr);
211
+ });
198
212
  }
199
213
 
200
214
  // 保存一个dev shell状态的文件,如果重复执行dev shell
@@ -249,6 +263,9 @@ class DevModule {
249
263
  let appAddr;
250
264
 
251
265
  try {
266
+ // 确保 sdk 能通过公钥正常访问
267
+ await new Key().ensure(sdkEnv.sdkUrl);
268
+
252
269
  keyFile = path.join(this.tempDir, "debug/ssh/id_ed25519");
253
270
  appAddr = this.getAddress();
254
271
 
@@ -271,7 +288,7 @@ class DevModule {
271
288
  }
272
289
 
273
290
  async connectShell(keyFile, appAddr) {
274
- let jump = await sdkSSHAddr(this.env);
291
+ let jump = await sdkSSHAddr(env);
275
292
  const subproces = execa(
276
293
  "ssh",
277
294
  [
@@ -290,8 +307,10 @@ class DevModule {
290
307
  "root@" + appAddr,
291
308
  "-i",
292
309
  keyFile,
310
+ "-t",
311
+ `"cd ~/${env.get("APP_ID")}; exec \\$SHELL -l"`,
293
312
  ],
294
- { stdio: "inherit" }
313
+ { stdio: "inherit", shell: true }
295
314
  );
296
315
  await subproces;
297
316
  }