package bnp import ( "archive/tar" "fmt" "os" "path/filepath" "sort" "strings" "github.com/klauspost/compress/zstd" ) // packEntry is one file destined for the artifact: its path inside the .bnp // (always forward-slash relative) and either literal Data or a Source file to // stream from. type packEntry struct { ArtifactPath string Data []byte Source string } // packArtifact writes entries as a zstd-compressed tar to outPath. Entries are // sorted by artifact path for a deterministic archive. Directory entries are // synthesized as needed so the reader's dirExists checks fire for // migrations/, schemas/, assets/, web/. func packArtifact(outPath string, entries []packEntry) (int64, error) { sort.Slice(entries, func(i, j int) bool { return entries[i].ArtifactPath < entries[j].ArtifactPath }) out, err := os.Create(outPath) if err != nil { return 0, fmt.Errorf("create %s: %w", outPath, err) } defer func() { _ = out.Close() }() enc, err := zstd.NewWriter(out, zstd.WithEncoderLevel(zstd.SpeedDefault)) if err != nil { return 0, err } tw := tar.NewWriter(enc) seenDir := map[string]bool{} writeDir := func(dir string) error { if dir == "" || dir == "." || seenDir[dir] { return nil } seenDir[dir] = true return tw.WriteHeader(&tar.Header{ Name: dir + "/", Typeflag: tar.TypeDir, Mode: 0o755, }) } var total int64 for _, e := range entries { name := filepath.ToSlash(e.ArtifactPath) // Ensure parent directories exist as explicit entries. for _, d := range parentDirs(name) { if err := writeDir(d); err != nil { return 0, fmt.Errorf("write dir header %q: %w", d, err) } } var data []byte if e.Source != "" { data, err = os.ReadFile(e.Source) if err != nil { return 0, fmt.Errorf("read %s: %w", e.Source, err) } } else { data = e.Data } if err := tw.WriteHeader(&tar.Header{ Name: name, Typeflag: tar.TypeReg, Mode: 0o644, Size: int64(len(data)), }); err != nil { return 0, fmt.Errorf("write header %q: %w", name, err) } if _, err := tw.Write(data); err != nil { return 0, fmt.Errorf("write %q: %w", name, err) } total += int64(len(data)) } if err := tw.Close(); err != nil { return 0, fmt.Errorf("close tar: %w", err) } if err := enc.Close(); err != nil { return 0, fmt.Errorf("close zstd: %w", err) } return total, nil } // parentDirs returns the ancestor directories of a forward-slash path, from // shallowest to deepest ("a/b/c.txt" → ["a", "a/b"]). func parentDirs(name string) []string { var dirs []string parts := strings.Split(name, "/") for i := 1; i < len(parts); i++ { dirs = append(dirs, strings.Join(parts[:i], "/")) } return dirs } // collectDir returns pack entries for every regular file under root, mapped // under artifactPrefix. Symlinks and irregular files are skipped. Returns // (entries, hadFiles). func collectDir(root, artifactPrefix string) ([]packEntry, bool, error) { info, err := os.Stat(root) if err != nil || !info.IsDir() { return nil, false, nil //nolint:nilerr // absent optional dir is not an error } var entries []packEntry walkErr := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || !d.Type().IsRegular() { return nil } rel, err := filepath.Rel(root, path) if err != nil { return err } entries = append(entries, packEntry{ ArtifactPath: artifactPrefix + "/" + filepath.ToSlash(rel), Source: path, }) return nil }) if walkErr != nil { return nil, false, fmt.Errorf("walk %s: %w", root, walkErr) } return entries, len(entries) > 0, nil }