avancement planning
This commit is contained in:
-13
@@ -1,13 +0,0 @@
|
||||
Copyright (c) 2015, Rebecca Turner
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
# hosted-git-info
|
||||
|
||||
This will let you identify and transform various git hosts URLs between
|
||||
protocols. It also can tell you what the URL is for the raw path for
|
||||
particular file for direct access without git.
|
||||
|
||||
## Example
|
||||
|
||||
```javascript
|
||||
const hostedGitInfo = require("hosted-git-info")
|
||||
const info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts)
|
||||
/* info looks like:
|
||||
{
|
||||
type: "github",
|
||||
domain: "github.com",
|
||||
user: "npm",
|
||||
project: "hosted-git-info"
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
If the URL can't be matched with a git host, `null` will be returned. We
|
||||
can match git, ssh and https urls. Additionally, we can match ssh connect
|
||||
strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg,
|
||||
`github:npm/hosted-git-info`). GitHub specifically, is detected in the case
|
||||
of a third, unprefixed, form: `npm/hosted-git-info`.
|
||||
|
||||
If it does match, the returned object has properties of:
|
||||
|
||||
* info.type -- The short name of the service
|
||||
* info.domain -- The domain for git protocol use
|
||||
* info.user -- The name of the user/org on the git host
|
||||
* info.project -- The name of the project on the git host
|
||||
|
||||
## Version Contract
|
||||
|
||||
The major version will be bumped any time…
|
||||
|
||||
* The constructor stops accepting URLs that it previously accepted.
|
||||
* A method is removed.
|
||||
* A method can no longer accept the number and type of arguments it previously accepted.
|
||||
* A method can return a different type than it currently returns.
|
||||
|
||||
Implications:
|
||||
|
||||
* I do not consider the specific format of the urls returned from, say
|
||||
`.https()` to be a part of the contract. The contract is that it will
|
||||
return a string that can be used to fetch the repo via HTTPS. But what
|
||||
that string looks like, specifically, can change.
|
||||
* Dropping support for a hosted git provider would constitute a breaking
|
||||
change.
|
||||
|
||||
## Usage
|
||||
|
||||
### const info = hostedGitInfo.fromUrl(gitSpecifier[, options])
|
||||
|
||||
* *gitSpecifer* is a URL of a git repository or a SCP-style specifier of one.
|
||||
* *options* is an optional object. It can have the following properties:
|
||||
* *noCommittish* — If true then committishes won't be included in generated URLs.
|
||||
* *noGitPlus* — If true then `git+` won't be prefixed on URLs.
|
||||
|
||||
### const infoOrURL = hostedGitInfo.fromManifest(manifest[, options])
|
||||
|
||||
* *manifest* is a package manifest, such as that returned by [`pacote.manifest()`](https://npmjs.com/pacote)
|
||||
* *options* is an optional object. It can have the same properties as `fromUrl` above.
|
||||
|
||||
## Methods
|
||||
|
||||
All of the methods take the same options as the `fromUrl` factory. Options
|
||||
provided to a method override those provided to the constructor.
|
||||
|
||||
* info.file(path, opts)
|
||||
|
||||
Given the path of a file relative to the repository, returns a URL for
|
||||
directly fetching it from the githost. If no committish was set then
|
||||
`HEAD` will be used as the default.
|
||||
|
||||
For example `hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git#v1.0.0").file("package.json")`
|
||||
would return `https://raw.githubusercontent.com/npm/hosted-git-info/v1.0.0/package.json`
|
||||
|
||||
* info.shortcut(opts)
|
||||
|
||||
eg, `github:npm/hosted-git-info`
|
||||
|
||||
* info.browse(path, fragment, opts)
|
||||
|
||||
eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0`,
|
||||
`https://github.com/npm/hosted-git-info/tree/v1.2.0/package.json`,
|
||||
`https://github.com/npm/hosted-git-info/tree/v1.2.0/README.md#supported-hosts`
|
||||
|
||||
* info.bugs(opts)
|
||||
|
||||
eg, `https://github.com/npm/hosted-git-info/issues`
|
||||
|
||||
* info.docs(opts)
|
||||
|
||||
eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0#readme`
|
||||
|
||||
* info.https(opts)
|
||||
|
||||
eg, `git+https://github.com/npm/hosted-git-info.git`
|
||||
|
||||
* info.sshurl(opts)
|
||||
|
||||
eg, `git+ssh://git@github.com/npm/hosted-git-info.git`
|
||||
|
||||
* info.ssh(opts)
|
||||
|
||||
eg, `git@github.com:npm/hosted-git-info.git`
|
||||
|
||||
* info.path(opts)
|
||||
|
||||
eg, `npm/hosted-git-info`
|
||||
|
||||
* info.tarball(opts)
|
||||
|
||||
eg, `https://github.com/npm/hosted-git-info/archive/v1.2.0.tar.gz`
|
||||
|
||||
* info.getDefaultRepresentation()
|
||||
|
||||
Returns the default output type. The default output type is based on the
|
||||
string you passed in to be parsed
|
||||
|
||||
* info.toString(opts)
|
||||
|
||||
Uses the getDefaultRepresentation to call one of the other methods to get a URL for
|
||||
this resource. As such `hostedGitInfo.fromUrl(url).toString()` will give
|
||||
you a normalized version of the URL that still uses the same protocol.
|
||||
|
||||
Shortcuts will still be returned as shortcuts, but the special case github
|
||||
form of `org/project` will be normalized to `github:org/project`.
|
||||
|
||||
SSH connect strings will be normalized into `git+ssh` URLs.
|
||||
|
||||
## Supported hosts
|
||||
|
||||
Currently this supports GitHub (including Gists), Bitbucket, GitLab and Sourcehut.
|
||||
Pull requests for additional hosts welcome.
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const parseUrl = require('./parse-url')
|
||||
|
||||
// look for github shorthand inputs, such as npm/cli
|
||||
const isGitHubShorthand = (arg) => {
|
||||
// it cannot contain whitespace before the first #
|
||||
// it cannot start with a / because that's probably an absolute file path
|
||||
// but it must include a slash since repos are username/repository
|
||||
// it cannot start with a . because that's probably a relative file path
|
||||
// it cannot start with an @ because that's a scoped package if it passes the other tests
|
||||
// it cannot contain a : before a # because that tells us that there's a protocol
|
||||
// a second / may not exist before a #
|
||||
const firstHash = arg.indexOf('#')
|
||||
const firstSlash = arg.indexOf('/')
|
||||
const secondSlash = arg.indexOf('/', firstSlash + 1)
|
||||
const firstColon = arg.indexOf(':')
|
||||
const firstSpace = /\s/.exec(arg)
|
||||
const firstAt = arg.indexOf('@')
|
||||
|
||||
const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash)
|
||||
const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash)
|
||||
const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash)
|
||||
const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash)
|
||||
const hasSlash = firstSlash > 0
|
||||
// if a # is found, what we really want to know is that the character
|
||||
// immediately before # is not a /
|
||||
const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/')
|
||||
const doesNotStartWithDot = !arg.startsWith('.')
|
||||
|
||||
return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash &&
|
||||
doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash &&
|
||||
secondSlashOnlyAfterHash
|
||||
}
|
||||
|
||||
module.exports = (giturl, opts, { gitHosts, protocols }) => {
|
||||
if (!giturl) {
|
||||
return
|
||||
}
|
||||
|
||||
const correctedUrl = isGitHubShorthand(giturl) ? `github:${giturl}` : giturl
|
||||
const parsed = parseUrl(correctedUrl, protocols)
|
||||
if (!parsed) {
|
||||
return
|
||||
}
|
||||
|
||||
const gitHostShortcut = gitHosts.byShortcut[parsed.protocol]
|
||||
const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.')
|
||||
? parsed.hostname.slice(4)
|
||||
: parsed.hostname]
|
||||
const gitHostName = gitHostShortcut || gitHostDomain
|
||||
if (!gitHostName) {
|
||||
return
|
||||
}
|
||||
|
||||
const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain]
|
||||
let auth = null
|
||||
if (protocols[parsed.protocol]?.auth && (parsed.username || parsed.password)) {
|
||||
auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}`
|
||||
}
|
||||
|
||||
let committish = null
|
||||
let user = null
|
||||
let project = null
|
||||
let defaultRepresentation = null
|
||||
|
||||
try {
|
||||
if (gitHostShortcut) {
|
||||
let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname
|
||||
const firstAt = pathname.indexOf('@')
|
||||
// we ignore auth for shortcuts, so just trim it out
|
||||
if (firstAt > -1) {
|
||||
pathname = pathname.slice(firstAt + 1)
|
||||
}
|
||||
|
||||
const lastSlash = pathname.lastIndexOf('/')
|
||||
if (lastSlash > -1) {
|
||||
user = decodeURIComponent(pathname.slice(0, lastSlash))
|
||||
// we want nulls only, never empty strings
|
||||
if (!user) {
|
||||
user = null
|
||||
}
|
||||
project = decodeURIComponent(pathname.slice(lastSlash + 1))
|
||||
} else {
|
||||
project = decodeURIComponent(pathname)
|
||||
}
|
||||
|
||||
if (project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
if (parsed.hash) {
|
||||
committish = decodeURIComponent(parsed.hash.slice(1))
|
||||
}
|
||||
|
||||
defaultRepresentation = 'shortcut'
|
||||
} else {
|
||||
if (!gitHostInfo.protocols.includes(parsed.protocol)) {
|
||||
return
|
||||
}
|
||||
|
||||
const segments = gitHostInfo.extract(parsed)
|
||||
if (!segments) {
|
||||
return
|
||||
}
|
||||
|
||||
user = segments.user && decodeURIComponent(segments.user)
|
||||
project = decodeURIComponent(segments.project)
|
||||
committish = decodeURIComponent(segments.committish)
|
||||
defaultRepresentation = protocols[parsed.protocol]?.name || parsed.protocol.slice(0, -1)
|
||||
}
|
||||
} catch (err) {
|
||||
/* istanbul ignore else */
|
||||
if (err instanceof URIError) {
|
||||
return
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return [gitHostName, user, auth, project, committish, defaultRepresentation, opts]
|
||||
}
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
/* eslint-disable max-len */
|
||||
|
||||
'use strict'
|
||||
|
||||
const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : ''
|
||||
const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : ''
|
||||
const formatHashFragment = (f) => f.toLowerCase()
|
||||
.replace(/^\W+/g, '') // strip leading non-characters
|
||||
.replace(/(?<!\W)\W+$/, '') // strip trailing non-characters
|
||||
.replace(/\//g, '') // strip all slashes
|
||||
.replace(/\W+/g, '-') // replace remaining non-characters with '-'
|
||||
|
||||
const defaults = {
|
||||
sshtemplate: ({ domain, user, project, committish }) =>
|
||||
`git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
sshurltemplate: ({ domain, user, project, committish }) =>
|
||||
`git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
edittemplate: ({ domain, user, project, committish, editpath, path }) =>
|
||||
`https://${domain}/${user}/${project}${maybeJoin('/', editpath, '/', maybeEncode(committish || 'HEAD'), '/', path)}`,
|
||||
browsetemplate: ({ domain, user, project, committish, treepath }) =>
|
||||
`https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`,
|
||||
browsetreetemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) =>
|
||||
`https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'HEAD')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`,
|
||||
browseblobtemplate: ({ domain, user, project, committish, blobpath, path, fragment, hashformat }) =>
|
||||
`https://${domain}/${user}/${project}/${blobpath}/${maybeEncode(committish || 'HEAD')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`,
|
||||
docstemplate: ({ domain, user, project, treepath, committish }) =>
|
||||
`https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`,
|
||||
httpstemplate: ({ auth, domain, user, project, committish }) =>
|
||||
`git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
filetemplate: ({ domain, user, project, committish, path }) =>
|
||||
`https://${domain}/${user}/${project}/raw/${maybeEncode(committish || 'HEAD')}/${path}`,
|
||||
shortcuttemplate: ({ type, user, project, committish }) =>
|
||||
`${type}:${user}/${project}${maybeJoin('#', committish)}`,
|
||||
pathtemplate: ({ user, project, committish }) =>
|
||||
`${user}/${project}${maybeJoin('#', committish)}`,
|
||||
bugstemplate: ({ domain, user, project }) =>
|
||||
`https://${domain}/${user}/${project}/issues`,
|
||||
hashformat: formatHashFragment,
|
||||
}
|
||||
|
||||
const hosts = {}
|
||||
hosts.github = {
|
||||
// First two are insecure and generally shouldn't be used any more, but
|
||||
// they are still supported.
|
||||
protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'],
|
||||
domain: 'github.com',
|
||||
treepath: 'tree',
|
||||
blobpath: 'blob',
|
||||
editpath: 'edit',
|
||||
filetemplate: ({ auth, user, project, committish, path }) =>
|
||||
`https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish || 'HEAD')}/${path}`,
|
||||
gittemplate: ({ auth, domain, user, project, committish }) =>
|
||||
`git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
tarballtemplate: ({ domain, user, project, committish }) =>
|
||||
`https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish || 'HEAD')}`,
|
||||
extract: (url) => {
|
||||
let [, user, project, type, committish] = url.pathname.split('/', 5)
|
||||
if (type && type !== 'tree') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
committish = url.hash.slice(1)
|
||||
}
|
||||
|
||||
if (project && project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
if (!user || !project) {
|
||||
return
|
||||
}
|
||||
|
||||
return { user, project, committish }
|
||||
},
|
||||
}
|
||||
|
||||
hosts.bitbucket = {
|
||||
protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'],
|
||||
domain: 'bitbucket.org',
|
||||
treepath: 'src',
|
||||
blobpath: 'src',
|
||||
editpath: '?mode=edit',
|
||||
edittemplate: ({ domain, user, project, committish, treepath, path, editpath }) =>
|
||||
`https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish || 'HEAD'), '/', path, editpath)}`,
|
||||
tarballtemplate: ({ domain, user, project, committish }) =>
|
||||
`https://${domain}/${user}/${project}/get/${maybeEncode(committish || 'HEAD')}.tar.gz`,
|
||||
extract: (url) => {
|
||||
let [, user, project, aux] = url.pathname.split('/', 4)
|
||||
if (['get'].includes(aux)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (project && project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
if (!user || !project) {
|
||||
return
|
||||
}
|
||||
|
||||
return { user, project, committish: url.hash.slice(1) }
|
||||
},
|
||||
}
|
||||
|
||||
hosts.gitlab = {
|
||||
protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'],
|
||||
domain: 'gitlab.com',
|
||||
treepath: 'tree',
|
||||
blobpath: 'tree',
|
||||
editpath: '-/edit',
|
||||
httpstemplate: ({ auth, domain, user, project, committish }) =>
|
||||
`git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
tarballtemplate: ({ domain, user, project, committish }) =>
|
||||
`https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish || 'HEAD')}`,
|
||||
extract: (url) => {
|
||||
const path = url.pathname.slice(1)
|
||||
if (path.includes('/-/') || path.includes('/archive.tar.gz')) {
|
||||
return
|
||||
}
|
||||
|
||||
const segments = path.split('/')
|
||||
let project = segments.pop()
|
||||
if (project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
const user = segments.join('/')
|
||||
if (!user || !project) {
|
||||
return
|
||||
}
|
||||
|
||||
return { user, project, committish: url.hash.slice(1) }
|
||||
},
|
||||
}
|
||||
|
||||
hosts.gist = {
|
||||
protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'],
|
||||
domain: 'gist.github.com',
|
||||
editpath: 'edit',
|
||||
sshtemplate: ({ domain, project, committish }) =>
|
||||
`git@${domain}:${project}.git${maybeJoin('#', committish)}`,
|
||||
sshurltemplate: ({ domain, project, committish }) =>
|
||||
`git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`,
|
||||
edittemplate: ({ domain, user, project, committish, editpath }) =>
|
||||
`https://${domain}/${user}/${project}${maybeJoin('/', maybeEncode(committish))}/${editpath}`,
|
||||
browsetemplate: ({ domain, project, committish }) =>
|
||||
`https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`,
|
||||
browsetreetemplate: ({ domain, project, committish, path, hashformat }) =>
|
||||
`https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`,
|
||||
browseblobtemplate: ({ domain, project, committish, path, hashformat }) =>
|
||||
`https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`,
|
||||
docstemplate: ({ domain, project, committish }) =>
|
||||
`https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`,
|
||||
httpstemplate: ({ domain, project, committish }) =>
|
||||
`git+https://${domain}/${project}.git${maybeJoin('#', committish)}`,
|
||||
filetemplate: ({ user, project, committish, path }) =>
|
||||
`https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`,
|
||||
shortcuttemplate: ({ type, project, committish }) =>
|
||||
`${type}:${project}${maybeJoin('#', committish)}`,
|
||||
pathtemplate: ({ project, committish }) =>
|
||||
`${project}${maybeJoin('#', committish)}`,
|
||||
bugstemplate: ({ domain, project }) =>
|
||||
`https://${domain}/${project}`,
|
||||
gittemplate: ({ domain, project, committish }) =>
|
||||
`git://${domain}/${project}.git${maybeJoin('#', committish)}`,
|
||||
tarballtemplate: ({ project, committish }) =>
|
||||
`https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish || 'HEAD')}`,
|
||||
extract: (url) => {
|
||||
let [, user, project, aux] = url.pathname.split('/', 4)
|
||||
if (aux === 'raw') {
|
||||
return
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
project = user
|
||||
user = null
|
||||
}
|
||||
|
||||
if (project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
return { user, project, committish: url.hash.slice(1) }
|
||||
},
|
||||
hashformat: function (fragment) {
|
||||
return fragment && 'file-' + formatHashFragment(fragment)
|
||||
},
|
||||
}
|
||||
|
||||
hosts.sourcehut = {
|
||||
protocols: ['git+ssh:', 'https:'],
|
||||
domain: 'git.sr.ht',
|
||||
treepath: 'tree',
|
||||
blobpath: 'tree',
|
||||
filetemplate: ({ domain, user, project, committish, path }) =>
|
||||
`https://${domain}/${user}/${project}/blob/${maybeEncode(committish) || 'HEAD'}/${path}`,
|
||||
httpstemplate: ({ domain, user, project, committish }) =>
|
||||
`https://${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
|
||||
tarballtemplate: ({ domain, user, project, committish }) =>
|
||||
`https://${domain}/${user}/${project}/archive/${maybeEncode(committish) || 'HEAD'}.tar.gz`,
|
||||
bugstemplate: () => null,
|
||||
extract: (url) => {
|
||||
let [, user, project, aux] = url.pathname.split('/', 4)
|
||||
|
||||
// tarball url
|
||||
if (['archive'].includes(aux)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (project && project.endsWith('.git')) {
|
||||
project = project.slice(0, -4)
|
||||
}
|
||||
|
||||
if (!user || !project) {
|
||||
return
|
||||
}
|
||||
|
||||
return { user, project, committish: url.hash.slice(1) }
|
||||
},
|
||||
}
|
||||
|
||||
for (const [name, host] of Object.entries(hosts)) {
|
||||
hosts[name] = Object.assign({}, defaults, host)
|
||||
}
|
||||
|
||||
module.exports = hosts
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { LRUCache } = require('lru-cache')
|
||||
const hosts = require('./hosts.js')
|
||||
const fromUrl = require('./from-url.js')
|
||||
const parseUrl = require('./parse-url.js')
|
||||
|
||||
const cache = new LRUCache({ max: 1000 })
|
||||
|
||||
function unknownHostedUrl (url) {
|
||||
try {
|
||||
const {
|
||||
protocol,
|
||||
hostname,
|
||||
pathname,
|
||||
} = new URL(url)
|
||||
|
||||
if (!hostname) {
|
||||
return null
|
||||
}
|
||||
|
||||
const proto = /(?:git\+)http:$/.test(protocol) ? 'http:' : 'https:'
|
||||
const path = pathname.replace(/\.git$/, '')
|
||||
return `${proto}//${hostname}${path}`
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class GitHost {
|
||||
constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) {
|
||||
Object.assign(this, GitHost.#gitHosts[type], {
|
||||
type,
|
||||
user,
|
||||
auth,
|
||||
project,
|
||||
committish,
|
||||
default: defaultRepresentation,
|
||||
opts,
|
||||
})
|
||||
}
|
||||
|
||||
static #gitHosts = { byShortcut: {}, byDomain: {} }
|
||||
static #protocols = {
|
||||
'git+ssh:': { name: 'sshurl' },
|
||||
'ssh:': { name: 'sshurl' },
|
||||
'git+https:': { name: 'https', auth: true },
|
||||
'git:': { auth: true },
|
||||
'http:': { auth: true },
|
||||
'https:': { auth: true },
|
||||
'git+http:': { auth: true },
|
||||
}
|
||||
|
||||
static addHost (name, host) {
|
||||
GitHost.#gitHosts[name] = host
|
||||
GitHost.#gitHosts.byDomain[host.domain] = name
|
||||
GitHost.#gitHosts.byShortcut[`${name}:`] = name
|
||||
GitHost.#protocols[`${name}:`] = { name }
|
||||
}
|
||||
|
||||
static fromUrl (giturl, opts) {
|
||||
if (typeof giturl !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
const key = giturl + JSON.stringify(opts || {})
|
||||
|
||||
if (!cache.has(key)) {
|
||||
const hostArgs = fromUrl(giturl, opts, {
|
||||
gitHosts: GitHost.#gitHosts,
|
||||
protocols: GitHost.#protocols,
|
||||
})
|
||||
cache.set(key, hostArgs ? new GitHost(...hostArgs) : undefined)
|
||||
}
|
||||
|
||||
return cache.get(key)
|
||||
}
|
||||
|
||||
static fromManifest (manifest, opts = {}) {
|
||||
if (!manifest || typeof manifest !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
const r = manifest.repository
|
||||
// TODO: look into also checking the `bugs`/`homepage` URLs
|
||||
|
||||
const rurl = r && (
|
||||
typeof r === 'string'
|
||||
? r
|
||||
: typeof r === 'object' && typeof r.url === 'string'
|
||||
? r.url
|
||||
: null
|
||||
)
|
||||
|
||||
if (!rurl) {
|
||||
throw new Error('no repository')
|
||||
}
|
||||
|
||||
const info = (rurl && GitHost.fromUrl(rurl.replace(/^git\+/, ''), opts)) || null
|
||||
if (info) {
|
||||
return info
|
||||
}
|
||||
const unk = unknownHostedUrl(rurl)
|
||||
return GitHost.fromUrl(unk, opts) || unk
|
||||
}
|
||||
|
||||
static parseUrl (url) {
|
||||
return parseUrl(url)
|
||||
}
|
||||
|
||||
#fill (template, opts) {
|
||||
if (typeof template !== 'function') {
|
||||
return null
|
||||
}
|
||||
|
||||
const options = { ...this, ...this.opts, ...opts }
|
||||
|
||||
// the path should always be set so we don't end up with 'undefined' in urls
|
||||
if (!options.path) {
|
||||
options.path = ''
|
||||
}
|
||||
|
||||
// template functions will insert the leading slash themselves
|
||||
if (options.path.startsWith('/')) {
|
||||
options.path = options.path.slice(1)
|
||||
}
|
||||
|
||||
if (options.noCommittish) {
|
||||
options.committish = null
|
||||
}
|
||||
|
||||
const result = template(options)
|
||||
return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result
|
||||
}
|
||||
|
||||
hash () {
|
||||
return this.committish ? `#${this.committish}` : ''
|
||||
}
|
||||
|
||||
ssh (opts) {
|
||||
return this.#fill(this.sshtemplate, opts)
|
||||
}
|
||||
|
||||
sshurl (opts) {
|
||||
return this.#fill(this.sshurltemplate, opts)
|
||||
}
|
||||
|
||||
browse (path, ...args) {
|
||||
// not a string, treat path as opts
|
||||
if (typeof path !== 'string') {
|
||||
return this.#fill(this.browsetemplate, path)
|
||||
}
|
||||
|
||||
if (typeof args[0] !== 'string') {
|
||||
return this.#fill(this.browsetreetemplate, { ...args[0], path })
|
||||
}
|
||||
|
||||
return this.#fill(this.browsetreetemplate, { ...args[1], fragment: args[0], path })
|
||||
}
|
||||
|
||||
// If the path is known to be a file, then browseFile should be used. For some hosts
|
||||
// the url is the same as browse, but for others like GitHub a file can use both `/tree/`
|
||||
// and `/blob/` in the path. When using a default committish of `HEAD` then the `/tree/`
|
||||
// path will redirect to a specific commit. Using the `/blob/` path avoids this and
|
||||
// does not redirect to a different commit.
|
||||
browseFile (path, ...args) {
|
||||
if (typeof args[0] !== 'string') {
|
||||
return this.#fill(this.browseblobtemplate, { ...args[0], path })
|
||||
}
|
||||
|
||||
return this.#fill(this.browseblobtemplate, { ...args[1], fragment: args[0], path })
|
||||
}
|
||||
|
||||
docs (opts) {
|
||||
return this.#fill(this.docstemplate, opts)
|
||||
}
|
||||
|
||||
bugs (opts) {
|
||||
return this.#fill(this.bugstemplate, opts)
|
||||
}
|
||||
|
||||
https (opts) {
|
||||
return this.#fill(this.httpstemplate, opts)
|
||||
}
|
||||
|
||||
git (opts) {
|
||||
return this.#fill(this.gittemplate, opts)
|
||||
}
|
||||
|
||||
shortcut (opts) {
|
||||
return this.#fill(this.shortcuttemplate, opts)
|
||||
}
|
||||
|
||||
path (opts) {
|
||||
return this.#fill(this.pathtemplate, opts)
|
||||
}
|
||||
|
||||
tarball (opts) {
|
||||
return this.#fill(this.tarballtemplate, { ...opts, noCommittish: false })
|
||||
}
|
||||
|
||||
file (path, opts) {
|
||||
return this.#fill(this.filetemplate, { ...opts, path })
|
||||
}
|
||||
|
||||
edit (path, opts) {
|
||||
return this.#fill(this.edittemplate, { ...opts, path })
|
||||
}
|
||||
|
||||
getDefaultRepresentation () {
|
||||
return this.default
|
||||
}
|
||||
|
||||
toString (opts) {
|
||||
if (this.default && typeof this[this.default] === 'function') {
|
||||
return this[this.default](opts)
|
||||
}
|
||||
|
||||
return this.sshurl(opts)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, host] of Object.entries(hosts)) {
|
||||
GitHost.addHost(name, host)
|
||||
}
|
||||
|
||||
module.exports = GitHost
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
const url = require('url')
|
||||
|
||||
const lastIndexOfBefore = (str, char, beforeChar) => {
|
||||
const startPosition = str.indexOf(beforeChar)
|
||||
return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity)
|
||||
}
|
||||
|
||||
const safeUrl = (u) => {
|
||||
try {
|
||||
return new url.URL(u)
|
||||
} catch {
|
||||
// this fn should never throw
|
||||
}
|
||||
}
|
||||
|
||||
// accepts input like git:github.com:user/repo and inserts the // after the first :
|
||||
const correctProtocol = (arg, protocols) => {
|
||||
const firstColon = arg.indexOf(':')
|
||||
const proto = arg.slice(0, firstColon + 1)
|
||||
if (Object.prototype.hasOwnProperty.call(protocols, proto)) {
|
||||
return arg
|
||||
}
|
||||
|
||||
const firstAt = arg.indexOf('@')
|
||||
if (firstAt > -1) {
|
||||
if (firstAt > firstColon) {
|
||||
return `git+ssh://${arg}`
|
||||
} else {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
|
||||
const doubleSlash = arg.indexOf('//')
|
||||
if (doubleSlash === firstColon + 1) {
|
||||
return arg
|
||||
}
|
||||
|
||||
return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}`
|
||||
}
|
||||
|
||||
// attempt to correct an scp style url so that it will parse with `new URL()`
|
||||
const correctUrl = (giturl) => {
|
||||
// ignore @ that come after the first hash since the denotes the start
|
||||
// of a committish which can contain @ characters
|
||||
const firstAt = lastIndexOfBefore(giturl, '@', '#')
|
||||
// ignore colons that come after the hash since that could include colons such as:
|
||||
// git@github.com:user/package-2#semver:^1.0.0
|
||||
const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#')
|
||||
|
||||
if (lastColonBeforeHash > firstAt) {
|
||||
// the last : comes after the first @ (or there is no @)
|
||||
// like it would in:
|
||||
// proto://hostname.com:user/repo
|
||||
// username@hostname.com:user/repo
|
||||
// :password@hostname.com:user/repo
|
||||
// username:password@hostname.com:user/repo
|
||||
// proto://username@hostname.com:user/repo
|
||||
// proto://:password@hostname.com:user/repo
|
||||
// proto://username:password@hostname.com:user/repo
|
||||
// then we replace the last : with a / to create a valid path
|
||||
giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1)
|
||||
}
|
||||
|
||||
if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) {
|
||||
// we have no : at all
|
||||
// as it would be in:
|
||||
// username@hostname.com/user/repo
|
||||
// then we prepend a protocol
|
||||
giturl = `git+ssh://${giturl}`
|
||||
}
|
||||
|
||||
return giturl
|
||||
}
|
||||
|
||||
module.exports = (giturl, protocols) => {
|
||||
const withProtocol = protocols ? correctProtocol(giturl, protocols) : giturl
|
||||
return safeUrl(withProtocol) || safeUrl(correctUrl(withProtocol))
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"name": "hosted-git-info",
|
||||
"version": "8.1.0",
|
||||
"description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/npm/hosted-git-info.git"
|
||||
},
|
||||
"keywords": [
|
||||
"git",
|
||||
"github",
|
||||
"bitbucket",
|
||||
"gitlab"
|
||||
],
|
||||
"author": "GitHub Inc.",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/npm/hosted-git-info/issues"
|
||||
},
|
||||
"homepage": "https://github.com/npm/hosted-git-info",
|
||||
"scripts": {
|
||||
"posttest": "npm run lint",
|
||||
"snap": "tap",
|
||||
"test": "tap",
|
||||
"test:coverage": "tap --coverage-report=html",
|
||||
"lint": "npm run eslint",
|
||||
"postlint": "template-oss-check",
|
||||
"lintfix": "npm run eslint -- --fix",
|
||||
"template-oss-apply": "template-oss-apply --force",
|
||||
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@npmcli/eslint-config": "^5.0.0",
|
||||
"@npmcli/template-oss": "4.24.3",
|
||||
"tap": "^16.0.1"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"lib/"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
},
|
||||
"tap": {
|
||||
"color": 1,
|
||||
"coverage": true,
|
||||
"nyc-arg": [
|
||||
"--exclude",
|
||||
"tap-snapshots/**"
|
||||
]
|
||||
},
|
||||
"templateOSS": {
|
||||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
||||
"version": "4.24.3",
|
||||
"publish": "true"
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) 2010-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
-331
@@ -1,331 +0,0 @@
|
||||
# lru-cache
|
||||
|
||||
A cache object that deletes the least-recently-used items.
|
||||
|
||||
Specify a max number of the most recently used items that you
|
||||
want to keep, and this cache will keep that many of the most
|
||||
recently accessed items.
|
||||
|
||||
This is not primarily a TTL cache, and does not make strong TTL
|
||||
guarantees. There is no preemptive pruning of expired items by
|
||||
default, but you _may_ set a TTL on the cache or on a single
|
||||
`set`. If you do so, it will treat expired items as missing, and
|
||||
delete them when fetched. If you are more interested in TTL
|
||||
caching than LRU caching, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
As of version 7, this is one of the most performant LRU
|
||||
implementations available in JavaScript, and supports a wide
|
||||
diversity of use cases. However, note that using some of the
|
||||
features will necessarily impact performance, by causing the
|
||||
cache to have to do more work. See the "Performance" section
|
||||
below.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install lru-cache --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// hybrid module, either works
|
||||
import { LRUCache } from 'lru-cache'
|
||||
// or:
|
||||
const { LRUCache } = require('lru-cache')
|
||||
// or in minified form for web browsers:
|
||||
import { LRUCache } from 'http://unpkg.com/lru-cache@9/dist/mjs/index.min.mjs'
|
||||
|
||||
// At least one of 'max', 'ttl', or 'maxSize' is required, to prevent
|
||||
// unsafe unbounded storage.
|
||||
//
|
||||
// In most cases, it's best to specify a max for performance, so all
|
||||
// the required memory allocation is done up-front.
|
||||
//
|
||||
// All the other options are optional, see the sections below for
|
||||
// documentation on what each one does. Most of them can be
|
||||
// overridden for specific items in get()/set()
|
||||
const options = {
|
||||
max: 500,
|
||||
|
||||
// for use with tracking overall storage size
|
||||
maxSize: 5000,
|
||||
sizeCalculation: (value, key) => {
|
||||
return 1
|
||||
},
|
||||
|
||||
// for use when you need to clean up something when objects
|
||||
// are evicted from the cache
|
||||
dispose: (value, key) => {
|
||||
freeFromMemoryOrWhatever(value)
|
||||
},
|
||||
|
||||
// how long to live in ms
|
||||
ttl: 1000 * 60 * 5,
|
||||
|
||||
// return stale items before removing from cache?
|
||||
allowStale: false,
|
||||
|
||||
updateAgeOnGet: false,
|
||||
updateAgeOnHas: false,
|
||||
|
||||
// async method to use for cache.fetch(), for
|
||||
// stale-while-revalidate type of behavior
|
||||
fetchMethod: async (
|
||||
key,
|
||||
staleValue,
|
||||
{ options, signal, context }
|
||||
) => {},
|
||||
}
|
||||
|
||||
const cache = new LRUCache(options)
|
||||
|
||||
cache.set('key', 'value')
|
||||
cache.get('key') // "value"
|
||||
|
||||
// non-string keys ARE fully supported
|
||||
// but note that it must be THE SAME object, not
|
||||
// just a JSON-equivalent object.
|
||||
var someObject = { a: 1 }
|
||||
cache.set(someObject, 'a value')
|
||||
// Object keys are not toString()-ed
|
||||
cache.set('[object Object]', 'a different value')
|
||||
assert.equal(cache.get(someObject), 'a value')
|
||||
// A similar object with same keys/values won't work,
|
||||
// because it's a different object identity
|
||||
assert.equal(cache.get({ a: 1 }), undefined)
|
||||
|
||||
cache.clear() // empty the cache
|
||||
```
|
||||
|
||||
If you put more stuff in the cache, then less recently used items
|
||||
will fall out. That's what an LRU cache is.
|
||||
|
||||
For full description of the API and all options, please see [the
|
||||
LRUCache typedocs](https://isaacs.github.io/node-lru-cache/)
|
||||
|
||||
## Storage Bounds Safety
|
||||
|
||||
This implementation aims to be as flexible as possible, within
|
||||
the limits of safe memory consumption and optimal performance.
|
||||
|
||||
At initial object creation, storage is allocated for `max` items.
|
||||
If `max` is set to zero, then some performance is lost, and item
|
||||
count is unbounded. Either `maxSize` or `ttl` _must_ be set if
|
||||
`max` is not specified.
|
||||
|
||||
If `maxSize` is set, then this creates a safe limit on the
|
||||
maximum storage consumed, but without the performance benefits of
|
||||
pre-allocation. When `maxSize` is set, every item _must_ provide
|
||||
a size, either via the `sizeCalculation` method provided to the
|
||||
constructor, or via a `size` or `sizeCalculation` option provided
|
||||
to `cache.set()`. The size of every item _must_ be a positive
|
||||
integer.
|
||||
|
||||
If neither `max` nor `maxSize` are set, then `ttl` tracking must
|
||||
be enabled. Note that, even when tracking item `ttl`, items are
|
||||
_not_ preemptively deleted when they become stale, unless
|
||||
`ttlAutopurge` is enabled. Instead, they are only purged the
|
||||
next time the key is requested. Thus, if `ttlAutopurge`, `max`,
|
||||
and `maxSize` are all not set, then the cache will potentially
|
||||
grow unbounded.
|
||||
|
||||
In this case, a warning is printed to standard error. Future
|
||||
versions may require the use of `ttlAutopurge` if `max` and
|
||||
`maxSize` are not specified.
|
||||
|
||||
If you truly wish to use a cache that is bound _only_ by TTL
|
||||
expiration, consider using a `Map` object, and calling
|
||||
`setTimeout` to delete entries when they expire. It will perform
|
||||
much better than an LRU cache.
|
||||
|
||||
Here is an implementation you may use, under the same
|
||||
[license](./LICENSE) as this package:
|
||||
|
||||
```js
|
||||
// a storage-unbounded ttl cache that is not an lru-cache
|
||||
const cache = {
|
||||
data: new Map(),
|
||||
timers: new Map(),
|
||||
set: (k, v, ttl) => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.set(
|
||||
k,
|
||||
setTimeout(() => cache.delete(k), ttl)
|
||||
)
|
||||
cache.data.set(k, v)
|
||||
},
|
||||
get: k => cache.data.get(k),
|
||||
has: k => cache.data.has(k),
|
||||
delete: k => {
|
||||
if (cache.timers.has(k)) {
|
||||
clearTimeout(cache.timers.get(k))
|
||||
}
|
||||
cache.timers.delete(k)
|
||||
return cache.data.delete(k)
|
||||
},
|
||||
clear: () => {
|
||||
cache.data.clear()
|
||||
for (const v of cache.timers.values()) {
|
||||
clearTimeout(v)
|
||||
}
|
||||
cache.timers.clear()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If that isn't to your liking, check out
|
||||
[@isaacs/ttlcache](http://npm.im/@isaacs/ttlcache).
|
||||
|
||||
## Storing Undefined Values
|
||||
|
||||
This cache never stores undefined values, as `undefined` is used
|
||||
internally in a few places to indicate that a key is not in the
|
||||
cache.
|
||||
|
||||
You may call `cache.set(key, undefined)`, but this is just
|
||||
an alias for `cache.delete(key)`. Note that this has the effect
|
||||
that `cache.has(key)` will return _false_ after setting it to
|
||||
undefined.
|
||||
|
||||
```js
|
||||
cache.set(myKey, undefined)
|
||||
cache.has(myKey) // false!
|
||||
```
|
||||
|
||||
If you need to track `undefined` values, and still note that the
|
||||
key is in the cache, an easy workaround is to use a sigil object
|
||||
of your own.
|
||||
|
||||
```js
|
||||
import { LRUCache } from 'lru-cache'
|
||||
const undefinedValue = Symbol('undefined')
|
||||
const cache = new LRUCache(...)
|
||||
const mySet = (key, value) =>
|
||||
cache.set(key, value === undefined ? undefinedValue : value)
|
||||
const myGet = (key, value) => {
|
||||
const v = cache.get(key)
|
||||
return v === undefinedValue ? undefined : v
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
As of January 2022, version 7 of this library is one of the most
|
||||
performant LRU cache implementations in JavaScript.
|
||||
|
||||
Benchmarks can be extremely difficult to get right. In
|
||||
particular, the performance of set/get/delete operations on
|
||||
objects will vary _wildly_ depending on the type of key used. V8
|
||||
is highly optimized for objects with keys that are short strings,
|
||||
especially integer numeric strings. Thus any benchmark which
|
||||
tests _solely_ using numbers as keys will tend to find that an
|
||||
object-based approach performs the best.
|
||||
|
||||
Note that coercing _anything_ to strings to use as object keys is
|
||||
unsafe, unless you can be 100% certain that no other type of
|
||||
value will be used. For example:
|
||||
|
||||
```js
|
||||
const myCache = {}
|
||||
const set = (k, v) => (myCache[k] = v)
|
||||
const get = k => myCache[k]
|
||||
|
||||
set({}, 'please hang onto this for me')
|
||||
set('[object Object]', 'oopsie')
|
||||
```
|
||||
|
||||
Also beware of "Just So" stories regarding performance. Garbage
|
||||
collection of large (especially: deep) object graphs can be
|
||||
incredibly costly, with several "tipping points" where it
|
||||
increases exponentially. As a result, putting that off until
|
||||
later can make it much worse, and less predictable. If a library
|
||||
performs well, but only in a scenario where the object graph is
|
||||
kept shallow, then that won't help you if you are using large
|
||||
objects as keys.
|
||||
|
||||
In general, when attempting to use a library to improve
|
||||
performance (such as a cache like this one), it's best to choose
|
||||
an option that will perform well in the sorts of scenarios where
|
||||
you'll actually use it.
|
||||
|
||||
This library is optimized for repeated gets and minimizing
|
||||
eviction time, since that is the expected need of a LRU. Set
|
||||
operations are somewhat slower on average than a few other
|
||||
options, in part because of that optimization. It is assumed
|
||||
that you'll be caching some costly operation, ideally as rarely
|
||||
as possible, so optimizing set over get would be unwise.
|
||||
|
||||
If performance matters to you:
|
||||
|
||||
1. If it's at all possible to use small integer values as keys,
|
||||
and you can guarantee that no other types of values will be
|
||||
used as keys, then do that, and use a cache such as
|
||||
[lru-fast](https://npmjs.com/package/lru-fast), or
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache)
|
||||
which uses an Object as its data store.
|
||||
|
||||
2. Failing that, if at all possible, use short non-numeric
|
||||
strings (ie, less than 256 characters) as your keys, and use
|
||||
[mnemonist's
|
||||
LRUCache](https://yomguithereal.github.io/mnemonist/lru-cache).
|
||||
|
||||
3. If the types of your keys will be anything else, especially
|
||||
long strings, strings that look like floats, objects, or some
|
||||
mix of types, or if you aren't sure, then this library will
|
||||
work well for you.
|
||||
|
||||
If you do not need the features that this library provides
|
||||
(like asynchronous fetching, a variety of TTL staleness
|
||||
options, and so on), then [mnemonist's
|
||||
LRUMap](https://yomguithereal.github.io/mnemonist/lru-map) is
|
||||
a very good option, and just slightly faster than this module
|
||||
(since it does considerably less).
|
||||
|
||||
4. Do not use a `dispose` function, size tracking, or especially
|
||||
ttl behavior, unless absolutely needed. These features are
|
||||
convenient, and necessary in some use cases, and every attempt
|
||||
has been made to make the performance impact minimal, but it
|
||||
isn't nothing.
|
||||
|
||||
## Breaking Changes in Version 7
|
||||
|
||||
This library changed to a different algorithm and internal data
|
||||
structure in version 7, yielding significantly better
|
||||
performance, albeit with some subtle changes as a result.
|
||||
|
||||
If you were relying on the internals of LRUCache in version 6 or
|
||||
before, it probably will not work in version 7 and above.
|
||||
|
||||
## Breaking Changes in Version 8
|
||||
|
||||
- The `fetchContext` option was renamed to `context`, and may no
|
||||
longer be set on the cache instance itself.
|
||||
- Rewritten in TypeScript, so pretty much all the types moved
|
||||
around a lot.
|
||||
- The AbortController/AbortSignal polyfill was removed. For this
|
||||
reason, **Node version 16.14.0 or higher is now required**.
|
||||
- Internal properties were moved to actual private class
|
||||
properties.
|
||||
- Keys and values must not be `null` or `undefined`.
|
||||
- Minified export available at `'lru-cache/min'`, for both CJS
|
||||
and MJS builds.
|
||||
|
||||
## Breaking Changes in Version 9
|
||||
|
||||
- Named export only, no default export.
|
||||
- AbortController polyfill returned, albeit with a warning when
|
||||
used.
|
||||
|
||||
## Breaking Changes in Version 10
|
||||
|
||||
- `cache.fetch()` return type is now `Promise<V | undefined>`
|
||||
instead of `Promise<V | void>`. This is an irrelevant change
|
||||
practically speaking, but can require changes for TypeScript
|
||||
users.
|
||||
|
||||
For more info, see the [change log](CHANGELOG.md).
|
||||
-1277
File diff suppressed because it is too large
Load Diff
-1
File diff suppressed because one or more lines are too long
-1546
File diff suppressed because it is too large
Load Diff
-1
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
-7
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
-1277
File diff suppressed because it is too large
Load Diff
-1
File diff suppressed because one or more lines are too long
-1542
File diff suppressed because it is too large
Load Diff
-1
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
-7
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
{
|
||||
"name": "lru-cache",
|
||||
"publishConfig": {
|
||||
"tag": "legacy-v10"
|
||||
},
|
||||
"description": "A cache object that deletes the least-recently-used items.",
|
||||
"version": "10.4.3",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me>",
|
||||
"keywords": [
|
||||
"mru",
|
||||
"lru",
|
||||
"cache"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "npm run prepare",
|
||||
"prepare": "tshy && bash fixup.sh",
|
||||
"pretest": "npm run prepare",
|
||||
"presnap": "npm run prepare",
|
||||
"test": "tap",
|
||||
"snap": "tap",
|
||||
"preversion": "npm test",
|
||||
"postversion": "npm publish",
|
||||
"prepublishOnly": "git push origin --follow-tags",
|
||||
"format": "prettier --write .",
|
||||
"typedoc": "typedoc --tsconfig ./.tshy/esm.json ./src/*.ts",
|
||||
"benchmark-results-typedoc": "bash scripts/benchmark-results-typedoc.sh",
|
||||
"prebenchmark": "npm run prepare",
|
||||
"benchmark": "make -C benchmark",
|
||||
"preprofile": "npm run prepare",
|
||||
"profile": "make -C benchmark profile"
|
||||
},
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"tshy": {
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-lru-cache.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/tap": "^15.0.6",
|
||||
"benchmark": "^2.1.4",
|
||||
"esbuild": "^0.17.11",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"marked": "^4.2.12",
|
||||
"mkdirp": "^2.1.5",
|
||||
"prettier": "^2.6.2",
|
||||
"tap": "^20.0.3",
|
||||
"tshy": "^2.0.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typedoc": "^0.25.3",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"license": "ISC",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 70,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
},
|
||||
"tap": {
|
||||
"node-arg": [
|
||||
"--expose-gc"
|
||||
],
|
||||
"plugin": [
|
||||
"@tapjs/clock"
|
||||
]
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
},
|
||||
"./min": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.min.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.min.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"module": "./dist/esm/index.js"
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) npm, Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
# npm-package-arg
|
||||
|
||||
[](https://github.com/npm/npm-package-arg)
|
||||
|
||||
Parses package name and specifier passed to commands like `npm install` or
|
||||
`npm cache add`, or as found in `package.json` dependency sections.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```javascript
|
||||
const assert = require("assert")
|
||||
const npa = require("npm-package-arg")
|
||||
|
||||
// Pass in the descriptor, and it'll return an object
|
||||
try {
|
||||
const parsed = npa("@bar/foo@1.2")
|
||||
} catch (ex) {
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
## USING
|
||||
|
||||
`const npa = require('npm-package-arg')`
|
||||
|
||||
### const result = npa(*arg*[, *where*])
|
||||
|
||||
* *arg* - a string that you might pass to `npm install`, like:
|
||||
`foo@1.2`, `@bar/foo@1.2`, `foo@user/foo`, `http://x.com/foo.tgz`,
|
||||
`git+https://github.com/user/foo`, `bitbucket:user/foo`, `foo.tar.gz`,
|
||||
`../foo/bar/` or `bar`. If the *arg* you provide doesn't have a specifier
|
||||
part, eg `foo` then the specifier will default to `latest`.
|
||||
* *where* - Optionally the path to resolve file paths relative to. Defaults to `process.cwd()`
|
||||
|
||||
**Throws** if the package name is invalid, a dist-tag is invalid or a URL's protocol is not supported.
|
||||
|
||||
### const result = npa.resolve(*name*, *spec*[, *where*])
|
||||
|
||||
* *name* - The name of the module you want to install. For example: `foo` or `@bar/foo`.
|
||||
* *spec* - The specifier indicating where and how you can get this module. Something like:
|
||||
`1.2`, `^1.7.17`, `http://x.com/foo.tgz`, `git+https://github.com/user/foo`,
|
||||
`bitbucket:user/foo`, `file:foo.tar.gz` or `file:../foo/bar/`. If not
|
||||
included then the default is `latest`.
|
||||
* *where* - Optionally the path to resolve file paths relative to. Defaults to `process.cwd()`
|
||||
|
||||
**Throws** if the package name is invalid, a dist-tag is invalid or a URL's protocol is not supported.
|
||||
|
||||
### const purl = npa.toPurl(*arg*, *reg*)
|
||||
|
||||
Returns the [purl (package URL)](https://github.com/package-url/purl-spec) form of the given package name/spec.
|
||||
|
||||
* *arg* - A package/version string. For example: `foo@1.0.0` or `@bar/foo@2.0.0-alpha.1`.
|
||||
* *reg* - Optionally the URL to the package registry. If not specified, assumes the default
|
||||
`https://registry.npmjs.org`.
|
||||
|
||||
**Throws** if the package name is invalid, or the supplied arg can't be resolved to a purl.
|
||||
|
||||
## RESULT OBJECT
|
||||
|
||||
The objects that are returned by npm-package-arg contain the following
|
||||
keys:
|
||||
|
||||
* `type` - One of the following strings:
|
||||
* `git` - A git repo
|
||||
* `tag` - A tagged version, like `"foo@latest"`
|
||||
* `version` - A specific version number, like `"foo@1.2.3"`
|
||||
* `range` - A version range, like `"foo@2.x"`
|
||||
* `file` - A local `.tar.gz`, `.tar` or `.tgz` file.
|
||||
* `directory` - A local directory.
|
||||
* `remote` - An http url (presumably to a tgz)
|
||||
* `alias` - A specifier with an alias, like `myalias@npm:foo@1.2.3`
|
||||
* `registry` - If true this specifier refers to a resource hosted on a
|
||||
registry. This is true for `tag`, `version` and `range` types.
|
||||
* `name` - If known, the `name` field expected in the resulting pkg.
|
||||
* `scope` - If a name is something like `@org/module` then the `scope`
|
||||
field will be set to `@org`. If it doesn't have a scoped name, then
|
||||
scope is `null`.
|
||||
* `escapedName` - A version of `name` escaped to match the npm scoped packages
|
||||
specification. Mostly used when making requests against a registry. When
|
||||
`name` is `null`, `escapedName` will also be `null`.
|
||||
* `rawSpec` - The specifier part that was parsed out in calls to `npa(arg)`,
|
||||
or the value of `spec` in calls to `npa.resolve(name, spec)`.
|
||||
* `saveSpec` - The normalized specifier, for saving to package.json files.
|
||||
`null` for registry dependencies. See note below about how this is (not) encoded.
|
||||
* `fetchSpec` - The version of the specifier to be used to fetch this
|
||||
resource. `null` for shortcuts to hosted git dependencies as there isn't
|
||||
just one URL to try with them.
|
||||
* `gitRange` - If set, this is a semver specifier to match against git tags with
|
||||
* `gitCommittish` - If set, this is the specific committish to use with a git dependency.
|
||||
* `hosted` - If `from === 'hosted'` then this will be a `hosted-git-info`
|
||||
object. This property is not included when serializing the object as
|
||||
JSON.
|
||||
* `raw` - The original un-modified string that was provided. If called as
|
||||
`npa.resolve(name, spec)` then this will be `name + '@' + spec`.
|
||||
* `subSpec` - If `type === 'alias'`, this is a Result Object for parsing the
|
||||
target specifier for the alias.
|
||||
|
||||
## SAVE SPECS
|
||||
|
||||
TLDR: `file:` urls are NOT uri encoded.
|
||||
|
||||
Historically, npm would uri decode file package args, but did not do any uri encoding for the `saveSpec`. This meant that it generated incorrect saveSpecs for directories with characters that *looked* like encoded uri characters, and also that it could not parse directories with some unencoded uri characters (such as `%`).
|
||||
|
||||
In order to fix this, and to not break all existing versions of npm, this module now parses all file package args as not being uri encoded. And in order to not break all of the package.json files npm has made in the past, it also does not uri encode the saveSpec. This includes package args that start with `file:`. This does mean that npm `file:` package args are not RFC compliant, and making them so constitutes quite a breaking change.
|
||||
-481
@@ -1,481 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
const { URL } = require('node:url')
|
||||
// We need to use path/win32 so that we get consistent results in tests, but this also means we need to manually convert backslashes to forward slashes when generating file: urls with paths.
|
||||
const path = isWindows ? require('node:path/win32') : require('node:path')
|
||||
const { homedir } = require('node:os')
|
||||
const HostedGit = require('hosted-git-info')
|
||||
const semver = require('semver')
|
||||
const validatePackageName = require('validate-npm-package-name')
|
||||
const { log } = require('proc-log')
|
||||
|
||||
const hasSlashes = isWindows ? /\\|[/]/ : /[/]/
|
||||
const isURL = /^(?:git[+])?[a-z]+:/i
|
||||
const isGit = /^[^@]+@[^:.]+\.[^:]+:.+$/i
|
||||
const isFileType = /[.](?:tgz|tar.gz|tar)$/i
|
||||
const isPortNumber = /:[0-9]+(\/|$)/i
|
||||
const isWindowsFile = /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/
|
||||
const isPosixFile = /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
|
||||
const defaultRegistry = 'https://registry.npmjs.org'
|
||||
|
||||
function npa (arg, where) {
|
||||
let name
|
||||
let spec
|
||||
if (typeof arg === 'object') {
|
||||
if (arg instanceof Result && (!where || where === arg.where)) {
|
||||
return arg
|
||||
} else if (arg.name && arg.rawSpec) {
|
||||
return npa.resolve(arg.name, arg.rawSpec, where || arg.where)
|
||||
} else {
|
||||
return npa(arg.raw, where || arg.where)
|
||||
}
|
||||
}
|
||||
const nameEndsAt = arg.indexOf('@', 1) // Skip possible leading @
|
||||
const namePart = nameEndsAt > 0 ? arg.slice(0, nameEndsAt) : arg
|
||||
if (isURL.test(arg)) {
|
||||
spec = arg
|
||||
} else if (isGit.test(arg)) {
|
||||
spec = `git+ssh://${arg}`
|
||||
// eslint-disable-next-line max-len
|
||||
} else if (!namePart.startsWith('@') && (hasSlashes.test(namePart) || isFileType.test(namePart))) {
|
||||
spec = arg
|
||||
} else if (nameEndsAt > 0) {
|
||||
name = namePart
|
||||
spec = arg.slice(nameEndsAt + 1) || '*'
|
||||
} else {
|
||||
const valid = validatePackageName(arg)
|
||||
if (valid.validForOldPackages) {
|
||||
name = arg
|
||||
spec = '*'
|
||||
} else {
|
||||
spec = arg
|
||||
}
|
||||
}
|
||||
return resolve(name, spec, where, arg)
|
||||
}
|
||||
|
||||
function isFileSpec (spec) {
|
||||
if (!spec) {
|
||||
return false
|
||||
}
|
||||
if (spec.toLowerCase().startsWith('file:')) {
|
||||
return true
|
||||
}
|
||||
if (isWindows) {
|
||||
return isWindowsFile.test(spec)
|
||||
}
|
||||
// We never hit this in windows tests, obviously
|
||||
/* istanbul ignore next */
|
||||
return isPosixFile.test(spec)
|
||||
}
|
||||
|
||||
function isAliasSpec (spec) {
|
||||
if (!spec) {
|
||||
return false
|
||||
}
|
||||
return spec.toLowerCase().startsWith('npm:')
|
||||
}
|
||||
|
||||
function resolve (name, spec, where, arg) {
|
||||
const res = new Result({
|
||||
raw: arg,
|
||||
name: name,
|
||||
rawSpec: spec,
|
||||
fromArgument: arg != null,
|
||||
})
|
||||
|
||||
if (name) {
|
||||
res.name = name
|
||||
}
|
||||
|
||||
if (!where) {
|
||||
where = process.cwd()
|
||||
}
|
||||
|
||||
if (isFileSpec(spec)) {
|
||||
return fromFile(res, where)
|
||||
} else if (isAliasSpec(spec)) {
|
||||
return fromAlias(res, where)
|
||||
}
|
||||
|
||||
const hosted = HostedGit.fromUrl(spec, {
|
||||
noGitPlus: true,
|
||||
noCommittish: true,
|
||||
})
|
||||
if (hosted) {
|
||||
return fromHostedGit(res, hosted)
|
||||
} else if (spec && isURL.test(spec)) {
|
||||
return fromURL(res)
|
||||
} else if (spec && (hasSlashes.test(spec) || isFileType.test(spec))) {
|
||||
return fromFile(res, where)
|
||||
} else {
|
||||
return fromRegistry(res)
|
||||
}
|
||||
}
|
||||
|
||||
function toPurl (arg, reg = defaultRegistry) {
|
||||
const res = npa(arg)
|
||||
|
||||
if (res.type !== 'version') {
|
||||
throw invalidPurlType(res.type, res.raw)
|
||||
}
|
||||
|
||||
// URI-encode leading @ of scoped packages
|
||||
let purl = 'pkg:npm/' + res.name.replace(/^@/, '%40') + '@' + res.rawSpec
|
||||
if (reg !== defaultRegistry) {
|
||||
purl += '?repository_url=' + reg
|
||||
}
|
||||
|
||||
return purl
|
||||
}
|
||||
|
||||
function invalidPackageName (name, valid, raw) {
|
||||
// eslint-disable-next-line max-len
|
||||
const err = new Error(`Invalid package name "${name}" of package "${raw}": ${valid.errors.join('; ')}.`)
|
||||
err.code = 'EINVALIDPACKAGENAME'
|
||||
return err
|
||||
}
|
||||
|
||||
function invalidTagName (name, raw) {
|
||||
// eslint-disable-next-line max-len
|
||||
const err = new Error(`Invalid tag name "${name}" of package "${raw}": Tags may not have any characters that encodeURIComponent encodes.`)
|
||||
err.code = 'EINVALIDTAGNAME'
|
||||
return err
|
||||
}
|
||||
|
||||
function invalidPurlType (type, raw) {
|
||||
// eslint-disable-next-line max-len
|
||||
const err = new Error(`Invalid type "${type}" of package "${raw}": Purl can only be generated for "version" types.`)
|
||||
err.code = 'EINVALIDPURLTYPE'
|
||||
return err
|
||||
}
|
||||
|
||||
class Result {
|
||||
constructor (opts) {
|
||||
this.type = opts.type
|
||||
this.registry = opts.registry
|
||||
this.where = opts.where
|
||||
if (opts.raw == null) {
|
||||
this.raw = opts.name ? `${opts.name}@${opts.rawSpec}` : opts.rawSpec
|
||||
} else {
|
||||
this.raw = opts.raw
|
||||
}
|
||||
this.name = undefined
|
||||
this.escapedName = undefined
|
||||
this.scope = undefined
|
||||
this.rawSpec = opts.rawSpec || ''
|
||||
this.saveSpec = opts.saveSpec
|
||||
this.fetchSpec = opts.fetchSpec
|
||||
if (opts.name) {
|
||||
this.setName(opts.name)
|
||||
}
|
||||
this.gitRange = opts.gitRange
|
||||
this.gitCommittish = opts.gitCommittish
|
||||
this.gitSubdir = opts.gitSubdir
|
||||
this.hosted = opts.hosted
|
||||
}
|
||||
|
||||
// TODO move this to a getter/setter in a semver major
|
||||
setName (name) {
|
||||
const valid = validatePackageName(name)
|
||||
if (!valid.validForOldPackages) {
|
||||
throw invalidPackageName(name, valid, this.raw)
|
||||
}
|
||||
|
||||
this.name = name
|
||||
this.scope = name[0] === '@' ? name.slice(0, name.indexOf('/')) : undefined
|
||||
// scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar
|
||||
this.escapedName = name.replace('/', '%2f')
|
||||
return this
|
||||
}
|
||||
|
||||
toString () {
|
||||
const full = []
|
||||
if (this.name != null && this.name !== '') {
|
||||
full.push(this.name)
|
||||
}
|
||||
const spec = this.saveSpec || this.fetchSpec || this.rawSpec
|
||||
if (spec != null && spec !== '') {
|
||||
full.push(spec)
|
||||
}
|
||||
return full.length ? full.join('@') : this.raw
|
||||
}
|
||||
|
||||
toJSON () {
|
||||
const result = Object.assign({}, this)
|
||||
delete result.hosted
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// sets res.gitCommittish, res.gitRange, and res.gitSubdir
|
||||
function setGitAttrs (res, committish) {
|
||||
if (!committish) {
|
||||
res.gitCommittish = null
|
||||
return
|
||||
}
|
||||
|
||||
// for each :: separated item:
|
||||
for (const part of committish.split('::')) {
|
||||
// if the item has no : the n it is a commit-ish
|
||||
if (!part.includes(':')) {
|
||||
if (res.gitRange) {
|
||||
throw new Error('cannot override existing semver range with a committish')
|
||||
}
|
||||
if (res.gitCommittish) {
|
||||
throw new Error('cannot override existing committish with a second committish')
|
||||
}
|
||||
res.gitCommittish = part
|
||||
continue
|
||||
}
|
||||
// split on name:value
|
||||
const [name, value] = part.split(':')
|
||||
// if name is semver do semver lookup of ref or tag
|
||||
if (name === 'semver') {
|
||||
if (res.gitCommittish) {
|
||||
throw new Error('cannot override existing committish with a semver range')
|
||||
}
|
||||
if (res.gitRange) {
|
||||
throw new Error('cannot override existing semver range with a second semver range')
|
||||
}
|
||||
res.gitRange = decodeURIComponent(value)
|
||||
continue
|
||||
}
|
||||
if (name === 'path') {
|
||||
if (res.gitSubdir) {
|
||||
throw new Error('cannot override existing path with a second path')
|
||||
}
|
||||
res.gitSubdir = `/${value}`
|
||||
continue
|
||||
}
|
||||
log.warn('npm-package-arg', `ignoring unknown key "${name}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from: EncodePathChars and lookup_table in src/node_url.cc
|
||||
// url.pathToFileURL only returns absolute references. We can't use it to encode paths.
|
||||
// encodeURI mangles windows paths. We can't use it to encode paths.
|
||||
// Under the hood, url.pathToFileURL does a limited set of encoding, with an extra windows step, and then calls path.resolve.
|
||||
// The encoding node does without path.resolve is not available outside of the source, so we are recreating it here.
|
||||
const encodedPathChars = new Map([
|
||||
['\0', '%00'],
|
||||
['\t', '%09'],
|
||||
['\n', '%0A'],
|
||||
['\r', '%0D'],
|
||||
[' ', '%20'],
|
||||
['"', '%22'],
|
||||
['#', '%23'],
|
||||
['%', '%25'],
|
||||
['?', '%3F'],
|
||||
['[', '%5B'],
|
||||
['\\', isWindows ? '/' : '%5C'],
|
||||
[']', '%5D'],
|
||||
['^', '%5E'],
|
||||
['|', '%7C'],
|
||||
['~', '%7E'],
|
||||
])
|
||||
|
||||
function pathToFileURL (str) {
|
||||
let result = ''
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
result = `${result}${encodedPathChars.get(str[i]) ?? str[i]}`
|
||||
}
|
||||
if (result.startsWith('file:')) {
|
||||
return result
|
||||
}
|
||||
return `file:${result}`
|
||||
}
|
||||
|
||||
function fromFile (res, where) {
|
||||
res.type = isFileType.test(res.rawSpec) ? 'file' : 'directory'
|
||||
res.where = where
|
||||
|
||||
let rawSpec = pathToFileURL(res.rawSpec)
|
||||
|
||||
if (rawSpec.startsWith('file:/')) {
|
||||
// XXX backwards compatibility lack of compliance with RFC 8089
|
||||
|
||||
// turn file://path into file:/path
|
||||
if (/^file:\/\/[^/]/.test(rawSpec)) {
|
||||
rawSpec = `file:/${rawSpec.slice(5)}`
|
||||
}
|
||||
|
||||
// turn file:/../path into file:../path
|
||||
// for 1 or 3 leading slashes (2 is already ruled out from handling file:// explicitly above)
|
||||
if (/^\/{1,3}\.\.?(\/|$)/.test(rawSpec.slice(5))) {
|
||||
rawSpec = rawSpec.replace(/^file:\/{1,3}/, 'file:')
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedUrl
|
||||
let specUrl
|
||||
try {
|
||||
// always put the '/' on "where", or else file:foo from /path/to/bar goes to /path/to/foo, when we want it to be /path/to/bar/foo
|
||||
resolvedUrl = new URL(rawSpec, `${pathToFileURL(path.resolve(where))}/`)
|
||||
specUrl = new URL(rawSpec)
|
||||
} catch (originalError) {
|
||||
const er = new Error('Invalid file: URL, must comply with RFC 8089')
|
||||
throw Object.assign(er, {
|
||||
raw: res.rawSpec,
|
||||
spec: res,
|
||||
where,
|
||||
originalError,
|
||||
})
|
||||
}
|
||||
|
||||
// turn /C:/blah into just C:/blah on windows
|
||||
let specPath = decodeURIComponent(specUrl.pathname)
|
||||
let resolvedPath = decodeURIComponent(resolvedUrl.pathname)
|
||||
if (isWindows) {
|
||||
specPath = specPath.replace(/^\/+([a-z]:\/)/i, '$1')
|
||||
resolvedPath = resolvedPath.replace(/^\/+([a-z]:\/)/i, '$1')
|
||||
}
|
||||
|
||||
// replace ~ with homedir, but keep the ~ in the saveSpec
|
||||
// otherwise, make it relative to where param
|
||||
if (/^\/~(\/|$)/.test(specPath)) {
|
||||
res.saveSpec = `file:${specPath.substr(1)}`
|
||||
resolvedPath = path.resolve(homedir(), specPath.substr(3))
|
||||
} else if (!path.isAbsolute(rawSpec.slice(5))) {
|
||||
res.saveSpec = `file:${path.relative(where, resolvedPath)}`
|
||||
} else {
|
||||
res.saveSpec = `file:${path.resolve(resolvedPath)}`
|
||||
}
|
||||
|
||||
res.fetchSpec = path.resolve(where, resolvedPath)
|
||||
// re-normalize the slashes in saveSpec due to node:path/win32 behavior in windows
|
||||
res.saveSpec = res.saveSpec.split('\\').join('/')
|
||||
// Ignoring because this only happens in windows
|
||||
/* istanbul ignore next */
|
||||
if (res.saveSpec.startsWith('file://')) {
|
||||
// normalization of \\win32\root paths can cause a double / which we don't want
|
||||
res.saveSpec = `file:/${res.saveSpec.slice(7)}`
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function fromHostedGit (res, hosted) {
|
||||
res.type = 'git'
|
||||
res.hosted = hosted
|
||||
res.saveSpec = hosted.toString({ noGitPlus: false, noCommittish: false })
|
||||
res.fetchSpec = hosted.getDefaultRepresentation() === 'shortcut' ? null : hosted.toString()
|
||||
setGitAttrs(res, hosted.committish)
|
||||
return res
|
||||
}
|
||||
|
||||
function unsupportedURLType (protocol, spec) {
|
||||
const err = new Error(`Unsupported URL Type "${protocol}": ${spec}`)
|
||||
err.code = 'EUNSUPPORTEDPROTOCOL'
|
||||
return err
|
||||
}
|
||||
|
||||
function fromURL (res) {
|
||||
let rawSpec = res.rawSpec
|
||||
res.saveSpec = rawSpec
|
||||
if (rawSpec.startsWith('git+ssh:')) {
|
||||
// git ssh specifiers are overloaded to also use scp-style git
|
||||
// specifiers, so we have to parse those out and treat them special.
|
||||
// They are NOT true URIs, so we can't hand them to URL.
|
||||
|
||||
// This regex looks for things that look like:
|
||||
// git+ssh://git@my.custom.git.com:username/project.git#deadbeef
|
||||
// ...and various combinations. The username in the beginning is *required*.
|
||||
const matched = rawSpec.match(/^git\+ssh:\/\/([^:#]+:[^#]+(?:\.git)?)(?:#(.*))?$/i)
|
||||
// Filter out all-number "usernames" which are really port numbers
|
||||
// They can either be :1234 :1234/ or :1234/path but not :12abc
|
||||
if (matched && !matched[1].match(isPortNumber)) {
|
||||
res.type = 'git'
|
||||
setGitAttrs(res, matched[2])
|
||||
res.fetchSpec = matched[1]
|
||||
return res
|
||||
}
|
||||
} else if (rawSpec.startsWith('git+file://')) {
|
||||
// URL can't handle windows paths
|
||||
rawSpec = rawSpec.replace(/\\/g, '/')
|
||||
}
|
||||
const parsedUrl = new URL(rawSpec)
|
||||
// check the protocol, and then see if it's git or not
|
||||
switch (parsedUrl.protocol) {
|
||||
case 'git:':
|
||||
case 'git+http:':
|
||||
case 'git+https:':
|
||||
case 'git+rsync:':
|
||||
case 'git+ftp:':
|
||||
case 'git+file:':
|
||||
case 'git+ssh:':
|
||||
res.type = 'git'
|
||||
setGitAttrs(res, parsedUrl.hash.slice(1))
|
||||
if (parsedUrl.protocol === 'git+file:' && /^git\+file:\/\/[a-z]:/i.test(rawSpec)) {
|
||||
// URL can't handle drive letters on windows file paths, the host can't contain a :
|
||||
res.fetchSpec = `git+file://${parsedUrl.host.toLowerCase()}:${parsedUrl.pathname}`
|
||||
} else {
|
||||
parsedUrl.hash = ''
|
||||
res.fetchSpec = parsedUrl.toString()
|
||||
}
|
||||
if (res.fetchSpec.startsWith('git+')) {
|
||||
res.fetchSpec = res.fetchSpec.slice(4)
|
||||
}
|
||||
break
|
||||
case 'http:':
|
||||
case 'https:':
|
||||
res.type = 'remote'
|
||||
res.fetchSpec = res.saveSpec
|
||||
break
|
||||
|
||||
default:
|
||||
throw unsupportedURLType(parsedUrl.protocol, rawSpec)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function fromAlias (res, where) {
|
||||
const subSpec = npa(res.rawSpec.substr(4), where)
|
||||
if (subSpec.type === 'alias') {
|
||||
throw new Error('nested aliases not supported')
|
||||
}
|
||||
|
||||
if (!subSpec.registry) {
|
||||
throw new Error('aliases only work for registry deps')
|
||||
}
|
||||
|
||||
if (!subSpec.name) {
|
||||
throw new Error('aliases must have a name')
|
||||
}
|
||||
|
||||
res.subSpec = subSpec
|
||||
res.registry = true
|
||||
res.type = 'alias'
|
||||
res.saveSpec = null
|
||||
res.fetchSpec = null
|
||||
return res
|
||||
}
|
||||
|
||||
function fromRegistry (res) {
|
||||
res.registry = true
|
||||
const spec = res.rawSpec.trim()
|
||||
// no save spec for registry components as we save based on the fetched
|
||||
// version, not on the argument so this can't compute that.
|
||||
res.saveSpec = null
|
||||
res.fetchSpec = spec
|
||||
const version = semver.valid(spec, true)
|
||||
const range = semver.validRange(spec, true)
|
||||
if (version) {
|
||||
res.type = 'version'
|
||||
} else if (range) {
|
||||
res.type = 'range'
|
||||
} else {
|
||||
if (encodeURIComponent(spec) !== spec) {
|
||||
throw invalidTagName(spec, res.raw)
|
||||
}
|
||||
res.type = 'tag'
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
module.exports = npa
|
||||
module.exports.resolve = resolve
|
||||
module.exports.toPurl = toPurl
|
||||
module.exports.Result = Result
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"name": "npm-package-arg",
|
||||
"version": "12.0.2",
|
||||
"description": "Parse the things that can be arguments to `npm install`",
|
||||
"main": "./lib/npa.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"lib/"
|
||||
],
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^8.0.0",
|
||||
"proc-log": "^5.0.0",
|
||||
"semver": "^7.3.5",
|
||||
"validate-npm-package-name": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@npmcli/eslint-config": "^5.0.0",
|
||||
"@npmcli/template-oss": "4.23.5",
|
||||
"tap": "^16.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap",
|
||||
"snap": "tap",
|
||||
"npmclilint": "npmcli-lint",
|
||||
"lint": "npm run eslint",
|
||||
"lintfix": "npm run eslint -- --fix",
|
||||
"posttest": "npm run lint",
|
||||
"postsnap": "npm run lintfix --",
|
||||
"postlint": "template-oss-check",
|
||||
"template-oss-apply": "template-oss-apply --force",
|
||||
"eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/npm/npm-package-arg.git"
|
||||
},
|
||||
"author": "GitHub Inc.",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/npm/npm-package-arg/issues"
|
||||
},
|
||||
"homepage": "https://github.com/npm/npm-package-arg",
|
||||
"engines": {
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
},
|
||||
"tap": {
|
||||
"branches": 97,
|
||||
"nyc-arg": [
|
||||
"--exclude",
|
||||
"tap-snapshots/**"
|
||||
]
|
||||
},
|
||||
"templateOSS": {
|
||||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
||||
"version": "4.23.5",
|
||||
"publish": true
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pacote",
|
||||
"version": "21.0.0",
|
||||
"version": "21.0.4",
|
||||
"description": "JavaScript package downloader",
|
||||
"author": "GitHub Inc.",
|
||||
"bin": {
|
||||
@@ -26,10 +26,10 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@npmcli/arborist": "^8.0.0",
|
||||
"@npmcli/eslint-config": "^5.0.0",
|
||||
"@npmcli/template-oss": "4.23.4",
|
||||
"hosted-git-info": "^8.0.0",
|
||||
"@npmcli/arborist": "^9.0.2",
|
||||
"@npmcli/eslint-config": "^6.0.0",
|
||||
"@npmcli/template-oss": "4.28.0",
|
||||
"hosted-git-info": "^9.0.0",
|
||||
"mutate-fs": "^2.1.1",
|
||||
"nock": "^13.2.4",
|
||||
"npm-registry-mock": "^1.3.2",
|
||||
@@ -46,23 +46,23 @@
|
||||
"git"
|
||||
],
|
||||
"dependencies": {
|
||||
"@npmcli/git": "^6.0.0",
|
||||
"@npmcli/installed-package-contents": "^3.0.0",
|
||||
"@npmcli/package-json": "^6.0.0",
|
||||
"@npmcli/promise-spawn": "^8.0.0",
|
||||
"@npmcli/run-script": "^9.0.0",
|
||||
"cacache": "^19.0.0",
|
||||
"@npmcli/git": "^7.0.0",
|
||||
"@npmcli/installed-package-contents": "^4.0.0",
|
||||
"@npmcli/package-json": "^7.0.0",
|
||||
"@npmcli/promise-spawn": "^9.0.0",
|
||||
"@npmcli/run-script": "^10.0.0",
|
||||
"cacache": "^20.0.0",
|
||||
"fs-minipass": "^3.0.0",
|
||||
"minipass": "^7.0.2",
|
||||
"npm-package-arg": "^12.0.0",
|
||||
"npm-packlist": "^10.0.0",
|
||||
"npm-pick-manifest": "^10.0.0",
|
||||
"npm-registry-fetch": "^18.0.0",
|
||||
"proc-log": "^5.0.0",
|
||||
"npm-package-arg": "^13.0.0",
|
||||
"npm-packlist": "^10.0.1",
|
||||
"npm-pick-manifest": "^11.0.1",
|
||||
"npm-registry-fetch": "^19.0.0",
|
||||
"proc-log": "^6.0.0",
|
||||
"promise-retry": "^2.0.1",
|
||||
"sigstore": "^3.0.0",
|
||||
"ssri": "^12.0.0",
|
||||
"tar": "^6.1.11"
|
||||
"sigstore": "^4.0.0",
|
||||
"ssri": "^13.0.0",
|
||||
"tar": "^7.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
"templateOSS": {
|
||||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
||||
"version": "4.23.4",
|
||||
"version": "4.28.0",
|
||||
"windowsCI": false,
|
||||
"publish": "true"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user