202 lines
5.9 KiB
JavaScript
202 lines
5.9 KiB
JavaScript
import { search } from "@inquirer/prompts";
|
|
import { execa } from "execa";
|
|
import Fuse from "fuse.js";
|
|
import { glob } from "glob";
|
|
import { task } from "hereby";
|
|
import path from "node:path";
|
|
import fs from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const { values } = parseArgs({
|
|
args: process.argv.slice(3),
|
|
options: {
|
|
server: { type: "string", short: "s", multiple: true }, // 服务名称
|
|
type: { type: "string", short: "t" }, // 生成类型:api, rpc, docker
|
|
imageName: { type: "string", short: "i" }, // 镜像名字
|
|
}
|
|
})
|
|
|
|
|
|
const Paths = {
|
|
root: __dirname,
|
|
desc: path.join(__dirname, "desc"),
|
|
app: path.join(__dirname, "app"),
|
|
getServiceName: (filePath) => path.basename(filePath, path.extname(filePath)),
|
|
getOutputDir: (serviceName) => path.join(__dirname, "app", serviceName),
|
|
pathlistToChoices: (filePaths) => filePaths.map(filePath => ({
|
|
title: Paths.getServiceName(filePath),
|
|
value: filePath,
|
|
})),
|
|
getDescFiles: async (pattern) => {
|
|
return await glob(pattern, { cwd: Paths.desc, absolute: true });
|
|
},
|
|
getAllApi: async () => {
|
|
const apiPattern = "api/*.api";
|
|
return Paths.pathlistToChoices(await Paths.getDescFiles(apiPattern));
|
|
},
|
|
getAllProto: async () => {
|
|
const protoPattern = "rpc/*.proto";
|
|
return Paths.pathlistToChoices(await Paths.getDescFiles(protoPattern));
|
|
},
|
|
getAllservice: async () => {
|
|
let all = [];
|
|
const services = await fs.readdir(Paths.app);
|
|
for (const service of services) {
|
|
const servicePath = path.join(Paths.app, service);
|
|
const svcTypes = await fs.readdir(servicePath);
|
|
svcTypes.map(svcType => all.push({
|
|
title: `${service} - ${svcType}`,
|
|
value: path.join(servicePath, svcType, svcType === "api" ? `${service}.go` : "pb.go"),
|
|
}));
|
|
}
|
|
return all;
|
|
},
|
|
}
|
|
|
|
|
|
const Generators = {
|
|
async api(apiFile) {
|
|
const serviceName = Paths.getServiceName(apiFile);
|
|
const outputDir = path.join(Paths.getOutputDir(serviceName), 'api');
|
|
|
|
await fs.mkdir(outputDir, { recursive: true });
|
|
await run('goctl', [
|
|
'api', 'go',
|
|
'--api', apiFile,
|
|
'--dir', outputDir,
|
|
'--style', 'goZero'
|
|
]);
|
|
},
|
|
async rpc(protoFile) {
|
|
const serviceName = Paths.getServiceName(protoFile);
|
|
const outputDir = path.join(Paths.getOutputDir(serviceName), 'rpc');
|
|
|
|
await fs.mkdir(outputDir, { recursive: true });
|
|
await run('goctl', [
|
|
'rpc', 'protoc', protoFile,
|
|
`--proto_path=${path.join(Paths.desc, "rpc",)}`,
|
|
`--go_out=${outputDir}`,
|
|
`--go-grpc_out=${outputDir}`,
|
|
`--zrpc_out=${outputDir}`,
|
|
'--style=goZero',
|
|
]);
|
|
},
|
|
async docker(servicePath) {
|
|
const dockerFiles = await glob("DockerFile", { cwd: __dirname, absolute: true });
|
|
if (dockerFiles.length !== 0) {
|
|
fs.rm(dockerFiles[0], { force: true });
|
|
}
|
|
await run('goctl', [
|
|
"docker", "--go", path.relative(__dirname, servicePath)
|
|
])
|
|
}
|
|
};
|
|
|
|
|
|
const GenerateConfig = {
|
|
api: {
|
|
getChoices: () => Paths.getAllApi(),
|
|
prompt: "Select an API description file",
|
|
generate: (path) => Generators.api(path),
|
|
},
|
|
rpc: {
|
|
getChoices: () => Paths.getAllProto(),
|
|
prompt: "Select a proto file",
|
|
generate: (path) => Generators.rpc(path),
|
|
},
|
|
docker: {
|
|
getChoices: () => Paths.getAllservice(),
|
|
prompt: "Select a service to generate Dockerfile",
|
|
generate: (path) => Generators.docker(path),
|
|
}
|
|
};
|
|
|
|
|
|
|
|
async function run(cmd, args, opts = {}) {
|
|
console.log(`>> ${cmd} ${args.join(' ')}`);
|
|
return execa(cmd, args, {
|
|
stdio: 'inherit',
|
|
...opts
|
|
});
|
|
}
|
|
|
|
|
|
async function searchSelector(chooses, message) {
|
|
const fuse = new Fuse(chooses, {
|
|
keys: ['title'],
|
|
threshold: 0.4,
|
|
})
|
|
return search({
|
|
message,
|
|
source: async (term) => {
|
|
if (!term) {
|
|
return chooses.map(s => ({ name: s.title, value: s.value }));
|
|
}
|
|
const result = fuse.search(term);
|
|
return result.map(s => ({ name: s.item.title, value: s.item.value }));
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
async function generateHandle() {
|
|
const type = values.type;
|
|
if (!type || !GenerateConfig[type]) {
|
|
console.error("Please specify valid -t <api|rpc|docker>");
|
|
return;
|
|
}
|
|
const config = GenerateConfig[type];
|
|
|
|
const input = values.server
|
|
? (Array.isArray(values.server) ? values.server[0] : values.server)
|
|
: await searchSelector(await config.getChoices(), config.prompt);
|
|
|
|
await config.generate(input);
|
|
}
|
|
|
|
async function buildImage(imageName) {
|
|
await run("docker", ["build", "-t", imageName, "."])
|
|
}
|
|
|
|
|
|
export const init = task({
|
|
name: "init",
|
|
desc: "initialize the project",
|
|
run: async () => {
|
|
await run("go", ["install", "github.com/zeromicro/go-zero/tools/goctl@latest"]);
|
|
}
|
|
})
|
|
|
|
|
|
export const tidy = task({
|
|
name: "tidy",
|
|
desc: "tidy go.mod and go.sum",
|
|
run: async () => { run("go", ["mod", "tidy"]) },
|
|
})
|
|
|
|
|
|
export const build = task({
|
|
name: "build",
|
|
desc: "build docker image",
|
|
run: async () => {
|
|
if (values.imageName) {
|
|
buildImage(values.imageName);
|
|
} else {
|
|
console.error("Please specify image name with -i <imageName>");
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
export const gen = task({
|
|
name: "gen",
|
|
desc: "generate API/RPC service code",
|
|
run: generateHandle,
|
|
});
|
|
|
|
|