Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/importmap/importmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ func walkDependencies(deps map[string]string, callback func(specifier, pkgName,
prefix = "/gh"
} else if pkg.PkgPrNew {
prefix = "/pr"
} else if pkg.Tgz {
prefix = "/tgz"
}
callback(specifier, pkgName, pkgVersion, prefix)
}
Expand Down
4 changes: 4 additions & 0 deletions internal/npm/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Package struct {
Version string
Github bool
PkgPrNew bool
Tgz bool
}

func (p *Package) String() string {
Expand All @@ -42,6 +43,9 @@ func (p *Package) String() string {
if p.PkgPrNew {
return "pr/" + s
}
if p.Tgz {
return "tgz/" + s
}
return s
}

Expand Down
6 changes: 4 additions & 2 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,8 @@ REBUILD:
header.WriteString("github:")
} else if ctx.esmPath.PrPrefix {
header.WriteString("pkg.pr.new/")
} else if ctx.esmPath.TgzPrefix {
header.WriteString("tgz/")
}
header.WriteString(ctx.esmPath.PkgName)
if ctx.esmPath.GhPrefix {
Expand Down Expand Up @@ -1350,7 +1352,7 @@ REBUILD:
finalJS.Write(jsContent)

// check if the package is deprecated
if !ctx.esmPath.GhPrefix && !ctx.esmPath.PrPrefix {
if !ctx.esmPath.GhPrefix && !ctx.esmPath.PrPrefix && !ctx.esmPath.TgzPrefix {
deprecated, _ := ctx.npmrc.isDeprecated(ctx.pkgJson.Name, ctx.pkgJson.Version)
if deprecated != "" {
fmt.Fprintf(finalJS, `console.warn("%%c[esm.sh]%%c %%cdeprecated%%c %s@%s: " + %s, "color:grey", "", "color:red", "");%s`, ctx.esmPath.PkgName, ctx.esmPath.PkgVersion, utils.MustEncodeJSON(deprecated), "\n")
Expand Down Expand Up @@ -1459,7 +1461,7 @@ func (ctx *BuildContext) install() (err error) {
return err
}

if ctx.esmPath.GhPrefix || ctx.esmPath.PrPrefix {
if ctx.esmPath.GhPrefix || ctx.esmPath.PrPrefix || ctx.esmPath.TgzPrefix {
// if the name in package.json is not the same as the repository name
if p.Name != ctx.esmPath.PkgName {
p.PkgName = p.Name
Expand Down
4 changes: 2 additions & 2 deletions server/build_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func resolveBuildArgs(npmrc *NpmRC, installDir string, args *BuildArgs, esm EsmP
if err == nil {
p = raw.ToNpmPackage()
}
} else if esm.GhPrefix || esm.PrPrefix {
} else if esm.GhPrefix || esm.PrPrefix || esm.TgzPrefix {
p, err = npmrc.installPackage(esm.Package())
} else {
p, err = npmrc.getPackageInfo(esm.PkgName, esm.PkgVersion)
Expand Down Expand Up @@ -254,7 +254,7 @@ func walkDeps(npmrc *NpmRC, installDir string, pkg npm.Package, mark *set.Set[st
if err == nil {
p = raw.ToNpmPackage()
}
} else if pkg.Github || pkg.PkgPrNew {
} else if pkg.Github || pkg.PkgPrNew || pkg.Tgz {
p, err = npmrc.installPackage(pkg)
} else {
p, err = npmrc.getPackageInfo(pkg.Name, pkg.Version)
Expand Down
5 changes: 4 additions & 1 deletion server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
PkgVersion: pkgJson.Version,
GhPrefix: ctx.esmPath.GhPrefix,
PrPrefix: ctx.esmPath.PrPrefix,
TgzPrefix: ctx.esmPath.TgzPrefix,
}, ctx.getBuildArgsPrefix(false), ctx.externalAll)
return
}
Expand All @@ -718,6 +719,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
subModule := EsmPath{
GhPrefix: ctx.esmPath.GhPrefix,
PrPrefix: ctx.esmPath.PrPrefix,
TgzPrefix: ctx.esmPath.TgzPrefix,
PkgName: ctx.esmPath.PkgName,
PkgVersion: ctx.esmPath.PkgVersion,
SubPath: subPath,
Expand Down Expand Up @@ -788,6 +790,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
if p.Name != "" {
dep.GhPrefix = p.Github
dep.PrPrefix = p.PkgPrNew
dep.TgzPrefix = p.Tgz
dep.PkgName = p.Name
dep.PkgVersion = p.Version
}
Expand Down Expand Up @@ -846,7 +849,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
var exactVersion bool
if dep.GhPrefix {
exactVersion = isCommitish(dep.PkgVersion) || npm.IsExactVersion(strings.TrimPrefix(dep.PkgVersion, "v"))
} else if dep.PrPrefix {
} else if dep.PrPrefix || dep.TgzPrefix {
exactVersion = true
} else {
exactVersion = npm.IsExactVersion(dep.PkgVersion)
Expand Down
7 changes: 6 additions & 1 deletion server/npmrc.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ func (npmrc *NpmRC) installPackage(pkg npm.Package) (packageJson *npm.PackageJSO
return
}
}
} else if pkg.Tgz {
url, err := url.PathUnescape(pkg.Version)
if err == nil {
err = fetchPackageTarball(&NpmRegistry{}, installDir, pkg.Name, url)
}
} else if pkg.PkgPrNew {
err = fetchPackageTarball(&NpmRegistry{}, installDir, pkg.Name, "https://pkg.pr.new/"+pkg.Name+"@"+pkg.Version)
} else {
Expand Down Expand Up @@ -391,7 +396,7 @@ func (npmrc *NpmRC) installDependencies(wd string, pkgJson *npm.PackageJSON, npm
// skip installing `@types/*` packages
return
}
if !npm.IsExactVersion(pkg.Version) && !pkg.Github && !pkg.PkgPrNew {
if !npm.IsExactVersion(pkg.Version) && !pkg.Github && !pkg.PkgPrNew && !pkg.Tgz {
p, e := npmrc.getPackageInfo(pkg.Name, pkg.Version)
if e != nil {
return
Expand Down
35 changes: 35 additions & 0 deletions server/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
type EsmPath struct {
GhPrefix bool
PrPrefix bool
TgzPrefix bool
PkgName string
PkgVersion string
SubPath string
Expand All @@ -25,6 +26,7 @@ func (p EsmPath) Package() npm.Package {
return npm.Package{
Github: p.GhPrefix,
PkgPrNew: p.PrPrefix,
Tgz: p.TgzPrefix,
Name: p.PkgName,
Version: p.PkgVersion,
}
Expand All @@ -41,6 +43,9 @@ func (p EsmPath) Name() string {
if p.PrPrefix {
return "pr/" + name
}
if p.TgzPrefix {
return "tgz/" + name
}
return name
}

Expand All @@ -52,6 +57,36 @@ func (p EsmPath) Specifier() string {
}

func praseEsmPath(npmrc *NpmRC, pathname string) (esm EsmPath, extraQuery string, exactVersion bool, hasTargetSegment bool, err error) {
if strings.HasPrefix(pathname, "/tgz/") {
pathname = pathname[5:]
var pkgName string
var subPath string
var rest string
if pathname[0] == '@' {
var scope string
scope, rest = utils.SplitByFirstByte(pathname, '/')
pkgName, rest = utils.SplitByFirstByte(rest, '@')
pkgName = scope + "/" + pkgName
} else {
pkgName, rest = utils.SplitByFirstByte(pathname, '@')
}
tarballUrl, subPath := utils.SplitByFirstByte(rest, '/')
tarballUrl, err = url.PathUnescape(tarballUrl)
if err != nil {
return
}
tarballUrl = url.PathEscape(tarballUrl)
exactVersion = true
hasTargetSegment = validateTargetSegment(strings.Split(subPath, "/"))
esm = EsmPath{
PkgName: pkgName,
PkgVersion: tarballUrl,
SubPath: subPath,
SubModuleName: stripEntryModuleExt(subPath),
TgzPrefix: true,
}
return
}
// see https://pkg.pr.new
if strings.HasPrefix(pathname, "/pr/") || strings.HasPrefix(pathname, "/pkg.pr.new/") {
if strings.HasPrefix(pathname, "/pr/") {
Expand Down
16 changes: 12 additions & 4 deletions server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
)

return func(ctx *rex.Context) any {
pathname := ctx.R.URL.Path
pathname := ctx.R.URL.EscapedPath()

// ban malicious requests
if strings.HasPrefix(pathname, "/.") || strings.HasSuffix(pathname, ".env") || strings.HasSuffix(pathname, ".php") {
Expand Down Expand Up @@ -175,12 +175,12 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
i := len(pathname) - 1
j := 0
for {
if i < 0 || pathname[i] == '/' {
break
}
if pathname[i] == ':' {
j = i
}
if i < 0 || pathname[i] < '0' || pathname[i] > '9' {
break
}
i--
}
if j > 0 {
Expand Down Expand Up @@ -852,6 +852,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
registryPrefix = "/gh"
} else if esm.PrPrefix {
registryPrefix = "/pr"
} else if esm.TgzPrefix {
registryPrefix = "/tgz"
}

// redirect `/@types/PKG` to it's main dts file
Expand Down Expand Up @@ -964,6 +966,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
if asteriskPrefix {
if esm.GhPrefix || esm.PrPrefix {
pkgName = pkgName[0:3] + "*" + pkgName[3:]
} else if esm.TgzPrefix {
pkgName = pkgName[0:5] + "*" + pkgName[5:]
} else {
pkgName = "*" + pkgName
}
Expand Down Expand Up @@ -991,6 +995,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
if asteriskPrefix {
if esm.GhPrefix || esm.PrPrefix {
pkgName = pkgName[0:3] + "*" + pkgName[3:]
} else if esm.TgzPrefix {
pkgName = pkgName[0:5] + "*" + pkgName[5:]
} else {
pkgName = "*" + pkgName
}
Expand Down Expand Up @@ -1299,6 +1305,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
if asteriskPrefix {
if esm.GhPrefix || esm.PrPrefix {
pkgName = pkgName[0:3] + "*" + pkgName[3:]
} else if esm.TgzPrefix {
pkgName = pkgName[0:5] + "*" + pkgName[5:]
} else {
pkgName = "*" + pkgName
}
Expand Down
36 changes: 36 additions & 0 deletions test/tgz/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { assertEquals, assertStringIncludes } from "jsr:@std/assert";

Deno.test("tgz", async () => {
const res = await fetch("http://localhost:8080/tgz/preact@https%3A%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz", { headers: { "user-agent": "i'm a browser" } });
assertEquals(res.status, 200);
assertEquals(res.headers.get("content-type"), "application/javascript; charset=utf-8");
assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable");
assertEquals(res.headers.get("x-typescript-types"), "http://localhost:8080/tgz/preact@https:%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz/src/index.d.ts");
const text = await res.text();
assertStringIncludes(text, "/es2022/preact.mjs");
});

Deno.test("tgz dts", async () => {
const res = await fetch("http://localhost:8080/tgz/preact@https%3A%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz/src/index.d.ts");
assertEquals(res.status, 200);
assertEquals(res.headers.get("content-type"), "application/typescript; charset=utf-8");
assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable");
const text = await res.text();
assertStringIncludes(text, "export abstract class Component<P, S> {");
});

Deno.test("tgz routing", async () => {
{
const { h } = await import("http://localhost:8080/tgz/preact@https%3A%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz");
assertEquals(typeof h, "function");
}
{
const { h } = await import("http://localhost:8080/tgz/preact@https:%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz");
assertEquals(typeof h, "function");
}
});

Deno.test("access tgz raw files", async () => {
const { name } = await fetch("http://localhost:8080/tgz/preact@https:%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz/package.json").then((res) => res.json());
assertEquals(name, "preact");
});
Loading