Skip to content

Add Node v24 to test workflow#1608

Open
Copilot wants to merge 4 commits intomainfrom
copilot/add-node-v24-to-test-workflow
Open

Add Node v24 to test workflow#1608
Copilot wants to merge 4 commits intomainfrom
copilot/add-node-v24-to-test-workflow

Conversation

Copy link

Copilot AI commented Mar 16, 2026

Adds Node.js v24 to the CI test matrix alongside existing v20 and v22 coverage, and fixes compatibility issues so tests pass on Node v24.

Changes

  • .github/workflows/test.yml: Added 24 to the matrix.node array
  • src/lib/parseGitHubUrl.ts: New local WHATWG URL-based replacement for the parse-github-url package, which used the deprecated url.parse() that emits a DEP0169 warning to stderr in Node v24 (causing tests that assert empty stderr to fail)
  • src/lib/version-util.ts: Updated import to use local parseGitHubUrl; simplified isGitHubUrl by removing a try/catch that was only needed due to url.parse() throwing on certain inputs
  • src/package-managers/gitTags.ts: Updated import to use local parseGitHubUrl
Original prompt

This section details on the original issue you should resolve

<issue_title>Add Node v24 to test workflow</issue_title>
<issue_description>Add Node v24 to https://github.com/raineorshine/npm-check-updates/blob/main/.github/workflows/test.yml and make sure tests pass.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: raineorshine <750276+raineorshine@users.noreply.github.com>
Copilot AI changed the title [WIP] Add Node v24 to test workflow Add Node v24 to test workflow Mar 16, 2026
Copilot AI requested a review from raineorshine March 16, 2026 23:49
Copy link
Owner

@raineorshine raineorshine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEP0169] DeprecationWarning: url.parse() behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for url.parse() vulnerabilities.

Copilot AI and others added 2 commits March 17, 2026 01:40
…DEP0169 warning in Node v24

Co-authored-by: raineorshine <750276+raineorshine@users.noreply.github.com>
Co-authored-by: raineorshine <750276+raineorshine@users.noreply.github.com>
Copy link
Author

Copilot AI commented Mar 17, 2026

DEP0169] DeprecationWarning: url.parse() behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for url.parse() vulne...

Fixed in commit 30a7722. The parse-github-url package (v1.0.3, latest) uses url.parse() internally, which emits the DEP0169 deprecation warning to stderr in Node v24. Tests asserting stderr is empty were failing as a result.

I replaced the parse-github-url dependency with a local WHATWG URL-based implementation in src/lib/parseGitHubUrl.ts that provides the same interface but uses new URL() instead of url.parse(). Also simplified isGitHubUrl to remove the try/catch that was only needed due to the old url.parse() regression.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • bitbucket.org
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https REDACTED REDACTED bash date�� node prepare.js npm bin/npm radeDependenciesnode (dns block)
  • github.com (HTTP Only)
    • Triggering command: /usr/bin/ssh /usr/bin/ssh -o SendEnv=GIT_PROTOCOL git@github.com git-upload-pack &#39;/raineorshine/ncu-test-v2&#39; (packet block)
    • Triggering command: /usr/bin/ssh /usr/bin/ssh -o SendEnv=GIT_PROTOCOL git@github.com git-upload-pack &#39;/raineorshine/ncu-test-v2&#39; echo Success git p/bin/bash -v24-to-test-wornpm --global run-script/lib/node-gyp-bin/npm node js --json run k/npm-check-updates/node_modules/.bin/bash get --global /.bin/node node (packet block)
    • Triggering command: /usr/bin/ssh /usr/bin/ssh -o SendEnv=GIT_PROTOCOL git@github.com git-upload-pack &#39;/raineorshine/ncu-test-v2&#39; echo Success git k/_temp/ghcca-nolodash.filter get --global tes/npm-check-up--format bash js node test.js git 0/x64/lib/node_modules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/bash ut 10000 --global run-script/lib/n--json bash (packet block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from raineorshine March 17, 2026 01:41
@raineorshine raineorshine marked this pull request as ready for review March 17, 2026 02:10
@raineorshine
Copy link
Owner

Maybe wait for jonschlinkert/parse-github-url#35

@Zamiell
Copy link
Contributor

Zamiell commented Mar 17, 2026

FYI I did that pull request specifically for npm-check-updates. : )

@Zamiell
Copy link
Contributor

Zamiell commented Mar 17, 2026

Although no-one responded in 5 days so it is not looking good. You might just want to accept copilot's pull request, as it is only an extra 112 lines to maintain and doesn't look that complicated.

@raineorshine
Copy link
Owner

raineorshine commented Mar 17, 2026

Thanks, I was thinking the same thing.

Have you looked over the implementation to see if it compares favorably to parse-github-url? Just want to make sure we don't introduce a regression that’s not covered by the tests.

Alternatively I could just point towards your fork temporarily, then switch it back after they merge. It's not just the script that’s needed for maintenance but the test suite, too.

@Zamiell
Copy link
Contributor

Zamiell commented Mar 17, 2026

Have you looked over the implementation to see if it compares favorably to parse-github-url?

i haven't.
if you think the npm-check-updates tests are not already comprehensive, then maybe we could ask copilot to brainstorm some specific missing test cases. /shrug

@raineorshine
Copy link
Owner

I'm referring to the parse-github-url test suite. It's not really in scope for npm-check-updates to have full test coverage of all dependencies.

@Zamiell
Copy link
Contributor

Zamiell commented Mar 17, 2026

ohhh. i could try running copilot's code on their test suite. i'll do that now.

@Zamiell
Copy link
Contributor

Zamiell commented Mar 17, 2026

It didn't pass all the tests. Copilot iterated on it and made this:

interface ParsedGitHubUrl {
  auth: string | null
  blob?: string | null
  branch?: string
  filepath: string | null
  hash: string | null
  host: string
  href: string
  name: string | null
  owner: string | null
  path: string
  pathname: string
  protocol: string | null
  repo: string | null
  repository: string | null
}

type ParsedBase = {
  auth?: string | null
  hash: string | null
  host: string | null
  href: string
  path: string | null
  pathname: string | null
  protocol: string | null
}

const cache: Record<string, ParsedGitHubUrl | null> = Object.create(null)

function isChecksum(str: string): boolean {
  return (/^[a-f0-9]{40}$/i).test(str)
}

function getBranch(str: string, obj: ParsedGitHubUrl): string {
  const segs = str.split('#')
  let branch: string | undefined
  if (segs.length > 1) {
    branch = segs[segs.length - 1]
  }
  if (!branch && obj.hash && obj.hash.charAt(0) === '#') {
    branch = obj.hash.slice(1)
  }
  return branch || 'master'
}

function trimSlash(path: string): string {
  return path.charAt(0) === '/' ? path.slice(1) : path
}

function parseName(str: string | undefined): string | null {
  return str ? str.replace(/\.git$/, '') : null
}

function parseOwner(str: string | undefined): string | null {
  if (!str) {
    return null
  }
  const idx = str.indexOf(':')
  if (idx > -1) {
    return str.slice(idx + 1)
  }
  return str
}

function parseScpLike(str: string): ParsedBase | null {
  let branch = ''
  const hashIndex = str.indexOf('#')
  let target = str
  if (hashIndex > -1) {
    branch = str.slice(hashIndex)
    target = str.slice(0, hashIndex)
  }
  const match = target.match(/^git@([^:]+):(.+)$/)
  if (!match) {
    return null
  }
  return {
    hash: branch || null,
    host: match[1],
    href: str,
    path: match[2],
    pathname: match[2],
    protocol: null,
  }
}

function parseGitHubShorthand(str: string): ParsedBase {
  let hash = ''
  let target = str
  const hashIndex = str.indexOf('#')
  if (hashIndex > -1) {
    hash = str.slice(hashIndex)
    target = str.slice(0, hashIndex)
  }
  return {
    hash: hash || null,
    host: null,
    href: str,
    path: target,
    pathname: target,
    protocol: null,
  }
}

function parseWithUrlApi(str: string): ParsedBase | null {
  try {
    const parsed = new URL(str)
    if (!parsed.hostname && parsed.protocol && parsed.protocol !== 'github:') {
      return null
    }
    return {
      auth: parsed.username ? (parsed.password ? `${parsed.username}:${parsed.password}` : parsed.username) : null,
      hash: parsed.hash || null,
      host: parsed.host || null,
      href: parsed.href || str,
      path: parsed.pathname || null,
      pathname: parsed.pathname || null,
      protocol: parsed.protocol || null,
    }
  } catch {
    return null
  }
}

function parseUrl(str: string): ParsedBase | null {
  if ((/^git@/).test(str)) {
    return parseScpLike(str)
  }
  if ((/^(?:github:)[^/]+\/[^/]+/).test(str)) {
    return parseGitHubShorthand(str.slice(7))
  }
  if ((/^[a-z][a-z0-9+.-]*:[^/]/i).test(str)) {
    return null
  }
  const parsed = parseWithUrlApi(str)
  if (parsed) {
    return parsed
  }
  return parseGitHubShorthand(str)
}

function parse(str: string): ParsedGitHubUrl | null {
  if (typeof str !== 'string' || !str.length) {
    return null
  }

  if (str.indexOf('git@gist') !== -1 || str.indexOf('//gist') !== -1) {
    return null
  }

  const parsed = parseUrl(str)
  if (!parsed || typeof parsed.path !== 'string' || !parsed.path.length || typeof parsed.pathname !== 'string' || !parsed.pathname.length) {
    return null
  }

  const obj: ParsedGitHubUrl = {
    auth: parsed.auth || null,
    filepath: null,
    hash: parsed.hash,
    host: parsed.host || 'github.com',
    href: parsed.href,
    name: null,
    owner: null,
    path: trimSlash(parsed.path),
    pathname: trimSlash(parsed.pathname),
    protocol: parsed.protocol,
    repo: null,
    repository: null,
  }

  if (obj.path.indexOf('repos') === 0) {
    obj.path = obj.path.slice(6)
  }

  const seg = obj.path.split('/').filter(Boolean)
  const hasBlob = seg[2] === 'blob'
  if (hasBlob && !isChecksum(seg[3])) {
    obj.branch = seg[3]
    if (seg.length > 4) {
      obj.filepath = seg.slice(4).join('/')
    }
  }

  const blob = str.indexOf('blob')
  if (hasBlob && blob !== -1) {
    obj.blob = str.slice(blob + 5)
  }

  const hasTree = seg[2] === 'tree'
  const tree = str.indexOf('tree')
  if (hasTree && tree !== -1) {
    const idx = tree + 5
    let branch = str.slice(idx)
    const slash = branch.indexOf('/')
    if (slash !== -1) {
      branch = branch.slice(0, slash)
    }
    obj.branch = branch
  }

  obj.owner = parseOwner(seg[0])
  obj.name = parseName(seg[1])

  if (seg.length > 1 && obj.owner && obj.name) {
    obj.repo = `${obj.owner}/${obj.name}`
  } else {
    const href = obj.href.split(':')
    if (href.length === 2 && obj.href.indexOf('//') === -1) {
      obj.repo = obj.repo || href[href.length - 1]
      const repoSegments = obj.repo.split('/')
      obj.owner = repoSegments[0]
      obj.name = repoSegments[1]
    } else {
      const match = obj.href.match(/\/([^/]*)$/)
      obj.owner = match ? match[1] : null
      obj.repo = null
    }

    if (obj.repo && (!obj.owner || !obj.name)) {
      const segs = obj.repo.split('/')
      if (segs.length === 2) {
        obj.owner = segs[0]
        obj.name = segs[1]
      }
    }
  }

  if (!obj.branch) {
    obj.branch = seg[2] || getBranch(obj.path, obj)
    if (seg.length > 3) {
      obj.filepath = seg.slice(3).join('/')
    }
  }

  obj.host = obj.host || 'github.com'
  obj.owner = obj.owner || null
  obj.name = obj.name || null
  obj.repository = obj.repo
  obj.blob = obj.blob || null
  return obj
}

export default function parseGitHubUrl(str: string): ParsedGitHubUrl | null {
  if (!(str in cache)) {
    cache[str] = parse(str)
  }
  return cache[str]
}

This version passes the tests, but the code looks significantly different now, so up to you as to which version you want.

@raineorshine
Copy link
Owner

We're looking for the minimal change needed to fix the deprecation warning, not an entire rewrite. This strikes me as a bit "let's try something and see what happens", which does not instill confidence. We have a bit more responsibility to the users of this library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Node v24 to test workflow

3 participants