Files
pack/internal/utils/utils.go
T
3e53630854 feat: respect cache backend type for registry cache import/export, add cache support to build subcommand (#595)
* fix: respect cache backend type for registry cache import/export

* docs: update local development build instructions

- Update local image building to use the provided project task.
- Document the workflow for building with a local registry.
- Add examples for configuring buildctl with caching and debugging flags.

Generated-by: aiautocommit

* refactor(buildkit): clean up code comments and formatting

- Remove redundant comments and improve internal documentation.
- Fix minor formatting inconsistencies in code blocks and comment descriptions.

Generated-by: aiautocommit

* feat: add ParseBool utility to handle common boolean string variants

Generated-by: aiautocommit

* feat: integrate local registry and buildkit configuration

- Add buildkitd configuration to enable HTTP/insecure access to the local registry.
- Introduce a local registry service container on port 7890.
- Update the buildkit setup to mount the new registry configuration.
- Add tasks for building the frontend image and running project builds using the local registry and buildctl.

Generated-by: aiautocommit

* docs: update CLAUDE.md with architecture and style guidance

Generated-by: aiautocommit

* feat: add support for multiple cache-from and cache-to build flags

- Allow users to configure external cache sources and destinations via the `build` subcommand.
- Update cache parsing logic to support multiple entries matching the Docker buildx CLI format.
- Remove hardcoded default cache type to provide better control over BuildKit cache configurations.

Generated-by: aiautocommit

* test: add integration tests for cache-import parsing

Generated-by: aiautocommit

* refactor(buildkit): extract cache entry parsing logic

- Centralize cache configuration logic by moving loop-based parsing into a reusable `cacheEntriesFromFlags` helper.
- Improve code maintainability and readability by reducing duplication in `BuildWithBuildkitClient`.
- Add comprehensive unit tests for cache entry processing, including edge cases for flag parsing and empty inputs.

Generated-by: aiautocommit

* fix(test): remove unused buildkit cache flags from integration tests

* Remove unused flag variables that were meant for remote BuildKit caching.
* Ensure integration tests avoid remote cache (e.g., type=gha) to prevent exceeding GitHub Actions cache limits and excessive eviction.

Generated-by: aiautocommit

* test(buildkit): consolidate cache import tests

- Move TestParseCacheImports from its own file to frontend_test.go to consolidate related buildkit frontend tests.

Generated-by: aiautocommit

* style: fix indentation in build test helper

Generated-by: aiautocommit

* docs: document support for BuildKit cache backends

Generated-by: aiautocommit

* docs(developing-locally): add information on Docker, BuildKit, and caching

- Add missing setup prerequisites.
- Include debugging and configuration guidance for BuildKit/Docker.
- Clarify cache export and import implementation expectations in Railpack.

Generated-by: aiautocommit

* docs: clarify cache flag parsing and pnpm version logic

Generated-by: aiautocommit

---------

Co-authored-by: Matt Van Horn <[email protected]>

Co-authored-by: Michael Bianco <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-13 11:15:16 -06:00

192 lines
4.7 KiB
Go

package utils
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/tailscale/hujson"
)
func RemoveDuplicates[T comparable](sliceList []T) []T {
allKeys := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
// Parses common true/false string values (true/1/yes/on and false/0/no/off).
// The second return is false when the string is empty or unrecognized.
func ParseBool(s string) (value bool, ok bool) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "true", "1", "yes", "on":
return true, true
case "false", "0", "no", "off":
return false, true
default:
return false, false
}
}
// MergeStringSlicePointers combines multiple string slice pointers, deduplicates values, and sorts them
func MergeStringSlicePointers(slices ...*[]string) *[]string {
if len(slices) == 0 {
return nil
}
var allStrings []string
for _, slice := range slices {
if slice != nil {
allStrings = append(allStrings, *slice...)
}
}
if len(allStrings) == 0 {
return nil
}
// Deduplicate and sort
seen := make(map[string]bool)
var uniqueStrings []string
for _, s := range allStrings {
if !seen[s] {
seen[s] = true
uniqueStrings = append(uniqueStrings, s)
}
}
sort.Strings(uniqueStrings)
return &uniqueStrings
}
// CapitalizeFirst converts the first character of a string to uppercase.
// The rest of the string remains unchanged.
// Examples:
// - "hello" -> "Hello"
// - "world" -> "World"
// - "" -> ""
// - "already Capitalized" -> "Already Capitalized"
func CapitalizeFirst(s string) string {
if s == "" {
return ""
}
runes := []rune(s)
runes[0] = []rune(strings.ToUpper(string(runes[0])))[0]
return string(runes)
}
// ParsePackageWithVersion parses a slice of package specifications in the format "name@version"
// and returns a map of package names to their versions.
// If a package has no version specified (no @ symbol), it defaults to "latest".
// Examples:
// - ["[email protected]"] -> {"node": "14.2"}
// - ["python"] -> {"python": "latest"}
// - ["[email protected]", "go"] -> {"ruby": "3.0.0", "go": "latest"}
// - ["node@^14.3", "python@>=3.9"] -> {"node": "^14.3", "python": ">=3.9"}
func ParsePackageWithVersion(versions []string) map[string]string {
parsedVersions := make(map[string]string)
for _, version := range versions {
parts := strings.Split(version, "@")
if len(parts) == 1 {
parsedVersions[parts[0]] = "latest"
} else {
parsedVersions[parts[0]] = parts[1]
}
}
return parsedVersions
}
// ExtractSemverVersion extracts the first version number found in a string.
// It supports full semver (major.minor.patch) as well as partial versions (major or major.minor).
// The version can appear anywhere in the string and can have prefixes or suffixes.
// Examples:
// - "1.2.3" -> "1.2.3"
// - "v1.2.3" -> "1.2.3"
// - "python-3.10.7" -> "3.10.7"
// - "version1.2.3-beta" -> "1.2.3"
// - "requires node 14.2" -> "14.2"
// - "python 3" -> "3"
//
// Returns an empty string if no version number is found.
func ExtractSemverVersion(version string) string {
semverRe := regexp.MustCompile(`(\d+(?:\.\d+)?(?:\.\d+)?)`)
matches := semverRe.FindStringSubmatch(strings.TrimSpace(version))
if len(matches) > 1 {
return matches[1]
}
return ""
}
// ParseSemver parses a semantic version string and returns a Semver struct.
func ParseSemver(version string) (*Semver, error) {
// Handle corepack version format like "[email protected]"
if idx := strings.Index(version, "@"); idx != -1 {
version = version[idx+1:]
}
// Remove potential prefixes like "v"
version = strings.TrimPrefix(version, "v")
// Split by dots
parts := strings.Split(version, ".")
// Parse major version
major, err := strconv.Atoi(parts[0])
if err != nil {
return nil, fmt.Errorf("invalid major version: %s", parts[0])
}
minor := 0
patch := 0
if len(parts) > 1 {
// Parse minor version
minorVer, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("invalid minor version: %s", parts[1])
}
minor = minorVer
}
if len(parts) > 2 {
// Parse patch version - take care of potential suffixes like "-alpha", "-beta", etc.
patchStr := parts[2]
patchParts := strings.Split(patchStr, "-")
patchVer, err := strconv.Atoi(patchParts[0])
if err != nil {
return nil, fmt.Errorf("invalid patch version: %s", patchParts[0])
}
patch = patchVer
}
return &Semver{
Major: major,
Minor: minor,
Patch: patch,
}, nil
}
type Semver struct {
Major int
Minor int
Patch int
}
// convert hujson/extended JSON into standardized json
func StandardizeJSON(b []byte) ([]byte, error) {
ast, err := hujson.Parse(b)
if err != nil {
return b, err
}
ast.Standardize()
return ast.Pack(), nil
}