@lakutata/cli 1.0.13 → 1.0.17
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/build/Deploy.d.ts +1 -0
- package/build/Deploy.js +83 -0
- package/build/Deploy.js.map +1 -0
- package/build/lib/manipulators/CreateDockerFileManipulator.js +53 -11
- package/build/lib/manipulators/CreateDockerFileManipulator.js.map +1 -1
- package/package.json +3 -2
- package/src/Deploy.ts +79 -0
- package/src/lib/manipulators/CreateDockerFileManipulator.ts +52 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/build/Deploy.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
(async () => {
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
const shelljs = require('shelljs');
|
|
7
|
+
const moment = require('moment-timezone');
|
|
8
|
+
const packageJson = require(path.resolve(process.cwd(), './package.json'));
|
|
9
|
+
const argvLen = process.argv.length;
|
|
10
|
+
const REGISTRY_ADDR = process.argv[argvLen - 1];
|
|
11
|
+
const DOCKERFILE_PATH = process.argv[argvLen - 2];
|
|
12
|
+
const REPOSITORY_ADDR = new URL(`repo://${REGISTRY_ADDR}`).hostname;
|
|
13
|
+
if (!REGISTRY_ADDR)
|
|
14
|
+
throw new Error('未配置仓库地址');
|
|
15
|
+
const TIMESTAMP = Date.now();
|
|
16
|
+
const IMAGE_BASE_NAME = `lakutata_tmp_image_${TIMESTAMP}`;
|
|
17
|
+
const IMAGE_VERSION = `${packageJson.version}-build.${moment().format('YYYYMMDDHHmmss')}`;
|
|
18
|
+
const execPromise = (command, options) => {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
shelljs.exec(command, options, (code, stdout, stderr) => {
|
|
21
|
+
code && stderr ? reject(stderr) : resolve(stdout);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const account = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'username',
|
|
29
|
+
message: 'Docker仓库账号'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'password',
|
|
33
|
+
name: 'password',
|
|
34
|
+
message: 'Docker仓库密码',
|
|
35
|
+
mask: '*'
|
|
36
|
+
}
|
|
37
|
+
]);
|
|
38
|
+
const settings = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: 'list',
|
|
41
|
+
name: 'platform',
|
|
42
|
+
message: '目标构建平台架构',
|
|
43
|
+
choices: [
|
|
44
|
+
'linux/amd64',
|
|
45
|
+
'linux/arm64',
|
|
46
|
+
'linux/riscv64',
|
|
47
|
+
'linux/ppc64le',
|
|
48
|
+
'linux/s390x',
|
|
49
|
+
'linux/386',
|
|
50
|
+
'linux/arm/v7',
|
|
51
|
+
'linux/arm/v6'
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'confirm',
|
|
56
|
+
name: 'tagLatest',
|
|
57
|
+
message: '是否为镜像打上latest标签',
|
|
58
|
+
default: false
|
|
59
|
+
}
|
|
60
|
+
]);
|
|
61
|
+
const tagLatest = settings.tagLatest;
|
|
62
|
+
try {
|
|
63
|
+
await execPromise(`docker login --username=${account.username} ${REPOSITORY_ADDR} -p${account.password}`);
|
|
64
|
+
shelljs.exec(`docker buildx build -f ${DOCKERFILE_PATH} -t ${IMAGE_BASE_NAME}:${IMAGE_VERSION} --platform=${settings.platform} .`);
|
|
65
|
+
shelljs.exec(`docker tag ${IMAGE_BASE_NAME}:${IMAGE_VERSION} ${REGISTRY_ADDR}:${IMAGE_VERSION}`);
|
|
66
|
+
if (tagLatest) {
|
|
67
|
+
shelljs.exec(`docker tag ${IMAGE_BASE_NAME}:${IMAGE_VERSION} ${REGISTRY_ADDR}:latest`);
|
|
68
|
+
}
|
|
69
|
+
shelljs.exec(`docker push ${REGISTRY_ADDR}:${IMAGE_VERSION}`);
|
|
70
|
+
if (tagLatest) {
|
|
71
|
+
shelljs.exec(`docker push ${REGISTRY_ADDR}:latest`);
|
|
72
|
+
}
|
|
73
|
+
shelljs.exec(`docker rmi -f ${REGISTRY_ADDR}:${IMAGE_VERSION}`);
|
|
74
|
+
if (tagLatest) {
|
|
75
|
+
shelljs.exec(`docker rmi -f ${REGISTRY_ADDR}:latest`);
|
|
76
|
+
}
|
|
77
|
+
shelljs.exec(`docker rmi -f ${IMAGE_BASE_NAME}:${IMAGE_VERSION}`);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.error(e.message);
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
//# sourceMappingURL=Deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Deploy.js","sourceRoot":"","sources":["../src/Deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,9 +7,10 @@ exports.CreateDockerFileManipulator = void 0;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
|
10
|
-
|
|
10
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
|
+
function generateDockerfileContent(timezone, envName, outDir, runtime, entryPoint) {
|
|
11
12
|
const dockerFileContents = [
|
|
12
|
-
'FROM node:
|
|
13
|
+
'FROM node:lts-buster-slim',
|
|
13
14
|
'#Set docker timezone',
|
|
14
15
|
`ENV TZ=${timezone}`,
|
|
15
16
|
`ENV NODE_ENV=${envName}`,
|
|
@@ -27,42 +28,82 @@ function generateDockerfileContent(timezone, envName, outDir, entryPoint) {
|
|
|
27
28
|
'RUN npm install --build-from-source',
|
|
28
29
|
'RUN tsc',
|
|
29
30
|
'#Expose ports',
|
|
30
|
-
'#
|
|
31
|
-
`ENTRYPOINT
|
|
31
|
+
'#TODO export ports or remove this line',
|
|
32
|
+
`ENTRYPOINT ${runtime} ${path_1.default.resolve('/app/', entryPoint)}`
|
|
32
33
|
];
|
|
33
34
|
let content = '';
|
|
34
35
|
dockerFileContents.forEach(line => {
|
|
35
|
-
content
|
|
36
|
+
if (!content) {
|
|
37
|
+
content = line;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
content = `${content}\n${line}`;
|
|
41
|
+
}
|
|
36
42
|
});
|
|
37
43
|
return content;
|
|
38
44
|
}
|
|
39
45
|
async function CreateDockerFileManipulator(projectPath, tsConfigFilePath, directories) {
|
|
40
46
|
var _a, _b;
|
|
41
47
|
try {
|
|
48
|
+
const repositories = await inquirer_1.default.prompt([
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'production',
|
|
52
|
+
message: '请输入正式环境仓库地址'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'development',
|
|
57
|
+
message: '请输入测试环境仓库地址'
|
|
58
|
+
}
|
|
59
|
+
]);
|
|
60
|
+
const productionRepository = repositories.production ? repositories.production : '';
|
|
61
|
+
const developmentRepository = repositories.development ? repositories.development : '';
|
|
42
62
|
const timezone = moment_timezone_1.default.tz.guess();
|
|
43
63
|
const tsConfigJson = JSON.parse(fs_1.default.readFileSync(tsConfigFilePath).toString());
|
|
44
64
|
const outDir = (_a = tsConfigJson === null || tsConfigJson === void 0 ? void 0 : tsConfigJson.compilerOptions) === null || _a === void 0 ? void 0 : _a.outDir;
|
|
45
65
|
if (!outDir)
|
|
46
66
|
return console.error('项目输出目录设置有误');
|
|
47
|
-
const
|
|
67
|
+
const packageJsonFilePath = path_1.default.resolve(projectPath, './package.json');
|
|
68
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonFilePath).toString());
|
|
48
69
|
const mainEntry = (packageJson === null || packageJson === void 0 ? void 0 : packageJson.main) ? packageJson.main : '';
|
|
49
70
|
if (!mainEntry)
|
|
50
71
|
return console.error('应用程序主入口设置有误');
|
|
51
72
|
const dockerfiles = {};
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
if (productionRepository) {
|
|
74
|
+
dockerfiles.Dockerfile = generateDockerfileContent(timezone, 'production', outDir, 'node', mainEntry);
|
|
75
|
+
packageJson.scripts.deploy = `lakutata-deploy ./Dockerfile ${productionRepository}`;
|
|
76
|
+
}
|
|
77
|
+
if (((_b = packageJson === null || packageJson === void 0 ? void 0 : packageJson.scripts) === null || _b === void 0 ? void 0 : _b.test) && developmentRepository) {
|
|
54
78
|
const testScriptString = packageJson.scripts.test;
|
|
55
79
|
const testScriptParams = testScriptString.split(' ').reverse();
|
|
56
80
|
let testEntryPoint = '';
|
|
81
|
+
let runtime = '';
|
|
57
82
|
for (const testScriptParam of testScriptParams) {
|
|
58
83
|
const regExp = /src\//;
|
|
59
84
|
if (['.ts', '.js'].includes(path_1.default.extname(testScriptParam)) && regExp.test(testScriptParam)) {
|
|
60
|
-
|
|
85
|
+
switch (path_1.default.extname(testScriptParam)) {
|
|
86
|
+
case '.ts':
|
|
87
|
+
{
|
|
88
|
+
runtime = 'ts-node';
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case '.js':
|
|
92
|
+
{
|
|
93
|
+
runtime = 'node';
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
default: {
|
|
97
|
+
console.error('不正确的测试主入口扩展名');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
testEntryPoint = testScriptParam;
|
|
61
101
|
break;
|
|
62
102
|
}
|
|
63
103
|
}
|
|
64
|
-
if (testEntryPoint) {
|
|
65
|
-
dockerfiles.DockerfileDev = generateDockerfileContent(timezone, 'development', outDir, testEntryPoint);
|
|
104
|
+
if (testEntryPoint && runtime) {
|
|
105
|
+
dockerfiles.DockerfileDev = generateDockerfileContent(timezone, 'development', outDir, runtime, testEntryPoint);
|
|
106
|
+
packageJson.scripts['deploy:dev'] = `lakutata-deploy ./DockerfileDev ${developmentRepository}`;
|
|
66
107
|
}
|
|
67
108
|
}
|
|
68
109
|
for (const dockerfileName of Object.keys(dockerfiles)) {
|
|
@@ -72,6 +113,7 @@ async function CreateDockerFileManipulator(projectPath, tsConfigFilePath, direct
|
|
|
72
113
|
});
|
|
73
114
|
console.info(dockerfileName, '写入成功');
|
|
74
115
|
}
|
|
116
|
+
fs_1.default.writeFileSync(packageJsonFilePath, JSON.stringify(packageJson, null, ' '), { encoding: 'utf-8', flag: 'w' });
|
|
75
117
|
console.info('Dockerfile已创建成功');
|
|
76
118
|
}
|
|
77
119
|
catch (e) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateDockerFileManipulator.js","sourceRoot":"","sources":["../../../src/lib/manipulators/CreateDockerFileManipulator.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAmB;AACnB,gDAAuB;AACvB,sEAAoC;
|
|
1
|
+
{"version":3,"file":"CreateDockerFileManipulator.js","sourceRoot":"","sources":["../../../src/lib/manipulators/CreateDockerFileManipulator.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAmB;AACnB,gDAAuB;AACvB,sEAAoC;AACpC,wDAA+B;AAU/B,SAAS,yBAAyB,CAAC,QAAgB,EAAE,OAAe,EAAE,MAAc,EAAE,OAAe,EAAE,UAAkB;IACrH,MAAM,kBAAkB,GAAa;QACjC,2BAA2B;QAC3B,sBAAsB;QACtB,UAAU,QAAQ,EAAE;QACpB,gBAAgB,OAAO,EAAE;QACzB,cAAc;QACd,oDAAoD;QACpD,8BAA8B;QAC9B,oCAAoC;QACpC,2CAA2C;QAC3C,8BAA8B;QAC9B,mBAAmB;QACnB,cAAc;QACd,cAAc;QACd,8BAA8B;QAC9B,cAAc,cAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE;QAC7C,qCAAqC;QACrC,SAAS;QACT,eAAe;QACf,wCAAwC;QACxC,cAAc,OAAO,IAAI,cAAI,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE;KAC/D,CAAA;IACD,IAAI,OAAO,GAAW,EAAE,CAAA;IACxB,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAC9B,IAAI,CAAC,OAAO,EAAE;YACV,OAAO,GAAG,IAAI,CAAA;SACjB;aAAM;YACH,OAAO,GAAG,GAAG,OAAO,KAAK,IAAI,EAAE,CAAA;SAClC;IACL,CAAC,CAAC,CAAA;IACF,OAAO,OAAO,CAAA;AAClB,CAAC;AAEM,KAAK,UAAU,2BAA2B,CAAC,WAAmB,EAAE,gBAAwB,EAAE,WAAuC;;IACpI,IAAI;QACA,MAAM,YAAY,GAAG,MAAM,kBAAQ,CAAC,MAAM,CAAC;YACvC;gBACI,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,aAAa;aACzB;YACD;gBACI,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,aAAa;aACzB;SACJ,CAAC,CAAA;QACF,MAAM,oBAAoB,GAAW,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAA;QAC3F,MAAM,qBAAqB,GAAW,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAA;QAC9F,MAAM,QAAQ,GAAG,yBAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;QAClC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC7E,MAAM,MAAM,GAAW,MAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,eAAe,0CAAE,MAAM,CAAA;QAC5D,IAAI,CAAC,MAAM;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;QAC/C,MAAM,mBAAmB,GAAW,cAAI,CAAC,OAAO,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAA;QAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC/E,MAAM,SAAS,GAAW,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,IAAI,EAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;QACnE,IAAI,CAAC,SAAS;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QACnD,MAAM,WAAW,GAA8B,EAAE,CAAA;QACjD,IAAI,oBAAoB,EAAE;YACtB,WAAW,CAAC,UAAU,GAAG,yBAAyB,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;YACrG,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,gCAAgC,oBAAoB,EAAE,CAAA;SACtF;QACD,IAAI,CAAA,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,OAAO,0CAAE,IAAI,KAAI,qBAAqB,EAAE;YAErD,MAAM,gBAAgB,GAAW,WAAW,CAAC,OAAO,CAAC,IAAI,CAAA;YACzD,MAAM,gBAAgB,GAAa,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAA;YACxE,IAAI,cAAc,GAAW,EAAE,CAAA;YAC/B,IAAI,OAAO,GAAW,EAAE,CAAA;YACxB,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE;gBAC5C,MAAM,MAAM,GAAG,OAAO,CAAA;gBACtB,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,cAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE;oBACxF,QAAQ,cAAI,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE;wBACnC,KAAK,KAAK;4BAAE;gCACR,OAAO,GAAG,SAAS,CAAA;6BACtB;4BACG,MAAK;wBACT,KAAK,KAAK;4BAAE;gCACR,OAAO,GAAG,MAAM,CAAA;6BACnB;4BACG,MAAK;wBACT,OAAO,CAAC,CAAC;4BACL,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;yBAChC;qBACJ;oBACD,cAAc,GAAG,eAAe,CAAA;oBAChC,MAAK;iBACR;aACJ;YACD,IAAI,cAAc,IAAI,OAAO,EAAE;gBAC3B,WAAW,CAAC,aAAa,GAAG,yBAAyB,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC,CAAA;gBAC/G,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,mCAAmC,qBAAqB,EAAE,CAAA;aACjG;SACJ;QACD,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;YAEnD,YAAE,CAAC,aAAa,CAAC,cAAI,CAAC,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,EAAE;gBACrF,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,GAAG;aACZ,CAAC,CAAA;YACF,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;SACvC;QACD,YAAE,CAAC,aAAa,CAAC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,EAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAC,CAAC,CAAA;QAC7G,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;KAClC;IAAC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,KAAK,CAAC,mBAAoB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAA;KAC3D;AACL,CAAC;AAzED,kEAyEC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakutata/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Lakutata CLI Tool",
|
|
5
5
|
"main": "./build/CLI.js",
|
|
6
6
|
"types": "./build/CLI.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"lakutata": "./build/CLI.js"
|
|
8
|
+
"lakutata": "./build/CLI.js",
|
|
9
|
+
"lakutata-deploy": "./build/Deploy.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"clean": "shx rm -rf ./build",
|
package/src/Deploy.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
(async () => {
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const inquirer = require('inquirer')
|
|
5
|
+
const shelljs = require('shelljs')
|
|
6
|
+
const moment = require('moment-timezone')
|
|
7
|
+
const packageJson = require(path.resolve(process.cwd(), './package.json'))
|
|
8
|
+
const argvLen = process.argv.length
|
|
9
|
+
const REGISTRY_ADDR = process.argv[argvLen - 1]
|
|
10
|
+
const DOCKERFILE_PATH = process.argv[argvLen - 2]
|
|
11
|
+
const REPOSITORY_ADDR = new URL(`repo://${REGISTRY_ADDR}`).hostname
|
|
12
|
+
if (!REGISTRY_ADDR) throw new Error('未配置仓库地址')
|
|
13
|
+
const TIMESTAMP = Date.now()
|
|
14
|
+
const IMAGE_BASE_NAME = `lakutata_tmp_image_${TIMESTAMP}`
|
|
15
|
+
const IMAGE_VERSION = `${packageJson.version}-build.${moment().format('YYYYMMDDHHmmss')}`
|
|
16
|
+
const execPromise = (command, options?) => {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
shelljs.exec(command, options, (code, stdout, stderr) => {
|
|
19
|
+
code && stderr ? reject(stderr) : resolve(stdout)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
const account = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'username',
|
|
27
|
+
message: 'Docker仓库账号'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'password',
|
|
31
|
+
name: 'password',
|
|
32
|
+
message: 'Docker仓库密码',
|
|
33
|
+
mask: '*'
|
|
34
|
+
}
|
|
35
|
+
])
|
|
36
|
+
const settings = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'platform',
|
|
40
|
+
message: '目标构建平台架构',
|
|
41
|
+
choices: [
|
|
42
|
+
'linux/amd64',
|
|
43
|
+
'linux/arm64',
|
|
44
|
+
'linux/riscv64',
|
|
45
|
+
'linux/ppc64le',
|
|
46
|
+
'linux/s390x',
|
|
47
|
+
'linux/386',
|
|
48
|
+
'linux/arm/v7',
|
|
49
|
+
'linux/arm/v6'
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
name: 'tagLatest',
|
|
55
|
+
message: '是否为镜像打上latest标签',
|
|
56
|
+
default: false
|
|
57
|
+
}
|
|
58
|
+
])
|
|
59
|
+
const tagLatest: boolean = settings.tagLatest
|
|
60
|
+
try {
|
|
61
|
+
await execPromise(`docker login --username=${account.username} ${REPOSITORY_ADDR} -p${account.password}`)
|
|
62
|
+
shelljs.exec(`docker buildx build -f ${DOCKERFILE_PATH} -t ${IMAGE_BASE_NAME}:${IMAGE_VERSION} --platform=${settings.platform} .`)
|
|
63
|
+
shelljs.exec(`docker tag ${IMAGE_BASE_NAME}:${IMAGE_VERSION} ${REGISTRY_ADDR}:${IMAGE_VERSION}`)
|
|
64
|
+
if (tagLatest) {
|
|
65
|
+
shelljs.exec(`docker tag ${IMAGE_BASE_NAME}:${IMAGE_VERSION} ${REGISTRY_ADDR}:latest`)
|
|
66
|
+
}
|
|
67
|
+
shelljs.exec(`docker push ${REGISTRY_ADDR}:${IMAGE_VERSION}`)
|
|
68
|
+
if (tagLatest) {
|
|
69
|
+
shelljs.exec(`docker push ${REGISTRY_ADDR}:latest`)
|
|
70
|
+
}
|
|
71
|
+
shelljs.exec(`docker rmi -f ${REGISTRY_ADDR}:${IMAGE_VERSION}`)
|
|
72
|
+
if (tagLatest) {
|
|
73
|
+
shelljs.exec(`docker rmi -f ${REGISTRY_ADDR}:latest`)
|
|
74
|
+
}
|
|
75
|
+
shelljs.exec(`docker rmi -f ${IMAGE_BASE_NAME}:${IMAGE_VERSION}`)
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error((e as Error).message)
|
|
78
|
+
}
|
|
79
|
+
})()
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import moment from 'moment-timezone'
|
|
4
|
+
import inquirer from 'inquirer'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 生成Dockerfile文件内容
|
|
7
8
|
* @param timezone
|
|
8
9
|
* @param envName
|
|
9
10
|
* @param outDir
|
|
11
|
+
* @param runtime
|
|
10
12
|
* @param entryPoint
|
|
11
13
|
*/
|
|
12
|
-
function generateDockerfileContent(timezone: string, envName: string, outDir: string, entryPoint: string): string {
|
|
14
|
+
function generateDockerfileContent(timezone: string, envName: string, outDir: string, runtime: string, entryPoint: string): string {
|
|
13
15
|
const dockerFileContents: string[] = [
|
|
14
|
-
'FROM node:
|
|
16
|
+
'FROM node:lts-buster-slim',
|
|
15
17
|
'#Set docker timezone',
|
|
16
18
|
`ENV TZ=${timezone}`,
|
|
17
19
|
`ENV NODE_ENV=${envName}`,
|
|
@@ -29,41 +31,78 @@ function generateDockerfileContent(timezone: string, envName: string, outDir: st
|
|
|
29
31
|
'RUN npm install --build-from-source',
|
|
30
32
|
'RUN tsc',
|
|
31
33
|
'#Expose ports',
|
|
32
|
-
'#
|
|
33
|
-
`ENTRYPOINT
|
|
34
|
+
'#TODO export ports or remove this line',
|
|
35
|
+
`ENTRYPOINT ${runtime} ${path.resolve('/app/', entryPoint)}`
|
|
34
36
|
]
|
|
35
37
|
let content: string = ''
|
|
36
38
|
dockerFileContents.forEach(line => {
|
|
37
|
-
content
|
|
39
|
+
if (!content) {
|
|
40
|
+
content = line
|
|
41
|
+
} else {
|
|
42
|
+
content = `${content}\n${line}`
|
|
43
|
+
}
|
|
38
44
|
})
|
|
39
45
|
return content
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
export async function CreateDockerFileManipulator(projectPath: string, tsConfigFilePath: string, directories: { [name: string]: string }) {
|
|
43
49
|
try {
|
|
50
|
+
const repositories = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'production',
|
|
54
|
+
message: '请输入正式环境仓库地址'
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'development',
|
|
59
|
+
message: '请输入测试环境仓库地址'
|
|
60
|
+
}
|
|
61
|
+
])
|
|
62
|
+
const productionRepository: string = repositories.production ? repositories.production : ''
|
|
63
|
+
const developmentRepository: string = repositories.development ? repositories.development : ''
|
|
44
64
|
const timezone = moment.tz.guess()
|
|
45
65
|
const tsConfigJson = JSON.parse(fs.readFileSync(tsConfigFilePath).toString())
|
|
46
66
|
const outDir: string = tsConfigJson?.compilerOptions?.outDir
|
|
47
67
|
if (!outDir) return console.error('项目输出目录设置有误')
|
|
48
|
-
const
|
|
68
|
+
const packageJsonFilePath: string = path.resolve(projectPath, './package.json')
|
|
69
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonFilePath).toString())
|
|
49
70
|
const mainEntry: string = packageJson?.main ? packageJson.main : ''
|
|
50
71
|
if (!mainEntry) return console.error('应用程序主入口设置有误')
|
|
51
72
|
const dockerfiles: { [key: string]: string } = {}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
if (productionRepository) {
|
|
74
|
+
dockerfiles.Dockerfile = generateDockerfileContent(timezone, 'production', outDir, 'node', mainEntry)
|
|
75
|
+
packageJson.scripts.deploy = `lakutata-deploy ./Dockerfile ${productionRepository}`
|
|
76
|
+
}
|
|
77
|
+
if (packageJson?.scripts?.test && developmentRepository) {
|
|
78
|
+
//有测试主入口并且仓库地址被正确配置
|
|
55
79
|
const testScriptString: string = packageJson.scripts.test
|
|
56
80
|
const testScriptParams: string[] = testScriptString.split(' ').reverse()
|
|
57
81
|
let testEntryPoint: string = ''
|
|
82
|
+
let runtime: string = ''
|
|
58
83
|
for (const testScriptParam of testScriptParams) {
|
|
59
84
|
const regExp = /src\//
|
|
60
85
|
if (['.ts', '.js'].includes(path.extname(testScriptParam)) && regExp.test(testScriptParam)) {
|
|
61
|
-
|
|
86
|
+
switch (path.extname(testScriptParam)) {
|
|
87
|
+
case '.ts': {
|
|
88
|
+
runtime = 'ts-node'
|
|
89
|
+
}
|
|
90
|
+
break
|
|
91
|
+
case '.js': {
|
|
92
|
+
runtime = 'node'
|
|
93
|
+
}
|
|
94
|
+
break
|
|
95
|
+
default: {
|
|
96
|
+
console.error('不正确的测试主入口扩展名')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
testEntryPoint = testScriptParam
|
|
62
100
|
break
|
|
63
101
|
}
|
|
64
102
|
}
|
|
65
|
-
if (testEntryPoint) {
|
|
66
|
-
dockerfiles.DockerfileDev = generateDockerfileContent(timezone, 'development', outDir, testEntryPoint)
|
|
103
|
+
if (testEntryPoint && runtime) {
|
|
104
|
+
dockerfiles.DockerfileDev = generateDockerfileContent(timezone, 'development', outDir, runtime, testEntryPoint)
|
|
105
|
+
packageJson.scripts['deploy:dev'] = `lakutata-deploy ./DockerfileDev ${developmentRepository}`
|
|
67
106
|
}
|
|
68
107
|
}
|
|
69
108
|
for (const dockerfileName of Object.keys(dockerfiles)) {
|
|
@@ -74,6 +113,7 @@ export async function CreateDockerFileManipulator(projectPath: string, tsConfigF
|
|
|
74
113
|
})
|
|
75
114
|
console.info(dockerfileName, '写入成功')
|
|
76
115
|
}
|
|
116
|
+
fs.writeFileSync(packageJsonFilePath, JSON.stringify(packageJson, null, ' '), {encoding: 'utf-8', flag: 'w'})
|
|
77
117
|
console.info('Dockerfile已创建成功')
|
|
78
118
|
} catch (e) {
|
|
79
119
|
console.error(`Dockerfile创建错误: ${(e as Error).message}`)
|