From 278379d3008e0c3700c608353988285cecb2d20c Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 02:03:39 +0800
Subject: [PATCH 01/22] chore: update readme
---
README.md | 14 ++++++++++----
packages/plugin-eval-addons/package.json | 2 +-
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 3ee4da6c69..ad5c1e0a7b 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,9 @@ Koishi 是一个在 [Node.js](https://nodejs.org/) 环境下运行的机器人
这个项目的名字和图标来源于东方 Project 中的角色古明地恋 (Komeiji Koishi)。
-![demo](./.github/demo.png)
+
+
+
## 安装
@@ -85,7 +87,7 @@ koishi-plugin-common 包含了一些常用功能,它们在你使用 koishi 库
- 输出聊天记录到控制台
- 欢迎入群,复读,处理申请,频率限制,自定义回复……
-### [koishi-plugin-eval](https://koishi.js.org/plugins/eval.html) [![npm](https://img.shields.io/npm/v/koishi-plugin-eval/next?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-eval)
+### [koishi-plugin-eval](https://koishi.js.org/plugins/eval.html) [![npm](https://img.shields.io/npm/v/koishi-plugin-eval?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-eval)
koishi-plugin-eval 允许用户直接使用机器人执行脚本。它利用了 Node.js 的 [vm](https://nodejs.org/api/vm.html) 和 [worker_threads](https://nodejs.org/api/worker_threads.html) 模块,在保护执行安全的前提下能够获得较快的响应速度。同时,插件还提供了一些内置的 API 供用户调用,结合教学功能可以在客户端实现复杂的行为。
@@ -99,14 +101,18 @@ koishi-plugin-eval-addons 在前一个插件的基础上,允许用户编写自
### [koishi-plugin-monitor](./packages/plugin-monitor) [![npm](https://img.shields.io/npm/v/koishi-plugin-monitor/next?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-monitor)
-### [koishi-plugin-puppeteer](./packages/plugin-puppeteer) [![npm](https://img.shields.io/npm/v/koishi-plugin-puppeteer?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-puppeteer)
+### [koishi-plugin-puppeteer](https://koishi.js.org/plugins/puppeteer.html) [![npm](https://img.shields.io/npm/v/koishi-plugin-puppeteer?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-puppeteer)
-### [koishi-plugin-recorder](./packages/plugin-recorder) [![npm](https://img.shields.io/npm/v/koishi-plugin-recorder/next?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-recorder)
+koishi-plugin-puppeteer 本身提供了网页截图(shot)指令和 TeX 渲染指令(tex),同时也封装了一系列与网页进行交互的接口。利用这些接口我们可以开发更多以渲染图片为基础的插件,如 koishi-plugin-chess 等。
### [koishi-plugin-rss](./packages/plugin-rss) [![npm](https://img.shields.io/npm/v/koishi-plugin-rss?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-rss)
+koishi-plugin-rss 提供了 [RSS](https://en.wikipedia.org/wiki/RSS) 支持,允许不同的群订阅不同的 RSS 信息源并实时进行通知。
+
### [koishi-plugin-schedule](./packages/plugin-schedule) [![npm](https://img.shields.io/npm/v/koishi-plugin-schedule?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-schedule)
+koishi-plugin-schedule 允许用户设置定时任务并执行。这些计划任务会被存储在数据库中,即使重启机器人也能继续工作。
+
### [koishi-plugin-status](./packages/plugin-status) [![npm](https://img.shields.io/npm/v/koishi-plugin-status/next?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-status)
### [koishi-plugin-teach](./packages/plugin-teach) [![npm](https://img.shields.io/npm/v/koishi-plugin-teach?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-teach)
diff --git a/packages/plugin-eval-addons/package.json b/packages/plugin-eval-addons/package.json
index 79cba96065..4379a5c068 100644
--- a/packages/plugin-eval-addons/package.json
+++ b/packages/plugin-eval-addons/package.json
@@ -1,6 +1,6 @@
{
"name": "koishi-plugin-eval-addons",
- "version": "1.0.0-beta.8",
+ "version": "1.0.0-beta.9",
"description": "Execute JavaScript in Koishi",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
From a01be705703625a3c7124c4e26b278ef4c445117 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 14:47:53 +0800
Subject: [PATCH 02/22] feat(cli): hint for exit command
---
packages/koishi/src/run.ts | 16 ++++++++++++++--
packages/koishi/src/worker.ts | 32 ++++++++++++++++++++++++++++----
2 files changed, 42 insertions(+), 6 deletions(-)
diff --git a/packages/koishi/src/run.ts b/packages/koishi/src/run.ts
index 66812ecf1f..0e307c71ca 100644
--- a/packages/koishi/src/run.ts
+++ b/packages/koishi/src/run.ts
@@ -26,6 +26,13 @@ process.on('SIGINT', () => {
}
})
+interface Message {
+ type: 'start' | 'exit'
+ payload: any
+}
+
+let payload: any
+
function createWorker(options: WorkerOptions) {
child = fork(resolve(__dirname, 'worker'), [], {
execArgv: options['--'],
@@ -33,9 +40,14 @@ function createWorker(options: WorkerOptions) {
let started = false
- child.on('message', (data) => {
- if (data === 'start') {
+ child.on('message', (message: Message) => {
+ if (message.type === 'start') {
started = true
+ if (payload) {
+ child.send({ type: 'send', payload })
+ }
+ } else if (message.type === 'exit') {
+ payload = message.payload
}
})
diff --git a/packages/koishi/src/worker.ts b/packages/koishi/src/worker.ts
index 6fe3061c78..fe56ad8f7d 100644
--- a/packages/koishi/src/worker.ts
+++ b/packages/koishi/src/worker.ts
@@ -1,6 +1,6 @@
import { App, AppOptions, Context, Plugin } from 'koishi-core'
import { resolve, dirname } from 'path'
-import { Logger } from 'koishi-utils'
+import { Logger, noop } from 'koishi-utils'
import { performance } from 'perf_hooks'
import { yellow } from 'kleur'
import 'koishi-adapter-cqhttp'
@@ -105,14 +105,38 @@ if (config.logLevel && !process.env.KOISHI_LOG_LEVEL) {
Logger.baseLevel = config.logLevel
}
+interface Message {
+ type: 'send'
+ payload: any
+}
+
+process.on('message', (data: Message) => {
+ if (data.type === 'send') {
+ const { groupId, userId, selfId, message } = data.payload
+ const bot = app.bots[selfId]
+ if (groupId) {
+ bot.sendGroupMsg(groupId, message)
+ } else {
+ bot.sendPrivateMsg(userId, message)
+ }
+ }
+})
+
const app = new App(config)
app.command('exit', '停止机器人运行', { authority: 4 })
.option('restart', '-r 重新启动')
.shortcut('关机', { prefix: true })
.shortcut('重启', { prefix: true, options: { restart: true } })
- .action(({ options }) => {
- process.exit(options.restart ? 514 : 0)
+ .action(async ({ options, session }) => {
+ const { groupId, userId, selfId } = session
+ if (!options.restart) {
+ await session.$send('正在关机……').catch(noop)
+ process.exit()
+ }
+ process.send({ type: 'exit', payload: { groupId, userId, selfId, message: '已成功重启。' } })
+ await session.$send(`正在重启……`).catch(noop)
+ process.exit(514)
})
if (Array.isArray(config.plugins)) {
@@ -131,5 +155,5 @@ app.start().then(() => {
const time = Math.max(0, performance.now() - +process.env.KOISHI_START_TIME).toFixed()
logger.success(`bot started successfully in ${time} ms.`)
- process.send('start')
+ process.send({ type: 'start' })
}, handleException)
From 008ae389ab9fc3f6aebe5af9dd6dbbd974b7725b Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 17:51:57 +0800
Subject: [PATCH 03/22] fix(eval-addons): fix namespace sandbox escape
---
packages/plugin-eval-addons/src/index.ts | 7 ++++---
packages/plugin-eval-addons/src/worker.ts | 16 +++++++++-------
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/packages/plugin-eval-addons/src/index.ts b/packages/plugin-eval-addons/src/index.ts
index 655e8825c7..e30d667597 100644
--- a/packages/plugin-eval-addons/src/index.ts
+++ b/packages/plugin-eval-addons/src/index.ts
@@ -5,18 +5,19 @@ import { safeLoad } from 'js-yaml'
import { promises as fs } from 'fs'
import { attachTraps, FieldOptions } from 'koishi-plugin-eval'
import Git, { CheckRepoActions } from 'simple-git'
+import { AddonWorkerConfig } from './worker'
const logger = new Logger('addon')
-type AddonConfig = Config
+export interface Config extends AddonMainConfig, AddonWorkerConfig {}
-export interface Config {
+export interface AddonMainConfig {
gitRemote?: string
exclude?: RegExp
}
declare module 'koishi-plugin-eval/dist/main' {
- interface MainConfig extends AddonConfig {}
+ interface MainConfig extends AddonMainConfig {}
}
interface OptionManifest extends OptionConfig {
diff --git a/packages/plugin-eval-addons/src/worker.ts b/packages/plugin-eval-addons/src/worker.ts
index 74437becca..b773dfa899 100644
--- a/packages/plugin-eval-addons/src/worker.ts
+++ b/packages/plugin-eval-addons/src/worker.ts
@@ -1,5 +1,5 @@
import { config, context, internal, WorkerAPI, Context, response, mapDirectory, formatError } from 'koishi-plugin-eval/dist/worker'
-import { promises, readFileSync } from 'fs'
+import { promises as fs, readFileSync } from 'fs'
import { resolve, posix, dirname } from 'path'
import { Logger, Time, CQCode, Random } from 'koishi-utils'
import json5 from 'json5'
@@ -9,10 +9,12 @@ const logger = new Logger('addon')
const { SourceTextModule, SyntheticModule } = require('vm')
+export interface AddonWorkerConfig {
+ moduleRoot?: string
+}
+
declare module 'koishi-plugin-eval/dist/worker' {
- interface WorkerConfig {
- moduleRoot?: string
- }
+ interface WorkerConfig extends AddonWorkerConfig {}
interface WorkerData {
addonNames: string[]
@@ -120,7 +122,7 @@ async function loadSource(path: string) {
for (const postfix of suffixes) {
try {
const target = path + postfix
- return [await promises.readFile(resolve(config.moduleRoot, target), 'utf8'), target]
+ return [await fs.readFile(resolve(config.moduleRoot, target), 'utf8'), target]
} catch {}
}
throw new Error(`cannot load source file "${path}"`)
@@ -142,7 +144,7 @@ async function createModule(path: string) {
await module.evaluate()
if (!path.includes('/')) {
- internal.setGlobal(path, module.namespace)
+ internal.setGlobal(path, internal.decontextify(module.namespace))
}
return module
}
@@ -158,5 +160,5 @@ export async function evaluate(path: string) {
export default Promise.all(config.addonNames.map(evaluate)).then(() => {
response.commands = Object.keys(commandMap)
mapDirectory('koishi/utils/', require.resolve('koishi-utils'))
- internal.setGlobal('utils', modules['koishi/utils.ts'].namespace)
+ internal.setGlobal('utils', internal.decontextify(modules['koishi/utils.ts'].namespace))
})
From 3354b839e34bbf024c1f5dd8a14cb8c147b53240 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 17:55:52 +0800
Subject: [PATCH 04/22] feat(eval-addons): empty module should not be exposed
to global
---
packages/plugin-eval-addons/src/worker.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-eval-addons/src/worker.ts b/packages/plugin-eval-addons/src/worker.ts
index b773dfa899..d2d7012c55 100644
--- a/packages/plugin-eval-addons/src/worker.ts
+++ b/packages/plugin-eval-addons/src/worker.ts
@@ -144,7 +144,10 @@ async function createModule(path: string) {
await module.evaluate()
if (!path.includes('/')) {
- internal.setGlobal(path, internal.decontextify(module.namespace))
+ const namespace = internal.decontextify(module.namespace)
+ if (Object.keys(namespace).length) {
+ internal.setGlobal(path, namespace)
+ }
}
return module
}
From c991fd5e4f8e6b807f9e397e774a592a388284b8 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 20:31:42 +0800
Subject: [PATCH 05/22] typings: adjust typings
---
packages/koishi-core/src/app.ts | 4 ++--
packages/koishi-utils/src/logger.ts | 12 +++++-------
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/packages/koishi-core/src/app.ts b/packages/koishi-core/src/app.ts
index 1cb51f8404..af7300ed38 100644
--- a/packages/koishi-core/src/app.ts
+++ b/packages/koishi-core/src/app.ts
@@ -46,8 +46,8 @@ export class App extends Context {
_commands: Command[]
_commandMap: Record
_hooks: Record any][]>
- _userCache: LruCache>>
- _groupCache: LruCache>>
+ _userCache: LruCache, Promise>>
+ _groupCache: LruCache, Promise>>
private _nameRE: RegExp
private _prefixRE: RegExp
diff --git a/packages/koishi-utils/src/logger.ts b/packages/koishi-utils/src/logger.ts
index 2b270eb811..90563b2d27 100644
--- a/packages/koishi-utils/src/logger.ts
+++ b/packages/koishi-utils/src/logger.ts
@@ -14,6 +14,10 @@ const instances: Record = {}
type LogFunction = (format: any, ...param: any[]) => void
+type LogType = 'success' | 'error' | 'info' | 'warn' | 'debug'
+
+export interface Logger extends Record {}
+
export class Logger {
static readonly SUCCESS = 1
static readonly ERROR = 1
@@ -44,12 +48,6 @@ export class Logger {
private code: number
private displayName: string
- public success: LogFunction
- public error: LogFunction
- public info: LogFunction
- public warn: LogFunction
- public debug: LogFunction
-
constructor(private name: string, private showDiff = false) {
if (name in instances) return instances[name]
let hash = 0
@@ -71,7 +69,7 @@ export class Logger {
return Logger.color(this.code, value, decoration)
}
- private createMethod(name: string, prefix: string, minLevel: number) {
+ private createMethod(name: LogType, prefix: string, minLevel: number) {
this[name] = (...args: [any, ...any[]]) => {
if (this.level < minLevel) return
process.stderr.write(prefix + this.displayName + this.format(...args) + '\n')
From fbb372f7dd12f0647824663316c8f10258116fc1 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Sun, 30 Aug 2020 23:51:27 +0800
Subject: [PATCH 06/22] feat(cli): support ws-reverse
---
packages/koishi/src/init.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/koishi/src/init.ts b/packages/koishi/src/init.ts
index 3a6c5d5206..9f47724cbb 100644
--- a/packages/koishi/src/init.ts
+++ b/packages/koishi/src/init.ts
@@ -14,12 +14,18 @@ async function createConfig() {
choices: [
{ title: 'HTTP', value: 'cqhttp:http' },
{ title: 'WebSocket', value: 'cqhttp:ws' },
+ { title: 'WebSocket Reverse', value: 'cqhttp:ws-reverse' },
],
}, {
name: 'port',
- type: (_, data) => data.type === 'cqhttp:http' ? 'number' : null,
+ type: (_, data) => data.type === 'cqhttp:http' || data.type === 'cqhttp:ws-reverse' ? 'number' : null,
message: 'Koishi Port',
initial: 8080,
+ }, {
+ name: 'path',
+ type: (_, data) => data.type === 'cqhttp:http' || data.type === 'cqhttp:ws-reverse' ? 'text' : null,
+ message: 'Koishi Path',
+ initial: '/',
}, {
name: 'server',
type: (_, data) => data.type === 'cqhttp:http' ? 'text' : null,
@@ -41,7 +47,7 @@ async function createConfig() {
}, {
name: 'token',
type: 'text',
- message: 'Token for CoolQ Server',
+ message: 'Token for CQHTTP Server',
}], {
onCancel: () => succeed = false,
})
From 9dfd4cb7c2da2791291db761784f6804bc71ffa8 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 12:29:31 +0800
Subject: [PATCH 07/22] fix(mongo): fix typo of timers
---
packages/plugin-mongo/src/index.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-mongo/src/index.ts b/packages/plugin-mongo/src/index.ts
index e3aa4e5c10..6bb403afcb 100644
--- a/packages/plugin-mongo/src/index.ts
+++ b/packages/plugin-mongo/src/index.ts
@@ -112,8 +112,8 @@ extendDatabase(MongoDatabase, {
if (data.timers) {
$set.timers = {}
for (const key in data.timers) {
- if (key === '$date') $set.timer._date = data.timers.$date
- else $set.timer[key.replace(/\./gmi, '_')] = data.timers[key]
+ if (key === '$date') $set.timers._date = data.timers.$date
+ else $set.timers[key.replace(/\./gmi, '_')] = data.timers[key]
}
}
if (data.usage) {
From 967da352f6b360fe1536a7b48cf5cae418a4c5ff Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 14:46:02 +0800
Subject: [PATCH 08/22] feat(cli): init database config
---
packages/koishi/src/init.ts | 182 ++++++++++++++++++++++++++----------
1 file changed, 132 insertions(+), 50 deletions(-)
diff --git a/packages/koishi/src/init.ts b/packages/koishi/src/init.ts
index 9f47724cbb..44f48449c2 100644
--- a/packages/koishi/src/init.ts
+++ b/packages/koishi/src/init.ts
@@ -2,61 +2,143 @@ import { existsSync, writeFileSync, mkdirSync } from 'fs'
import { yellow, red, green } from 'kleur'
import { resolve, extname, dirname } from 'path'
import { AppConfig } from './worker'
-import prompts from 'prompts'
+import prompts, { PrevCaller, PromptObject } from 'prompts'
import { CAC } from 'cac'
+import { AppOptions } from 'koishi-core'
+import { omit } from 'koishi-utils'
+import * as mysql from 'koishi-plugin-mysql'
+import * as mongo from 'koishi-plugin-mongo'
-async function createConfig() {
+function conditional(type: T, key: string, ...values: string[]): PrevCaller {
+ return (prev, data, prompt) => {
+ if (!values.includes(data[key])) return null
+ return typeof type === 'function' ? (type as PrevCaller)(prev, data, prompt) : type
+ }
+}
+
+const serverQuestions: PromptObject[] = [{
+ name: 'type',
+ type: 'select',
+ message: 'Server Type',
+ choices: [
+ { title: 'HTTP', value: 'cqhttp:http' },
+ { title: 'WebSocket', value: 'cqhttp:ws' },
+ { title: 'WebSocket Reverse', value: 'cqhttp:ws-reverse' },
+ ],
+}, {
+ name: 'port',
+ type: conditional('number', 'type', 'cqhttp:http', 'cqhttp:ws-reverse'),
+ message: 'Koishi Port',
+ initial: 8080,
+}, {
+ name: 'path',
+ type: conditional('text', 'type', 'cqhttp:http', 'cqhttp:ws-reverse'),
+ message: 'Koishi Path',
+ initial: '/',
+}, {
+ name: 'server',
+ type: conditional('text', 'type', 'cqhttp:http'),
+ message: 'HTTP Server',
+ initial: 'http://localhost:5700',
+}, {
+ name: 'server',
+ type: conditional('text', 'type', 'cqhttp:ws'),
+ message: 'WebSocket Server',
+ initial: 'ws://localhost:6700',
+}, {
+ name: 'selfId',
+ type: 'number',
+ message: 'Your Bot\'s QQ Number',
+}, {
+ name: 'secret',
+ type: 'text',
+ message: 'Secret for Koishi Server',
+}, {
+ name: 'token',
+ type: 'text',
+ message: 'Token for CQHTTP Server',
+}, {
+ name: 'database',
+ type: 'select',
+ message: 'Database Type',
+ choices: [
+ { title: 'None', value: null },
+ { title: 'MySQL', value: 'mysql' },
+ { title: 'MongoDB', value: 'mongo' },
+ ],
+}]
+
+const mysqlQuestions: PromptObject[] = [{
+ name: 'host',
+ type: 'text',
+ message: 'MySQL / Host',
+ initial: '127.0.0.1',
+}, {
+ name: 'port',
+ type: 'number',
+ message: 'MySQL / Port',
+ initial: '3306',
+}, {
+ name: 'user',
+ type: 'text',
+ message: 'MySQL / Username',
+ initial: 'root',
+}, {
+ name: 'password',
+ type: 'text',
+ message: 'MySQL / Password',
+}, {
+ name: 'database',
+ type: 'text',
+ message: 'MySQL / Database',
+ initial: 'koishi',
+}]
+
+const mongoQuestions: PromptObject[] = [{
+ name: 'host',
+ type: 'text',
+ message: 'MongoDB / Host',
+ initial: '127.0.0.1',
+}, {
+ name: 'port',
+ type: 'number',
+ message: 'MongoDB / Port',
+ initial: '27017',
+}, {
+ name: 'username',
+ type: 'text',
+ message: 'MongoDB / Username',
+ initial: 'root',
+}, {
+ name: 'password',
+ type: 'text',
+ message: 'MongoDB / Password',
+}, {
+ name: 'name',
+ type: 'text',
+ message: 'MongoDB / Database',
+ initial: 'koishi',
+}]
+
+async function question(questions: PromptObject[]) {
let succeed = true
- const data = await prompts([{
- name: 'type',
- type: 'select',
- message: 'Connection Type',
- choices: [
- { title: 'HTTP', value: 'cqhttp:http' },
- { title: 'WebSocket', value: 'cqhttp:ws' },
- { title: 'WebSocket Reverse', value: 'cqhttp:ws-reverse' },
- ],
- }, {
- name: 'port',
- type: (_, data) => data.type === 'cqhttp:http' || data.type === 'cqhttp:ws-reverse' ? 'number' : null,
- message: 'Koishi Port',
- initial: 8080,
- }, {
- name: 'path',
- type: (_, data) => data.type === 'cqhttp:http' || data.type === 'cqhttp:ws-reverse' ? 'text' : null,
- message: 'Koishi Path',
- initial: '/',
- }, {
- name: 'server',
- type: (_, data) => data.type === 'cqhttp:http' ? 'text' : null,
- message: 'HTTP Server',
- initial: 'http://localhost:5700',
- }, {
- name: 'server',
- type: (_, data) => data.type === 'cqhttp:ws' ? 'text' : null,
- message: 'WebSocket Server',
- initial: 'ws://localhost:6700',
- }, {
- name: 'selfId',
- type: 'number',
- message: 'Your Bot\'s QQ Number',
- }, {
- name: 'secret',
- type: 'text',
- message: 'Secret for Koishi Server',
- }, {
- name: 'token',
- type: 'text',
- message: 'Token for CQHTTP Server',
- }], {
+ const data = await prompts(questions, {
onCancel: () => succeed = false,
})
- if (!succeed) return
- const config = {} as AppConfig
- for (const key in data) {
- if (data[key]) config[key] = data[key]
+ if (!succeed) throw new Error('interrupted')
+ return data
+}
+
+async function createConfig() {
+ const data = await question(serverQuestions)
+ const config = omit(data, ['database']) as AppConfig
+ config.plugins = []
+ console.log(data.database)
+ if (data.database === 'mysql') {
+ config.plugins.push(['mysql', await question(mysqlQuestions)])
+ } else if (data.database === 'mongo') {
+ config.plugins.push(['mongo', await question(mongoQuestions)])
}
- config.plugins = ['common', 'schedule']
return config
}
@@ -88,7 +170,7 @@ export default function (cli: CAC) {
}
// create configurations
- const config = await createConfig()
+ const config = await createConfig().catch(() => {})
if (!config) {
console.warn(`${error} initialization was canceled`)
process.exit(0)
From 85ec2160e344b5cf02c6724fa744191c12eed0e1 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 14:57:53 +0800
Subject: [PATCH 09/22] chore: remove recorder plugin
---
packages/plugin-recorder/README.md | 3 --
packages/plugin-recorder/package.json | 46 -------------------
packages/plugin-recorder/src/index.ts | 45 ------------------
.../tests/__snapshots__/index.spec.ts.snap | 7 ---
packages/plugin-recorder/tests/index.spec.ts | 41 -----------------
packages/plugin-recorder/tsconfig.json | 10 ----
6 files changed, 152 deletions(-)
delete mode 100644 packages/plugin-recorder/README.md
delete mode 100644 packages/plugin-recorder/package.json
delete mode 100644 packages/plugin-recorder/src/index.ts
delete mode 100644 packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap
delete mode 100644 packages/plugin-recorder/tests/index.spec.ts
delete mode 100644 packages/plugin-recorder/tsconfig.json
diff --git a/packages/plugin-recorder/README.md b/packages/plugin-recorder/README.md
deleted file mode 100644
index 6d88da71be..0000000000
--- a/packages/plugin-recorder/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# [koishi-plugin-recorder](https://koishi.js.org/plugins/recorder.html)
-
-[![npm](https://img.shields.io/npm/v/koishi-plugin-recorder?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-recorder)
diff --git a/packages/plugin-recorder/package.json b/packages/plugin-recorder/package.json
deleted file mode 100644
index b5bcf79b8e..0000000000
--- a/packages/plugin-recorder/package.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "name": "koishi-plugin-recorder",
- "description": "Save Chat Records for Koishi",
- "version": "2.0.0-beta.7",
- "main": "dist/index.js",
- "typings": "dist/index.d.ts",
- "files": [
- "dist"
- ],
- "author": "Shigma <1700011071@pku.edu.cn>",
- "license": "MIT",
- "scripts": {
- "build": "tsc -b",
- "lint": "eslint src --ext .ts",
- "prepack": "tsc -b"
- },
- "repository": {
- "type": "git",
- "url": "git+https://github.com/koishijs/koishi.git"
- },
- "bugs": {
- "url": "https://github.com/koishijs/koishi/issues"
- },
- "homepage": "https://github.com/koishijs/koishi/packages/plugin-recorder#readme",
- "keywords": [
- "bot",
- "qqbot",
- "cqhttp",
- "coolq",
- "chatbot",
- "koishi",
- "plugin",
- "record",
- "recorder"
- ],
- "peerDependencies": {
- "koishi-core": "^2.0.2"
- },
- "devDependencies": {
- "del": "^5.1.0",
- "koishi-test-utils": "^4.0.0"
- },
- "dependencies": {
- "koishi-utils": "^3.1.2"
- }
-}
diff --git a/packages/plugin-recorder/src/index.ts b/packages/plugin-recorder/src/index.ts
deleted file mode 100644
index 5ad4a5ced5..0000000000
--- a/packages/plugin-recorder/src/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { WriteStream, createWriteStream, existsSync, mkdirSync } from 'fs'
-import { resolve, dirname } from 'path'
-import { Session, Context } from 'koishi-core'
-import { pick } from 'koishi-utils'
-
-declare module 'koishi-core/dist/context' {
- interface EventMap {
- 'before-record' (session: Session): any
- }
-}
-
-const streams: Record = {}
-
-export interface RecorderOptions {
- folder?: string
-}
-
-const cwd = process.cwd()
-
-export const name = 'recorder'
-
-export function apply(ctx: Context, options: RecorderOptions = {}) {
- async function handleMessage(session: Session) {
- if (session.subType === 'group' && session.$group.assignee !== session.selfId) return
- if (await ctx.serial('before-record', session)) return
- const output = JSON.stringify(pick(session, ['time', 'userId', 'message'])) + '\n'
- const path = resolve(cwd, options.folder || 'messages', `${session.groupId}.txt`)
- if (!streams[path]) {
- const folder = dirname(path)
- if (!existsSync(folder)) {
- mkdirSync(folder, { recursive: true })
- }
- streams[path] = createWriteStream(path, { flags: 'a' })
- }
- streams[path].write(output)
- }
-
- ctx.on('attach-group', (session: Session) => {
- handleMessage(session)
- })
-
- ctx.on('before-send', (session: Session) => {
- handleMessage(session)
- })
-}
diff --git a/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap b/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap
deleted file mode 100644
index 7b25290913..0000000000
--- a/packages/plugin-recorder/tests/__snapshots__/index.spec.ts.snap
+++ /dev/null
@@ -1,7 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`check content 1`] = `
-"{\\"$ctxType\\":\\"group\\",\\"$ctxId\\":321,\\"userId\\":123,\\"message\\":\\"foo\\"}
-{\\"$ctxType\\":\\"group\\",\\"$ctxId\\":321,\\"userId\\":789,\\"message\\":\\"baz\\"}
-"
-`;
diff --git a/packages/plugin-recorder/tests/index.spec.ts b/packages/plugin-recorder/tests/index.spec.ts
deleted file mode 100644
index 6081a09522..0000000000
--- a/packages/plugin-recorder/tests/index.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { MockedApp, BASE_SELF_ID } from 'koishi-test-utils'
-import { startAll, stopAll } from 'koishi-core'
-import { readFileSync } from 'fs-extra'
-import { resolve } from 'path'
-import { sleep } from 'koishi-utils'
-import * as recorder from '../src'
-import del from 'del'
-
-const app1 = new MockedApp()
-const app2 = new MockedApp({ selfId: BASE_SELF_ID + 1 })
-
-app1.plugin(recorder)
-app2.plugin(recorder)
-
-beforeAll(() => startAll())
-
-const outFolder = resolve(process.cwd(), 'messages')
-
-afterAll(() => del(outFolder))
-
-test('group message', async () => {
- const mock = jest.fn()
- app1.on('record-writing', mock)
- await app1.receiveMessage('group', 'foo', 123, 321)
- await app1.receiveMessage('group', 'bar', 456, 654)
- await app1.receiveMessage('group', 'baz', 789, 321)
- expect(mock).toBeCalledTimes(3)
-})
-
-test('private message', async () => {
- const mock = jest.fn()
- app1.on('record-writing', mock)
- await app1.receiveMessage('user', 'foo', 123)
- expect(mock).toBeCalledTimes(0)
-})
-
-test('check content', async () => {
- await stopAll()
- await sleep(100)
- expect(readFileSync(resolve(outFolder, '321.txt'), 'utf8')).toMatchSnapshot()
-})
diff --git a/packages/plugin-recorder/tsconfig.json b/packages/plugin-recorder/tsconfig.json
deleted file mode 100644
index a497f05e83..0000000000
--- a/packages/plugin-recorder/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.base",
- "compilerOptions": {
- "outDir": "dist",
- "rootDir": "src",
- },
- "include": [
- "src",
- ],
-}
\ No newline at end of file
From 7403b876b97afec10b654a91acaa5bf9c2c3dab8 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 15:09:23 +0800
Subject: [PATCH 10/22] build: auto generate ecosystem meta
---
.gitignore | 4 +--
build/bump.ts | 11 +++++-
build/utils.ts | 3 +-
package.json | 1 +
packages/koishi/ecosystem.json | 64 ++++++++++++++++++++++++++++++++++
5 files changed, 79 insertions(+), 4 deletions(-)
create mode 100644 packages/koishi/ecosystem.json
diff --git a/.gitignore b/.gitignore
index 3dbe0d016b..4514a66f85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,10 +3,10 @@ temp
/addons
/atri
+/coverage
/docs
/shiki
-/coverage
-/plugins
+/test
todo.md
yarn.lock
diff --git a/build/bump.ts b/build/bump.ts
index 2bec33f835..8bed6e66a7 100644
--- a/build/bump.ts
+++ b/build/bump.ts
@@ -109,7 +109,7 @@ function bumpPkg(source: Package, flag: BumpType, only = false) {
const { name } = source
if (target.name === name) return
Object.entries({ devDependencies, peerDependencies, dependencies, optionalDependencies })
- .filter(([_, dependencies = {}]) => dependencies[name])
+ .filter(([, dependencies = {}]) => dependencies[name])
.forEach(([type]) => {
target.meta[type][name] = '^' + newVersion
target.dirty = true
@@ -150,4 +150,13 @@ const flag = options.major ? 'major' : options.minor ? 'minor' : options.patch ?
}
return pkg.save()
}))
+
+ const ecosystem: Record> = {}
+ for (const path in packages) {
+ if (!path.startsWith('packages/') || path.startsWith('packages/koishi')) continue
+ const { name, version, description } = packages[path].meta
+ ecosystem[name] = { version, description }
+ }
+
+ await writeJson(resolve(__dirname, '../packages/koishi/ecosystem.json'), ecosystem, { spaces: 2 })
})()
diff --git a/build/utils.ts b/build/utils.ts
index e11227986b..14f88a9a39 100644
--- a/build/utils.ts
+++ b/build/utils.ts
@@ -17,6 +17,7 @@ export type DependencyType = 'dependencies' | 'devDependencies' | 'peerDependenc
export interface PackageJson extends Partial>> {
name?: string
+ description?: string
private?: boolean
version?: string
}
@@ -36,7 +37,7 @@ export function spawnSync(command: string, silent?: boolean) {
export function spawnAsync(command: string) {
const args = command.split(/\s+/)
const child = spawn(args[0], args.slice(1), { stdio: 'inherit' })
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
child.on('close', resolve)
})
}
diff --git a/package.json b/package.json
index 745b6cbcd5..647d1a2aa1 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"workspaces": [
"atri",
"addons",
+ "test",
"shiki",
"shiki/core",
"shiki/cosmos",
diff --git a/packages/koishi/ecosystem.json b/packages/koishi/ecosystem.json
new file mode 100644
index 0000000000..09d3c1040c
--- /dev/null
+++ b/packages/koishi/ecosystem.json
@@ -0,0 +1,64 @@
+{
+ "koishi-adapter-cqhttp": {
+ "version": "1.0.1",
+ "description": "CQHTTP adapter for Koishi"
+ },
+ "koishi-plugin-chess": {
+ "version": "2.0.0-beta.10",
+ "description": "Chess Plugin for Koishi"
+ },
+ "koishi-plugin-common": {
+ "version": "3.0.0-beta.15",
+ "description": "Common plugins for Koishi"
+ },
+ "koishi-plugin-eval": {
+ "version": "2.0.0",
+ "description": "Execute JavaScript in Koishi"
+ },
+ "koishi-plugin-eval-addons": {
+ "version": "1.0.0-beta.9",
+ "description": "Execute JavaScript in Koishi"
+ },
+ "koishi-plugin-github": {
+ "version": "2.0.0-beta.9",
+ "description": "GitHub webhook plugin for Koishi"
+ },
+ "koishi-plugin-image-search": {
+ "version": "2.0.0",
+ "description": "Image searching plugin for Koishi"
+ },
+ "koishi-plugin-mongo": {
+ "version": "1.0.1",
+ "description": "MongoDB support for Koishi"
+ },
+ "koishi-plugin-monitor": {
+ "version": "1.0.0-beta.14"
+ },
+ "koishi-plugin-mysql": {
+ "version": "2.0.0",
+ "description": "MySQL support for Koishi"
+ },
+ "koishi-plugin-puppeteer": {
+ "version": "1.0.0",
+ "description": "Take Screenshots in Koishi"
+ },
+ "koishi-plugin-rss": {
+ "version": "1.0.0",
+ "description": "Subscribe RSS Url for Koishi"
+ },
+ "koishi-plugin-schedule": {
+ "version": "2.0.1",
+ "description": "Schedule plugin for Koishi"
+ },
+ "koishi-plugin-status": {
+ "version": "2.0.0-beta.13",
+ "description": "Show Status of Koishi"
+ },
+ "koishi-plugin-teach": {
+ "version": "1.0.2",
+ "description": "Teach plugin for Koishi"
+ },
+ "koishi-plugin-tools": {
+ "version": "1.0.0"
+ }
+}
From 661435e6c04c74ebc382ac867c52bdaa37115f65 Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 16:03:19 +0800
Subject: [PATCH 11/22] feat(cli): support multiselect plugins, remove bundled
plugins
---
packages/koishi/package.json | 12 +---
packages/koishi/src/init.ts | 120 +++++++++++++++++++++++++----------
tsconfig.json | 1 -
3 files changed, 90 insertions(+), 43 deletions(-)
diff --git a/packages/koishi/package.json b/packages/koishi/package.json
index 05da115ad0..4bc743caa2 100644
--- a/packages/koishi/package.json
+++ b/packages/koishi/package.json
@@ -8,7 +8,8 @@
"node": ">=12.0.0"
},
"files": [
- "dist"
+ "dist",
+ "ecosystem.json"
],
"bin": "dist/cli.js",
"author": "Shigma <1700011071@pku.edu.cn>",
@@ -34,21 +35,14 @@
"koishi"
],
"devDependencies": {
- "@types/js-yaml": "^3.12.5",
"@types/prompts": "^2.0.8"
},
"dependencies": {
"cac": "^6.6.1",
"kleur": "^4.1.1",
- "koishi-core": "^2.0.2",
"koishi-adapter-cqhttp": "^1.0.1",
+ "koishi-core": "^2.0.2",
"koishi-plugin-common": "^3.0.0-beta.15",
- "koishi-plugin-mysql": "^2.0.0",
- "koishi-plugin-mongo": "^1.0.1",
- "koishi-plugin-schedule": "^2.0.1",
- "koishi-plugin-status": "^2.0.0-beta.13",
- "koishi-plugin-teach": "^1.0.2",
- "koishi-utils": "^3.1.2",
"prompts": "^2.3.2"
}
}
diff --git a/packages/koishi/src/init.ts b/packages/koishi/src/init.ts
index 44f48449c2..1400c94714 100644
--- a/packages/koishi/src/init.ts
+++ b/packages/koishi/src/init.ts
@@ -1,11 +1,9 @@
-import { existsSync, writeFileSync, mkdirSync } from 'fs'
+import { promises as fs, existsSync } from 'fs'
import { yellow, red, green } from 'kleur'
import { resolve, extname, dirname } from 'path'
import { AppConfig } from './worker'
-import prompts, { PrevCaller, PromptObject } from 'prompts'
import { CAC } from 'cac'
-import { AppOptions } from 'koishi-core'
-import { omit } from 'koishi-utils'
+import prompts, { Choice, PrevCaller, PromptObject } from 'prompts'
import * as mysql from 'koishi-plugin-mysql'
import * as mongo from 'koishi-plugin-mongo'
@@ -16,7 +14,7 @@ function conditional(type: T, key: string, ...va
}
}
-const serverQuestions: PromptObject[] = [{
+const serverQuestions: PromptObject[] = [{
name: 'type',
type: 'select',
message: 'Server Type',
@@ -120,6 +118,11 @@ const mongoQuestions: PromptObject[] = [{
initial: 'koishi',
}]
+const dbQuestionMap = {
+ mysql: mysqlQuestions,
+ mongo: mongoQuestions,
+}
+
async function question(questions: PromptObject[]) {
let succeed = true
const data = await prompts(questions, {
@@ -129,31 +132,97 @@ async function question(questions: PromptObject[]) {
return data
}
+type DependencyType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies'
+
+interface Package extends Partial>> {
+ version: string
+ description?: string
+}
+
+const ecosystem: Record = require('../ecosystem')
+const builtinPackages = ['koishi-plugin-common']
+
async function createConfig() {
const data = await question(serverQuestions)
- const config = omit(data, ['database']) as AppConfig
- config.plugins = []
- console.log(data.database)
- if (data.database === 'mysql') {
- config.plugins.push(['mysql', await question(mysqlQuestions)])
- } else if (data.database === 'mongo') {
- config.plugins.push(['mongo', await question(mongoQuestions)])
+ const { database } = data
+ const config = { ...data, database: undefined, plugins: [] } as AppConfig
+
+ // database
+ if (database) {
+ config.plugins.push([database, await question(dbQuestionMap[database])])
}
+
+ // official plugins
+ const choices: Choice[] = Object.entries(ecosystem).map(([title, meta]) => {
+ if (!title.startsWith('koishi-plugin-')) return
+ const value = title.slice(14)
+ if (value in dbQuestionMap) return
+ const { description } = meta
+ const selected = builtinPackages.includes(title)
+ return { title, value, description, selected }
+ }).filter(Boolean)
+
+ const { plugins } = await prompts({
+ type: 'multiselect',
+ name: 'plugins',
+ message: 'Choose Official Plugins',
+ choices,
+ })
+
+ config.plugins.push(...plugins.map(name => [name]))
+
return config
}
+const workingDirectory = process.cwd()
const supportedTypes = ['js', 'ts', 'json'] as const
type ConfigFileType = typeof supportedTypes[number]
-export default function (cli: CAC) {
- const error = red('error')
- const success = green('success')
+const error = red('error')
+const success = green('success')
+
+async function updateMeta(config: AppConfig) {
+ const path = resolve(workingDirectory, 'package.json')
+ const meta: Package = JSON.parse(await fs.readFile(path, 'utf8'))
+ let modified = false
+ if (!meta.dependencies) meta.dependencies = {}
+ for (const [name] of config.plugins as string[]) {
+ const fullname = 'koishi-plugin-' + name
+ if (!meta.dependencies[fullname]) {
+ modified = true
+ meta.dependencies[fullname] = ecosystem[fullname].version
+ }
+ }
+ if (!modified) return
+ await fs.writeFile(path, JSON.stringify(meta, null, 2))
+ console.log(`${success} package.json was updated`)
+}
+
+async function writeConfig(config: AppConfig, path: string, type: ConfigFileType) {
+ // generate output
+ let output = JSON.stringify(config, null, 2)
+ if (type !== 'json') {
+ output = output.replace(/^(\s+)"([\w$]+)":/mg, '$1$2:')
+ if (type === 'js') {
+ output = 'module.exports = ' + output
+ } else if (type === 'ts') {
+ output = 'export = ' + output
+ }
+ }
+ // write to file
+ const folder = dirname(path)
+ await fs.mkdir(folder, { recursive: true })
+ await fs.writeFile(path, output)
+ console.log(`${success} created config file: ${path}`)
+}
+
+export default function (cli: CAC) {
cli.command('init [file]', 'initialize a koishi configuration file')
.option('-f, --forced', 'overwrite config file if it exists')
- .action(async (file, options) => {
+ .action(async (file = 'koishi.config.js', options) => {
// resolve file path
- const path = resolve(process.cwd(), String(file || 'koishi.config.js'))
+ const path = resolve(workingDirectory, file)
if (!options.forced && existsSync(path)) {
console.warn(`${error} ${options.output} already exists. If you want to overwrite the current file, use ${yellow('koishi init -f')}`)
process.exit(1)
@@ -176,22 +245,7 @@ export default function (cli: CAC) {
process.exit(0)
}
- // generate output
- let output = JSON.stringify(config, null, 2)
- if (extension !== 'json') {
- output = output.replace(/^(\s+)"([\w$]+)":/mg, '$1$2:')
- if (extension === 'js') {
- output = 'module.exports = ' + output
- } else if (extension === 'ts') {
- output = 'export = ' + output
- }
- }
-
- // write to file
- const folder = dirname(path)
- if (!existsSync(folder)) mkdirSync(folder, { recursive: true })
- writeFileSync(path, output)
- console.warn(`${success} created config file: ${path}`)
+ await Promise.all([updateMeta(config), writeConfig(config, path, extension)])
process.exit(0)
})
}
diff --git a/tsconfig.json b/tsconfig.json
index f32d0e2261..5bbe1c23f1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -24,7 +24,6 @@
{ "path": "./packages/plugin-github" },
{ "path": "./packages/plugin-image-search" },
{ "path": "./packages/plugin-monitor" },
- { "path": "./packages/plugin-recorder" },
{ "path": "./packages/plugin-rss" },
{ "path": "./packages/plugin-schedule" },
{ "path": "./packages/plugin-status" },
From d79f8efcc37041ca76bfc47de30f09a15af80c1a Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 16:43:30 +0800
Subject: [PATCH 12/22] feat(cli): pretty code generation
---
packages/koishi/src/init.ts | 50 ++++++++++++++++++++++++++++---------
1 file changed, 38 insertions(+), 12 deletions(-)
diff --git a/packages/koishi/src/init.ts b/packages/koishi/src/init.ts
index 1400c94714..3e6d65e76b 100644
--- a/packages/koishi/src/init.ts
+++ b/packages/koishi/src/init.ts
@@ -176,7 +176,7 @@ async function createConfig() {
const workingDirectory = process.cwd()
const supportedTypes = ['js', 'ts', 'json'] as const
-type ConfigFileType = typeof supportedTypes[number]
+type SourceType = typeof supportedTypes[number]
const error = red('error')
const success = green('success')
@@ -190,7 +190,7 @@ async function updateMeta(config: AppConfig) {
const fullname = 'koishi-plugin-' + name
if (!meta.dependencies[fullname]) {
modified = true
- meta.dependencies[fullname] = ecosystem[fullname].version
+ meta.dependencies[fullname] = '^' + ecosystem[fullname].version
}
}
if (!modified) return
@@ -198,16 +198,42 @@ async function updateMeta(config: AppConfig) {
console.log(`${success} package.json was updated`)
}
-async function writeConfig(config: AppConfig, path: string, type: ConfigFileType) {
+type Serializable = string | number | Serializable[] | { [key: string]: Serializable }
+
+function joinLines(lines: string[], type: SourceType, indent: string) {
+ return `\n ${indent}${lines.join(',\n ' + indent)}${type === 'json' ? '' : ','}\n${indent}`
+}
+
+function codegen(value: Serializable, type: SourceType, indent = '', path = '/') {
+ if (value === null) return 'null'
+ switch (typeof value) {
+ case 'number': case 'boolean': return '' + value
+ case 'string': return type === 'json' || value.includes("'") && !value.includes('"')
+ ? `"${value.replace(/"/g, '\\"')}"`
+ : `'${value.replace(/'/g, "\\'")}'`
+ case 'undefined': return undefined
+ }
+
+ if (Array.isArray(value)) {
+ return path === '/plugins/0/'
+ ? `[${value.map(value => codegen(value, type, indent, path + '0/')).join(', ')}]`
+ : `[${joinLines(value.map(value => codegen(value, type, ' ' + indent, path + '0/')), type, indent)}]`
+ }
+
+ return `{${joinLines(Object.entries(value).filter(([, value]) => value !== undefined).map(([key, value]) => {
+ const keyString = type === 'json' ? `"${key}"` : key
+ const valueString = codegen(value, type, ' ' + indent, path + key + '/')
+ return keyString + ': ' + valueString
+ }), type, indent)}}`
+}
+
+async function writeConfig(config: any, path: string, type: SourceType) {
// generate output
- let output = JSON.stringify(config, null, 2)
- if (type !== 'json') {
- output = output.replace(/^(\s+)"([\w$]+)":/mg, '$1$2:')
- if (type === 'js') {
- output = 'module.exports = ' + output
- } else if (type === 'ts') {
- output = 'export = ' + output
- }
+ let output = codegen(config, type) + '\n'
+ if (type === 'js') {
+ output = 'module.exports = ' + output
+ } else if (type === 'ts') {
+ output = 'export = ' + output
}
// write to file
@@ -229,7 +255,7 @@ export default function (cli: CAC) {
}
// parse extension
- const extension = extname(path).slice(1) as ConfigFileType
+ const extension = extname(path).slice(1) as SourceType
if (!extension) {
console.warn(`${error} configuration file should have an extension, received "${file}"`)
process.exit(1)
From 753ca9f2b5856b7ac86e51c7c0ddeb2967311abd Mon Sep 17 00:00:00 2001
From: Shigma <1700011071@pku.edu.cn>
Date: Mon, 31 Aug 2020 17:27:21 +0800
Subject: [PATCH 13/22] chore: update readme
---
.github/wechat.png | Bin 126667 -> 0 bytes
README.md | 8 +++-----
2 files changed, 3 insertions(+), 5 deletions(-)
delete mode 100644 .github/wechat.png
diff --git a/.github/wechat.png b/.github/wechat.png
deleted file mode 100644
index a92bd138ccb074e88632a8fa7bcb20e7ef5ec31b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 126667
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
RY(y
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 |