mirror of
https://github.com/AventurasTeam/Aventuras.git
synced 2026-10-31 11:20:08 +00:00
`npm run release` cannot push for anyone without an HTTPS credential
helper:
```
Username for 'https://github.com': …
remote: Password authentication is not supported for Git operations.
```
`scripts/release.js` pushed to a hardcoded
`https://github.com/AventurasTeam/Aventuras.git`. GitHub no longer
accepts a password there, so the push fails outright — while the
contributor's working SSH remote sits unused two lines away. (Present
since `5fd37fcb`, February; it just needed someone to cut a release from
a clone without a helper.)
### The fix
The URL was not arbitrary — releasing to `origin` would happily tag a
**fork**, and hardcoding guaranteed the canonical repository. This keeps
that guarantee and drops the transport:
`pickReleaseRemote` scans `git remote -v` for a remote whose **push URL
points at the release repository** and hands git that remote's name, so
the push uses whatever transport and credentials that remote already
has. When nothing matches it falls back to the HTTPS URL, i.e. today's
behaviour.
Two details that matter:
- **Matching is on the repository path, never the remote name**, so a
fork cannot be selected however it is called. A test pins this.
- **`unkarelian/Aventuras` is accepted alongside
`AventurasTeam/Aventuras`.** GitHub redirects the pre-rename path, so a
remote predating the move still points here — and matching on the
repository *name* alone would sweep up every fork, which is exactly what
the owner check is for.
### Notes
- Extracted into `scripts/remote.js` rather than left inline:
`release.js` cuts a release on import, so it cannot be imported by a
test. 18 cases in `scripts/remote.test.js`, following the `version.js` /
`version.test.js` split from #416.
- The console now prints `upstream (git@github.com:...)` instead of a
bare remote name, so the repository about to be released to stays
visible.
- The failure hint for an unreachable remote now names the HTTPS
fallback as a likely cause.
86 lines
3.2 KiB
JavaScript
86 lines
3.2 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import { githubRepoPath, pickReleaseRemote, RELEASE_REPO_URL } from './remote.js'
|
|
|
|
describe('githubRepoPath', () => {
|
|
it.each([
|
|
['git@github.com:AventurasTeam/Aventuras.git', 'aventurasteam/aventuras'],
|
|
['https://github.com/AventurasTeam/Aventuras.git', 'aventurasteam/aventuras'],
|
|
['https://github.com/AventurasTeam/Aventuras', 'aventurasteam/aventuras'],
|
|
['https://github.com/AventurasTeam/Aventuras/', 'aventurasteam/aventuras'],
|
|
['ssh://git@github.com/AventurasTeam/Aventuras.git', 'aventurasteam/aventuras'],
|
|
])('reads %o', (url, expected) => {
|
|
expect(githubRepoPath(url)).toBe(expected)
|
|
})
|
|
|
|
it('lowercases, so a differently-cased remote still matches', () => {
|
|
expect(githubRepoPath('git@github.com:AVENTURASTEAM/AVENTURAS.git')).toBe(
|
|
'aventurasteam/aventuras',
|
|
)
|
|
})
|
|
|
|
it.each(['', 'not a url', 'git@gitlab.com:AventurasTeam/Aventuras.git'])(
|
|
'returns null for %o',
|
|
(url) => {
|
|
expect(githubRepoPath(url)).toBeNull()
|
|
},
|
|
)
|
|
})
|
|
|
|
const line = (name, url, kind) => `${name}\t${url} (${kind})`
|
|
|
|
describe('pickReleaseRemote', () => {
|
|
it('picks the remote pointing at the release repo, whatever it is called', () => {
|
|
const out = [
|
|
line('origin', 'git@github.com:Pento95/Aventuras.git', 'fetch'),
|
|
line('origin', 'git@github.com:Pento95/Aventuras.git', 'push'),
|
|
line('upstream', 'git@github.com:AventurasTeam/Aventuras.git', 'fetch'),
|
|
line('upstream', 'git@github.com:AventurasTeam/Aventuras.git', 'push'),
|
|
].join('\n')
|
|
|
|
expect(pickReleaseRemote(out).ref).toBe('upstream')
|
|
})
|
|
|
|
it('accepts the pre-rename path, which GitHub redirects here', () => {
|
|
const out = line('upstream', 'git@github.com:unkarelian/Aventuras.git', 'push')
|
|
expect(pickReleaseRemote(out).ref).toBe('upstream')
|
|
})
|
|
|
|
it('never picks a fork, which shares the repository name', () => {
|
|
// The whole reason the match includes the owner.
|
|
const out = [
|
|
line('origin', 'git@github.com:Pento95/Aventuras.git', 'push'),
|
|
line('other', 'git@github.com:SomeoneElse/Aventuras.git', 'push'),
|
|
].join('\n')
|
|
|
|
expect(pickReleaseRemote(out).ref).toBe(RELEASE_REPO_URL)
|
|
})
|
|
|
|
it('ignores fetch-only entries, since pushing is what this is for', () => {
|
|
const out = [
|
|
line('mirror', 'git@github.com:AventurasTeam/Aventuras.git', 'fetch'),
|
|
line('mirror', 'git@github.com:Pento95/Aventuras.git', 'push'),
|
|
].join('\n')
|
|
|
|
expect(pickReleaseRemote(out).ref).toBe(RELEASE_REPO_URL)
|
|
})
|
|
|
|
it('falls back to the canonical URL when no remote matches', () => {
|
|
const out = line('origin', 'git@github.com:Pento95/Aventuras.git', 'push')
|
|
expect(pickReleaseRemote(out).ref).toBe(RELEASE_REPO_URL)
|
|
})
|
|
|
|
it.each([
|
|
['', 'empty'],
|
|
[null, 'null'],
|
|
[undefined, 'undefined'],
|
|
])('falls back for %o input (%s)', (input) => {
|
|
expect(pickReleaseRemote(input).ref).toBe(RELEASE_REPO_URL)
|
|
})
|
|
|
|
it('labels a matched remote with its URL, so the target repo stays visible', () => {
|
|
const out = line('upstream', 'git@github.com:AventurasTeam/Aventuras.git', 'push')
|
|
expect(pickReleaseRemote(out).label).toBe(
|
|
'upstream (git@github.com:AventurasTeam/Aventuras.git)',
|
|
)
|
|
})
|
|
})
|