From fda01e81b5c8d2775d03cbb07d0675c0c0e2af2b Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Wed, 3 Jun 2026 09:12:56 +0800 Subject: [PATCH] refactor(cli): extract publish-time warnings into testable helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull the three inline warning blocks in newPluginPublishCmd — gitignoredTrackedWarning, untrackedFilesWarning, submoduleWarning — into package-private helpers that take a repo dir and an io.Writer. Output is byte-identical to the previous inline code; this just makes them unit- testable without driving the whole cobra command. --- cmd/ninja/cmd/plugin.go | 79 +++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/cmd/ninja/cmd/plugin.go b/cmd/ninja/cmd/plugin.go index 222fa42..2665d05 100644 --- a/cmd/ninja/cmd/plugin.go +++ b/cmd/ninja/cmd/plugin.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "fmt" + "io" "os" "os/exec" "regexp" @@ -301,14 +302,7 @@ func newPluginPublishCmd() *cobra.Command { // Run the tracked-yet-gitignored warning BEFORE the dirty check so // the developer sees it even on the aborted-publish path; the spec // asks for this warning to be unconditional. - out, _ := exec.Command("git", "ls-files", "--cached", "--ignored", "--exclude-standard").Output() - if names := strings.TrimSpace(string(out)); names != "" { - fmt.Fprintln(os.Stderr, "warning: these tracked files match .gitignore and will still be shipped:") - for _, n := range strings.Split(names, "\n") { - fmt.Fprintln(os.Stderr, " "+n) - } - fmt.Fprintln(os.Stderr, " (run `git rm --cached ` to drop)") - } + gitignoredTrackedWarning(".", os.Stderr) if !allowDirty { out, _ := exec.Command("git", "status", "--porcelain").Output() @@ -318,27 +312,14 @@ func newPluginPublishCmd() *cobra.Command { } else { // `git stash create` only captures tracked content, so untracked // files would be silently dropped from the archive. Warn loudly. - out, _ := exec.Command("git", "ls-files", "--others", "--exclude-standard").Output() - if names := strings.TrimSpace(string(out)); names != "" { - fmt.Fprintln(os.Stderr, "warning: --allow-dirty: these untracked files will NOT be in the archive:") - for _, n := range strings.Split(names, "\n") { - fmt.Fprintln(os.Stderr, " "+n) - } - fmt.Fprintln(os.Stderr, " (run `git add ` if they should be shipped)") - } + untrackedFilesWarning(".", os.Stderr) } // `git archive` does not recurse into submodules, so any submodule // paths will appear as empty directories in the tarball. Detect via // .gitmodules so this works even for submodules that haven't been // initialised yet. - if paths := submodulePaths("."); len(paths) > 0 { - fmt.Fprintln(os.Stderr, "warning: this repo has submodules; git archive will ship them as empty directories:") - for _, p := range paths { - fmt.Fprintln(os.Stderr, " "+p) - } - fmt.Fprintln(os.Stderr, " (vendor the contents or pack them separately if the plugin depends on them)") - } + submoduleWarning(".", os.Stderr) archiveBytes, err := archive.BuildSourceArchive(".") if err != nil { @@ -658,6 +639,58 @@ func writeMod(path string, m *core.ModFile) error { return os.WriteFile(path, []byte(b.String()), 0o644) } +// gitignoredTrackedWarning writes a warning to w if any tracked files +// in repoDir match the active .gitignore — those files still ship in the +// archive, which is almost always not what the author wants. +func gitignoredTrackedWarning(repoDir string, w io.Writer) { + cmd := exec.Command("git", "ls-files", "--cached", "--ignored", "--exclude-standard") + cmd.Dir = repoDir + out, _ := cmd.Output() + names := strings.TrimSpace(string(out)) + if names == "" { + return + } + fmt.Fprintln(w, "warning: these tracked files match .gitignore and will still be shipped:") + for _, n := range strings.Split(names, "\n") { + fmt.Fprintln(w, " "+n) + } + fmt.Fprintln(w, " (run `git rm --cached ` to drop)") +} + +// untrackedFilesWarning writes a warning to w listing untracked files in +// repoDir. Used on the --allow-dirty publish path because `git stash create` +// only captures tracked content, so untracked files silently vanish from the +// archive. +func untrackedFilesWarning(repoDir string, w io.Writer) { + cmd := exec.Command("git", "ls-files", "--others", "--exclude-standard") + cmd.Dir = repoDir + out, _ := cmd.Output() + names := strings.TrimSpace(string(out)) + if names == "" { + return + } + fmt.Fprintln(w, "warning: --allow-dirty: these untracked files will NOT be in the archive:") + for _, n := range strings.Split(names, "\n") { + fmt.Fprintln(w, " "+n) + } + fmt.Fprintln(w, " (run `git add ` if they should be shipped)") +} + +// submoduleWarning writes a warning to w if repoDir contains submodules. +// `git archive` doesn't recurse into them so they ship as empty directories; +// detection is via .gitmodules so it fires even for uninitialised submodules. +func submoduleWarning(repoDir string, w io.Writer) { + paths := submodulePaths(repoDir) + if len(paths) == 0 { + return + } + fmt.Fprintln(w, "warning: this repo has submodules; git archive will ship them as empty directories:") + for _, p := range paths { + fmt.Fprintln(w, " "+p) + } + fmt.Fprintln(w, " (vendor the contents or pack them separately if the plugin depends on them)") +} + // autoCommitPluginMod stages and commits plugin.mod if it differs from // what's already at HEAD. No-op when there's nothing to commit. func autoCommitPluginMod() error {