create-kokkoro 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Yuki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,8 +2,42 @@
2
2
 
3
3
  Kokkoro QQ 机器人框架的项目创建工具。
4
4
 
5
+ ## 使用
6
+
7
+ 运行以下命令创建 Kokkoro 项目:
8
+
5
9
  ```shell
6
10
  bun create kokkoro
7
11
  ```
8
12
 
9
- 按照提示输入项目名称,即可在当前目录创建 Kokkoro 项目。
13
+ 命令会创建项目目录、`plugins` 目录、`package.json`、`kokkoro.json` 和 `main.ts`。
14
+
15
+ 如果项目目录不是空目录,创建将中止。使用 `--force` 选项可以覆盖脚手架创建的同名文件,目录中的其他内容不受影响。
16
+
17
+ ```shell
18
+ bun create kokkoro --force
19
+ ```
20
+
21
+ ## 配置项目
22
+
23
+ 命令会依次询问以下内容:
24
+
25
+ 1. 输入项目名称,默认值为 `kokkoro-app`。
26
+ 2. 输入服务端口,默认值为 `3000`。
27
+ 3. 选择 QQ 服务接入方式,可选 `WebSocket` 或 `WebHook`。
28
+ 4. 选择是否添加机器人。不添加机器人时,`kokkoro.json` 中的 `bots` 为空数组。
29
+ 5. 添加机器人时,输入机器人的 `AppID` 和 `ClientSecret`。
30
+ 6. 使用 `WebHook` 并添加机器人时,输入 WebHook 路径,默认值为 `/callback`。
31
+
32
+ ### 启动项目
33
+
34
+ 创建完成后,进入项目目录并安装依赖:
35
+
36
+ ```shell
37
+ cd kokkoro-app
38
+
39
+ bun install
40
+ bun start
41
+ ```
42
+
43
+ 将 `kokkoro-app` 替换为你输入的项目名称。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-kokkoro",
3
- "version": "0.0.0",
4
- "description": "The project scaffolding tool for the Kokkoro QQ bot framework.",
3
+ "version": "0.1.0",
4
+ "description": "The project creation tool for the Kokkoro QQ bot framework.",
5
5
  "keywords": [
6
6
  "bot",
7
7
  "kokkoro",
@@ -20,12 +20,16 @@
20
20
  "license": "MIT",
21
21
  "author": "Yuki <admin@yuki.sh>",
22
22
  "type": "module",
23
+ "exports": "./src/project.ts",
23
24
  "bin": {
24
25
  "create-kokkoro": "./src/index.ts"
25
26
  },
26
27
  "files": [
27
28
  "src"
28
29
  ],
30
+ "dependencies": {
31
+ "komut": "^0.2.0"
32
+ },
29
33
  "peerDependencies": {
30
34
  "typescript": "^6.0.3"
31
35
  }
package/src/index.ts CHANGED
@@ -1 +1,16 @@
1
1
  #!/usr/bin/env bun
2
+
3
+ import { argv } from 'bun';
4
+
5
+ import { input } from 'komut/prompts';
6
+
7
+ import { createProject } from './project';
8
+
9
+ const name = input('项目名称', { default: 'kokkoro-app' });
10
+
11
+ if (name === null) {
12
+ throw new Error('已取消创建项目。');
13
+ }
14
+ const isForced = argv.includes('--force');
15
+
16
+ await createProject(name, isForced);
package/src/project.ts ADDED
@@ -0,0 +1,155 @@
1
+ import { Glob, write } from 'bun';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { basename, join, resolve } from 'node:path';
4
+
5
+ import { input, select } from 'komut/prompts';
6
+
7
+ type Protocol = 'websocket' | 'webhook';
8
+
9
+ async function hasEntries(directory: string): Promise<boolean> {
10
+ try {
11
+ const entries = new Glob('*').scan({
12
+ cwd: directory,
13
+ dot: true,
14
+ onlyFiles: false,
15
+ });
16
+ const iterator = entries[Symbol.asyncIterator]();
17
+ const { done } = await iterator.next();
18
+
19
+ return !done;
20
+ } catch (error) {
21
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
22
+ return false;
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ function prompt(message: string, defaultValue?: string): string {
29
+ const value = input(message, { default: defaultValue });
30
+
31
+ if (value === null) {
32
+ throw new Error('已取消创建项目。');
33
+ }
34
+ return value;
35
+ }
36
+
37
+ function promptRequired(message: string): string {
38
+ while (true) {
39
+ const value = prompt(message);
40
+
41
+ if (value) {
42
+ return value;
43
+ }
44
+ console.error(`${message}不能为空。`);
45
+ }
46
+ }
47
+
48
+ function promptPort(): number {
49
+ while (true) {
50
+ const port = Number(prompt('服务端口', '3000'));
51
+
52
+ if (Number.isInteger(port) && port >= 0 && port <= 65535) {
53
+ return port;
54
+ }
55
+ console.error('服务端口必须是 0 到 65535 之间的整数。');
56
+ }
57
+ }
58
+
59
+ function promptProtocol(): Protocol {
60
+ const choice = select('QQ 服务接入方式', [
61
+ { label: 'WebSocket', value: 'websocket' },
62
+ { label: 'WebHook', value: 'webhook' },
63
+ ]);
64
+
65
+ if (choice === null) {
66
+ throw new Error('已取消创建项目。');
67
+ }
68
+ return <Protocol>choice.value;
69
+ }
70
+
71
+ function promptWebHookPath(): string {
72
+ while (true) {
73
+ const path = prompt('WebHook 路径', '/callback');
74
+
75
+ if (path.startsWith('/')) {
76
+ return path;
77
+ }
78
+ console.error('WebHook 路径必须以 / 开头。');
79
+ }
80
+ }
81
+
82
+ function promptBots(protocol: Protocol) {
83
+ const choice = select('是否添加机器人', [
84
+ { label: '是', value: 'yes' },
85
+ { label: '否', value: 'no' },
86
+ ]);
87
+
88
+ if (choice === null) {
89
+ throw new Error('已取消创建项目。');
90
+ }
91
+
92
+ if (choice.value === 'no') {
93
+ return [];
94
+ }
95
+ const bot = {
96
+ appId: promptRequired('机器人 AppID'),
97
+ clientSecret: promptRequired('机器人 ClientSecret'),
98
+ };
99
+
100
+ return protocol === 'webhook' ? [{ ...bot, webhook: { path: promptWebHookPath() } }] : [bot];
101
+ }
102
+
103
+ /** 在指定目录创建 Kokkoro 项目。 */
104
+ export async function createProject(directory: string, isForced = false): Promise<void> {
105
+ if ((await hasEntries(directory)) && !isForced) {
106
+ throw new Error(`目标目录不是空目录。如需继续,请使用 --force 选项覆盖模板文件。\n${directory}`);
107
+ }
108
+ const port = promptPort();
109
+ const protocol = promptProtocol();
110
+ const bots = promptBots(protocol);
111
+ const name = basename(resolve(directory));
112
+ const manifest = {
113
+ name,
114
+ private: true,
115
+ type: 'module',
116
+ scripts: {
117
+ start: 'bun run main.ts',
118
+ },
119
+ dependencies: {
120
+ kokkoro: '^3.0.0',
121
+ },
122
+ devEngines: {
123
+ runtime: {
124
+ name: 'bun',
125
+ onFail: 'warn',
126
+ },
127
+ packageManager: {
128
+ name: 'bun',
129
+ onFail: 'warn',
130
+ },
131
+ },
132
+ };
133
+ const config = {
134
+ $schema: 'https://kokkoro.js.org/schema.json',
135
+ protocol,
136
+ server: { port },
137
+ bots,
138
+ };
139
+ const pluginsDirectory = join(directory, 'plugins');
140
+
141
+ await mkdir(pluginsDirectory, { recursive: true });
142
+ await Promise.all([
143
+ write(join(directory, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`),
144
+ write(join(directory, 'kokkoro.json'), `${JSON.stringify(config, null, 2)}\n`),
145
+ write(join(directory, 'main.ts'), "import { run } from 'kokkoro';\n\nawait run();\n"),
146
+ ]);
147
+
148
+ console.log('\n项目创建完成,请依次运行以下命令:\n');
149
+
150
+ if (directory !== '.') {
151
+ console.log(` cd ${directory}`);
152
+ }
153
+ console.log(' bun install');
154
+ console.log(' bun start');
155
+ }