firest commit
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
|||||||
|
FROM golang:alpine AS builder
|
||||||
|
|
||||||
|
LABEL stage=gobuilder
|
||||||
|
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
|
||||||
|
RUN apk update --no-cache && apk add --no-cache tzdata
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
ADD go.mod .
|
||||||
|
ADD go.sum .
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go build -ldflags="-s -w" -o /app/user app/user/api/user.go
|
||||||
|
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
|
||||||
|
ENV TZ Asia/Shanghai
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/user /app/user
|
||||||
|
COPY app/user/api/etc /app/etc
|
||||||
|
|
||||||
|
CMD ["./user", "-f", "etc/user-api.yaml"]
|
||||||
+183
@@ -0,0 +1,183 @@
|
|||||||
|
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" },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
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.log("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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 gen = task({
|
||||||
|
name: "gen",
|
||||||
|
desc: "generate API/RPC service code",
|
||||||
|
run: generateHandle,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Name: user-api
|
||||||
|
Host: 0.0.0.0
|
||||||
|
Port: 8888
|
||||||
|
|
||||||
|
UsercenterRpcConf:
|
||||||
|
Target: k8s://juwan/user-rpc-svc:9001
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
rest.RestConf
|
||||||
|
UsercenterRpcConf zrpc.RpcClientConf
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/api/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// Get user infomaction by user id
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/user/info",
|
||||||
|
Handler: userInfoHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
"juwan-backend/app/user/api/internal/logic"
|
||||||
|
"juwan-backend/app/user/api/internal/svc"
|
||||||
|
"juwan-backend/app/user/api/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get user infomaction by user id
|
||||||
|
func userInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.UserInfoReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := logic.NewUserInfoLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.UserInfo(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/api/internal/svc"
|
||||||
|
"juwan-backend/app/user/api/internal/types"
|
||||||
|
"juwan-backend/app/user/rpc/usercenter"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfoLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user infomaction by user id
|
||||||
|
func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic {
|
||||||
|
return &UserInfoLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UserInfoLogic) UserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
|
||||||
|
logx.Infof("Request user info, user id: %d", req.UserId)
|
||||||
|
res, err := l.svcCtx.Usercenter.GetUserInfo(l.ctx, &usercenter.GetUserInfoReq{
|
||||||
|
Id: req.UserId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &types.UserInfoResp{
|
||||||
|
UserId: res.Id,
|
||||||
|
Nickname: res.Nickname,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"juwan-backend/app/user/api/internal/config"
|
||||||
|
"juwan-backend/app/user/rpc/usercenter"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceContext struct {
|
||||||
|
Config config.Config
|
||||||
|
Usercenter usercenter.Usercenter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
Usercenter: usercenter.NewUsercenter(zrpc.MustNewClient(c.UsercenterRpcConf)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
type UserInfoReq struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResp struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/api/internal/config"
|
||||||
|
"juwan-backend/app/user/api/internal/handler"
|
||||||
|
"juwan-backend/app/user/api/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFile = flag.String("f", "etc/user-api.yaml", "the config file")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
|
||||||
|
server := rest.MustNewServer(c.RestConf)
|
||||||
|
defer server.Stop()
|
||||||
|
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
|
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
|
||||||
|
server.Start()
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Name: pb.rpc
|
||||||
|
ListenOn: 0.0.0.0:9001
|
||||||
|
|
||||||
|
# UserDB: "${DB_URI}?sslmode=disable"
|
||||||
|
DB:
|
||||||
|
UserDB: "${DB_URI}?sslmode=disable"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "github.com/zeromicro/go-zero/zrpc"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
zrpc.RpcServerConf
|
||||||
|
DB struct {
|
||||||
|
UserDB string
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/rpc/internal/svc"
|
||||||
|
"juwan-backend/app/user/rpc/pb"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserInfoLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
|
||||||
|
return &GetUserInfoLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetUserInfoLogic) GetUserInfo(in *pb.GetUserInfoReq) (*pb.GetUserInfoResp, error) {
|
||||||
|
users := map[int64]string{
|
||||||
|
1: "WangHuahua",
|
||||||
|
2: "LiKunkun",
|
||||||
|
}
|
||||||
|
|
||||||
|
nikename := "Unknow"
|
||||||
|
if name, ok := users[in.Id]; ok {
|
||||||
|
nikename = name
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.GetUserInfoResp{
|
||||||
|
Id: in.Id,
|
||||||
|
Nickname: nikename,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.9.2
|
||||||
|
// Source: user.proto
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/rpc/internal/logic"
|
||||||
|
"juwan-backend/app/user/rpc/internal/svc"
|
||||||
|
"juwan-backend/app/user/rpc/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UsercenterServer struct {
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
pb.UnimplementedUsercenterServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUsercenterServer(svcCtx *svc.ServiceContext) *UsercenterServer {
|
||||||
|
return &UsercenterServer{
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsercenterServer) GetUserInfo(ctx context.Context, in *pb.GetUserInfoReq) (*pb.GetUserInfoResp, error) {
|
||||||
|
l := logic.NewGetUserInfoLogic(ctx, s.svcCtx)
|
||||||
|
return l.GetUserInfo(in)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package svc
|
||||||
|
|
||||||
|
import "juwan-backend/app/user/rpc/internal/config"
|
||||||
|
|
||||||
|
type ServiceContext struct {
|
||||||
|
Config config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
return &ServiceContext{
|
||||||
|
Config: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/rpc/internal/config"
|
||||||
|
"juwan-backend/app/user/rpc/internal/server"
|
||||||
|
"juwan-backend/app/user/rpc/internal/svc"
|
||||||
|
"juwan-backend/app/user/rpc/pb"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/core/service"
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFile = flag.String("f", "etc/pb.yaml", "the config file")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
|
||||||
|
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||||
|
pb.RegisterUsercenterServer(grpcServer, server.NewUsercenterServer(ctx))
|
||||||
|
|
||||||
|
if c.Mode == service.DevMode || c.Mode == service.TestMode {
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
|
||||||
|
s.Start()
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.9
|
||||||
|
// protoc v6.32.0
|
||||||
|
// source: user.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserInfoReq struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoReq) Reset() {
|
||||||
|
*x = GetUserInfoReq{}
|
||||||
|
mi := &file_user_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoReq) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetUserInfoReq) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetUserInfoReq) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_user_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetUserInfoReq.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetUserInfoReq) Descriptor() ([]byte, []int) {
|
||||||
|
return file_user_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoReq) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserInfoResp struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoResp) Reset() {
|
||||||
|
*x = GetUserInfoResp{}
|
||||||
|
mi := &file_user_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoResp) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetUserInfoResp) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetUserInfoResp) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_user_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetUserInfoResp.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetUserInfoResp) Descriptor() ([]byte, []int) {
|
||||||
|
return file_user_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoResp) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetUserInfoResp) GetNickname() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Nickname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_user_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_user_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\n" +
|
||||||
|
"user.proto\x12\x02pb\" \n" +
|
||||||
|
"\x0eGetUserInfoReq\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\"=\n" +
|
||||||
|
"\x0fGetUserInfoResp\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1a\n" +
|
||||||
|
"\bnickname\x18\x02 \x01(\tR\bnickname2D\n" +
|
||||||
|
"\n" +
|
||||||
|
"Usercenter\x126\n" +
|
||||||
|
"\vGetUserInfo\x12\x12.pb.GetUserInfoReq\x1a\x13.pb.GetUserInfoRespB\x06Z\x04./pbb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_user_proto_rawDescOnce sync.Once
|
||||||
|
file_user_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_user_proto_rawDescGZIP() []byte {
|
||||||
|
file_user_proto_rawDescOnce.Do(func() {
|
||||||
|
file_user_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_user_proto_rawDesc), len(file_user_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_user_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_user_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_user_proto_goTypes = []any{
|
||||||
|
(*GetUserInfoReq)(nil), // 0: pb.GetUserInfoReq
|
||||||
|
(*GetUserInfoResp)(nil), // 1: pb.GetUserInfoResp
|
||||||
|
}
|
||||||
|
var file_user_proto_depIdxs = []int32{
|
||||||
|
0, // 0: pb.Usercenter.GetUserInfo:input_type -> pb.GetUserInfoReq
|
||||||
|
1, // 1: pb.Usercenter.GetUserInfo:output_type -> pb.GetUserInfoResp
|
||||||
|
1, // [1:2] is the sub-list for method output_type
|
||||||
|
0, // [0:1] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_user_proto_init() }
|
||||||
|
func file_user_proto_init() {
|
||||||
|
if File_user_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_proto_rawDesc), len(file_user_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_user_proto_goTypes,
|
||||||
|
DependencyIndexes: file_user_proto_depIdxs,
|
||||||
|
MessageInfos: file_user_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_user_proto = out.File
|
||||||
|
file_user_proto_goTypes = nil
|
||||||
|
file_user_proto_depIdxs = nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.5.1
|
||||||
|
// - protoc v6.32.0
|
||||||
|
// source: user.proto
|
||||||
|
|
||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
Usercenter_GetUserInfo_FullMethodName = "/pb.Usercenter/GetUserInfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsercenterClient is the client API for Usercenter service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type UsercenterClient interface {
|
||||||
|
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type usercenterClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUsercenterClient(cc grpc.ClientConnInterface) UsercenterClient {
|
||||||
|
return &usercenterClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *usercenterClient) GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(GetUserInfoResp)
|
||||||
|
err := c.cc.Invoke(ctx, Usercenter_GetUserInfo_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsercenterServer is the server API for Usercenter service.
|
||||||
|
// All implementations must embed UnimplementedUsercenterServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type UsercenterServer interface {
|
||||||
|
GetUserInfo(context.Context, *GetUserInfoReq) (*GetUserInfoResp, error)
|
||||||
|
mustEmbedUnimplementedUsercenterServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedUsercenterServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedUsercenterServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedUsercenterServer) GetUserInfo(context.Context, *GetUserInfoReq) (*GetUserInfoResp, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetUserInfo not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUsercenterServer) mustEmbedUnimplementedUsercenterServer() {}
|
||||||
|
func (UnimplementedUsercenterServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeUsercenterServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to UsercenterServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeUsercenterServer interface {
|
||||||
|
mustEmbedUnimplementedUsercenterServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterUsercenterServer(s grpc.ServiceRegistrar, srv UsercenterServer) {
|
||||||
|
// If the following call pancis, it indicates UnimplementedUsercenterServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&Usercenter_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Usercenter_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetUserInfoReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UsercenterServer).GetUserInfo(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Usercenter_GetUserInfo_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UsercenterServer).GetUserInfo(ctx, req.(*GetUserInfoReq))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usercenter_ServiceDesc is the grpc.ServiceDesc for Usercenter service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var Usercenter_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "pb.Usercenter",
|
||||||
|
HandlerType: (*UsercenterServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetUserInfo",
|
||||||
|
Handler: _Usercenter_GetUserInfo_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "user.proto",
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
|
// goctl 1.9.2
|
||||||
|
// Source: user.proto
|
||||||
|
|
||||||
|
package usercenter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"juwan-backend/app/user/rpc/pb"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
GetUserInfoReq = pb.GetUserInfoReq
|
||||||
|
GetUserInfoResp = pb.GetUserInfoResp
|
||||||
|
|
||||||
|
Usercenter interface {
|
||||||
|
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultUsercenter struct {
|
||||||
|
cli zrpc.Client
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUsercenter(cli zrpc.Client) Usercenter {
|
||||||
|
return &defaultUsercenter{
|
||||||
|
cli: cli,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultUsercenter) GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error) {
|
||||||
|
client := pb.NewUsercenterClient(m.cli.Conn())
|
||||||
|
return client.GetUserInfo(ctx, in, opts...)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: db-dx-init-script
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: db-dx-init-script
|
||||||
|
data:
|
||||||
|
init-extensions-sql: |
|
||||||
|
create extension if not exists "uuid-ossp";
|
||||||
|
create extension if not exists "pg_trgm";
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
namespace: juwan
|
||||||
|
name: cluster-example
|
||||||
|
spec:
|
||||||
|
instances: 3
|
||||||
|
backup:
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
||||||
|
endpointURL: https://cn-nb1.rains3.com
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: rc-creds
|
||||||
|
key: SOucqRaJr4OyfcIu
|
||||||
|
secretAccessKey:
|
||||||
|
name: rc-creds
|
||||||
|
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
||||||
|
wal:
|
||||||
|
compression: gzip
|
||||||
|
storage:
|
||||||
|
size: 1Gi
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: juwan
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
namespace: juwan
|
||||||
|
name: find-endpoints
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: discov-endpoints
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["endpoints"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: ["discovery.k8s.io"]
|
||||||
|
resources: ["endpointslices"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: find-endpoints-discov-endpoints
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: discov-endpoints
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: find-endpoints
|
||||||
|
namespace: juwan
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: user-api
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-api
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: user-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: user-api
|
||||||
|
spec:
|
||||||
|
serviceAccountName: find-endpoints
|
||||||
|
containers:
|
||||||
|
- name: user-api
|
||||||
|
image: user-api:v1
|
||||||
|
ports:
|
||||||
|
- containerPort: 8888
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8888
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8888
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 20
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1024Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: timezone
|
||||||
|
mountPath: /etc/localtime
|
||||||
|
volumes:
|
||||||
|
- name: timezone
|
||||||
|
hostPath:
|
||||||
|
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: user-api-svc
|
||||||
|
namespace: juwan
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 8888
|
||||||
|
targetPort: 8888
|
||||||
|
selector:
|
||||||
|
app: user-api
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: user-api-hpa-c
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-api-hpa-c
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: user-api
|
||||||
|
minReplicas: 3
|
||||||
|
maxReplicas: 10
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: user-api-hpa-m
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-api-hpa-m
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: user-api
|
||||||
|
minReplicas: 3
|
||||||
|
maxReplicas: 10
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: user-rpc
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-rpc
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: user-rpc
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: user-rpc
|
||||||
|
spec:
|
||||||
|
serviceAccountName: find-endpoints
|
||||||
|
initContainers:
|
||||||
|
- name: wait-for-db
|
||||||
|
image: busybox:1.36
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
'until nc -z -v -w5 user-db-rw 5432; do echo "Waiting for database..."; sleep 2; done;',
|
||||||
|
]
|
||||||
|
containers:
|
||||||
|
- name: user-rpc
|
||||||
|
image: user-rpc:v1
|
||||||
|
ports:
|
||||||
|
- containerPort: 9001
|
||||||
|
env:
|
||||||
|
- name: DB_URI
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: user-db-app
|
||||||
|
key: uri
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 9001
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 9001
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
periodSeconds: 20
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 1024Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: timezone
|
||||||
|
mountPath: /etc/localtime
|
||||||
|
volumes:
|
||||||
|
- name: timezone
|
||||||
|
hostPath:
|
||||||
|
path: /usr/share/zoneinfo/Asia/Shanghai
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: user-rpc-svc
|
||||||
|
namespace: juwan
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 9001
|
||||||
|
targetPort: 9001
|
||||||
|
selector:
|
||||||
|
app: user-rpc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: user-rpc-hpa-c
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-rpc-hpa-c
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: user-rpc
|
||||||
|
minReplicas: 3
|
||||||
|
maxReplicas: 10
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: user-rpc-hpa-m
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: user-rpc-hpa-m
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: user-rpc
|
||||||
|
minReplicas: 3
|
||||||
|
maxReplicas: 10
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
namespace: juwan
|
||||||
|
name: user-db
|
||||||
|
spec:
|
||||||
|
instances: 3
|
||||||
|
postInitSQLRefs:
|
||||||
|
configMapRefs:
|
||||||
|
- name: db-dx-init-script
|
||||||
|
key: init-extensions-sql
|
||||||
|
optional: false
|
||||||
|
backup:
|
||||||
|
barmanObjectStore:
|
||||||
|
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
||||||
|
endpointURL: https://cn-nb1.rains3.com
|
||||||
|
s3Credentials:
|
||||||
|
accessKeyId:
|
||||||
|
name: rc-creds
|
||||||
|
key: SOucqRaJr4OyfcIu
|
||||||
|
secretAccessKey:
|
||||||
|
name: rc-creds
|
||||||
|
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
||||||
|
wal:
|
||||||
|
compression: gzip
|
||||||
|
storage:
|
||||||
|
size: 1Gi
|
||||||
|
monitoring:
|
||||||
|
enablePodMonitor: true
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
author: "Asadz"
|
||||||
|
date: "2024-06-19"
|
||||||
|
version: "1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
UserInfoReq {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
}
|
||||||
|
UserInfoResp {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
service user-api {
|
||||||
|
@doc (
|
||||||
|
summary: "Get user infomaction by user id"
|
||||||
|
)
|
||||||
|
@handler userInfo
|
||||||
|
post /user/info (UserInfoReq) returns (UserInfoResp)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "./pb";
|
||||||
|
|
||||||
|
package pb;
|
||||||
|
|
||||||
|
message GetUserInfoReq {
|
||||||
|
int64 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetUserInfoResp {
|
||||||
|
int64 id = 1;
|
||||||
|
string nickname = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service Usercenter {
|
||||||
|
rpc GetUserInfo(GetUserInfoReq) returns (GetUserInfoResp);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
create extension if not exists "uuid-ossp";
|
||||||
|
create extension if not exists "pg_trgm";
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
passwd VARCHAR(255) NOT NULL,
|
||||||
|
nikename VARCHAR(50) NOT NULL,
|
||||||
|
phone VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
role_type SMALLINT NOT NULL, -- 1:玩家, 2:打手, 3:店长
|
||||||
|
is_verified BOOLEAN DEFAULT false,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
module juwan-backend
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/zeromicro/go-zero v1.10.0
|
||||||
|
google.golang.org/grpc v1.79.1
|
||||||
|
google.golang.org/protobuf v1.36.11
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/coreos/go-semver v0.3.1 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grafana/pyroscope-go v1.2.7 // indirect
|
||||||
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/redis/go-redis/v9 v9.17.3 // indirect
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
go.uber.org/mock v0.6.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
golang.org/x/net v0.48.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.34.0 // indirect
|
||||||
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
|
golang.org/x/term v0.38.0 // indirect
|
||||||
|
golang.org/x/text v0.32.0 // indirect
|
||||||
|
golang.org/x/time v0.10.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.29.3 // indirect
|
||||||
|
k8s.io/apimachinery v0.29.4 // indirect
|
||||||
|
k8s.io/client-go v0.29.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.110.1 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||||
|
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||||
|
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||||
|
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
|
||||||
|
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
|
||||||
|
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||||
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac=
|
||||||
|
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc=
|
||||||
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
|
||||||
|
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||||
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
||||||
|
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/zeromicro/go-zero v1.10.0 h1:+qfAqj+BGt0qjW1PQk2VO5WLwIQBh60CA3OTLsBosS8=
|
||||||
|
github.com/zeromicro/go-zero v1.10.0/go.mod h1:qAModWGsfkrBl0JP9oS7K7k6dgucExOuQdpzHyXVKLg=
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
|
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
|
||||||
|
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||||
|
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
|
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||||
|
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
|
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
|
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||||
|
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||||
|
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||||
|
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||||
|
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
|
||||||
|
k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
|
||||||
|
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
|
||||||
|
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
|
||||||
|
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
|
||||||
|
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
|
||||||
|
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE=
|
||||||
|
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
|
||||||
|
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||||
|
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../hereby/bin/hereby.js" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../hereby/bin/hereby.js" "$@"
|
||||||
|
fi
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\hereby\bin\hereby.js" %*
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../hereby/bin/hereby.js" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../hereby/bin/hereby.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../hereby/bin/hereby.js" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../hereby/bin/hereby.js" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||||
|
|
||||||
|
case `uname` in
|
||||||
|
*CYGWIN*|*MINGW*|*MSYS*)
|
||||||
|
if command -v cygpath > /dev/null 2>&1; then
|
||||||
|
basedir=`cygpath -w "$basedir"`
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -x "$basedir/node" ]; then
|
||||||
|
exec "$basedir/node" "$basedir/../which/bin/node-which" "$@"
|
||||||
|
else
|
||||||
|
exec node "$basedir/../which/bin/node-which" "$@"
|
||||||
|
fi
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO off
|
||||||
|
GOTO start
|
||||||
|
:find_dp0
|
||||||
|
SET dp0=%~dp0
|
||||||
|
EXIT /b
|
||||||
|
:start
|
||||||
|
SETLOCAL
|
||||||
|
CALL :find_dp0
|
||||||
|
|
||||||
|
IF EXIST "%dp0%\node.exe" (
|
||||||
|
SET "_prog=%dp0%\node.exe"
|
||||||
|
) ELSE (
|
||||||
|
SET "_prog=node"
|
||||||
|
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||||
|
)
|
||||||
|
|
||||||
|
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||||
|
|
||||||
|
$exe=""
|
||||||
|
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||||
|
# Fix case when both the Windows and Linux builds of Node
|
||||||
|
# are installed in the same directory
|
||||||
|
$exe=".exe"
|
||||||
|
}
|
||||||
|
$ret=0
|
||||||
|
if (Test-Path "$basedir/node$exe") {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
|
||||||
|
} else {
|
||||||
|
& "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
} else {
|
||||||
|
# Support pipeline input
|
||||||
|
if ($MyInvocation.ExpectingInput) {
|
||||||
|
$input | & "node$exe" "$basedir/../which/bin/node-which" $args
|
||||||
|
} else {
|
||||||
|
& "node$exe" "$basedir/../which/bin/node-which" $args
|
||||||
|
}
|
||||||
|
$ret=$LASTEXITCODE
|
||||||
|
}
|
||||||
|
exit $ret
|
||||||
+1126
File diff suppressed because it is too large
Load Diff
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
# @inquirer/ansi
|
||||||
|
|
||||||
|
A lightweight package providing ANSI escape sequences for terminal cursor manipulation and screen clearing.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/ansi
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/ansi
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {
|
||||||
|
cursorUp,
|
||||||
|
cursorDown,
|
||||||
|
cursorTo,
|
||||||
|
cursorLeft,
|
||||||
|
cursorHide,
|
||||||
|
cursorShow,
|
||||||
|
eraseLines,
|
||||||
|
} from '@inquirer/ansi';
|
||||||
|
|
||||||
|
// Move cursor up 3 lines
|
||||||
|
process.stdout.write(cursorUp(3));
|
||||||
|
|
||||||
|
// Move cursor to specific position (x: 10, y: 5)
|
||||||
|
process.stdout.write(cursorTo(10, 5));
|
||||||
|
|
||||||
|
// Hide/show cursor
|
||||||
|
process.stdout.write(cursorHide);
|
||||||
|
process.stdout.write(cursorShow);
|
||||||
|
|
||||||
|
// Clear 5 lines
|
||||||
|
process.stdout.write(eraseLines(5));
|
||||||
|
```
|
||||||
|
|
||||||
|
Or when used inside an inquirer prompt:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { cursorHide } from '@inquirer/ansi';
|
||||||
|
import { createPrompt } from '@inquirer/core';
|
||||||
|
|
||||||
|
export default createPrompt((config, done: (value: void) => void) => {
|
||||||
|
return `Choose an option${cursorHide}`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Cursor Movement
|
||||||
|
|
||||||
|
- **`cursorUp(count?: number)`** - Move cursor up by `count` lines (default: 1)
|
||||||
|
- **`cursorDown(count?: number)`** - Move cursor down by `count` lines (default: 1)
|
||||||
|
- **`cursorTo(x: number, y?: number)`** - Move cursor to position (x, y). If y is omitted, only moves horizontally
|
||||||
|
- **`cursorLeft`** - Move cursor to beginning of line
|
||||||
|
|
||||||
|
### Cursor Visibility
|
||||||
|
|
||||||
|
- **`cursorHide`** - Hide the cursor
|
||||||
|
- **`cursorShow`** - Show the cursor
|
||||||
|
|
||||||
|
### Screen Manipulation
|
||||||
|
|
||||||
|
- **`eraseLines(count: number)`** - Clear `count` lines and position cursor at the beginning of the first cleared line
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/** Move cursor to first column */
|
||||||
|
export declare const cursorLeft: string;
|
||||||
|
/** Hide the cursor */
|
||||||
|
export declare const cursorHide: string;
|
||||||
|
/** Show the cursor */
|
||||||
|
export declare const cursorShow: string;
|
||||||
|
/** Move cursor up by count rows */
|
||||||
|
export declare const cursorUp: (rows?: number) => string;
|
||||||
|
/** Move cursor down by count rows */
|
||||||
|
export declare const cursorDown: (rows?: number) => string;
|
||||||
|
/** Move cursor to position (x, y) */
|
||||||
|
export declare const cursorTo: (x: number, y?: number) => string;
|
||||||
|
/** Erase the specified number of lines above the cursor */
|
||||||
|
export declare const eraseLines: (lines: number) => string;
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
const ESC = '\u001B[';
|
||||||
|
/** Move cursor to first column */
|
||||||
|
export const cursorLeft = ESC + 'G';
|
||||||
|
/** Hide the cursor */
|
||||||
|
export const cursorHide = ESC + '?25l';
|
||||||
|
/** Show the cursor */
|
||||||
|
export const cursorShow = ESC + '?25h';
|
||||||
|
/** Move cursor up by count rows */
|
||||||
|
export const cursorUp = (rows = 1) => (rows > 0 ? `${ESC}${rows}A` : '');
|
||||||
|
/** Move cursor down by count rows */
|
||||||
|
export const cursorDown = (rows = 1) => rows > 0 ? `${ESC}${rows}B` : '';
|
||||||
|
/** Move cursor to position (x, y) */
|
||||||
|
export const cursorTo = (x, y) => {
|
||||||
|
if (typeof y === 'number' && !Number.isNaN(y)) {
|
||||||
|
return `${ESC}${y + 1};${x + 1}H`;
|
||||||
|
}
|
||||||
|
return `${ESC}${x + 1}G`;
|
||||||
|
};
|
||||||
|
const eraseLine = ESC + '2K';
|
||||||
|
/** Erase the specified number of lines above the cursor */
|
||||||
|
export const eraseLines = (lines) => lines > 0 ? (eraseLine + cursorUp(1)).repeat(lines - 1) + eraseLine + cursorLeft : '';
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"name": "@inquirer/ansi",
|
||||||
|
"version": "2.0.3",
|
||||||
|
"keywords": [
|
||||||
|
"ansi",
|
||||||
|
"answer",
|
||||||
|
"answers",
|
||||||
|
"ask",
|
||||||
|
"base",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command-line",
|
||||||
|
"confirm",
|
||||||
|
"enquirer",
|
||||||
|
"generate",
|
||||||
|
"generator",
|
||||||
|
"hyper",
|
||||||
|
"input",
|
||||||
|
"inquire",
|
||||||
|
"inquirer",
|
||||||
|
"interface",
|
||||||
|
"iterm",
|
||||||
|
"javascript",
|
||||||
|
"menu",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"prompt",
|
||||||
|
"promptly",
|
||||||
|
"prompts",
|
||||||
|
"question",
|
||||||
|
"readline",
|
||||||
|
"scaffold",
|
||||||
|
"scaffolder",
|
||||||
|
"scaffolding",
|
||||||
|
"stdin",
|
||||||
|
"stdout",
|
||||||
|
"terminal",
|
||||||
|
"tty",
|
||||||
|
"ui",
|
||||||
|
"yeoman",
|
||||||
|
"yo",
|
||||||
|
"zsh"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/ansi/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Simon Boudrias <admin@simonboudrias.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SBoudrias/Inquirer.js.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"gitHead": "99d00a9adc53be8b7edf5926b2ec4ba0b792f68f"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
# `@inquirer/checkbox`
|
||||||
|
|
||||||
|
Simple interactive command line prompt to display a list of checkboxes (multi select).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" align="center">Or</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/checkbox
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/checkbox
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { checkbox, Separator } from '@inquirer/prompts';
|
||||||
|
// Or
|
||||||
|
// import checkbox, { Separator } from '@inquirer/checkbox';
|
||||||
|
|
||||||
|
const answer = await checkbox({
|
||||||
|
message: 'Select a package manager',
|
||||||
|
choices: [
|
||||||
|
{ name: 'npm', value: 'npm' },
|
||||||
|
{ name: 'yarn', value: 'yarn' },
|
||||||
|
new Separator(),
|
||||||
|
{ name: 'pnpm', value: 'pnpm', disabled: true },
|
||||||
|
{
|
||||||
|
name: 'pnpm',
|
||||||
|
value: 'pnpm',
|
||||||
|
disabled: '(pnpm is not available)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Property | Type | Required | Description |
|
||||||
|
| --------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| message | `string` | yes | The question to ask |
|
||||||
|
| choices | `Choice[]` | yes | List of the available choices. |
|
||||||
|
| pageSize | `number` | no | By default, lists of choice longer than 7 will be paginated. Use this option to control how many choices will appear on the screen at once. |
|
||||||
|
| loop | `boolean` | no | Defaults to `true`. When set to `false`, the cursor will be constrained to the top and bottom of the choice list without looping. |
|
||||||
|
| required | `boolean` | no | When set to `true`, ensures at least one choice must be selected. |
|
||||||
|
| validate | `async (Choice[]) => boolean \| string` | no | On submit, validate the choices. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. |
|
||||||
|
| shortcuts | [See Shortcuts](#Shortcuts) | no | Customize shortcut keys for `all` and `invert`. |
|
||||||
|
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
|
||||||
|
|
||||||
|
`Separator` objects can be used in the `choices` array to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options.
|
||||||
|
|
||||||
|
### `Choice` object
|
||||||
|
|
||||||
|
The `Choice` object is typed as
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Choice<Value> = {
|
||||||
|
value: Value;
|
||||||
|
name?: string;
|
||||||
|
checkedName?: string;
|
||||||
|
description?: string;
|
||||||
|
short?: string;
|
||||||
|
checked?: boolean;
|
||||||
|
disabled?: boolean | string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's each property:
|
||||||
|
|
||||||
|
- `value`: The value is what will be returned by `await checkbox()`.
|
||||||
|
- `name`: This is the string displayed in the choice list.
|
||||||
|
- `checkedName`: Alternative `name` (or format) displayed when the choice is checked.
|
||||||
|
- `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice.
|
||||||
|
- `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`.
|
||||||
|
- `checked`: If `true`, the option will be checked by default.
|
||||||
|
- `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available.
|
||||||
|
|
||||||
|
Also note the `choices` array can contain `Separator`s to help organize long lists.
|
||||||
|
|
||||||
|
`choices` can also be an array of string, in which case the string will be used both as the `value` and the `name`.
|
||||||
|
|
||||||
|
## Shortcuts
|
||||||
|
|
||||||
|
You can customize the shortcut keys for `all` and `invert` or disable them by setting them to `null`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Shortcuts = {
|
||||||
|
all?: string | null; // default: 'a'
|
||||||
|
invert?: string | null; // default: 'i'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Theme = {
|
||||||
|
prefix: string | { idle: string; done: string };
|
||||||
|
spinner: {
|
||||||
|
interval: number;
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
answer: (text: string) => string;
|
||||||
|
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
|
||||||
|
error: (text: string) => string;
|
||||||
|
defaultAnswer: (text: string) => string;
|
||||||
|
help: (text: string) => string;
|
||||||
|
highlight: (text: string) => string;
|
||||||
|
key: (text: string) => string;
|
||||||
|
disabledChoice: (text: string) => string;
|
||||||
|
description: (text: string) => string;
|
||||||
|
renderSelectedChoices: <T>(
|
||||||
|
selectedChoices: ReadonlyArray<Choice<T>>,
|
||||||
|
allChoices: ReadonlyArray<Choice<T> | Separator>,
|
||||||
|
) => string;
|
||||||
|
keysHelpTip: (keys: [key: string, action: string][]) => string | undefined;
|
||||||
|
};
|
||||||
|
icon: {
|
||||||
|
checked: string;
|
||||||
|
unchecked: string;
|
||||||
|
cursor: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### `theme.style.keysHelpTip`
|
||||||
|
|
||||||
|
This function allows you to customize the keyboard shortcuts help tip displayed below the prompt. It receives an array of key-action pairs and should return a formatted string. You can also hook here to localize the labels to different languages.
|
||||||
|
|
||||||
|
It can also returns `undefined` to hide the help tip entirely.
|
||||||
|
|
||||||
|
```js
|
||||||
|
theme: {
|
||||||
|
style: {
|
||||||
|
keysHelpTip: (keys) => {
|
||||||
|
// Return undefined to hide the help tip completely
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
// Or customize the formatting. Or localize the labels.
|
||||||
|
return keys.map(([key, action]) => `${key}: ${action}`).join(' | ');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
import { Separator, type Theme, type Keybinding } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
type CheckboxTheme = {
|
||||||
|
icon: {
|
||||||
|
checked: string;
|
||||||
|
unchecked: string;
|
||||||
|
cursor: string;
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
disabledChoice: (text: string) => string;
|
||||||
|
renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
|
||||||
|
description: (text: string) => string;
|
||||||
|
keysHelpTip: (keys: [key: string, action: string][]) => string | undefined;
|
||||||
|
};
|
||||||
|
keybindings: ReadonlyArray<Keybinding>;
|
||||||
|
};
|
||||||
|
type CheckboxShortcuts = {
|
||||||
|
all?: string | null;
|
||||||
|
invert?: string | null;
|
||||||
|
};
|
||||||
|
type Choice<Value> = {
|
||||||
|
value: Value;
|
||||||
|
name?: string;
|
||||||
|
checkedName?: string;
|
||||||
|
description?: string;
|
||||||
|
short?: string;
|
||||||
|
disabled?: boolean | string;
|
||||||
|
checked?: boolean;
|
||||||
|
type?: never;
|
||||||
|
};
|
||||||
|
type NormalizedChoice<Value> = {
|
||||||
|
value: Value;
|
||||||
|
name: string;
|
||||||
|
checkedName: string;
|
||||||
|
description?: string;
|
||||||
|
short: string;
|
||||||
|
disabled: boolean | string;
|
||||||
|
checked: boolean;
|
||||||
|
};
|
||||||
|
declare const _default: <Value>(config: {
|
||||||
|
message: string;
|
||||||
|
prefix?: string | undefined;
|
||||||
|
pageSize?: number | undefined;
|
||||||
|
choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[];
|
||||||
|
loop?: boolean | undefined;
|
||||||
|
required?: boolean | undefined;
|
||||||
|
validate?: ((choices: readonly NormalizedChoice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
|
||||||
|
theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
|
||||||
|
shortcuts?: CheckboxShortcuts | undefined;
|
||||||
|
}, context?: import("@inquirer/type").Context) => Promise<Value[]>;
|
||||||
|
export default _default;
|
||||||
|
export { Separator } from '@inquirer/core';
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
import { createPrompt, useState, useKeypress, usePrefix, usePagination, useMemo, makeTheme, isUpKey, isDownKey, isSpaceKey, isNumberKey, isEnterKey, ValidationError, Separator, } from '@inquirer/core';
|
||||||
|
import { cursorHide } from '@inquirer/ansi';
|
||||||
|
import { styleText } from 'node:util';
|
||||||
|
import figures from '@inquirer/figures';
|
||||||
|
const checkboxTheme = {
|
||||||
|
icon: {
|
||||||
|
checked: styleText('green', figures.circleFilled),
|
||||||
|
unchecked: figures.circle,
|
||||||
|
cursor: figures.pointer,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
disabledChoice: (text) => styleText('dim', `- ${text}`),
|
||||||
|
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '),
|
||||||
|
description: (text) => styleText('cyan', text),
|
||||||
|
keysHelpTip: (keys) => keys
|
||||||
|
.map(([key, action]) => `${styleText('bold', key)} ${styleText('dim', action)}`)
|
||||||
|
.join(styleText('dim', ' • ')),
|
||||||
|
},
|
||||||
|
keybindings: [],
|
||||||
|
};
|
||||||
|
function isSelectable(item) {
|
||||||
|
return !Separator.isSeparator(item) && !item.disabled;
|
||||||
|
}
|
||||||
|
function isChecked(item) {
|
||||||
|
return isSelectable(item) && item.checked;
|
||||||
|
}
|
||||||
|
function toggle(item) {
|
||||||
|
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
||||||
|
}
|
||||||
|
function check(checked) {
|
||||||
|
return function (item) {
|
||||||
|
return isSelectable(item) ? { ...item, checked } : item;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function normalizeChoices(choices) {
|
||||||
|
return choices.map((choice) => {
|
||||||
|
if (Separator.isSeparator(choice))
|
||||||
|
return choice;
|
||||||
|
if (typeof choice === 'string') {
|
||||||
|
return {
|
||||||
|
value: choice,
|
||||||
|
name: choice,
|
||||||
|
short: choice,
|
||||||
|
checkedName: choice,
|
||||||
|
disabled: false,
|
||||||
|
checked: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const name = choice.name ?? String(choice.value);
|
||||||
|
const normalizedChoice = {
|
||||||
|
value: choice.value,
|
||||||
|
name,
|
||||||
|
short: choice.short ?? name,
|
||||||
|
checkedName: choice.checkedName ?? name,
|
||||||
|
disabled: choice.disabled ?? false,
|
||||||
|
checked: choice.checked ?? false,
|
||||||
|
};
|
||||||
|
if (choice.description) {
|
||||||
|
normalizedChoice.description = choice.description;
|
||||||
|
}
|
||||||
|
return normalizedChoice;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default createPrompt((config, done) => {
|
||||||
|
const { pageSize = 7, loop = true, required, validate = () => true } = config;
|
||||||
|
const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts };
|
||||||
|
const theme = makeTheme(checkboxTheme, config.theme);
|
||||||
|
const { keybindings } = theme;
|
||||||
|
const [status, setStatus] = useState('idle');
|
||||||
|
const prefix = usePrefix({ status, theme });
|
||||||
|
const [items, setItems] = useState(normalizeChoices(config.choices));
|
||||||
|
const bounds = useMemo(() => {
|
||||||
|
const first = items.findIndex(isSelectable);
|
||||||
|
const last = items.findLastIndex(isSelectable);
|
||||||
|
if (first === -1) {
|
||||||
|
throw new ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
|
||||||
|
}
|
||||||
|
return { first, last };
|
||||||
|
}, [items]);
|
||||||
|
const [active, setActive] = useState(bounds.first);
|
||||||
|
const [errorMsg, setError] = useState();
|
||||||
|
useKeypress(async (key) => {
|
||||||
|
if (isEnterKey(key)) {
|
||||||
|
const selection = items.filter(isChecked);
|
||||||
|
const isValid = await validate([...selection]);
|
||||||
|
if (required && !items.some(isChecked)) {
|
||||||
|
setError('At least one choice must be selected');
|
||||||
|
}
|
||||||
|
else if (isValid === true) {
|
||||||
|
setStatus('done');
|
||||||
|
done(selection.map((choice) => choice.value));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setError(isValid || 'You must select a valid value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
|
||||||
|
if (loop ||
|
||||||
|
(isUpKey(key, keybindings) && active !== bounds.first) ||
|
||||||
|
(isDownKey(key, keybindings) && active !== bounds.last)) {
|
||||||
|
const offset = isUpKey(key, keybindings) ? -1 : 1;
|
||||||
|
let next = active;
|
||||||
|
do {
|
||||||
|
next = (next + offset + items.length) % items.length;
|
||||||
|
} while (!isSelectable(items[next]));
|
||||||
|
setActive(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isSpaceKey(key)) {
|
||||||
|
setError(undefined);
|
||||||
|
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
|
||||||
|
}
|
||||||
|
else if (key.name === shortcuts.all) {
|
||||||
|
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
|
||||||
|
setItems(items.map(check(selectAll)));
|
||||||
|
}
|
||||||
|
else if (key.name === shortcuts.invert) {
|
||||||
|
setItems(items.map(toggle));
|
||||||
|
}
|
||||||
|
else if (isNumberKey(key)) {
|
||||||
|
const selectedIndex = Number(key.name) - 1;
|
||||||
|
// Find the nth item (ignoring separators)
|
||||||
|
let selectableIndex = -1;
|
||||||
|
const position = items.findIndex((item) => {
|
||||||
|
if (Separator.isSeparator(item))
|
||||||
|
return false;
|
||||||
|
selectableIndex++;
|
||||||
|
return selectableIndex === selectedIndex;
|
||||||
|
});
|
||||||
|
const selectedItem = items[position];
|
||||||
|
if (selectedItem && isSelectable(selectedItem)) {
|
||||||
|
setActive(position);
|
||||||
|
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const message = theme.style.message(config.message, status);
|
||||||
|
let description;
|
||||||
|
const page = usePagination({
|
||||||
|
items,
|
||||||
|
active,
|
||||||
|
renderItem({ item, isActive }) {
|
||||||
|
if (Separator.isSeparator(item)) {
|
||||||
|
return ` ${item.separator}`;
|
||||||
|
}
|
||||||
|
if (item.disabled) {
|
||||||
|
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
|
||||||
|
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
|
||||||
|
}
|
||||||
|
if (isActive) {
|
||||||
|
description = item.description;
|
||||||
|
}
|
||||||
|
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
|
||||||
|
const name = item.checked ? item.checkedName : item.name;
|
||||||
|
const color = isActive ? theme.style.highlight : (x) => x;
|
||||||
|
const cursor = isActive ? theme.icon.cursor : ' ';
|
||||||
|
return color(`${cursor}${checkbox} ${name}`);
|
||||||
|
},
|
||||||
|
pageSize,
|
||||||
|
loop,
|
||||||
|
});
|
||||||
|
if (status === 'done') {
|
||||||
|
const selection = items.filter(isChecked);
|
||||||
|
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
|
||||||
|
return [prefix, message, answer].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
const keys = [
|
||||||
|
['↑↓', 'navigate'],
|
||||||
|
['space', 'select'],
|
||||||
|
];
|
||||||
|
if (shortcuts.all)
|
||||||
|
keys.push([shortcuts.all, 'all']);
|
||||||
|
if (shortcuts.invert)
|
||||||
|
keys.push([shortcuts.invert, 'invert']);
|
||||||
|
keys.push(['⏎', 'submit']);
|
||||||
|
const helpLine = theme.style.keysHelpTip(keys);
|
||||||
|
const lines = [
|
||||||
|
[prefix, message].filter(Boolean).join(' '),
|
||||||
|
page,
|
||||||
|
' ',
|
||||||
|
description ? theme.style.description(description) : '',
|
||||||
|
errorMsg ? theme.style.error(errorMsg) : '',
|
||||||
|
helpLine,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n')
|
||||||
|
.trimEnd();
|
||||||
|
return `${lines}${cursorHide}`;
|
||||||
|
});
|
||||||
|
export { Separator } from '@inquirer/core';
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"name": "@inquirer/checkbox",
|
||||||
|
"version": "5.0.4",
|
||||||
|
"description": "Inquirer checkbox prompt",
|
||||||
|
"keywords": [
|
||||||
|
"answer",
|
||||||
|
"answers",
|
||||||
|
"ask",
|
||||||
|
"base",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command-line",
|
||||||
|
"confirm",
|
||||||
|
"enquirer",
|
||||||
|
"generate",
|
||||||
|
"generator",
|
||||||
|
"hyper",
|
||||||
|
"input",
|
||||||
|
"inquire",
|
||||||
|
"inquirer",
|
||||||
|
"interface",
|
||||||
|
"iterm",
|
||||||
|
"javascript",
|
||||||
|
"menu",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"prompt",
|
||||||
|
"promptly",
|
||||||
|
"prompts",
|
||||||
|
"question",
|
||||||
|
"readline",
|
||||||
|
"scaffold",
|
||||||
|
"scaffolder",
|
||||||
|
"scaffolding",
|
||||||
|
"stdin",
|
||||||
|
"stdout",
|
||||||
|
"terminal",
|
||||||
|
"tty",
|
||||||
|
"ui",
|
||||||
|
"yeoman",
|
||||||
|
"yo",
|
||||||
|
"zsh"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Simon Boudrias <admin@simonboudrias.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SBoudrias/Inquirer.js.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.3",
|
||||||
|
"@inquirer/core": "^11.1.1",
|
||||||
|
"@inquirer/figures": "^2.0.3",
|
||||||
|
"@inquirer/type": "^4.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@inquirer/testing": "^3.0.4",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"gitHead": "99d00a9adc53be8b7edf5926b2ec4ba0b792f68f"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
# `@inquirer/confirm`
|
||||||
|
|
||||||
|
Simple interactive command line prompt to gather boolean input from users.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" align="center">Or</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/confirm
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { confirm } from '@inquirer/prompts';
|
||||||
|
// Or
|
||||||
|
// import confirm from '@inquirer/confirm';
|
||||||
|
|
||||||
|
const answer = await confirm({ message: 'Continue?' });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Property | Type | Required | Description |
|
||||||
|
| ----------- | ----------------------- | -------- | ------------------------------------------------------- |
|
||||||
|
| message | `string` | yes | The question to ask |
|
||||||
|
| default | `boolean` | no | Default answer (true or false) |
|
||||||
|
| transformer | `(boolean) => string` | no | Transform the prompt printed message to a custom string |
|
||||||
|
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Theme = {
|
||||||
|
prefix: string | { idle: string; done: string };
|
||||||
|
spinner: {
|
||||||
|
interval: number;
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
answer: (text: string) => string;
|
||||||
|
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
|
||||||
|
defaultAnswer: (text: string) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { type Theme } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
type ConfirmConfig = {
|
||||||
|
message: string;
|
||||||
|
default?: boolean;
|
||||||
|
transformer?: (value: boolean) => string;
|
||||||
|
theme?: PartialDeep<Theme>;
|
||||||
|
};
|
||||||
|
declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>;
|
||||||
|
export default _default;
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import { createPrompt, useState, useKeypress, isEnterKey, isTabKey, usePrefix, makeTheme, } from '@inquirer/core';
|
||||||
|
function getBooleanValue(value, defaultValue) {
|
||||||
|
let answer = defaultValue !== false;
|
||||||
|
if (/^(y|yes)/i.test(value))
|
||||||
|
answer = true;
|
||||||
|
else if (/^(n|no)/i.test(value))
|
||||||
|
answer = false;
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
function boolToString(value) {
|
||||||
|
return value ? 'Yes' : 'No';
|
||||||
|
}
|
||||||
|
export default createPrompt((config, done) => {
|
||||||
|
const { transformer = boolToString } = config;
|
||||||
|
const [status, setStatus] = useState('idle');
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const theme = makeTheme(config.theme);
|
||||||
|
const prefix = usePrefix({ status, theme });
|
||||||
|
useKeypress((key, rl) => {
|
||||||
|
if (status !== 'idle')
|
||||||
|
return;
|
||||||
|
if (isEnterKey(key)) {
|
||||||
|
const answer = getBooleanValue(value, config.default);
|
||||||
|
setValue(transformer(answer));
|
||||||
|
setStatus('done');
|
||||||
|
done(answer);
|
||||||
|
}
|
||||||
|
else if (isTabKey(key)) {
|
||||||
|
const answer = boolToString(!getBooleanValue(value, config.default));
|
||||||
|
rl.clearLine(0); // Remove the tab character.
|
||||||
|
rl.write(answer);
|
||||||
|
setValue(answer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue(rl.line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let formattedValue = value;
|
||||||
|
let defaultValue = '';
|
||||||
|
if (status === 'done') {
|
||||||
|
formattedValue = theme.style.answer(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`;
|
||||||
|
}
|
||||||
|
const message = theme.style.message(config.message, status);
|
||||||
|
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
|
||||||
|
});
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"name": "@inquirer/confirm",
|
||||||
|
"version": "6.0.4",
|
||||||
|
"description": "Inquirer confirm prompt",
|
||||||
|
"keywords": [
|
||||||
|
"answer",
|
||||||
|
"answers",
|
||||||
|
"ask",
|
||||||
|
"base",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command-line",
|
||||||
|
"confirm",
|
||||||
|
"enquirer",
|
||||||
|
"generate",
|
||||||
|
"generator",
|
||||||
|
"hyper",
|
||||||
|
"input",
|
||||||
|
"inquire",
|
||||||
|
"inquirer",
|
||||||
|
"interface",
|
||||||
|
"iterm",
|
||||||
|
"javascript",
|
||||||
|
"menu",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"prompt",
|
||||||
|
"promptly",
|
||||||
|
"prompts",
|
||||||
|
"question",
|
||||||
|
"readline",
|
||||||
|
"scaffold",
|
||||||
|
"scaffolder",
|
||||||
|
"scaffolding",
|
||||||
|
"stdin",
|
||||||
|
"stdout",
|
||||||
|
"terminal",
|
||||||
|
"tty",
|
||||||
|
"ui",
|
||||||
|
"yeoman",
|
||||||
|
"yo",
|
||||||
|
"zsh"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Simon Boudrias <admin@simonboudrias.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SBoudrias/Inquirer.js.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.1.1",
|
||||||
|
"@inquirer/type": "^4.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@inquirer/testing": "^3.0.4",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"gitHead": "99d00a9adc53be8b7edf5926b2ec4ba0b792f68f"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+383
@@ -0,0 +1,383 @@
|
|||||||
|
# `@inquirer/core`
|
||||||
|
|
||||||
|
The `@inquirer/core` package is the library enabling the creation of Inquirer prompts.
|
||||||
|
|
||||||
|
It aims to implements a lightweight API similar to React hooks - but without JSX.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/core
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/core
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Basic concept
|
||||||
|
|
||||||
|
Visual terminal apps are at their core strings rendered onto the terminal.
|
||||||
|
|
||||||
|
The most basic prompt is a function returning a string that'll be rendered in the terminal. This function will run every time the prompt state change, and the new returned string will replace the previously rendered one. The prompt cursor appears after the string.
|
||||||
|
|
||||||
|
Wrapping the rendering function with `createPrompt()` will setup the rendering layer, inject the state management utilities, and wait until the `done` callback is called.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createPrompt } from '@inquirer/core';
|
||||||
|
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
// Implement logic
|
||||||
|
|
||||||
|
return '? My question';
|
||||||
|
});
|
||||||
|
|
||||||
|
// And it is then called as
|
||||||
|
const answer = await input({
|
||||||
|
/* config */
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
State management and user interactions are handled through hooks. Hooks are common [within the React ecosystem](https://react.dev/reference/react/hooks), and Inquirer reimplement the common ones.
|
||||||
|
|
||||||
|
### State hook
|
||||||
|
|
||||||
|
State lets a component “remember” information like user input. For example, an input prompt can use state to store the input value, while a list prompt can use state to track the cursor index.
|
||||||
|
|
||||||
|
`useState` declares a state variable that you can update directly.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createPrompt, useState } from '@inquirer/core';
|
||||||
|
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keypress hook
|
||||||
|
|
||||||
|
Almost all prompts need to react to user actions. In a terminal, this is done through typing.
|
||||||
|
|
||||||
|
`useKeypress` allows you to react to keypress events, and access the prompt line.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
useKeypress((key) => {
|
||||||
|
if (key.name === 'enter') {
|
||||||
|
done(answer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Behind the scenes, Inquirer prompts are wrappers around [readlines](https://nodejs.org/api/readline.html). Aside the keypress event object, the hook also pass the active readline instance to the event handler.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
useKeypress((key, readline) => {
|
||||||
|
setValue(readline.line);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ref hook
|
||||||
|
|
||||||
|
Refs let a prompt hold some information that isn’t used for rendering, like a class instance or a timeout ID. Unlike with state, updating a ref does not re-render your prompt. Refs are an “escape hatch” from the rendering paradigm.
|
||||||
|
|
||||||
|
`useRef` declares a ref. You can hold any value in it, but most often it’s used to hold a timeout ID.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
const timeout = useRef(null);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Effect Hook
|
||||||
|
|
||||||
|
Effects let a prompt connect to and synchronize with external systems. This includes dealing with network or animations.
|
||||||
|
|
||||||
|
`useEffect` connects a component to an external system.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const chat = createPrompt((config, done) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const connection = createConnection(roomId);
|
||||||
|
connection.connect();
|
||||||
|
return () => connection.disconnect();
|
||||||
|
}, [roomId]);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance hook
|
||||||
|
|
||||||
|
A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell Inquirer to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render.
|
||||||
|
|
||||||
|
`useMemo` lets you cache the result of an expensive calculation.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const todoSelect = createPrompt((config, done) => {
|
||||||
|
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rendering hooks
|
||||||
|
|
||||||
|
#### Prefix / loading
|
||||||
|
|
||||||
|
All default prompts, and most custom ones, uses a prefix at the beginning of the prompt line. This helps visually delineate different questions, and provides a convenient area to render a loading spinner.
|
||||||
|
|
||||||
|
`usePrefix` is a built-in hook to do this.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
const prefix = usePrefix({ status });
|
||||||
|
|
||||||
|
return `${prefix} My question`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pagination
|
||||||
|
|
||||||
|
When looping through a long list of options (like in the `select` prompt), paginating the results appearing on the screen at once can be necessary. The `usePagination` hook is the utility used within the `select` and `checkbox` prompts to cycle through the list of options.
|
||||||
|
|
||||||
|
Pagination works by taking in the list of options and returning a subset of the rendered items that fit within the page. The hook takes in a few options. It needs a list of options (`items`), and a `pageSize` which is the number of lines to be rendered. The `active` index is the index of the currently selected/selectable item. The `loop` option is a boolean that indicates if the list should loop around when reaching the end: this is the default behavior. The pagination hook renders items only as necessary, so it takes a function that can render an item at an index, including an `active` state, called `renderItem`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default createPrompt((config, done) => {
|
||||||
|
const [active, setActive] = useState(0);
|
||||||
|
|
||||||
|
const allChoices = config.choices.map((choice) => choice.name);
|
||||||
|
|
||||||
|
const page = usePagination({
|
||||||
|
items: allChoices,
|
||||||
|
active: active,
|
||||||
|
renderItem: ({ item, index, isActive }) => `${isActive ? ">" : " "}${index}. ${item.toString()}`
|
||||||
|
pageSize: config.pageSize,
|
||||||
|
loop: config.loop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `... ${page}`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## `createPrompt()` API
|
||||||
|
|
||||||
|
As we saw earlier, the rendering function should return a string, and eventually call `done` to close the prompt and return the answer.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const input = createPrompt((config, done) => {
|
||||||
|
const [value, setValue] = useState();
|
||||||
|
|
||||||
|
useKeypress((key, readline) => {
|
||||||
|
if (key.name === 'enter') {
|
||||||
|
done(answer);
|
||||||
|
} else {
|
||||||
|
setValue(readline.line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return `? ${config.message} ${value}`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The rendering function can also return a tuple of 2 string (`[string, string]`.) The first string represents the prompt. The second one is content to render under the prompt, like an error message. The text input cursor will appear after the first string.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const number = createPrompt((config, done) => {
|
||||||
|
// Add some logic here
|
||||||
|
|
||||||
|
return [`? My question ${input}`, `! The input must be a number`];
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typescript
|
||||||
|
|
||||||
|
If using typescript, `createPrompt` takes 2 generic arguments.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// createPrompt<Value, Config>
|
||||||
|
const input = createPrompt<string, { message: string }>(// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The first one is the type of the resolved value
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const answer: string = await input();
|
||||||
|
```
|
||||||
|
|
||||||
|
The second one is the type of the prompt config; in other words the interface the created prompt will provide to users.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const answer = await input({
|
||||||
|
message: 'My question',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key utilities
|
||||||
|
|
||||||
|
Listening for keypress events inside an inquirer prompt is a very common pattern. To ease this, we export a few utility functions taking in the keypress event object and return a boolean:
|
||||||
|
|
||||||
|
- `isEnterKey()`
|
||||||
|
- `isBackspaceKey()`
|
||||||
|
- `isSpaceKey()`
|
||||||
|
- `isUpKey()` - Note: this utility will handle vim and emacs keybindings (up, `k`, and `ctrl+p`)
|
||||||
|
- `isDownKey()` - Note: this utility will handle vim and emacs keybindings (down, `j`, and `ctrl+n`)
|
||||||
|
- `isNumberKey()` one of 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
Theming utilities will allow you to expose customization of the prompt style. Inquirer also has a few standard theme values shared across all the official prompts.
|
||||||
|
|
||||||
|
To allow standard customization:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createPrompt, usePrefix, makeTheme, type Theme } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
|
||||||
|
type PromptConfig = {
|
||||||
|
theme?: PartialDeep<Theme>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createPrompt<string, PromptConfig>((config, done) => {
|
||||||
|
const theme = makeTheme(config.theme);
|
||||||
|
|
||||||
|
const prefix = usePrefix({ status, theme });
|
||||||
|
|
||||||
|
return `${prefix} ${theme.style.highlight('hello')}`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To setup a custom theme:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createPrompt, makeTheme, type Theme } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
|
||||||
|
type PromptTheme = {};
|
||||||
|
|
||||||
|
const promptTheme: PromptTheme = {
|
||||||
|
icon: '!',
|
||||||
|
};
|
||||||
|
|
||||||
|
type PromptConfig = {
|
||||||
|
theme?: PartialDeep<Theme<PromptTheme>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createPrompt<string, PromptConfig>((config, done) => {
|
||||||
|
const theme = makeTheme(promptTheme, config.theme);
|
||||||
|
|
||||||
|
const prefix = usePrefix({ status, theme });
|
||||||
|
|
||||||
|
return `${prefix} ${theme.icon}`;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The [default theme keys cover](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/core/src/lib/theme.ts):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type DefaultTheme = {
|
||||||
|
prefix: string | { idle: string; done: string };
|
||||||
|
spinner: {
|
||||||
|
interval: number;
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
answer: (text: string) => string;
|
||||||
|
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
|
||||||
|
error: (text: string) => string;
|
||||||
|
defaultAnswer: (text: string) => string;
|
||||||
|
help: (text: string) => string;
|
||||||
|
highlight: (text: string) => string;
|
||||||
|
key: (text: string) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
You can refer to any `@inquirer/prompts` prompts for real examples:
|
||||||
|
|
||||||
|
- [Confirm Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/src/index.ts)
|
||||||
|
- [Input Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/src/index.ts)
|
||||||
|
- [Password Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/password/src/index.ts)
|
||||||
|
- [Editor Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/src/index.ts)
|
||||||
|
- [Select Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/select/src/index.ts)
|
||||||
|
- [Checkbox Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/src/index.ts)
|
||||||
|
- [Rawlist Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/rawlist/src/index.ts)
|
||||||
|
- [Expand Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/expand/src/index.ts)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { styleText } from 'node:util';
|
||||||
|
import {
|
||||||
|
createPrompt,
|
||||||
|
useState,
|
||||||
|
useKeypress,
|
||||||
|
isEnterKey,
|
||||||
|
usePrefix,
|
||||||
|
type Status,
|
||||||
|
} from '@inquirer/core';
|
||||||
|
|
||||||
|
const confirm = createPrompt<boolean, { message: string; default?: boolean }>(
|
||||||
|
(config, done) => {
|
||||||
|
const [status, setStatus] = useState<Status>('idle');
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const prefix = usePrefix({});
|
||||||
|
|
||||||
|
useKeypress((key, rl) => {
|
||||||
|
if (isEnterKey(key)) {
|
||||||
|
const answer = value ? /^y(es)?/i.test(value) : config.default !== false;
|
||||||
|
setValue(answer ? 'yes' : 'no');
|
||||||
|
setStatus('done');
|
||||||
|
done(answer);
|
||||||
|
} else {
|
||||||
|
setValue(rl.line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let formattedValue = value;
|
||||||
|
let defaultValue = '';
|
||||||
|
if (status === 'done') {
|
||||||
|
formattedValue = styleText('cyan', value);
|
||||||
|
} else {
|
||||||
|
defaultValue = styleText('dim', config.default === false ? ' (y/N)' : ' (Y/n)');
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = styleText('bold', config.message);
|
||||||
|
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which then can be used like this:
|
||||||
|
*/
|
||||||
|
const answer = await confirm({ message: 'Do you want to continue?' });
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
export { isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isTabKey, isNumberKey, isEnterKey, isShiftKey, type KeypressEvent, type Keybinding, } from './lib/key.ts';
|
||||||
|
export * from './lib/errors.ts';
|
||||||
|
export { usePrefix } from './lib/use-prefix.ts';
|
||||||
|
export { useState } from './lib/use-state.ts';
|
||||||
|
export { useEffect } from './lib/use-effect.ts';
|
||||||
|
export { useMemo } from './lib/use-memo.ts';
|
||||||
|
export { useRef } from './lib/use-ref.ts';
|
||||||
|
export { useKeypress } from './lib/use-keypress.ts';
|
||||||
|
export { makeTheme } from './lib/make-theme.ts';
|
||||||
|
export type { Theme, Status } from './lib/theme.ts';
|
||||||
|
export { usePagination } from './lib/pagination/use-pagination.ts';
|
||||||
|
export { createPrompt } from './lib/create-prompt.ts';
|
||||||
|
export { Separator } from './lib/Separator.ts';
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
export { isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isTabKey, isNumberKey, isEnterKey, isShiftKey, } from "./lib/key.js";
|
||||||
|
export * from "./lib/errors.js";
|
||||||
|
export { usePrefix } from "./lib/use-prefix.js";
|
||||||
|
export { useState } from "./lib/use-state.js";
|
||||||
|
export { useEffect } from "./lib/use-effect.js";
|
||||||
|
export { useMemo } from "./lib/use-memo.js";
|
||||||
|
export { useRef } from "./lib/use-ref.js";
|
||||||
|
export { useKeypress } from "./lib/use-keypress.js";
|
||||||
|
export { makeTheme } from "./lib/make-theme.js";
|
||||||
|
export { usePagination } from "./lib/pagination/use-pagination.js";
|
||||||
|
export { createPrompt } from "./lib/create-prompt.js";
|
||||||
|
export { Separator } from "./lib/Separator.js";
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Separator object
|
||||||
|
* Used to space/separate choices group
|
||||||
|
*/
|
||||||
|
export declare class Separator {
|
||||||
|
readonly separator: string;
|
||||||
|
readonly type: string;
|
||||||
|
constructor(separator?: string);
|
||||||
|
static isSeparator(choice: unknown): choice is Separator;
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { styleText } from 'node:util';
|
||||||
|
import figures from '@inquirer/figures';
|
||||||
|
/**
|
||||||
|
* Separator object
|
||||||
|
* Used to space/separate choices group
|
||||||
|
*/
|
||||||
|
export class Separator {
|
||||||
|
separator = styleText('dim', Array.from({ length: 15 }).join(figures.line));
|
||||||
|
type = 'separator';
|
||||||
|
constructor(separator) {
|
||||||
|
if (separator) {
|
||||||
|
this.separator = separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static isSeparator(choice) {
|
||||||
|
return Boolean(choice &&
|
||||||
|
typeof choice === 'object' &&
|
||||||
|
'type' in choice &&
|
||||||
|
choice.type === 'separator');
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
import { type Prompt, type Prettify } from '@inquirer/type';
|
||||||
|
type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined];
|
||||||
|
export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
|
||||||
|
export {};
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
import * as readline from 'node:readline';
|
||||||
|
import { AsyncResource } from 'node:async_hooks';
|
||||||
|
import MuteStream from 'mute-stream';
|
||||||
|
import { onExit as onSignalExit } from 'signal-exit';
|
||||||
|
import ScreenManager from "./screen-manager.js";
|
||||||
|
import { PromisePolyfill } from "./promise-polyfill.js";
|
||||||
|
import { withHooks, effectScheduler } from "./hook-engine.js";
|
||||||
|
import { AbortPromptError, CancelPromptError, ExitPromptError } from "./errors.js";
|
||||||
|
function getCallSites() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
const _prepareStackTrace = Error.prepareStackTrace;
|
||||||
|
let result = [];
|
||||||
|
try {
|
||||||
|
Error.prepareStackTrace = (_, callSites) => {
|
||||||
|
const callSitesWithoutCurrent = callSites.slice(1);
|
||||||
|
result = callSitesWithoutCurrent;
|
||||||
|
return callSitesWithoutCurrent;
|
||||||
|
};
|
||||||
|
// oxlint-disable-next-line no-unused-expressions
|
||||||
|
new Error().stack;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// An error will occur if the Node flag --frozen-intrinsics is used.
|
||||||
|
// https://nodejs.org/api/cli.html#--frozen-intrinsics
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Error.prepareStackTrace = _prepareStackTrace;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
export function createPrompt(view) {
|
||||||
|
const callSites = getCallSites();
|
||||||
|
const prompt = (config, context = {}) => {
|
||||||
|
// Default `input` to stdin
|
||||||
|
const { input = process.stdin, signal } = context;
|
||||||
|
const cleanups = new Set();
|
||||||
|
// Add mute capabilities to the output
|
||||||
|
const output = new MuteStream();
|
||||||
|
output.pipe(context.output ?? process.stdout);
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
terminal: true,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
});
|
||||||
|
const screen = new ScreenManager(rl);
|
||||||
|
const { promise, resolve, reject } = PromisePolyfill.withResolver();
|
||||||
|
const cancel = () => reject(new CancelPromptError());
|
||||||
|
if (signal) {
|
||||||
|
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
|
||||||
|
if (signal.aborted) {
|
||||||
|
abort();
|
||||||
|
return Object.assign(promise, { cancel });
|
||||||
|
}
|
||||||
|
signal.addEventListener('abort', abort);
|
||||||
|
cleanups.add(() => signal.removeEventListener('abort', abort));
|
||||||
|
}
|
||||||
|
cleanups.add(onSignalExit((code, signal) => {
|
||||||
|
reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
|
||||||
|
}));
|
||||||
|
// SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
|
||||||
|
// Otherwise, the prompt will stop and in some scenarios never resolve.
|
||||||
|
// Ref issue #1741
|
||||||
|
const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
|
||||||
|
rl.on('SIGINT', sigint);
|
||||||
|
cleanups.add(() => rl.removeListener('SIGINT', sigint));
|
||||||
|
// Re-renders only happen when the state change; but the readline cursor could change position
|
||||||
|
// and that also requires a re-render (and a manual one because we mute the streams).
|
||||||
|
// We set the listener after the initial workLoop to avoid a double render if render triggered
|
||||||
|
// by a state change sets the cursor to the right position.
|
||||||
|
const checkCursorPos = () => screen.checkCursorPos();
|
||||||
|
rl.input.on('keypress', checkCursorPos);
|
||||||
|
cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos));
|
||||||
|
return withHooks(rl, (cycle) => {
|
||||||
|
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
|
||||||
|
// triggers after the process is done (which happens after timeouts are done triggering.)
|
||||||
|
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
|
||||||
|
const hooksCleanup = AsyncResource.bind(() => effectScheduler.clearAll());
|
||||||
|
rl.on('close', hooksCleanup);
|
||||||
|
cleanups.add(() => rl.removeListener('close', hooksCleanup));
|
||||||
|
cycle(() => {
|
||||||
|
try {
|
||||||
|
const nextView = view(config, (value) => {
|
||||||
|
setImmediate(() => resolve(value));
|
||||||
|
});
|
||||||
|
// Typescript won't allow this, but not all users rely on typescript.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (nextView === undefined) {
|
||||||
|
const callerFilename = callSites[1]?.getFileName();
|
||||||
|
throw new Error(`Prompt functions must return a string.\n at ${callerFilename}`);
|
||||||
|
}
|
||||||
|
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
|
||||||
|
screen.render(content, bottomContent);
|
||||||
|
effectScheduler.run();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.assign(promise
|
||||||
|
.then((answer) => {
|
||||||
|
effectScheduler.clearAll();
|
||||||
|
return answer;
|
||||||
|
}, (error) => {
|
||||||
|
effectScheduler.clearAll();
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
// Wait for the promise to settle, then cleanup.
|
||||||
|
.finally(() => {
|
||||||
|
cleanups.forEach((cleanup) => cleanup());
|
||||||
|
screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
|
||||||
|
output.end();
|
||||||
|
})
|
||||||
|
// Once cleanup is done, let the expose promise resolve/reject to the internal one.
|
||||||
|
.then(() => promise), { cancel });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
export declare class AbortPromptError extends Error {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
constructor(options?: {
|
||||||
|
cause?: unknown;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export declare class CancelPromptError extends Error {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
export declare class ExitPromptError extends Error {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
export declare class HookError extends Error {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
export declare class ValidationError extends Error {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
export class AbortPromptError extends Error {
|
||||||
|
name = 'AbortPromptError';
|
||||||
|
message = 'Prompt was aborted';
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
this.cause = options?.cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class CancelPromptError extends Error {
|
||||||
|
name = 'CancelPromptError';
|
||||||
|
message = 'Prompt was canceled';
|
||||||
|
}
|
||||||
|
export class ExitPromptError extends Error {
|
||||||
|
name = 'ExitPromptError';
|
||||||
|
}
|
||||||
|
export class HookError extends Error {
|
||||||
|
name = 'HookError';
|
||||||
|
}
|
||||||
|
export class ValidationError extends Error {
|
||||||
|
name = 'ValidationError';
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import type { InquirerReadline } from '@inquirer/type';
|
||||||
|
export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T;
|
||||||
|
export declare function readline(): InquirerReadline;
|
||||||
|
export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
|
||||||
|
type SetPointer<Value> = {
|
||||||
|
get(): Value;
|
||||||
|
set(value: Value): void;
|
||||||
|
initialized: true;
|
||||||
|
};
|
||||||
|
type UnsetPointer<Value> = {
|
||||||
|
get(): void;
|
||||||
|
set(value: Value): void;
|
||||||
|
initialized: false;
|
||||||
|
};
|
||||||
|
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
|
||||||
|
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
|
||||||
|
export declare function handleChange(): void;
|
||||||
|
export declare const effectScheduler: {
|
||||||
|
queue(cb: (readline: InquirerReadline) => void | (() => void)): void;
|
||||||
|
run(): void;
|
||||||
|
clearAll(): void;
|
||||||
|
};
|
||||||
|
export {};
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
|
||||||
|
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
|
||||||
|
import { HookError, ValidationError } from "./errors.js";
|
||||||
|
const hookStorage = new AsyncLocalStorage();
|
||||||
|
function createStore(rl) {
|
||||||
|
const store = {
|
||||||
|
rl,
|
||||||
|
hooks: [],
|
||||||
|
hooksCleanup: [],
|
||||||
|
hooksEffect: [],
|
||||||
|
index: 0,
|
||||||
|
handleChange() { },
|
||||||
|
};
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
// Run callback in with the hook engine setup.
|
||||||
|
export function withHooks(rl, cb) {
|
||||||
|
const store = createStore(rl);
|
||||||
|
return hookStorage.run(store, () => {
|
||||||
|
function cycle(render) {
|
||||||
|
store.handleChange = () => {
|
||||||
|
store.index = 0;
|
||||||
|
render();
|
||||||
|
};
|
||||||
|
store.handleChange();
|
||||||
|
}
|
||||||
|
return cb(cycle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Safe getStore utility that'll return the store or throw if undefined.
|
||||||
|
function getStore() {
|
||||||
|
const store = hookStorage.getStore();
|
||||||
|
if (!store) {
|
||||||
|
throw new HookError('[Inquirer] Hook functions can only be called from within a prompt');
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
export function readline() {
|
||||||
|
return getStore().rl;
|
||||||
|
}
|
||||||
|
// Merge state updates happening within the callback function to avoid multiple renders.
|
||||||
|
export function withUpdates(fn) {
|
||||||
|
const wrapped = (...args) => {
|
||||||
|
const store = getStore();
|
||||||
|
let shouldUpdate = false;
|
||||||
|
const oldHandleChange = store.handleChange;
|
||||||
|
store.handleChange = () => {
|
||||||
|
shouldUpdate = true;
|
||||||
|
};
|
||||||
|
const returnValue = fn(...args);
|
||||||
|
if (shouldUpdate) {
|
||||||
|
oldHandleChange();
|
||||||
|
}
|
||||||
|
store.handleChange = oldHandleChange;
|
||||||
|
return returnValue;
|
||||||
|
};
|
||||||
|
return AsyncResource.bind(wrapped);
|
||||||
|
}
|
||||||
|
export function withPointer(cb) {
|
||||||
|
const store = getStore();
|
||||||
|
const { index } = store;
|
||||||
|
const pointer = {
|
||||||
|
get() {
|
||||||
|
return store.hooks[index];
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
store.hooks[index] = value;
|
||||||
|
},
|
||||||
|
initialized: index in store.hooks,
|
||||||
|
};
|
||||||
|
const returnValue = cb(pointer);
|
||||||
|
store.index++;
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
export function handleChange() {
|
||||||
|
getStore().handleChange();
|
||||||
|
}
|
||||||
|
export const effectScheduler = {
|
||||||
|
queue(cb) {
|
||||||
|
const store = getStore();
|
||||||
|
const { index } = store;
|
||||||
|
store.hooksEffect.push(() => {
|
||||||
|
store.hooksCleanup[index]?.();
|
||||||
|
const cleanFn = cb(readline());
|
||||||
|
if (cleanFn != null && typeof cleanFn !== 'function') {
|
||||||
|
throw new ValidationError('useEffect return value must be a cleanup function or nothing.');
|
||||||
|
}
|
||||||
|
store.hooksCleanup[index] = cleanFn;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
run() {
|
||||||
|
const store = getStore();
|
||||||
|
withUpdates(() => {
|
||||||
|
store.hooksEffect.forEach((effect) => {
|
||||||
|
effect();
|
||||||
|
});
|
||||||
|
// Warning: Clean the hooks before exiting the `withUpdates` block.
|
||||||
|
// Failure to do so means an updates would hit the same effects again.
|
||||||
|
store.hooksEffect.length = 0;
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
clearAll() {
|
||||||
|
const store = getStore();
|
||||||
|
store.hooksCleanup.forEach((cleanFn) => {
|
||||||
|
cleanFn?.();
|
||||||
|
});
|
||||||
|
store.hooksEffect.length = 0;
|
||||||
|
store.hooksCleanup.length = 0;
|
||||||
|
},
|
||||||
|
};
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
export type KeypressEvent = {
|
||||||
|
name: string;
|
||||||
|
ctrl: boolean;
|
||||||
|
shift: boolean;
|
||||||
|
};
|
||||||
|
export type Keybinding = 'emacs' | 'vim';
|
||||||
|
export declare const isUpKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
|
||||||
|
export declare const isDownKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
|
||||||
|
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
|
||||||
|
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
|
||||||
|
export declare const isTabKey: (key: KeypressEvent) => boolean;
|
||||||
|
export declare const isNumberKey: (key: KeypressEvent) => boolean;
|
||||||
|
export declare const isEnterKey: (key: KeypressEvent) => boolean;
|
||||||
|
export declare const isShiftKey: (key: KeypressEvent) => boolean;
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
export const isUpKey = (key, keybindings = []) =>
|
||||||
|
// The up key
|
||||||
|
key.name === 'up' ||
|
||||||
|
// Vim keybinding: hjkl keys map to left/down/up/right
|
||||||
|
(keybindings.includes('vim') && key.name === 'k') ||
|
||||||
|
// Emacs keybinding: Ctrl+P means "previous" in Emacs navigation conventions
|
||||||
|
(keybindings.includes('emacs') && key.ctrl && key.name === 'p');
|
||||||
|
export const isDownKey = (key, keybindings = []) =>
|
||||||
|
// The down key
|
||||||
|
key.name === 'down' ||
|
||||||
|
// Vim keybinding: hjkl keys map to left/down/up/right
|
||||||
|
(keybindings.includes('vim') && key.name === 'j') ||
|
||||||
|
// Emacs keybinding: Ctrl+N means "next" in Emacs navigation conventions
|
||||||
|
(keybindings.includes('emacs') && key.ctrl && key.name === 'n');
|
||||||
|
export const isSpaceKey = (key) => key.name === 'space';
|
||||||
|
export const isBackspaceKey = (key) => key.name === 'backspace';
|
||||||
|
export const isTabKey = (key) => key.name === 'tab';
|
||||||
|
export const isNumberKey = (key) => '1234567890'.includes(key.name);
|
||||||
|
export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
|
||||||
|
export const isShiftKey = (key) => key.shift;
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import type { Prettify, PartialDeep } from '@inquirer/type';
|
||||||
|
import { type Theme } from './theme.ts';
|
||||||
|
export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>;
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
import { defaultTheme } from "./theme.js";
|
||||||
|
function isPlainObject(value) {
|
||||||
|
if (typeof value !== 'object' || value === null)
|
||||||
|
return false;
|
||||||
|
let proto = value;
|
||||||
|
while (Object.getPrototypeOf(proto) !== null) {
|
||||||
|
proto = Object.getPrototypeOf(proto);
|
||||||
|
}
|
||||||
|
return Object.getPrototypeOf(value) === proto;
|
||||||
|
}
|
||||||
|
function deepMerge(...objects) {
|
||||||
|
const output = {};
|
||||||
|
for (const obj of objects) {
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
const prevValue = output[key];
|
||||||
|
output[key] =
|
||||||
|
isPlainObject(prevValue) && isPlainObject(value)
|
||||||
|
? deepMerge(prevValue, value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
export function makeTheme(...themes) {
|
||||||
|
const themesToMerge = [
|
||||||
|
defaultTheme,
|
||||||
|
...themes.filter((theme) => theme != null),
|
||||||
|
];
|
||||||
|
return deepMerge(...themesToMerge);
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import type { Prettify } from '@inquirer/type';
|
||||||
|
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
|
||||||
|
items: ReadonlyArray<T>;
|
||||||
|
/** The index of the active item. */
|
||||||
|
active: number;
|
||||||
|
/** Renders an item as part of a page. */
|
||||||
|
renderItem: (layout: Prettify<{
|
||||||
|
item: T;
|
||||||
|
index: number;
|
||||||
|
isActive: boolean;
|
||||||
|
}>) => string;
|
||||||
|
/** The size of the page. */
|
||||||
|
pageSize: number;
|
||||||
|
/** Allows creating an infinitely looping list. `true` if unspecified. */
|
||||||
|
loop?: boolean;
|
||||||
|
}): string;
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
import { useRef } from "../use-ref.js";
|
||||||
|
import { readlineWidth, breakLines } from "../utils.js";
|
||||||
|
function usePointerPosition({ active, renderedItems, pageSize, loop, }) {
|
||||||
|
const state = useRef({
|
||||||
|
lastPointer: active,
|
||||||
|
lastActive: undefined,
|
||||||
|
});
|
||||||
|
const { lastPointer, lastActive } = state.current;
|
||||||
|
const middle = Math.floor(pageSize / 2);
|
||||||
|
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
|
||||||
|
const defaultPointerPosition = renderedItems
|
||||||
|
.slice(0, active)
|
||||||
|
.reduce((acc, item) => acc + item.length, 0);
|
||||||
|
let pointer = defaultPointerPosition;
|
||||||
|
if (renderedLength > pageSize) {
|
||||||
|
if (loop) {
|
||||||
|
/**
|
||||||
|
* Creates the next position for the pointer considering an infinitely
|
||||||
|
* looping list of items to be rendered on the page.
|
||||||
|
*
|
||||||
|
* The goal is to progressively move the cursor to the middle position as the user move down, and then keep
|
||||||
|
* the cursor there. When the user move up, maintain the cursor position.
|
||||||
|
*/
|
||||||
|
// By default, keep the cursor position as-is.
|
||||||
|
pointer = lastPointer;
|
||||||
|
if (
|
||||||
|
// First render, skip this logic.
|
||||||
|
lastActive != null &&
|
||||||
|
// Only move the pointer down when the user moves down.
|
||||||
|
lastActive < active &&
|
||||||
|
// Check user didn't move up across page boundary.
|
||||||
|
active - lastActive < pageSize) {
|
||||||
|
pointer = Math.min(
|
||||||
|
// Furthest allowed position for the pointer is the middle of the list
|
||||||
|
middle, Math.abs(active - lastActive) === 1
|
||||||
|
? Math.min(
|
||||||
|
// Move the pointer at most the height of the last active item.
|
||||||
|
lastPointer + (renderedItems[lastActive]?.length ?? 0),
|
||||||
|
// If the user moved by one item, move the pointer to the natural position of the active item as
|
||||||
|
// long as it doesn't move the cursor up.
|
||||||
|
Math.max(defaultPointerPosition, lastPointer))
|
||||||
|
: // Otherwise, move the pointer down by the difference between the active and last active item.
|
||||||
|
lastPointer + active - lastActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/**
|
||||||
|
* Creates the next position for the pointer considering a finite list of
|
||||||
|
* items to be rendered on a page.
|
||||||
|
*
|
||||||
|
* The goal is to keep the pointer in the middle of the page whenever possible, until
|
||||||
|
* we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively
|
||||||
|
* to the bottom or top of the list.
|
||||||
|
*/
|
||||||
|
const spaceUnderActive = renderedItems
|
||||||
|
.slice(active)
|
||||||
|
.reduce((acc, item) => acc + item.length, 0);
|
||||||
|
pointer =
|
||||||
|
spaceUnderActive < pageSize - middle
|
||||||
|
? // If the active item is near the end of the list, progressively move the cursor towards the end.
|
||||||
|
pageSize - spaceUnderActive
|
||||||
|
: // Otherwise, progressively move the pointer to the middle of the list.
|
||||||
|
Math.min(defaultPointerPosition, middle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save state for the next render
|
||||||
|
state.current.lastPointer = pointer;
|
||||||
|
state.current.lastActive = active;
|
||||||
|
return pointer;
|
||||||
|
}
|
||||||
|
export function usePagination({ items, active, renderItem, pageSize, loop = true, }) {
|
||||||
|
const width = readlineWidth();
|
||||||
|
const bound = (num) => ((num % items.length) + items.length) % items.length;
|
||||||
|
const renderedItems = items.map((item, index) => {
|
||||||
|
if (item == null)
|
||||||
|
return [];
|
||||||
|
return breakLines(renderItem({ item, index, isActive: index === active }), width).split('\n');
|
||||||
|
});
|
||||||
|
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
|
||||||
|
const renderItemAtIndex = (index) => renderedItems[index] ?? [];
|
||||||
|
const pointer = usePointerPosition({ active, renderedItems, pageSize, loop });
|
||||||
|
// Render the active item to decide the position.
|
||||||
|
// If the active item fits under the pointer, we render it there.
|
||||||
|
// Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
|
||||||
|
const activeItem = renderItemAtIndex(active).slice(0, pageSize);
|
||||||
|
const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length;
|
||||||
|
// Create an array of lines for the page, and add the lines of the active item into the page
|
||||||
|
const pageBuffer = Array.from({ length: pageSize });
|
||||||
|
pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem);
|
||||||
|
// Store to prevent rendering the same item twice
|
||||||
|
const itemVisited = new Set([active]);
|
||||||
|
// Fill the page under the active item
|
||||||
|
let bufferPointer = activeItemPosition + activeItem.length;
|
||||||
|
let itemPointer = bound(active + 1);
|
||||||
|
while (bufferPointer < pageSize &&
|
||||||
|
!itemVisited.has(itemPointer) &&
|
||||||
|
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) {
|
||||||
|
const lines = renderItemAtIndex(itemPointer);
|
||||||
|
const linesToAdd = lines.slice(0, pageSize - bufferPointer);
|
||||||
|
pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd);
|
||||||
|
// Move pointers for next iteration
|
||||||
|
itemVisited.add(itemPointer);
|
||||||
|
bufferPointer += linesToAdd.length;
|
||||||
|
itemPointer = bound(itemPointer + 1);
|
||||||
|
}
|
||||||
|
// Fill the page over the active item
|
||||||
|
bufferPointer = activeItemPosition - 1;
|
||||||
|
itemPointer = bound(active - 1);
|
||||||
|
while (bufferPointer >= 0 &&
|
||||||
|
!itemVisited.has(itemPointer) &&
|
||||||
|
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) {
|
||||||
|
const lines = renderItemAtIndex(itemPointer);
|
||||||
|
const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1));
|
||||||
|
pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd);
|
||||||
|
// Move pointers for next iteration
|
||||||
|
itemVisited.add(itemPointer);
|
||||||
|
bufferPointer -= linesToAdd.length;
|
||||||
|
itemPointer = bound(itemPointer - 1);
|
||||||
|
}
|
||||||
|
return pageBuffer.filter((line) => typeof line === 'string').join('\n');
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
export declare class PromisePolyfill<T> extends Promise<T> {
|
||||||
|
static withResolver<T>(): {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (value: T) => void;
|
||||||
|
reject: (error: unknown) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// TODO: Remove this class once Node 22 becomes the minimum supported version.
|
||||||
|
export class PromisePolyfill extends Promise {
|
||||||
|
// Available starting from Node 22
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
|
||||||
|
static withResolver() {
|
||||||
|
let resolve;
|
||||||
|
let reject;
|
||||||
|
const promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
return { promise, resolve: resolve, reject: reject };
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
import type { InquirerReadline } from '@inquirer/type';
|
||||||
|
export default class ScreenManager {
|
||||||
|
private height;
|
||||||
|
private extraLinesUnderPrompt;
|
||||||
|
private cursorPos;
|
||||||
|
private readonly rl;
|
||||||
|
constructor(rl: InquirerReadline);
|
||||||
|
write(content: string): void;
|
||||||
|
render(content: string, bottomContent?: string): void;
|
||||||
|
checkCursorPos(): void;
|
||||||
|
done({ clearContent }: {
|
||||||
|
clearContent: boolean;
|
||||||
|
}): void;
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
import { stripVTControlCharacters } from 'node:util';
|
||||||
|
import { breakLines, readlineWidth } from "./utils.js";
|
||||||
|
import { cursorDown, cursorUp, cursorTo, cursorShow, eraseLines } from '@inquirer/ansi';
|
||||||
|
const height = (content) => content.split('\n').length;
|
||||||
|
const lastLine = (content) => content.split('\n').pop() ?? '';
|
||||||
|
export default class ScreenManager {
|
||||||
|
// These variables are keeping information to allow correct prompt re-rendering
|
||||||
|
height = 0;
|
||||||
|
extraLinesUnderPrompt = 0;
|
||||||
|
cursorPos;
|
||||||
|
rl;
|
||||||
|
constructor(rl) {
|
||||||
|
this.rl = rl;
|
||||||
|
this.cursorPos = rl.getCursorPos();
|
||||||
|
}
|
||||||
|
write(content) {
|
||||||
|
this.rl.output.unmute();
|
||||||
|
this.rl.output.write(content);
|
||||||
|
this.rl.output.mute();
|
||||||
|
}
|
||||||
|
render(content, bottomContent = '') {
|
||||||
|
// Write message to screen and setPrompt to control backspace
|
||||||
|
const promptLine = lastLine(content);
|
||||||
|
const rawPromptLine = stripVTControlCharacters(promptLine);
|
||||||
|
// Remove the rl.line from our prompt. We can't rely on the content of
|
||||||
|
// rl.line (mainly because of the password prompt), so just rely on it's
|
||||||
|
// length.
|
||||||
|
let prompt = rawPromptLine;
|
||||||
|
if (this.rl.line.length > 0) {
|
||||||
|
prompt = prompt.slice(0, -this.rl.line.length);
|
||||||
|
}
|
||||||
|
this.rl.setPrompt(prompt);
|
||||||
|
// SetPrompt will change cursor position, now we can get correct value
|
||||||
|
this.cursorPos = this.rl.getCursorPos();
|
||||||
|
const width = readlineWidth();
|
||||||
|
content = breakLines(content, width);
|
||||||
|
bottomContent = breakLines(bottomContent, width);
|
||||||
|
// Manually insert an extra line if we're at the end of the line.
|
||||||
|
// This prevent the cursor from appearing at the beginning of the
|
||||||
|
// current line.
|
||||||
|
if (rawPromptLine.length % width === 0) {
|
||||||
|
content += '\n';
|
||||||
|
}
|
||||||
|
let output = content + (bottomContent ? '\n' + bottomContent : '');
|
||||||
|
/**
|
||||||
|
* Re-adjust the cursor at the correct position.
|
||||||
|
*/
|
||||||
|
// We need to consider parts of the prompt under the cursor as part of the bottom
|
||||||
|
// content in order to correctly cleanup and re-render.
|
||||||
|
const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows;
|
||||||
|
const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0);
|
||||||
|
// Return cursor to the input position (on top of the bottomContent)
|
||||||
|
if (bottomContentHeight > 0)
|
||||||
|
output += cursorUp(bottomContentHeight);
|
||||||
|
// Return cursor to the initial left offset.
|
||||||
|
output += cursorTo(this.cursorPos.cols);
|
||||||
|
/**
|
||||||
|
* Render and store state for future re-rendering
|
||||||
|
*/
|
||||||
|
this.write(cursorDown(this.extraLinesUnderPrompt) + eraseLines(this.height) + output);
|
||||||
|
this.extraLinesUnderPrompt = bottomContentHeight;
|
||||||
|
this.height = height(output);
|
||||||
|
}
|
||||||
|
checkCursorPos() {
|
||||||
|
const cursorPos = this.rl.getCursorPos();
|
||||||
|
if (cursorPos.cols !== this.cursorPos.cols) {
|
||||||
|
this.write(cursorTo(cursorPos.cols));
|
||||||
|
this.cursorPos = cursorPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done({ clearContent }) {
|
||||||
|
this.rl.setPrompt('');
|
||||||
|
let output = cursorDown(this.extraLinesUnderPrompt);
|
||||||
|
output += clearContent ? eraseLines(this.height) : '\n';
|
||||||
|
output += cursorShow;
|
||||||
|
this.write(output);
|
||||||
|
this.rl.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
import type { Prettify } from '@inquirer/type';
|
||||||
|
/**
|
||||||
|
* Union type representing the possible statuses of a prompt.
|
||||||
|
*
|
||||||
|
* - `'loading'`: The prompt is currently loading.
|
||||||
|
* - `'idle'`: The prompt is loaded and currently waiting for the user to
|
||||||
|
* submit an answer.
|
||||||
|
* - `'done'`: The user has submitted an answer and the prompt is finished.
|
||||||
|
* - `string`: Any other string: The prompt is in a custom state.
|
||||||
|
*/
|
||||||
|
export type Status = 'loading' | 'idle' | 'done' | (string & {});
|
||||||
|
type DefaultTheme = {
|
||||||
|
/**
|
||||||
|
* Prefix to prepend to the message. If a function is provided, it will be
|
||||||
|
* called with the current status of the prompt, and the return value will be
|
||||||
|
* used as the prefix.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* If `status === 'loading'`, this property is ignored and the spinner (styled
|
||||||
|
* by the `spinner` property) will be displayed instead.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (status) => status === 'done' ? styleText('green', '✔') : styleText('blue', '?')
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
prefix: string | Prettify<Omit<Record<Status, string>, 'loading'>>;
|
||||||
|
/**
|
||||||
|
* Configuration for the spinner that is displayed when the prompt is in the
|
||||||
|
* `'loading'` state.
|
||||||
|
*
|
||||||
|
* We recommend the use of {@link https://github.com/sindresorhus/cli-spinners|cli-spinners} for a list of available spinners.
|
||||||
|
*/
|
||||||
|
spinner: {
|
||||||
|
/**
|
||||||
|
* The time interval between frames, in milliseconds.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* 80
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
interval: number;
|
||||||
|
/**
|
||||||
|
* A list of frames to show for the spinner.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Object containing functions to style different parts of the prompt.
|
||||||
|
*/
|
||||||
|
style: {
|
||||||
|
/**
|
||||||
|
* Style to apply to the user's answer once it has been submitted.
|
||||||
|
*
|
||||||
|
* @param text - The user's answer.
|
||||||
|
* @returns The styled answer.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('cyan', text)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
answer: (text: string) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to the message displayed to the user.
|
||||||
|
*
|
||||||
|
* @param text - The message to style.
|
||||||
|
* @param status - The current status of the prompt.
|
||||||
|
* @returns The styled message.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text, status) => styleText('bold', text)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
message: (text: string, status: Status) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to error messages.
|
||||||
|
*
|
||||||
|
* @param text - The error message.
|
||||||
|
* @returns The styled error message.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('red', `> ${text}`)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
error: (text: string) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to the default answer when one is provided.
|
||||||
|
*
|
||||||
|
* @param text - The default answer.
|
||||||
|
* @returns The styled default answer.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('dim', `(${text})`)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
defaultAnswer: (text: string) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to help text.
|
||||||
|
*
|
||||||
|
* @param text - The help text.
|
||||||
|
* @returns The styled help text.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('dim', text)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
help: (text: string) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to highlighted text.
|
||||||
|
*
|
||||||
|
* @param text - The text to highlight.
|
||||||
|
* @returns The highlighted text.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('cyan', text)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
highlight: (text: string) => string;
|
||||||
|
/**
|
||||||
|
* Style to apply to keyboard keys referred to in help texts.
|
||||||
|
*
|
||||||
|
* @param text - The key to style.
|
||||||
|
* @returns The styled key.
|
||||||
|
*
|
||||||
|
* @defaultValue
|
||||||
|
* ```ts
|
||||||
|
* // import { styleText } from 'node:util';
|
||||||
|
* (text) => styleText('cyan', styleText('bold', `<${text}>`))
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
key: (text: string) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type Theme<Extension extends object = object> = Prettify<Extension & DefaultTheme>;
|
||||||
|
export declare const defaultTheme: DefaultTheme;
|
||||||
|
export {};
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import { styleText } from 'node:util';
|
||||||
|
import figures from '@inquirer/figures';
|
||||||
|
export const defaultTheme = {
|
||||||
|
prefix: {
|
||||||
|
idle: styleText('blue', '?'),
|
||||||
|
done: styleText('green', figures.tick),
|
||||||
|
},
|
||||||
|
spinner: {
|
||||||
|
interval: 80,
|
||||||
|
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => styleText('yellow', frame)),
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
answer: (text) => styleText('cyan', text),
|
||||||
|
message: (text) => styleText('bold', text),
|
||||||
|
error: (text) => styleText('red', `> ${text}`),
|
||||||
|
defaultAnswer: (text) => styleText('dim', `(${text})`),
|
||||||
|
help: (text) => styleText('dim', text),
|
||||||
|
highlight: (text) => styleText('cyan', text),
|
||||||
|
key: (text) => styleText('cyan', styleText('bold', `<${text}>`)),
|
||||||
|
},
|
||||||
|
};
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
import type { InquirerReadline } from '@inquirer/type';
|
||||||
|
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import { withPointer, effectScheduler } from "./hook-engine.js";
|
||||||
|
export function useEffect(cb, depArray) {
|
||||||
|
withPointer((pointer) => {
|
||||||
|
const oldDeps = pointer.get();
|
||||||
|
const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
|
||||||
|
if (hasChanged) {
|
||||||
|
effectScheduler.queue(cb);
|
||||||
|
}
|
||||||
|
pointer.set(depArray);
|
||||||
|
});
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { type InquirerReadline } from '@inquirer/type';
|
||||||
|
import { type KeypressEvent } from './key.ts';
|
||||||
|
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void;
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { useRef } from "./use-ref.js";
|
||||||
|
import { useEffect } from "./use-effect.js";
|
||||||
|
import { withUpdates } from "./hook-engine.js";
|
||||||
|
export function useKeypress(userHandler) {
|
||||||
|
const signal = useRef(userHandler);
|
||||||
|
signal.current = userHandler;
|
||||||
|
useEffect((rl) => {
|
||||||
|
let ignore = false;
|
||||||
|
const handler = withUpdates((_input, event) => {
|
||||||
|
if (ignore)
|
||||||
|
return;
|
||||||
|
void signal.current(event, rl);
|
||||||
|
});
|
||||||
|
rl.input.on('keypress', handler);
|
||||||
|
return () => {
|
||||||
|
ignore = true;
|
||||||
|
rl.input.removeListener('keypress', handler);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
import { withPointer } from "./hook-engine.js";
|
||||||
|
export function useMemo(fn, dependencies) {
|
||||||
|
return withPointer((pointer) => {
|
||||||
|
const prev = pointer.get();
|
||||||
|
if (!prev ||
|
||||||
|
prev.dependencies.length !== dependencies.length ||
|
||||||
|
prev.dependencies.some((dep, i) => dep !== dependencies[i])) {
|
||||||
|
const value = fn();
|
||||||
|
pointer.set({ value, dependencies });
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return prev.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import type { Theme, Status } from './theme.ts';
|
||||||
|
export declare function usePrefix({ status, theme, }: {
|
||||||
|
status?: Status;
|
||||||
|
theme?: Theme;
|
||||||
|
}): string;
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
import { useState } from "./use-state.js";
|
||||||
|
import { useEffect } from "./use-effect.js";
|
||||||
|
import { makeTheme } from "./make-theme.js";
|
||||||
|
export function usePrefix({ status = 'idle', theme, }) {
|
||||||
|
const [showLoader, setShowLoader] = useState(false);
|
||||||
|
const [tick, setTick] = useState(0);
|
||||||
|
const { prefix, spinner } = makeTheme(theme);
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === 'loading') {
|
||||||
|
let tickInterval;
|
||||||
|
let inc = -1;
|
||||||
|
// Delay displaying spinner by 300ms, to avoid flickering
|
||||||
|
const delayTimeout = setTimeout(() => {
|
||||||
|
setShowLoader(true);
|
||||||
|
tickInterval = setInterval(() => {
|
||||||
|
inc = inc + 1;
|
||||||
|
setTick(inc % spinner.frames.length);
|
||||||
|
}, spinner.interval);
|
||||||
|
}, 300);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(delayTimeout);
|
||||||
|
clearInterval(tickInterval);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setShowLoader(false);
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
if (showLoader) {
|
||||||
|
return spinner.frames[tick];
|
||||||
|
}
|
||||||
|
// There's a delay before we show the loader. So we want to ignore `loading` here, and pass idle instead.
|
||||||
|
const iconName = status === 'loading' ? 'idle' : status;
|
||||||
|
return typeof prefix === 'string' ? prefix : (prefix[iconName] ?? prefix['idle']);
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
export declare function useRef<Value>(val: Value): {
|
||||||
|
current: Value;
|
||||||
|
};
|
||||||
|
export declare function useRef<Value>(val?: Value): {
|
||||||
|
current: Value | undefined;
|
||||||
|
};
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
import { useState } from "./use-state.js";
|
||||||
|
export function useRef(val) {
|
||||||
|
return useState({ current: val })[0];
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
type NotFunction<T> = T extends (...args: never) => unknown ? never : T;
|
||||||
|
export declare function useState<Value>(defaultValue: NotFunction<Value> | (() => Value)): [Value, (newValue: Value) => void];
|
||||||
|
export declare function useState<Value>(defaultValue?: NotFunction<Value> | (() => Value)): [Value | undefined, (newValue?: Value) => void];
|
||||||
|
export {};
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { AsyncResource } from 'node:async_hooks';
|
||||||
|
import { withPointer, handleChange } from "./hook-engine.js";
|
||||||
|
export function useState(defaultValue) {
|
||||||
|
return withPointer((pointer) => {
|
||||||
|
const setState = AsyncResource.bind(function setState(newValue) {
|
||||||
|
// Noop if the value is still the same.
|
||||||
|
if (pointer.get() !== newValue) {
|
||||||
|
pointer.set(newValue);
|
||||||
|
// Trigger re-render
|
||||||
|
handleChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (pointer.initialized) {
|
||||||
|
return [pointer.get(), setState];
|
||||||
|
}
|
||||||
|
const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
|
||||||
|
pointer.set(value);
|
||||||
|
return [value, setState];
|
||||||
|
});
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Force line returns at specific width. This function is ANSI code friendly and it'll
|
||||||
|
* ignore invisible codes during width calculation.
|
||||||
|
* @param {string} content
|
||||||
|
* @param {number} width
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export declare function breakLines(content: string, width: number): string;
|
||||||
|
/**
|
||||||
|
* Returns the width of the active readline, or 80 as default value.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export declare function readlineWidth(): number;
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import cliWidth from 'cli-width';
|
||||||
|
import wrapAnsi from 'wrap-ansi';
|
||||||
|
import { readline } from "./hook-engine.js";
|
||||||
|
/**
|
||||||
|
* Force line returns at specific width. This function is ANSI code friendly and it'll
|
||||||
|
* ignore invisible codes during width calculation.
|
||||||
|
* @param {string} content
|
||||||
|
* @param {number} width
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function breakLines(content, width) {
|
||||||
|
return content
|
||||||
|
.split('\n')
|
||||||
|
.flatMap((line) => wrapAnsi(line, width, { trim: false, hard: true })
|
||||||
|
.split('\n')
|
||||||
|
.map((str) => str.trimEnd()))
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the width of the active readline, or 80 as default value.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function readlineWidth() {
|
||||||
|
return cliWidth({ defaultWidth: 80, output: readline().output });
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"name": "@inquirer/core",
|
||||||
|
"version": "11.1.1",
|
||||||
|
"description": "Core Inquirer prompt API",
|
||||||
|
"keywords": [
|
||||||
|
"answer",
|
||||||
|
"answers",
|
||||||
|
"ask",
|
||||||
|
"base",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command-line",
|
||||||
|
"confirm",
|
||||||
|
"enquirer",
|
||||||
|
"generate",
|
||||||
|
"generator",
|
||||||
|
"hyper",
|
||||||
|
"input",
|
||||||
|
"inquire",
|
||||||
|
"inquirer",
|
||||||
|
"interface",
|
||||||
|
"iterm",
|
||||||
|
"javascript",
|
||||||
|
"menu",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"prompt",
|
||||||
|
"promptly",
|
||||||
|
"prompts",
|
||||||
|
"question",
|
||||||
|
"readline",
|
||||||
|
"scaffold",
|
||||||
|
"scaffolder",
|
||||||
|
"scaffolding",
|
||||||
|
"stdin",
|
||||||
|
"stdout",
|
||||||
|
"terminal",
|
||||||
|
"tty",
|
||||||
|
"ui",
|
||||||
|
"yeoman",
|
||||||
|
"yo",
|
||||||
|
"zsh"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/core/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Simon Boudrias <admin@simonboudrias.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SBoudrias/Inquirer.js.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/ansi": "^2.0.3",
|
||||||
|
"@inquirer/figures": "^2.0.3",
|
||||||
|
"@inquirer/type": "^4.0.3",
|
||||||
|
"cli-width": "^4.1.0",
|
||||||
|
"mute-stream": "^3.0.0",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"wrap-ansi": "^9.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@inquirer/testing": "^3.0.4",
|
||||||
|
"@types/mute-stream": "^0.0.4",
|
||||||
|
"@types/node": "^25.0.2",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"gitHead": "99d00a9adc53be8b7edf5926b2ec4ba0b792f68f"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
# `@inquirer/editor`
|
||||||
|
|
||||||
|
Prompt that'll open the user preferred editor with default content and allow for a convenient multi-line input controlled through the command line.
|
||||||
|
|
||||||
|
The editor launched is the one [defined by the user's `EDITOR` environment variable](https://dev.to/jonasbn/til-integrate-visual-studio-code-with-shell--cli-2l1l).
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" align="center">Or</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/editor
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/editor
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { editor } from '@inquirer/prompts';
|
||||||
|
// Or
|
||||||
|
// import editor from '@inquirer/editor';
|
||||||
|
|
||||||
|
const answer = await editor({
|
||||||
|
message: 'Enter a description',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Property | Type | Required | Description |
|
||||||
|
| ---------------- | ------------------------------------------------------------------------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| message | `string` | yes | The question to ask |
|
||||||
|
| default | `string` | no | Default value which will automatically be present in the editor |
|
||||||
|
| validate | `string => boolean \| string \| Promise<boolean \| string>` | no | On submit, validate the content. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. |
|
||||||
|
| postfix | `string` | no (default to `.txt`) | The postfix of the file being edited. Adding this will add color highlighting to the file content in most editors. |
|
||||||
|
| file | [`IFileOptions`](https://github.com/mrkmg/node-external-editor#config-options) | no | Exposes the [`external-editor` package options](https://github.com/mrkmg/node-external-editor#config-options) to configure the temporary file. |
|
||||||
|
| waitForUserInput | `boolean` | no (default to `true`) | Open the editor automatically without waiting for the user to press enter. Note that this mean the user will not see the question! So make sure you have a default value that provide guidance if it's unclear what input is expected. |
|
||||||
|
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Theme = {
|
||||||
|
prefix: string | { idle: string; done: string };
|
||||||
|
spinner: {
|
||||||
|
interval: number;
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
|
||||||
|
error: (text: string) => string;
|
||||||
|
help: (text: string) => string;
|
||||||
|
key: (text: string) => string;
|
||||||
|
};
|
||||||
|
validationFailureMode: 'keep' | 'clear';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`validationFailureMode` defines the behavior of the prompt when the value submitted is invalid. By default, we'll keep the value allowing the user to edit it. When the theme option is set to `clear`, we'll remove and reset to the default value or empty string.
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
import { type IFileOptions } from '@inquirer/external-editor';
|
||||||
|
import { type Theme } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
type EditorTheme = {
|
||||||
|
validationFailureMode: 'keep' | 'clear';
|
||||||
|
};
|
||||||
|
type EditorConfig = {
|
||||||
|
message: string;
|
||||||
|
default?: string;
|
||||||
|
postfix?: string;
|
||||||
|
waitForUserInput?: boolean;
|
||||||
|
validate?: (value: string) => boolean | string | Promise<string | boolean>;
|
||||||
|
file?: IFileOptions;
|
||||||
|
theme?: PartialDeep<Theme<EditorTheme>>;
|
||||||
|
};
|
||||||
|
declare const _default: import("@inquirer/type").Prompt<string, EditorConfig>;
|
||||||
|
export default _default;
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
import { editAsync } from '@inquirer/external-editor';
|
||||||
|
import { createPrompt, useEffect, useState, useKeypress, usePrefix, isEnterKey, makeTheme, } from '@inquirer/core';
|
||||||
|
const editorTheme = {
|
||||||
|
validationFailureMode: 'keep',
|
||||||
|
};
|
||||||
|
export default createPrompt((config, done) => {
|
||||||
|
const { waitForUserInput = true, file: { postfix = config.postfix ?? '.txt', ...fileProps } = {}, validate = () => true, } = config;
|
||||||
|
const theme = makeTheme(editorTheme, config.theme);
|
||||||
|
const [status, setStatus] = useState('idle');
|
||||||
|
const [value = '', setValue] = useState(config.default);
|
||||||
|
const [errorMsg, setError] = useState();
|
||||||
|
const prefix = usePrefix({ status, theme });
|
||||||
|
function startEditor(rl) {
|
||||||
|
rl.pause();
|
||||||
|
const editCallback = async (error, answer) => {
|
||||||
|
rl.resume();
|
||||||
|
if (error) {
|
||||||
|
setError(error.toString());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setStatus('loading');
|
||||||
|
const finalAnswer = answer ?? '';
|
||||||
|
const isValid = await validate(finalAnswer);
|
||||||
|
if (isValid === true) {
|
||||||
|
setError(undefined);
|
||||||
|
setStatus('done');
|
||||||
|
done(finalAnswer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (theme.validationFailureMode === 'clear') {
|
||||||
|
setValue(config.default);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue(finalAnswer);
|
||||||
|
}
|
||||||
|
setError(isValid || 'You must provide a valid value');
|
||||||
|
setStatus('idle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
editAsync(value, (error, answer) => void editCallback(error, answer), {
|
||||||
|
postfix,
|
||||||
|
...fileProps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
useEffect((rl) => {
|
||||||
|
if (!waitForUserInput) {
|
||||||
|
startEditor(rl);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
useKeypress((key, rl) => {
|
||||||
|
// Ignore keypress while our prompt is doing other processing.
|
||||||
|
if (status !== 'idle') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isEnterKey(key)) {
|
||||||
|
startEditor(rl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const message = theme.style.message(config.message, status);
|
||||||
|
let helpTip = '';
|
||||||
|
if (status === 'loading') {
|
||||||
|
helpTip = theme.style.help('Received');
|
||||||
|
}
|
||||||
|
else if (status === 'idle') {
|
||||||
|
const enterKey = theme.style.key('enter');
|
||||||
|
helpTip = theme.style.help(`Press ${enterKey} to launch your preferred editor.`);
|
||||||
|
}
|
||||||
|
let error = '';
|
||||||
|
if (errorMsg) {
|
||||||
|
error = theme.style.error(errorMsg);
|
||||||
|
}
|
||||||
|
return [[prefix, message, helpTip].filter(Boolean).join(' '), error];
|
||||||
|
});
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"name": "@inquirer/editor",
|
||||||
|
"version": "5.0.4",
|
||||||
|
"description": "Inquirer multiline editor prompt",
|
||||||
|
"keywords": [
|
||||||
|
"answer",
|
||||||
|
"answers",
|
||||||
|
"ask",
|
||||||
|
"base",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command-line",
|
||||||
|
"confirm",
|
||||||
|
"enquirer",
|
||||||
|
"generate",
|
||||||
|
"generator",
|
||||||
|
"hyper",
|
||||||
|
"input",
|
||||||
|
"inquire",
|
||||||
|
"inquirer",
|
||||||
|
"interface",
|
||||||
|
"iterm",
|
||||||
|
"javascript",
|
||||||
|
"menu",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"prompt",
|
||||||
|
"promptly",
|
||||||
|
"prompts",
|
||||||
|
"question",
|
||||||
|
"readline",
|
||||||
|
"scaffold",
|
||||||
|
"scaffolder",
|
||||||
|
"scaffolding",
|
||||||
|
"stdin",
|
||||||
|
"stdout",
|
||||||
|
"terminal",
|
||||||
|
"tty",
|
||||||
|
"ui",
|
||||||
|
"yeoman",
|
||||||
|
"yo",
|
||||||
|
"zsh"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/README.md",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Simon Boudrias <admin@simonboudrias.com>",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SBoudrias/Inquirer.js.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@inquirer/core": "^11.1.1",
|
||||||
|
"@inquirer/external-editor": "^2.0.3",
|
||||||
|
"@inquirer/type": "^4.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@inquirer/testing": "^3.0.4",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"gitHead": "99d00a9adc53be8b7edf5926b2ec4ba0b792f68f"
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2025 Simon Boudrias
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
+141
@@ -0,0 +1,141 @@
|
|||||||
|
# `@inquirer/expand`
|
||||||
|
|
||||||
|
Compact single select prompt. Every option is assigned a shortcut key, and selecting `h` will expand all the choices and their descriptions.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>npm</th>
|
||||||
|
<th>yarn</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/prompts
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colSpan="2" align="center">Or</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @inquirer/expand
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @inquirer/expand
|
||||||
|
```
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { expand } from '@inquirer/prompts';
|
||||||
|
// Or
|
||||||
|
// import expand from '@inquirer/expand';
|
||||||
|
|
||||||
|
const answer = await expand({
|
||||||
|
message: 'Conflict on file.js',
|
||||||
|
default: 'y',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
key: 'y',
|
||||||
|
name: 'Overwrite',
|
||||||
|
value: 'overwrite',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'a',
|
||||||
|
name: 'Overwrite this one and all next',
|
||||||
|
value: 'overwrite_all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'd',
|
||||||
|
name: 'Show diff',
|
||||||
|
value: 'diff',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'x',
|
||||||
|
name: 'Abort',
|
||||||
|
value: 'abort',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Property | Type | Required | Description |
|
||||||
|
| -------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| message | `string` | yes | The question to ask |
|
||||||
|
| choices | `Choice[]` | yes | Array of the different allowed choices. The `h`/help option is always provided by default |
|
||||||
|
| default | `string` | no | Default choices to be selected. (value must be one of the choices `key`) |
|
||||||
|
| expanded | `boolean` | no | Expand the choices by default |
|
||||||
|
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
|
||||||
|
|
||||||
|
`Separator` objects can be used in the `choices` array to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options.
|
||||||
|
|
||||||
|
### `Choice` object
|
||||||
|
|
||||||
|
The `Choice` object is typed as
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Choice<Value> = {
|
||||||
|
value: Value;
|
||||||
|
name?: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's each property:
|
||||||
|
|
||||||
|
- `value`: The value is what will be returned by `await expand()`.
|
||||||
|
- `name`: The string displayed in the choice list. It'll default to the stringify `value`.
|
||||||
|
- `key`: The input the use must provide to select the choice. Must be a lowercase single alpha-numeric character string.
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type Theme = {
|
||||||
|
prefix: string | { idle: string; done: string };
|
||||||
|
spinner: {
|
||||||
|
interval: number;
|
||||||
|
frames: string[];
|
||||||
|
};
|
||||||
|
style: {
|
||||||
|
answer: (text: string) => string;
|
||||||
|
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
|
||||||
|
error: (text: string) => string;
|
||||||
|
defaultAnswer: (text: string) => string;
|
||||||
|
highlight: (text: string) => string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
|
||||||
|
Licensed under the MIT license.
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { Separator, type Theme } from '@inquirer/core';
|
||||||
|
import type { PartialDeep } from '@inquirer/type';
|
||||||
|
type Key = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
||||||
|
type Choice<Value> = {
|
||||||
|
key: Key;
|
||||||
|
value: Value;
|
||||||
|
} | {
|
||||||
|
key: Key;
|
||||||
|
name: string;
|
||||||
|
value: Value;
|
||||||
|
};
|
||||||
|
declare const _default: <Value>(config: {
|
||||||
|
message: string;
|
||||||
|
choices: readonly {
|
||||||
|
key: Key;
|
||||||
|
name: string;
|
||||||
|
}[] | readonly (Separator | Choice<Value>)[];
|
||||||
|
default?: (Key | "h") | undefined;
|
||||||
|
expanded?: boolean | undefined;
|
||||||
|
theme?: PartialDeep<Theme> | undefined;
|
||||||
|
}, context?: import("@inquirer/type").Context) => Promise<Value>;
|
||||||
|
export default _default;
|
||||||
|
export { Separator } from '@inquirer/core';
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
import { createPrompt, useMemo, useState, useKeypress, usePrefix, isEnterKey, makeTheme, Separator, } from '@inquirer/core';
|
||||||
|
import { styleText } from 'node:util';
|
||||||
|
function normalizeChoices(choices) {
|
||||||
|
return choices.map((choice) => {
|
||||||
|
if (Separator.isSeparator(choice)) {
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
const name = 'name' in choice ? choice.name : String(choice.value);
|
||||||
|
const value = 'value' in choice ? choice.value : name;
|
||||||
|
return {
|
||||||
|
value: value,
|
||||||
|
name,
|
||||||
|
key: choice.key.toLowerCase(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const helpChoice = {
|
||||||
|
key: 'h',
|
||||||
|
name: 'Help, list all options',
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
export default createPrompt((config, done) => {
|
||||||
|
const { default: defaultKey = 'h' } = config;
|
||||||
|
const choices = useMemo(() => normalizeChoices(config.choices), [config.choices]);
|
||||||
|
const [status, setStatus] = useState('idle');
|
||||||
|
const [value, setValue] = useState('');
|
||||||
|
const [expanded, setExpanded] = useState(config.expanded ?? false);
|
||||||
|
const [errorMsg, setError] = useState();
|
||||||
|
const theme = makeTheme(config.theme);
|
||||||
|
const prefix = usePrefix({ theme, status });
|
||||||
|
useKeypress((event, rl) => {
|
||||||
|
if (isEnterKey(event)) {
|
||||||
|
const answer = (value || defaultKey).toLowerCase();
|
||||||
|
if (answer === 'h' && !expanded) {
|
||||||
|
setExpanded(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const selectedChoice = choices.find((choice) => !Separator.isSeparator(choice) && choice.key === answer);
|
||||||
|
if (selectedChoice) {
|
||||||
|
setStatus('done');
|
||||||
|
// Set the value as we might've selected the default one.
|
||||||
|
setValue(answer);
|
||||||
|
done(selectedChoice.value);
|
||||||
|
}
|
||||||
|
else if (value === '') {
|
||||||
|
setError('Please input a value');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setError(`"${styleText('red', value)}" isn't an available option`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setValue(rl.line);
|
||||||
|
setError(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const message = theme.style.message(config.message, status);
|
||||||
|
if (status === 'done') {
|
||||||
|
// If the prompt is done, it's safe to assume there is a selected value.
|
||||||
|
const selectedChoice = choices.find((choice) => !Separator.isSeparator(choice) && choice.key === value.toLowerCase());
|
||||||
|
return `${prefix} ${message} ${theme.style.answer(selectedChoice.name)}`;
|
||||||
|
}
|
||||||
|
const allChoices = expanded ? choices : [...choices, helpChoice];
|
||||||
|
// Collapsed display style
|
||||||
|
let longChoices = '';
|
||||||
|
let shortChoices = allChoices
|
||||||
|
.map((choice) => {
|
||||||
|
if (Separator.isSeparator(choice))
|
||||||
|
return '';
|
||||||
|
if (choice.key === defaultKey) {
|
||||||
|
return choice.key.toUpperCase();
|
||||||
|
}
|
||||||
|
return choice.key;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
shortChoices = ` ${theme.style.defaultAnswer(shortChoices)}`;
|
||||||
|
// Expanded display style
|
||||||
|
if (expanded) {
|
||||||
|
shortChoices = '';
|
||||||
|
longChoices = allChoices
|
||||||
|
.map((choice) => {
|
||||||
|
if (Separator.isSeparator(choice)) {
|
||||||
|
return ` ${choice.separator}`;
|
||||||
|
}
|
||||||
|
const line = ` ${choice.key}) ${choice.name}`;
|
||||||
|
if (choice.key === value.toLowerCase()) {
|
||||||
|
return theme.style.highlight(line);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
let helpTip = '';
|
||||||
|
const currentOption = choices.find((choice) => !Separator.isSeparator(choice) && choice.key === value.toLowerCase());
|
||||||
|
if (currentOption) {
|
||||||
|
helpTip = `${styleText('cyan', '>>')} ${currentOption.name}`;
|
||||||
|
}
|
||||||
|
let error = '';
|
||||||
|
if (errorMsg) {
|
||||||
|
error = theme.style.error(errorMsg);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
`${prefix} ${message}${shortChoices} ${value}`,
|
||||||
|
[longChoices, helpTip, error].filter(Boolean).join('\n'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
export { Separator } from '@inquirer/core';
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user