@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.
- package/README.md +21 -0
- package/lib/api.js +123 -0
- package/lib/archiver.js +128 -0
- package/lib/builder.js +183 -0
- package/lib/dev.js +300 -0
- package/lib/docker/promise.js +91 -0
- package/lib/docker-compose.js +51 -0
- package/lib/env.js +104 -0
- package/lib/generator.js +115 -0
- package/lib/key.js +112 -0
- package/lib/sdk.js +129 -0
- package/lib/utils.js +349 -0
- package/package.json +53 -0
- package/scripts/cli.js +98 -0
- package/template/_lazycat/_gitignore +1 -0
- package/template/_lazycat/debug/devforward/50x.html +30 -0
- package/template/_lazycat/debug/devforward/Dockerfile +16 -0
- package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +11 -0
- package/template/_lazycat/debug/devforward/entrypoint.sh +10 -0
- package/template/_lazycat/debug/devforward/nginx.conf.template +56 -0
- package/template/_lazycat/debug/devforward/sshd_config +116 -0
- package/template/_lazycat/debug/shell/50x.html +32 -0
- package/template/_lazycat/debug/shell/Dockerfile +16 -0
- package/template/_lazycat/debug/shell/build.sh +15 -0
- package/template/_lazycat/debug/shell/docker-compose.override.yml.in +23 -0
- package/template/_lazycat/debug/shell/entrypoint.sh +10 -0
- package/template/_lazycat/debug/shell/nginx.conf.template +64 -0
- package/template/_lazycat/debug/shell/sshd_config +117 -0
- package/template/_lazycat/docker-compose.yml.in +17 -0
- package/template/_lazycat/icon.svg +1 -0
- package/template/_lazycat/screenshot.png +0 -0
- package/template/golang/.godir +1 -0
- package/template/golang/README.md +13 -0
- package/template/golang/assets/css/bootstrap-responsive.css +1088 -0
- package/template/golang/assets/css/bootstrap-responsive.min.css +9 -0
- package/template/golang/assets/css/bootstrap.css +5893 -0
- package/template/golang/assets/css/bootstrap.min.css +9 -0
- package/template/golang/assets/css/rego.css +45 -0
- package/template/golang/assets/img/glyphicons-halflings-white.png +0 -0
- package/template/golang/assets/img/glyphicons-halflings.png +0 -0
- package/template/golang/assets/js/bootstrap.js +2025 -0
- package/template/golang/assets/js/bootstrap.min.js +6 -0
- package/template/golang/assets/js/rego.js +121 -0
- package/template/golang/go.mod +3 -0
- package/template/golang/index.html +267 -0
- package/template/golang/rego.go +83 -0
- package/template/release/golang/Dockerfile +18 -0
- package/template/release/golang/build.sh +14 -0
- package/template/release/vue/Dockerfile +9 -0
- package/template/release/vue/build.sh +9 -0
- package/template/release/vue/docker-compose.yml.in +8 -0
- package/template/vue/README.md +24 -0
- package/template/vue/_dockerignore +1 -0
- package/template/vue/babel.config.js +5 -0
- package/template/vue/package.json +43 -0
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/index.html +33 -0
- package/template/vue/src/App.vue +39 -0
- package/template/vue/src/lzc.js +110 -0
- package/template/vue/src/main.js +19 -0
- package/template/vue/src/todo.vue +640 -0
- package/template/vue/src/top-bar.vue +100 -0
- package/template/vue/src/webdav.vue +183 -0
- package/template/vue/vue.config.js +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## 开始使用
|
|
2
|
+
|
|
3
|
+
### 命令行工具使用方法
|
|
4
|
+
|
|
5
|
+
#### 创建项目
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lzc-cli create a_project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
#### 在本地打包镜像, 如果本地没有发布应用相关文件,会先执行 `init` 命令
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
lzc-cli build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
#### 发布 app 至 app p 至 appdb
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
lzc-cli publish
|
|
21
|
+
```
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
//
|
|
6
|
+
// sdk 提供的API
|
|
7
|
+
export default class API {
|
|
8
|
+
constructor(appId, host) {
|
|
9
|
+
this.appId = appId;
|
|
10
|
+
this.host = host;
|
|
11
|
+
}
|
|
12
|
+
// applyZip api 支持以下query
|
|
13
|
+
// build boolean 是否构建app容器
|
|
14
|
+
// pull boolean 是否从docker.io拉取image
|
|
15
|
+
async install(zipPath, params) {
|
|
16
|
+
let searchParams = new URLSearchParams(params);
|
|
17
|
+
const resp = await fetch(
|
|
18
|
+
`${this.host}/api/v1/app/applyZip?${searchParams.toString()}`,
|
|
19
|
+
{
|
|
20
|
+
method: "POST",
|
|
21
|
+
body: fs.createReadStream(zipPath),
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
if (resp.status == 200) {
|
|
25
|
+
} else {
|
|
26
|
+
throw chalk.red(await resp.text());
|
|
27
|
+
}
|
|
28
|
+
// update
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async uninstall() {
|
|
32
|
+
return await fetch(`${this.host}/api/v1/app/uninstall?id=${this.appId}`, {
|
|
33
|
+
method: "DELETE",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async status() {
|
|
38
|
+
const resp = await fetch(`${this.host}/api/v1/app/status?id=${this.appId}`);
|
|
39
|
+
if (resp.status == 200) {
|
|
40
|
+
const status = await resp.json();
|
|
41
|
+
return status;
|
|
42
|
+
} else {
|
|
43
|
+
let text = await resp.text();
|
|
44
|
+
// console.error(chalk.red(`Status Code: ${resp.status} ${text}`));
|
|
45
|
+
throw text;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async postPublicKey(key) {
|
|
50
|
+
const resp = await fetch(`${this.host}/api/v1/register`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: key,
|
|
53
|
+
});
|
|
54
|
+
if (resp.status != 200) {
|
|
55
|
+
throw new Error(await resp.text());
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async checkStatus() {
|
|
60
|
+
const spinner = ora().start();
|
|
61
|
+
let { status, info } = await this.status();
|
|
62
|
+
spinner.text = "部署进度 " + status;
|
|
63
|
+
|
|
64
|
+
switch (status) {
|
|
65
|
+
case "running":
|
|
66
|
+
console.log(chalk.yellow("应用正在运行中"));
|
|
67
|
+
break;
|
|
68
|
+
case "error":
|
|
69
|
+
spinner.stop();
|
|
70
|
+
console.log(status, info);
|
|
71
|
+
throw info?.msg;
|
|
72
|
+
default:
|
|
73
|
+
try {
|
|
74
|
+
await this.desireStatusTimer((result) => {
|
|
75
|
+
spinner.text = "部署进度 " + result.status;
|
|
76
|
+
return result.status === "running";
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw error;
|
|
80
|
+
} finally {
|
|
81
|
+
spinner.stop();
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
spinner.stop();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
desireStatusTimer(desireCheckFn) {
|
|
89
|
+
let that = this;
|
|
90
|
+
const step = 2000;
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
function schedule(timeout) {
|
|
93
|
+
setTimeout(async () => {
|
|
94
|
+
try {
|
|
95
|
+
const answer = await that.status();
|
|
96
|
+
if (answer) {
|
|
97
|
+
if (answer.status == "error") {
|
|
98
|
+
timeout += step;
|
|
99
|
+
if (timeout > 1000 * 10) {
|
|
100
|
+
reject(new Error(answer.error));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
schedule(timeout);
|
|
104
|
+
} else if (desireCheckFn(answer)) {
|
|
105
|
+
resolve(answer);
|
|
106
|
+
} else {
|
|
107
|
+
schedule(1000);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
timeout += step;
|
|
112
|
+
if (timeout > 1000 * 10) {
|
|
113
|
+
reject(e);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
schedule(timeout);
|
|
117
|
+
}
|
|
118
|
+
}, timeout);
|
|
119
|
+
}
|
|
120
|
+
schedule(1000);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
package/lib/archiver.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
archiveFolder,
|
|
3
|
+
copyDotAppDir,
|
|
4
|
+
ensureDir,
|
|
5
|
+
toPair,
|
|
6
|
+
META_MARK,
|
|
7
|
+
envsubstr,
|
|
8
|
+
} from "./utils.js";
|
|
9
|
+
|
|
10
|
+
import { execPreBuild } from "../lib/builder.js";
|
|
11
|
+
|
|
12
|
+
import execa from "execa";
|
|
13
|
+
|
|
14
|
+
import glob from "fast-glob";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import Env from "./env.js";
|
|
18
|
+
import DockerCompose from "./docker-compose.js";
|
|
19
|
+
|
|
20
|
+
class Archiver {
|
|
21
|
+
constructor(tmpDir) {
|
|
22
|
+
this.tmpDir = tmpDir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async load(baseDir) {
|
|
26
|
+
// 1 拷贝 dotApp 路径至 temp
|
|
27
|
+
this.dotAppDir = baseDir;
|
|
28
|
+
this.env = Env(baseDir);
|
|
29
|
+
await copyDotAppDir(this.dotAppDir, this.tmpDir, {
|
|
30
|
+
env: this.env.all,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async add(cwd) {
|
|
35
|
+
const allEnv = {
|
|
36
|
+
...this.env.all,
|
|
37
|
+
APP_IMAGE_NAME: this.env.get("APP_ID"),
|
|
38
|
+
};
|
|
39
|
+
const target = this.tmpDir;
|
|
40
|
+
try {
|
|
41
|
+
let base = new DockerCompose(path.join(target, "docker-compose.yml"));
|
|
42
|
+
// 2 遍历当前路径, 拷贝至 temp, 同时对 docker-compose 做合并操作
|
|
43
|
+
const _files = await glob(["**"], {
|
|
44
|
+
cwd: cwd,
|
|
45
|
+
dot: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (let file of _files) {
|
|
49
|
+
let absFile = path.join(cwd, file);
|
|
50
|
+
if (path.basename(file).startsWith("docker-compose")) {
|
|
51
|
+
if (file.endsWith(".in")) {
|
|
52
|
+
const options = {
|
|
53
|
+
envs: toPair(allEnv),
|
|
54
|
+
syntax: "default",
|
|
55
|
+
protect: false,
|
|
56
|
+
};
|
|
57
|
+
const output = await envsubstr(fs.readFileSync(absFile, "utf8"), {
|
|
58
|
+
options,
|
|
59
|
+
});
|
|
60
|
+
base.merge({ content: output });
|
|
61
|
+
} else {
|
|
62
|
+
base.merge({ path: absFile });
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
let targetAbsFile = path.join(target, file);
|
|
66
|
+
ensureDir(targetAbsFile);
|
|
67
|
+
fs.copyFileSync(absFile, targetAbsFile);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
base.save();
|
|
72
|
+
|
|
73
|
+
// if (opts.build || false) {
|
|
74
|
+
// // new Builder()
|
|
75
|
+
// }
|
|
76
|
+
} catch (e) {
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async finalize() {
|
|
82
|
+
await this._changeContent();
|
|
83
|
+
return await archiveFolder(this.tmpDir);
|
|
84
|
+
}
|
|
85
|
+
//
|
|
86
|
+
// permissions:
|
|
87
|
+
// - lzcapis
|
|
88
|
+
//
|
|
89
|
+
async _changeContent() {
|
|
90
|
+
let base = new DockerCompose(path.join(this.tmpDir, "docker-compose.yml"));
|
|
91
|
+
base
|
|
92
|
+
.pipe((template) => {
|
|
93
|
+
const meta = template[META_MARK];
|
|
94
|
+
if (
|
|
95
|
+
meta &&
|
|
96
|
+
Array.isArray(meta["permissions"]) &&
|
|
97
|
+
meta["permissions"].includes("lzcapis")
|
|
98
|
+
) {
|
|
99
|
+
template[META_MARK]["ingress"].push({
|
|
100
|
+
service: "lazycat-apis-sidecar",
|
|
101
|
+
port: 8888,
|
|
102
|
+
subdomain: this.env.get("APP_ID"),
|
|
103
|
+
path: "/lzcapis/",
|
|
104
|
+
auth: "oidc",
|
|
105
|
+
authcallback: "/lzcapis/oidc-callback",
|
|
106
|
+
});
|
|
107
|
+
template["services"]["lazycat-apis-sidecar"] = {
|
|
108
|
+
image: "registry.linakesi.com/lazycat-apis-sidecar",
|
|
109
|
+
pull_policy: "always",
|
|
110
|
+
// volumes_from: ["${APP_NAME}:rw"],
|
|
111
|
+
volumes: ["lzcapis-lzcapp:/lzcapp"],
|
|
112
|
+
command: [
|
|
113
|
+
"--client-id=${LAZYCAT_AUTH_OIDC_CLIENT_ID}",
|
|
114
|
+
"--client-secret=${LAZYCAT_AUTH_OIDC_CLIENT_SECRET}",
|
|
115
|
+
"--client-url=https://${LAZYCAT_APP_ORIGIN}/lzcapis/",
|
|
116
|
+
"--issuer=${LAZYCAT_AUTH_OIDC_ISSUER_URL}",
|
|
117
|
+
"--prefix=lzcapis",
|
|
118
|
+
"--fs-root=/lzcapp/documents",
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
template["volumes"]["lzcapis-lzcapp"] = null;
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.save();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default Archiver;
|
package/lib/builder.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// build strategies based on build.yml
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import execa from "execa";
|
|
6
|
+
// import { archiveFolder } from "./utils.js";
|
|
7
|
+
import { DockerClient } from "./sdk.js";
|
|
8
|
+
import { DockerfileParser } from "dockerfile-ast";
|
|
9
|
+
import glob from "fast-glob";
|
|
10
|
+
import ignore from "@balena/dockerignore";
|
|
11
|
+
import { createLogUpdate } from "log-update";
|
|
12
|
+
|
|
13
|
+
export const PRE_BUILD_FILE = "build.sh";
|
|
14
|
+
|
|
15
|
+
export async function execPreBuild(targetDir, env = {}) {
|
|
16
|
+
const buildFile = path.join(targetDir, PRE_BUILD_FILE);
|
|
17
|
+
if (!fs.existsSync(buildFile)) return;
|
|
18
|
+
|
|
19
|
+
const subproces = execa(buildFile, {
|
|
20
|
+
cwd: targetDir,
|
|
21
|
+
env: env,
|
|
22
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
23
|
+
});
|
|
24
|
+
return await subproces;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// builder 需要在app 的场景下使用, 不过有两个路径要注意
|
|
28
|
+
// 一个是 docker build 时的context. 这个一般是运行cli时候的路径
|
|
29
|
+
// 一个是 .lazycat 的路径
|
|
30
|
+
export default class Builder {
|
|
31
|
+
constructor(env) {
|
|
32
|
+
this.env = env;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async local() {}
|
|
36
|
+
|
|
37
|
+
async buildImage(contextDir, buildDir) {
|
|
38
|
+
try {
|
|
39
|
+
await this.dockerRemoteBuildV2(
|
|
40
|
+
contextDir,
|
|
41
|
+
path.join(buildDir, "Dockerfile")
|
|
42
|
+
);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// private functions
|
|
49
|
+
|
|
50
|
+
async collectContextFromDockerFile(contextDir, dockerfilePath) {
|
|
51
|
+
// const contextDir = path.join(process.cwd(), context);
|
|
52
|
+
// const dockerfilePath = path.join(process.cwd(), dockerfile);
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(dockerfilePath)) {
|
|
55
|
+
throw "未发现 Dockerfile";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let src = [path.relative(contextDir, dockerfilePath)];
|
|
59
|
+
|
|
60
|
+
// 通过 COPY 和 ADD 获取所有context中的文件
|
|
61
|
+
// TODO 未处理 dockerignore 情况
|
|
62
|
+
const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
|
|
63
|
+
for (let a of ast.getInstructions()) {
|
|
64
|
+
if (["COPY", "ADD"].includes(a.getInstruction())) {
|
|
65
|
+
const from = a.getArguments()[0].getValue().replace(/^\//, "");
|
|
66
|
+
|
|
67
|
+
const fromFullPath = path.join(contextDir, from);
|
|
68
|
+
|
|
69
|
+
if (fs.existsSync(fromFullPath)) {
|
|
70
|
+
const stat = fs.statSync(path.join(contextDir, from));
|
|
71
|
+
if (stat.isDirectory()) {
|
|
72
|
+
let files = await glob(path.join(from, "**"), {
|
|
73
|
+
cwd: contextDir,
|
|
74
|
+
});
|
|
75
|
+
src = src.concat(files);
|
|
76
|
+
} else if (stat.isFile()) {
|
|
77
|
+
src.push(from);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// try use glob
|
|
81
|
+
let files = await glob(from, {
|
|
82
|
+
cwd: contextDir,
|
|
83
|
+
});
|
|
84
|
+
src = src.concat(files);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// filter by dockerignore
|
|
89
|
+
const dockerIgnoreFile = path.join(contextDir, ".dockerignore");
|
|
90
|
+
//
|
|
91
|
+
if (fs.existsSync(dockerIgnoreFile)) {
|
|
92
|
+
let ig = ignore();
|
|
93
|
+
let data = fs.readFileSync(dockerIgnoreFile, "utf8");
|
|
94
|
+
ig.add(data.split("\n"));
|
|
95
|
+
src = ig.filter(src);
|
|
96
|
+
}
|
|
97
|
+
return src;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
|
|
101
|
+
async dockerRemoteBuildV2(contextDir, dockerfile) {
|
|
102
|
+
const env = this.env;
|
|
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();
|
|
106
|
+
try {
|
|
107
|
+
console.log(chalk.green("开始在设备中构建镜像"));
|
|
108
|
+
let stream = await docker.buildImage(
|
|
109
|
+
{
|
|
110
|
+
context: contextDir,
|
|
111
|
+
src: src,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
buildargs: env,
|
|
115
|
+
dockerfile: src[0],
|
|
116
|
+
t: env["APP_ID"],
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
await new Promise((resolve, reject) => {
|
|
120
|
+
let pulls = [];
|
|
121
|
+
let logUpdate;
|
|
122
|
+
|
|
123
|
+
function refresh() {
|
|
124
|
+
logUpdate(pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
docker.modem.followProgress(
|
|
143
|
+
stream,
|
|
144
|
+
(err, res) => {
|
|
145
|
+
err ? reject(err) : resolve(res);
|
|
146
|
+
},
|
|
147
|
+
(res) => {
|
|
148
|
+
if (res.status) {
|
|
149
|
+
if (res.id) {
|
|
150
|
+
if (["Downloading", "Extracting"].includes(res.status)) {
|
|
151
|
+
updatePull({
|
|
152
|
+
id: res.id,
|
|
153
|
+
content: `${res.status} ${res.progress}`,
|
|
154
|
+
});
|
|
155
|
+
} else {
|
|
156
|
+
updatePull({ id: res.id, content: res.status });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
refresh();
|
|
160
|
+
} else {
|
|
161
|
+
process.stdout.write(res.status);
|
|
162
|
+
process.stdout.write("\n");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (res.stream) {
|
|
167
|
+
if (res.stream.startsWith("Step")) {
|
|
168
|
+
pulls = [];
|
|
169
|
+
logUpdate = createLogUpdate(process.stdout);
|
|
170
|
+
}
|
|
171
|
+
process.stdout.write(res.stream);
|
|
172
|
+
}
|
|
173
|
+
if (res.error) {
|
|
174
|
+
reject(res.error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|