mirror of
https://github.com/Eugeny/tabby.git
synced 2024-11-22 03:26:09 +03:00
Compare commits
4 Commits
076961bf86
...
5c8a4dd500
Author | SHA1 | Date | |
---|---|---|---|
|
5c8a4dd500 | ||
|
ac6f60f1ae | ||
|
aa67130e37 | ||
|
ba02105e6a |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -251,7 +251,7 @@ jobs:
|
||||
repo: 'eugeny/tabby'
|
||||
dir: 'dist'
|
||||
rpmvers: 'el/9 el/8 ol/6 ol/7'
|
||||
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble debian/jessie debian/stretch debian/buster'
|
||||
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble ubuntu/oracular debian/jessie debian/stretch debian/buster'
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload AppImage (${{matrix.arch}})
|
||||
|
@ -31,7 +31,7 @@
|
||||
"apply-loader": "2.0.0",
|
||||
"axios": "^1.4.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"browserify-sign": "^4.2.1",
|
||||
"browserify-sign": "^4.2.2",
|
||||
"clone-deep": "^4.0.1",
|
||||
"compare-versions": "^5",
|
||||
"core-js": "^3.31.0",
|
||||
|
@ -24,6 +24,7 @@
|
||||
"devDependencies": {
|
||||
"electron-promise-ipc": "^2.2.4",
|
||||
"ps-node": "^0.1.6",
|
||||
"ssh-config": "^5.0.0",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"which": "^3.0.0",
|
||||
"winston": "^3.3.3"
|
||||
|
@ -1,4 +1,3 @@
|
||||
import at from 'core-js-pure/actual/array/at'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as fsSync from 'fs'
|
||||
import * as path from 'path'
|
||||
@ -6,125 +5,297 @@ import slugify from 'slugify'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { PartialProfile } from 'tabby-core'
|
||||
import { SSHProfileImporter, PortForwardType, SSHProfile, SSHProfileOptions, AutoPrivateKeyLocator } from 'tabby-ssh'
|
||||
import {
|
||||
SSHProfileImporter,
|
||||
PortForwardType,
|
||||
SSHProfile,
|
||||
AutoPrivateKeyLocator,
|
||||
ForwardedPortConfig,
|
||||
} from 'tabby-ssh'
|
||||
|
||||
import { ElectronService } from './services/electron.service'
|
||||
import SSHConfig, { LineType } from 'ssh-config'
|
||||
|
||||
// Enum to delineate the properties in SSHProfile options
|
||||
enum SSHProfilePropertyNames {
|
||||
Host = 'host',
|
||||
Port = 'port',
|
||||
User = 'user',
|
||||
X11 = 'x11',
|
||||
PrivateKeys = 'privateKeys',
|
||||
KeepaliveInterval = 'keepaliveInterval',
|
||||
KeepaliveCountMax = 'keepaliveCountMax',
|
||||
ReadyTimeout = 'readyTimeout',
|
||||
JumpHost = 'jumpHost',
|
||||
AgentForward = 'agentForward',
|
||||
ProxyCommand = 'proxyCommand',
|
||||
ForwardedPorts = 'forwardedPorts',
|
||||
}
|
||||
|
||||
// Data structure to map the (lowercase) ssh-config attributes (as keys) to a tuple
|
||||
// containing the name of the corresponding SSHProfile attribute
|
||||
const decodeFields: Record<string, SSHProfilePropertyNames> = {
|
||||
hostname: SSHProfilePropertyNames.Host,
|
||||
user: SSHProfilePropertyNames.User,
|
||||
port: SSHProfilePropertyNames.Port,
|
||||
forwardx11: SSHProfilePropertyNames.X11,
|
||||
serveraliveinterval: SSHProfilePropertyNames.KeepaliveInterval,
|
||||
serveralivecountmax: SSHProfilePropertyNames.KeepaliveCountMax,
|
||||
connecttimeout: SSHProfilePropertyNames.ReadyTimeout,
|
||||
proxyjump: SSHProfilePropertyNames.JumpHost,
|
||||
forwardagent: SSHProfilePropertyNames.AgentForward,
|
||||
identityfile: SSHProfilePropertyNames.PrivateKeys,
|
||||
proxycommand: SSHProfilePropertyNames.ProxyCommand,
|
||||
localforward: SSHProfilePropertyNames.ForwardedPorts,
|
||||
remoteforward: SSHProfilePropertyNames.ForwardedPorts,
|
||||
dynamicforward: SSHProfilePropertyNames.ForwardedPorts,
|
||||
}
|
||||
|
||||
// Function to use the above to return details corresponding to the supplied SSHProperty name.
|
||||
// If the name of the supplied SSH Config file Property is valid, and one that we process,
|
||||
// then we get back the name of the corresponding Property in the SSHProfile object
|
||||
function decodeTarget (SSHProperty: string): string {
|
||||
const lower = SSHProperty.toLowerCase()
|
||||
if (lower in decodeFields) {
|
||||
return decodeFields[lower]
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// Function to combine SSHConfig values into a single string. This is used to smash
|
||||
// together the proxyCommand values which are split on whitespace and presented as
|
||||
// an array of objects in the SSHConfig object
|
||||
function convertSSHConfigValuesToString (arg: string | string[] | object[]): string {
|
||||
if (typeof arg === 'string') { return arg }
|
||||
let allStrings = true
|
||||
for (const item of arg) {
|
||||
if (typeof item !== 'string') {
|
||||
allStrings = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allStrings) {
|
||||
return arg.join(' ')
|
||||
}
|
||||
|
||||
// Have to explicitly unwrap the arg into a list of objects to avoid Typescript grumbles
|
||||
const objList: object[] = []
|
||||
for (const item of arg) {
|
||||
if ( typeof item === 'object' && 'val' in item ) {
|
||||
objList.push(item)
|
||||
}
|
||||
}
|
||||
return objList.filter(obj => 'val' in obj)
|
||||
.map(obj => 'val' in obj ? obj.val as string: '')
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
// Function to read in the SSH config file and return it as a string
|
||||
async function readSSHConfigFile (filePath: string): Promise<string> {
|
||||
try {
|
||||
return await fs.readFile(filePath, 'utf8')
|
||||
} catch (err) {
|
||||
console.error('Error reading SSH config file:', err)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
// Function to take an ssh-config entry and convert it into an SSHProfile
|
||||
function convertHostToSSHProfile (host: string, settings: Record<string, string | string[] | object[] >): PartialProfile<SSHProfile> {
|
||||
|
||||
// inline function to generate an id for this profile
|
||||
const deriveID = (name: string) => 'openssh-config:' + slugify(name)
|
||||
|
||||
// Start point of the profile, with an ID, name, type and group
|
||||
const thisProfile: PartialProfile<SSHProfile> = {
|
||||
id: deriveID(host),
|
||||
name: `${host} (.ssh/config)`,
|
||||
type: 'ssh',
|
||||
group: 'Imported from .ssh/config',
|
||||
}
|
||||
const options = {}
|
||||
|
||||
function convertToForwardedPortDescriptor (forwardType: PortForwardType.Local | PortForwardType.Remote | PortForwardType.Dynamic, details: string): ForwardedPortConfig {
|
||||
const detailsParts = details.split(/\s/)
|
||||
const bindParts = detailsParts[0].trim().split(':')
|
||||
if (bindParts.length === 1) {
|
||||
bindParts.unshift('127.0.0.1')
|
||||
}
|
||||
let tgtParts = ['', '22']
|
||||
if ( detailsParts.length > 1 ) {
|
||||
tgtParts = detailsParts[1].trim().split(':')
|
||||
}
|
||||
return {
|
||||
host: bindParts[0],
|
||||
port: parseInt(bindParts[1]),
|
||||
targetAddress: tgtParts[0],
|
||||
targetPort: parseInt(tgtParts[1]),
|
||||
type: forwardType,
|
||||
description: details,
|
||||
}
|
||||
}
|
||||
|
||||
// for each ssh-config key in turn...
|
||||
for (const key in settings) {
|
||||
// decode a target attribute and an action
|
||||
const targetName = decodeTarget(key)
|
||||
|
||||
switch (targetName) {
|
||||
|
||||
// The following have single string values
|
||||
case SSHProfilePropertyNames.User:
|
||||
case SSHProfilePropertyNames.Host:
|
||||
case SSHProfilePropertyNames.JumpHost:
|
||||
const basicString = settings[key]
|
||||
if (typeof basicString === 'string') {
|
||||
if (targetName === SSHProfilePropertyNames.JumpHost) {
|
||||
options[targetName] = deriveID(basicString)
|
||||
} else {
|
||||
options[targetName] = basicString
|
||||
}
|
||||
} else {
|
||||
console.log('Unexpected value in settings for ' + key)
|
||||
}
|
||||
break
|
||||
|
||||
// The following have single integer values
|
||||
case SSHProfilePropertyNames.Port:
|
||||
case SSHProfilePropertyNames.KeepaliveInterval:
|
||||
case SSHProfilePropertyNames.KeepaliveCountMax:
|
||||
case SSHProfilePropertyNames.ReadyTimeout:
|
||||
const numberString = settings[key]
|
||||
if (typeof numberString === 'string') {
|
||||
options[targetName] = parseInt(numberString, 10)
|
||||
} else {
|
||||
console.log('Unexpected value in settings for ' + key)
|
||||
}
|
||||
break
|
||||
|
||||
// The following have single yes/no values
|
||||
case SSHProfilePropertyNames.X11:
|
||||
case SSHProfilePropertyNames.AgentForward:
|
||||
let booleanString = settings[key]
|
||||
booleanString = typeof booleanString === 'string' ? booleanString.toLowerCase() : ''
|
||||
if ( booleanString === 'yes' || booleanString === 'no' ) {
|
||||
options[targetName] = booleanString === 'yes'
|
||||
} else {
|
||||
console.log('Unexpected value in settings for ' + key)
|
||||
}
|
||||
break
|
||||
|
||||
// ProxyCommand will be an array if unquoted and containing multiple words,
|
||||
// or a simple string otherwise
|
||||
case SSHProfilePropertyNames.ProxyCommand:
|
||||
const proxyCommand = convertSSHConfigValuesToString(settings[key])
|
||||
options[targetName] = proxyCommand
|
||||
break
|
||||
|
||||
// IdentityFile may have multiple values and the need to have '~' converted to the
|
||||
// path to the HOME directory
|
||||
case SSHProfilePropertyNames.PrivateKeys:
|
||||
const processedKeys: string [] = (settings[key] as string[]).map( s => {
|
||||
let retVal: string = s
|
||||
if (s.startsWith('~/')) {
|
||||
retVal = path.join(process.env.HOME ?? '~', s.slice(2))
|
||||
}
|
||||
return retVal
|
||||
})
|
||||
options[targetName] = processedKeys
|
||||
break
|
||||
|
||||
// The port forwarding directives all end up in the same space, but with a different value
|
||||
// in the SSHProfileOptions object
|
||||
case SSHProfilePropertyNames.ForwardedPorts:
|
||||
const forwardTypeString = key.toLowerCase()
|
||||
let forwardType: PortForwardType | null = null
|
||||
switch (forwardTypeString) {
|
||||
case 'localforward':
|
||||
forwardType = PortForwardType.Local
|
||||
break
|
||||
case 'remoteforward':
|
||||
forwardType = PortForwardType.Remote
|
||||
break
|
||||
case 'dynamicforward':
|
||||
forwardType = PortForwardType.Dynamic
|
||||
break
|
||||
}
|
||||
if (forwardType) {
|
||||
options[targetName] ??= []
|
||||
for (const forwarderDetails of settings[key]) {
|
||||
if (typeof forwarderDetails === 'string') {
|
||||
options[targetName].push(convertToForwardedPortDescriptor(forwardType, forwarderDetails))
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
thisProfile.options = options
|
||||
return thisProfile
|
||||
}
|
||||
|
||||
function convertToSSHProfiles (config: SSHConfig): PartialProfile<SSHProfile>[] {
|
||||
const myMap = new Map<string, PartialProfile<SSHProfile>>()
|
||||
|
||||
function noWildCardsInName (name: string) {
|
||||
return !/[?*]/.test(name)
|
||||
}
|
||||
|
||||
for (const entry of config) {
|
||||
// Each entry represents a line in the SSH Config. If the line is a 'Host' line,
|
||||
// then it will also contain the configuration for that identifiable Host.
|
||||
// There may be more than one host per line and some 'Hosts' have wildcards in their
|
||||
// names
|
||||
// If this is a genuine entry rather than a Comment...
|
||||
// ... and there is a 'Host' Parameter
|
||||
if (entry.type === LineType.DIRECTIVE && entry.param === 'Host') {
|
||||
|
||||
// for each Name in this entry
|
||||
const hostList: string[] = []
|
||||
// if there is more than one host specified on this line, then the names will be
|
||||
// in an array
|
||||
if (typeof entry.value === 'string') {
|
||||
hostList.push(entry.value)
|
||||
} else if (Array.isArray(entry.value)) {
|
||||
for (const item of entry.value) {
|
||||
hostList.push(item.val)
|
||||
}
|
||||
}
|
||||
// for each Host identified on this line, check that there are no wildcards in the
|
||||
// name and that we've not seen the name before.
|
||||
// If that is the case, then get the full configuration for this name.
|
||||
// If that has a 'Hostname' property (if that's missing, the name is not usable
|
||||
// for our purposes) then convert the configuration into an SSHProfile and stash it
|
||||
for (const host of hostList) {
|
||||
if (noWildCardsInName(host)) {
|
||||
if (!(host in myMap)) {
|
||||
// NOTE: SSHConfig.compute() lies about the return types
|
||||
const configuration: Record<string, string | string[] | object[]> = config.compute(host)
|
||||
if (configuration['HostName']) {
|
||||
myMap[host] = convertHostToSSHProfile(host, configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Convert the values from the map into a list of Partial SSHProfiles sorted
|
||||
// by Hostname
|
||||
return Object.keys(myMap).sort().map(key => myMap[key])
|
||||
}
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OpenSSHImporter extends SSHProfileImporter {
|
||||
async getProfiles (): Promise<PartialProfile<SSHProfile>[]> {
|
||||
const deriveID = name => 'openssh-config:' + slugify(name)
|
||||
|
||||
const results: PartialProfile<SSHProfile>[] = []
|
||||
const configPath = path.join(process.env.HOME ?? '~', '.ssh', 'config')
|
||||
try {
|
||||
const lines = (await fs.readFile(configPath, 'utf8')).split('\n')
|
||||
const globalOptions: Partial<SSHProfileOptions> = {}
|
||||
let currentProfile: PartialProfile<SSHProfile>|null = null
|
||||
for (let line of lines) {
|
||||
if (line.trim().startsWith('#') || !line.trim()) {
|
||||
continue
|
||||
}
|
||||
if (line.toLowerCase().startsWith('host ')) {
|
||||
if (currentProfile) {
|
||||
results.push(currentProfile)
|
||||
}
|
||||
const name = line.substr(5).trim()
|
||||
currentProfile = {
|
||||
id: deriveID(name),
|
||||
name: `${name} (.ssh/config)`,
|
||||
type: 'ssh',
|
||||
group: 'Imported from .ssh/config',
|
||||
options: {
|
||||
...globalOptions,
|
||||
host: name,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
const target: Partial<SSHProfileOptions> = currentProfile?.options ?? globalOptions
|
||||
line = line.trim()
|
||||
const idx = /\s/.exec(line)?.index ?? -1
|
||||
if (idx === -1) {
|
||||
continue
|
||||
}
|
||||
const key = line.substr(0, idx).trim()
|
||||
const value = line.substr(idx + 1).trim()
|
||||
|
||||
if (key === 'IdentityFile') {
|
||||
target.privateKeys = value.split(',').map(s => s.trim()).map(s => {
|
||||
if (s.startsWith('~')) {
|
||||
s = path.join(process.env.HOME ?? '~', s.slice(2))
|
||||
}
|
||||
return s
|
||||
})
|
||||
} else if (key === 'RemoteForward') {
|
||||
const bind = value.split(/\s/)[0].trim()
|
||||
const tgt = value.split(/\s/)[1].trim()
|
||||
target.forwardedPorts ??= []
|
||||
target.forwardedPorts.push({
|
||||
type: PortForwardType.Remote,
|
||||
description: value,
|
||||
host: bind.split(':')[0] ?? '127.0.0.1',
|
||||
port: parseInt(bind.split(':')[1] ?? bind),
|
||||
targetAddress: tgt.split(':')[0],
|
||||
targetPort: parseInt(tgt.split(':')[1]),
|
||||
})
|
||||
} else if (key === 'LocalForward') {
|
||||
const bind = value.split(/\s/)[0].trim()
|
||||
const tgt = value.split(/\s/)[1].trim()
|
||||
target.forwardedPorts ??= []
|
||||
target.forwardedPorts.push({
|
||||
type: PortForwardType.Local,
|
||||
description: value,
|
||||
host: bind.includes(':') ? bind.split(':')[0] : '127.0.0.1',
|
||||
port: parseInt(at(bind.split(':'), -1)),
|
||||
targetAddress: tgt.split(':')[0],
|
||||
targetPort: parseInt(tgt.split(':')[1]),
|
||||
})
|
||||
} else if (key === 'DynamicForward') {
|
||||
const bind = value.trim()
|
||||
target.forwardedPorts ??= []
|
||||
target.forwardedPorts.push({
|
||||
type: PortForwardType.Dynamic,
|
||||
description: value,
|
||||
host: bind.includes(':') ? bind.split(':')[0] : '127.0.0.1',
|
||||
port: parseInt(at(bind.split(':'), -1)),
|
||||
targetAddress: '',
|
||||
targetPort: 22,
|
||||
})
|
||||
} else {
|
||||
const mappedKey = {
|
||||
hostname: 'host',
|
||||
host: 'host',
|
||||
port: 'port',
|
||||
user: 'user',
|
||||
forwardx11: 'x11',
|
||||
serveraliveinterval: 'keepaliveInterval',
|
||||
serveralivecountmax: 'keepaliveCountMax',
|
||||
proxycommand: 'proxyCommand',
|
||||
proxyjump: 'jumpHost',
|
||||
}[key.toLowerCase()]
|
||||
if (mappedKey) {
|
||||
target[mappedKey] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentProfile) {
|
||||
results.push(currentProfile)
|
||||
}
|
||||
for (const p of results) {
|
||||
if (p.options?.proxyCommand) {
|
||||
p.options.proxyCommand = p.options.proxyCommand
|
||||
.replace('%h', p.options.host ?? '')
|
||||
.replace('%p', (p.options.port ?? 22).toString())
|
||||
}
|
||||
if (p.options?.jumpHost) {
|
||||
p.options.jumpHost = deriveID(p.options.jumpHost)
|
||||
}
|
||||
}
|
||||
return results
|
||||
try {
|
||||
const sshConfigContent = await readSSHConfigFile(configPath)
|
||||
const config: SSHConfig = SSHConfig.parse(sshConfigContent)
|
||||
return convertToSSHProfiles(config)
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
return []
|
||||
|
42
yarn.lock
42
yarn.lock
@ -1746,10 +1746,10 @@ bn.js@^4.0.0, bn.js@^4.11.9:
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.0.0, bn.js@^5.1.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
|
||||
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
|
||||
bn.js@^5.0.0, bn.js@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
|
||||
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
|
||||
|
||||
boolean@^3.0.1:
|
||||
version "3.2.0"
|
||||
@ -1828,7 +1828,7 @@ browserify-aes@^1.0.0:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
browserify-rsa@^4.0.1:
|
||||
browserify-rsa@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
|
||||
integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
|
||||
@ -1836,20 +1836,20 @@ browserify-rsa@^4.0.1:
|
||||
bn.js "^5.0.0"
|
||||
randombytes "^2.0.1"
|
||||
|
||||
browserify-sign@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
|
||||
integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
|
||||
browserify-sign@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e"
|
||||
integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==
|
||||
dependencies:
|
||||
bn.js "^5.1.1"
|
||||
browserify-rsa "^4.0.1"
|
||||
bn.js "^5.2.1"
|
||||
browserify-rsa "^4.1.0"
|
||||
create-hash "^1.2.0"
|
||||
create-hmac "^1.1.7"
|
||||
elliptic "^6.5.3"
|
||||
elliptic "^6.5.4"
|
||||
inherits "^2.0.4"
|
||||
parse-asn1 "^5.1.5"
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
parse-asn1 "^5.1.6"
|
||||
readable-stream "^3.6.2"
|
||||
safe-buffer "^5.2.1"
|
||||
|
||||
browserslist@^4.14.5, browserslist@^4.21.3:
|
||||
version "4.21.5"
|
||||
@ -3062,7 +3062,7 @@ electron@^29:
|
||||
"@types/node" "^20.9.0"
|
||||
extract-zip "^2.0.1"
|
||||
|
||||
elliptic@^6.5.3:
|
||||
elliptic@^6.5.4:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
@ -6695,7 +6695,7 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-asn1@^5.1.5:
|
||||
parse-asn1@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
|
||||
integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
|
||||
@ -7443,10 +7443,10 @@ read@1, read@~1.0.1, read@~1.0.7:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
readable-stream@^3.6.0, readable-stream@^3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
|
Loading…
Reference in New Issue
Block a user