diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 0c72c667..63446901 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -15,7 +15,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: npm ci - run: npm run build:demo - run: npm run test:demo @@ -23,7 +23,7 @@ jobs: # https://github.com/refined-github/github-url-detection/pull/161 name: Ensure that the demo is built correctly - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: demo/dist/ @@ -42,4 +42,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/index.test.ts b/index.test.ts index bf230e5e..65325c34 100644 --- a/index.test.ts +++ b/index.test.ts @@ -197,3 +197,48 @@ test('getRepositoryInfo', () => { }); } }); + +test('parseRepoExplorerTitle', () => { + const parse = pageDetect.utils.parseRepoExplorerTitle; + + assert.deepEqual( + parse('/eslint/js/tree/2.x', 'eslint/js at 2.x'), + { + nameWithOwner: 'eslint/js', + branch: '2.x', + filePath: '', + }, + ); + assert.deepEqual( + parse('/eslint/js/tree/2.x', 'js/ at 2.x · eslint/js'), + { + nameWithOwner: 'eslint/js', + branch: '2.x', + filePath: '', + }, + ); + assert.deepEqual( + parse('/eslint/js/tree/2.x/tools', 'js/tools at 2.x · eslint/js'), + { + nameWithOwner: 'eslint/js', + branch: '2.x', + filePath: 'tools', + }, + ); + assert.deepEqual( + parse('/eslint/js/tree/2.x/docs/ast', 'js/docs/ast at 2.x · eslint/js'), + { + nameWithOwner: 'eslint/js', + branch: '2.x', + filePath: 'docs/ast', + }, + ); + assert.deepEqual( + parse('https://github.com/eslint/js', 'only /tree/ URLs are supported'), + undefined, + ); + assert.deepEqual( + parse('https://github.com/eslint/js/issues', 'irrelephant'), + undefined, + ); +}); diff --git a/index.ts b/index.ts index 7f7f0576..eccd5d25 100644 --- a/index.ts +++ b/index.ts @@ -449,25 +449,71 @@ TEST: addTests('isRepoHome', [ 'https://github.com/sindresorhus/refined-github?files=1', ]); +export type RepoExplorerInfo = { + nameWithOwner: string; + branch: string; + filePath: string; +}; + +// https://github.com/eslint/js/tree/2.x ->'eslint/js at 2.x' +// https://github.com/eslint/js/tree/2.x ->'js/ at 2.x · eslint/js' +// https://github.com/eslint/js/tree/2.x/tools -> 'js/tools at 2.x · eslint/js' +const titleParseRegex = /^(?:(?[^ ]+) at (?[^ ]+)|[^/ ]+(?:\/(?[^ ]*))? at (?[^ ]+)(?: · (?[^ ]+))?)$/; +// TODO: Reuse regex group names on the next MAJOR version https://github.com/tc39/proposal-duplicate-named-capturing-groups/issues/4 + +const parseRepoExplorerTitle = (pathname: string, title: string): RepoExplorerInfo | undefined => { + const match = titleParseRegex.exec(title); + if (!match?.groups) { + return; + } + + let {nameWithOwner, branch, filePath, nameWithOwner2, branch2} = match.groups; + + nameWithOwner ??= nameWithOwner2; + branch ??= branch2; + filePath ??= ''; + + if (!nameWithOwner || !branch || !pathname.startsWith(`/${nameWithOwner}/tree/`)) { + return; + } + + return {nameWithOwner, branch, filePath}; +}; + const _isRepoRoot = (url?: URL | HTMLAnchorElement | Location): boolean => { const repository = getRepo(url ?? location); if (!repository) { + // Not a repo return false; } - if (!repository.path) { - // Absolute repo root: `isRepoHome` - return true; - } + const path = repository.path ? repository.path.split('/') : []; - if (url) { - // Root of a branch/commit/tag - return /^tree\/[^/]+$/.test(repository.path); - } + switch (path.length) { + case 0: { + // Absolute repo root: `isRepoHome` + return true; + } + + case 2: { + // 100% certainty that it's a root if it's `tree` + return path[0] === 'tree'; + } - // If we're checking the current page, add support for branches with slashes // #15 #24 - return repository.path.startsWith('tree/') && document.title.startsWith(repository.nameWithOwner) && !document.title.endsWith(repository.nameWithOwner); + default: { + if (url) { + // From the URL we can safely only know it's a root if it's `user/repo/tree/something` + // With `user/repo/tree/something/else` we can't be sure whether `else` is a folder or still the branch name ("something/else") + return false; + } + + // If we're checking the current page, add support for branches with slashes + const titleInfo = parseRepoExplorerTitle(location.pathname, document.title); + + return titleInfo?.filePath === ''; + } + } }; // `_isRepoRoot` logic depends on whether a URL was passed, so don't use a `url` default parameter @@ -857,4 +903,5 @@ export const utils = { getCleanPathname, getCleanGistPathname, getRepositoryInfo: getRepo, + parseRepoExplorerTitle, }; diff --git a/package.json b/package.json index 5bd7e754..90f41226 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "build:esbuild": "esbuild index.ts --bundle --external:github-reserved-names --outdir=distribution --format=esm --drop-labels=TEST", "build:typescript": "tsc --declaration --emitDeclarationOnly", "build:demo": "vite build demo", + "try": "esbuild index.ts --bundle --global-name=x --format=iife | pbcopy && echo 'Copied to clipboard'", "fix": "xo --fix", "prepack": "npm run build", "test": "run-p build test:* xo",