mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
basic node provider
This commit is contained in:
+2
-2
@@ -42,12 +42,12 @@ var BuildCommand = &cli.Command{
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
plan, err := core.GenerateBuildPlan(userApp, env, &core.GenerateBuildPlanOptions{})
|
||||
buildResult, err := core.GenerateBuildPlan(userApp, env, &core.GenerateBuildPlanOptions{})
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
serializedPlan, err := json.MarshalIndent(plan, "", " ")
|
||||
serializedPlan, err := json.MarshalIndent(buildResult, "", " ")
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ func TestApp(t *testing.T) {
|
||||
require.Contains(t, content, "node-bun")
|
||||
|
||||
var packageJSON PackageJSON
|
||||
app.ReadJSON("package.json", &packageJSON)
|
||||
err = app.ReadJSON("package.json", &packageJSON)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, packageJSON.Name, "node-bun")
|
||||
|
||||
files, err := app.FindFiles("*.ts")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Environment struct {
|
||||
@@ -54,3 +56,25 @@ func (e *Environment) GetVariable(name string) string {
|
||||
func (e *Environment) SetVariable(name, value string) {
|
||||
e.Variables[name] = value
|
||||
}
|
||||
|
||||
// ConfigVariable returns the RAILPACK_ prefixed version of a variable name
|
||||
func (e *Environment) ConfigVariable(name string) string {
|
||||
return fmt.Sprintf("RAILPACK_%s", name)
|
||||
}
|
||||
|
||||
// GetConfigVariable returns the value of a RAILPACK_ prefixed variable with newlines removed
|
||||
func (e *Environment) GetConfigVariable(name string) string {
|
||||
configVar := e.ConfigVariable(name)
|
||||
if val, exists := e.Variables[configVar]; exists {
|
||||
return strings.ReplaceAll(val, "\n", "")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsConfigVariableTruthy checks if a RAILPACK_ prefixed variable is set to "1" or "true"
|
||||
func (e *Environment) IsConfigVariableTruthy(name string) bool {
|
||||
if val := e.GetConfigVariable(name); val != "" {
|
||||
return val == "1" || val == "true"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+36
-4
@@ -8,12 +8,17 @@ import (
|
||||
"github.com/railwayapp/railpack-go/core/generate"
|
||||
"github.com/railwayapp/railpack-go/core/plan"
|
||||
"github.com/railwayapp/railpack-go/core/providers"
|
||||
"github.com/railwayapp/railpack-go/core/resolver"
|
||||
)
|
||||
|
||||
type GenerateBuildPlanOptions struct {
|
||||
type GenerateBuildPlanOptions struct{}
|
||||
|
||||
type BuildResult struct {
|
||||
Plan *plan.BuildPlan `json:"plan"`
|
||||
ResolvedPackages map[string]*resolver.ResolvedPackage `json:"resolved_packages"`
|
||||
}
|
||||
|
||||
func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuildPlanOptions) (*plan.BuildPlan, error) {
|
||||
func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuildPlanOptions) (*BuildResult, error) {
|
||||
ctx, err := generate.NewGenerateContext(app, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -26,12 +31,39 @@ func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuil
|
||||
}
|
||||
|
||||
if matched {
|
||||
log.Debugf("Provider %s matched", provider)
|
||||
log.Debugf("Provider `%s` matched", provider.Name())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
resolvedPackages, err := ctx.Resolver.ResolvePackages()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve packages: %w", err)
|
||||
}
|
||||
|
||||
buildPlan := plan.NewBuildPlan()
|
||||
|
||||
buildPlan.Variables = ctx.Variables
|
||||
for _, step := range ctx.Steps {
|
||||
buildPlan.AddStep(*step)
|
||||
}
|
||||
|
||||
for _, aptPackage := range ctx.AptPackages {
|
||||
buildPlan.Packages.AddAptPackage(aptPackage)
|
||||
}
|
||||
|
||||
for _, resolvedPackage := range resolvedPackages {
|
||||
if resolvedPackage.ResolvedVersion != nil {
|
||||
buildPlan.Packages.AddMisePackage(resolvedPackage.Name, *resolvedPackage.ResolvedVersion)
|
||||
}
|
||||
}
|
||||
|
||||
buildResult := &BuildResult{
|
||||
Plan: buildPlan,
|
||||
ResolvedPackages: resolvedPackages,
|
||||
}
|
||||
|
||||
return buildResult, nil
|
||||
}
|
||||
|
||||
func runProvider(provider providers.Provider, ctx *generate.GenerateContext) (bool, error) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type GenerateContext struct {
|
||||
app *app.App
|
||||
env *app.Environment
|
||||
App *app.App
|
||||
Env *app.Environment
|
||||
Resolver *resolver.Resolver
|
||||
Variables map[string]string
|
||||
AptPackages []string
|
||||
@@ -23,8 +23,8 @@ func NewGenerateContext(app *app.App, env *app.Environment) (*GenerateContext, e
|
||||
}
|
||||
|
||||
return &GenerateContext{
|
||||
app: app,
|
||||
env: env,
|
||||
App: app,
|
||||
Env: env,
|
||||
Resolver: resolver,
|
||||
Variables: map[string]string{},
|
||||
AptPackages: []string{},
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package plan
|
||||
|
||||
// Command represents a command that can be executed
|
||||
type Command interface {
|
||||
commandType() string
|
||||
}
|
||||
|
||||
// ExecCommand represents a command to be executed
|
||||
type ExecCommand struct {
|
||||
Cmd string `json:"cmd"`
|
||||
}
|
||||
|
||||
// GlobalPathCommand represents a global path addition
|
||||
type GlobalPathCommand struct {
|
||||
GlobalPath string `json:"globalPath"`
|
||||
}
|
||||
|
||||
// VariableCommand represents a shell variable setting
|
||||
type VariableCommand struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// CopyCommand represents a file copy operation
|
||||
type CopyCommand struct {
|
||||
Src string `json:"src"`
|
||||
Dst string `json:"dst"`
|
||||
}
|
||||
|
||||
// Implementation of Command interface for each type
|
||||
func (e ExecCommand) commandType() string { return "exec" }
|
||||
func (g GlobalPathCommand) commandType() string { return "globalPath" }
|
||||
func (v VariableCommand) commandType() string { return "variable" }
|
||||
func (c CopyCommand) commandType() string { return "copy" }
|
||||
|
||||
// Constructor functions
|
||||
func NewExecCommand(cmd string) Command {
|
||||
return ExecCommand{Cmd: cmd}
|
||||
}
|
||||
|
||||
func NewGlobalPathCommand(path string) Command {
|
||||
return GlobalPathCommand{GlobalPath: path}
|
||||
}
|
||||
|
||||
func NewVariableCommand(name, value string) Command {
|
||||
return VariableCommand{Name: name, Value: value}
|
||||
}
|
||||
|
||||
func NewCopyCommand(src, dst string) Command {
|
||||
return CopyCommand{Src: src, Dst: dst}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package plan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type PlanPackages struct {
|
||||
Apt []string `json:"apt,omitempty"`
|
||||
Mise map[string]string `json:"mise,omitempty"`
|
||||
}
|
||||
|
||||
func NewPlanPackages() *PlanPackages {
|
||||
return &PlanPackages{
|
||||
Apt: []string{},
|
||||
Mise: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlanPackages) AddAptPackage(pkg string) {
|
||||
p.Apt = append(p.Apt, pkg)
|
||||
}
|
||||
|
||||
func (p *PlanPackages) AddMisePackage(pkg string, version string) {
|
||||
p.Mise[pkg] = version
|
||||
}
|
||||
|
||||
// MisePackage represents a single mise package configuration
|
||||
type MisePackage struct {
|
||||
Version string `toml:"version"`
|
||||
}
|
||||
|
||||
// MiseConfig represents the overall mise configuration
|
||||
type MiseConfig struct {
|
||||
Tools map[string]MisePackage `toml:"tools"`
|
||||
}
|
||||
|
||||
func (p *PlanPackages) GenerateMiseToml() (string, error) {
|
||||
config := MiseConfig{
|
||||
Tools: make(map[string]MisePackage),
|
||||
}
|
||||
|
||||
for name, version := range p.Mise {
|
||||
config.Tools[name] = MisePackage{
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := toml.NewEncoder(buf).Encode(config); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -5,12 +5,14 @@ import "encoding/json"
|
||||
type BuildPlan struct {
|
||||
Variables map[string]string `json:"variables,omitempty"`
|
||||
Steps []Step `json:"steps,omitempty"`
|
||||
Packages *PlanPackages `json:"packages,omitempty"`
|
||||
}
|
||||
|
||||
func NewBuildPlan() *BuildPlan {
|
||||
return &BuildPlan{
|
||||
Variables: map[string]string{},
|
||||
Steps: []Step{},
|
||||
Packages: NewPlanPackages(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-3
@@ -3,8 +3,24 @@ package plan
|
||||
type Step struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
DependsOn []string `json:"depends_on,omitempty"`
|
||||
Commands []string `json:"commands,omitempty"`
|
||||
DependsOn []string `json:"depends_on,omitempty"`
|
||||
Commands []Command `json:"commands,omitempty"`
|
||||
}
|
||||
|
||||
func NewStep(name string) *Step {
|
||||
return &Step{
|
||||
Name: name,
|
||||
DependsOn: make([]string, 0),
|
||||
Commands: make([]Command, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Step) DependOn(name string) {
|
||||
s.DependsOn = append(s.DependsOn, name)
|
||||
}
|
||||
|
||||
func (s *Step) AddCommands(commands []Command) {
|
||||
s.Commands = append(s.Commands, commands...)
|
||||
}
|
||||
|
||||
func MergeSteps(steps ...*Step) *Step {
|
||||
@@ -15,7 +31,7 @@ func MergeSteps(steps ...*Step) *Step {
|
||||
result := &Step{
|
||||
Name: steps[0].Name,
|
||||
DependsOn: make([]string, 0),
|
||||
Commands: make([]string, 0),
|
||||
Commands: make([]Command, 0),
|
||||
}
|
||||
|
||||
for _, step := range steps {
|
||||
|
||||
+180
-3
@@ -1,13 +1,190 @@
|
||||
package node
|
||||
|
||||
import "github.com/railwayapp/railpack-go/core/generate"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/railwayapp/railpack-go/core/app"
|
||||
"github.com/railwayapp/railpack-go/core/generate"
|
||||
"github.com/railwayapp/railpack-go/core/plan"
|
||||
)
|
||||
|
||||
type PackageJson struct {
|
||||
Scripts map[string]string `json:"scripts"`
|
||||
PackageManager *string `json:"packageManager"`
|
||||
Engines map[string]string `json:"engines"`
|
||||
}
|
||||
|
||||
type PackageManager string
|
||||
|
||||
const (
|
||||
DEFAULT_NODE_VERSION = "22"
|
||||
DEFAULT_BUN_VERSION = "latest"
|
||||
)
|
||||
|
||||
type NodeProvider struct{}
|
||||
|
||||
const DEFAULT_NODE_VERSION = "22"
|
||||
func (p *NodeProvider) Name() string {
|
||||
return "node"
|
||||
}
|
||||
|
||||
func (p *NodeProvider) Plan(ctx *generate.GenerateContext) (bool, error) {
|
||||
ctx.Resolver.Default("node", DEFAULT_NODE_VERSION)
|
||||
packageJson, err := p.getPackageJson(ctx.App)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if packageJson == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := p.packages(ctx, packageJson); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := p.install(ctx, packageJson); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// if err := p.build(ctx, packageJson); err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (p *NodeProvider) install(ctx *generate.GenerateContext, packageJson *PackageJson) error {
|
||||
corepack := p.usesCorepack(packageJson)
|
||||
if corepack {
|
||||
setup := plan.NewStep("setup")
|
||||
setup.AddCommands([]plan.Command{
|
||||
plan.NewCopyCommand("package.json", "."),
|
||||
plan.NewExecCommand("npm install -g corepack"),
|
||||
plan.NewExecCommand("corepack enable"),
|
||||
plan.NewExecCommand("corepack prepare --activate"),
|
||||
})
|
||||
|
||||
ctx.AddStep(setup)
|
||||
}
|
||||
|
||||
pkgManager := p.getPackageManager(ctx.App)
|
||||
|
||||
install := plan.NewStep("install")
|
||||
install.AddCommands([]plan.Command{
|
||||
plan.NewCopyCommand(".", "."),
|
||||
plan.NewExecCommand(pkgManager.InstallDeps()),
|
||||
})
|
||||
|
||||
if corepack {
|
||||
install.DependOn("setup")
|
||||
}
|
||||
|
||||
ctx.AddStep(install)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NodeProvider) packages(ctx *generate.GenerateContext, packageJson *PackageJson) error {
|
||||
packageManager := p.getPackageManager(ctx.App)
|
||||
|
||||
// Node
|
||||
node := ctx.Resolver.Default("node", DEFAULT_NODE_VERSION)
|
||||
|
||||
envVersion := ctx.Env.GetConfigVariable("NODE_VERSION")
|
||||
if envVersion != "" {
|
||||
ctx.Resolver.Version(node, envVersion, "RAILPACK_NODE_VERSION")
|
||||
}
|
||||
|
||||
if packageJson.Engines != nil && packageJson.Engines["node"] != "" {
|
||||
ctx.Resolver.Version(node, packageJson.Engines["node"], "package.json > engines > node")
|
||||
}
|
||||
|
||||
if packageManager == PackageManagerBun {
|
||||
bun := ctx.Resolver.Default("bun", DEFAULT_BUN_VERSION)
|
||||
|
||||
envVersion := ctx.Env.GetConfigVariable("BUN_VERSION")
|
||||
if envVersion != "" {
|
||||
ctx.Resolver.Version(bun, envVersion, "RAILPACK_BUN_VERSION")
|
||||
}
|
||||
}
|
||||
|
||||
return p.managerPackages(ctx, packageManager)
|
||||
}
|
||||
|
||||
func (p *NodeProvider) managerPackages(ctx *generate.GenerateContext, packageManager PackageManager) error {
|
||||
// NPM
|
||||
if packageManager == PackageManagerNpm {
|
||||
npm := ctx.Resolver.Default("npm", "latest")
|
||||
|
||||
lockfile, err := ctx.App.ReadFile("package-lock.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading package-lock.json: %w", err)
|
||||
}
|
||||
|
||||
if strings.Contains(lockfile, "\"lockfileVersion\": 1") {
|
||||
ctx.Resolver.Version(npm, "6", "package-lock.json")
|
||||
} else if strings.Contains(lockfile, "\"lockfileVersion\": 2") {
|
||||
ctx.Resolver.Version(npm, "8", "package-lock.json")
|
||||
}
|
||||
}
|
||||
|
||||
// Pnpm
|
||||
if packageManager == PackageManagerPnpm {
|
||||
pnpm := ctx.Resolver.Default("pnpm", "latest")
|
||||
|
||||
lockfile, err := ctx.App.ReadFile("pnpm-lock.yaml")
|
||||
if err == nil {
|
||||
if strings.HasPrefix(lockfile, "lockfileVersion: 5.3") {
|
||||
ctx.Resolver.Version(pnpm, "6", "pnpm-lock.yaml")
|
||||
} else if strings.HasPrefix(lockfile, "lockfileVersion: 5.4") {
|
||||
ctx.Resolver.Version(pnpm, "7", "pnpm-lock.yaml")
|
||||
} else if strings.HasPrefix(lockfile, "lockfileVersion: '6.0'") {
|
||||
ctx.Resolver.Version(pnpm, "8", "pnpm-lock.yaml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Yarn
|
||||
if packageManager == PackageManagerYarn1 {
|
||||
ctx.Resolver.Default("yarn", "1")
|
||||
ctx.AddAptPackage("tar")
|
||||
ctx.AddAptPackage("gpg")
|
||||
} else if packageManager == PackageManagerYarn2 {
|
||||
ctx.Resolver.Default("yarn", "2")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *NodeProvider) usesCorepack(packageJson *PackageJson) bool {
|
||||
return packageJson.PackageManager != nil
|
||||
}
|
||||
|
||||
func (p *NodeProvider) getPackageManager(app *app.App) PackageManager {
|
||||
packageManager := PackageManagerNpm
|
||||
|
||||
if app.HasMatch("pnpm-lock.yaml") {
|
||||
packageManager = PackageManagerPnpm
|
||||
} else if app.HasMatch("bun.lockb") || app.HasMatch("bun.lock") {
|
||||
packageManager = PackageManagerBun
|
||||
} else if app.HasMatch(".yarnrc.yml") || app.HasMatch(".yarnrc.yaml") {
|
||||
packageManager = PackageManagerYarn2
|
||||
} else if app.HasMatch("yarn.lock") {
|
||||
packageManager = PackageManagerYarn1
|
||||
}
|
||||
|
||||
return packageManager
|
||||
}
|
||||
|
||||
func (p *NodeProvider) getPackageJson(app *app.App) (*PackageJson, error) {
|
||||
if !app.HasMatch("package.json") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var packageJson PackageJson
|
||||
err := app.ReadJSON("package.json", &packageJson)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading package.json: %w", err)
|
||||
}
|
||||
|
||||
return &packageJson, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package node
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
PackageManagerNpm PackageManager = "npm"
|
||||
PackageManagerPnpm PackageManager = "pnpm"
|
||||
PackageManagerBun PackageManager = "bun"
|
||||
PackageManagerYarn1 PackageManager = "yarn1"
|
||||
PackageManagerYarn2 PackageManager = "yarn2"
|
||||
)
|
||||
|
||||
func (p PackageManager) Name() string {
|
||||
switch p {
|
||||
case PackageManagerNpm:
|
||||
return "npm"
|
||||
case PackageManagerPnpm:
|
||||
return "pnpm"
|
||||
case PackageManagerBun:
|
||||
return "bun"
|
||||
case PackageManagerYarn1, PackageManagerYarn2:
|
||||
return "yarn"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p PackageManager) InstallDeps() string {
|
||||
switch p {
|
||||
case PackageManagerNpm:
|
||||
return "npm ci"
|
||||
case PackageManagerPnpm:
|
||||
return "pnpm install --frozen-lockfile"
|
||||
case PackageManagerBun:
|
||||
return "bun i --no-save"
|
||||
case PackageManagerYarn1:
|
||||
return "yarn install --frozen-lockfile"
|
||||
case PackageManagerYarn2:
|
||||
return "yarn install --check-cache"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p PackageManager) RunCmd(cmd string) string {
|
||||
return fmt.Sprintf("%s run %s", p.Name(), cmd)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
Name() string
|
||||
Plan(ctx *generate.GenerateContext) (bool, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ module github.com/railwayapp/railpack-go
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0
|
||||
github.com/charmbracelet/log v0.4.0
|
||||
github.com/simonnilsson/ask v0.4.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ=
|
||||
@@ -27,6 +29,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/simonnilsson/ask v0.4.0 h1:m+IaCOwgvXI1w9+g4PiCmxwBD/nEjd1ycc+oneQLMLU=
|
||||
github.com/simonnilsson/ask v0.4.0/go.mod h1:sUb3WVJKlF20TzemrTDTB5b1e2mxYYqluNe75OPa7Tg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
|
||||
|
||||
Reference in New Issue
Block a user