Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
f10bb36
feat(gnoland): support chain upgrade genesis replay with original cha…
Apr 1, 2026
badb0bd
misc: add gnoland-1 genesis generation script
Apr 1, 2026
48c2cc0
feat(tm2): add InitialHeight to GenesisDoc for chain upgrades
moul Apr 1, 2026
60b558e
misc: rewrite generate-genesis.sh with 3-phase approach
moul Apr 1, 2026
876fe1a
Merge branch 'master' into feat/genesis-replay-upgrade
moul Apr 2, 2026
66ef0aa
Merge branch 'master' into feat/genesis-replay-upgrade
moul Apr 3, 2026
969b93c
Merge branch 'master' into feat/genesis-replay-upgrade
moul Apr 8, 2026
7e0f6f9
feat(gnoland): improve hardfork genesis replay mechanism
moul Apr 9, 2026
18963a7
test(tm2): add coverage for GenesisDoc.InitialHeight in consensus rep…
moul Apr 9, 2026
c66db84
Merge branch 'master' into feat/genesis-replay-upgrade
moul Apr 9, 2026
3c6dc63
feat(hardfork): add hardfork genesis tool and simplify generate-genes…
moul Apr 9, 2026
aefa3ae
chore(hardfork): gitignore built binary
moul Apr 9, 2026
b22f514
Merge remote-tracking branch 'origin/feat/genesis-replay-upgrade' int…
moul Apr 9, 2026
cf65c0c
feat(hardfork): add 'hardfork test' subcommand for local genesis repl…
moul Apr 10, 2026
ccc49cb
feat(gnoland): support chain upgrade genesis replay with original cha…
Apr 1, 2026
33b17e1
feat(tm2): add InitialHeight to GenesisDoc for chain upgrades
moul Apr 1, 2026
9e6dd7f
feat(gnoland): improve hardfork genesis replay mechanism
moul Apr 9, 2026
773a01c
test(tm2): add coverage for GenesisDoc.InitialHeight in consensus rep…
moul Apr 9, 2026
f67f3ca
chore(adr): rename ADR to pr5489
moul Apr 13, 2026
5801781
feat(gnoland): replace OriginalChainID with PastChainIDs allowlist
moul Apr 13, 2026
0ae7333
fix(gnoland): only override genesis tx timestamp when metadata.Timest…
moul Apr 13, 2026
beda29e
test(gnoland): add isPastChainID unit tests + multi-chain replay test
moul Apr 13, 2026
aec7ff6
Merge branch 'master' into feat/genesis-tx-metadata
moul Apr 13, 2026
a19bae5
fix(gnoland): gofmt/goimports formatting in app_test.go
moul Apr 13, 2026
b4e87a7
fix(test): use single account with correct seq for multi-chain replay…
moul Apr 13, 2026
28502eb
fix: address tbruyelle review comments on PR #5489
moul Apr 14, 2026
9fca49a
fix(blockchain): allow empty block store when InitialHeight > 1
moul Apr 14, 2026
5d0e278
fix(tm2): support InitialHeight > 1 across consensus, state, store an…
moul Apr 14, 2026
0d82ad8
Merge branch 'feat/genesis-tx-metadata' into feat/genesis-replay-upgrade
jaekwon Apr 15, 2026
570f152
feat(hardfork): add per-signer account metadata for replay sig verifi…
jaekwon Apr 15, 2026
25e2485
fix(hardfork): review fixes on genesis replay signer metadata
moul Apr 15, 2026
23cde60
chore: fix gofmt, goimports, and go mod tidy for CI
moul Apr 15, 2026
f73f26f
fix: align struct tag comment for gofmt
moul Apr 15, 2026
1761034
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 15, 2026
5b2ba29
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 15, 2026
b2402d3
test(gnoland): add SignerInfo force-set, failed tx skip, and BlockHei…
moul Apr 15, 2026
d28cb54
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 16, 2026
96a909f
test(tm2): add failing tests for InitialHeight bugs
moul Apr 16, 2026
981adbd
fix(tm2): fix InitialHeight bugs in consensus layer
moul Apr 16, 2026
1ca9d35
test(hardfork): add failing tests for overlay and JSONL bugs
moul Apr 16, 2026
6b3bb8f
fix(hardfork): fix overlay no-op and JSONL amino serialization
moul Apr 16, 2026
b00ed9f
docs(adr): mark pr5411 as superseded, update pr5489 with bug fixes
moul Apr 16, 2026
6c362cd
fix(gnoland): validate GnoGenesisState.InitialHeight matches GenesisD…
moul Apr 17, 2026
13c9c9c
fix(hardfork): verifyGenesisFile failure aborts with error
moul Apr 17, 2026
2cbc7b0
fix(gnoland): failed tx response carries error marker
moul Apr 17, 2026
179a3cd
test(hardfork): add tests for bruteForceSignerSequence
moul Apr 17, 2026
e0c56ac
docs(adr): document PastChainIDs self-inclusion and update bug status
moul Apr 17, 2026
ab1dd85
fix(tm2): regenerate abci.proto after RequestInitChain.InitialHeight
moul Apr 17, 2026
39b2fa6
feat(gnoland): add GasReplayMode and genesis replay report
moul Apr 17, 2026
a4ead54
chore(docs-linter): skip staging.gno.land URLs
moul Apr 17, 2026
7e99bea
feat: hardfork-replay improvements (BaseApp InitialHeight + PastChain…
moul Apr 17, 2026
374a118
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 17, 2026
4127fe7
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 18, 2026
ec1e8e0
fix(tm2/sdk): guard BaseApp.Info against unloaded multistore
moul Apr 18, 2026
657f2dd
docs: route dead jaekwon/ephesus link through web.archive.org
moul Apr 18, 2026
5c900b2
fix(docs-linter): skip archive.org URLs (known rate-limits)
moul Apr 18, 2026
33708e1
docs(adr): bring pr5489 up to date with final PR state
moul Apr 19, 2026
bd3580d
refactor: absorb misc/hardfork into 'gnogenesis fork' subcommand
moul Apr 19, 2026
b4a8c7c
Revert "fix(docs-linter): skip archive.org URLs (known rate-limits)"
moul Apr 19, 2026
ecfb8e1
Revert "docs: route dead jaekwon/ephesus link through web.archive.org"
moul Apr 19, 2026
b750f76
refactor: simplify hardfork genesis tooling — drop overlay mechanism
moul Apr 19, 2026
4fe836e
docs(adr): consolidate hardfork ADRs — one for tm2, one for gno.land
moul Apr 19, 2026
6903f11
feat(gnogenesis): --migration-tx flag on fork generate
moul Apr 20, 2026
fe98c89
fix(tm2/sdk): enforce strict block-height contiguity on InitialHeight…
moul Apr 27, 2026
fd5fdb9
fix(gnoland,tm2/sdk): refuse hardfork boot on replay failures (Strict…
moul Apr 27, 2026
b3ae6e3
fix(gnoland): preserve metadata-driven ctxFn for migration txs
moul Apr 27, 2026
423ce7e
fix(auth,gnoland): rename to NewAccountWithUncheckedNumber + add Sign…
moul Apr 27, 2026
09b9fb6
test(abci): RequestInitChain.InitialHeight amino round-trip + doc sem…
moul Apr 27, 2026
9ad296e
docs(gnogenesis/fork): drop stale BUG comment in generate_test.go
moul Apr 27, 2026
0d5a99a
Merge branch 'master' into feat/genesis-replay-upgrade3
moul Apr 27, 2026
7f8b6c8
chore(deployments): drop gnoland-1 generate-genesis.sh and .gitignore
moul Apr 27, 2026
70c6777
docs(adr): bring pr5511 ADRs up to date
moul Apr 27, 2026
1babfe4
fix(consensus): skip phantom heights during replay when InitialHeight…
moul Apr 22, 2026
5bf2fa5
fix(gnogenesis): default gas-storage params and gas_replay_mode in ha…
aeddi Apr 24, 2026
e312684
feat(gnogenesis): add --skip-failing-genesis-txs and --skip-genesis-s…
aeddi Apr 24, 2026
9577fef
chore: regenerate .proto files to match merged-master Go types
moul Apr 29, 2026
4640541
fix(ci): lint + docs-linter green
moul Apr 29, 2026
245ec43
Merge remote-tracking branch 'origin/master' into feat/genesis-replay…
moul Apr 30, 2026
50ac6c0
refactor(tm2): InitialHeight as first-class state, version-parity in …
jaekwon May 1, 2026
6281999
fix(blockchain): nil-guard switchToConsensusFn in poolRoutine
jaekwon May 1, 2026
c307ad1
feat: valoper signing-key rotation (VALOPLAN2)
jaekwon May 3, 2026
2323fdc
feat(gnogenesis): valoper-seed migration tooling for hardfork ceremony
jaekwon May 3, 2026
91b87db
fix(valoper): security + correctness fixes from final review
jaekwon May 3, 2026
d95a74c
fix(valoper): pre-flight moniker regex; enforce KeepRunning in v3
jaekwon May 3, 2026
b869ed0
docs(valoper-seed): document v3 deployment prerequisite
jaekwon May 3, 2026
79aee40
feat: position-aware KeepRunning + gnogenesis fork addpkg
jaekwon May 3, 2026
3440c58
refactor(v3): single-entry power upsert + binding KeepRunning opt-out
jaekwon May 4, 2026
7777b53
test(v3): pin no-ghost rotation invariant + document phantom-state risk
jaekwon May 4, 2026
989c521
fix(valoper): bind auth.owner to OperatorAddress, not OriginCaller
jaekwon May 4, 2026
0944665
fix(gnogenesis): valoper-seed rejects operator_addr == derive(pubkey)
jaekwon May 4, 2026
4b7964f
feat(gnoland): auto-run AssertGenesisValopersConsistent in hardfork mode
jaekwon May 4, 2026
8e7530f
test(gnoland): drop legacy NewProposalRequest from valset surface all…
jaekwon May 4, 2026
23957b4
fix(v3): defense-in-depth empty-set guard in RotateValoperSigningKey
jaekwon May 4, 2026
6dde639
test(gnogenesis): drift test for moniker regex vs realm constant
jaekwon May 4, 2026
dda26b0
test(v3): same-block proposal-execute then rotation accumulates
jaekwon May 4, 2026
fac706b
examples make generate
jaekwon May 4, 2026
472576b
chore: simplify pass — constants, free fn, comment trim
jaekwon May 4, 2026
f1c67b7
docs: drop references to uncommitted VALOPLAN2.md
jaekwon May 4, 2026
5e423d6
fix(gnogenesis,gnoland): SkipValoperCoverageAssertion for fork test
jaekwon May 4, 2026
c8b6fe7
chore(gnoland,v3): extract shouldRunValoperCoverageAssertion + drop s…
jaekwon May 4, 2026
056991e
docs(adr): valset checkpoint replay design (deferred)
jaekwon May 4, 2026
6f95e1f
Merge branch 'master' into feat/genesis-replay-upgrade3
moul May 4, 2026
5797272
fix(ci): gofmt + tparallel after VALOPLAN2 series
moul May 4, 2026
2272259
test: pin two reachability claims raised in PR review
moul May 4, 2026
dfdf648
fix(valopers): tighten Register squat guard with IsUserCall + doc no-…
moul May 4, 2026
c1bff93
Merge branch 'master' into feat/genesis-replay-upgrade3
moul May 7, 2026
33e03f5
Merge branch 'master' into feat/genesis-replay-upgrade3
moul May 7, 2026
2caf421
test(gnovm): register setSysParamUint64 + setSysParamInt64 in native …
moul May 7, 2026
d292654
fix(valopers): drop IsUserCall from squat guard; bump gas in valopers…
moul May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contribs/gnogenesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"github.com/gnolang/contribs/gnogenesis/internal/balances"
"github.com/gnolang/contribs/gnogenesis/internal/fork"
"github.com/gnolang/contribs/gnogenesis/internal/generate"
"github.com/gnolang/contribs/gnogenesis/internal/params"
"github.com/gnolang/contribs/gnogenesis/internal/txs"
Expand All @@ -28,6 +29,7 @@ func newGenesisCmd(io commands.IO) *commands.Command {
balances.NewBalancesCmd(io),
txs.NewTxsCmd(io),
params.NewParamsCmd(io),
fork.NewForkCmd(io),
)

return cmd
Expand Down
144 changes: 144 additions & 0 deletions contribs/gnogenesis/internal/fork/addpkg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package fork

import (
"context"
"errors"
"flag"
"fmt"
"os"
"strings"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/std"
)

// Default deployer for hardfork addpkg txs. The hardfork ceremony runs
// with --skip-genesis-sig-verification=true, so the actual signature
// is irrelevant; what matters is the deployer address that becomes
// the package's owner. Mirrors gnogenesis txs add packages' default
// account, which gnoland-1's genesis used.
const defaultDeployerAddr = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"

// genesisDeployFee mirrors gno.land/cmd/start.go's genesis fee. Each
// addpkg tx in the .jsonl carries the same fee for parity with the
// fee used when these realms were originally deployed; under
// --skip-genesis-sig-verification the fee is also un-checked.
var genesisDeployFee = std.NewFee(50000, std.MustParseCoin(ugnot.ValueString(1)))

type addpkgCfg struct {
output string
deployerStr string
}

// newAddpkgCmd builds a deterministic .jsonl of MsgAddPackage txs
// from one or more local package directories. Used during a hardfork
// ceremony to deploy realms that don't exist on the source chain
// (e.g., r/sys/validators/v3 when forking from gnoland-1, where v3
// was added post-source-launch).
//
// Output format matches what `gnogenesis fork generate --migration-tx`
// expects: gnoland.TxWithMetadata, one amino-JSON line per tx,
// BlockHeight forced to 0 by readMigrationTxs at consume time.
func newAddpkgCmd(io commands.IO) *commands.Command {
cfg := &addpkgCfg{}

return commands.NewCommand(
commands.Metadata{
Name: "addpkg",
ShortUsage: "addpkg [flags] <pkgdir> [<pkgdir>...]",
ShortHelp: "build a .jsonl of MsgAddPackage txs from local package dirs",
LongHelp: `Build a deterministic .jsonl of MsgAddPackage migration txs from one
or more local package directories. Output is intended for
'gnogenesis fork generate --migration-tx' as a prerequisite step
when the source chain doesn't have a needed realm deployed.

Example: forking from gnoland-1 (which doesn't have v3) to a chain
that requires r/sys/validators/v3:

gnogenesis fork addpkg \
--output addpkg-v3.jsonl \
examples/gno.land/r/sys/validators/v3
gnogenesis fork generate \
--source ... \
--migration-tx addpkg-v3.jsonl \
--migration-tx valoper-seed.jsonl \
...

Each emitted tx is a MsgAddPackage with:
- Caller = --deployer (default: gnoland-1's test1 account)
- Package = LoadPackagesFromDir(<pkgdir>) — recursive, includes sub-realms
- Metadata.BlockHeight = 0 (genesis-mode)
- Signatures = [] (consumer runs with --skip-genesis-sig-verification)

Output is written in the order packages are loaded; LoadPackagesFromDir
sorts by pkgpath internally, so the same input produces a byte-equal
output across runs.`,
},
cfg,
func(ctx context.Context, args []string) error {
return execAddpkg(ctx, cfg, io, args)
},
)
}

func (c *addpkgCfg) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&c.output, "output", "", "output .jsonl path (required)")
fs.StringVar(&c.deployerStr, "deployer", defaultDeployerAddr,
"bech32 address that becomes the package owner; defaults to gnoland-1's test1 account")
}

func execAddpkg(_ context.Context, cfg *addpkgCfg, io commands.IO, args []string) error {
if cfg.output == "" {
return errors.New("--output is required")
}
if len(args) == 0 {
return errors.New("at least one pkgdir argument is required")
}

deployer, err := crypto.AddressFromBech32(cfg.deployerStr)
if err != nil {
return fmt.Errorf("invalid --deployer %q: %w", cfg.deployerStr, err)
}

var allTxs []gnoland.TxWithMetadata
for _, dir := range args {
txs, err := gnoland.LoadPackagesFromDir(dir, deployer, genesisDeployFee)
if err != nil {
return fmt.Errorf("LoadPackagesFromDir %q: %w", dir, err)
}
// Ensure each tx has Metadata.BlockHeight=0 explicitly,
// even though readMigrationTxs forces it at consume time —
// keeps the .jsonl self-describing.
for i := range txs {
if txs[i].Metadata == nil {
txs[i].Metadata = &gnoland.GnoTxMetadata{}
}
txs[i].Metadata.BlockHeight = 0
// Strip signatures: consumer runs with
// --skip-genesis-sig-verification.
txs[i].Tx.Signatures = []std.Signature{}
}
allTxs = append(allTxs, txs...)
}

var buf strings.Builder
for _, tx := range allTxs {
line, err := amino.MarshalJSON(tx)
if err != nil {
return fmt.Errorf("marshal tx: %w", err)
}
buf.Write(line)
buf.WriteByte('\n')
}

if err := os.WriteFile(cfg.output, []byte(buf.String()), 0o644); err != nil {
return fmt.Errorf("write %s: %w", cfg.output, err)
}

io.Printfln("wrote %d MsgAddPackage txs to %s", len(allTxs), cfg.output)
return nil
}
102 changes: 102 additions & 0 deletions contribs/gnogenesis/internal/fork/addpkg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package fork

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/gnolang/gno/gno.land/pkg/gnoland"
"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
"github.com/gnolang/gno/tm2/pkg/amino"
"github.com/gnolang/gno/tm2/pkg/commands"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// writePkg creates a minimal gno package on disk: gnomod.toml + a
// single .gno file with the right package decl.
func writePkg(t *testing.T, root, pkgPath, body string) string {
t.Helper()
dir := filepath.Join(root, pkgPath)
require.NoError(t, os.MkdirAll(dir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir, "gnomod.toml"),
[]byte("module = \"gno.land/"+pkgPath+"\"\ngno = \"0.9\"\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.gno"),
[]byte(body), 0o644))
return dir
}

func runAddpkg(t *testing.T, args ...string) (string, error) {
t.Helper()
dir := t.TempDir()
outPath := filepath.Join(dir, "out.jsonl")
cfg := &addpkgCfg{output: outPath, deployerStr: defaultDeployerAddr}
io := commands.NewTestIO()
if err := execAddpkg(t.Context(), cfg, io, args); err != nil {
return "", err
}
data, err := os.ReadFile(outPath)
require.NoError(t, err)
return string(data), nil
}

func TestAddpkg_HappyPath(t *testing.T) {
t.Parallel()
root := t.TempDir()
pkgDir := writePkg(t, root, "r/test/foo",
"package foo\n\nfunc Hello() string { return \"hi\" }\n")

out, err := runAddpkg(t, pkgDir)
require.NoError(t, err)

lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
require.Len(t, lines, 1)

var tx gnoland.TxWithMetadata
require.NoError(t, amino.UnmarshalJSON([]byte(lines[0]), &tx))
require.Len(t, tx.Tx.Msgs, 1)
msg, ok := tx.Tx.Msgs[0].(vm.MsgAddPackage)
require.True(t, ok, "msg is MsgAddPackage")
assert.Equal(t, "gno.land/r/test/foo", msg.Package.Path)
assert.Equal(t, defaultDeployerAddr, msg.Creator.String())
require.NotNil(t, tx.Metadata)
assert.Equal(t, int64(0), tx.Metadata.BlockHeight)
assert.Empty(t, tx.Tx.Signatures, "signatures stripped (consumer skips sig verification)")
}

func TestAddpkg_MultiplePackages(t *testing.T) {
t.Parallel()
root := t.TempDir()
a := writePkg(t, root, "r/test/foo", "package foo\n")
b := writePkg(t, root, "r/test/bar", "package bar\n")

out, err := runAddpkg(t, a, b)
require.NoError(t, err)
lines := strings.Split(strings.TrimRight(out, "\n"), "\n")
require.Len(t, lines, 2)
}

func TestAddpkg_RejectsMissingOutput(t *testing.T) {
t.Parallel()
cfg := &addpkgCfg{output: "", deployerStr: defaultDeployerAddr}
err := execAddpkg(t.Context(), cfg, commands.NewTestIO(), []string{"/tmp/dummy"})
require.Error(t, err)
assert.Contains(t, err.Error(), "--output is required")
}

func TestAddpkg_RejectsNoArgs(t *testing.T) {
t.Parallel()
cfg := &addpkgCfg{output: filepath.Join(t.TempDir(), "out.jsonl"), deployerStr: defaultDeployerAddr}
err := execAddpkg(t.Context(), cfg, commands.NewTestIO(), nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "at least one pkgdir")
}

func TestAddpkg_RejectsBadDeployer(t *testing.T) {
t.Parallel()
cfg := &addpkgCfg{output: filepath.Join(t.TempDir(), "out.jsonl"), deployerStr: "not-bech32"}
err := execAddpkg(t.Context(), cfg, commands.NewTestIO(), []string{"/tmp/dummy"})
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid --deployer")
}
52 changes: 52 additions & 0 deletions contribs/gnogenesis/internal/fork/fork.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Package fork provides the `gnogenesis fork` subcommands for building and
// smoke-testing hardfork genesis files.
//
// A hardfork genesis is built from:
// 1. SOURCE CHAIN — provides historical state (genesis + tx history)
// 2. NEW BINARY — the updated gnoland built from this repo
//
// Source modes (auto-detected from --source):
//
// http(s)://... RPC of a running or recently-halted node
// /path/to/dir local node data directory (must contain config/genesis.json)
// /path/to/file exported file: genesis.json (no txs) or .jsonl (txs) or .tar.gz
package fork

import (
"github.com/gnolang/gno/tm2/pkg/commands"
)

// NewForkCmd returns the `gnogenesis fork` parent command with its
// subcommands (`generate`, `test`) attached.
func NewForkCmd(io commands.IO) *commands.Command {
cmd := commands.NewCommand(
commands.Metadata{
Name: "fork",
ShortUsage: "<subcommand> [flags] [<arg>...]",
ShortHelp: "build and smoke-test hardfork genesis files",
LongHelp: `Build a hardfork genesis from a source chain and smoke-test it locally.

Subcommands:
generate Assemble a new-chain genesis.json from a source chain's state + tx history.
test Run an in-memory InitChain replay against a genesis.json (fast smoke-test).
valoper-seed Build a deterministic .jsonl of valopers.Register migration txs from a CSV.
addpkg Build a .jsonl of MsgAddPackage migration txs from local package dirs.

Source modes (auto-detected from --source):
http(s)://... RPC of a running or recently-halted node
/path/to/dir local node data directory (must contain config/genesis.json)
/path/to/file exported file: genesis.json (no txs) or .jsonl (txs) or .tar.gz`,
},
commands.NewEmptyConfig(),
commands.HelpExec,
)

cmd.AddSubCommands(
newGenerateCmd(io),
newTestCmd(io),
newValoperSeedCmd(io),
newAddpkgCmd(io),
)

return cmd
}
Loading
Loading