@preventive/upstream 1.0.0-alpha.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/advisories.d.ts +21 -7
- package/git.d.ts +2 -2
- package/npm.d.ts +117 -22
- package/npm.js +1 -1
- package/package.d.ts +5 -2
- package/package.json +3 -1
- package/src/advisories/github.js +87 -26
- package/src/advisories/index.js +4 -3
- package/src/advisories/osv.js +3 -3
- package/src/advisories/repos.js +21 -10
- package/src/args.js +1 -1
- package/src/cache.js +77 -24
- package/src/cargo/repos.js +8 -8
- package/src/download.js +8 -7
- package/src/git.js +1 -1
- package/src/npm/cacache.js +44 -0
- package/src/npm/registry.js +10 -4
- package/src/npm/repos.js +25 -13
- package/src/npm/tarball.js +99 -37
- package/src/npm/versions.js +97 -0
- package/src/package.js +24 -7
- package/src/remote.js +10 -3
package/src/package.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
2
|
|
|
3
3
|
import { assertion, isRepo, isRepoPath, sameName } from './args.js'
|
|
4
|
-
import { githubRepoOfUrl } from './remote.js'
|
|
4
|
+
import { githubRepoOfUrl, withoutDotGit } from './remote.js'
|
|
5
5
|
|
|
6
6
|
const bugsRegex = /^(?i:https?:\/\/github\.com)(?::\d{1,5})?\/(?<repo>[\w-]+\/[\w.-]+)\/issues\/?$/u
|
|
7
7
|
// npm's `owner/name` shorthand means GitHub. No dots in the owner, so a
|
|
@@ -11,9 +11,12 @@ const shorthandRegex = /^(?:github:)?(?<repo>[\w-]+\/[\w.-]+)$/u
|
|
|
11
11
|
// The ref is one segment: a branch with a `/` reads as part of the directory.
|
|
12
12
|
const homepageRegex = /^(?:https?:\/\/)?(?:www\.)?github\.com(?::\d{1,5})?\/(?<repo>[\w-]+\/[\w.-]+?)(?:\.git)?(?:\/(?:tree\/[^/]+(?:\/(?<directory>.*))?)?)?$/iu
|
|
13
13
|
const str = (value) => (typeof value === 'string' ? value : '')
|
|
14
|
-
|
|
14
|
+
// Read past the whitespace around it, as `homepage` is.
|
|
15
|
+
const urlOf = (field) => str(field?.url ?? field).trim()
|
|
15
16
|
const homepageUrl = (homepage) => str(homepage).trim().split(/[?#]/u)[0] // npm appends `#readme`.
|
|
16
|
-
|
|
17
|
+
// Every trailing `.git` dropped, as the URLs' are: no answer ends in one
|
|
18
|
+
// (the npm repo cache refuses one).
|
|
19
|
+
const repoIn = (regex, text) => withoutDotGit(regex.exec(text)?.groups.repo)
|
|
17
20
|
// A URL's path spells a tree path percent-encoded; one that does not decode names none.
|
|
18
21
|
function decodePath(path = '') {
|
|
19
22
|
try {
|
|
@@ -33,6 +36,19 @@ function repoSubdirectory(value) {
|
|
|
33
36
|
return isRepoPath(path) ? path : undefined // No empty, `.`, `..` or `.git` part: a path inside the repo.
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
// `repository.directory` is a path its author may write with Windows' `\`.
|
|
40
|
+
// A tree path is `/`-separated: a `\` is a name's own only in a homepage's
|
|
41
|
+
// (`%5C`), which is the path itself. Its empty and `.` parts are dropped
|
|
42
|
+
// (`./a//b/.` is `a/b`), and one of nothing else (`./`, `/`, `/.`, `.`, `''`)
|
|
43
|
+
// declares the repo's root, `''`; one with a `..` part names none, whatever
|
|
44
|
+
// it would come to. A package.json that declares none says nothing of where
|
|
45
|
+
// in the repo it sits.
|
|
46
|
+
function declaredDirectory(value) {
|
|
47
|
+
if (typeof value !== 'string') return undefined
|
|
48
|
+
const parts = value.replaceAll('\\', '/').split('/').filter((part) => part !== '' && part !== '.')
|
|
49
|
+
return parts.length === 0 ? '' : repoSubdirectory(parts.join('/'))
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
export const isRepoDirectory = (value) => typeof value === 'string' && (value === '' || repoSubdirectory(value) === value)
|
|
37
53
|
export const assertRepoDirectory = assertion('a path inside the repository', isRepoDirectory)
|
|
38
54
|
|
|
@@ -41,11 +57,12 @@ export function getRepo(pkg) {
|
|
|
41
57
|
const { bugs, repository } = pkg
|
|
42
58
|
const declared = repositoryRepo(urlOf(repository))
|
|
43
59
|
const homepage = homepageRegex.exec(homepageUrl(pkg.homepage))?.groups
|
|
60
|
+
const homepageRepo = withoutDotGit(homepage?.repo)
|
|
44
61
|
// `repository` first: it is the field a publisher maintains, where a
|
|
45
62
|
// `bugs` tracker can be left pointing at a former owner or misspelt.
|
|
46
|
-
const github = [declared, repoIn(bugsRegex, urlOf(bugs)),
|
|
63
|
+
const github = [declared, repoIn(bugsRegex, urlOf(bugs)), homepageRepo].find(isRepo)
|
|
47
64
|
if (github === undefined) return {}
|
|
48
|
-
const directory = (sameName(declared, github)
|
|
49
|
-
|
|
50
|
-
return { github, ...(directory && { directory }), url: `https://github.com/${github}` }
|
|
65
|
+
const directory = (sameName(declared, github) ? declaredDirectory(repository?.directory) : undefined)
|
|
66
|
+
?? (sameName(homepageRepo, github) ? repoSubdirectory(decodePath(homepage.directory)) : undefined)
|
|
67
|
+
return { github, ...(directory !== undefined && { directory }), url: `https://github.com/${github}` }
|
|
51
68
|
}
|
package/src/remote.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { isRepo } from './args.js'
|
|
2
2
|
|
|
3
3
|
// `user:token@` is skipped, never returned. It is held to the characters
|
|
4
|
-
// RFC 3986 allows there
|
|
4
|
+
// RFC 3986 allows there -- a `#`, `?` or `\` before the `@` ends the
|
|
5
5
|
// authority early, so `https://evil.example#@github.com/a/b` goes to
|
|
6
|
-
// evil.example
|
|
6
|
+
// evil.example -- and URL parsing has to agree the host is github.com.
|
|
7
7
|
const gitUrlRegex = /^(?i:(?:git\+)?(?:https?|git|ssh)):\/\/(?:[\w.~%!$&'()*+,;=:-]*@)?(?i:github\.com)(?::\d{1,5})?\/(?<repo>[\w-]+\/[\w.-]+?)(?:\.git)?\/?$/u
|
|
8
8
|
const scpRegex = /^(?i:git\+ssh:\/\/)?git@(?i:github\.com):(?<repo>[\w-]+\/[\w.-]+?)(?:\.git)?$/u
|
|
9
9
|
|
|
10
|
+
// A repo with every trailing `.git` dropped, so none ends in one: in a loop,
|
|
11
|
+
// as a regex would backtrack over a long run of them in a crafted URL.
|
|
12
|
+
export function withoutDotGit(repo) {
|
|
13
|
+
while (repo?.endsWith('.git')) repo = repo.slice(0, -4)
|
|
14
|
+
return repo
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
export function githubRepoOfUrl(url) {
|
|
11
18
|
if (typeof url !== 'string') return undefined
|
|
12
19
|
const onGitHub = URL.parse(url.replace(/^git\+/iu, ''))?.hostname.toLowerCase() === 'github.com'
|
|
13
|
-
const repo = scpRegex.exec(url)?.groups.repo ?? (onGitHub ? gitUrlRegex.exec(url)?.groups.repo : undefined)
|
|
20
|
+
const repo = withoutDotGit(scpRegex.exec(url)?.groups.repo ?? (onGitHub ? gitUrlRegex.exec(url)?.groups.repo : undefined))
|
|
14
21
|
return isRepo(repo) ? repo : undefined
|
|
15
22
|
}
|