growork 1.0.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.
@@ -0,0 +1,345 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+
4
+ // 复制 FeishuService 中的纯函数进行测试
5
+
6
+ function parseDocumentId(url: string): { type: 'docx' | 'wiki'; documentId: string } {
7
+ const docxMatch = url.match(/\/docx\/([a-zA-Z0-9]+)/);
8
+ if (docxMatch) {
9
+ return { type: 'docx', documentId: docxMatch[1] };
10
+ }
11
+
12
+ const wikiMatch = url.match(/\/wiki\/([a-zA-Z0-9]+)/);
13
+ if (wikiMatch) {
14
+ return { type: 'wiki', documentId: wikiMatch[1] };
15
+ }
16
+
17
+ throw new Error(`无法解析飞书文档 URL: ${url}`);
18
+ }
19
+
20
+ function getLanguageName(langCode: number): string {
21
+ const langMap: Record<number, string> = {
22
+ 1: 'plaintext', 7: 'bash', 9: 'cpp', 10: 'c',
23
+ 15: 'dart', 22: 'go', 28: 'json', 29: 'java', 30: 'javascript',
24
+ 32: 'kotlin', 47: 'python', 50: 'ruby', 51: 'rust',
25
+ 54: 'sql', 58: 'shell', 59: 'swift', 61: 'typescript', 65: 'yaml',
26
+ };
27
+ return langMap[langCode] || '';
28
+ }
29
+
30
+ function textElementsToMarkdown(elements: any[] | undefined): string {
31
+ if (!elements) return '';
32
+
33
+ return elements.map(el => {
34
+ if (el.text_run) {
35
+ let text = el.text_run.content || '';
36
+ const style = el.text_run.text_element_style;
37
+
38
+ if (style?.bold) text = `**${text}**`;
39
+ if (style?.italic) text = `*${text}*`;
40
+ if (style?.strikethrough) text = `~~${text}~~`;
41
+ if (style?.inline_code) text = `\`${text}\``;
42
+ if (style?.link?.url) text = `[${text}](${style.link.url})`;
43
+
44
+ return text;
45
+ }
46
+ if (el.mention_user) {
47
+ return `@${el.mention_user.user_id || 'user'}`;
48
+ }
49
+ if (el.mention_doc) {
50
+ const title = el.mention_doc.title || '文档';
51
+ const url = el.mention_doc.url || '';
52
+ return `[${title}](${url})`;
53
+ }
54
+ return '';
55
+ }).join('');
56
+ }
57
+
58
+ describe('飞书 URL 解析', () => {
59
+ describe('docx 格式', () => {
60
+ it('应解析 feishu.cn docx URL', () => {
61
+ const result = parseDocumentId('https://xxx.feishu.cn/docx/abc123def');
62
+ assert.strictEqual(result.type, 'docx');
63
+ assert.strictEqual(result.documentId, 'abc123def');
64
+ });
65
+
66
+ it('应解析 larksuite.com docx URL', () => {
67
+ const result = parseDocumentId('https://xxx.larksuite.com/docx/XYZ789');
68
+ assert.strictEqual(result.type, 'docx');
69
+ assert.strictEqual(result.documentId, 'XYZ789');
70
+ });
71
+
72
+ it('应解析带查询参数的 docx URL', () => {
73
+ const result = parseDocumentId('https://xxx.feishu.cn/docx/abc123?from=share');
74
+ assert.strictEqual(result.type, 'docx');
75
+ assert.strictEqual(result.documentId, 'abc123');
76
+ });
77
+ });
78
+
79
+ describe('wiki 格式', () => {
80
+ it('应解析 feishu.cn wiki URL', () => {
81
+ const result = parseDocumentId('https://xxx.feishu.cn/wiki/node123');
82
+ assert.strictEqual(result.type, 'wiki');
83
+ assert.strictEqual(result.documentId, 'node123');
84
+ });
85
+
86
+ it('应解析 larksuite.com wiki URL', () => {
87
+ const result = parseDocumentId('https://xxx.larksuite.com/wiki/WikiToken456');
88
+ assert.strictEqual(result.type, 'wiki');
89
+ assert.strictEqual(result.documentId, 'WikiToken456');
90
+ });
91
+ });
92
+
93
+ describe('无效 URL', () => {
94
+ it('应拒绝不支持的 URL 格式', () => {
95
+ assert.throws(
96
+ () => parseDocumentId('https://xxx.feishu.cn/docs/abc123'),
97
+ /无法解析飞书文档 URL/
98
+ );
99
+ });
100
+
101
+ it('只检查路径格式,不验证域名', () => {
102
+ // 当前实现不验证域名,只检查路径中是否有 /docx/ 或 /wiki/
103
+ const result = parseDocumentId('https://example.com/docx/abc123');
104
+ assert.strictEqual(result.documentId, 'abc123');
105
+ });
106
+
107
+ it('应拒绝空路径', () => {
108
+ assert.throws(
109
+ () => parseDocumentId('https://xxx.feishu.cn/'),
110
+ /无法解析飞书文档 URL/
111
+ );
112
+ });
113
+ });
114
+ });
115
+
116
+ describe('语言代码映射', () => {
117
+ it('应返回正确的语言名称', () => {
118
+ assert.strictEqual(getLanguageName(47), 'python');
119
+ assert.strictEqual(getLanguageName(30), 'javascript');
120
+ assert.strictEqual(getLanguageName(61), 'typescript');
121
+ assert.strictEqual(getLanguageName(22), 'go');
122
+ assert.strictEqual(getLanguageName(51), 'rust');
123
+ });
124
+
125
+ it('应对未知代码返回空字符串', () => {
126
+ assert.strictEqual(getLanguageName(999), '');
127
+ assert.strictEqual(getLanguageName(0), '');
128
+ });
129
+ });
130
+
131
+ describe('文本元素转 Markdown', () => {
132
+ it('应处理普通文本', () => {
133
+ const elements = [{ text_run: { content: 'Hello World' } }];
134
+ assert.strictEqual(textElementsToMarkdown(elements), 'Hello World');
135
+ });
136
+
137
+ it('应处理粗体文本', () => {
138
+ const elements = [{
139
+ text_run: {
140
+ content: 'Bold',
141
+ text_element_style: { bold: true }
142
+ }
143
+ }];
144
+ assert.strictEqual(textElementsToMarkdown(elements), '**Bold**');
145
+ });
146
+
147
+ it('应处理斜体文本', () => {
148
+ const elements = [{
149
+ text_run: {
150
+ content: 'Italic',
151
+ text_element_style: { italic: true }
152
+ }
153
+ }];
154
+ assert.strictEqual(textElementsToMarkdown(elements), '*Italic*');
155
+ });
156
+
157
+ it('应处理删除线文本', () => {
158
+ const elements = [{
159
+ text_run: {
160
+ content: 'Strikethrough',
161
+ text_element_style: { strikethrough: true }
162
+ }
163
+ }];
164
+ assert.strictEqual(textElementsToMarkdown(elements), '~~Strikethrough~~');
165
+ });
166
+
167
+ it('应处理行内代码', () => {
168
+ const elements = [{
169
+ text_run: {
170
+ content: 'code',
171
+ text_element_style: { inline_code: true }
172
+ }
173
+ }];
174
+ assert.strictEqual(textElementsToMarkdown(elements), '`code`');
175
+ });
176
+
177
+ it('应处理链接', () => {
178
+ const elements = [{
179
+ text_run: {
180
+ content: 'Click here',
181
+ text_element_style: { link: { url: 'https://example.com' } }
182
+ }
183
+ }];
184
+ assert.strictEqual(textElementsToMarkdown(elements), '[Click here](https://example.com)');
185
+ });
186
+
187
+ it('应处理组合样式', () => {
188
+ const elements = [{
189
+ text_run: {
190
+ content: 'Bold Italic',
191
+ text_element_style: { bold: true, italic: true }
192
+ }
193
+ }];
194
+ assert.strictEqual(textElementsToMarkdown(elements), '***Bold Italic***');
195
+ });
196
+
197
+ it('应处理多个元素', () => {
198
+ const elements = [
199
+ { text_run: { content: 'Normal ' } },
200
+ { text_run: { content: 'Bold', text_element_style: { bold: true } } },
201
+ { text_run: { content: ' text' } }
202
+ ];
203
+ assert.strictEqual(textElementsToMarkdown(elements), 'Normal **Bold** text');
204
+ });
205
+
206
+ it('应处理 @用户', () => {
207
+ const elements = [{ mention_user: { user_id: 'user123' } }];
208
+ assert.strictEqual(textElementsToMarkdown(elements), '@user123');
209
+ });
210
+
211
+ it('应处理文档引用', () => {
212
+ const elements = [{
213
+ mention_doc: { title: 'My Doc', url: 'https://xxx.feishu.cn/docx/abc' }
214
+ }];
215
+ assert.strictEqual(textElementsToMarkdown(elements), '[My Doc](https://xxx.feishu.cn/docx/abc)');
216
+ });
217
+
218
+ it('应处理 undefined 输入', () => {
219
+ assert.strictEqual(textElementsToMarkdown(undefined), '');
220
+ });
221
+
222
+ it('应处理空数组', () => {
223
+ assert.strictEqual(textElementsToMarkdown([]), '');
224
+ });
225
+ });
226
+
227
+ describe('Block 转 Markdown', () => {
228
+ // 模拟 blockToMarkdown 的核心逻辑
229
+ function blockToMarkdown(block: any): string | null {
230
+ const blockType = block.block_type;
231
+
232
+ switch (blockType) {
233
+ case 2: // Text
234
+ const text = textElementsToMarkdown(block.text?.elements);
235
+ return text ? text + '\n' : '';
236
+ case 3: // Heading1
237
+ return `# ${textElementsToMarkdown(block.heading1?.elements)}\n`;
238
+ case 4: // Heading2
239
+ return `## ${textElementsToMarkdown(block.heading2?.elements)}\n`;
240
+ case 5: // Heading3
241
+ return `### ${textElementsToMarkdown(block.heading3?.elements)}\n`;
242
+ case 12: // Bullet
243
+ return `- ${textElementsToMarkdown(block.bullet?.elements)}`;
244
+ case 13: // Ordered
245
+ return `1. ${textElementsToMarkdown(block.ordered?.elements)}`;
246
+ case 14: // Code
247
+ const lang = getLanguageName(block.code?.style?.language || 0);
248
+ const codeText = textElementsToMarkdown(block.code?.elements);
249
+ return `\`\`\`${lang}\n${codeText}\n\`\`\`\n`;
250
+ case 15: // Quote
251
+ return `> ${textElementsToMarkdown(block.quote?.elements)}\n`;
252
+ case 17: // TodoList
253
+ const checked = block.todo?.style?.done ? 'x' : ' ';
254
+ return `- [${checked}] ${textElementsToMarkdown(block.todo?.elements)}`;
255
+ case 18: // Divider
256
+ return '---\n';
257
+ default:
258
+ return null;
259
+ }
260
+ }
261
+
262
+ it('应转换文本块', () => {
263
+ const block = {
264
+ block_type: 2,
265
+ text: { elements: [{ text_run: { content: 'Hello' } }] }
266
+ };
267
+ assert.strictEqual(blockToMarkdown(block), 'Hello\n');
268
+ });
269
+
270
+ it('应转换各级标题', () => {
271
+ assert.strictEqual(
272
+ blockToMarkdown({ block_type: 3, heading1: { elements: [{ text_run: { content: 'H1' } }] } }),
273
+ '# H1\n'
274
+ );
275
+ assert.strictEqual(
276
+ blockToMarkdown({ block_type: 4, heading2: { elements: [{ text_run: { content: 'H2' } }] } }),
277
+ '## H2\n'
278
+ );
279
+ assert.strictEqual(
280
+ blockToMarkdown({ block_type: 5, heading3: { elements: [{ text_run: { content: 'H3' } }] } }),
281
+ '### H3\n'
282
+ );
283
+ });
284
+
285
+ it('应转换无序列表', () => {
286
+ const block = {
287
+ block_type: 12,
288
+ bullet: { elements: [{ text_run: { content: 'Item' } }] }
289
+ };
290
+ assert.strictEqual(blockToMarkdown(block), '- Item');
291
+ });
292
+
293
+ it('应转换有序列表', () => {
294
+ const block = {
295
+ block_type: 13,
296
+ ordered: { elements: [{ text_run: { content: 'First' } }] }
297
+ };
298
+ assert.strictEqual(blockToMarkdown(block), '1. First');
299
+ });
300
+
301
+ it('应转换代码块', () => {
302
+ const block = {
303
+ block_type: 14,
304
+ code: {
305
+ elements: [{ text_run: { content: 'console.log("hi")' } }],
306
+ style: { language: 30 }
307
+ }
308
+ };
309
+ assert.strictEqual(blockToMarkdown(block), '```javascript\nconsole.log("hi")\n```\n');
310
+ });
311
+
312
+ it('应转换引用块', () => {
313
+ const block = {
314
+ block_type: 15,
315
+ quote: { elements: [{ text_run: { content: 'Quote text' } }] }
316
+ };
317
+ assert.strictEqual(blockToMarkdown(block), '> Quote text\n');
318
+ });
319
+
320
+ it('应转换待办事项(未完成)', () => {
321
+ const block = {
322
+ block_type: 17,
323
+ todo: {
324
+ elements: [{ text_run: { content: 'Todo item' } }],
325
+ style: { done: false }
326
+ }
327
+ };
328
+ assert.strictEqual(blockToMarkdown(block), '- [ ] Todo item');
329
+ });
330
+
331
+ it('应转换待办事项(已完成)', () => {
332
+ const block = {
333
+ block_type: 17,
334
+ todo: {
335
+ elements: [{ text_run: { content: 'Done item' } }],
336
+ style: { done: true }
337
+ }
338
+ };
339
+ assert.strictEqual(blockToMarkdown(block), '- [x] Done item');
340
+ });
341
+
342
+ it('应转换分割线', () => {
343
+ assert.strictEqual(blockToMarkdown({ block_type: 18 }), '---\n');
344
+ });
345
+ });
@@ -0,0 +1,273 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+
4
+ // 复制 NotionService 中的纯函数进行测试
5
+
6
+ function parsePageId(url: string): string {
7
+ const match = url.match(/([a-f0-9]{32})|([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
8
+ if (!match) {
9
+ throw new Error(`无法解析 Notion 页面 URL: ${url}`);
10
+ }
11
+ return match[0].replace(/-/g, '');
12
+ }
13
+
14
+ function extractPropertyValue(prop: any): string {
15
+ if (!prop) return '';
16
+ switch (prop.type) {
17
+ case 'title':
18
+ return prop.title?.map((t: any) => t.plain_text).join('') || '';
19
+ case 'rich_text':
20
+ return prop.rich_text?.map((t: any) => t.plain_text).join('') || '';
21
+ case 'select':
22
+ return prop.select?.name || '';
23
+ case 'multi_select':
24
+ return prop.multi_select?.map((s: any) => s.name).join(', ') || '';
25
+ case 'number':
26
+ return prop.number?.toString() ?? '';
27
+ case 'checkbox':
28
+ return prop.checkbox ? '✓' : '';
29
+ case 'date':
30
+ return prop.date?.start || '';
31
+ case 'url':
32
+ return prop.url || '';
33
+ case 'files':
34
+ return prop.files?.map((f: any) => f.name || 'file').join(', ') || '';
35
+ default:
36
+ return '';
37
+ }
38
+ }
39
+
40
+ describe('Notion URL 解析', () => {
41
+ describe('标准 32 字符 ID', () => {
42
+ it('应解析简单 URL', () => {
43
+ const result = parsePageId('https://www.notion.so/abc123def456789012345678901234ab');
44
+ assert.strictEqual(result, 'abc123def456789012345678901234ab');
45
+ });
46
+
47
+ it('应解析带工作区前缀的 URL', () => {
48
+ const result = parsePageId('https://www.notion.so/myworkspace/Page-Title-abc123def456789012345678901234ab');
49
+ assert.strictEqual(result, 'abc123def456789012345678901234ab');
50
+ });
51
+
52
+ it('应解析带查询参数的 URL', () => {
53
+ const result = parsePageId('https://www.notion.so/abc123def456789012345678901234ab?v=viewid123');
54
+ assert.strictEqual(result, 'abc123def456789012345678901234ab');
55
+ });
56
+
57
+ it('应解析 notion.so 域名(无 www)', () => {
58
+ const result = parsePageId('https://notion.so/abc123def456789012345678901234ab');
59
+ assert.strictEqual(result, 'abc123def456789012345678901234ab');
60
+ });
61
+ });
62
+
63
+ describe('UUID 格式 (带连字符)', () => {
64
+ it('应解析 UUID 格式并移除连字符', () => {
65
+ const result = parsePageId('https://www.notion.so/abc12345-6789-0abc-def1-234567890123');
66
+ assert.strictEqual(result, 'abc1234567890abcdef1234567890123');
67
+ });
68
+
69
+ it('应解析带标题的 UUID URL', () => {
70
+ const result = parsePageId('https://www.notion.so/workspace/My-Page-abc12345-6789-0abc-def1-234567890123');
71
+ assert.strictEqual(result, 'abc1234567890abcdef1234567890123');
72
+ });
73
+ });
74
+
75
+ describe('无效 URL', () => {
76
+ it('应拒绝没有 ID 的 URL', () => {
77
+ assert.throws(
78
+ () => parsePageId('https://www.notion.so/'),
79
+ /无法解析 Notion 页面 URL/
80
+ );
81
+ });
82
+
83
+ it('应拒绝 ID 太短的 URL', () => {
84
+ assert.throws(
85
+ () => parsePageId('https://www.notion.so/abc123'),
86
+ /无法解析 Notion 页面 URL/
87
+ );
88
+ });
89
+
90
+ it('只检查 ID 格式,不验证域名', () => {
91
+ // 当前实现不验证域名,只检查是否存在 32 位十六进制 ID
92
+ const result = parsePageId('https://example.com/abc123def456789012345678901234ab');
93
+ assert.strictEqual(result, 'abc123def456789012345678901234ab');
94
+ });
95
+
96
+ it('应拒绝包含非法字符的 ID', () => {
97
+ assert.throws(
98
+ () => parsePageId('https://www.notion.so/xyz!@#$%^&*()'),
99
+ /无法解析 Notion 页面 URL/
100
+ );
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('Notion 属性值提取', () => {
106
+ describe('title 属性', () => {
107
+ it('应提取 title 文本', () => {
108
+ const prop = {
109
+ type: 'title',
110
+ title: [{ plain_text: 'Page Title' }]
111
+ };
112
+ assert.strictEqual(extractPropertyValue(prop), 'Page Title');
113
+ });
114
+
115
+ it('应合并多个 title 片段', () => {
116
+ const prop = {
117
+ type: 'title',
118
+ title: [{ plain_text: 'Hello ' }, { plain_text: 'World' }]
119
+ };
120
+ assert.strictEqual(extractPropertyValue(prop), 'Hello World');
121
+ });
122
+
123
+ it('应处理空 title', () => {
124
+ const prop = { type: 'title', title: [] };
125
+ assert.strictEqual(extractPropertyValue(prop), '');
126
+ });
127
+ });
128
+
129
+ describe('rich_text 属性', () => {
130
+ it('应提取富文本', () => {
131
+ const prop = {
132
+ type: 'rich_text',
133
+ rich_text: [{ plain_text: 'Some text' }]
134
+ };
135
+ assert.strictEqual(extractPropertyValue(prop), 'Some text');
136
+ });
137
+ });
138
+
139
+ describe('select 属性', () => {
140
+ it('应提取选项名称', () => {
141
+ const prop = {
142
+ type: 'select',
143
+ select: { name: 'Option A' }
144
+ };
145
+ assert.strictEqual(extractPropertyValue(prop), 'Option A');
146
+ });
147
+
148
+ it('应处理空选择', () => {
149
+ const prop = { type: 'select', select: null };
150
+ assert.strictEqual(extractPropertyValue(prop), '');
151
+ });
152
+ });
153
+
154
+ describe('multi_select 属性', () => {
155
+ it('应提取多个选项', () => {
156
+ const prop = {
157
+ type: 'multi_select',
158
+ multi_select: [{ name: 'Tag1' }, { name: 'Tag2' }, { name: 'Tag3' }]
159
+ };
160
+ assert.strictEqual(extractPropertyValue(prop), 'Tag1, Tag2, Tag3');
161
+ });
162
+
163
+ it('应处理空多选', () => {
164
+ const prop = { type: 'multi_select', multi_select: [] };
165
+ assert.strictEqual(extractPropertyValue(prop), '');
166
+ });
167
+ });
168
+
169
+ describe('number 属性', () => {
170
+ it('应转换数字为字符串', () => {
171
+ const prop = { type: 'number', number: 42 };
172
+ assert.strictEqual(extractPropertyValue(prop), '42');
173
+ });
174
+
175
+ it('应处理浮点数', () => {
176
+ const prop = { type: 'number', number: 3.14 };
177
+ assert.strictEqual(extractPropertyValue(prop), '3.14');
178
+ });
179
+
180
+ it('应处理零', () => {
181
+ const prop = { type: 'number', number: 0 };
182
+ assert.strictEqual(extractPropertyValue(prop), '0');
183
+ });
184
+
185
+ it('应处理 null 数字', () => {
186
+ const prop = { type: 'number', number: null };
187
+ assert.strictEqual(extractPropertyValue(prop), '');
188
+ });
189
+ });
190
+
191
+ describe('checkbox 属性', () => {
192
+ it('应返回勾选标记', () => {
193
+ const prop = { type: 'checkbox', checkbox: true };
194
+ assert.strictEqual(extractPropertyValue(prop), '✓');
195
+ });
196
+
197
+ it('应返回空字符串表示未勾选', () => {
198
+ const prop = { type: 'checkbox', checkbox: false };
199
+ assert.strictEqual(extractPropertyValue(prop), '');
200
+ });
201
+ });
202
+
203
+ describe('date 属性', () => {
204
+ it('应提取开始日期', () => {
205
+ const prop = {
206
+ type: 'date',
207
+ date: { start: '2024-01-15' }
208
+ };
209
+ assert.strictEqual(extractPropertyValue(prop), '2024-01-15');
210
+ });
211
+
212
+ it('应处理带时间的日期', () => {
213
+ const prop = {
214
+ type: 'date',
215
+ date: { start: '2024-01-15T10:30:00' }
216
+ };
217
+ assert.strictEqual(extractPropertyValue(prop), '2024-01-15T10:30:00');
218
+ });
219
+
220
+ it('应处理空日期', () => {
221
+ const prop = { type: 'date', date: null };
222
+ assert.strictEqual(extractPropertyValue(prop), '');
223
+ });
224
+ });
225
+
226
+ describe('url 属性', () => {
227
+ it('应提取 URL', () => {
228
+ const prop = {
229
+ type: 'url',
230
+ url: 'https://example.com'
231
+ };
232
+ assert.strictEqual(extractPropertyValue(prop), 'https://example.com');
233
+ });
234
+
235
+ it('应处理空 URL', () => {
236
+ const prop = { type: 'url', url: null };
237
+ assert.strictEqual(extractPropertyValue(prop), '');
238
+ });
239
+ });
240
+
241
+ describe('files 属性', () => {
242
+ it('应提取文件名', () => {
243
+ const prop = {
244
+ type: 'files',
245
+ files: [{ name: 'doc.pdf' }, { name: 'image.png' }]
246
+ };
247
+ assert.strictEqual(extractPropertyValue(prop), 'doc.pdf, image.png');
248
+ });
249
+
250
+ it('应处理无文件名的情况', () => {
251
+ const prop = {
252
+ type: 'files',
253
+ files: [{ url: 'https://...' }]
254
+ };
255
+ assert.strictEqual(extractPropertyValue(prop), 'file');
256
+ });
257
+ });
258
+
259
+ describe('边界情况', () => {
260
+ it('应处理 null 属性', () => {
261
+ assert.strictEqual(extractPropertyValue(null), '');
262
+ });
263
+
264
+ it('应处理 undefined 属性', () => {
265
+ assert.strictEqual(extractPropertyValue(undefined), '');
266
+ });
267
+
268
+ it('应处理未知属性类型', () => {
269
+ const prop = { type: 'unknown_type', value: 'test' };
270
+ assert.strictEqual(extractPropertyValue(prop), '');
271
+ });
272
+ });
273
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }