Skip to content

feat(cli): top-level goten init + plugin registry - #29

Merged
dnahilman merged 1 commit into
mainfrom
feat/cli-goten-init
May 26, 2026
Merged

feat(cli): top-level goten init + plugin registry#29
dnahilman merged 1 commit into
mainfrom
feat/cli-goten-init

Conversation

@dnahilman

Copy link
Copy Markdown
Owner

Closes #25.

Summary

Adds goten init — top-level CLI subcommand that bootstraps a project's migrations from embedded SQL files for core + any plugins declared in goten.config.yaml. Inspired by npx @better-auth/cli generate.

# In a brand-new project, no goten.config.yaml needed yet:
goten init                      # writes ./migrations/*.sql from embed
goten migrate up                # apply to DB

# After adding a plugin later:
go get github.com/dnahilman/goten/plugins/username
# edit goten.config.yaml: migrations.plugins: [username]
goten init                      # core skipped (identical); username files written
goten migrate up                # applies new plugin migrations

Why

Until now, a new project had to git clone the goten repo or curl the raw migration files off GitHub. goten init removes that friction and is idempotent — re-running after adding a plugin only writes the new files.

Design notes

  • Plugin registry is a hardcoded map in cmd/goten/registry.go. Compile-time list of official plugins (currently just username). 3rd-party plugins are explicitly out of scope; unknown names error with the available list.
  • migrations.plugins: accepts both forms — shorthand - username (new) auto-resolves to ./plugins/username/migrations; full path - ./plugins/username/migrations (existing) still works. Auto-detected by resolvePluginEntry based on presence of / or \. This is shared by both init and the existing discovery walker, so migrate up/down/status benefits too.
  • Idempotent semantics: identical destination → silently skipped; divergent → error mentioning --force; missing → written. Per-section summary printed.
  • Best-effort config load: missing goten.config.yaml falls back to a synthetic default (./migrations/, no plugins) so init works as the very first step in a new project.

Steps (matching #25's checklist)

  • 1.1 Expose goten.CoreMigrationsFS via //go:embed
  • 1.2 Export usernameplugin.MigrationsFS
  • 1.3 Plugin registry in cmd/goten/registry.go
  • 1.4 resolvePluginEntry shared between init and discovery.go
  • 1.5 runInit / cmdInit with --force flag
  • 1.6 Top-level subcommand wired in cmd/goten/main.go
  • 1.7 Tests in cmd/goten/init_test.go (8 cases + resolver table-driven)
  • 1.8 CHANGELOG entry

Test plan

  • go test ./cmd/goten/... — all green; 9 new tests:
    • TestRunInit_CoreOnly_FreshProject — fresh dir, no plugins, core files appear with embed-identical content.
    • TestRunInit_CoreAndUsername_Shorthandplugins: [username] shorthand resolves correctly.
    • TestRunInit_CoreAndUsername_FullPath — backwards-compat with explicit path.
    • TestRunInit_Idempotent — second run reports "0 written".
    • TestRunInit_ConflictWithoutForce — pre-existing different content errors and mentions --force; original file untouched.
    • TestRunInit_ConflictWithForce--force overwrites to match embed.
    • TestRunInit_UnknownPlugin — error names the unknown plugin AND lists available plugins.
    • TestRunInit_NoConfigFile_FreshBootstrap — works with no config file at all (synthetic default).
    • TestResolvePluginEntry — table-driven test of the shorthand/path resolver.
  • make build — all six modules compile.
  • Manual smoke: bootstrap from mktemp -d, run init, check files; re-run, output says "0 written, 2 skipped".

🤖 Generated with Claude Code

Closes #25.

Adds `goten init` — a top-level CLI subcommand that bootstraps a new
project by copying embedded migration SQL files for core + any plugins
declared in goten.config.yaml.

Idempotent: identical destinations are skipped, divergent ones require
--force. The synthetic default config kicks in when no goten.config.yaml
exists yet, so this works from a clean directory.

Plugin registry is a hardcoded map keyed by shorthand name. Adding a
new official plugin = one line in cmd/goten/registry.go.

`migrations.plugins:` now accepts shorthand (`- username`) or explicit
path (`- ./plugins/username/migrations`); both are honored by `init`
and by the existing migrate up/down/status flow via the new
resolvePluginEntry helper (replaces inline path-base logic in
discovery.go).

Steps from the issue:
- 1.1 Expose goten.CoreMigrationsFS via //go:embed
- 1.2 Export usernameplugin.MigrationsFS
- 1.3 Plugin registry in cmd/goten/registry.go
- 1.4 resolvePluginEntry shared by init + discovery
- 1.5 runInit / cmdInit with --force flag
- 1.6 Wire init as top-level subcommand
- 1.7 8 new init tests + 1 resolver test, all existing tests still pass
- 1.8 CHANGELOG entry

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dnahilman
dnahilman merged commit decf0be into main May 26, 2026
2 checks passed
@dnahilman
dnahilman deleted the feat/cli-goten-init branch May 26, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): top-level goten init + plugin registry

1 participant