Skip to content

Commit

Permalink
Support 'major.minor.patch+build' versioning (esm-dev#985)
Browse files Browse the repository at this point in the history
  • Loading branch information
ije authored Jan 6, 2025
1 parent 28bb02d commit 646516e
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 112 deletions.
66 changes: 61 additions & 5 deletions server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"strconv"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/evanw/esbuild/pkg/api"
"github.com/ije/gox/set"
"github.com/ije/gox/utils"
"github.com/ije/gox/valid"
)

// BuildEntry represents the build entrypoints of a module
Expand Down Expand Up @@ -1204,11 +1206,11 @@ func (ctx *BuildContext) analyzeSplitting() (err error) {

refs := map[string]Ref{}
for _, exportName := range exportNames.Values() {
esmPath := ctx.esm
esmPath.SubPath = exportName
esmPath.SubModuleName = stripEntryModuleExt(exportName)
esm := ctx.esm
esm.SubPath = exportName
esm.SubModuleName = stripEntryModuleExt(exportName)
b := &BuildContext{
esm: esmPath,
esm: esm,
npmrc: ctx.npmrc,
args: ctx.args,
externalAll: ctx.externalAll,
Expand All @@ -1220,7 +1222,7 @@ func (ctx *BuildContext) analyzeSplitting() (err error) {
}
_, includes, err := b.buildModule(true)
if err != nil {
return fmt.Errorf("failed to analyze %s: %v", esmPath.Specifier(), err)
return fmt.Errorf("failed to analyze %s: %v", esm.Specifier(), err)
}
for _, include := range includes {
module, importer := include[0], include[1]
Expand Down Expand Up @@ -1349,3 +1351,57 @@ func normalizeSavePath(zoneId string, pathname string) string {
}
return strings.Join(segs, "/")
}

// normalizeImportSpecifier normalizes the given specifier.
func normalizeImportSpecifier(specifier string) string {
specifier = strings.TrimPrefix(specifier, "npm:")
specifier = strings.TrimPrefix(specifier, "./node_modules/")
if specifier == "." {
specifier = "./index"
} else if specifier == ".." {
specifier = "../index"
}
if nodeBuiltinModules[specifier] {
return "node:" + specifier
}
return specifier
}

// isHttpSepcifier returns true if the specifier is a remote URL.
func isHttpSepcifier(specifier string) bool {
return strings.HasPrefix(specifier, "https://") || strings.HasPrefix(specifier, "http://")
}

// isRelPathSpecifier returns true if the specifier is a local path.
func isRelPathSpecifier(specifier string) bool {
return strings.HasPrefix(specifier, "./") || strings.HasPrefix(specifier, "../")
}

// isAbsPathSpecifier returns true if the specifier is an absolute path.
func isAbsPathSpecifier(specifier string) bool {
return strings.HasPrefix(specifier, "/") || strings.HasPrefix(specifier, "file://")
}

// isJsModuleSpecifier returns true if the specifier is a json module.
func isJsonModuleSpecifier(specifier string) bool {
if !strings.HasSuffix(specifier, ".json") {
return false
}
_, _, subpath, _ := splitEsmPath(specifier)
return subpath != "" && strings.HasSuffix(subpath, ".json")
}

// isJsModuleSpecifier checks if the given specifier is a node.js built-in module.
func isNodeBuiltInModule(specifier string) bool {
return strings.HasPrefix(specifier, "node:") && nodeBuiltinModules[specifier[5:]]
}

// isCommitish returns true if the given string is a commit hash.
func isCommitish(s string) bool {
return len(s) >= 7 && len(s) <= 40 && valid.IsHexString(s) && containsDigit(s)
}

// semverLessThan returns true if the version a is less than the version b.
func semverLessThan(a string, b string) bool {
return semver.MustParse(a).LessThan(semver.MustParse(b))
}
42 changes: 42 additions & 0 deletions server/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,48 @@ func isDistTag(s string) bool {
}
}

// isExactVersion returns true if the given version is an exact version.
func isExactVersion(version string) bool {
a := strings.SplitN(version, ".", 3)
if len(a) != 3 {
return false
}
if len(a[0]) == 0 || !isNumericString(a[0]) || len(a[1]) == 0 || !isNumericString(a[1]) {
return false
}
p := a[2]
if len(p) == 0 {
return false
}
patchEnd := false
for i, c := range p {
if !patchEnd {
if c == '-' || c == '+' {
if i == 0 || i == len(p)-1 {
return false
}
patchEnd = true
} else if c < '0' || c > '9' {
return false
}
} else {
if !(c == '.' || c == '_' || c == '-' || c == '+' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
return false
}
}
}
return true
}

func isNumericString(s string) bool {
for _, c := range s {
if c < '0' || c > '9' {
return false
}
}
return true
}

// based on https://github.com/npm/validate-npm-package-name
func validatePackageName(pkgName string) bool {
if len(pkgName) > 214 {
Expand Down
34 changes: 17 additions & 17 deletions server/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (p EsmPath) Specifier() string {
return p.Name()
}

func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery string, withExactVersion bool, hasTargetSegment bool, err error) {
func praseEsmPath(npmrc *NpmRC, pathname string) (esm EsmPath, extraQuery string, withExactVersion bool, hasTargetSegment bool, err error) {
// see https://pkg.pr.new
if strings.HasPrefix(pathname, "/pr/") || strings.HasPrefix(pathname, "/pkg.pr.new/") {
if strings.HasPrefix(pathname, "/pr/") {
Expand All @@ -70,7 +70,7 @@ func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery st
}
withExactVersion = true
hasTargetSegment = validateTargetSegment(strings.Split(subPath, "/"))
esmPath = EsmPath{
esm = EsmPath{
PkgName: pkgName,
PkgVersion: version,
SubPath: subPath,
Expand Down Expand Up @@ -131,11 +131,11 @@ func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery st
}

version, extraQuery := utils.SplitByFirstByte(maybeVersion, '&')
if v, e := url.QueryUnescape(version); e == nil {
if v, e := url.PathUnescape(version); e == nil {
version = v
}

esmPath = EsmPath{
esm = EsmPath{
PkgName: pkgName,
PkgVersion: version,
SubPath: subPath,
Expand All @@ -144,38 +144,38 @@ func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery st
}

// workaround for es5-ext "../#/.." path
if esmPath.SubModuleName != "" && esmPath.PkgName == "es5-ext" {
esmPath.SubModuleName = strings.ReplaceAll(esmPath.SubModuleName, "/%23/", "/#/")
if esm.SubModuleName != "" && esm.PkgName == "es5-ext" {
esm.SubModuleName = strings.ReplaceAll(esm.SubModuleName, "/%23/", "/#/")
}

if ghPrefix {
if isCommitish(esmPath.PkgVersion) || isExactVersion(strings.TrimPrefix(esmPath.PkgVersion, "v")) {
if isCommitish(esm.PkgVersion) || isExactVersion(strings.TrimPrefix(esm.PkgVersion, "v")) {
withExactVersion = true
return
}
var refs []GitRef
refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", esmPath.PkgName))
refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", esm.PkgName))
if err != nil {
return
}
if esmPath.PkgVersion == "" {
if esm.PkgVersion == "" {
for _, ref := range refs {
if ref.Ref == "HEAD" {
esmPath.PkgVersion = ref.Sha[:7]
esm.PkgVersion = ref.Sha[:7]
return
}
}
} else {
// try to find the exact tag or branch
for _, ref := range refs {
if ref.Ref == "refs/tags/"+esmPath.PkgVersion || ref.Ref == "refs/heads/"+esmPath.PkgVersion {
esmPath.PkgVersion = ref.Sha[:7]
if ref.Ref == "refs/tags/"+esm.PkgVersion || ref.Ref == "refs/heads/"+esm.PkgVersion {
esm.PkgVersion = ref.Sha[:7]
return
}
}
// try to find the semver tag
var c *semver.Constraints
c, err = semver.NewConstraint(strings.TrimPrefix(esmPath.PkgVersion, "semver:"))
c, err = semver.NewConstraint(strings.TrimPrefix(esm.PkgVersion, "semver:"))
if err == nil {
vs := make([]*semver.Version, len(refs))
i := 0
Expand All @@ -193,7 +193,7 @@ func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery st
if i > 1 {
sort.Sort(semver.Collection(vs))
}
esmPath.PkgVersion = vs[i-1].String()
esm.PkgVersion = vs[i-1].String()
return
}
}
Expand All @@ -202,12 +202,12 @@ func praseEsmPath(npmrc *NpmRC, pathname string) (esmPath EsmPath, extraQuery st
return
}

withExactVersion = isExactVersion(esmPath.PkgVersion)
withExactVersion = len(esm.PkgVersion) > 0 && isExactVersion(esm.PkgVersion)
if !withExactVersion {
var p *PackageJSON
p, err = npmrc.getPackageInfo(pkgName, esmPath.PkgVersion)
p, err = npmrc.getPackageInfo(pkgName, esm.PkgVersion)
if err == nil {
esmPath.PkgVersion = p.Version
esm.PkgVersion = p.Version
}
}
return
Expand Down
12 changes: 6 additions & 6 deletions server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -1711,17 +1711,17 @@ func esmRouter() rex.Handle {
fmt.Fprintf(buf, "import \"%s\";\n", dep)
}
}
esmPath := buildCtx.Path()
esm := buildCtx.Path()
if !ret.CJS && len(exports) > 0 {
esmPath += "?exports=" + strings.Join(exports, ",")
esm += "?exports=" + strings.Join(exports, ",")
}
ctx.Header.Set("X-ESM-Path", esmPath)
fmt.Fprintf(buf, "export * from \"%s\";\n", esmPath)
ctx.Header.Set("X-ESM-Path", esm)
fmt.Fprintf(buf, "export * from \"%s\";\n", esm)
if ret.ExportDefault && (len(exports) == 0 || stringInSlice(exports, "default")) {
fmt.Fprintf(buf, "export { default } from \"%s\";\n", esmPath)
fmt.Fprintf(buf, "export { default } from \"%s\";\n", esm)
}
if ret.CJS && len(exports) > 0 {
fmt.Fprintf(buf, "import _ from \"%s\";\n", esmPath)
fmt.Fprintf(buf, "import _ from \"%s\";\n", esm)
fmt.Fprintf(buf, "export const { %s } = _;\n", strings.Join(exports, ", "))
}
if !noDts && ret.Dts != "" {
Expand Down
84 changes: 0 additions & 84 deletions server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,98 +11,14 @@ import (
"strings"
"sync"

"github.com/Masterminds/semver/v3"
"github.com/ije/gox/utils"
"github.com/ije/gox/valid"
)

// isHttpSepcifier returns true if the specifier is a remote URL.
func isHttpSepcifier(specifier string) bool {
return strings.HasPrefix(specifier, "https://") || strings.HasPrefix(specifier, "http://")
}

// isRelPathSpecifier returns true if the specifier is a local path.
func isRelPathSpecifier(specifier string) bool {
return strings.HasPrefix(specifier, "./") || strings.HasPrefix(specifier, "../")
}

// isAbsPathSpecifier returns true if the specifier is an absolute path.
func isAbsPathSpecifier(specifier string) bool {
return strings.HasPrefix(specifier, "/") || strings.HasPrefix(specifier, "file://")
}

// isJsModuleSpecifier returns true if the specifier is a json module.
func isJsonModuleSpecifier(specifier string) bool {
if !strings.HasSuffix(specifier, ".json") {
return false
}
_, _, subpath, _ := splitEsmPath(specifier)
return subpath != "" && strings.HasSuffix(subpath, ".json")
}

// isJsModuleSpecifier checks if the given specifier is a node.js built-in module.
func isNodeBuiltInModule(specifier string) bool {
return strings.HasPrefix(specifier, "node:") && nodeBuiltinModules[specifier[5:]]
}

// normalizeImportSpecifier normalizes the given specifier.
func normalizeImportSpecifier(specifier string) string {
specifier = strings.TrimPrefix(specifier, "npm:")
specifier = strings.TrimPrefix(specifier, "./node_modules/")
if specifier == "." {
specifier = "./index"
} else if specifier == ".." {
specifier = "../index"
}
if nodeBuiltinModules[specifier] {
return "node:" + specifier
}
return specifier
}

// isExactVersion returns true if the given version is an exact version.
func isExactVersion(version string) bool {
a := strings.SplitN(version, ".", 3)
if len(a) != 3 {
return false
}
if !valid.IsDigtalOnlyString(a[0]) || !valid.IsDigtalOnlyString(a[1]) {
return false
}
p := a[2]
if len(p) == 0 {
return false
}
d, e := utils.SplitByFirstByte(p, '-')
if !valid.IsDigtalOnlyString(d) {
return false
}
if e == "" {
return p[len(p)-1] != '-'
}
for _, c := range e {
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '.' || c == '-' || c == '+') {
return false
}
}
return true
}

// semverLessThan returns true if the version a is less than the version b.
func semverLessThan(a string, b string) bool {
return semver.MustParse(a).LessThan(semver.MustParse(b))
}

// checks if the given hostname is a local address.
func isLocalhost(hostname string) bool {
return hostname == "localhost" || hostname == "127.0.0.1" || (valid.IsIPv4(hostname) && strings.HasPrefix(hostname, "192.168."))
}

// isCommitish returns true if the given string is a commit hash.
func isCommitish(s string) bool {
return len(s) >= 7 && len(s) <= 40 && valid.IsHexString(s) && containsDigit(s)
}

// isJsReservedWord returns true if the given string is a reserved word in JavaScript.
func isJsReservedWord(word string) bool {
switch word {
Expand Down
6 changes: 6 additions & 0 deletions test/jsr/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { assertExists } from "jsr:@std/assert";

Deno.test("jsr:@bids/schema", async () => {
const { schema } = await import("http://localhost:8080/jsr/@bids/[email protected]+2");
assertExists(schema.objects);
});

0 comments on commit 646516e

Please sign in to comment.