Skip to content

Commit 7a871ba

Browse files
committed
tgz support
1 parent 8b0cd0d commit 7a871ba

9 files changed

Lines changed: 105 additions & 10 deletions

File tree

internal/importmap/importmap.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ func walkDependencies(deps map[string]string, callback func(specifier, pkgName,
374374
prefix = "/gh"
375375
} else if pkg.PkgPrNew {
376376
prefix = "/pr"
377+
} else if pkg.Tgz {
378+
prefix = "/tgz"
377379
}
378380
callback(specifier, pkgName, pkgVersion, prefix)
379381
}

internal/npm/npm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Package struct {
3232
Version string
3333
Github bool
3434
PkgPrNew bool
35+
Tgz bool
3536
}
3637

3738
func (p *Package) String() string {
@@ -42,6 +43,9 @@ func (p *Package) String() string {
4243
if p.PkgPrNew {
4344
return "pr/" + s
4445
}
46+
if p.Tgz {
47+
return "tgz/" + s
48+
}
4549
return s
4650
}
4751

server/build.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,8 @@ REBUILD:
11381138
header.WriteString("github:")
11391139
} else if ctx.esmPath.PrPrefix {
11401140
header.WriteString("pkg.pr.new/")
1141+
} else if ctx.esmPath.TgzPrefix {
1142+
header.WriteString("tgz/")
11411143
}
11421144
header.WriteString(ctx.esmPath.PkgName)
11431145
if ctx.esmPath.GhPrefix {
@@ -1350,7 +1352,7 @@ REBUILD:
13501352
finalJS.Write(jsContent)
13511353

13521354
// check if the package is deprecated
1353-
if !ctx.esmPath.GhPrefix && !ctx.esmPath.PrPrefix {
1355+
if !ctx.esmPath.GhPrefix && !ctx.esmPath.PrPrefix && !ctx.esmPath.TgzPrefix {
13541356
deprecated, _ := ctx.npmrc.isDeprecated(ctx.pkgJson.Name, ctx.pkgJson.Version)
13551357
if deprecated != "" {
13561358
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")
@@ -1459,7 +1461,7 @@ func (ctx *BuildContext) install() (err error) {
14591461
return err
14601462
}
14611463

1462-
if ctx.esmPath.GhPrefix || ctx.esmPath.PrPrefix {
1464+
if ctx.esmPath.GhPrefix || ctx.esmPath.PrPrefix || ctx.esmPath.TgzPrefix {
14631465
// if the name in package.json is not the same as the repository name
14641466
if p.Name != ctx.esmPath.PkgName {
14651467
p.PkgName = p.Name

server/build_args.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func resolveBuildArgs(npmrc *NpmRC, installDir string, args *BuildArgs, esm EsmP
136136
if err == nil {
137137
p = raw.ToNpmPackage()
138138
}
139-
} else if esm.GhPrefix || esm.PrPrefix {
139+
} else if esm.GhPrefix || esm.PrPrefix || esm.TgzPrefix {
140140
p, err = npmrc.installPackage(esm.Package())
141141
} else {
142142
p, err = npmrc.getPackageInfo(esm.PkgName, esm.PkgVersion)
@@ -254,7 +254,7 @@ func walkDeps(npmrc *NpmRC, installDir string, pkg npm.Package, mark *set.Set[st
254254
if err == nil {
255255
p = raw.ToNpmPackage()
256256
}
257-
} else if pkg.Github || pkg.PkgPrNew {
257+
} else if pkg.Github || pkg.PkgPrNew || pkg.Tgz {
258258
p, err = npmrc.installPackage(pkg)
259259
} else {
260260
p, err = npmrc.getPackageInfo(pkg.Name, pkg.Version)

server/build_resolver.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
708708
PkgVersion: pkgJson.Version,
709709
GhPrefix: ctx.esmPath.GhPrefix,
710710
PrPrefix: ctx.esmPath.PrPrefix,
711+
TgzPrefix: ctx.esmPath.TgzPrefix,
711712
}, ctx.getBuildArgsPrefix(false), ctx.externalAll)
712713
return
713714
}
@@ -718,6 +719,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
718719
subModule := EsmPath{
719720
GhPrefix: ctx.esmPath.GhPrefix,
720721
PrPrefix: ctx.esmPath.PrPrefix,
722+
TgzPrefix: ctx.esmPath.TgzPrefix,
721723
PkgName: ctx.esmPath.PkgName,
722724
PkgVersion: ctx.esmPath.PkgVersion,
723725
SubPath: subPath,
@@ -788,6 +790,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
788790
if p.Name != "" {
789791
dep.GhPrefix = p.Github
790792
dep.PrPrefix = p.PkgPrNew
793+
dep.TgzPrefix = p.Tgz
791794
dep.PkgName = p.Name
792795
dep.PkgVersion = p.Version
793796
}
@@ -846,7 +849,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind esbuild.Re
846849
var exactVersion bool
847850
if dep.GhPrefix {
848851
exactVersion = isCommitish(dep.PkgVersion) || npm.IsExactVersion(strings.TrimPrefix(dep.PkgVersion, "v"))
849-
} else if dep.PrPrefix {
852+
} else if dep.PrPrefix || dep.TgzPrefix {
850853
exactVersion = true
851854
} else {
852855
exactVersion = npm.IsExactVersion(dep.PkgVersion)

server/npmrc.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,11 @@ func (npmrc *NpmRC) installPackage(pkg npm.Package) (packageJson *npm.PackageJSO
333333
return
334334
}
335335
}
336+
} else if pkg.Tgz {
337+
url, err := url.PathUnescape(pkg.Version)
338+
if err == nil {
339+
err = fetchPackageTarball(&NpmRegistry{}, installDir, pkg.Name, url)
340+
}
336341
} else if pkg.PkgPrNew {
337342
err = fetchPackageTarball(&NpmRegistry{}, installDir, pkg.Name, "https://pkg.pr.new/"+pkg.Name+"@"+pkg.Version)
338343
} else {
@@ -391,7 +396,7 @@ func (npmrc *NpmRC) installDependencies(wd string, pkgJson *npm.PackageJSON, npm
391396
// skip installing `@types/*` packages
392397
return
393398
}
394-
if !npm.IsExactVersion(pkg.Version) && !pkg.Github && !pkg.PkgPrNew {
399+
if !npm.IsExactVersion(pkg.Version) && !pkg.Github && !pkg.PkgPrNew && !pkg.Tgz {
395400
p, e := npmrc.getPackageInfo(pkg.Name, pkg.Version)
396401
if e != nil {
397402
return

server/path.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
type EsmPath struct {
1616
GhPrefix bool
1717
PrPrefix bool
18+
TgzPrefix bool
1819
PkgName string
1920
PkgVersion string
2021
SubPath string
@@ -25,6 +26,7 @@ func (p EsmPath) Package() npm.Package {
2526
return npm.Package{
2627
Github: p.GhPrefix,
2728
PkgPrNew: p.PrPrefix,
29+
Tgz: p.TgzPrefix,
2830
Name: p.PkgName,
2931
Version: p.PkgVersion,
3032
}
@@ -41,6 +43,9 @@ func (p EsmPath) Name() string {
4143
if p.PrPrefix {
4244
return "pr/" + name
4345
}
46+
if p.TgzPrefix {
47+
return "tgz/" + name
48+
}
4449
return name
4550
}
4651

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

5459
func praseEsmPath(npmrc *NpmRC, pathname string) (esm EsmPath, extraQuery string, exactVersion bool, hasTargetSegment bool, err error) {
60+
if strings.HasPrefix(pathname, "/tgz/") {
61+
pathname = pathname[5:]
62+
var pkgName string
63+
var subPath string
64+
var rest string
65+
if pathname[0] == '@' {
66+
var scope string
67+
scope, rest = utils.SplitByFirstByte(pathname, '/')
68+
pkgName, rest = utils.SplitByFirstByte(rest, '@')
69+
pkgName = scope + "/" + pkgName
70+
} else {
71+
pkgName, rest = utils.SplitByFirstByte(pathname, '@')
72+
}
73+
tarballUrl, subPath := utils.SplitByFirstByte(rest, '/')
74+
tarballUrl, err = url.PathUnescape(tarballUrl)
75+
if err != nil {
76+
return
77+
}
78+
tarballUrl = url.PathEscape(tarballUrl)
79+
exactVersion = true
80+
hasTargetSegment = validateTargetSegment(strings.Split(subPath, "/"))
81+
esm = EsmPath{
82+
PkgName: pkgName,
83+
PkgVersion: tarballUrl,
84+
SubPath: subPath,
85+
SubModuleName: stripEntryModuleExt(subPath),
86+
TgzPrefix: true,
87+
}
88+
return
89+
}
5590
// see https://pkg.pr.new
5691
if strings.HasPrefix(pathname, "/pr/") || strings.HasPrefix(pathname, "/pkg.pr.new/") {
5792
if strings.HasPrefix(pathname, "/pr/") {

server/router.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
6969
)
7070

7171
return func(ctx *rex.Context) any {
72-
pathname := ctx.R.URL.Path
72+
pathname := ctx.R.URL.EscapedPath()
7373

7474
// ban malicious requests
7575
if strings.HasPrefix(pathname, "/.") || strings.HasSuffix(pathname, ".env") || strings.HasSuffix(pathname, ".php") {
@@ -175,12 +175,12 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
175175
i := len(pathname) - 1
176176
j := 0
177177
for {
178-
if i < 0 || pathname[i] == '/' {
179-
break
180-
}
181178
if pathname[i] == ':' {
182179
j = i
183180
}
181+
if i < 0 || pathname[i] < '0' || pathname[i] > '9' {
182+
break
183+
}
184184
i--
185185
}
186186
if j > 0 {
@@ -852,6 +852,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
852852
registryPrefix = "/gh"
853853
} else if esm.PrPrefix {
854854
registryPrefix = "/pr"
855+
} else if esm.TgzPrefix {
856+
registryPrefix = "/tgz"
855857
}
856858

857859
// redirect `/@types/PKG` to it's main dts file
@@ -964,6 +966,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
964966
if asteriskPrefix {
965967
if esm.GhPrefix || esm.PrPrefix {
966968
pkgName = pkgName[0:3] + "*" + pkgName[3:]
969+
} else if esm.TgzPrefix {
970+
pkgName = pkgName[0:5] + "*" + pkgName[5:]
967971
} else {
968972
pkgName = "*" + pkgName
969973
}
@@ -991,6 +995,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
991995
if asteriskPrefix {
992996
if esm.GhPrefix || esm.PrPrefix {
993997
pkgName = pkgName[0:3] + "*" + pkgName[3:]
998+
} else if esm.TgzPrefix {
999+
pkgName = pkgName[0:5] + "*" + pkgName[5:]
9941000
} else {
9951001
pkgName = "*" + pkgName
9961002
}
@@ -1299,6 +1305,8 @@ func esmRouter(db Database, buildStorage storage.Storage, logger *log.Logger) re
12991305
if asteriskPrefix {
13001306
if esm.GhPrefix || esm.PrPrefix {
13011307
pkgName = pkgName[0:3] + "*" + pkgName[3:]
1308+
} else if esm.TgzPrefix {
1309+
pkgName = pkgName[0:5] + "*" + pkgName[5:]
13021310
} else {
13031311
pkgName = "*" + pkgName
13041312
}

test/tgz/test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { assertEquals, assertStringIncludes } from "jsr:@std/assert";
2+
3+
Deno.test("tgz", async () => {
4+
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" } });
5+
assertEquals(res.status, 200);
6+
assertEquals(res.headers.get("content-type"), "application/javascript; charset=utf-8");
7+
assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable");
8+
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");
9+
const text = await res.text();
10+
assertStringIncludes(text, "/es2022/preact.mjs");
11+
});
12+
13+
Deno.test("tgz dts", async () => {
14+
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");
15+
assertEquals(res.status, 200);
16+
assertEquals(res.headers.get("content-type"), "application/typescript; charset=utf-8");
17+
assertEquals(res.headers.get("cache-control"), "public, max-age=31536000, immutable");
18+
const text = await res.text();
19+
assertStringIncludes(text, "export abstract class Component<P, S> {");
20+
});
21+
22+
Deno.test("tgz routing", async () => {
23+
{
24+
const { h } = await import("http://localhost:8080/tgz/preact@https%3A%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz");
25+
assertEquals(typeof h, "function");
26+
}
27+
{
28+
const { h } = await import("http://localhost:8080/tgz/preact@https:%2F%2Fregistry.yarnpkg.com%2Fpreact%2F-%2Fpreact-10.26.6.tgz");
29+
assertEquals(typeof h, "function");
30+
}
31+
});
32+
33+
Deno.test("access tgz raw files", async () => {
34+
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());
35+
assertEquals(name, "preact");
36+
});

0 commit comments

Comments
 (0)