322 lines
9.8 KiB
JavaScript
322 lines
9.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// src/cli.ts
|
|
import { execSync } from "child_process";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { parseArgs } from "util";
|
|
var pwd = process.cwd();
|
|
try {
|
|
execSync("docker --version", { stdio: "ignore" });
|
|
} catch (error) {
|
|
console.error("\u274C Docker \u547D\u4EE4\u4E0D\u53EF\u7528\uFF0C\u8BF7\u786E\u4FDD\u5DF2\u5B89\u88C5\u5E76\u6B63\u786E\u914D\u7F6E Docker");
|
|
process.exit(1);
|
|
}
|
|
function parseCliArgs() {
|
|
const { values, positionals } = parseArgs({
|
|
options: {
|
|
env: {
|
|
type: "string",
|
|
default: "dev"
|
|
},
|
|
help: {
|
|
type: "boolean",
|
|
short: "h",
|
|
default: false
|
|
},
|
|
extra: {
|
|
type: "string",
|
|
multiple: true,
|
|
default: []
|
|
},
|
|
registry: {
|
|
type: "string",
|
|
default: "https://registry.npmmirror.com"
|
|
},
|
|
port: {
|
|
type: "string",
|
|
default: "3001"
|
|
},
|
|
"node-version": {
|
|
type: "string",
|
|
default: "20"
|
|
}
|
|
},
|
|
allowPositionals: true
|
|
});
|
|
if (values.help) {
|
|
console.log(`
|
|
\u4F7F\u7528\u65B9\u6CD5: docker-builder <\u9879\u76EE\u76EE\u5F55> [\u9009\u9879]
|
|
|
|
\u53C2\u6570:
|
|
<\u9879\u76EE\u76EE\u5F55> \u8981\u6253\u5305\u7684\u9879\u76EE\u76EE\u5F55\uFF08\u5FC5\u9700\uFF09
|
|
|
|
\u9009\u9879:
|
|
--env=<environment> \u73AF\u5883\u7C7B\u578B (dev|prod|staging)\uFF0C\u9ED8\u8BA4: dev
|
|
--extra=<model> \u989D\u5916\u7684\u6A21\u578B\u76EE\u5F55\uFF08\u53EF\u591A\u6B21\u4F7F\u7528\uFF09
|
|
--registry=<url> npm \u955C\u50CF\u7AD9\u5730\u5740\uFF0C\u9ED8\u8BA4: https://registry.npmmirror.com
|
|
--port=<port> \u66B4\u9732\u7684\u7AEF\u53E3\u53F7\uFF0C\u9ED8\u8BA4: 3001
|
|
--node-version=<ver> Node.js \u7248\u672C\uFF0C\u9ED8\u8BA4: 20
|
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
|
|
|
|
\u793A\u4F8B:
|
|
docker-builder project
|
|
docker-builder project --extra=model1 --extra=model2
|
|
docker-builder project --env=prod --port=8080 --node-version=18
|
|
`);
|
|
return null;
|
|
}
|
|
if (positionals.length === 0) {
|
|
console.error("\u274C \u8BF7\u6307\u5B9A\u8981\u6253\u5305\u7684\u9879\u76EE\u76EE\u5F55");
|
|
console.error(" \u4F7F\u7528 --help \u67E5\u770B\u8BE6\u7EC6\u7528\u6CD5");
|
|
process.exit(1);
|
|
}
|
|
const projectDir = path.resolve(positionals[0]);
|
|
const env = values.env;
|
|
if (!["dev", "prod", "staging"].includes(env)) {
|
|
console.error(`\u274C \u65E0\u6548\u7684\u73AF\u5883\u7C7B\u578B: ${env}`);
|
|
console.error(" \u652F\u6301\u7684\u73AF\u5883: dev, prod, staging");
|
|
process.exit(1);
|
|
}
|
|
return {
|
|
projectDir,
|
|
options: {
|
|
env,
|
|
extra: values.extra,
|
|
registry: values.registry,
|
|
port: values.port,
|
|
nodeVersion: values["node-version"]
|
|
}
|
|
};
|
|
}
|
|
function generateDockerfile(projectName, options) {
|
|
const { extra, env, registry, port, nodeVersion } = options;
|
|
const allProjects = [projectName];
|
|
allProjects.push(...extra);
|
|
console.log("\n\u{1F50D} \u68C0\u67E5\u9879\u76EE\u4F9D\u8D56\u5173\u7CFB...");
|
|
for (const project of allProjects) {
|
|
console.log(`\u68C0\u67E5\u9879\u76EE: ${project}`);
|
|
const packageJsonPath = path.join(pwd, project, "package.json");
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
const dependencies = packageJson.dependencies || {};
|
|
for (const dep in dependencies) {
|
|
if (dependencies[dep].startsWith("file:../")) {
|
|
const depProject = dependencies[dep].replace("file:../", "");
|
|
if (!allProjects.includes(depProject)) {
|
|
console.error(`\u274C \u53D1\u73B0\u672A\u5305\u542B\u7684\u4F9D\u8D56\u9879\u76EE: ${depProject}\uFF0C\u8BF7\u5728\u547D\u4EE4\u884C\u4E2D\u901A\u8FC7 --extra \u53C2\u6570\u6DFB\u52A0`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
console.error(`\u274C \u627E\u4E0D\u5230\u9879\u76EE\u7684 package.json: ${packageJsonPath}`);
|
|
}
|
|
}
|
|
let dockerfile = `# ===========================
|
|
# Stage 1: Build
|
|
# ===========================
|
|
FROM node:${nodeVersion}-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
|
|
# \u8BBE\u7F6E\u955C\u50CF\u7AD9
|
|
RUN npm config set registry ${registry}
|
|
|
|
`;
|
|
for (const project of allProjects) {
|
|
dockerfile += `# \u590D\u5236 ${project}
|
|
`;
|
|
dockerfile += `COPY ./${project} ./${project}
|
|
`;
|
|
}
|
|
dockerfile += `
|
|
# ===========================
|
|
# \u6784\u5EFA\u6240\u6709\u9879\u76EE
|
|
# ===========================
|
|
`;
|
|
for (const project of allProjects) {
|
|
dockerfile += `
|
|
# \u6784\u5EFA ${project}
|
|
WORKDIR /app/${project}
|
|
RUN npm install
|
|
`;
|
|
if (project === projectName) {
|
|
dockerfile += `RUN npm run make:domain && npm run build
|
|
`;
|
|
} else {
|
|
dockerfile += `RUN npm run build || true
|
|
`;
|
|
}
|
|
}
|
|
dockerfile += `
|
|
# ===========================
|
|
# \u6E05\u7406\u6784\u5EFA\u4EA7\u7269
|
|
# ===========================
|
|
`;
|
|
for (const project of allProjects) {
|
|
dockerfile += `
|
|
# \u6E05\u7406 ${project}
|
|
WORKDIR /app/${project}
|
|
RUN rm -rf src \\
|
|
&& find node_modules -type d -name "test" -o -name "__tests__" -exec rm -rf {} + \\
|
|
&& find node_modules -type f \\( -name "*.ts" -o -name "*.d.ts" \\) -delete
|
|
`;
|
|
}
|
|
dockerfile += `
|
|
# ===========================
|
|
# Stage 2: Runtime
|
|
# ===========================
|
|
FROM node:${nodeVersion}-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
# \u4ECE\u6784\u5EFA\u9636\u6BB5\u590D\u5236\u6240\u6709\u5185\u5BB9
|
|
COPY --from=builder /app ./
|
|
|
|
WORKDIR /app/${projectName}
|
|
|
|
# \u5B89\u88C5 PM2
|
|
RUN npm install -g @socket.io/pm2
|
|
|
|
# \u518D\u6B21\u6E05\u7406\uFF08\u786E\u4FDD\u8FD0\u884C\u65F6\u955C\u50CF\u6700\u5C0F\u5316\uFF09
|
|
`;
|
|
for (const project of allProjects) {
|
|
dockerfile += `
|
|
# \u6E05\u7406 ${project} \u8FD0\u884C\u65F6\u4E0D\u9700\u8981\u7684\u6587\u4EF6
|
|
WORKDIR /app/${project}
|
|
RUN rm -rf src \\
|
|
&& find node_modules -type d -name "test" -o -name "__tests__" -exec rm -rf {} + \\
|
|
&& find node_modules -type f \\( -name "*.ts" -o -name "*.d.ts" -o -name "*.map" \\) -delete \\
|
|
&& find . -type f -name "*.test.js" -delete
|
|
`;
|
|
}
|
|
dockerfile += `
|
|
# \u5207\u6362\u56DE\u4E3B\u9879\u76EE\u76EE\u5F55
|
|
WORKDIR /app/${projectName}
|
|
|
|
EXPOSE ${port}
|
|
|
|
CMD ["pm2-runtime", "start", "pm2.${env}.json"]
|
|
`;
|
|
return dockerfile;
|
|
}
|
|
function generateDockerignore() {
|
|
return `# Node modules
|
|
node_modules
|
|
npm-debug.log
|
|
|
|
# \u6D4B\u8BD5\u548C\u6587\u6863
|
|
test
|
|
tests
|
|
__tests__
|
|
*.test.js
|
|
*.spec.js
|
|
docs
|
|
coverage
|
|
|
|
# \u5F00\u53D1\u6587\u4EF6
|
|
.git
|
|
.gitignore
|
|
.env
|
|
.env.*
|
|
*.md
|
|
.vscode
|
|
.idea
|
|
|
|
# \u6784\u5EFA\u4EA7\u7269\uFF08\u5728\u5BB9\u5668\u5185\u6784\u5EFA\uFF09
|
|
dist
|
|
build
|
|
|
|
# \u4E34\u65F6\u6587\u4EF6
|
|
*.log
|
|
*.tmp
|
|
.DS_Store
|
|
`;
|
|
}
|
|
function buildDockerImage(imageBase, name, pwd2) {
|
|
console.log(`
|
|
\u{1F527} Building image for ${name}...`);
|
|
execSync(`docker build -t ${imageBase}:${name} ${pwd2}`, {
|
|
stdio: "inherit"
|
|
});
|
|
}
|
|
function saveDockerImage(imageBase, name, pwd2) {
|
|
const distDir = path.join(pwd2, "dist");
|
|
if (!fs.existsSync(distDir)) {
|
|
fs.mkdirSync(distDir);
|
|
}
|
|
const outputImagePath = path.join(distDir, `${imageBase}-${name}.tar`);
|
|
console.log(`\u{1F4E6} Saving image to ${outputImagePath}...`);
|
|
execSync(`docker save -o ${outputImagePath} ${imageBase}:${name}`, {
|
|
stdio: "inherit"
|
|
});
|
|
}
|
|
function main() {
|
|
const args = parseCliArgs();
|
|
if (!args) {
|
|
process.exit(0);
|
|
}
|
|
const { projectDir, options } = args;
|
|
const pwd2 = process.cwd();
|
|
console.log(`-> \u914D\u7F6E\u4FE1\u606F:`);
|
|
console.log(` \u9879\u76EE\u76EE\u5F55: ${projectDir}`);
|
|
console.log(` \u73AF\u5883: ${options.env}`);
|
|
console.log(
|
|
` \u989D\u5916\u6A21\u578B: ${options.extra.length > 0 ? options.extra.join(", ") : "\u65E0"}`
|
|
);
|
|
console.log(` npm \u955C\u50CF\u7AD9: ${options.registry}`);
|
|
console.log(` \u7AEF\u53E3: ${options.port}`);
|
|
console.log(` Node.js \u7248\u672C: ${options.nodeVersion}`);
|
|
if (!fs.existsSync(projectDir)) {
|
|
console.error(`\u274C \u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${projectDir}`);
|
|
process.exit(1);
|
|
}
|
|
let skipConfigReplace = false;
|
|
const configDir = path.join(projectDir, "configurations");
|
|
if (!fs.existsSync(configDir)) {
|
|
console.info(
|
|
`\u9879\u76EE\u76EE\u5F55\u4E2D\u6CA1\u6709 configurations \u6587\u4EF6\u5939\uFF1A${configDir}, \u8DF3\u8FC7\u914D\u7F6E\u66FF\u6362\u6B65\u9AA4`
|
|
);
|
|
skipConfigReplace = true;
|
|
}
|
|
const imageBase = path.basename(projectDir);
|
|
const projectName = path.basename(projectDir);
|
|
const dockerfileContent = generateDockerfile(projectName, options);
|
|
const dockerignoreContent = generateDockerignore();
|
|
fs.writeFileSync(path.join(pwd2, "Dockerfile"), dockerfileContent);
|
|
fs.writeFileSync(path.join(pwd2, ".dockerignore"), dockerignoreContent);
|
|
console.log("\n\u{1F4DD} \u5DF2\u751F\u6210 Dockerfile \u548C .dockerignore");
|
|
try {
|
|
if (!skipConfigReplace) {
|
|
const configFiles = fs.readdirSync(configDir).filter((file) => file.endsWith(".json"));
|
|
for (const file of configFiles) {
|
|
const configPath = path.join(configDir, file);
|
|
const name = path.basename(file, ".json") + `-${options.env}`;
|
|
console.log(`
|
|
\u{1F527} \u4F7F\u7528\u914D\u7F6E ${configPath} \u6784\u5EFA\u955C\u50CF ${name}...`);
|
|
fs.copyFileSync(
|
|
configPath,
|
|
path.join(projectDir, "configuration/mysql.json")
|
|
);
|
|
buildDockerImage(imageBase, name, pwd2);
|
|
saveDockerImage(imageBase, name, pwd2);
|
|
}
|
|
} else {
|
|
const name = `default-${options.env}`;
|
|
console.log(`
|
|
\u{1F527} \u6784\u5EFA\u9ED8\u8BA4\u955C\u50CF ${name}...`);
|
|
buildDockerImage(imageBase, name, pwd2);
|
|
saveDockerImage(imageBase, name, pwd2);
|
|
}
|
|
console.log("\n\u2705 \u6240\u6709\u955C\u50CF\u6784\u5EFA\u6210\u529F\uFF01");
|
|
} finally {
|
|
fs.rmSync(path.join(projectDir, "configuration/mysql.json"), {
|
|
force: true
|
|
});
|
|
fs.rmSync(path.join(pwd2, "Dockerfile"), { force: true });
|
|
fs.rmSync(path.join(pwd2, ".dockerignore"), { force: true });
|
|
}
|
|
}
|
|
main();
|