fix(cli): warn when publishing a repo that contains submodules

`git archive` does not recurse into submodules, so a plugin shipping
vendored code via submodule produced a tarball where the submodule path
existed but was empty — silent failure. Now publish reads .gitmodules
and lists submodule paths to stderr with guidance to vendor or pack
them separately. The publish still proceeds, since the developer may
not actually need the submodule contents in the archive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-06-03 08:57:46 +08:00
parent c3cfa18ae0
commit 137a50c932

View File

@ -325,6 +325,18 @@ func newPluginPublishCmd() *cobra.Command {
fmt.Fprintln(os.Stderr, " (run `git rm --cached <file>` to drop)") fmt.Fprintln(os.Stderr, " (run `git rm --cached <file>` to drop)")
} }
// `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)")
}
archiveBytes, err := archive.BuildSourceArchive(".") archiveBytes, err := archive.BuildSourceArchive(".")
if err != nil { if err != nil {
return fmt.Errorf("build archive: %w", err) return fmt.Errorf("build archive: %w", err)
@ -577,6 +589,27 @@ func checkRepoHasHEAD(repoDir string) error {
return nil return nil
} }
// submodulePaths returns the configured submodule paths from .gitmodules.
// Reading the file directly (rather than running `git submodule status`) means
// we detect submodules that have been declared but not yet initialised.
func submodulePaths(repoDir string) []string {
cmd := exec.Command("git", "config", "--file", ".gitmodules", "--get-regexp", `submodule\..*\.path`)
cmd.Dir = repoDir
out, err := cmd.Output()
if err != nil {
return nil
}
var paths []string
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
// each line is "submodule.<name>.path <path>"
fields := strings.Fields(line)
if len(fields) >= 2 {
paths = append(paths, fields[len(fields)-1])
}
}
return paths
}
func upsertPluginMod(scope, name, kind string, categories []string) error { func upsertPluginMod(scope, name, kind string, categories []string) error {
const file = "plugin.mod" const file = "plugin.mod"
existing, _ := os.ReadFile(file) existing, _ := os.ReadFile(file)