Changing front

This commit is contained in:
2023-01-16 17:44:37 +01:00
parent 0b8a93b256
commit 4fe4be7730
48586 changed files with 4725790 additions and 17464 deletions

15
front/app/node_modules/normalize-package-data/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
This package contains code originally written by Isaac Z. Schlueter.
Used with permission.
Copyright (c) Meryn Stol ("Author")
All rights reserved.
The BSD License
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

108
front/app/node_modules/normalize-package-data/README.md generated vendored Normal file
View File

@@ -0,0 +1,108 @@
# normalize-package-data
[![Build Status](https://travis-ci.org/npm/normalize-package-data.svg?branch=master)](https://travis-ci.org/npm/normalize-package-data)
normalize-package-data exports a function that normalizes package metadata. This data is typically found in a package.json file, but in principle could come from any source - for example the npm registry.
normalize-package-data is used by [read-package-json](https://npmjs.org/package/read-package-json) to normalize the data it reads from a package.json file. In turn, read-package-json is used by [npm](https://npmjs.org/package/npm) and various npm-related tools.
## Installation
```
npm install normalize-package-data
```
## Usage
Basic usage is really simple. You call the function that normalize-package-data exports. Let's call it `normalizeData`.
```javascript
normalizeData = require('normalize-package-data')
packageData = require("./package.json")
normalizeData(packageData)
// packageData is now normalized
```
#### Strict mode
You may activate strict validation by passing true as the second argument.
```javascript
normalizeData = require('normalize-package-data')
packageData = require("./package.json")
normalizeData(packageData, true)
// packageData is now normalized
```
If strict mode is activated, only Semver 2.0 version strings are accepted. Otherwise, Semver 1.0 strings are accepted as well. Packages must have a name, and the name field must not have contain leading or trailing whitespace.
#### Warnings
Optionally, you may pass a "warning" function. It gets called whenever the `normalizeData` function encounters something that doesn't look right. It indicates less than perfect input data.
```javascript
normalizeData = require('normalize-package-data')
packageData = require("./package.json")
warnFn = function(msg) { console.error(msg) }
normalizeData(packageData, warnFn)
// packageData is now normalized. Any number of warnings may have been logged.
```
You may combine strict validation with warnings by passing `true` as the second argument, and `warnFn` as third.
When `private` field is set to `true`, warnings will be suppressed.
### Potential exceptions
If the supplied data has an invalid name or version field, `normalizeData` will throw an error. Depending on where you call `normalizeData`, you may want to catch these errors so can pass them to a callback.
## What normalization (currently) entails
* The value of `name` field gets trimmed (unless in strict mode).
* The value of the `version` field gets cleaned by `semver.clean`. See [documentation for the semver module](https://github.com/isaacs/node-semver).
* If `name` and/or `version` fields are missing, they are set to empty strings.
* If `files` field is not an array, it will be removed.
* If `bin` field is a string, then `bin` field will become an object with `name` set to the value of the `name` field, and `bin` set to the original string value.
* If `man` field is a string, it will become an array with the original string as its sole member.
* If `keywords` field is string, it is considered to be a list of keywords separated by one or more white-space characters. It gets converted to an array by splitting on `\s+`.
* All people fields (`author`, `maintainers`, `contributors`) get converted into objects with name, email and url properties.
* If `bundledDependencies` field (a typo) exists and `bundleDependencies` field does not, `bundledDependencies` will get renamed to `bundleDependencies`.
* If the value of any of the dependencies fields (`dependencies`, `devDependencies`, `optionalDependencies`) is a string, it gets converted into an object with familiar `name=>value` pairs.
* The values in `optionalDependencies` get added to `dependencies`. The `optionalDependencies` array is left untouched.
* As of v2: Dependencies that point at known hosted git providers (currently: github, bitbucket, gitlab) will have their URLs canonicalized, but protocols will be preserved.
* As of v2: Dependencies that use shortcuts for hosted git providers (`org/proj`, `github:org/proj`, `bitbucket:org/proj`, `gitlab:org/proj`, `gist:docid`) will have the shortcut left in place. (In the case of github, the `org/proj` form will be expanded to `github:org/proj`.) THIS MARKS A BREAKING CHANGE FROM V1, where the shortcut was previously expanded to a URL.
* If `description` field does not exist, but `readme` field does, then (more or less) the first paragraph of text that's found in the readme is taken as value for `description`.
* If `repository` field is a string, it will become an object with `url` set to the original string value, and `type` set to `"git"`.
* If `repository.url` is not a valid url, but in the style of "[owner-name]/[repo-name]", `repository.url` will be set to git+https://github.com/[owner-name]/[repo-name].git
* If `bugs` field is a string, the value of `bugs` field is changed into an object with `url` set to the original string value.
* If `bugs` field does not exist, but `repository` field points to a repository hosted on GitHub, the value of the `bugs` field gets set to an url in the form of https://github.com/[owner-name]/[repo-name]/issues . If the repository field points to a GitHub Gist repo url, the associated http url is chosen.
* If `bugs` field is an object, the resulting value only has email and url properties. If email and url properties are not strings, they are ignored. If no valid values for either email or url is found, bugs field will be removed.
* If `homepage` field is not a string, it will be removed.
* If the url in the `homepage` field does not specify a protocol, then http is assumed. For example, `myproject.org` will be changed to `http://myproject.org`.
* If `homepage` field does not exist, but `repository` field points to a repository hosted on GitHub, the value of the `homepage` field gets set to an url in the form of https://github.com/[owner-name]/[repo-name]#readme . If the repository field points to a GitHub Gist repo url, the associated http url is chosen.
### Rules for name field
If `name` field is given, the value of the name field must be a string. The string may not:
* start with a period.
* contain the following characters: `/@\s+%`
* contain any characters that would need to be encoded for use in urls.
* resemble the word `node_modules` or `favicon.ico` (case doesn't matter).
### Rules for version field
If `version` field is given, the value of the version field must be a valid *semver* string, as determined by the `semver.valid` method. See [documentation for the semver module](https://github.com/isaacs/node-semver).
### Rules for license field
The `license`/`licence` field should be a valid *SPDX license expression* or one of the special values allowed by [validate-npm-package-license](https://npmjs.com/package/validate-npm-package-license). See [documentation for the license field in package.json](https://docs.npmjs.com/files/package.json#license).
## Credits
This package contains code based on read-package-json written by Isaac Z. Schlueter. Used with permission.
## License
normalize-package-data is released under the [BSD 2-Clause License](https://opensource.org/licenses/BSD-2-Clause).
Copyright (c) 2013 Meryn Stol

View File

@@ -0,0 +1,24 @@
module.exports = extractDescription
// Extracts description from contents of a readme file in markdown format
function extractDescription (d) {
if (!d) {
return
}
if (d === 'ERROR: No README data found!') {
return
}
// the first block of text before the first heading
// that isn't the first line heading
d = d.trim().split('\n')
let s = 0
while (d[s] && d[s].trim().match(/^(#|$)/)) {
s++
}
const l = d.length
let e = s + 1
while (e < l && d[e].trim()) {
e++
}
return d.slice(s, e).join(' ').trim()
}

View File

@@ -0,0 +1,475 @@
var isValidSemver = require('semver/functions/valid')
var cleanSemver = require('semver/functions/clean')
var validateLicense = require('validate-npm-package-license')
var hostedGitInfo = require('hosted-git-info')
var isBuiltinModule = require('is-core-module')
var depTypes = ['dependencies', 'devDependencies', 'optionalDependencies']
var extractDescription = require('./extract_description')
var url = require('url')
var typos = require('./typos.json')
var isEmail = str => str.includes('@') && (str.indexOf('@') < str.lastIndexOf('.'))
module.exports = {
// default warning function
warn: function () {},
fixRepositoryField: function (data) {
if (data.repositories) {
this.warn('repositories')
data.repository = data.repositories[0]
}
if (!data.repository) {
return this.warn('missingRepository')
}
if (typeof data.repository === 'string') {
data.repository = {
type: 'git',
url: data.repository,
}
}
var r = data.repository.url || ''
if (r) {
var hosted = hostedGitInfo.fromUrl(r)
if (hosted) {
r = data.repository.url
= hosted.getDefaultRepresentation() === 'shortcut' ? hosted.https() : hosted.toString()
}
}
if (r.match(/github.com\/[^/]+\/[^/]+\.git\.git$/)) {
this.warn('brokenGitUrl', r)
}
},
fixTypos: function (data) {
Object.keys(typos.topLevel).forEach(function (d) {
if (Object.prototype.hasOwnProperty.call(data, d)) {
this.warn('typo', d, typos.topLevel[d])
}
}, this)
},
fixScriptsField: function (data) {
if (!data.scripts) {
return
}
if (typeof data.scripts !== 'object') {
this.warn('nonObjectScripts')
delete data.scripts
return
}
Object.keys(data.scripts).forEach(function (k) {
if (typeof data.scripts[k] !== 'string') {
this.warn('nonStringScript')
delete data.scripts[k]
} else if (typos.script[k] && !data.scripts[typos.script[k]]) {
this.warn('typo', k, typos.script[k], 'scripts')
}
}, this)
},
fixFilesField: function (data) {
var files = data.files
if (files && !Array.isArray(files)) {
this.warn('nonArrayFiles')
delete data.files
} else if (data.files) {
data.files = data.files.filter(function (file) {
if (!file || typeof file !== 'string') {
this.warn('invalidFilename', file)
return false
} else {
return true
}
}, this)
}
},
fixBinField: function (data) {
if (!data.bin) {
return
}
if (typeof data.bin === 'string') {
var b = {}
var match
if (match = data.name.match(/^@[^/]+[/](.*)$/)) {
b[match[1]] = data.bin
} else {
b[data.name] = data.bin
}
data.bin = b
}
},
fixManField: function (data) {
if (!data.man) {
return
}
if (typeof data.man === 'string') {
data.man = [data.man]
}
},
fixBundleDependenciesField: function (data) {
var bdd = 'bundledDependencies'
var bd = 'bundleDependencies'
if (data[bdd] && !data[bd]) {
data[bd] = data[bdd]
delete data[bdd]
}
if (data[bd] && !Array.isArray(data[bd])) {
this.warn('nonArrayBundleDependencies')
delete data[bd]
} else if (data[bd]) {
data[bd] = data[bd].filter(function (filtered) {
if (!filtered || typeof filtered !== 'string') {
this.warn('nonStringBundleDependency', filtered)
return false
} else {
if (!data.dependencies) {
data.dependencies = {}
}
if (!Object.prototype.hasOwnProperty.call(data.dependencies, filtered)) {
this.warn('nonDependencyBundleDependency', filtered)
data.dependencies[filtered] = '*'
}
return true
}
}, this)
}
},
fixDependencies: function (data, strict) {
objectifyDeps(data, this.warn)
addOptionalDepsToDeps(data, this.warn)
this.fixBundleDependenciesField(data)
;['dependencies', 'devDependencies'].forEach(function (deps) {
if (!(deps in data)) {
return
}
if (!data[deps] || typeof data[deps] !== 'object') {
this.warn('nonObjectDependencies', deps)
delete data[deps]
return
}
Object.keys(data[deps]).forEach(function (d) {
var r = data[deps][d]
if (typeof r !== 'string') {
this.warn('nonStringDependency', d, JSON.stringify(r))
delete data[deps][d]
}
var hosted = hostedGitInfo.fromUrl(data[deps][d])
if (hosted) {
data[deps][d] = hosted.toString()
}
}, this)
}, this)
},
fixModulesField: function (data) {
if (data.modules) {
this.warn('deprecatedModules')
delete data.modules
}
},
fixKeywordsField: function (data) {
if (typeof data.keywords === 'string') {
data.keywords = data.keywords.split(/,\s+/)
}
if (data.keywords && !Array.isArray(data.keywords)) {
delete data.keywords
this.warn('nonArrayKeywords')
} else if (data.keywords) {
data.keywords = data.keywords.filter(function (kw) {
if (typeof kw !== 'string' || !kw) {
this.warn('nonStringKeyword')
return false
} else {
return true
}
}, this)
}
},
fixVersionField: function (data, strict) {
// allow "loose" semver 1.0 versions in non-strict mode
// enforce strict semver 2.0 compliance in strict mode
var loose = !strict
if (!data.version) {
data.version = ''
return true
}
if (!isValidSemver(data.version, loose)) {
throw new Error('Invalid version: "' + data.version + '"')
}
data.version = cleanSemver(data.version, loose)
return true
},
fixPeople: function (data) {
modifyPeople(data, unParsePerson)
modifyPeople(data, parsePerson)
},
fixNameField: function (data, options) {
if (typeof options === 'boolean') {
options = { strict: options }
} else if (typeof options === 'undefined') {
options = {}
}
var strict = options.strict
if (!data.name && !strict) {
data.name = ''
return
}
if (typeof data.name !== 'string') {
throw new Error('name field must be a string.')
}
if (!strict) {
data.name = data.name.trim()
}
ensureValidName(data.name, strict, options.allowLegacyCase)
if (isBuiltinModule(data.name)) {
this.warn('conflictingName', data.name)
}
},
fixDescriptionField: function (data) {
if (data.description && typeof data.description !== 'string') {
this.warn('nonStringDescription')
delete data.description
}
if (data.readme && !data.description) {
data.description = extractDescription(data.readme)
}
if (data.description === undefined) {
delete data.description
}
if (!data.description) {
this.warn('missingDescription')
}
},
fixReadmeField: function (data) {
if (!data.readme) {
this.warn('missingReadme')
data.readme = 'ERROR: No README data found!'
}
},
fixBugsField: function (data) {
if (!data.bugs && data.repository && data.repository.url) {
var hosted = hostedGitInfo.fromUrl(data.repository.url)
if (hosted && hosted.bugs()) {
data.bugs = { url: hosted.bugs() }
}
} else if (data.bugs) {
if (typeof data.bugs === 'string') {
if (isEmail(data.bugs)) {
data.bugs = { email: data.bugs }
/* eslint-disable-next-line node/no-deprecated-api */
} else if (url.parse(data.bugs).protocol) {
data.bugs = { url: data.bugs }
} else {
this.warn('nonEmailUrlBugsString')
}
} else {
bugsTypos(data.bugs, this.warn)
var oldBugs = data.bugs
data.bugs = {}
if (oldBugs.url) {
/* eslint-disable-next-line node/no-deprecated-api */
if (typeof (oldBugs.url) === 'string' && url.parse(oldBugs.url).protocol) {
data.bugs.url = oldBugs.url
} else {
this.warn('nonUrlBugsUrlField')
}
}
if (oldBugs.email) {
if (typeof (oldBugs.email) === 'string' && isEmail(oldBugs.email)) {
data.bugs.email = oldBugs.email
} else {
this.warn('nonEmailBugsEmailField')
}
}
}
if (!data.bugs.email && !data.bugs.url) {
delete data.bugs
this.warn('emptyNormalizedBugs')
}
}
},
fixHomepageField: function (data) {
if (!data.homepage && data.repository && data.repository.url) {
var hosted = hostedGitInfo.fromUrl(data.repository.url)
if (hosted && hosted.docs()) {
data.homepage = hosted.docs()
}
}
if (!data.homepage) {
return
}
if (typeof data.homepage !== 'string') {
this.warn('nonUrlHomepage')
return delete data.homepage
}
/* eslint-disable-next-line node/no-deprecated-api */
if (!url.parse(data.homepage).protocol) {
data.homepage = 'http://' + data.homepage
}
},
fixLicenseField: function (data) {
const license = data.license || data.licence
if (!license) {
return this.warn('missingLicense')
}
if (
typeof (license) !== 'string' ||
license.length < 1 ||
license.trim() === ''
) {
return this.warn('invalidLicense')
}
if (!validateLicense(license).validForNewPackages) {
return this.warn('invalidLicense')
}
},
}
function isValidScopedPackageName (spec) {
if (spec.charAt(0) !== '@') {
return false
}
var rest = spec.slice(1).split('/')
if (rest.length !== 2) {
return false
}
return rest[0] && rest[1] &&
rest[0] === encodeURIComponent(rest[0]) &&
rest[1] === encodeURIComponent(rest[1])
}
function isCorrectlyEncodedName (spec) {
return !spec.match(/[/@\s+%:]/) &&
spec === encodeURIComponent(spec)
}
function ensureValidName (name, strict, allowLegacyCase) {
if (name.charAt(0) === '.' ||
!(isValidScopedPackageName(name) || isCorrectlyEncodedName(name)) ||
(strict && (!allowLegacyCase) && name !== name.toLowerCase()) ||
name.toLowerCase() === 'node_modules' ||
name.toLowerCase() === 'favicon.ico') {
throw new Error('Invalid name: ' + JSON.stringify(name))
}
}
function modifyPeople (data, fn) {
if (data.author) {
data.author = fn(data.author)
}['maintainers', 'contributors'].forEach(function (set) {
if (!Array.isArray(data[set])) {
return
}
data[set] = data[set].map(fn)
})
return data
}
function unParsePerson (person) {
if (typeof person === 'string') {
return person
}
var name = person.name || ''
var u = person.url || person.web
var wrappedUrl = u ? (' (' + u + ')') : ''
var e = person.email || person.mail
var wrappedEmail = e ? (' <' + e + '>') : ''
return name + wrappedEmail + wrappedUrl
}
function parsePerson (person) {
if (typeof person !== 'string') {
return person
}
var matchedName = person.match(/^([^(<]+)/)
var matchedUrl = person.match(/\(([^()]+)\)/)
var matchedEmail = person.match(/<([^<>]+)>/)
var obj = {}
if (matchedName && matchedName[0].trim()) {
obj.name = matchedName[0].trim()
}
if (matchedEmail) {
obj.email = matchedEmail[1]
}
if (matchedUrl) {
obj.url = matchedUrl[1]
}
return obj
}
function addOptionalDepsToDeps (data, warn) {
var o = data.optionalDependencies
if (!o) {
return
}
var d = data.dependencies || {}
Object.keys(o).forEach(function (k) {
d[k] = o[k]
})
data.dependencies = d
}
function depObjectify (deps, type, warn) {
if (!deps) {
return {}
}
if (typeof deps === 'string') {
deps = deps.trim().split(/[\n\r\s\t ,]+/)
}
if (!Array.isArray(deps)) {
return deps
}
warn('deprecatedArrayDependencies', type)
var o = {}
deps.filter(function (d) {
return typeof d === 'string'
}).forEach(function (d) {
d = d.trim().split(/(:?[@\s><=])/)
var dn = d.shift()
var dv = d.join('')
dv = dv.trim()
dv = dv.replace(/^@/, '')
o[dn] = dv
})
return o
}
function objectifyDeps (data, warn) {
depTypes.forEach(function (type) {
if (!data[type]) {
return
}
data[type] = depObjectify(data[type], type, warn)
})
}
function bugsTypos (bugs, warn) {
if (!bugs) {
return
}
Object.keys(bugs).forEach(function (k) {
if (typos.bugs[k]) {
warn('typo', k, typos.bugs[k], 'bugs')
bugs[typos.bugs[k]] = bugs[k]
delete bugs[k]
}
})
}

View File

@@ -0,0 +1,22 @@
var util = require('util')
var messages = require('./warning_messages.json')
module.exports = function () {
var args = Array.prototype.slice.call(arguments, 0)
var warningName = args.shift()
if (warningName === 'typo') {
return makeTypoWarning.apply(null, args)
} else {
var msgTemplate = messages[warningName] ? messages[warningName] : warningName + ": '%s'"
args.unshift(msgTemplate)
return util.format.apply(null, args)
}
}
function makeTypoWarning (providedName, probableName, field) {
if (field) {
providedName = field + "['" + providedName + "']"
probableName = field + "['" + probableName + "']"
}
return util.format(messages.typo, providedName, probableName)
}

View File

@@ -0,0 +1,48 @@
module.exports = normalize
var fixer = require('./fixer')
normalize.fixer = fixer
var makeWarning = require('./make_warning')
var fieldsToFix = ['name', 'version', 'description', 'repository', 'modules', 'scripts',
'files', 'bin', 'man', 'bugs', 'keywords', 'readme', 'homepage', 'license']
var otherThingsToFix = ['dependencies', 'people', 'typos']
var thingsToFix = fieldsToFix.map(function (fieldName) {
return ucFirst(fieldName) + 'Field'
})
// two ways to do this in CoffeeScript on only one line, sub-70 chars:
// thingsToFix = fieldsToFix.map (name) -> ucFirst(name) + "Field"
// thingsToFix = (ucFirst(name) + "Field" for name in fieldsToFix)
thingsToFix = thingsToFix.concat(otherThingsToFix)
function normalize (data, warn, strict) {
if (warn === true) {
warn = null
strict = true
}
if (!strict) {
strict = false
}
if (!warn || data.private) {
warn = function (msg) { /* noop */ }
}
if (data.scripts &&
data.scripts.install === 'node-gyp rebuild' &&
!data.scripts.preinstall) {
data.gypfile = true
}
fixer.warn = function () {
warn(makeWarning.apply(null, arguments))
}
thingsToFix.forEach(function (thingName) {
fixer['fix' + ucFirst(thingName)](data, strict)
})
data._id = data.name + '@' + data.version
}
function ucFirst (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}

View File

@@ -0,0 +1,11 @@
var util = require('util')
module.exports = function () {
var args = Array.prototype.slice.call(arguments, 0)
args.forEach(function (arg) {
if (!arg) {
throw new TypeError('Bad arguments.')
}
})
return util.format.apply(null, arguments)
}

View File

@@ -0,0 +1,25 @@
{
"topLevel": {
"dependancies": "dependencies"
,"dependecies": "dependencies"
,"depdenencies": "dependencies"
,"devEependencies": "devDependencies"
,"depends": "dependencies"
,"dev-dependencies": "devDependencies"
,"devDependences": "devDependencies"
,"devDepenencies": "devDependencies"
,"devdependencies": "devDependencies"
,"repostitory": "repository"
,"repo": "repository"
,"prefereGlobal": "preferGlobal"
,"hompage": "homepage"
,"hampage": "homepage"
,"autohr": "author"
,"autor": "author"
,"contributers": "contributors"
,"publicationConfig": "publishConfig"
,"script": "scripts"
},
"bugs": { "web": "url", "name": "url" },
"script": { "server": "start", "tests": "test" }
}

View File

@@ -0,0 +1,30 @@
{
"repositories": "'repositories' (plural) Not supported. Please pick one as the 'repository' field"
,"missingRepository": "No repository field."
,"brokenGitUrl": "Probably broken git url: %s"
,"nonObjectScripts": "scripts must be an object"
,"nonStringScript": "script values must be string commands"
,"nonArrayFiles": "Invalid 'files' member"
,"invalidFilename": "Invalid filename in 'files' list: %s"
,"nonArrayBundleDependencies": "Invalid 'bundleDependencies' list. Must be array of package names"
,"nonStringBundleDependency": "Invalid bundleDependencies member: %s"
,"nonDependencyBundleDependency": "Non-dependency in bundleDependencies: %s"
,"nonObjectDependencies": "%s field must be an object"
,"nonStringDependency": "Invalid dependency: %s %s"
,"deprecatedArrayDependencies": "specifying %s as array is deprecated"
,"deprecatedModules": "modules field is deprecated"
,"nonArrayKeywords": "keywords should be an array of strings"
,"nonStringKeyword": "keywords should be an array of strings"
,"conflictingName": "%s is also the name of a node core module."
,"nonStringDescription": "'description' field should be a string"
,"missingDescription": "No description"
,"missingReadme": "No README data"
,"missingLicense": "No license field."
,"nonEmailUrlBugsString": "Bug string field must be url, email, or {email,url}"
,"nonUrlBugsUrlField": "bugs.url field must be a string url. Deleted."
,"nonEmailBugsEmailField": "bugs.email field must be a string email. Deleted."
,"emptyNormalizedBugs": "Normalized value of bugs field is an empty object. Deleted."
,"nonUrlHomepage": "homepage field must be a string url. Deleted."
,"invalidLicense": "license should be a valid SPDX license expression"
,"typo": "%s should probably be %s."
}

View File

@@ -0,0 +1,13 @@
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.

View File

@@ -0,0 +1,133 @@
# 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.
## 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/REAMDE.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.

View File

@@ -0,0 +1,122 @@
'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]
}

View File

@@ -0,0 +1,228 @@
/* 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+|\/|\W+$/g, '').replace(/\W+/g, '-')
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: ({ user, project }) =>
`https://todo.sr.ht/${user}/${project}`,
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

View File

@@ -0,0 +1,179 @@
'use strict'
const LRU = require('lru-cache')
const hosts = require('./hosts.js')
const fromUrl = require('./from-url.js')
const parseUrl = require('./parse-url.js')
const cache = new LRU({ max: 1000 })
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 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

View File

@@ -0,0 +1,78 @@
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))
}

View File

@@ -0,0 +1,59 @@
{
"name": "hosted-git-info",
"version": "6.1.1",
"description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab",
"main": "./lib/index.js",
"repository": {
"type": "git",
"url": "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": "eslint \"**/*.js\"",
"postlint": "template-oss-check",
"lintfix": "npm run lint -- --fix",
"template-oss-apply": "template-oss-apply --force"
},
"dependencies": {
"lru-cache": "^7.5.1"
},
"devDependencies": {
"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.7.1",
"tap": "^16.0.1"
},
"files": [
"bin/",
"lib/"
],
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.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.7.1"
}
}

View File

@@ -0,0 +1,55 @@
{
"name": "normalize-package-data",
"version": "5.0.0",
"author": "GitHub Inc.",
"description": "Normalizes data that can be found in package.json files.",
"license": "BSD-2-Clause",
"repository": {
"type": "git",
"url": "https://github.com/npm/normalize-package-data.git"
},
"main": "lib/normalize.js",
"scripts": {
"test": "tap",
"npmclilint": "npmcli-lint",
"lint": "eslint \"**/*.js\"",
"lintfix": "npm run lint -- --fix",
"posttest": "npm run lint",
"postsnap": "npm run lintfix --",
"postlint": "template-oss-check",
"snap": "tap",
"template-oss-apply": "template-oss-apply --force"
},
"dependencies": {
"hosted-git-info": "^6.0.0",
"is-core-module": "^2.8.1",
"semver": "^7.3.5",
"validate-npm-package-license": "^3.0.4"
},
"devDependencies": {
"@npmcli/eslint-config": "^3.0.1",
"@npmcli/template-oss": "4.5.1",
"tap": "^16.0.1"
},
"files": [
"bin/",
"lib/"
],
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.5.1"
},
"tap": {
"branches": 86,
"functions": 92,
"lines": 86,
"statements": 86,
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
]
}
}