Repository: ariga/atlas
Pull request #753 (2022-04-29 19:52:39)

cmd/atlascmd: minor style fixes and rename action package to atlascmd


Patch:
diff --git a/cmd/action/apply.go b/cmd/action/apply.go
deleted file mode 100644
index 3bcc5301..00000000
--- a/cmd/action/apply.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"context"
-	"errors"
-	"io/ioutil"
-	"strings"
-
-	"ariga.io/atlas/sql/schema"
-	"ariga.io/atlas/sql/sqlclient"
-
-	"github.com/manifoldco/promptui"
-	"github.com/spf13/cobra"
-)
-
-var (
-	// ApplyFlags are the flags used in Apply command.
-	ApplyFlags struct {
-		URL         string
-		DevURL      string
-		File        string
-		Web         bool
-		Addr        string
-		DryRun      bool
-		Schema      []string
-		AutoApprove bool
-		Verbose     bool
-	}
-	// ApplyCmd represents the apply command.
-	ApplyCmd = &cobra.Command{
-		Use:   "apply",
-		Short: "Apply an atlas schema to a target database.",
-		// Use 80-columns as max width.
-		Long: `'atlas schema apply' plans and executes a database migration to bring a given
-database to the state described in the Atlas schema file. Before running the
-migration, Atlas will print the migration plan and prompt the user for approval.
-
-If run with the "--dry-run" flag, atlas will exit after printing out the planned
-migration.`,
-		Run: CmdApplyRun,
-		Example: `  atlas schema apply -u "mysql://user:pass@localhost/dbname" -f atlas.hcl
-  atlas schema apply -u "mysql://localhost" -f atlas.hcl --schema prod --schema staging
-  atlas schema apply -u "mysql://user:pass@localhost:3306/dbname" -f atlas.hcl --dry-run
-  atlas schema apply -u "mariadb://user:pass@localhost:3306/dbname" -f atlas.hcl
-  atlas schema apply --url "postgres://user:pass@host:port/dbname?sslmode=disable" -f atlas.hcl
-  atlas schema apply -u "sqlite://file:ex1.db?_fk=1" -f atlas.hcl`,
-	}
-)
-
-const (
-	answerApply = "Apply"
-	answerAbort = "Abort"
-)
-
-func init() {
-	schemaCmd.AddCommand(ApplyCmd)
-	ApplyCmd.Flags().SortFlags = false
-	ApplyCmd.Flags().StringVarP(&ApplyFlags.File, "file", "f", "", "[/path/to/file] file containing the HCL schema.")
-	ApplyCmd.Flags().StringVarP(&ApplyFlags.URL, "url", "u", "", "URL to the database using the format:\n[driver://username:password@address/dbname?param=value]")
-	ApplyCmd.Flags().StringSliceVarP(&ApplyFlags.Schema, "schema", "s", nil, "Set schema names.")
-	ApplyCmd.Flags().StringVarP(&ApplyFlags.DevURL, "dev-url", "", "", "URL for the dev database. Used to validate schemas and calculate diffs\nbefore running migration.")
-	ApplyCmd.Flags().BoolVarP(&ApplyFlags.DryRun, "dry-run", "", false, "Dry-run. Print SQL plan without prompting for execution.")
-	ApplyCmd.Flags().BoolVarP(&ApplyFlags.AutoApprove, "auto-approve", "", false, "Auto approve. Apply the schema changes without prompting for approval.")
-	ApplyCmd.Flags().BoolVarP(&ApplyFlags.Web, "web", "w", false, "Open in a local Atlas UI.")
-	ApplyCmd.Flags().StringVarP(&ApplyFlags.Addr, "addr", "", ":5800", "used with -w, local address to bind the server to.")
-	ApplyCmd.Flags().BoolVarP(&ApplyFlags.Verbose, migrateDiffFlagVerbose, "", false, "enable verbose logging")
-	cobra.CheckErr(ApplyCmd.MarkFlagRequired("url"))
-	cobra.CheckErr(ApplyCmd.MarkFlagRequired("file"))
-	dsn2url(ApplyCmd, &ApplyFlags.URL)
-}
-
-// CmdApplyRun is the command used when running CLI.
-func CmdApplyRun(cmd *cobra.Command, _ []string) {
-	if ApplyFlags.Web {
-		schemaCmd.PrintErrln("The Atlas UI is not available in this release.")
-		return
-	}
-	c, err := sqlclient.Open(cmd.Context(), ApplyFlags.URL)
-	cobra.CheckErr(err)
-	defer c.Close()
-	applyRun(cmd.Context(), c, ApplyFlags.File, ApplyFlags.DryRun, ApplyFlags.AutoApprove)
-}
-
-func applyRun(ctx context.Context, client *sqlclient.Client, file string, dryRun, autoApprove bool) {
-	schemas := ApplyFlags.Schema
-	if client.URL.Schema != "" {
-		schemas = append(schemas, client.URL.Schema)
-	}
-	realm, err := client.InspectRealm(ctx, &schema.InspectRealmOption{
-		Schemas: schemas,
-	})
-	cobra.CheckErr(err)
-	f, err := ioutil.ReadFile(file)
-	cobra.CheckErr(err)
-	desired := &schema.Realm{}
-	cobra.CheckErr(client.Eval(f, desired, nil))
-	if len(schemas) > 0 {
-		// Validate all schemas in file were selected by user.
-		sm := make(map[string]bool, len(schemas))
-		for _, s := range schemas {
-			sm[s] = true
-		}
-		for _, s := range desired.Schemas {
-			if !sm[s.Name] {
-				schemaCmd.Printf("schema %q from file %q was not selected %q, all schemas defined in file must be selected\n", s.Name, file, schemas)
-				return
-			}
-		}
-	}
-	if _, ok := client.Driver.(schema.Normalizer); ok && ApplyFlags.DevURL != "" {
-		dev, err := sqlclient.Open(ctx, ApplyFlags.DevURL)
-		cobra.CheckErr(err)
-		defer dev.Close()
-		desired, err = dev.Driver.(schema.Normalizer).NormalizeRealm(ctx, desired)
-		cobra.CheckErr(err)
-	}
-	changes, err := client.RealmDiff(realm, desired)
-	cobra.CheckErr(err)
-	if len(changes) == 0 {
-		schemaCmd.Println("Schema is synced, no changes to be made")
-		return
-	}
-	p, err := client.PlanChanges(ctx, "plan", changes)
-	cobra.CheckErr(err)
-	schemaCmd.Println("-- Planned Changes:")
-	for _, c := range p.Changes {
-		if c.Comment != "" {
-			schemaCmd.Println("--", strings.ToUpper(c.Comment[:1])+c.Comment[1:])
-		}
-		schemaCmd.Println(c.Cmd)
-	}
-	if dryRun {
-		return
-	}
-	if autoApprove || promptUser() {
-		cobra.CheckErr(client.ApplyChanges(ctx, changes))
-	}
-}
-
-func promptUser() bool {
-	prompt := promptui.Select{
-		Label: "Are you sure?",
-		Items: []string{answerApply, answerAbort},
-	}
-	_, result, err := prompt.Run()
-	cobra.CheckErr(err)
-	return result == answerApply
-}
-
-func dsn2url(cmd *cobra.Command, p *string) {
-	cmd.Flags().StringVarP(p, "dsn", "d", "", "")
-	cobra.CheckErr(cmd.Flags().MarkHidden("dsn"))
-	cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
-		dsnF, urlF := cmd.Flag("dsn"), cmd.Flag("url")
-		switch {
-		case !dsnF.Changed && !urlF.Changed:
-			return errors.New(`required flag "url" was not set`)
-		case dsnF.Changed && urlF.Changed:
-			return errors.New(`both flags "url" and "dsn" were set`)
-		case dsnF.Changed && !urlF.Changed:
-			urlF.Changed = true
-			urlF.Value = dsnF.Value
-		}
-		return nil
-	}
-}
diff --git a/cmd/action/env.go b/cmd/action/env.go
deleted file mode 100644
index 040d41bb..00000000
--- a/cmd/action/env.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"fmt"
-	"os"
-
-	"ariga.io/atlas/cmd/internal/update"
-
-	"github.com/spf13/cobra"
-)
-
-// EnvCmd is the "atlas env" command.
-var EnvCmd = &cobra.Command{
-	Use:   "env",
-	Short: "Print atlas environment variables.",
-	Long: `'atlas env' prints atlas environment information.
-
-Every set environment param will be printed in the form of NAME=VALUE.
-
-List of supported environment parameters:
-* ATLAS_NO_UPDATE_NOTIFIER: On any command, the CLI will check for new releases using the GitHub API.
-  This check will happen at most once every 24 hours. To cancel this behavior, set the environment 
-  variable "ATLAS_NO_UPDATE_NOTIFIER".`,
-	Run: func(cmd *cobra.Command, args []string) {
-		keys := []string{update.AtlasNoUpdateNotifier}
-		for _, k := range keys {
-			if v, ok := os.LookupEnv(k); ok {
-				cmd.Println(fmt.Sprintf("%s=%s", k, v))
-			}
-		}
-	},
-}
-
-func init() {
-	RootCmd.AddCommand(EnvCmd)
-}
diff --git a/cmd/action/fmt.go b/cmd/action/fmt.go
deleted file mode 100644
index c112fa27..00000000
--- a/cmd/action/fmt.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"bytes"
-	"io/fs"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/hashicorp/hcl/v2/hclwrite"
-	"github.com/spf13/cobra"
-)
-
-var (
-	// FmtCmd represents the fmt command.
-	FmtCmd = &cobra.Command{
-		Use:   "fmt [path ...]",
-		Short: "Formats Atlas HCL files",
-		Long: "`atlas schema fmt`" + ` formats all ".hcl" files under the given path using
-canonical HCL layout style as defined by the github.com/hashicorp/hcl/v2/hclwrite package.
-Unless stated otherwise, the fmt command will use the current directory.
-
-After running, the command will print the names of the files it has formatted. If all
-files in the directory are formatted, no input will be printed out.
-`,
-		Run: CmdFmtRun,
-	}
-)
-
-func init() {
-	schemaCmd.AddCommand(FmtCmd)
-}
-
-// CmdFmtRun formats all HCL files in a given directory using canonical HCL formatting
-// rules.
-func CmdFmtRun(cmd *cobra.Command, args []string) {
-	if len(args) == 0 {
-		args = append(args, "./")
-	}
-	for _, path := range args {
-		handlePath(cmd, path)
-	}
-}
-
-func handlePath(cmd *cobra.Command, path string) {
-	tasks, err := tasks(path)
-	cobra.CheckErr(err)
-	for _, task := range tasks {
-		changed, err := fmtFile(task)
-		cobra.CheckErr(err)
-		if changed {
-			cmd.Println(task.path)
-		}
-	}
-}
-
-func tasks(path string) ([]fmttask, error) {
-	var tasks []fmttask
-	stat, err := os.Stat(path)
-	if err != nil {
-		return nil, err
-	}
-	if !stat.IsDir() {
-		if strings.HasSuffix(path, ".hcl") {
-			tasks = append(tasks, fmttask{
-				path: path,
-				info: stat,
-			})
-		}
-		return tasks, nil
-	}
-	all, err := ioutil.ReadDir(path)
-	if err != nil {
-		return nil, err
-	}
-	for _, f := range all {
-		if f.IsDir() {
-			continue
-		}
-		if strings.HasSuffix(f.Name(), ".hcl") {
-			tasks = append(tasks, fmttask{
-				path: filepath.Join(path, f.Name()),
-				info: f,
-			})
-		}
-	}
-	return tasks, nil
-}
-
-type fmttask struct {
-	path string
-	info fs.FileInfo
-}
-
-// fmtFile tries to format a file and reports if formatting occurred.
-func fmtFile(task fmttask) (bool, error) {
-	orig, err := os.ReadFile(task.path)
-	if err != nil {
-		return false, err
-	}
-	formatted := hclwrite.Format(orig)
-	if !bytes.Equal(formatted, orig) {
-		return true, os.WriteFile(task.path, formatted, task.info.Mode())
-	}
-	return false, nil
-}
diff --git a/cmd/action/inspect.go b/cmd/action/inspect.go
deleted file mode 100644
index e0873bd0..00000000
--- a/cmd/action/inspect.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"ariga.io/atlas/sql/schema"
-	"ariga.io/atlas/sql/sqlclient"
-
-	"github.com/spf13/cobra"
-)
-
-var (
-	// InspectFlags are the flags used in Inspect command.
-	InspectFlags struct {
-		URL    string
-		Web    bool
-		Addr   string
-		Schema []string
-	}
-	// InspectCmd represents the inspect command.
-	InspectCmd = &cobra.Command{
-		Use:   "inspect",
-		Short: "Inspect an a database's and print its schema in Atlas DDL syntax.",
-		Long: "`atlas schema inspect`" + ` connects to the given database and inspects its schema.
-It then prints to the screen the schema of that database in Atlas DDL syntax. This output can be 
-saved to a file, commonly by redirecting the output to a file named with a ".hcl" suffix:
-
-  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname" > atlas.hcl
-
-This file can then be edited and used with the` + " `atlas schema apply` " + `command to plan
-and execute schema migrations against the given database. In cases where users wish to inspect
-all multiple schemas in a given database (for instance a MySQL server may contain multiple named
-databases), omit the relevant part from the url, e.g. "mysql://user:pass@localhost:3306/".
-To select specific schemas from the databases, users may use the "--schema" (or "-s" shorthand)
-flag.
-	`,
-		Run: CmdInspectRun,
-		Example: `  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname"
-  atlas schema inspect -u "mariadb://user:pass@localhost:3306/" --schema=schemaA,schemaB -s schemaC
-  atlas schema inspect --url "postgres://user:pass@host:port/dbname?sslmode=disable"
-  atlas schema inspect -u "sqlite://file:ex1.db?_fk=1"`,
-	}
-)
-
-func init() {
-	schemaCmd.AddCommand(InspectCmd)
-	InspectCmd.Flags().StringVarP(&InspectFlags.URL, "url", "u", "", "[driver://username:password@protocol(address)/dbname?param=value] Select data source using the url format")
-	InspectCmd.Flags().BoolVarP(&InspectFlags.Web, "web", "w", false, "Open in a local Atlas UI")
-	InspectCmd.Flags().StringVarP(&InspectFlags.Addr, "addr", "", ":5800", "Used with -w, local address to bind the server to")
-	InspectCmd.Flags().StringSliceVarP(&InspectFlags.Schema, "schema", "s", nil, "Set schema name")
-	cobra.CheckErr(InspectCmd.MarkFlagRequired("url"))
-	dsn2url(InspectCmd, &InspectFlags.URL)
-}
-
-// CmdInspectRun is the command used when running CLI.
-func CmdInspectRun(cmd *cobra.Command, _ []string) {
-	if InspectFlags.Web {
-		schemaCmd.PrintErrln("The Atlas UI is not available in this release.")
-		return
-	}
-	client, err := sqlclient.Open(cmd.Context(), InspectFlags.URL)
-	cobra.CheckErr(err)
-	defer client.Close()
-	schemas := InspectFlags.Schema
-	if client.URL.Schema != "" {
-		schemas = append(schemas, client.URL.Schema)
-	}
-	s, err := client.InspectRealm(cmd.Context(), &schema.InspectRealmOption{
-		Schemas: schemas,
-	})
-	cobra.CheckErr(err)
-	ddl, err := client.MarshalSpec(s)
-	cobra.CheckErr(err)
-	schemaCmd.Print(string(ddl))
-}
diff --git a/cmd/action/root.go b/cmd/action/root.go
deleted file mode 100644
index eb20dcfb..00000000
--- a/cmd/action/root.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"ariga.io/atlas/cmd/internal/update"
-
-	"github.com/spf13/cobra"
-)
-
-// RootCmd represents the base command when called without any subcommands
-var RootCmd = &cobra.Command{
-	Use:   "atlas",
-	Short: "A database toolkit.",
-}
-
-// CheckForUpdate exposes internal update logic to CLI
-func CheckForUpdate() {
-	update.CheckForUpdate(version, RootCmd.PrintErrln)
-}
diff --git a/cmd/action/schema.go b/cmd/action/schema.go
deleted file mode 100644
index a1cef211..00000000
--- a/cmd/action/schema.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"github.com/spf13/cobra"
-)
-
-// schemaCmd represents the schema command
-var schemaCmd = &cobra.Command{
-	Use:   "schema",
-	Short: "Work with atlas schemas.",
-	Long:  "The `atlas schema` command groups subcommands for working with Atlas schemas.",
-}
-
-func init() {
-	RootCmd.AddCommand(schemaCmd)
-}
diff --git a/cmd/action/version.go b/cmd/action/version.go
deleted file mode 100644
index ab44cbed..00000000
--- a/cmd/action/version.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021-present The Atlas Authors. All rights reserved.
-// This source code is licensed under the Apache 2.0 license found
-// in the LICENSE file in the root directory of this source tree.
-
-package action
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/spf13/cobra"
-	"golang.org/x/mod/semver"
-)
-
-// version is the atlas CLI build version
-// Should be set by build script "-X 'ariga.io/atlas/cmd/action.version=${version}'"
-var version string
-
-var versionCmd = &cobra.Command{
-	Use:   "version",
-	Short: "Prints this Atlas CLI version information.",
-	Run: func(cmd *cobra.Command, args []string) {
-		v, u := parse(version)
-		RootCmd.Printf("atlas CLI version %s\n%s\n", v, u)
-	},
-}
-
-func init() {
-	RootCmd.AddCommand(versionCmd)
-}
-
-// parse returns a user facing version and release notes url
-func parse(version string) (string, string) {
-	u := "https://github.com/ariga/atlas/releases/latest"
-	if ok := semver.IsValid(version); !ok {
-		return "- development", u
-	}
-	s := strings.Split(version, "-")
-	if len(s) != 0 && s[len(s)-1] != "canary" {
-		u = fmt.Sprintf("https://github.com/ariga/atlas/releases/tag/%s", version)
-	}
-	return version, u
-}
diff --git a/cmd/atlas/doc.go b/cmd/atlas/doc.go
index 7794ec6b..bc422dc8 100644
--- a/cmd/atlas/doc.go
+++ b/cmd/atlas/doc.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"text/template"
 
-	"ariga.io/atlas/cmd/action"
+	"ariga.io/atlas/cmd/atlascmd"
 
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra/doc"
@@ -37,7 +37,7 @@ func main() {
 	if err := t.ExecuteTemplate(f, "header", nil); err != nil {
 		log.Fatal(err)
 	}
-	blocks := prepare(action.RootCmd, make([]*block, 0), 0)
+	blocks := prepare(atlascmd.Root, make([]*block, 0), 0)
 	if err := t.ExecuteTemplate(f, "body", struct {
 		Blocks []*block
 	}{Blocks: blocks}); err != nil {
diff --git a/cmd/atlas/main.go b/cmd/atlas/main.go
index 187ebf2e..24643320 100644
--- a/cmd/atlas/main.go
+++ b/cmd/atlas/main.go
@@ -5,8 +5,8 @@ import (
 	"os"
 	"os/signal"
 
-	"ariga.io/atlas/cmd/action"
-	_ "ariga.io/atlas/cmd/internal/docker"
+	"ariga.io/atlas/cmd/atlascmd"
+	_ "ariga.io/atlas/cmd/atlascmd/docker"
 	_ "ariga.io/atlas/sql/mysql"
 	_ "ariga.io/atlas/sql/postgres"
 	_ "ariga.io/atlas/sql/sqlite"
@@ -18,15 +18,15 @@ import (
 
 func main() {
 	ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
-	action.RootCmd.SetOut(os.Stdout)
-	err := action.RootCmd.ExecuteContext(ctx)
+	atlascmd.Root.SetOut(os.Stdout)
+	err := atlascmd.Root.ExecuteContext(ctx)
 	// Print error from command
 	if err != nil {
-		action.RootCmd.PrintErrln("Error:", err)
+		atlascmd.Root.PrintErrln("Error:", err)
 	}
-	// Check for update
-	action.CheckForUpdate()
-	// Exit code according to command success
+	// Check for update.
+	atlascmd.CheckForUpdate()
+	// Exit code according to command success.
 	if err != nil {
 		os.Exit(1)
 	}
diff --git a/cmd/atlascmd/atlascmd.go b/cmd/atlascmd/atlascmd.go
new file mode 100644
index 00000000..6be07309
--- /dev/null
+++ b/cmd/atlascmd/atlascmd.go
@@ -0,0 +1,86 @@
+// Copyright 2021-present The Atlas Authors. All rights reserved.
+// This source code is licensed under the Apache 2.0 license found
+// in the LICENSE file in the root directory of this source tree.
+
+// Package atlascmd holds the atlas commands used to build
+// an atlas distribution.
+package atlascmd
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"ariga.io/atlas/cmd/atlascmd/update"
+
+	"github.com/spf13/cobra"
+	"golang.org/x/mod/semver"
+)
+
+var (
+	// Root represents the root command when called without any subcommands.
+	Root = &cobra.Command{
+		Use:   "atlas",
+		Short: "A database toolkit.",
+	}
+
+	// version is the atlas CLI build version
+	// Should be set by build script "-X 'ariga.io/atlas/cmd/action.version=${version}'"
+	version string
+
+	// schemaCmd represents the subcommand 'atlas version'.
+	versionCmd = &cobra.Command{
+		Use:   "version",
+		Short: "Prints this Atlas CLI version information.",
+		Run: func(cmd *cobra.Command, args []string) {
+			v, u := parse(version)
+			Root.Printf("atlas version %s\n%s\n", v, u)
+		},
+	}
+
+	// envCmd represents the subcommand 'atlas env'.
+	envCmd = &cobra.Command{
+		Use:   "env",
+		Short: "Print atlas environment variables.",
+		Long: `'atlas env' prints atlas environment information.
+
+Every set environment param will be printed in the form of NAME=VALUE.
+
+List of supported environment parameters:
+* ATLAS_NO_UPDATE_NOTIFIER: On any command, the CLI will check for new releases using the GitHub API.
+  This check will happen at most once every 24 hours. To cancel this behavior, set the environment 
+  variable "ATLAS_NO_UPDATE_NOTIFIER".`,
+		Run: func(cmd *cobra.Command, args []string) {
+			keys := []string{update.AtlasNoUpdateNotifier}
+			for _, k := range keys {
+				if v, ok := os.LookupEnv(k); ok {
+					cmd.Println(fmt.Sprintf("%s=%s", k, v))
+				}
+			}
+		},
+	}
+)
+
+// CheckForUpdate exposes internal update logic to CLI
+func CheckForUpdate() {
+	update.CheckForUpdate(version, Root.PrintErrln)
+}
+
+func init() {
+	Root.AddCommand(envCmd)
+	Root.AddCommand(schemaCmd)
+	Root.AddCommand(versionCmd)
+}
+
+// parse returns a user facing version and release notes url
+func parse(version string) (string, string) {
+	u := "https://github.com/ariga/atlas/releases/latest"
+	if ok := semver.IsValid(version); !ok {
+		return "- development", u
+	}
+	s := strings.Split(version, "-")
+	if len(s) != 0 && s[len(s)-1] != "canary" {
+		u = fmt.Sprintf("https://github.com/ariga/atlas/releases/tag/%s", version)
+	}
+	return version, u
+}
diff --git a/cmd/action/diff.go b/cmd/atlascmd/diff.go
similarity index 96%
rename from cmd/action/diff.go
rename to cmd/atlascmd/diff.go
index bb73d12f..3a8b1a18 100644
--- a/cmd/action/diff.go
+++ b/cmd/atlascmd/diff.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"strings"
@@ -23,7 +23,7 @@ func newDiffCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "diff",
 		Short: "Calculate and print the diff between two schemas.",
-		Long: "`atlas schema diff`" + ` connects to two given databases, inspects
+		Long: `'atlas schema diff' connects to two given databases, inspects
 them, calculates the difference in their schemas, and prints a plan of
 SQL statements to migrate the "from" database to the schema of the "to" database.`,
 		Run: func(cmd *cobra.Command, args []string) {
diff --git a/cmd/internal/docker/docker.go b/cmd/atlascmd/docker/docker.go
similarity index 97%
rename from cmd/internal/docker/docker.go
rename to cmd/atlascmd/docker/docker.go
index e396eb13..80feffb6 100644
--- a/cmd/internal/docker/docker.go
+++ b/cmd/atlascmd/docker/docker.go
@@ -22,7 +22,7 @@ import (
 
 	"ariga.io/atlas/sql/sqlclient"
 
-	mysqld "github.com/go-sql-driver/mysql"
+	"github.com/go-sql-driver/mysql"
 )
 
 const pass = "pass"
@@ -205,8 +205,8 @@ func (c *Container) Close() error {
 // Wait waits for this container to be ready.
 func (c *Container) Wait(ctx context.Context, timeout time.Duration) error {
 	fmt.Fprintln(c.out, "Waiting for service to be ready ... ")
-	mysqld.SetLogger(log.New(ioutil.Discard, "", 1))
-	defer mysqld.SetLogger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
+	mysql.SetLogger(log.New(ioutil.Discard, "", 1))
+	defer mysql.SetLogger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
 	if timeout > time.Minute {
 		timeout = time.Minute
 	}
diff --git a/cmd/internal/docker/docker_test.go b/cmd/atlascmd/docker/docker_test.go
similarity index 100%
rename from cmd/internal/docker/docker_test.go
rename to cmd/atlascmd/docker/docker_test.go
diff --git a/cmd/action/migrate.go b/cmd/atlascmd/migrate.go
similarity index 98%
rename from cmd/action/migrate.go
rename to cmd/atlascmd/migrate.go
index a52f40a5..dae4e6f5 100644
--- a/cmd/action/migrate.go
+++ b/cmd/atlascmd/migrate.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"context"
@@ -67,7 +67,7 @@ var (
 			return nil
 		},
 	}
-	// MigrateDiffCmd represents the migrate diff command.
+	// MigrateDiffCmd represents the 'atlas migrate diff' subcommand.
 	MigrateDiffCmd = &cobra.Command{
 		Use:   "diff",
 		Short: "Compute the diff between the migration directory and a connected database and create a new migration file.",
@@ -102,7 +102,7 @@ the atlas.sum file. If there is a mismatch it will be reported.`,
 
 func init() {
 	// Add sub-commands.
-	RootCmd.AddCommand(MigrateCmd)
+	Root.AddCommand(MigrateCmd)
 	MigrateCmd.AddCommand(MigrateDiffCmd)
 	MigrateCmd.AddCommand(MigrateValidateCmd)
 	MigrateCmd.AddCommand(MigrateHashCmd)
diff --git a/cmd/action/migrations/20220318104614_initial.sql b/cmd/atlascmd/migrations/20220318104614_initial.sql
similarity index 100%
rename from cmd/action/migrations/20220318104614_initial.sql
rename to cmd/atlascmd/migrations/20220318104614_initial.sql
diff --git a/cmd/action/migrations/atlas.sum b/cmd/atlascmd/migrations/atlas.sum
similarity index 100%
rename from cmd/action/migrations/atlas.sum
rename to cmd/atlascmd/migrations/atlas.sum
diff --git a/cmd/atlascmd/schema.go b/cmd/atlascmd/schema.go
new file mode 100644
index 00000000..8e8a4799
--- /dev/null
+++ b/cmd/atlascmd/schema.go
@@ -0,0 +1,336 @@
+// Copyright 2021-present The Atlas Authors. All rights reserved.
+// This source code is licensed under the Apache 2.0 license found
+// in the LICENSE file in the root directory of this source tree.
+
+package atlascmd
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"io/fs"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"ariga.io/atlas/sql/schema"
+	"ariga.io/atlas/sql/sqlclient"
+
+	"github.com/hashicorp/hcl/v2/hclwrite"
+	"github.com/manifoldco/promptui"
+	"github.com/spf13/cobra"
+)
+
+var (
+	// schemaCmd represents the subcommand 'atlas schema'.
+	schemaCmd = &cobra.Command{
+		Use:   "schema",
+		Short: "Work with atlas schemas.",
+		Long:  "The `atlas schema` command groups subcommands for working with Atlas schemas.",
+	}
+
+	// ApplyFlags are the flags used in SchemaApply command.
+	ApplyFlags struct {
+		URL         string
+		DevURL      string
+		File        string
+		Web         bool
+		Addr        string
+		DryRun      bool
+		Schema      []string
+		AutoApprove bool
+		Verbose     bool
+	}
+	// SchemaApply represents the 'atlas schema apply' subcommand command.
+	SchemaApply = &cobra.Command{
+		Use:   "apply",
+		Short: "Apply an atlas schema to a target database.",
+		// Use 80-columns as max width.
+		Long: `'atlas schema apply' plans and executes a database migration to bring a given
+database to the state described in the Atlas schema file. Before running the
+migration, Atlas will print the migration plan and prompt the user for approval.
+
+If run with the "--dry-run" flag, atlas will exit after printing out the planned
+migration.`,
+		Run: CmdApplyRun,
+		Example: `  atlas schema apply -u "mysql://user:pass@localhost/dbname" -f atlas.hcl
+  atlas schema apply -u "mysql://localhost" -f atlas.hcl --schema prod --schema staging
+  atlas schema apply -u "mysql://user:pass@localhost:3306/dbname" -f atlas.hcl --dry-run
+  atlas schema apply -u "mariadb://user:pass@localhost:3306/dbname" -f atlas.hcl
+  atlas schema apply --url "postgres://user:pass@host:port/dbname?sslmode=disable" -f atlas.hcl
+  atlas schema apply -u "sqlite://file:ex1.db?_fk=1" -f atlas.hcl`,
+	}
+
+	// InspectFlags are the flags used in SchemaInspect command.
+	InspectFlags struct {
+		URL    string
+		Web    bool
+		Addr   string
+		Schema []string
+	}
+	// SchemaInspect represents the 'atlas schema inspect' subcommand.
+	SchemaInspect = &cobra.Command{
+		Use:   "inspect",
+		Short: "Inspect a database's and print its schema in Atlas DDL syntax.",
+		Long: `'atlas schema inspect' connects to the given database and inspects its schema.
+It then prints to the screen the schema of that database in Atlas DDL syntax. This output can be
+saved to a file, commonly by redirecting the output to a file named with a ".hcl" suffix:
+
+  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname" > atlas.hcl
+
+This file can then be edited and used with the` + " `atlas schema apply` " + `command to plan
+and execute schema migrations against the given database. In cases where users wish to inspect
+all multiple schemas in a given database (for instance a MySQL server may contain multiple named
+databases), omit the relevant part from the url, e.g. "mysql://user:pass@localhost:3306/".
+To select specific schemas from the databases, users may use the "--schema" (or "-s" shorthand)
+flag.
+	`,
+		Run: CmdInspectRun,
+		Example: `  atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname"
+  atlas schema inspect -u "mariadb://user:pass@localhost:3306/" --schema=schemaA,schemaB -s schemaC
+  atlas schema inspect --url "postgres://user:pass@host:port/dbname?sslmode=disable"
+  atlas schema inspect -u "sqlite://file:ex1.db?_fk=1"`,
+	}
+
+	// SchemaFmt represents the 'atlas schema fmt' subcommand.
+	SchemaFmt = &cobra.Command{
+		Use:   "fmt [path ...]",
+		Short: "Formats Atlas HCL files",
+		Long: `'atlas schema fmt' formats all ".hcl" files under the given path using
+canonical HCL layout style as defined by the github.com/hashicorp/hcl/v2/hclwrite package.
+Unless stated otherwise, the fmt command will use the current directory.
+
+After running, the command will print the names of the files it has formatted. If all
+files in the directory are formatted, no input will be printed out.
+`,
+		Run: CmdFmtRun,
+	}
+)
+
+const (
+	answerApply = "SchemaApply"
+	answerAbort = "Abort"
+)
+
+func init() {
+	// Schema apply flags.
+	schemaCmd.AddCommand(SchemaApply)
+	SchemaApply.Flags().SortFlags = false
+	SchemaApply.Flags().StringVarP(&ApplyFlags.File, "file", "f", "", "[/path/to/file] file containing the HCL schema.")
+	SchemaApply.Flags().StringVarP(&ApplyFlags.URL, "url", "u", "", "URL to the database using the format:\n[driver://username:password@address/dbname?param=value]")
+	SchemaApply.Flags().StringSliceVarP(&ApplyFlags.Schema, "schema", "s", nil, "Set schema names.")
+	SchemaApply.Flags().StringVarP(&ApplyFlags.DevURL, "dev-url", "", "", "URL for the dev database. Used to validate schemas and calculate diffs\nbefore running migration.")
+	SchemaApply.Flags().BoolVarP(&ApplyFlags.DryRun, "dry-run", "", false, "Dry-run. Print SQL plan without prompting for execution.")
+	SchemaApply.Flags().BoolVarP(&ApplyFlags.AutoApprove, "auto-approve", "", false, "Auto approve. Apply the schema changes without prompting for approval.")
+	SchemaApply.Flags().BoolVarP(&ApplyFlags.Web, "web", "w", false, "Open in a local Atlas UI.")
+	SchemaApply.Flags().StringVarP(&ApplyFlags.Addr, "addr", "", ":5800", "used with -w, local address to bind the server to.")
+	SchemaApply.Flags().BoolVarP(&ApplyFlags.Verbose, migrateDiffFlagVerbose, "", false, "enable verbose logging")
+	cobra.CheckErr(SchemaApply.MarkFlagRequired("url"))
+	cobra.CheckErr(SchemaApply.MarkFlagRequired("file"))
+	dsn2url(SchemaApply, &ApplyFlags.URL)
+
+	// Schema inspect flags.
+	schemaCmd.AddCommand(SchemaInspect)
+	SchemaInspect.Flags().StringVarP(&InspectFlags.URL, "url", "u", "", "[driver://username:password@protocol(address)/dbname?param=value] Select data source using the url format")
+	SchemaInspect.Flags().BoolVarP(&InspectFlags.Web, "web", "w", false, "Open in a local Atlas UI")
+	SchemaInspect.Flags().StringVarP(&InspectFlags.Addr, "addr", "", ":5800", "Used with -w, local address to bind the server to")
+	SchemaInspect.Flags().StringSliceVarP(&InspectFlags.Schema, "schema", "s", nil, "Set schema name")
+	cobra.CheckErr(SchemaInspect.MarkFlagRequired("url"))
+	dsn2url(SchemaInspect, &InspectFlags.URL)
+
+	// Schema fmt.
+	schemaCmd.AddCommand(SchemaFmt)
+}
+
+// CmdInspectRun is the command used when running CLI.
+func CmdInspectRun(cmd *cobra.Command, _ []string) {
+	if InspectFlags.Web {
+		schemaCmd.PrintErrln("The Alas UI is not available in this release.")
+		return
+	}
+	client, err := sqlclient.Open(cmd.Context(), InspectFlags.URL)
+	cobra.CheckErr(err)
+	defer client.Close()
+	schemas := InspectFlags.Schema
+	if client.URL.Schema != "" {
+		schemas = append(schemas, client.URL.Schema)
+	}
+	s, err := client.InspectRealm(cmd.Context(), &schema.InspectRealmOption{
+		Schemas: schemas,
+	})
+	cobra.CheckErr(err)
+	ddl, err := client.MarshalSpec(s)
+	cobra.CheckErr(err)
+	schemaCmd.Print(string(ddl))
+}
+
+// CmdApplyRun is the command used when running CLI.
+func CmdApplyRun(cmd *cobra.Command, _ []string) {
+	if ApplyFlags.Web {
+		schemaCmd.PrintErrln("The Atlas UI is not available in this release.")
+		return
+	}
+	c, err := sqlclient.Open(cmd.Context(), ApplyFlags.URL)
+	cobra.CheckErr(err)
+	defer c.Close()
+	applyRun(cmd.Context(), c, ApplyFlags.File, ApplyFlags.DryRun, ApplyFlags.AutoApprove)
+}
+
+// CmdFmtRun formats all HCL files in a given directory using canonical HCL formatting
+// rules.
+func CmdFmtRun(cmd *cobra.Command, args []string) {
+	if len(args) == 0 {
+		args = append(args, "./")
+	}
+	for _, path := range args {
+		handlePath(cmd, path)
+	}
+}
+
+func applyRun(ctx context.Context, client *sqlclient.Client, file string, dryRun, autoApprove bool) {
+	schemas := ApplyFlags.Schema
+	if client.URL.Schema != "" {
+		schemas = append(schemas, client.URL.Schema)
+	}
+	realm, err := client.InspectRealm(ctx, &schema.InspectRealmOption{
+		Schemas: schemas,
+	})
+	cobra.CheckErr(err)
+	f, err := ioutil.ReadFile(file)
+	cobra.CheckErr(err)
+	desired := &schema.Realm{}
+	cobra.CheckErr(client.Eval(f, desired, nil))
+	if len(schemas) > 0 {
+		// Validate all schemas in file were selected by user.
+		sm := make(map[string]bool, len(schemas))
+		for _, s := range schemas {
+			sm[s] = true
+		}
+		for _, s := range desired.Schemas {
+			if !sm[s.Name] {
+				schemaCmd.Printf("schema %q from file %q was not selected %q, all schemas defined in file must be selected\n", s.Name, file, schemas)
+				return
+			}
+		}
+	}
+	if _, ok := client.Driver.(schema.Normalizer); ok && ApplyFlags.DevURL != "" {
+		dev, err := sqlclient.Open(ctx, ApplyFlags.DevURL)
+		cobra.CheckErr(err)
+		defer dev.Close()
+		desired, err = dev.Driver.(schema.Normalizer).NormalizeRealm(ctx, desired)
+		cobra.CheckErr(err)
+	}
+	changes, err := client.RealmDiff(realm, desired)
+	cobra.CheckErr(err)
+	if len(changes) == 0 {
+		schemaCmd.Println("Schema is synced, no changes to be made")
+		return
+	}
+	p, err := client.PlanChanges(ctx, "plan", changes)
+	cobra.CheckErr(err)
+	schemaCmd.Println("-- Planned Changes:")
+	for _, c := range p.Changes {
+		if c.Comment != "" {
+			schemaCmd.Println("--", strings.ToUpper(c.Comment[:1])+c.Comment[1:])
+		}
+		schemaCmd.Println(c.Cmd)
+	}
+	if dryRun {
+		return
+	}
+	if autoApprove || promptUser() {
+		cobra.CheckErr(client.ApplyChanges(ctx, changes))
+	}
+}
+
+func promptUser() bool {
+	prompt := promptui.Select{
+		Label: "Are you sure?",
+		Items: []string{answerApply, answerAbort},
+	}
+	_, result, err := prompt.Run()
+	cobra.CheckErr(err)
+	return result == answerApply
+}
+
+func dsn2url(cmd *cobra.Command, p *string) {
+	cmd.Flags().StringVarP(p, "dsn", "d", "", "")
+	cobra.CheckErr(cmd.Flags().MarkHidden("dsn"))
+	cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
+		dsnF, urlF := cmd.Flag("dsn"), cmd.Flag("url")
+		switch {
+		case !dsnF.Changed && !urlF.Changed:
+			return errors.New(`required flag "url" was not set`)
+		case dsnF.Changed && urlF.Changed:
+			return errors.New(`both flags "url" and "dsn" were set`)
+		case dsnF.Changed && !urlF.Changed:
+			urlF.Changed = true
+			urlF.Value = dsnF.Value
+		}
+		return nil
+	}
+}
+
+func handlePath(cmd *cobra.Command, path string) {
+	tasks, err := tasks(path)
+	cobra.CheckErr(err)
+	for _, task := range tasks {
+		changed, err := fmtFile(task)
+		cobra.CheckErr(err)
+		if changed {
+			cmd.Println(task.path)
+		}
+	}
+}
+
+func tasks(path string) ([]fmttask, error) {
+	var tasks []fmttask
+	stat, err := os.Stat(path)
+	if err != nil {
+		return nil, err
+	}
+	if !stat.IsDir() {
+		if strings.HasSuffix(path, ".hcl") {
+			tasks = append(tasks, fmttask{
+				path: path,
+				info: stat,
+			})
+		}
+		return tasks, nil
+	}
+	all, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, err
+	}
+	for _, f := range all {
+		if f.IsDir() {
+			continue
+		}
+		if strings.HasSuffix(f.Name(), ".hcl") {
+			tasks = append(tasks, fmttask{
+				path: filepath.Join(path, f.Name()),
+				info: f,
+			})
+		}
+	}
+	return tasks, nil
+}
+
+type fmttask struct {
+	path string
+	info fs.FileInfo
+}
+
+// fmtFile tries to format a file and reports if formatting occurred.
+func fmtFile(task fmttask) (bool, error) {
+	orig, err := os.ReadFile(task.path)
+	if err != nil {
+		return false, err
+	}
+	formatted := hclwrite.Format(orig)
+	if !bytes.Equal(formatted, orig) {
+		return true, os.WriteFile(task.path, formatted, task.info.Mode())
+	}
+	return false, nil
+}
diff --git a/cmd/internal/update/update.go b/cmd/atlascmd/update/update.go
similarity index 100%
rename from cmd/internal/update/update.go
rename to cmd/atlascmd/update/update.go
diff --git a/cmd/internal/update/update_test.go b/cmd/atlascmd/update/update_test.go
similarity index 100%
rename from cmd/internal/update/update_test.go
rename to cmd/atlascmd/update/update_test.go
diff --git a/doc/md/cli/reference.md b/doc/md/cli/reference.md
index 91a03e78..826369e2 100644
--- a/doc/md/cli/reference.md
+++ b/doc/md/cli/reference.md
@@ -202,7 +202,7 @@ atlas schema diff [flags]
 ```
 
 #### Details
-`atlas schema diff` connects to two given databases, inspects
+'atlas schema diff' connects to two given databases, inspects
 them, calculates the difference in their schemas, and prints a plan of
 SQL statements to migrate the "from" database to the schema of the "to" database.
 
@@ -224,7 +224,7 @@ atlas schema fmt [path ...]
 ```
 
 #### Details
-`atlas schema fmt` formats all ".hcl" files under the given path using
+'atlas schema fmt' formats all ".hcl" files under the given path using
 canonical HCL layout style as defined by the github.com/hashicorp/hcl/v2/hclwrite package.
 Unless stated otherwise, the fmt command will use the current directory.
 
@@ -235,7 +235,7 @@ files in the directory are formatted, no input will be printed out.
 
 ### atlas schema inspect
 
-Inspect an a database's and print its schema in Atlas DDL syntax.
+Inspect a database's and print its schema in Atlas DDL syntax.
 
 #### Usage
 ```
@@ -243,8 +243,8 @@ atlas schema inspect [flags]
 ```
 
 #### Details
-`atlas schema inspect` connects to the given database and inspects its schema.
-It then prints to the screen the schema of that database in Atlas DDL syntax. This output can be 
+'atlas schema inspect' connects to the given database and inspects its schema.
+It then prints to the screen the schema of that database in Atlas DDL syntax. This output can be
 saved to a file, commonly by redirecting the output to a file named with a ".hcl" suffix:
 
   atlas schema inspect -u "mysql://user:pass@localhost:3306/dbname" > atlas.hcl


Test patch:
diff --git a/cmd/action/env_test.go b/cmd/atlascmd/atlascmd_test.go
similarity index 81%
rename from cmd/action/env_test.go
rename to cmd/atlascmd/atlascmd_test.go
index 8f11ff70..e09efe37 100644
--- a/cmd/action/env_test.go
+++ b/cmd/atlascmd/atlascmd_test.go
@@ -2,19 +2,19 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"os"
 	"testing"
 
-	"ariga.io/atlas/cmd/internal/update"
+	"ariga.io/atlas/cmd/atlascmd/update"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestEnv(t *testing.T) {
-	out, err := runCmd(RootCmd, "env")
+	out, err := runCmd(Root, "env")
 	require.NoError(t, err)
 	require.Empty(t, out)
 }
@@ -22,7 +22,7 @@ func TestEnv(t *testing.T) {
 func TestEnv_Set(t *testing.T) {
 	err := os.Setenv(update.AtlasNoUpdateNotifier, "test")
 	require.NoError(t, err)
-	out, err := runCmd(RootCmd, "env")
+	out, err := runCmd(Root, "env")
 	require.NoError(t, err)
 	require.Equal(t, "ATLAS_NO_UPDATE_NOTIFIER=test\n", out)
 }
diff --git a/cmd/action/diff_test.go b/cmd/atlascmd/diff_test.go
similarity index 99%
rename from cmd/action/diff_test.go
rename to cmd/atlascmd/diff_test.go
index 729eaee0..82e3d532 100644
--- a/cmd/action/diff_test.go
+++ b/cmd/atlascmd/diff_test.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"bytes"
diff --git a/cmd/action/migrate_test.go b/cmd/atlascmd/migrate_test.go
similarity index 91%
rename from cmd/action/migrate_test.go
rename to cmd/atlascmd/migrate_test.go
index 3dd7de8b..a09d2caa 100644
--- a/cmd/action/migrate_test.go
+++ b/cmd/atlascmd/migrate_test.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"context"
@@ -28,7 +28,7 @@ import (
 )
 
 func TestMigrate(t *testing.T) {
-	_, err := runCmd(RootCmd, "migrate")
+	_, err := runCmd(Root, "migrate")
 	require.NoError(t, err)
 }
 
@@ -37,7 +37,7 @@ func TestMigrate_Diff(t *testing.T) {
 
 	// Expect no clean dev error.
 	s, err := runCmd(
-		RootCmd, "migrate", "diff",
+		Root, "migrate", "diff",
 		"name",
 		"--dir", "file://"+p,
 		"--dev-url", openSQLite(t, "create table t (c int);"),
@@ -48,7 +48,7 @@ func TestMigrate_Diff(t *testing.T) {
 
 	// Works.
 	s, err = runCmd(
-		RootCmd, "migrate", "diff",
+		Root, "migrate", "diff",
 		"name",
 		"--dir", "file://"+p,
 		"--dev-url", openSQLite(t, ""),
@@ -72,7 +72,7 @@ func TestMigrate_Diff(t *testing.T) {
 	require.NoError(t, err)
 	require.NoError(t, f.Close())
 	s, err = runCmd(
-		RootCmd, "migrate", "diff",
+		Root, "migrate", "diff",
 		"name",
 		"--dir", "file://"+t.TempDir(),
 		"--dev-url", fmt.Sprintf("sqlitelock://file:%s?cache=shared&_fk=1", filepath.Join(p, "test.db")),
@@ -83,14 +83,14 @@ func TestMigrate_Diff(t *testing.T) {
 }
 
 func TestMigrate_Validate(t *testing.T) {
-	s, err := runCmd(RootCmd, "migrate", "validate", "--dir", "file://migrations")
+	s, err := runCmd(Root, "migrate", "validate", "--dir", "file://migrations")
 	require.Zero(t, s)
 	require.NoError(t, err)
 }
 
 func TestMigrate_ValidateError(t *testing.T) {
 	if os.Getenv("DO_VALIDATE") == "1" {
-		runCmd(RootCmd, "migrate", "validate", "--dir", "file://migrations")
+		runCmd(Root, "migrate", "validate", "--dir", "file://migrations")
 		return
 	}
 	f := filepath.Join("migrations", "new.sql")
@@ -106,7 +106,7 @@ func TestMigrate_ValidateError(t *testing.T) {
 }
 
 func TestMigrate_Hash(t *testing.T) {
-	s, err := runCmd(RootCmd, "migrate", "hash", "--dir", "file://migrations")
+	s, err := runCmd(Root, "migrate", "hash", "--dir", "file://migrations")
 	require.Zero(t, s)
 	require.NoError(t, err)
 
@@ -114,7 +114,7 @@ func TestMigrate_Hash(t *testing.T) {
 	err = copyFile(filepath.Join("migrations", "20220318104614_initial.sql"), filepath.Join(p, "20220318104614_initial.sql"))
 	require.NoError(t, err)
 
-	s, err = runCmd(RootCmd, "migrate", "hash", "--dir", "file://"+p, "--force")
+	s, err = runCmd(Root, "migrate", "hash", "--dir", "file://"+p, "--force")
 	require.Zero(t, s)
 	require.NoError(t, err)
 	require.FileExists(t, filepath.Join(p, "atlas.sum"))
@@ -131,7 +131,7 @@ func TestMigrate_Hash(t *testing.T) {
 
 func TestMigrate_HashError(t *testing.T) {
 	if os.Getenv("DO_HASH") == "1" {
-		runCmd(RootCmd, "migrate", "hash", "--dir", "file://"+os.Getenv("MIGRATION_DIR"))
+		runCmd(Root, "migrate", "hash", "--dir", "file://"+os.Getenv("MIGRATION_DIR"))
 		return
 	}
 	p := t.TempDir()
diff --git a/cmd/action/fmt_test.go b/cmd/atlascmd/schema_test.go
similarity index 93%
rename from cmd/action/fmt_test.go
rename to cmd/atlascmd/schema_test.go
index a267934c..e447b410 100644
--- a/cmd/action/fmt_test.go
+++ b/cmd/atlascmd/schema_test.go
@@ -2,7 +2,7 @@
 // This source code is licensed under the Apache 2.0 license found
 // in the LICENSE file in the root directory of this source tree.
 
-package action
+package atlascmd
 
 import (
 	"bytes"
@@ -105,10 +105,10 @@ func TestFmt(t *testing.T) {
 
 func runFmt(t *testing.T, args []string) string {
 	var out bytes.Buffer
-	FmtCmd.ResetCommands() // Detach from sub-commands and parents, needed to skip input validation done by them.
-	FmtCmd.SetOut(&out)
-	FmtCmd.SetArgs(args)
-	err := FmtCmd.Execute()
+	SchemaFmt.ResetCommands() // Detach from sub-commands and parents, needed to skip input validation done by them.
+	SchemaFmt.SetOut(&out)
+	SchemaFmt.SetArgs(args)
+	err := SchemaFmt.Execute()
 	require.NoError(t, err)
 	return out.String()
 }