mirror of https://github.com/certd/certd
refactor: ```
parent
06603759fd
commit
df9f561fd3
|
@ -4,4 +4,4 @@
|
|||
out
|
||||
gen
|
||||
node_modules/
|
||||
packages/*/test/*.private.js
|
||||
/test/*.private.js
|
||||
|
|
|
@ -2,5 +2,13 @@
|
|||
"extends": "standard",
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.test.js", "*.spec.js"],
|
||||
"rules": {
|
||||
"no-unused-expressions": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"name": "@certd/certd",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"exports": "src/index.js",
|
||||
"main": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
|
|
@ -27,8 +27,8 @@ export class Certd {
|
|||
options = this.options
|
||||
}
|
||||
const certOptions = options.cert
|
||||
const providers = options.providers
|
||||
const providerOptions = providers[certOptions.challenge.dnsProvider]
|
||||
const accessProviders = options.accessProviders
|
||||
const providerOptions = accessProviders[certOptions.challenge.dnsProvider]
|
||||
const dnsProvider = await DnsProviderFactory.createByType(providerOptions.providerType, providerOptions)
|
||||
const cert = await this.acme.order({
|
||||
email: certOptions.email,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import util from './util.js'
|
||||
import log4js from 'log4js'
|
||||
import path from 'path'
|
||||
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
|
||||
const filename = path.join(util.getUserBasePath(), '/logs/certd.log')
|
||||
log4js.configure({
|
||||
appenders: { std: { type: 'stdout' }, file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename } },
|
||||
categories: { default: { appenders: ['file', 'std'], level: level } }
|
||||
appenders: { std: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['std'], level: 'info' } }
|
||||
})
|
||||
const logger = log4js.getLogger('certd')
|
||||
export default logger
|
||||
|
|
|
@ -4,28 +4,28 @@ import AliyunDnsProvider from '../../src/dns-provider/impl/aliyun.js'
|
|||
const { expect } = pkg
|
||||
describe('AliyunDnsProvider', function () {
|
||||
it('#getDomainList', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
||||
const domainList = await aliyunDnsProvider.getDomainList()
|
||||
console.log('domainList', domainList)
|
||||
expect(domainList.length).gt(0)
|
||||
})
|
||||
|
||||
it('#getRecords', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
||||
const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*')
|
||||
console.log('recordList', recordList)
|
||||
expect(recordList.length).gt(0)
|
||||
})
|
||||
|
||||
it('#createRecord', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
||||
const recordId = await aliyunDnsProvider.createRecord('___certd___.__test__.docmirror.cn', 'TXT', 'aaaa')
|
||||
console.log('recordId', recordId)
|
||||
expect(recordId != null).ok
|
||||
})
|
||||
|
||||
it('#removeRecord', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.accessProviders.aliyun)
|
||||
const recordId = await aliyunDnsProvider.removeRecord('___certd___.__test__.docmirror.cn', 'TXT', 'aaaa')
|
||||
console.log('recordId', recordId)
|
||||
expect(recordId != null).ok
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash'
|
||||
import optionsPrivate from './options.private.js'
|
||||
import optionsPrivate from '../../../test/options.private.js'
|
||||
const defaultOptions = {
|
||||
providers: {
|
||||
accessProviders: {
|
||||
aliyun: {
|
||||
providerType: 'aliyun',
|
||||
accessKeyId: '',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@certd/samples",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"main": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
@ -11,7 +11,9 @@
|
|||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@types/node": "^14.14.13",
|
||||
"lodash": "^4.17.20",
|
||||
"log4js": "^6.3.0"
|
||||
"log4js": "^6.3.0",
|
||||
"@certd/certd": "^0.0.1",
|
||||
"@certd/plugins": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
import Certd from '@certd/certd'
|
||||
import CertdPlugins from '@certd/plugins'
|
||||
import options from './options'
|
||||
import log from './util.log'
|
||||
export class DeployFlow {
|
||||
async run () {
|
||||
import log from './util.log.js'
|
||||
export class Deployer {
|
||||
async run (options) {
|
||||
const certd = new Certd()
|
||||
const cert = certd.certApply(options)
|
||||
const context = {}
|
||||
for (const deploy of options.deploy) {
|
||||
log.info(`-------部署任务【${deploy.deployName}】开始-------`)
|
||||
|
||||
for (const task of deploy.tasks) {
|
||||
await this.runTask({ options, cert, task })
|
||||
await this.runTask({ options, cert, task, context })
|
||||
}
|
||||
log.info(`-------部署任务【${deploy.deployName}】完成-------`)
|
||||
}
|
||||
return {
|
||||
cert,
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
async runTask ({ options, task, cert }) {
|
||||
async runTask ({ options, task, cert, context }) {
|
||||
const taskType = task.type
|
||||
const plugin = CertdPlugins[taskType]
|
||||
if (plugin == null) {
|
||||
throw new Error(`插件:${taskType}还未安装`)
|
||||
}
|
||||
const context = {}
|
||||
|
||||
log.info(`--插件【${task.taskName}】开始执行-------`)
|
||||
await plugin.execute({ cert, providers: options.providers, args: task, context })
|
||||
await plugin.execute({ cert, accessProviders: options.accessProviders, args: task, context })
|
||||
log.info(`--插件【${task.taskName}】执行完成-------`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import util from './util.js'
|
||||
import log4js from 'log4js'
|
||||
import path from 'path'
|
||||
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
|
||||
const filename = path.join(util.getUserBasePath(), '/logs/certd.log')
|
||||
log4js.configure({
|
||||
appenders: { std: { type: 'stdout' }, file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename } },
|
||||
categories: { default: { appenders: ['std'], level: level } }
|
||||
appenders: { std: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['std'], level: 'info' } }
|
||||
})
|
||||
const logger = log4js.getLogger('certd')
|
||||
export default logger
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import pkg from 'chai'
|
||||
import options from './options.js'
|
||||
import Deployer from '../src/index.js'
|
||||
const { expect } = pkg
|
||||
describe('AutoDeploy', function () {
|
||||
it('#run', async function () {
|
||||
const deploy = new Deployer()
|
||||
const ret = deploy.run(options)
|
||||
expect(ret).ok
|
||||
expect(ret.cert).ok
|
||||
expect(ret.AliyunCertId).ok
|
||||
})
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash'
|
||||
import optionsPrivate from './options.private.js'
|
||||
import optionsPrivate from '../../../test/options.private.js'
|
||||
const defaultOptions = {
|
||||
providers: {
|
||||
accessProviders: {
|
||||
aliyun: {
|
||||
providerType: 'aliyun',
|
||||
accessKeyId: '',
|
||||
|
@ -17,7 +17,7 @@ const defaultOptions = {
|
|||
}
|
||||
},
|
||||
cert: {
|
||||
domains: ['*.docmirror.club', 'docmirror.club'],
|
||||
domains: ['*.docmirror.club', 'docmirror.xyz'],
|
||||
email: 'xiaojunnuo@qq.com',
|
||||
challenge: {
|
||||
challengeType: 'dns',
|
||||
|
@ -38,17 +38,17 @@ const defaultOptions = {
|
|||
tasks: [
|
||||
{
|
||||
name: '上传证书到云',
|
||||
taskType: 'uploadCertToCloud',
|
||||
type: 'uploadCertToAliyun',
|
||||
certStore: 'aliyun'
|
||||
},
|
||||
{
|
||||
{ // CDN、SCDN、DCDN和负载均衡(SLB)
|
||||
name: '部署证书到SLB',
|
||||
taskType: 'deployCertToAliyunSLB',
|
||||
type: 'deployCertToAliyunSLB',
|
||||
certStore: 'aliyun'
|
||||
},
|
||||
{
|
||||
name: '部署证书到阿里云集群Ingress',
|
||||
taskType: 'deployCertToAliyunK8sIngress',
|
||||
type: 'deployCertToAliyunK8sIngress',
|
||||
certStore: 'aliyun'
|
||||
}
|
||||
]
|
||||
|
@ -58,7 +58,7 @@ const defaultOptions = {
|
|||
tasks: [
|
||||
{
|
||||
name: '上传证书到服务器,并重启nginx',
|
||||
taskType: 'sshAndExecute',
|
||||
type: 'sshAndExecute',
|
||||
ssh: 'myLinux',
|
||||
upload: [
|
||||
{ from: '{certPath}', to: '/xxx/xxx/xxx.cert.pem' },
|
||||
|
@ -73,7 +73,7 @@ const defaultOptions = {
|
|||
tasks: [
|
||||
{
|
||||
name: '触发jenkins任务',
|
||||
taskType: 'sshAndExecute',
|
||||
type: 'sshAndExecute',
|
||||
ssh: 'myLinux',
|
||||
script: 'sudo systemctl restart nginx'
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "@certd/plugins",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"main": "./src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
|
@ -10,8 +10,10 @@
|
|||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@types/node": "^14.14.13",
|
||||
"dayjs": "^1.9.7",
|
||||
"lodash": "^4.17.20",
|
||||
"log4js": "^6.3.0"
|
||||
"log4js": "^6.3.0",
|
||||
"@certd/certd": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { AbstractPlugin } from '../abstract-plugin.js'
|
||||
|
||||
export class AbstractAliyunPlugin extends AbstractPlugin {
|
||||
format (pem) {
|
||||
pem = pem.replace(/\r/g, '')
|
||||
pem = pem.replace(/\n\n/g, '')
|
||||
pem = pem.replace(/\n$/g, '')
|
||||
return pem
|
||||
}
|
||||
|
||||
getAccessProvider (accessProvider, accessProviders) {
|
||||
if (typeof accessProvider === 'string' && accessProviders) {
|
||||
accessProvider = accessProviders[accessProvider]
|
||||
}
|
||||
return accessProvider
|
||||
}
|
||||
|
||||
checkRet (ret) {
|
||||
if (ret.code != null) {
|
||||
throw new Error('执行失败:', ret.Message)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import { AbstractPlugin } from '../../abstract-plugin/index.js'
|
||||
import Core from '@alicloud/pop-core'
|
||||
import dayjs from 'dayjs'
|
||||
export class UploadCertToAliyunPlugin extends AbstractPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'deployToCdn',
|
||||
label: '部署到阿里云CDN',
|
||||
input: {
|
||||
domainName: {
|
||||
label: 'cdn加速域名',
|
||||
required: true
|
||||
},
|
||||
certName: {
|
||||
label: '证书名称'
|
||||
},
|
||||
certType: {
|
||||
label: '证书来源',
|
||||
options: [
|
||||
{ value: 'upload', label: '直接上传' },
|
||||
{ value: 'cas', label: '从证书库(需要uploadCertToAliyun插件作为前置任务)' }
|
||||
],
|
||||
required: true
|
||||
},
|
||||
// serverCertificateStatus: {
|
||||
// label: '启用https',
|
||||
// options: [
|
||||
// { value: 'on', label: '开启HTTPS,并更新证书' },
|
||||
// { value: 'auto', label: '若HTTPS开启则更新,未开启不更新' }
|
||||
// ],
|
||||
// required:true
|
||||
// },
|
||||
accessProvider: {
|
||||
label: 'Access提供者',
|
||||
type: [String, Object],
|
||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
||||
options: 'accessProviders[type=aliyun]',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClient (aliyunProvider) {
|
||||
return new Core({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: 'https://cdn.aliyuncs.com',
|
||||
apiVersion: '2018-05-10'
|
||||
})
|
||||
}
|
||||
|
||||
async execute ({ accessProviders, cert, args, context }) {
|
||||
let { accessProvider } = args
|
||||
if (typeof accessProvider === 'string' && accessProviders) {
|
||||
accessProvider = accessProviders[accessProvider]
|
||||
}
|
||||
const client = this.getClient(accessProvider)
|
||||
|
||||
const { certName, certType, domainName } = args
|
||||
const CertName = certName + '-' + dayjs().format('YYYYMMDDHHmmss')
|
||||
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
DomainName: domainName,
|
||||
ServerCertificateStatus: 'on',
|
||||
CertName: CertName,
|
||||
CertType: certType,
|
||||
ServerCertificate: context.aliyunCertId
|
||||
}
|
||||
if (certType === 'upload') {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
params.ServerCertificate = this.format(cert.crt.toString()),
|
||||
params.PrivateKey = this.format(cert.key.toString())
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
const ret = await client.request('SetDomainServerCertificate', params, requestOption)
|
||||
checkRet(ret)
|
||||
console.log('设置cdn证书成功', ret)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { AbstractPlugin } from '../../abstract-plugin/index.js'
|
||||
import Core from '@alicloud/pop-core'
|
||||
import dayjs from 'dayjs'
|
||||
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||
export class UploadToAliyunPlugin extends AbstractAliyunPlugin {
|
||||
/**
|
||||
* 插件定义
|
||||
* 名称
|
||||
* 入参
|
||||
* 出参
|
||||
*/
|
||||
static define () {
|
||||
return {
|
||||
name: 'updateToAliyun',
|
||||
label: '上传证书到阿里云',
|
||||
input: {
|
||||
name: {
|
||||
label: '证书名称'
|
||||
},
|
||||
accessProvider: {
|
||||
label: 'Access提供者',
|
||||
type: [String, Object],
|
||||
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
|
||||
options: 'accessProviders[type=aliyun]'
|
||||
}
|
||||
},
|
||||
output: {
|
||||
aliyunCertId: {
|
||||
type: String,
|
||||
desc: '上传成功后的阿里云CertId'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getClient (aliyunProvider) {
|
||||
return new Core({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: 'https://cas.aliyuncs.com',
|
||||
apiVersion: '2018-07-13'
|
||||
})
|
||||
}
|
||||
|
||||
async execute ({ accessProviders, cert, args, context }) {
|
||||
const { name, provider } = args
|
||||
const certName = name + '-' + dayjs().format('YYYYMMDDHHmmss')
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
Name: certName,
|
||||
Cert: this.format(cert.crt.toString()),
|
||||
Key: this.format(cert.key.toString())
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const accesseProvider = this.getAccessProvider(provider, accessProviders)
|
||||
const client = this.getClient(accesseProvider)
|
||||
const ret = await client.request('CreateUserCertificate', params, requestOption)
|
||||
|
||||
context.aliyunCertId = ret.CertId
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import { AbstractPlugin } from '../../abstract-plugin/index.js'
|
||||
import Core from '@alicloud/pop-core'
|
||||
import dayjs from 'dayjs'
|
||||
export class UploadCertToAliyunPlugin extends AbstractPlugin {
|
||||
getClient (aliyunProvider) {
|
||||
this.client = new Core({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: 'https://alidns.aliyuncs.com',
|
||||
apiVersion: '2015-01-09'
|
||||
})
|
||||
}
|
||||
|
||||
async execute ({ providers, cert, args, context }) {
|
||||
const { name, provider } = args
|
||||
const certName = name + '-' + dayjs().format('YYYYMMDDHHmmss')
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
Name: certName,
|
||||
Cert: cert.crt.toString(),
|
||||
Key: cert.key.toString()
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const client = this.getClient(providers[provider])
|
||||
const ret = await client.request('CreateUserCertificate', params, requestOption)
|
||||
|
||||
context.AliyunCertId = ret.CertId
|
||||
}
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
import util from './util.js'
|
||||
import log4js from 'log4js'
|
||||
import path from 'path'
|
||||
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
|
||||
const filename = path.join(util.getUserBasePath(), '/logs/certd.log')
|
||||
log4js.configure({
|
||||
appenders: { std: { type: 'stdout' }, file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename } },
|
||||
categories: { default: { appenders: ['file', 'std'], level: level } }
|
||||
appenders: { std: { type: 'stdout' } },
|
||||
categories: { default: { appenders: ['std'], level: 'info' } }
|
||||
})
|
||||
const logger = log4js.getLogger('certd')
|
||||
export default logger
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import pkg from 'chai'
|
||||
import { UploadToAliyunPlugin } from '../../src/aliyun/upload-to-aliyun/index.js'
|
||||
import options from '../options.js'
|
||||
import { Certd } from '@certd/certd'
|
||||
const { expect } = pkg
|
||||
describe('PluginUploadToAliyun', function () {
|
||||
it('#execute', async function () {
|
||||
const plugin = new UploadToAliyunPlugin()
|
||||
const certd = new Certd()
|
||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.club', 'docmirror.club'])
|
||||
const context = {}
|
||||
await plugin.execute({
|
||||
accessProviders: options.accessProviders,
|
||||
cert,
|
||||
args: { name: '上传证书到阿里云测试', provider: 'aliyun' },
|
||||
context
|
||||
})
|
||||
|
||||
console.log('context:', context)
|
||||
})
|
||||
})
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash'
|
||||
import optionsPrivate from './options.private.js'
|
||||
import optionsPrivate from '../../../test/options.private.mjs'
|
||||
const defaultOptions = {
|
||||
providers: {
|
||||
accessProviders: {
|
||||
aliyun: {
|
||||
providerType: 'aliyun',
|
||||
accessKeyId: '',
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import pkg from 'chai'
|
||||
import UploadCertToAliyun from '../../src/upload/upload-cert-to-aliyun/index.js'
|
||||
import options from '../options'
|
||||
import Certd from '@certd/certd'
|
||||
const { expect } = pkg
|
||||
describe('PluginUploadCertToAliyun', function () {
|
||||
it('#execute', function () {
|
||||
const plugin = new UploadCertToAliyun()
|
||||
const certd = new Certd()
|
||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
||||
plugin.execute({
|
||||
providers: options.providers,
|
||||
cert,
|
||||
args: { name: '上传证书到阿里云', provider: 'aliyun' }
|
||||
})
|
||||
})
|
||||
})
|
|
@ -333,6 +333,11 @@ date-format@^3.0.0:
|
|||
resolved "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
|
||||
integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
|
||||
|
||||
dayjs@^1.9.7:
|
||||
version "1.9.7"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.7.tgz#4b260bb17dceed2d5f29038dfee03c65a6786fc0"
|
||||
integrity sha512-IC877KBdMhBrCfBfJXHQlo0G8keZ0Opy7YIIq5QKtUbCuHMzim8S4PyiVK4YmihI3iOF9lhfUBW4AQWHTR5WHA==
|
||||
|
||||
debug@4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
|
||||
|
|
Loading…
Reference in New Issue