@lazycatcloud/lzc-cli 1.1.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.
Files changed (64) hide show
  1. package/README.md +21 -0
  2. package/lib/api.js +123 -0
  3. package/lib/archiver.js +128 -0
  4. package/lib/builder.js +183 -0
  5. package/lib/dev.js +300 -0
  6. package/lib/docker/promise.js +91 -0
  7. package/lib/docker-compose.js +51 -0
  8. package/lib/env.js +104 -0
  9. package/lib/generator.js +115 -0
  10. package/lib/key.js +112 -0
  11. package/lib/sdk.js +129 -0
  12. package/lib/utils.js +349 -0
  13. package/package.json +53 -0
  14. package/scripts/cli.js +98 -0
  15. package/template/_lazycat/_gitignore +1 -0
  16. package/template/_lazycat/debug/devforward/50x.html +30 -0
  17. package/template/_lazycat/debug/devforward/Dockerfile +16 -0
  18. package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +11 -0
  19. package/template/_lazycat/debug/devforward/entrypoint.sh +10 -0
  20. package/template/_lazycat/debug/devforward/nginx.conf.template +56 -0
  21. package/template/_lazycat/debug/devforward/sshd_config +116 -0
  22. package/template/_lazycat/debug/shell/50x.html +32 -0
  23. package/template/_lazycat/debug/shell/Dockerfile +16 -0
  24. package/template/_lazycat/debug/shell/build.sh +15 -0
  25. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +23 -0
  26. package/template/_lazycat/debug/shell/entrypoint.sh +10 -0
  27. package/template/_lazycat/debug/shell/nginx.conf.template +64 -0
  28. package/template/_lazycat/debug/shell/sshd_config +117 -0
  29. package/template/_lazycat/docker-compose.yml.in +17 -0
  30. package/template/_lazycat/icon.svg +1 -0
  31. package/template/_lazycat/screenshot.png +0 -0
  32. package/template/golang/.godir +1 -0
  33. package/template/golang/README.md +13 -0
  34. package/template/golang/assets/css/bootstrap-responsive.css +1088 -0
  35. package/template/golang/assets/css/bootstrap-responsive.min.css +9 -0
  36. package/template/golang/assets/css/bootstrap.css +5893 -0
  37. package/template/golang/assets/css/bootstrap.min.css +9 -0
  38. package/template/golang/assets/css/rego.css +45 -0
  39. package/template/golang/assets/img/glyphicons-halflings-white.png +0 -0
  40. package/template/golang/assets/img/glyphicons-halflings.png +0 -0
  41. package/template/golang/assets/js/bootstrap.js +2025 -0
  42. package/template/golang/assets/js/bootstrap.min.js +6 -0
  43. package/template/golang/assets/js/rego.js +121 -0
  44. package/template/golang/go.mod +3 -0
  45. package/template/golang/index.html +267 -0
  46. package/template/golang/rego.go +83 -0
  47. package/template/release/golang/Dockerfile +18 -0
  48. package/template/release/golang/build.sh +14 -0
  49. package/template/release/vue/Dockerfile +9 -0
  50. package/template/release/vue/build.sh +9 -0
  51. package/template/release/vue/docker-compose.yml.in +8 -0
  52. package/template/vue/README.md +24 -0
  53. package/template/vue/_dockerignore +1 -0
  54. package/template/vue/babel.config.js +5 -0
  55. package/template/vue/package.json +43 -0
  56. package/template/vue/public/favicon.ico +0 -0
  57. package/template/vue/public/index.html +33 -0
  58. package/template/vue/src/App.vue +39 -0
  59. package/template/vue/src/lzc.js +110 -0
  60. package/template/vue/src/main.js +19 -0
  61. package/template/vue/src/todo.vue +640 -0
  62. package/template/vue/src/top-bar.vue +100 -0
  63. package/template/vue/src/webdav.vue +183 -0
  64. package/template/vue/vue.config.js +5 -0
package/lib/dev.js ADDED
@@ -0,0 +1,300 @@
1
+ import execa from "execa";
2
+ import { execSync } from "child_process";
3
+ import fs from "fs";
4
+ import {
5
+ GitIgnore,
6
+ isDirSync,
7
+ isFileExist,
8
+ urlHostname,
9
+ APP_SDK_HOSTNAME,
10
+ } from "./utils.js";
11
+ import path from "path";
12
+ import os from "os";
13
+ import Env from "./env.js";
14
+ import _get from "lodash.get";
15
+ import { execPreBuild } from "./builder.js";
16
+
17
+ import Archiver from "../lib/archiver.js";
18
+
19
+ async function sdkSSHAddr(env) {
20
+ let url = await env.get("SDK_URL");
21
+ return `${APP_SDK_HOSTNAME}@${urlHostname(url)}:2222`;
22
+ }
23
+
24
+ function ParseAppAddress(appId) {
25
+ return `${appId}.${appId}.lzcapp`;
26
+ }
27
+
28
+ class DevModule {
29
+ constructor(appId, cwd) {
30
+ this.appId = appId;
31
+ this.tempDir = path.join(cwd, "output/debug");
32
+ this.cwd = cwd;
33
+ this.env = Env(cwd);
34
+ }
35
+
36
+ reset() {
37
+ try {
38
+ fs.rmSync(this.tempDir, { recursive: true });
39
+ } catch (e) {}
40
+ }
41
+
42
+ // 生成一个包含ssh key的App
43
+ async fakeApp(command) {
44
+ const buildDir = path.join(this.cwd, `debug/${command}`);
45
+ try {
46
+ const allEnv = {
47
+ ...this.env.all,
48
+ APP_IMAGE_NAME: this.env.get("APP_ID"),
49
+ };
50
+ await execPreBuild(buildDir, allEnv);
51
+ let arch = new Archiver(this.tempDir);
52
+ await arch.load(this.cwd);
53
+ await arch.add(buildDir);
54
+ return await arch.finalize();
55
+ } catch (e) {
56
+ throw e;
57
+ }
58
+ }
59
+
60
+ getAddress() {
61
+ return ParseAppAddress(this.appId);
62
+ }
63
+
64
+ // 使用tempDir中的private key进行连接
65
+ async portForward(localaddr) {
66
+ let localServer;
67
+
68
+ try {
69
+ if (!localaddr) {
70
+ let port = this.env.get("DEV_PORT");
71
+ localaddr = `0.0.0.0:${port}`;
72
+ }
73
+
74
+ localServer = execa.command(this.env.get("DEV_CMD"), {
75
+ stdio: "inherit",
76
+ });
77
+
78
+ const appAddr = this.getAddress();
79
+
80
+ let jump = await sdkSSHAddr(this.env);
81
+ const subproces = execa(
82
+ "ssh",
83
+ [
84
+ "-J",
85
+ jump,
86
+ "-o",
87
+ "StrictHostKeyChecking=no",
88
+ "-o",
89
+ "UserKnownHostsFile=/dev/null",
90
+ "-o",
91
+ "ConnectionAttempts=3",
92
+ "-o",
93
+ "ConnectTimeout=30",
94
+ "-o",
95
+ "LogLevel=ERROR",
96
+ "-R",
97
+ `/lazycat_cloud_debug.sock:${localaddr}`,
98
+ "root@" + appAddr,
99
+ "-i",
100
+ path.join(this.tempDir, "debug/ssh/id_ed25519"),
101
+ "-t",
102
+ ],
103
+ { stdio: ["pipe", "inherit", "inherit"] }
104
+ );
105
+
106
+ process.on("SIGINT", () => {
107
+ process.exit();
108
+ });
109
+
110
+ await subproces;
111
+ } catch (e) {
112
+ this.reset();
113
+ return Promise.reject(e);
114
+ } finally {
115
+ if (localServer) localServer.kill("SIGINT");
116
+ }
117
+ }
118
+
119
+ async syncProject(keyFile, appAddr) {
120
+ let jump = await sdkSSHAddr(this.env);
121
+ let rsh = [
122
+ "ssh",
123
+ "-J",
124
+ jump,
125
+ "-o",
126
+ `"StrictHostKeyChecking=no"`,
127
+ "-o",
128
+ `"UserKnownHostsFile=/dev/null"`,
129
+ "-o",
130
+ `"ConnectionAttempts=3"`,
131
+ "-o",
132
+ `"ConnectTimeout=30"`,
133
+ "-o",
134
+ `"LogLevel=ERROR"`,
135
+ "-i",
136
+ keyFile,
137
+ ].join(" ");
138
+ execSync(
139
+ `rsync --rsh='${rsh}' -czrPR . root@${appAddr}:/root/${this.appId} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --delete`,
140
+ { stdio: ["ignore", "ignore", "inherit"] }
141
+ );
142
+ }
143
+
144
+ // fallback fs.watch on not darwin and windows platform
145
+ // FILEPATH directory or file path
146
+ // CALLBACK => function(eventType, filename)
147
+ // fs.watch 虽然不支持递归,当可以直接监听整个文件夹的变动
148
+ async fallbackWatch(filepath, gitignore, callback) {
149
+ if (gitignore.contain(filepath)) {
150
+ return Promise.resolve();
151
+ }
152
+
153
+ fs.watch(filepath, callback);
154
+
155
+ // 如果为一个文件夹,则扫描当中是否含有子文件夹
156
+ if (isDirSync(filepath)) {
157
+ return gitignore.readdir(filepath, (err, files) => {
158
+ if (err) {
159
+ throw err;
160
+ }
161
+
162
+ if (files.length <= 0) {
163
+ return;
164
+ }
165
+
166
+ files.forEach((f) => {
167
+ if (f.isDirectory()) {
168
+ this.fallbackWatch(
169
+ path.join(filepath, f.name),
170
+ gitignore,
171
+ callback
172
+ );
173
+ }
174
+ });
175
+ });
176
+ }
177
+ }
178
+
179
+ // 监听非.gitignore文件
180
+ // TODO: 目前仅仅监听process.cwd()以下的文件
181
+ 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
+ }
198
+ }
199
+
200
+ // 保存一个dev shell状态的文件,如果重复执行dev shell
201
+ // 会先判断这个临时文件是否存在,如果不存在重新部署,更新流程
202
+ // 否则直接ssh连接
203
+ async storeShellStatus(keyFile, appAddr, isWatch) {
204
+ fs.writeFileSync(
205
+ path.join(this.tempDir, ".shellStatus"),
206
+ JSON.stringify({
207
+ keyFile,
208
+ appAddr,
209
+ isWatch,
210
+ })
211
+ );
212
+ }
213
+
214
+ hasShellStatus() {
215
+ return isFileExist(path.join(this.tempDir, ".shellStatus"));
216
+ }
217
+
218
+ async readShellStatus() {
219
+ return JSON.parse(
220
+ fs.readFileSync(path.join(this.tempDir, ".shellStatus"), "utf-8")
221
+ );
222
+ }
223
+
224
+ async directShell() {
225
+ let keyFile, appAddr, isWatch;
226
+ try {
227
+ ({ keyFile, appAddr, isWatch } = await this.readShellStatus());
228
+
229
+ if (!isWatch) {
230
+ await this.syncProject(keyFile, appAddr);
231
+ // 注册watch函数
232
+ await this.watchFile(keyFile, appAddr);
233
+
234
+ this.storeShellStatus(keyFile, appAddr, true);
235
+ }
236
+
237
+ await this.connectShell(keyFile, appAddr);
238
+ } catch (e) {
239
+ return Promise.reject(e);
240
+ } finally {
241
+ if (!isWatch) {
242
+ this.storeShellStatus(keyFile, appAddr, false);
243
+ }
244
+ }
245
+ }
246
+
247
+ async shell() {
248
+ let keyFile;
249
+ let appAddr;
250
+
251
+ try {
252
+ keyFile = path.join(this.tempDir, "debug/ssh/id_ed25519");
253
+ appAddr = this.getAddress();
254
+
255
+ // 当进入shell的时候,都同步一次
256
+ await this.syncProject(keyFile, appAddr);
257
+ // 注册watch函数
258
+ await this.watchFile(keyFile, appAddr);
259
+
260
+ this.storeShellStatus(keyFile, appAddr, true);
261
+
262
+ await this.connectShell(keyFile, appAddr);
263
+ } catch (e) {
264
+ console.log(e);
265
+ // this.reset();
266
+ return Promise.reject(e);
267
+ } finally {
268
+ // 当shell退出后,更新isWatch状态
269
+ this.storeShellStatus(keyFile, appAddr, false);
270
+ }
271
+ }
272
+
273
+ async connectShell(keyFile, appAddr) {
274
+ let jump = await sdkSSHAddr(this.env);
275
+ const subproces = execa(
276
+ "ssh",
277
+ [
278
+ "-J",
279
+ jump,
280
+ "-o",
281
+ "StrictHostKeyChecking=no",
282
+ "-o",
283
+ "UserKnownHostsFile=/dev/null",
284
+ "-o",
285
+ "ConnectionAttempts=3",
286
+ "-o",
287
+ "ConnectTimeout=30",
288
+ "-o",
289
+ "LogLevel=ERROR",
290
+ "root@" + appAddr,
291
+ "-i",
292
+ keyFile,
293
+ ],
294
+ { stdio: "inherit" }
295
+ );
296
+ await subproces;
297
+ }
298
+ }
299
+
300
+ export default DevModule;
@@ -0,0 +1,91 @@
1
+ import Docker from "dockerode";
2
+
3
+ var wrappedProto = Docker.prototype;
4
+
5
+ function denodeify(func) {
6
+ return function (...args) {
7
+ return new Promise((resolve, reject) => {
8
+ let input = [...args, (err, val) => (err ? reject(err) : resolve(val))];
9
+ func.apply(this, input);
10
+ });
11
+ };
12
+ }
13
+
14
+ function proxyPromise(method) {
15
+ var promisey = denodeify(method);
16
+ return function () {
17
+ return promisey.apply(this.$subject, arguments);
18
+ };
19
+ }
20
+
21
+ function promiseObj(target, input) {
22
+ for (var key in input) {
23
+ if (typeof input[key] !== "function") continue;
24
+ target[key] = proxyPromise(input[key]);
25
+ }
26
+ return target;
27
+ }
28
+
29
+ function PromiseProxy(subject) {
30
+ var result = Object.create(subject);
31
+ result.$subject = subject;
32
+ return promiseObj(result, subject);
33
+ }
34
+
35
+ function ContainerProxy(subject) {
36
+ var result = PromiseProxy(subject);
37
+ var exec = result.exec;
38
+ result.exec = function () {
39
+ return exec.apply(this, arguments).then(function (exec) {
40
+ return PromiseProxy(exec);
41
+ });
42
+ };
43
+ return result;
44
+ }
45
+
46
+ function DockerProxy(options) {
47
+ this.$subject = new Docker(options);
48
+ }
49
+
50
+ promiseObj(DockerProxy.prototype, wrappedProto);
51
+
52
+ // sadly we need to wrap run directly as a promise to consolidate both
53
+ // of the resulting arguments.
54
+ DockerProxy.prototype.run = function (image, command, stream) {
55
+ var subject = this.$subject;
56
+ return new Promise(function (accept, reject) {
57
+ subject.run(image, command, stream, function (err, result, container) {
58
+ if (err) return reject(err);
59
+ accept({
60
+ result: result,
61
+ // re-wrap
62
+ container: ContainerProxy(container),
63
+ });
64
+ });
65
+ });
66
+ };
67
+
68
+ // We also have wrap createContainer manually as it returns
69
+ DockerProxy.prototype.createContainer = function (opts) {
70
+ var subject = this.$subject;
71
+ return new Promise(function (accept, reject) {
72
+ subject.createContainer(opts, function (err, container) {
73
+ if (err) return reject(err);
74
+ accept(ContainerProxy(container));
75
+ });
76
+ });
77
+ };
78
+
79
+ DockerProxy.prototype.getImage = function (id) {
80
+ return PromiseProxy(this.$subject.getImage(id));
81
+ };
82
+
83
+ DockerProxy.prototype.getContainer = function (id) {
84
+ return ContainerProxy(this.$subject.getContainer(id));
85
+ };
86
+
87
+ DockerProxy.prototype.getVolume = function (name) {
88
+ return PromiseProxy(this.$subject.getVolume(name));
89
+ };
90
+
91
+ export default DockerProxy;
@@ -0,0 +1,51 @@
1
+ import yaml from "js-yaml";
2
+ import fs from "fs";
3
+ import mergeWith from "lodash.mergewith";
4
+ import isArray from "lodash.isarray";
5
+
6
+ export function load(filePath) {
7
+ return yaml.load(fs.readFileSync(filePath, "utf8"));
8
+ }
9
+
10
+ // 用于合并多个 DockerCompose 文件
11
+ export default class DockerCompose {
12
+ constructor(basefile) {
13
+ this.basefile = basefile;
14
+ this.obj = load(basefile);
15
+ }
16
+ // 直接修改 yaml 内容
17
+ pipe(cb) {
18
+ cb.apply(this, [this.obj]);
19
+ return this;
20
+ }
21
+
22
+ // mergeFile({file: ""}) or merge({content: ""})
23
+ merge(context) {
24
+ let newObj;
25
+ if (context.file) {
26
+ newObj = load(context.file);
27
+ }
28
+ if (context.content) {
29
+ newObj = yaml.load(context.content);
30
+ }
31
+
32
+ this.obj = mergeWith(this.obj, newObj, (objValue, srcValue) => {
33
+ if (isArray(objValue)) {
34
+ return objValue.concat(srcValue);
35
+ }
36
+ });
37
+ return this;
38
+ }
39
+
40
+ // to: optional
41
+ save(to) {
42
+ fs.writeFileSync(
43
+ to || this.basefile,
44
+ yaml.dump(this.obj, {
45
+ styles: {
46
+ "!!null": "empty", // dump null as ""
47
+ },
48
+ })
49
+ );
50
+ }
51
+ }
package/lib/env.js ADDED
@@ -0,0 +1,104 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import { ensureDir, APP_CONFIG_FILE } from "./utils.js";
5
+
6
+ const GLOBAL_CONFIG_NAME = "box-config.json";
7
+
8
+ const permitVars = [
9
+ "APP_ID",
10
+ "APP_NAME",
11
+ "APP_IMAGE_NAME",
12
+ "BUILD_CONTEXT",
13
+ "HTTP_SERVICE_PORT",
14
+ "DEV_PORT",
15
+ "DEV_CMD",
16
+ "SDK_URL",
17
+ ];
18
+
19
+ const permitGlobalVars = ["SDK_URL"];
20
+
21
+ class Env {
22
+ constructor(cwd) {
23
+ this.envPath = path.join(cwd, APP_CONFIG_FILE);
24
+ this.globalEnvPath = path.join(
25
+ os.homedir(),
26
+ ".config/lazycat",
27
+ GLOBAL_CONFIG_NAME
28
+ );
29
+ this.load();
30
+ }
31
+
32
+ load() {
33
+ this.allEnv = {};
34
+ this.env = {};
35
+ this.globalEnv = {};
36
+
37
+ if (fs.existsSync(this.envPath)) {
38
+ this.env = JSON.parse(fs.readFileSync(this.envPath));
39
+ }
40
+
41
+ if (fs.existsSync(this.globalEnvPath)) {
42
+ this.globalEnv = JSON.parse(fs.readFileSync(this.globalEnvPath));
43
+ }
44
+ this.updateAllEnv();
45
+ }
46
+
47
+ get isEnvExist() {
48
+ return fs.existsSync(this.envPath);
49
+ }
50
+
51
+ get all() {
52
+ return Object.keys(this.allEnv).reduce((p, c) => {
53
+ if (permitVars.includes(c)) p[c] = this.allEnv[c];
54
+ return p;
55
+ }, {});
56
+ }
57
+
58
+ hasKey(key) {
59
+ return Object.keys(this.allEnv).indexOf(key) > -1;
60
+ }
61
+
62
+ get(key) {
63
+ return this.allEnv[key];
64
+ }
65
+
66
+ updateAllEnv() {
67
+ this.allEnv = Object.assign({}, this.globalEnv, this.env);
68
+ }
69
+
70
+ setGlobal(pairs = {}) {
71
+ const configPath = this.globalEnvPath;
72
+ if (Object.keys(pairs).some((k) => permitGlobalVars.indexOf(k) < 0)) {
73
+ return;
74
+ }
75
+
76
+ ensureDir(configPath);
77
+ Object.assign(this.globalEnv, pairs);
78
+ fs.writeFileSync(configPath, JSON.stringify(this.globalEnv, null, " "));
79
+ this.updateAllEnv();
80
+ }
81
+
82
+ set(pairs = {}) {
83
+ if (Object.keys(pairs).some((k) => permitVars.indexOf(k) < 0)) {
84
+ return;
85
+ }
86
+
87
+ ensureDir(this.envPath);
88
+ Object.assign(this.env, pairs);
89
+ fs.writeFileSync(this.envPath, JSON.stringify(this.env, null, " "));
90
+ this.updateAllEnv();
91
+ }
92
+
93
+ stringify() {
94
+ let content = "";
95
+ Object.keys(this.all).forEach((key) => {
96
+ content += `${key}="${this.allEnv[key]}"\n`;
97
+ });
98
+ return content.trimEnd();
99
+ }
100
+ }
101
+
102
+ export default function (cwd) {
103
+ return new Env(cwd);
104
+ }
@@ -0,0 +1,115 @@
1
+ import glob from "fast-glob";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import execa from "execa";
5
+ import { isBinaryFileSync } from "isbinaryfile";
6
+ import { contextDirname } from "./utils.js";
7
+ import chalk from "chalk";
8
+
9
+ const __dirname = contextDirname();
10
+
11
+ export const TemplateConfig = {
12
+ vue: {
13
+ template: "vue",
14
+ defaultEnvs: {
15
+ HTTP_SERVICE_PORT: "8080",
16
+ BUILD_CONTEXT: ".lazycat/release",
17
+ },
18
+ after: async function (name) {
19
+ console.log(chalk.green("开始安装依赖"));
20
+
21
+ const subprocess = execa("npm", ["install"], {
22
+ cwd: path.join(process.cwd(), name),
23
+ stdio: "inherit",
24
+ });
25
+ await subprocess;
26
+ console.log(
27
+ chalk.green(
28
+ `
29
+ ✨ 懒猫云应用 ${name} 已创建:
30
+ ${chalk.yellow(`cd ${name}`)}
31
+ ${chalk.yellow(`npm run serve`)}
32
+ 将应用部署至设备中:
33
+ ${chalk.yellow(`lzc-cli deploy`)}
34
+ `.trim()
35
+ )
36
+ );
37
+ },
38
+ },
39
+ golang: {
40
+ template: "golang",
41
+ defaultEnvs: {
42
+ BUILD_CMD: "go build -o release .",
43
+ HTTP_SERVICE_PORT: "3000",
44
+ },
45
+ after: function (name) {
46
+ console.log(
47
+ chalk.green(
48
+ `
49
+ ✨ 懒猫云应用 ${name} 已创建:
50
+ ${chalk.yellow(`cd ${name}`)}
51
+ ${chalk.yellow(`go run .`)}
52
+ 将应用部署至设备中:
53
+ ${chalk.yellow(`lzc-cli deploy`)}
54
+ `.trim()
55
+ )
56
+ );
57
+ },
58
+ },
59
+ };
60
+
61
+ class Generator {
62
+ constructor(context) {
63
+ this.context = context || {};
64
+ this.templateRoot = path.join(__dirname, "../template/");
65
+ }
66
+
67
+ async generate(from, to, opts = {}) {
68
+ const files = await this.loadFiles(from, opts.prefix);
69
+ this.writeFileTree(to, files);
70
+ }
71
+
72
+ async loadFiles(dirPath, prefix = "") {
73
+ const templateDir = path.join(this.templateRoot, dirPath);
74
+
75
+ const _files = await glob(["**/*"], { cwd: templateDir });
76
+ let content;
77
+ let files = {};
78
+ for (let p of _files) {
79
+ const sourcePath = path.join(templateDir, p);
80
+
81
+ const statInfo = fs.statSync(sourcePath);
82
+ const mode = statInfo.mode;
83
+ if (isBinaryFileSync(sourcePath)) {
84
+ content = fs.readFileSync(sourcePath);
85
+ } else {
86
+ content = fs.readFileSync(sourcePath, "utf8");
87
+ }
88
+ // change _ prefix file into dot file
89
+ if (p.startsWith("_")) {
90
+ p = p.replace("_", ".");
91
+ }
92
+
93
+ files[path.join(prefix, p)] = {
94
+ content,
95
+ mode,
96
+ };
97
+ }
98
+ return new Promise((resolve) => resolve(files));
99
+ }
100
+
101
+ writeFileTree(target, files) {
102
+ Object.keys(files).forEach((p) => {
103
+ const filePath = path.join(target, p);
104
+ if (!fs.existsSync(path.dirname(filePath))) {
105
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
106
+ }
107
+ console.log(`创建文件 ${filePath}`);
108
+ fs.writeFileSync(filePath, files[p].content, { mode: files[p].mode });
109
+ });
110
+ }
111
+ }
112
+
113
+ export default (context) => {
114
+ return new Generator(context);
115
+ };