2c671a1be7
ci/woodpecker/tag/docker Pipeline was successful
npm clients need one registry for internal and public packages, but npm virtuals have no merger, so a local npm repo and npmjs cannot be served together. - merge packuments across members: union versions (member order wins), first-defined dist-tags minus ones naming missing versions - serve full and abbreviated (`install-v1`) packuments, scoped names included, via the shared virtual merge cache - point tarballs at `<virtual>/<member>/<path>` and 302 them to the owning member - reject an invalid Host with 400; serve virtual and local packuments `no-store` with `Vary: Host, X-Forwarded-Proto, Accept` - return 502 when a member errors; skip members without the package - add `docs/npm/virtual.md` and a stock `node:22` e2e-docker case Reviewed-on: #152 Co-authored-by: unkin-agent <unkin-agent@unkin.net> Co-committed-by: unkin-agent <unkin-agent@unkin.net>
69 lines
2.5 KiB
Go
69 lines
2.5 KiB
Go
//go:build dockere2e
|
|
|
|
package e2edocker
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestVirtualNPMInstall publishes to two local npm repos and drives a stock
|
|
// npm client through a virtual over both: merged versions and dist-tags, and
|
|
// an install whose tarballs resolve via the virtual's member redirect.
|
|
func TestVirtualNPMInstall(t *testing.T) {
|
|
network := os.Getenv("COMPOSE_NETWORK")
|
|
internal := os.Getenv("ARTIFACTAPI_INTERNAL")
|
|
if network == "" || internal == "" {
|
|
t.Skip("COMPOSE_NETWORK/ARTIFACTAPI_INTERNAL not set; run via scripts/docker-e2e.sh")
|
|
}
|
|
if _, err := exec.LookPath("docker"); err != nil {
|
|
t.Skip("docker not available on the test host")
|
|
}
|
|
|
|
id := time.Now().UnixNano()
|
|
a, b, virt := fmt.Sprintf("e2e-npm-a-%d", id), fmt.Sprintf("e2e-npm-b-%d", id), fmt.Sprintf("e2e-npm-v-%d", id)
|
|
for _, name := range []string{a, b} {
|
|
createRepo(t, fmt.Sprintf(`{"name":%q,"package_type":"npm","repo_type":"local"}`, name))
|
|
defer deleteRepo(t, name)
|
|
}
|
|
createVirtual(t, fmt.Sprintf(`{"name":%q,"package_type":"npm","members":[%q,%q]}`, virt, a, b))
|
|
defer deleteVirtual(t, virt)
|
|
|
|
base := strings.TrimRight(internal, "/")
|
|
script := `set -euo pipefail
|
|
pub() { d="/w/src/$2-$3-$1"; mkdir -p "$d"; cd "$d"
|
|
printf '{"name":"%s","version":"%s","main":"index.js"}' "$2" "$3" > package.json
|
|
echo "module.exports = '$1:$2@$3';" > index.js
|
|
printf 'registry=%s\n%s:_authToken=unused\n' "$BASE/api/v1/local/$1/" "${BASE#http:}/api/v1/local/$1/" > .npmrc
|
|
npm publish; }
|
|
pub "$A" @e2e/virt 1.0.0
|
|
pub "$B" @e2e/virt 1.0.0
|
|
pub "$B" @e2e/virt 1.1.0
|
|
mkdir -p /w/app && cd /w/app && npm init -y >/dev/null
|
|
echo "registry=$BASE/api/v1/virtual/$V/" > .npmrc
|
|
echo "VERSIONS $(npm view @e2e/virt versions --json | tr -d ' \n')"
|
|
npm install --no-audit --cache /tmp/fresh-cache @e2e/virt@1.0.0
|
|
node -e "console.log('REQ', require('@e2e/virt'))"
|
|
echo "RESOLVED $(node -p "require('./package-lock.json').packages['node_modules/@e2e/virt'].resolved")"`
|
|
|
|
out, err := exec.Command("docker", "run", "--rm", "--network", network,
|
|
"-e", "BASE="+base, "-e", "A="+a, "-e", "B="+b, "-e", "V="+virt,
|
|
"node:22", "bash", "-c", script).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("npm client run failed: %v\n%s", err, out)
|
|
}
|
|
for _, want := range []string{
|
|
`VERSIONS ["1.0.0","1.1.0"]`,
|
|
"REQ " + a + ":@e2e/virt@1.0.0",
|
|
"RESOLVED " + base + "/api/v1/virtual/" + virt + "/" + a + "/@e2e/virt/-/virt-1.0.0.tgz",
|
|
} {
|
|
if !strings.Contains(string(out), want) {
|
|
t.Fatalf("missing %q in npm output:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|