Repository: npm/node-semver (JavaScript)

Issue:
[BUG] Coercing version with prerelease identifier that starts with digits returns truncated identifier
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Current Behavior

Using semver `7.7.1` (and possibly earlier versions), attempting to coerce a version string that contains a prerelease identifier that starts with digits, will result in that identifier to be truncated after the digits.

Here are some example:

- `1.0.0-alpha.1ab` produces `1.0.0-alpha.1`
- `1.0.0-alpha.12ab` produces `1.0.0-alpha.12`
- `1.0.0-alpha.1234.23cd` produces `1.0.0-alpha.1234.23`

This is problematic when the identifier is a hash that may start with a digit.

The issue doesn't seem to happen if the identifier is composed of only number, or if the identifier starts with an alphabetic character:

- `1.9.5-nightly.abc123` is coerced as expected
- `1.9.5-nightly.abcdef` is coerced as expected
- `1.9.5-nightly.123456` is coerced as expected

### Expected Behavior

The prerelease identifier isn't truncated.

### Steps To Reproduce

1. Clone the reproduction repository: https://github.com/nhedger/semver-prerelease-issue
2. Install the dependencies
3. Run `node index.mjs`
4. See that the prerelease identifier is truncated

### Environment

- npm: 10.8.2
- Node: 20.17.0
- OS: macOS
- platform: MacBook Pro M1




Fix (patch):
diff --git a/classes/semver.js b/classes/semver.js
index 6fbc062b..97049a40 100644
--- a/classes/semver.js
+++ b/classes/semver.js
@@ -1,6 +1,6 @@
 const debug = require('../internal/debug')
 const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
-const { safeRe: re, safeSrc: src, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')
 
 const parseOptions = require('../internal/parse-options')
 const { compareIdentifiers } = require('../internal/identifiers')
@@ -182,8 +182,7 @@ class SemVer {
       }
       // Avoid an invalid semver results
       if (identifier) {
-        const r = new RegExp(`^${this.options.loose ? src[t.PRERELEASELOOSE] : src[t.PRERELEASE]}$`)
-        const match = `-${identifier}`.match(r)
+        const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE])
         if (!match || match[1] !== identifier) {
           throw new Error(`invalid identifier: ${identifier}`)
         }
diff --git a/internal/re.js b/internal/re.js
index 2a956ba0..0a1c0e65 100644
--- a/internal/re.js
+++ b/internal/re.js
@@ -76,12 +76,14 @@ createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
 
 // ## Pre-release Version Identifier
 // A numeric identifier, or a non-numeric identifier.
+// Non-numberic identifiers include numberic identifiers but can be longer.
+// Therefore non-numberic identifiers must go first.
 
-createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]
-}|${src[t.NONNUMERICIDENTIFIER]})`)
+createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER]
+}|${src[t.NUMERICIDENTIFIER]})`)
 
-createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]
-}|${src[t.NONNUMERICIDENTIFIER]})`)
+createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER]
+}|${src[t.NUMERICIDENTIFIERLOOSE]})`)
 
 // ## Pre-release Version
 // Hyphen, followed by one or more dot-separated pre-release version