From dc23570046fa16c010707d0e162713c61fb90add Mon Sep 17 00:00:00 2001 From: NAKUL SHARMA <23bcs11342@cuchd.in> Date: Thu, 10 Sep 2026 14:57:36 +0530 Subject: [PATCH] Fix: owner/repo shorthand rejected when input has a trailing slash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parseUrl() now accepts a trailing slash on both the bare owner/repo shorthand (e.g. "facebook/react/") and full GitHub URLs (e.g. https://github.com/facebook/react/). Added tests covering trailing-slash acceptance, extra-segment rejection, .git suffix stripping, and garbage input. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- index.html | 2 +- tests/parse-url.test.mjs | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/parse-url.test.mjs diff --git a/index.html b/index.html index 2a8aceb1..b5f56160 100644 --- a/index.html +++ b/index.html @@ -9278,7 +9278,7 @@ if(url.length>200||url.includes('{')|| url.includes('"'))return null; var m=url.match(/^(?:https?:\/\/)?(?:www\.)?github\.com\/([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)/); if(m)return{owner:m[1],repo:m[2].replace(/\.git$/,'')}; - var simple=url.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/); + var simple=url.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)\/?$/); if(simple)return{owner:simple[1],repo:simple[2]}; return null; } diff --git a/tests/parse-url.test.mjs b/tests/parse-url.test.mjs new file mode 100644 index 00000000..e2a65b02 --- /dev/null +++ b/tests/parse-url.test.mjs @@ -0,0 +1,58 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; +import vm from 'node:vm'; + +// parseUrl runs in a separate vm context (different realm), so objects it +// returns are not `instanceof` this realm's Object — deepStrictEqual would +// fail on that alone. Compare plain field values instead. +function assertParsed(actual, expectedOwner, expectedRepo) { + assert.ok(actual, `expected a parsed result, got ${actual}`); + assert.equal(actual.owner, expectedOwner); + assert.equal(actual.repo, expectedRepo); +} + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(__dirname, '..'); +const htmlSource = await readFile(join(repoRoot, 'index.html'), 'utf8'); + +const fnStart = htmlSource.indexOf('function parseUrl(url){'); +if (fnStart < 0) { + throw new Error('Could not locate parseUrl() in index.html'); +} +const fnEnd = htmlSource.indexOf('\n }', fnStart) + '\n }'.length; +const fnSource = htmlSource.slice(fnStart, fnEnd); + +const context = { console }; +vm.createContext(context); +vm.runInContext(`${fnSource}\nthis.parseUrl = parseUrl;`, context); +const { parseUrl } = context; + +test('parseUrl accepts the bare owner/repo shorthand', () => { + assertParsed(parseUrl('facebook/react'), 'facebook', 'react'); +}); + +test('parseUrl accepts owner/repo with a trailing slash', () => { + assertParsed(parseUrl('facebook/react/'), 'facebook', 'react'); +}); + +test('parseUrl still rejects owner/repo with extra path segments', () => { + assert.equal(parseUrl('facebook/react/extra'), null); +}); + +test('parseUrl accepts a full GitHub URL with a trailing slash', () => { + assertParsed(parseUrl('https://github.com/facebook/react/'), 'facebook', 'react'); +}); + +test('parseUrl strips .git suffix from full GitHub URLs', () => { + assertParsed(parseUrl('https://github.com/facebook/react.git'), 'facebook', 'react'); +}); + +test('parseUrl rejects garbage input', () => { + assert.equal(parseUrl(''), null); + assert.equal(parseUrl(null), null); + assert.equal(parseUrl('not a repo'), null); + assert.equal(parseUrl('{"owner":"x"}/repo'), null); +});