diff --git a/lib/command/check.ts b/lib/command/check.ts index ac3dd9d6c..ffc8fe634 100644 --- a/lib/command/check.ts +++ b/lib/command/check.ts @@ -7,7 +7,7 @@ import path from 'path'; import { loadConfig } from '../utils/config'; -import getProvider from '../utils/get-provider'; +import { getProvider } from '../provider'; import { errorHandler } from '../utils/error-helper'; class CheckCommand extends Command { diff --git a/lib/command/subscriptions.ts b/lib/command/subscriptions.ts index 57f7a8001..539560ed6 100644 --- a/lib/command/subscriptions.ts +++ b/lib/command/subscriptions.ts @@ -15,7 +15,7 @@ import { CommandConfig } from '../types'; import { loadConfig } from '../utils/config'; -import getProvider from '../utils/get-provider'; +import { getProvider } from '../provider'; import { errorHandler } from '../utils/error-helper'; import { formatSubscriptionUserInfo } from '../utils/subscription'; diff --git a/lib/gateway/server.ts b/lib/gateway/server.ts index f41075069..274a05901 100644 --- a/lib/gateway/server.ts +++ b/lib/gateway/server.ts @@ -10,7 +10,7 @@ import legacyUrl from 'url'; import got from 'got'; import { transformQxRewriteRemote } from '../utils/qx-helper'; -import getEngine from '../template'; +import { getEngine } from '../generator/template'; import { ArtifactConfig, CommandConfig, RemoteSnippet } from '../types'; import { getDownloadUrl } from '../utils'; import { loadRemoteSnippetList } from '../utils/remote-snippet'; diff --git a/lib/generate.ts b/lib/generate.ts index 2188d233a..9a7a1da20 100644 --- a/lib/generate.ts +++ b/lib/generate.ts @@ -1,57 +1,18 @@ 'use strict'; -import assert from 'assert'; -import Bluebird from 'bluebird'; -import chalk from 'chalk'; import fs from 'fs-extra'; -import _ from 'lodash'; import { Environment } from 'nunjucks'; import ora from 'ora'; import path from 'path'; import { logger } from '@surgio/logger'; +import { Artifact } from './generator/artifact'; -import getEngine from './template'; +import { getEngine } from './generator/template'; import { ArtifactConfig, - CommandConfig, - NodeTypeEnum, - PossibleNodeConfigType, - ProviderConfig, - RemoteSnippet, - SimpleNodeConfig, + CommandConfig, RemoteSnippet, } from './types'; -import { - getClashNodes, - getDownloadUrl, - getNodeNames, - getQuantumultNodes, - getV2rayNNodes, - getQuantumultXNodes, - getShadowsocksNodes, - getShadowsocksNodesJSON, - getShadowsocksrNodes, - getSurgeNodes, - getMellowNodes, - normalizeClashProxyGroupConfig, - toBase64, - toUrlSafeBase64, - getClashNodeNames, -} from './utils'; import { loadRemoteSnippetList } from './utils/remote-snippet'; -import { isIp, resolveDomain } from './utils/dns'; -import { - hkFilter, japanFilter, koreaFilter, - netflixFilter as defaultNetflixFilter, - singaporeFilter, - taiwanFilter, - usFilter, - validateFilter, - youtubePremiumFilter as defaultYoutubePremiumFilter, -} from './utils/filter'; -import getProvider from './utils/get-provider'; -import { prependFlag } from './utils/flag'; -import { NETWORK_CONCURRENCY } from './utils/constant'; -import Provider from './provider/Provider'; const spinner = ora(); @@ -69,7 +30,17 @@ async function run(config: CommandConfig): Promise { spinner.start(`正在生成规则 ${artifact.name}`); try { - const result = await generate(config, artifact, remoteSnippetList, templateEngine); + const artifactInstance = new Artifact(config, artifact, { + remoteSnippetList, + }); + + artifactInstance.on('initProvider:end', () => { + spinner.text = `已处理 Provider ${artifactInstance.initProgress}/${artifactInstance.providerNameList.length}...`; + }); + + await artifactInstance.init(); + + const result = artifactInstance.render(templateEngine); const destFilePath = path.join(config.output, artifact.name); if (artifact.destDir) { @@ -93,239 +64,13 @@ export async function generate( remoteSnippetList: ReadonlyArray, templateEngine: Environment, ): Promise { - const { - name: artifactName, - template, - customParams, - templateString, - } = artifact; - - assert(artifactName, '必须指定 artifact 的 name 属性'); - assert(artifact.provider, '必须指定 artifact 的 provider 属性'); - if (!templateString) { - assert(template, '必须指定 artifact 的 template 属性'); - } - - const gatewayConfig = config.gateway; - const gatewayHasToken: boolean = !!(gatewayConfig && gatewayConfig.accessToken); - const mainProviderName = artifact.provider; - const combineProviders = artifact.combineProviders || []; - const providerList = [mainProviderName].concat(combineProviders); - const nodeConfigListMap: Map> = new Map(); - const nodeList: PossibleNodeConfigType[] = []; - const nodeNameList: SimpleNodeConfig[] = []; - let customFilters: ProviderConfig['customFilters']; - let netflixFilter: ProviderConfig['netflixFilter']; - let youtubePremiumFilter: ProviderConfig['youtubePremiumFilter']; - let progress = 0; - - if (config.binPath && config.binPath.v2ray) { - config.binPath.vmess = config.binPath.v2ray; - } - - const providerMapper = async (providerName: string): Promise => { - const filePath = path.resolve(config.providerDir, `${providerName}.js`); - - if (!fs.existsSync(filePath)) { - throw new Error(`文件 ${filePath} 不存在`); - } - - let provider: Provider; - let nodeConfigList: ReadonlyArray; - - try { - provider = getProvider(providerName, require(filePath)); - } catch (err) { - err.message = `处理 ${chalk.cyan(providerName)} 时出现错误,相关文件 ${filePath} ,错误原因: ${err.message}`; - throw err; - } - - try { - nodeConfigList = await provider.getNodeList(); - } catch (err) { - err.message = `获取 ${chalk.cyan(providerName)} 节点时出现错误,相关文件 ${filePath} ,错误原因: ${err.message}`; - throw err; - } - - // Filter 仅使用第一个 Provider 中的定义 - if (providerName === mainProviderName) { - if (!netflixFilter) { - netflixFilter = provider.netflixFilter || defaultNetflixFilter; - } - if (!youtubePremiumFilter) { - youtubePremiumFilter = provider.youtubePremiumFilter || defaultYoutubePremiumFilter; - } - if (!customFilters) { - customFilters = { - ...config.customFilters, - ...provider.customFilters, - }; - } - } - - if ( - validateFilter(provider.nodeFilter) && - typeof provider.nodeFilter === 'object' && - provider.nodeFilter.supportSort - ) { - nodeConfigList = provider.nodeFilter.filter(nodeConfigList); - } - - nodeConfigList = await Bluebird.map(nodeConfigList, async nodeConfig => { - let isValid = false; - - if (nodeConfig.enable === false) { - return null; - } - - if (!provider.nodeFilter) { - isValid = true; - } else if (validateFilter(provider.nodeFilter)) { - isValid = typeof provider.nodeFilter === 'function' ? - provider.nodeFilter(nodeConfig) : - true; - } - - if (isValid) { - if (config.binPath && config.binPath[nodeConfig.type]) { - nodeConfig.binPath = config.binPath[nodeConfig.type]; - nodeConfig.localPort = provider.nextPort; - } - - nodeConfig.provider = provider; - nodeConfig.surgeConfig = config.surgeConfig; - - if (provider.renameNode) { - const newName = provider.renameNode(nodeConfig.nodeName); - - if (newName) { - nodeConfig.nodeName = newName; - } - } - - // 给节点名加国旗 - if (provider.addFlag) { - nodeConfig.nodeName = prependFlag(nodeConfig.nodeName); - } - - // TCP Fast Open - if (provider.tfo) { - nodeConfig.tfo = provider.tfo; - } - - // MPTCP - if (provider.mptcp) { - nodeConfig.mptcp = provider.mptcp; - } - - if ( - config.surgeConfig.resolveHostname && - !isIp(nodeConfig.hostname) && - [NodeTypeEnum.Vmess, NodeTypeEnum.Shadowsocksr].includes(nodeConfig.type) - ) { - try { - nodeConfig.hostnameIp = await resolveDomain(nodeConfig.hostname); - } /* istanbul ignore next */ catch (err) { - logger.warn(`${nodeConfig.hostname} 无法解析,将忽略该域名的解析结果`); - } - } - - return nodeConfig; - } - - return null; - }) - .filter(item => !!item); - - - nodeConfigListMap.set(providerName, nodeConfigList); - - spinner.text = `已处理 Provider ${++progress}/${providerList.length}...`; - }; - - await Bluebird.map(providerList, providerMapper, { concurrency: NETWORK_CONCURRENCY }); - - providerList.forEach(providerName => { - const nodeConfigList = nodeConfigListMap.get(providerName); - - nodeConfigList.forEach(nodeConfig => { - if (nodeConfig) { - nodeNameList.push({ - type: nodeConfig.type, - enable: nodeConfig.enable, - nodeName: nodeConfig.nodeName, - provider: nodeConfig.provider, - }); - nodeList.push(nodeConfig); - } - }); + const artifactInstance = new Artifact(config, artifact, { + remoteSnippetList, }); - const renderContext = { - proxyTestUrl: config.proxyTestUrl, - downloadUrl: getDownloadUrl(config.urlBase, artifactName, true, gatewayHasToken ? gatewayConfig.accessToken : undefined), - nodes: nodeList, - names: nodeNameList, - remoteSnippets: _.keyBy(remoteSnippetList, item => item.name), - nodeList, - provider: artifact.provider, - providerName: artifact.provider, - artifactName, - getDownloadUrl: (name: string) => getDownloadUrl(config.urlBase, name, true, gatewayHasToken ? gatewayConfig.accessToken : undefined), - getNodeNames, - getClashNodeNames, - getClashNodes, - getSurgeNodes, - getShadowsocksNodes, - getShadowsocksNodesJSON, - getShadowsocksrNodes, - getQuantumultNodes, - getV2rayNNodes, - getQuantumultXNodes, - getMellowNodes, - usFilter, - hkFilter, - japanFilter, - koreaFilter, - singaporeFilter, - taiwanFilter, - toUrlSafeBase64, - toBase64, - encodeURIComponent, - netflixFilter, - youtubePremiumFilter, - customFilters, - customParams: customParams || {}, - ...(artifact.proxyGroupModifier ? { - clashProxyConfig: { - Proxy: getClashNodes(nodeList), - 'Proxy Group': normalizeClashProxyGroupConfig( - nodeList, - { - usFilter, - hkFilter, - japanFilter, - koreaFilter, - singaporeFilter, - taiwanFilter, - netflixFilter, - youtubePremiumFilter, - ...customFilters, - }, - artifact.proxyGroupModifier, - { - proxyTestUrl: config.proxyTestUrl, - proxyTestInterval: config.proxyTestInterval, - }, - ), - }, - } : {}), - }; + await artifactInstance.init(); - if (templateString) { - return templateEngine.renderString(templateString, renderContext); - } - return templateEngine.render(`${template}.tpl`, renderContext); + return artifactInstance.render(templateEngine); } export default async function(config: CommandConfig): Promise { diff --git a/test/snapshots/template.test.ts.md b/lib/generator/__tests__/__snapshots__/template.test.ts.md similarity index 100% rename from test/snapshots/template.test.ts.md rename to lib/generator/__tests__/__snapshots__/template.test.ts.md diff --git a/test/snapshots/template.test.ts.snap b/lib/generator/__tests__/__snapshots__/template.test.ts.snap similarity index 100% rename from test/snapshots/template.test.ts.snap rename to lib/generator/__tests__/__snapshots__/template.test.ts.snap diff --git a/test/template.test.ts b/lib/generator/__tests__/template.test.ts similarity index 92% rename from test/template.test.ts rename to lib/generator/__tests__/template.test.ts index e029b03d3..8180c8ed7 100644 --- a/test/template.test.ts +++ b/lib/generator/__tests__/template.test.ts @@ -1,10 +1,11 @@ // tslint:disable:no-expression-statement import test from 'ava'; import fs from 'fs-extra'; -import path from 'path'; -import getEngine from '../lib/template'; +import { join } from 'path'; +import { getEngine } from '../template'; const templateEngine = getEngine(process.cwd(), 'https://example.com/'); +const assetDir = join(__dirname, '../../../test/asset/'); test('clash #1', t => { const body = `{{ str | patchYamlArray }}`; @@ -96,7 +97,7 @@ test('quantumultx filter 1', t => { test('quantumultx filter 2', t => { const body = `{{ str | quantumultx }}`; - const str = fs.readFileSync(path.join(__dirname, './asset/surge-script-list.txt'), { encoding: 'utf8' }); + const str = fs.readFileSync(join(assetDir, 'surge-script-list.txt'), { encoding: 'utf8' }); const result = templateEngine.renderString(body, { str, }); @@ -143,7 +144,7 @@ test('spaces in string', t => { }); test('ForeignMedia', t => { - const str = fs.readFileSync(path.join(__dirname, './asset/ForeignMedia.list'), { encoding: 'utf8' }); + const str = fs.readFileSync(join(assetDir, 'ForeignMedia.list'), { encoding: 'utf8' }); t.snapshot(templateEngine.renderString(`{{ str | quantumultx }}`, { str, diff --git a/lib/generator/artifact.ts b/lib/generator/artifact.ts new file mode 100644 index 000000000..8f2f3b6ab --- /dev/null +++ b/lib/generator/artifact.ts @@ -0,0 +1,344 @@ +import { logger } from '@surgio/logger'; +import assert from 'assert'; +import Bluebird from 'bluebird'; +import chalk from 'chalk'; +import fs from 'fs-extra'; +import _ from 'lodash'; +import { Environment } from 'nunjucks'; +import path from 'path'; +import { EventEmitter } from 'events'; + +import { getProvider } from '../provider'; +import { + ArtifactConfig, + CommandConfig, + NodeTypeEnum, + PossibleNodeConfigType, + ProviderConfig, RemoteSnippet, + SimpleNodeConfig, +} from '../types'; +import { + getClashNodeNames, + getClashNodes, + getDownloadUrl, getMellowNodes, + getNodeNames, getQuantumultNodes, getQuantumultXNodes, + getShadowsocksNodes, getShadowsocksNodesJSON, getShadowsocksrNodes, + getSurgeNodes, getV2rayNNodes, normalizeClashProxyGroupConfig, toBase64, toUrlSafeBase64, +} from '../utils'; +import { NETWORK_CONCURRENCY } from '../utils/constant'; +import { isIp, resolveDomain } from '../utils/dns'; +import { + hkFilter, japanFilter, koreaFilter, + netflixFilter as defaultNetflixFilter, singaporeFilter, taiwanFilter, usFilter, validateFilter, + youtubePremiumFilter as defaultYoutubePremiumFilter, +} from '../utils/filter'; +import { prependFlag } from '../utils/flag'; + +export interface ArtifactOptions { + readonly remoteSnippetList?: ReadonlyArray; +} + +export class Artifact extends EventEmitter { + public initProgress: number = 0; + public providerNameList: ReadonlyArray; + public nodeConfigListMap: Map> = new Map(); + public providerMap: Map> = new Map(); + public nodeList: PossibleNodeConfigType[] = []; // tslint:disable-line:readonly-array + public nodeNameList: SimpleNodeConfig[] = []; // tslint:disable-line:readonly-array + + private customFilters?: ProviderConfig['customFilters']; + private netflixFilter?: ProviderConfig['netflixFilter']; + private youtubePremiumFilter?: ProviderConfig['youtubePremiumFilter']; + + constructor( + public surgioConfig: CommandConfig, + public artifact: ArtifactConfig, + public options: ArtifactOptions = {} + ) { + super(); + + const { + name: artifactName, + template, + templateString, + } = artifact; + + assert(artifactName, '必须指定 artifact 的 name 属性'); + assert(artifact.provider, '必须指定 artifact 的 provider 属性'); + if (!templateString) { + assert(template, '必须指定 artifact 的 template 属性'); + } + + const mainProviderName = artifact.provider; + const combineProviders = artifact.combineProviders || []; + + this.providerNameList = [mainProviderName].concat(combineProviders); + } + + public get isReady(): boolean { + return this.initProgress === this.providerNameList.length; + } + + public get renderContext(): any { + const config = this.surgioConfig; + const gatewayConfig = config.gateway; + const gatewayHasToken = !!(gatewayConfig && gatewayConfig.accessToken); + const { + name: artifactName, + customParams, + } = this.artifact; + // tslint:disable-next-line:no-this-assignment + const { + nodeList, + nodeNameList, + netflixFilter, + youtubePremiumFilter, + customFilters, + } = this; + const remoteSnippets = _.keyBy(this.options.remoteSnippetList || [], item => item.name); + + return { + proxyTestUrl: config.proxyTestUrl, + downloadUrl: getDownloadUrl(config.urlBase, artifactName, true, gatewayHasToken ? gatewayConfig.accessToken : undefined), + nodes: nodeList, + names: nodeNameList, + remoteSnippets, + nodeList, + provider: this.artifact.provider, + providerName: this.artifact.provider, + artifactName, + getDownloadUrl: (name: string) => getDownloadUrl(config.urlBase, name, true, gatewayHasToken ? gatewayConfig.accessToken : undefined), + getNodeNames, + getClashNodeNames, + getClashNodes, + getSurgeNodes, + getShadowsocksNodes, + getShadowsocksNodesJSON, + getShadowsocksrNodes, + getQuantumultNodes, + getV2rayNNodes, + getQuantumultXNodes, + getMellowNodes, + usFilter, + hkFilter, + japanFilter, + koreaFilter, + singaporeFilter, + taiwanFilter, + toUrlSafeBase64, + toBase64, + encodeURIComponent, + netflixFilter, + youtubePremiumFilter, + customFilters, + customParams: customParams || {}, + ...(this.artifact.proxyGroupModifier ? { + clashProxyConfig: { + Proxy: getClashNodes(nodeList), + 'Proxy Group': normalizeClashProxyGroupConfig( + nodeList, + { + usFilter, + hkFilter, + japanFilter, + koreaFilter, + singaporeFilter, + taiwanFilter, + netflixFilter, + youtubePremiumFilter, + ...customFilters, + }, + this.artifact.proxyGroupModifier, + { + proxyTestUrl: config.proxyTestUrl, + proxyTestInterval: config.proxyTestInterval, + }, + ), + }, + } : {}), + }; + } + + public async init(): Promise { + if (this.isReady) { + throw new Error('Artifact 已经初始化完成'); + } + + this.emit('initArtifact:start', { artifact: this.artifact }); + + await Bluebird.map( + this.providerNameList, + this.providerMapper.bind(this), + { concurrency: NETWORK_CONCURRENCY }); + + this.providerNameList.forEach(providerName => { + const nodeConfigList = this.nodeConfigListMap.get(providerName); + + nodeConfigList.forEach(nodeConfig => { + if (nodeConfig) { + this.nodeNameList.push({ + type: nodeConfig.type, + enable: nodeConfig.enable, + nodeName: nodeConfig.nodeName, + provider: nodeConfig.provider, + }); + this.nodeList.push(nodeConfig); + } + }); + }); + + this.emit('initArtifact:end', { artifact: this.artifact }); + } + + public render(templateEngine: Environment): string { + if (!this.isReady) { + throw new Error('Artifact 还未初始化'); + } + + const renderContext = this.renderContext; + const { + templateString, + template, + } = this.artifact; + const result = templateString + ? templateEngine.renderString(templateString, renderContext) + : templateEngine.render(`${template}.tpl`, renderContext); + + this.emit('renderArtifact', { artifact: this.artifact, result }); + + return result; + } + + private async providerMapper(providerName: string): Promise { + const config = this.surgioConfig; + const mainProviderName = this.artifact.provider; + const filePath = path.resolve(config.providerDir, `${providerName}.js`); + + this.emit('initProvider:start', { + artifact: this.artifact, + providerName, + }); + + if (!fs.existsSync(filePath)) { + throw new Error(`文件 ${filePath} 不存在`); + } + + let provider: ReturnType; + let nodeConfigList: ReadonlyArray; + + try { + provider = getProvider(providerName, require(filePath)); + this.providerMap.set(providerName, provider); + } catch (err) { + err.message = `处理 ${chalk.cyan(providerName)} 时出现错误,相关文件 ${filePath} ,错误原因: ${err.message}`; + throw err; + } + + try { + nodeConfigList = await provider.getNodeList(); + } catch (err) { + err.message = `获取 ${chalk.cyan(providerName)} 节点时出现错误,相关文件 ${filePath} ,错误原因: ${err.message}`; + throw err; + } + + // Filter 仅使用第一个 Provider 中的定义 + if (providerName === mainProviderName) { + if (!this.netflixFilter) { + this.netflixFilter = provider.netflixFilter || defaultNetflixFilter; + } + if (!this.youtubePremiumFilter) { + this.youtubePremiumFilter = provider.youtubePremiumFilter || defaultYoutubePremiumFilter; + } + if (!this.customFilters) { + this.customFilters = { + ...config.customFilters, + ...provider.customFilters, + }; + } + } + + if ( + validateFilter(provider.nodeFilter) && + typeof provider.nodeFilter === 'object' && + provider.nodeFilter.supportSort + ) { + nodeConfigList = provider.nodeFilter.filter(nodeConfigList); + } + + nodeConfigList = await Bluebird.map(nodeConfigList, async nodeConfig => { + let isValid = false; + + if (nodeConfig.enable === false) { + return null; + } + + if (!provider.nodeFilter) { + isValid = true; + } else if (validateFilter(provider.nodeFilter)) { + isValid = typeof provider.nodeFilter === 'function' ? + provider.nodeFilter(nodeConfig) : + true; + } + + if (isValid) { + if (config.binPath && config.binPath[nodeConfig.type]) { + nodeConfig.binPath = config.binPath[nodeConfig.type]; + nodeConfig.localPort = provider.nextPort; + } + + nodeConfig.provider = provider; + nodeConfig.surgeConfig = config.surgeConfig; + + if (provider.renameNode) { + const newName = provider.renameNode(nodeConfig.nodeName); + + if (newName) { + nodeConfig.nodeName = newName; + } + } + + // 给节点名加国旗 + if (provider.addFlag) { + nodeConfig.nodeName = prependFlag(nodeConfig.nodeName); + } + + // TCP Fast Open + if (provider.tfo) { + nodeConfig.tfo = provider.tfo; + } + + // MPTCP + if (provider.mptcp) { + nodeConfig.mptcp = provider.mptcp; + } + + if ( + config.surgeConfig.resolveHostname && + !isIp(nodeConfig.hostname) && + [NodeTypeEnum.Vmess, NodeTypeEnum.Shadowsocksr].includes(nodeConfig.type) + ) { + try { + nodeConfig.hostnameIp = await resolveDomain(nodeConfig.hostname); + } /* istanbul ignore next */ catch (err) { + logger.warn(`${nodeConfig.hostname} 无法解析,将忽略该域名的解析结果`); + } + } + + return nodeConfig; + } + + return null; + }) + .filter(item => !!item); + + + this.nodeConfigListMap.set(providerName, nodeConfigList); + this.initProgress++; + + this.emit('initProvider:end', { + artifact: this.artifact, + providerName, + provider, + }); + }; +} diff --git a/lib/template.ts b/lib/generator/template.ts similarity index 95% rename from lib/template.ts rename to lib/generator/template.ts index 3094e1f5d..ae74a1822 100644 --- a/lib/template.ts +++ b/lib/generator/template.ts @@ -3,14 +3,14 @@ import { JsonObject } from 'type-fest'; import YAML from 'yaml'; import { URL } from 'url'; -import { decodeStringList, toBase64 } from './utils'; +import { decodeStringList, toBase64 } from '../utils'; import { MELLOW_UNSUPPORTED_RULE, QUANTUMULT_X_SUPPORTED_RULE, CLASH_SUPPORTED_RULE, -} from './utils/constant'; +} from '../utils/constant'; -export default function getEngine(templateDir: string, publicUrl: string): nunjucks.Environment { +export function getEngine(templateDir: string, publicUrl: string): nunjucks.Environment { const engine = nunjucks.configure(templateDir, { autoescape: false, }); diff --git a/lib/provider/ClashProvider.ts b/lib/provider/ClashProvider.ts index e8e052c14..4a09f6a2c 100644 --- a/lib/provider/ClashProvider.ts +++ b/lib/provider/ClashProvider.ts @@ -12,7 +12,8 @@ import { NodeTypeEnum, ShadowsocksNodeConfig, ShadowsocksrNodeConfig, - SnellNodeConfig, SubscriptionUserinfo, + SnellNodeConfig, + SubscriptionUserinfo, VmessNodeConfig, } from '../types'; import { parseSubscriptionUserInfo } from '../utils/subscription'; diff --git a/test/provider/ClashProvider.test.ts b/lib/provider/__tests__/ClashProvider.test.ts similarity index 96% rename from test/provider/ClashProvider.test.ts rename to lib/provider/__tests__/ClashProvider.test.ts index 23e78a5b2..40a319e6a 100644 --- a/test/provider/ClashProvider.test.ts +++ b/lib/provider/__tests__/ClashProvider.test.ts @@ -1,6 +1,6 @@ import test from 'ava'; -import ClashProvider, { getClashSubscription } from '../../lib/provider/ClashProvider'; -import { NodeTypeEnum, SupportProviderEnum } from '../../lib/types'; +import ClashProvider, { getClashSubscription } from '../ClashProvider'; +import { NodeTypeEnum, SupportProviderEnum } from '../../types'; test('ClashProvider', async t => { const provider = new ClashProvider('test', { diff --git a/test/provider/ShadowsocksSubscribeProvider.test.ts b/lib/provider/__tests__/ShadowsocksSubscribeProvider.test.ts similarity index 92% rename from test/provider/ShadowsocksSubscribeProvider.test.ts rename to lib/provider/__tests__/ShadowsocksSubscribeProvider.test.ts index a729f887e..428789070 100644 --- a/test/provider/ShadowsocksSubscribeProvider.test.ts +++ b/lib/provider/__tests__/ShadowsocksSubscribeProvider.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; -import { getShadowsocksSubscription } from '../../lib/provider/ShadowsocksSubscribeProvider'; -import { NodeTypeEnum } from '../../lib/types'; +import { getShadowsocksSubscription } from '../ShadowsocksSubscribeProvider'; +import { NodeTypeEnum } from '../../types'; test('getShadowsocksSubscription with udp', async t => { const { nodeList } = await getShadowsocksSubscription('http://example.com/test-ss-sub.txt', true); diff --git a/test/provider/ShadowsocksrSubscribeProvider.test.ts b/lib/provider/__tests__/ShadowsocksrSubscribeProvider.test.ts similarity index 87% rename from test/provider/ShadowsocksrSubscribeProvider.test.ts rename to lib/provider/__tests__/ShadowsocksrSubscribeProvider.test.ts index 63ca5cf4e..c3149a73b 100644 --- a/test/provider/ShadowsocksrSubscribeProvider.test.ts +++ b/lib/provider/__tests__/ShadowsocksrSubscribeProvider.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; -import { NodeTypeEnum } from '../../lib/types'; -import { getShadowsocksrSubscription } from '../../lib/provider/ShadowsocksrSubscribeProvider'; +import { NodeTypeEnum } from '../../types'; +import { getShadowsocksrSubscription } from '../ShadowsocksrSubscribeProvider'; test('getShadowsocksrSubscription', async t => { const { nodeList } = await getShadowsocksrSubscription('http://example.com/test-ssr-sub.txt?v=1', false); diff --git a/lib/utils/get-provider.ts b/lib/provider/index.ts similarity index 54% rename from lib/utils/get-provider.ts rename to lib/provider/index.ts index e37df1867..286cdedae 100644 --- a/lib/utils/get-provider.ts +++ b/lib/provider/index.ts @@ -1,15 +1,13 @@ -import assert from "assert"; - -import BlackSSLProvider from '../provider/BlackSSLProvider'; -import ClashProvider from '../provider/ClashProvider'; -import CustomProvider from '../provider/CustomProvider'; -import ShadowsocksJsonSubscribeProvider from '../provider/ShadowsocksJsonSubscribeProvider'; -import ShadowsocksrSubscribeProvider from '../provider/ShadowsocksrSubscribeProvider'; -import ShadowsocksSubscribeProvider from '../provider/ShadowsocksSubscribeProvider'; -import V2rayNSubscribeProvider from '../provider/V2rayNSubscribeProvider'; import { SupportProviderEnum } from '../types'; - -export default function(name: string, config: any): BlackSSLProvider|ShadowsocksJsonSubscribeProvider|ShadowsocksSubscribeProvider|CustomProvider|V2rayNSubscribeProvider|ShadowsocksrSubscribeProvider|ClashProvider { +import BlackSSLProvider from './BlackSSLProvider'; +import ClashProvider from './ClashProvider'; +import CustomProvider from './CustomProvider'; +import ShadowsocksJsonSubscribeProvider from './ShadowsocksJsonSubscribeProvider'; +import ShadowsocksrSubscribeProvider from './ShadowsocksrSubscribeProvider'; +import ShadowsocksSubscribeProvider from './ShadowsocksSubscribeProvider'; +import V2rayNSubscribeProvider from './V2rayNSubscribeProvider'; + +export function getProvider(name: string, config: any): BlackSSLProvider|ShadowsocksJsonSubscribeProvider|ShadowsocksSubscribeProvider|CustomProvider|V2rayNSubscribeProvider|ShadowsocksrSubscribeProvider|ClashProvider { switch (config.type) { case SupportProviderEnum.BlackSSL: return new BlackSSLProvider(name, config); diff --git a/lib/utils/config.ts b/lib/utils/config.ts index cb7224704..dc7db1db3 100644 --- a/lib/utils/config.ts +++ b/lib/utils/config.ts @@ -63,6 +63,10 @@ export const normalizeConfig = (cwd: string, userConfig: Partial) config.publicUrl = '/'; } + if (config.binPath && config.binPath.v2ray) { + config.binPath.vmess = config.binPath.v2ray; + } + return config; }; diff --git a/package.json b/package.json index 8dcf1e6d3..52feab9c7 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,8 @@ "js" ], "files": [ - "test/**/*.test.ts" + "test/**/*.test.ts", + "lib/**/*.test.ts" ], "require": [ "ts-node/register", diff --git a/test/cli.test.ts b/test/cli.test.ts index f7f2a660c..01d616298 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -9,7 +9,7 @@ const cli = path.join(__dirname, '../bin/surgio.js'); const fixture = path.join(__dirname, './fixture'); const resolve = p => path.join(fixture, p); -test('cli works', async t => { +test.only('cli works', async t => { await coffee.fork(cli, ['generate', '-h'], { cwd: resolve('plain'), }) diff --git a/tsconfig.json b/tsconfig.json index 9ffb5583a..85679692a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -45,7 +45,7 @@ ], "typeRoots": ["node_modules/@types"] }, - "include": ["lib/**/*.ts", "test/**.ts"], + "include": ["lib/**/*.ts", "test/**/*.ts"], "exclude": ["node_modules/**", "**/*.test.ts"], "compileOnSave": false }