feat: fix composer
This commit is contained in:
@@ -3,11 +3,15 @@ package composer
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/any-hub/any-hub/internal/proxy/hooks"
|
"github.com/any-hub/any-hub/internal/proxy/hooks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var composerDistRegistry sync.Map
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
hooks.MustRegister("composer", hooks.Hooks{
|
hooks.MustRegister("composer", hooks.Hooks{
|
||||||
NormalizePath: normalizePath,
|
NormalizePath: normalizePath,
|
||||||
@@ -19,13 +23,23 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizePath(_ *hooks.RequestContext, clean string, rawQuery []byte) (string, []byte) {
|
func normalizePath(_ *hooks.RequestContext, clean string, rawQuery []byte) (string, []byte) {
|
||||||
|
if trimmed := trimComposerNamespace(clean); trimmed != clean {
|
||||||
|
clean = trimmed
|
||||||
|
}
|
||||||
if isComposerDistPath(clean) {
|
if isComposerDistPath(clean) {
|
||||||
return clean, nil
|
return clean, nil
|
||||||
}
|
}
|
||||||
return clean, rawQuery
|
return clean, rawQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveDistUpstream(_ *hooks.RequestContext, _ string, clean string, rawQuery []byte) string {
|
func resolveDistUpstream(ctx *hooks.RequestContext, _ string, clean string, rawQuery []byte) string {
|
||||||
|
domain := ""
|
||||||
|
if ctx != nil {
|
||||||
|
domain = ctx.Domain
|
||||||
|
}
|
||||||
|
if target := resolveComposerMirrorDist(domain, clean); target != "" {
|
||||||
|
return target
|
||||||
|
}
|
||||||
if !isComposerDistPath(clean) {
|
if !isComposerDistPath(clean) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -43,9 +57,10 @@ func rewriteResponse(
|
|||||||
body []byte,
|
body []byte,
|
||||||
path string,
|
path string,
|
||||||
) (int, map[string]string, []byte, error) {
|
) (int, map[string]string, []byte, error) {
|
||||||
|
cleanPath := trimComposerNamespace(path)
|
||||||
switch {
|
switch {
|
||||||
case path == "/packages.json":
|
case cleanPath == "/packages.json":
|
||||||
data, changed, err := rewriteComposerRootBody(body)
|
data, changed, err := rewriteComposerRootBody(body, ctx.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, headers, body, err
|
return status, headers, body, err
|
||||||
}
|
}
|
||||||
@@ -54,7 +69,7 @@ func rewriteResponse(
|
|||||||
}
|
}
|
||||||
outHeaders := ensureJSONHeaders(headers)
|
outHeaders := ensureJSONHeaders(headers)
|
||||||
return status, outHeaders, data, nil
|
return status, outHeaders, data, nil
|
||||||
case isComposerMetadataPath(path):
|
case isComposerMetadataPath(cleanPath):
|
||||||
data, changed, err := rewriteComposerMetadata(body, ctx.Domain)
|
data, changed, err := rewriteComposerMetadata(body, ctx.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status, headers, body, err
|
return status, headers, body, err
|
||||||
@@ -104,26 +119,21 @@ func contentType(_ *hooks.RequestContext, locatorPath string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteComposerRootBody(body []byte) ([]byte, bool, error) {
|
func rewriteComposerRootBody(body []byte, domain string) ([]byte, bool, error) {
|
||||||
// packages.json from Packagist may contain "packages" as array or object; we only care about URL-like fields.
|
|
||||||
var root map[string]any
|
var root map[string]any
|
||||||
if err := json.Unmarshal(body, &root); err != nil {
|
if err := json.Unmarshal(body, &root); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
changed := false
|
changed := false
|
||||||
for key, val := range root {
|
if rewriteComposerRootURLField(root, "metadata-url", domain) {
|
||||||
str, ok := val.(string)
|
changed = true
|
||||||
if !ok {
|
}
|
||||||
continue
|
if rewriteComposerRootURLField(root, "providers-url", domain) {
|
||||||
}
|
changed = true
|
||||||
switch strings.ToLower(key) {
|
}
|
||||||
// case "metadata-url", "providers-url", "providers-lazy-url", "notify", "notify-batch", "search":
|
if ensureComposerMirrors(root, domain) {
|
||||||
case "metadata-url":
|
changed = true
|
||||||
str = strings.ReplaceAll(str, "https://repo.packagist.org", "")
|
|
||||||
root[key] = str
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changed {
|
if !changed {
|
||||||
@@ -136,6 +146,43 @@ func rewriteComposerRootBody(body []byte) ([]byte, bool, error) {
|
|||||||
return data, true, nil
|
return data, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rewriteComposerRootURLField(root map[string]any, key string, domain string) bool {
|
||||||
|
value, ok := root[key].(string)
|
||||||
|
if !ok || value == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
proxied := buildComposerProxyURL(value, domain)
|
||||||
|
if proxied == value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
root[key] = proxied
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureComposerMirrors(root map[string]any, domain string) bool {
|
||||||
|
domain = strings.TrimSpace(domain)
|
||||||
|
if domain == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
target := "https://" + domain + "/dists/%package%/%reference%.%type%"
|
||||||
|
if existing, ok := root["mirrors"].([]any); ok && len(existing) == 1 {
|
||||||
|
if entry, ok := existing[0].(map[string]any); ok {
|
||||||
|
distURL, _ := entry["dist-url"].(string)
|
||||||
|
preferred, _ := entry["preferred"].(bool)
|
||||||
|
if distURL == target && preferred {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root["mirrors"] = []map[string]any{
|
||||||
|
{
|
||||||
|
"dist-url": target,
|
||||||
|
"preferred": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func rewriteComposerMetadata(body []byte, domain string) ([]byte, bool, error) {
|
func rewriteComposerMetadata(body []byte, domain string) ([]byte, bool, error) {
|
||||||
type packagesRoot struct {
|
type packagesRoot struct {
|
||||||
Packages map[string]json.RawMessage `json:"packages"`
|
Packages map[string]json.RawMessage `json:"packages"`
|
||||||
@@ -236,7 +283,12 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str
|
|||||||
if !ok || urlValue == "" {
|
if !ok || urlValue == "" {
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
rewritten := rewriteComposerDistURL(urlValue)
|
reference, _ := distVal["reference"].(string)
|
||||||
|
distType, _ := distVal["type"].(string)
|
||||||
|
if packageName != "" && domain != "" && reference != "" && distType != "" {
|
||||||
|
registerComposerDist(domain, packageName, reference, distType, urlValue)
|
||||||
|
}
|
||||||
|
rewritten := rewriteComposerLegacyDistURL(urlValue, domain)
|
||||||
if rewritten == urlValue {
|
if rewritten == urlValue {
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
@@ -244,38 +296,58 @@ func rewriteComposerVersion(entry map[string]any, domain string, packageName str
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func rewriteComposerDistURL(original string) string {
|
func rewriteComposerLegacyDistURL(original string, domain string) string {
|
||||||
parsed, err := url.Parse(original)
|
trimmed := strings.TrimSpace(original)
|
||||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
if trimmed == "" {
|
||||||
return original
|
return original
|
||||||
}
|
}
|
||||||
if isPackagistHost(parsed.Host) {
|
parsed, err := url.Parse(trimmed)
|
||||||
pathVal := parsed.Path
|
if err != nil {
|
||||||
if raw := parsed.RawPath; raw != "" {
|
return original
|
||||||
pathVal = raw
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(pathVal, "/") {
|
|
||||||
pathVal = "/" + pathVal
|
|
||||||
}
|
|
||||||
if parsed.RawQuery != "" {
|
|
||||||
return pathVal + "?" + parsed.RawQuery
|
|
||||||
}
|
|
||||||
return pathVal
|
|
||||||
}
|
}
|
||||||
return original
|
if domain != "" && strings.EqualFold(parsed.Host, domain) && strings.HasPrefix(parsed.Path, "/dist/") {
|
||||||
|
// Already rewritten.
|
||||||
|
return original
|
||||||
|
}
|
||||||
|
if parsed.Scheme == "" || parsed.Host == "" {
|
||||||
|
return original
|
||||||
|
}
|
||||||
|
pathVal := parsed.Path
|
||||||
|
if raw := parsed.RawPath; raw != "" {
|
||||||
|
pathVal = raw
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(pathVal, "/") {
|
||||||
|
pathVal = "/" + pathVal
|
||||||
|
}
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString("/dist/")
|
||||||
|
builder.WriteString(parsed.Scheme)
|
||||||
|
builder.WriteString("/")
|
||||||
|
builder.WriteString(parsed.Host)
|
||||||
|
builder.WriteString(pathVal)
|
||||||
|
if parsed.RawQuery != "" {
|
||||||
|
builder.WriteString("?")
|
||||||
|
builder.WriteString(parsed.RawQuery)
|
||||||
|
}
|
||||||
|
proxiedPath := builder.String()
|
||||||
|
if domain == "" {
|
||||||
|
return proxiedPath
|
||||||
|
}
|
||||||
|
return buildComposerProxyURL(proxiedPath, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isComposerMetadataPath(path string) bool {
|
func isComposerMetadataPath(path string) bool {
|
||||||
|
clean := trimComposerNamespace(path)
|
||||||
switch {
|
switch {
|
||||||
case path == "/packages.json":
|
case clean == "/packages.json":
|
||||||
return true
|
return true
|
||||||
case strings.HasPrefix(path, "/p2/"):
|
case strings.HasPrefix(clean, "/p2/"):
|
||||||
return true
|
return true
|
||||||
case strings.HasPrefix(path, "/p/"):
|
case strings.HasPrefix(clean, "/p/"):
|
||||||
return true
|
return true
|
||||||
case strings.HasPrefix(path, "/provider-"):
|
case strings.HasPrefix(clean, "/provider-"):
|
||||||
return true
|
return true
|
||||||
case strings.HasPrefix(path, "/providers/"):
|
case strings.HasPrefix(clean, "/providers/"):
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -283,14 +355,19 @@ func isComposerMetadataPath(path string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isComposerDistPath(path string) bool {
|
func isComposerDistPath(path string) bool {
|
||||||
return strings.HasPrefix(path, "/dist/")
|
clean := trimComposerNamespace(path)
|
||||||
|
if strings.HasPrefix(clean, "/dist/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(clean, "/dists/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseComposerDistURL(path string, rawQuery string) (*url.URL, bool) {
|
func parseComposerDistURL(path string, rawQuery string) (*url.URL, bool) {
|
||||||
if !strings.HasPrefix(path, "/dist/") {
|
clean := trimComposerNamespace(path)
|
||||||
|
if !strings.HasPrefix(clean, "/dist/") {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
trimmed := strings.TrimPrefix(path, "/dist/")
|
trimmed := strings.TrimPrefix(clean, "/dist/")
|
||||||
parts := strings.SplitN(trimmed, "/", 3)
|
parts := strings.SplitN(trimmed, "/", 3)
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -328,3 +405,132 @@ func stripPackagistHost(raw string) string {
|
|||||||
func isPackagistHost(host string) bool {
|
func isPackagistHost(host string) bool {
|
||||||
return strings.EqualFold(host, "repo.packagist.org")
|
return strings.EqualFold(host, "repo.packagist.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildComposerProxyURL(raw string, domain string) string {
|
||||||
|
trimmed := stripPackagistHost(strings.TrimSpace(raw))
|
||||||
|
if trimmed == "" {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
if parsed, err := url.Parse(trimmed); err == nil && parsed.Host != "" {
|
||||||
|
if domain != "" && strings.EqualFold(parsed.Host, domain) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
if !isPackagistHost(parsed.Host) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
if path := parsed.EscapedPath(); path != "" {
|
||||||
|
trimmed = path
|
||||||
|
if parsed.RawQuery != "" {
|
||||||
|
trimmed = trimmed + "?" + parsed.RawQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(trimmed, "/") {
|
||||||
|
trimmed = "/" + trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain == "" {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
return "https://" + domain + trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveComposerMirrorDist(domain string, locator string) string {
|
||||||
|
domain = strings.TrimSpace(domain)
|
||||||
|
if domain == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
pkg, reference, distType, ok := parseComposerMirrorDistLocator(locator)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
target, ok := lookupComposerDist(domain, pkg, reference, distType)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseComposerMirrorDistLocator(locator string) (string, string, string, bool) {
|
||||||
|
clean := trimComposerNamespace(locator)
|
||||||
|
if !strings.HasPrefix(clean, "/dists/") {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimPrefix(clean, "/dists/")
|
||||||
|
lastSlash := strings.LastIndex(trimmed, "/")
|
||||||
|
if lastSlash <= 0 || lastSlash >= len(trimmed)-1 {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
packagePart := trimmed[:lastSlash]
|
||||||
|
file := trimmed[lastSlash+1:]
|
||||||
|
if packagePart == "" || file == "" {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
ext := path.Ext(file)
|
||||||
|
if ext == "" {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
reference := strings.TrimSuffix(file, ext)
|
||||||
|
distType := strings.TrimPrefix(ext, ".")
|
||||||
|
if reference == "" || distType == "" {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
packageName := strings.ToLower(strings.Trim(packagePart, "/"))
|
||||||
|
if packageName == "" {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
return packageName, reference, distType, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerComposerDist(domain string, packageName string, reference string, distType string, upstream string) {
|
||||||
|
key := composerDistKey(domain, packageName, reference, distType)
|
||||||
|
if key == "" || strings.TrimSpace(upstream) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
composerDistRegistry.Store(key, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupComposerDist(domain string, packageName string, reference string, distType string) (string, bool) {
|
||||||
|
key := composerDistKey(domain, packageName, reference, distType)
|
||||||
|
if key == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
value, ok := composerDistRegistry.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
str, _ := value.(string)
|
||||||
|
if strings.TrimSpace(str) == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return str, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func composerDistKey(domain string, packageName string, reference string, distType string) string {
|
||||||
|
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||||
|
pkg := strings.ToLower(strings.TrimSpace(packageName))
|
||||||
|
ref := strings.TrimSpace(reference)
|
||||||
|
typ := strings.ToLower(strings.TrimSpace(distType))
|
||||||
|
if domain == "" || pkg == "" || ref == "" || typ == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return domain + "|" + pkg + "|" + ref + "|" + typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimComposerNamespace(p string) string {
|
||||||
|
if strings.HasPrefix(p, "/composer/") {
|
||||||
|
return strings.TrimPrefix(p, "/composer")
|
||||||
|
}
|
||||||
|
if p == "/composer" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetComposerDistRegistry() {
|
||||||
|
composerDistRegistry.Range(func(key, _ any) bool {
|
||||||
|
composerDistRegistry.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package composer
|
package composer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/any-hub/any-hub/internal/proxy/hooks"
|
"github.com/any-hub/any-hub/internal/proxy/hooks"
|
||||||
@@ -24,9 +24,20 @@ func TestResolveDistUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteResponseUpdatesURLs(t *testing.T) {
|
func TestResolveMirrorDistUpstream(t *testing.T) {
|
||||||
|
resetComposerDistRegistry()
|
||||||
|
registerComposerDist("cache.example", "vendor/pkg", "abc123", "zip", "https://github.com/org/repo.zip")
|
||||||
ctx := &hooks.RequestContext{Domain: "cache.example"}
|
ctx := &hooks.RequestContext{Domain: "cache.example"}
|
||||||
body := []byte(`{"packages":{"a/b":{"1.0.0":{"dist":{"url":"https://repo.packagist.org/dist/package.zip"}}}}}`)
|
url := resolveDistUpstream(ctx, "", "/composer/dists/vendor/pkg/abc123.zip", nil)
|
||||||
|
if url != "https://github.com/org/repo.zip" {
|
||||||
|
t.Fatalf("unexpected upstream %s", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRewriteResponseUpdatesURLs(t *testing.T) {
|
||||||
|
resetComposerDistRegistry()
|
||||||
|
ctx := &hooks.RequestContext{Domain: "cache.example"}
|
||||||
|
body := []byte(`{"packages":{"a/b":{"1.0.0":{"dist":{"url":"https://api.github.com/repos/org/repo/zipball/ref","reference":"abc123","type":"zip"}}}}}`)
|
||||||
_, headers, rewritten, err := rewriteResponse(ctx, 200, map[string]string{}, body, "/p2/a/b.json")
|
_, headers, rewritten, err := rewriteResponse(ctx, 200, map[string]string{}, body, "/p2/a/b.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("rewrite failed: %v", err)
|
t.Fatalf("rewrite failed: %v", err)
|
||||||
@@ -37,7 +48,51 @@ func TestRewriteResponseUpdatesURLs(t *testing.T) {
|
|||||||
if headers["Content-Type"] != "application/json" {
|
if headers["Content-Type"] != "application/json" {
|
||||||
t.Fatalf("expected json content type")
|
t.Fatalf("expected json content type")
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(rewritten), "/dist/package.zip") {
|
var payload map[string]any
|
||||||
t.Fatalf("expected stripped packagist host, got %s", string(rewritten))
|
if err := json.Unmarshal(rewritten, &payload); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
pkgs := payload["packages"].(map[string]any)
|
||||||
|
versions := pkgs["a/b"].(map[string]any)
|
||||||
|
version := versions["1.0.0"].(map[string]any)
|
||||||
|
dist := version["dist"].(map[string]any)
|
||||||
|
distURL := dist["url"].(string)
|
||||||
|
expected := "https://cache.example/dist/https/api.github.com/repos/org/repo/zipball/ref"
|
||||||
|
if distURL != expected {
|
||||||
|
t.Fatalf("expected dist url %s, got %s", expected, distURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRewritePackagesRoot(t *testing.T) {
|
||||||
|
resetComposerDistRegistry()
|
||||||
|
ctx := &hooks.RequestContext{Domain: "cache.example"}
|
||||||
|
body := []byte(`{"metadata-url":"https://repo.packagist.org/p2/%package%.json","providers-url":"/p/%package%$%hash%.json"}`)
|
||||||
|
_, headers, rewritten, err := rewriteResponse(ctx, 200, map[string]string{}, body, "/packages.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rewrite failed: %v", err)
|
||||||
|
}
|
||||||
|
if headers["Content-Type"] != "application/json" {
|
||||||
|
t.Fatalf("expected json content type")
|
||||||
|
}
|
||||||
|
var payload map[string]any
|
||||||
|
if err := json.Unmarshal(rewritten, &payload); err != nil {
|
||||||
|
t.Fatalf("unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if payload["metadata-url"] != "https://cache.example/composer/p2/%package%.json" {
|
||||||
|
t.Fatalf("metadata URL not rewritten: %v", payload["metadata-url"])
|
||||||
|
}
|
||||||
|
if payload["providers-url"] != "https://cache.example/composer/p/%package%$%hash%.json" {
|
||||||
|
t.Fatalf("providers URL not rewritten: %v", payload["providers-url"])
|
||||||
|
}
|
||||||
|
mirrors, _ := payload["mirrors"].([]any)
|
||||||
|
if len(mirrors) == 0 {
|
||||||
|
t.Fatalf("mirrors missing")
|
||||||
|
}
|
||||||
|
entry, _ := mirrors[0].(map[string]any)
|
||||||
|
if entry["dist-url"] != "https://cache.example/composer/dists/%package%/%reference%.%type%" {
|
||||||
|
t.Fatalf("unexpected mirror dist-url: %v", entry["dist-url"])
|
||||||
|
}
|
||||||
|
if pref, _ := entry["preferred"].(bool); !pref {
|
||||||
|
t.Fatalf("mirror preferred flag missing")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,11 +91,25 @@ func TestComposerProxyCachesMetadataAndDists(t *testing.T) {
|
|||||||
if providersURL, _ := root["providers-url"].(string); providersURL != "" {
|
if providersURL, _ := root["providers-url"].(string); providersURL != "" {
|
||||||
assertProxyURL(t, "providers-url", providersURL)
|
assertProxyURL(t, "providers-url", providersURL)
|
||||||
}
|
}
|
||||||
if notifyURL, _ := root["notify-batch"].(string); notifyURL != "" {
|
if notifyURL, _ := root["notify-batch"].(string); notifyURL != "https://packagist.org/downloads/" {
|
||||||
assertProxyURL(t, "notify-batch", notifyURL)
|
t.Fatalf("notify-batch should remain packagist, got %s", notifyURL)
|
||||||
|
}
|
||||||
|
if mirrors, _ := root["mirrors"].([]any); len(mirrors) == 0 {
|
||||||
|
t.Fatalf("expected mirrors entry in packages root")
|
||||||
|
} else {
|
||||||
|
if entry, ok := mirrors[0].(map[string]any); ok {
|
||||||
|
if distURL, _ := entry["dist-url"].(string); distURL != "https://composer.hub.local/composer/dists/%package%/%reference%.%type%" {
|
||||||
|
t.Fatalf("unexpected mirrors dist-url: %s", distURL)
|
||||||
|
}
|
||||||
|
if preferred, _ := entry["preferred"].(bool); !preferred {
|
||||||
|
t.Fatalf("mirrors entry should be preferred")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("unexpected mirrors payload: %#v", mirrors[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metaPath := "/p2/example/package.json"
|
metaPath := "/composer/p2/example/package.json"
|
||||||
resp := doRequest(metaPath)
|
resp := doRequest(metaPath)
|
||||||
if resp.StatusCode != fiber.StatusOK {
|
if resp.StatusCode != fiber.StatusOK {
|
||||||
t.Fatalf("expected 200 for composer metadata, got %d", resp.StatusCode)
|
t.Fatalf("expected 200 for composer metadata, got %d", resp.StatusCode)
|
||||||
@@ -229,8 +243,9 @@ func (s *composerStub) buildMetadata() []byte {
|
|||||||
"name": "example/package",
|
"name": "example/package",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dist": map[string]any{
|
"dist": map[string]any{
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": s.URL + s.distPath,
|
"url": s.URL + s.distPath,
|
||||||
|
"reference": "abc123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -244,9 +259,9 @@ func (s *composerStub) handlePackages(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
payload := map[string]any{
|
payload := map[string]any{
|
||||||
"packages": map[string]any{},
|
"packages": map[string]any{},
|
||||||
"metadata-url": "p2/%package%.json",
|
"metadata-url": "https://repo.packagist.org/p2/%package%.json",
|
||||||
"providers-url": "p/%package%$%hash%.json",
|
"providers-url": "https://repo.packagist.org/p/%package%$%hash%.json",
|
||||||
"notify-batch": "/downloads/",
|
"notify-batch": "https://packagist.org/downloads/",
|
||||||
"provider-includes": map[string]any{
|
"provider-includes": map[string]any{
|
||||||
"p/provider-latest$%hash%.json": map[string]any{"sha256": "dummy"},
|
"p/provider-latest$%hash%.json": map[string]any{"sha256": "dummy"},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user