forked from snxraven/peardock
docker-modem calls url.resolve/parse/format, which bare-url does not implement. Map bare url imports to build/shims/node-url.cjs so the standalone peardock-server can talk to the Docker socket again.
261 lines
6.8 KiB
JavaScript
261 lines
6.8 KiB
JavaScript
/**
|
|
* Node.js-compatible `url` for Bare.
|
|
*
|
|
* bare-url is WHATWG-only (no url.resolve / url.parse / url.format).
|
|
* docker-modem (via dockerode) requires the legacy Node API:
|
|
* url.resolve, url.parse, url.format, url.URL
|
|
*
|
|
* Mapped from package.json imports under the "bare" condition.
|
|
*/
|
|
'use strict'
|
|
|
|
let URLImpl
|
|
let URLSearchParamsImpl
|
|
try {
|
|
const bare = require('bare-url')
|
|
URLImpl = bare.URL || bare
|
|
URLSearchParamsImpl =
|
|
bare.URLSearchParams ||
|
|
(typeof globalThis !== 'undefined' && globalThis.URLSearchParams) ||
|
|
null
|
|
} catch {
|
|
URLImpl = globalThis.URL
|
|
URLSearchParamsImpl = globalThis.URLSearchParams
|
|
}
|
|
|
|
const URL = URLImpl
|
|
|
|
function isAbsoluteUrl(s) {
|
|
return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(s)
|
|
}
|
|
|
|
/**
|
|
* Minimal Node url.parse compatible object.
|
|
* @param {string} urlStr
|
|
* @param {boolean} [parseQueryString]
|
|
*/
|
|
function parse(urlStr, parseQueryString) {
|
|
const input = String(urlStr ?? '')
|
|
try {
|
|
let u
|
|
if (isAbsoluteUrl(input)) {
|
|
u = new URL(input)
|
|
} else if (input.startsWith('//')) {
|
|
u = new URL('http:' + input)
|
|
} else {
|
|
// Path-only / relative — give a dummy base then re-map
|
|
u = new URL(input, 'http://peardock.invalid')
|
|
}
|
|
|
|
const dummy = u.hostname === 'peardock.invalid' && !isAbsoluteUrl(input)
|
|
const auth =
|
|
u.username || u.password
|
|
? decodeURIComponent(u.username) +
|
|
(u.password ? ':' + decodeURIComponent(u.password) : '')
|
|
: null
|
|
|
|
const search = u.search || null
|
|
let query = null
|
|
if (search) {
|
|
if (parseQueryString && URLSearchParamsImpl) {
|
|
query = Object.fromEntries(new URLSearchParamsImpl(u.searchParams || search))
|
|
} else {
|
|
query = search.startsWith('?') ? search.slice(1) : search
|
|
}
|
|
}
|
|
|
|
const pathname = u.pathname || null
|
|
const path = (pathname || '') + (search || '') || null
|
|
|
|
return {
|
|
protocol: dummy ? null : u.protocol || null,
|
|
slashes: dummy ? null : true,
|
|
auth: dummy ? null : auth,
|
|
host: dummy ? null : u.host || null,
|
|
port: dummy ? null : u.port || null,
|
|
hostname: dummy ? null : u.hostname || null,
|
|
hash: u.hash || null,
|
|
search,
|
|
query,
|
|
pathname,
|
|
path,
|
|
href: dummy ? input : u.href,
|
|
}
|
|
} catch {
|
|
return {
|
|
protocol: null,
|
|
slashes: null,
|
|
auth: null,
|
|
host: null,
|
|
port: null,
|
|
hostname: null,
|
|
hash: null,
|
|
search: null,
|
|
query: null,
|
|
pathname: null,
|
|
path: null,
|
|
href: input,
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Minimal Node url.format.
|
|
* @param {string|object} urlObj
|
|
*/
|
|
function format(urlObj) {
|
|
if (urlObj == null) return ''
|
|
if (typeof urlObj === 'string') return urlObj
|
|
if (typeof URLImpl === 'function' && urlObj instanceof URLImpl) return urlObj.href
|
|
|
|
let protocol = urlObj.protocol || ''
|
|
if (protocol && protocol[protocol.length - 1] !== ':') protocol += ':'
|
|
|
|
let auth = ''
|
|
if (urlObj.auth) {
|
|
auth = String(urlObj.auth).replace(/@/g, '') + '@'
|
|
}
|
|
|
|
let host = ''
|
|
if (urlObj.host != null && urlObj.host !== '') {
|
|
host = String(urlObj.host)
|
|
} else if (urlObj.hostname != null && urlObj.hostname !== '') {
|
|
host = String(urlObj.hostname)
|
|
if (urlObj.port != null && urlObj.port !== '') host += ':' + urlObj.port
|
|
}
|
|
|
|
let pathname = urlObj.pathname != null ? String(urlObj.pathname) : ''
|
|
if (pathname && pathname[0] !== '/' && host) pathname = '/' + pathname
|
|
|
|
let search = ''
|
|
if (urlObj.search != null && urlObj.search !== '') {
|
|
search = String(urlObj.search)
|
|
if (search[0] !== '?') search = '?' + search
|
|
} else if (urlObj.query != null && urlObj.query !== '') {
|
|
if (typeof urlObj.query === 'string') {
|
|
search = urlObj.query[0] === '?' ? urlObj.query : '?' + urlObj.query
|
|
} else if (typeof urlObj.query === 'object' && URLSearchParamsImpl) {
|
|
const q = new URLSearchParamsImpl(urlObj.query).toString()
|
|
if (q) search = '?' + q
|
|
}
|
|
}
|
|
|
|
let hash = urlObj.hash != null ? String(urlObj.hash) : ''
|
|
if (hash && hash[0] !== '#') hash = '#' + hash
|
|
|
|
// Match Node quirks roughly: with host → scheme://host/path; without → scheme:/path
|
|
if (host) {
|
|
const slashes = protocol ? '//' : ''
|
|
return protocol + slashes + auth + host + pathname + search + hash
|
|
}
|
|
if (protocol) {
|
|
// Node: url.format({ protocol: 'http:', pathname: '/x' }) => 'http:/x'
|
|
return protocol + (pathname.startsWith('/') ? pathname : '/' + pathname) + search + hash
|
|
}
|
|
return pathname + search + hash
|
|
}
|
|
|
|
/**
|
|
* Node url.resolve(from, to)
|
|
* @param {string} from
|
|
* @param {string} to
|
|
*/
|
|
function resolve(from, to) {
|
|
const base = String(from ?? '')
|
|
const rel = String(to ?? '')
|
|
|
|
if (!rel) {
|
|
try {
|
|
return isAbsoluteUrl(base) ? new URL(base).href : format(parse(base))
|
|
} catch {
|
|
return base
|
|
}
|
|
}
|
|
|
|
if (isAbsoluteUrl(rel)) {
|
|
try {
|
|
return new URL(rel).href
|
|
} catch {
|
|
return rel
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (isAbsoluteUrl(base)) {
|
|
return new URL(rel, base).href
|
|
}
|
|
// Path-only base (e.g. "/v1.41/" + "containers/json")
|
|
const origin = 'http://peardock.invalid'
|
|
const b = base.startsWith('/') ? base : '/' + base
|
|
const resolved = new URL(rel, origin + b)
|
|
return resolved.pathname + resolved.search + resolved.hash
|
|
} catch {
|
|
if (rel.startsWith('/')) return rel
|
|
const slash = base.endsWith('/') ? '' : '/'
|
|
return base + slash + rel
|
|
}
|
|
}
|
|
|
|
function pathToFileURL(filepath) {
|
|
if (typeof URLImpl !== 'function') throw new Error('URL not available')
|
|
const path = require('path')
|
|
let resolved = path.resolve(String(filepath))
|
|
// Windows drive letters
|
|
if (/^[a-zA-Z]:[\\/]/.test(resolved)) {
|
|
resolved = '/' + resolved.replace(/\\/g, '/')
|
|
} else if (!resolved.startsWith('/')) {
|
|
resolved = '/' + resolved
|
|
}
|
|
return new URLImpl('file://' + encodeURI(resolved).replace(/#/g, '%23'))
|
|
}
|
|
|
|
function fileURLToPath(url) {
|
|
const u = typeof url === 'string' ? new URLImpl(url) : url
|
|
if (u.protocol !== 'file:') {
|
|
throw new TypeError('Must be a file URL')
|
|
}
|
|
let p = decodeURIComponent(u.pathname)
|
|
// /C:/... → C:\... on windows-like
|
|
if (/^\/[a-zA-Z]:\//.test(p)) {
|
|
p = p.slice(1).replace(/\//g, require('path').sep)
|
|
}
|
|
return p
|
|
}
|
|
|
|
function domainToASCII(domain) {
|
|
return String(domain ?? '')
|
|
}
|
|
|
|
function domainToUnicode(domain) {
|
|
return String(domain ?? '')
|
|
}
|
|
|
|
function urlToHttpOptions(url) {
|
|
const u = typeof url === 'string' ? new URLImpl(url) : url
|
|
return {
|
|
protocol: u.protocol,
|
|
hostname: u.hostname,
|
|
hash: u.hash,
|
|
search: u.search,
|
|
pathname: u.pathname,
|
|
path: u.pathname + u.search,
|
|
href: u.href,
|
|
port: u.port,
|
|
auth: u.username ? u.username + (u.password ? ':' + u.password : '') : '',
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
URL,
|
|
Url: URL,
|
|
URLSearchParams: URLSearchParamsImpl,
|
|
parse,
|
|
format,
|
|
resolve,
|
|
pathToFileURL,
|
|
fileURLToPath,
|
|
domainToASCII,
|
|
domainToUnicode,
|
|
urlToHttpOptions,
|
|
}
|