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 !== "rpc" ? `${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 "); 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 "); } } }) export const gen = task({ name: "gen", desc: "generate API/RPC service code", run: generateHandle, });