@gravito/signal 3.0.4 → 3.1.2

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.
Files changed (124) hide show
  1. package/README.md +26 -15
  2. package/dist/Mailable.d.ts +453 -0
  3. package/dist/OrbitSignal.d.ts +267 -0
  4. package/{src/Queueable.ts → dist/Queueable.d.ts} +1 -1
  5. package/{src/TypedMailable.ts → dist/TypedMailable.d.ts} +12 -13
  6. package/dist/dev/DevMailbox.d.ts +79 -0
  7. package/dist/dev/DevServer.d.ts +39 -0
  8. package/dist/dev/storage/FileMailboxStorage.d.ts +16 -0
  9. package/dist/dev/storage/MailboxStorage.d.ts +14 -0
  10. package/dist/dev/storage/MemoryMailboxStorage.d.ts +13 -0
  11. package/dist/dev/ui/mailbox.d.ts +11 -0
  12. package/dist/dev/ui/preview.d.ts +11 -0
  13. package/dist/dev/ui/shared.d.ts +15 -0
  14. package/dist/errors.d.ts +58 -0
  15. package/{src/events.ts → dist/events.d.ts} +20 -29
  16. package/dist/{lib-HJTRWKU5.mjs → index.cjs} +4687 -10983
  17. package/dist/index.d.ts +303 -1874
  18. package/dist/index.js +317 -45939
  19. package/dist/index.mjs +59748 -27
  20. package/dist/renderers/HtmlRenderer.d.ts +34 -0
  21. package/dist/renderers/MjmlRenderer.d.ts +41 -0
  22. package/dist/renderers/ReactMjmlRenderer.d.ts +38 -0
  23. package/dist/renderers/ReactRenderer.d.ts +46 -0
  24. package/{src/renderers/Renderer.ts → dist/renderers/Renderer.d.ts} +23 -24
  25. package/dist/renderers/TemplateRenderer.d.ts +55 -0
  26. package/dist/renderers/VueMjmlRenderer.d.ts +39 -0
  27. package/dist/renderers/VueRenderer.d.ts +47 -0
  28. package/dist/renderers/mjml-templates.d.ts +11 -0
  29. package/dist/transports/BaseTransport.d.ts +111 -0
  30. package/dist/transports/LogTransport.d.ts +42 -0
  31. package/dist/transports/MemoryTransport.d.ts +51 -0
  32. package/dist/transports/SesTransport.d.ts +79 -0
  33. package/dist/transports/SmtpTransport.d.ts +124 -0
  34. package/dist/transports/Transport.d.ts +44 -0
  35. package/dist/types.d.ts +294 -0
  36. package/{src/utils/html.ts → dist/utils/html.d.ts} +1 -15
  37. package/dist/webhooks/SendGridWebhookDriver.d.ts +45 -0
  38. package/dist/webhooks/SesWebhookDriver.d.ts +23 -0
  39. package/package.json +20 -8
  40. package/CHANGELOG.md +0 -74
  41. package/dist/MjmlRenderer-IUH663FT.mjs +0 -8
  42. package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +0 -8
  43. package/dist/ReactRenderer-24SQ4KRU.mjs +0 -27
  44. package/dist/ReactRenderer-2JFLRVST.mjs +0 -45
  45. package/dist/ReactRenderer-CMCAOEPH.mjs +0 -28
  46. package/dist/ReactRenderer-KYNA4WKE.mjs +0 -28
  47. package/dist/ReactRenderer-LYEOSYFS.mjs +0 -28
  48. package/dist/ReactRenderer-V54CUUEI.mjs +0 -45
  49. package/dist/VueMjmlRenderer-4F4CXHDB.mjs +0 -8
  50. package/dist/VueMjmlRenderer-5WZR4CQG.mjs +0 -8
  51. package/dist/VueMjmlRenderer-U5YMWI44.mjs +0 -8
  52. package/dist/VueRenderer-3YBRQXME.mjs +0 -48
  53. package/dist/VueRenderer-46JGXTJ2.mjs +0 -48
  54. package/dist/VueRenderer-5KWD4R3C.mjs +0 -48
  55. package/dist/VueRenderer-C23U4O5E.mjs +0 -48
  56. package/dist/VueRenderer-DWTCD2RF.mjs +0 -31
  57. package/dist/VueRenderer-IIR5SYTM.mjs +0 -31
  58. package/dist/VueRenderer-LEVDFLHP.mjs +0 -31
  59. package/dist/VueRenderer-RNHSCCRI.mjs +0 -48
  60. package/dist/VueRenderer-SUP66ISX.mjs +0 -29
  61. package/dist/chunk-3WOR3XSL.mjs +0 -82
  62. package/dist/chunk-DBFIVHHG.mjs +0 -79
  63. package/dist/chunk-EBO3CZXG.mjs +0 -15
  64. package/dist/chunk-HEBXNMVQ.mjs +0 -48
  65. package/dist/chunk-KB7IDDBT.mjs +0 -82
  66. package/dist/chunk-LZL5UUPC.mjs +0 -82
  67. package/dist/chunk-W6LXIJKK.mjs +0 -57
  68. package/dist/chunk-XBIVBJS2.mjs +0 -8
  69. package/dist/index.d.mts +0 -2115
  70. package/dist/server-renderer-4IM3P5XZ.mjs +0 -37183
  71. package/dist/server-renderer-4W4FI7YG.mjs +0 -37269
  72. package/dist/server-renderer-7KWFSTPV.mjs +0 -37193
  73. package/dist/server-renderer-S5FPSTJ2.mjs +0 -37183
  74. package/dist/server-renderer-X5LUFVWT.mjs +0 -37193
  75. package/doc/OPTIMIZATION_PLAN.md +0 -496
  76. package/scripts/check-coverage.ts +0 -64
  77. package/src/Mailable.ts +0 -674
  78. package/src/OrbitSignal.ts +0 -451
  79. package/src/dev/DevMailbox.ts +0 -146
  80. package/src/dev/DevServer.ts +0 -192
  81. package/src/dev/storage/FileMailboxStorage.ts +0 -66
  82. package/src/dev/storage/MailboxStorage.ts +0 -15
  83. package/src/dev/storage/MemoryMailboxStorage.ts +0 -36
  84. package/src/dev/ui/mailbox.ts +0 -77
  85. package/src/dev/ui/preview.ts +0 -103
  86. package/src/dev/ui/shared.ts +0 -60
  87. package/src/errors.ts +0 -69
  88. package/src/index.ts +0 -41
  89. package/src/renderers/HtmlRenderer.ts +0 -41
  90. package/src/renderers/MjmlRenderer.ts +0 -73
  91. package/src/renderers/ReactMjmlRenderer.ts +0 -94
  92. package/src/renderers/ReactRenderer.ts +0 -66
  93. package/src/renderers/TemplateRenderer.ts +0 -84
  94. package/src/renderers/VueMjmlRenderer.ts +0 -99
  95. package/src/renderers/VueRenderer.ts +0 -71
  96. package/src/renderers/mjml-templates.ts +0 -50
  97. package/src/transports/BaseTransport.ts +0 -148
  98. package/src/transports/LogTransport.ts +0 -55
  99. package/src/transports/MemoryTransport.ts +0 -55
  100. package/src/transports/SesTransport.ts +0 -129
  101. package/src/transports/SmtpTransport.ts +0 -184
  102. package/src/transports/Transport.ts +0 -45
  103. package/src/types.ts +0 -309
  104. package/src/webhooks/SendGridWebhookDriver.ts +0 -80
  105. package/src/webhooks/SesWebhookDriver.ts +0 -44
  106. package/tests/DevMailbox.test.ts +0 -54
  107. package/tests/FileMailboxStorage.test.ts +0 -56
  108. package/tests/MjmlLayout.test.ts +0 -28
  109. package/tests/MjmlRenderer.test.ts +0 -53
  110. package/tests/OrbitSignalWebhook.test.ts +0 -56
  111. package/tests/ReactMjmlRenderer.test.ts +0 -33
  112. package/tests/SendGridWebhookDriver.test.ts +0 -69
  113. package/tests/SesWebhookDriver.test.ts +0 -46
  114. package/tests/VueMjmlRenderer.test.ts +0 -35
  115. package/tests/dev-server.test.ts +0 -66
  116. package/tests/log-transport.test.ts +0 -21
  117. package/tests/mailable-extra.test.ts +0 -68
  118. package/tests/mailable.test.ts +0 -77
  119. package/tests/orbit-signal.test.ts +0 -43
  120. package/tests/renderers.test.ts +0 -58
  121. package/tests/template-renderer.test.ts +0 -24
  122. package/tests/transports.test.ts +0 -52
  123. package/tests/ui.test.ts +0 -37
  124. package/tsconfig.json +0 -14
@@ -1,496 +0,0 @@
1
- # @gravito/signal 優化改進計劃
2
-
3
- ## 目前架構概覽
4
-
5
- `@gravito/signal` 是 Gravito 框架的郵件服務模組,提供以下核心功能:
6
-
7
- - **多傳輸層支援**:SMTP、AWS SES、Log、Memory
8
- - **多渲染引擎**:HTML、Template (Prism)、React、Vue
9
- - **開發模式**:內建郵件攔截與預覽 UI (`/__mail`)
10
- - **佇列整合**:透過 `@gravito/stream` 支援非同步發送
11
- - **國際化支援**:內建翻譯函式整合
12
-
13
- ---
14
-
15
- ## 第一階段:程式碼重構與 JSDoc 增強
16
-
17
- ### 1.1 JSDoc 文檔優化
18
-
19
- **目標**:增強 AI 可讀性與開發者體驗
20
-
21
- | 檔案 | 優先級 | 改進內容 |
22
- |------|--------|----------|
23
- | `types.ts` | 高 | 補充 `@example`、`@see` 關聯說明 |
24
- | `Mailable.ts` | 高 | 增加完整使用範例、方法間的關聯說明 |
25
- | `OrbitSignal.ts` | 高 | 補充配置說明、錯誤處理說明 |
26
- | `transports/*.ts` | 中 | 增加配置範例、錯誤碼說明 |
27
- | `renderers/*.ts` | 中 | 補充渲染流程說明 |
28
-
29
- **具體改進項目**:
30
-
31
- ```typescript
32
- // 範例:增強 Mailable 的 JSDoc
33
- /**
34
- * Base class for all mailable messages.
35
- *
36
- * @description
37
- * Mailable 提供流式 API 來建構郵件信封並使用多種引擎渲染內容。
38
- * 支援 HTML、Prism 模板、React 與 Vue 組件渲染。
39
- *
40
- * @architecture
41
- * ```
42
- * Mailable
43
- * ├── Envelope (from, to, subject, etc.)
44
- * ├── Renderer (HtmlRenderer | TemplateRenderer | ReactRenderer | VueRenderer)
45
- * └── Queueable (佇列支援介面)
46
- * ```
47
- *
48
- * @lifecycle
49
- * 1. 建立 Mailable 子類別
50
- * 2. 實作 build() 方法設定信封與內容
51
- * 3. 呼叫 send() 或 queue() 發送
52
- *
53
- * @see {@link OrbitSignal} 郵件服務主類別
54
- * @see {@link Renderer} 內容渲染器介面
55
- * @see {@link Queueable} 佇列介面
56
- */
57
- ```
58
-
59
- ### 1.2 stripHtml 方法重複問題
60
-
61
- **現狀**:`HtmlRenderer` 與 `TemplateRenderer` 各自實作相同的 `stripHtml` 方法
62
-
63
- **改進方案**:
64
-
65
- ```typescript
66
- // 新增 src/utils/html.ts
67
- /**
68
- * HTML 工具函式集合
69
- *
70
- * @module utils/html
71
- * @since 3.1.0
72
- */
73
-
74
- /**
75
- * 將 HTML 內容轉換為純文字。
76
- *
77
- * @description
78
- * 移除所有 HTML 標籤、樣式、腳本,並正規化空白字元。
79
- * 用於產生郵件的純文字版本。
80
- *
81
- * @param html - 原始 HTML 字串
82
- * @returns 純文字內容
83
- *
84
- * @example
85
- * ```typescript
86
- * const text = stripHtml('<h1>Hello</h1><p>World</p>')
87
- * // Returns: 'Hello World'
88
- * ```
89
- */
90
- export function stripHtml(html: string): string {
91
- return html
92
- .replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, '')
93
- .replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, '')
94
- .replace(/<[^>]+>/g, '')
95
- .replace(/&nbsp;/g, ' ')
96
- .replace(/&amp;/g, '&')
97
- .replace(/&lt;/g, '<')
98
- .replace(/&gt;/g, '>')
99
- .replace(/&quot;/g, '"')
100
- .replace(/&#39;/g, "'")
101
- .replace(/\s+/g, ' ')
102
- .trim()
103
- }
104
- ```
105
-
106
- ---
107
-
108
- ## 第二階段:功能增強
109
-
110
- ### 2.1 傳輸層錯誤處理增強
111
-
112
- **現狀**:傳輸層錯誤直接拋出,缺乏統一的錯誤類型
113
-
114
- **改進方案**:
115
-
116
- ```typescript
117
- // 新增 src/errors.ts
118
- /**
119
- * 郵件傳輸相關的錯誤類別
120
- *
121
- * @since 3.1.0
122
- */
123
- export class MailTransportError extends Error {
124
- constructor(
125
- message: string,
126
- public readonly code: MailErrorCode,
127
- public readonly cause?: Error
128
- ) {
129
- super(message)
130
- this.name = 'MailTransportError'
131
- }
132
- }
133
-
134
- export enum MailErrorCode {
135
- /** 連線失敗 */
136
- CONNECTION_FAILED = 'CONNECTION_FAILED',
137
- /** 認證失敗 */
138
- AUTH_FAILED = 'AUTH_FAILED',
139
- /** 收件人被拒 */
140
- RECIPIENT_REJECTED = 'RECIPIENT_REJECTED',
141
- /** 訊息被拒 */
142
- MESSAGE_REJECTED = 'MESSAGE_REJECTED',
143
- /** 速率限制 */
144
- RATE_LIMIT = 'RATE_LIMIT',
145
- /** 未知錯誤 */
146
- UNKNOWN = 'UNKNOWN',
147
- }
148
- ```
149
-
150
- ### 2.2 重試機制
151
-
152
- **目標**:為傳輸層增加可配置的重試機制
153
-
154
- ```typescript
155
- // Transport 介面擴展
156
- export interface TransportOptions {
157
- /** 最大重試次數,預設 3 */
158
- maxRetries?: number
159
- /** 重試延遲(毫秒),預設 1000 */
160
- retryDelay?: number
161
- /** 指數退避乘數,預設 2 */
162
- backoffMultiplier?: number
163
- }
164
-
165
- // 新增 BaseTransport 抽象類別
166
- export abstract class BaseTransport implements Transport {
167
- protected options: Required<TransportOptions>
168
-
169
- constructor(options?: TransportOptions) {
170
- this.options = {
171
- maxRetries: options?.maxRetries ?? 3,
172
- retryDelay: options?.retryDelay ?? 1000,
173
- backoffMultiplier: options?.backoffMultiplier ?? 2,
174
- }
175
- }
176
-
177
- async send(message: Message): Promise<void> {
178
- let lastError: Error | undefined
179
- let delay = this.options.retryDelay
180
-
181
- for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {
182
- try {
183
- return await this.doSend(message)
184
- } catch (error) {
185
- lastError = error as Error
186
- if (attempt < this.options.maxRetries) {
187
- await this.sleep(delay)
188
- delay *= this.options.backoffMultiplier
189
- }
190
- }
191
- }
192
-
193
- throw new MailTransportError(
194
- `發送失敗,已重試 ${this.options.maxRetries} 次`,
195
- MailErrorCode.UNKNOWN,
196
- lastError
197
- )
198
- }
199
-
200
- protected abstract doSend(message: Message): Promise<void>
201
-
202
- private sleep(ms: number): Promise<void> {
203
- return new Promise((resolve) => setTimeout(resolve, ms))
204
- }
205
- }
206
- ```
207
-
208
- ### 2.3 事件鉤子系統
209
-
210
- **目標**:提供郵件生命週期事件鉤子
211
-
212
- ```typescript
213
- // 新增 src/events.ts
214
- export type MailEventType =
215
- | 'beforeSend'
216
- | 'afterSend'
217
- | 'sendFailed'
218
- | 'beforeRender'
219
- | 'afterRender'
220
-
221
- export interface MailEvent {
222
- type: MailEventType
223
- mailable: Mailable
224
- message?: Message
225
- error?: Error
226
- timestamp: Date
227
- }
228
-
229
- export type MailEventHandler = (event: MailEvent) => void | Promise<void>
230
-
231
- // OrbitSignal 擴展
232
- export class OrbitSignal implements GravitoOrbit {
233
- private eventHandlers = new Map<MailEventType, MailEventHandler[]>()
234
-
235
- /**
236
- * 註冊事件處理器
237
- *
238
- * @example
239
- * ```typescript
240
- * mail.on('afterSend', async (event) => {
241
- * await analytics.track('email_sent', {
242
- * to: event.message?.to,
243
- * subject: event.message?.subject
244
- * })
245
- * })
246
- * ```
247
- */
248
- on(event: MailEventType, handler: MailEventHandler): this {
249
- const handlers = this.eventHandlers.get(event) || []
250
- handlers.push(handler)
251
- this.eventHandlers.set(event, handlers)
252
- return this
253
- }
254
-
255
- private async emit(event: MailEvent): Promise<void> {
256
- const handlers = this.eventHandlers.get(event.type) || []
257
- for (const handler of handlers) {
258
- await handler(event)
259
- }
260
- }
261
- }
262
- ```
263
-
264
- ---
265
-
266
- ## 第三階段:效能優化
267
-
268
- ### 3.1 渲染器快取
269
-
270
- **目標**:快取已編譯的模板以提升效能
271
-
272
- ```typescript
273
- // TemplateRenderer 增強
274
- export class TemplateRenderer implements Renderer {
275
- private static engineCache = new Map<string, TemplateEngine>()
276
-
277
- private getEngine(): TemplateEngine {
278
- const cached = TemplateRenderer.engineCache.get(this.viewsDir)
279
- if (cached) return cached
280
-
281
- const engine = new TemplateEngine(this.viewsDir)
282
- TemplateRenderer.engineCache.set(this.viewsDir, engine)
283
- return engine
284
- }
285
-
286
- /**
287
- * 清除模板引擎快取
288
- *
289
- * @description
290
- * 在開發模式下,當模板檔案變更時呼叫此方法。
291
- */
292
- static clearCache(): void {
293
- TemplateRenderer.engineCache.clear()
294
- }
295
- }
296
- ```
297
-
298
- ### 3.2 連線池管理
299
-
300
- **目標**:為 SMTP 傳輸實作連線池
301
-
302
- ```typescript
303
- // SmtpTransport 增強
304
- export interface SmtpConfig {
305
- // ... 現有配置
306
- /** 連線池大小,預設 5 */
307
- poolSize?: number
308
- /** 連線最大閒置時間(毫秒),預設 30000 */
309
- maxIdleTime?: number
310
- }
311
-
312
- export class SmtpTransport implements Transport {
313
- constructor(config: SmtpConfig) {
314
- this.transporter = nodemailer.createTransport({
315
- ...config,
316
- pool: true,
317
- maxConnections: config.poolSize ?? 5,
318
- maxMessages: Infinity,
319
- rateDelta: 1000,
320
- rateLimit: 10,
321
- })
322
- }
323
-
324
- /**
325
- * 關閉連線池
326
- *
327
- * @description
328
- * 在應用程式關閉時呼叫以釋放資源。
329
- */
330
- async close(): Promise<void> {
331
- this.transporter.close()
332
- }
333
-
334
- /**
335
- * 驗證 SMTP 連線
336
- *
337
- * @returns 連線是否成功
338
- */
339
- async verify(): Promise<boolean> {
340
- try {
341
- await this.transporter.verify()
342
- return true
343
- } catch {
344
- return false
345
- }
346
- }
347
- }
348
- ```
349
-
350
- ---
351
-
352
- ## 第四階段:開發者體驗增強
353
-
354
- ### 4.1 Dev UI 功能增強
355
-
356
- **目標**:增強開發郵件預覽 UI 的功能
357
-
358
- - [ ] 郵件搜尋功能
359
- - [ ] 標籤/分類過濾
360
- - [ ] 響應式預覽(桌面/平板/手機)
361
- - [ ] 郵件匯出功能(EML 格式)
362
- - [ ] 批次刪除功能
363
- - [ ] WebSocket 即時更新
364
-
365
- ### 4.2 CLI 工具
366
-
367
- **目標**:提供郵件相關的 CLI 命令
368
-
369
- ```bash
370
- # 發送測試郵件
371
- bun gravito mail:test --to test@example.com
372
-
373
- # 預覽 Mailable
374
- bun gravito mail:preview WelcomeEmail --data '{"name": "John"}'
375
-
376
- # 清空開發郵箱
377
- bun gravito mail:clear
378
- ```
379
-
380
- ### 4.3 型別安全增強
381
-
382
- **目標**:提升 TypeScript 型別推斷
383
-
384
- ```typescript
385
- // 強型別 Mailable
386
- export abstract class TypedMailable<TData extends Record<string, unknown>> extends Mailable {
387
- protected abstract data: TData
388
-
389
- view<K extends keyof TData>(template: string, data: TData): this {
390
- // 編譯時型別檢查
391
- return super.view(template, data)
392
- }
393
- }
394
-
395
- // 使用範例
396
- interface WelcomeData {
397
- name: string
398
- email: string
399
- activationUrl: string
400
- }
401
-
402
- class WelcomeEmail extends TypedMailable<WelcomeData> {
403
- protected data: WelcomeData
404
-
405
- constructor(data: WelcomeData) {
406
- super()
407
- this.data = data
408
- }
409
-
410
- build() {
411
- return this
412
- .to(this.data.email)
413
- .subject('歡迎加入!')
414
- .view('welcome', this.data) // 型別安全
415
- }
416
- }
417
- ```
418
-
419
- ---
420
-
421
- ## 第五階段:測試與品質保證
422
-
423
- ### 5.1 測試覆蓋率目標
424
-
425
- | 模組 | 目前覆蓋率 | 目標覆蓋率 |
426
- |------|-----------|-----------|
427
- | OrbitSignal | ~75% | 90% |
428
- | Mailable | ~70% | 90% |
429
- | Transports | ~60% | 85% |
430
- | Renderers | ~65% | 85% |
431
- | DevServer | ~50% | 80% |
432
-
433
- ### 5.2 新增測試案例
434
-
435
- - [ ] 傳輸層重試機制測試
436
- - [ ] 事件鉤子系統測試
437
- - [ ] 連線池管理測試
438
- - [ ] 渲染器快取測試
439
- - [ ] 錯誤處理邊界情況測試
440
- - [ ] 國際化功能測試
441
- - [ ] 佇列整合測試
442
-
443
- ### 5.3 效能基準測試
444
-
445
- ```typescript
446
- // benchmarks/mail-sending.bench.ts
447
- import { bench, describe } from 'vitest'
448
-
449
- describe('Mail Sending Performance', () => {
450
- bench('SmtpTransport - single message', async () => {
451
- await transport.send(testMessage)
452
- })
453
-
454
- bench('SmtpTransport - 100 messages concurrent', async () => {
455
- await Promise.all(
456
- Array.from({ length: 100 }, () => transport.send(testMessage))
457
- )
458
- })
459
-
460
- bench('TemplateRenderer - with cache', async () => {
461
- await renderer.render(testData)
462
- })
463
- })
464
- ```
465
-
466
- ---
467
-
468
- ## 實施時程建議
469
-
470
- | 階段 | 內容 | 優先級 |
471
- |------|------|--------|
472
- | 第一階段 | JSDoc 增強、程式碼重構 | 高 |
473
- | 第二階段 | 錯誤處理、重試機制、事件系統 | 高 |
474
- | 第三階段 | 效能優化(快取、連線池) | 中 |
475
- | 第四階段 | 開發者體驗(Dev UI、CLI) | 中 |
476
- | 第五階段 | 測試與品質保證 | 高 |
477
-
478
- ---
479
-
480
- ## 破壞性變更注意事項
481
-
482
- 以下改進可能導致破壞性變更,需要在下一個主版本(4.0)中實施:
483
-
484
- 1. **Transport 介面變更**:增加 `close()` 方法
485
- 2. **錯誤類型變更**:使用自定義錯誤類別
486
- 3. **配置選項變更**:傳輸層配置擴展
487
-
488
- 建議在 3.x 版本中以可選方式引入新功能,在 4.0 版本中設為預設行為。
489
-
490
- ---
491
-
492
- ## 參考資源
493
-
494
- - [Nodemailer 文檔](https://nodemailer.com/)
495
- - [AWS SES 最佳實踐](https://docs.aws.amazon.com/ses/latest/dg/best-practices.html)
496
- - [郵件傳送最佳實踐](https://postmarkapp.com/guides/best-practices-for-email-delivery)
@@ -1,64 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs'
2
- import { resolve } from 'node:path'
3
-
4
- const lcovPath = process.argv[2] ?? 'coverage/lcov.info'
5
- const threshold = Number.parseFloat(process.env.COVERAGE_THRESHOLD ?? '80')
6
-
7
- const root = resolve(process.cwd())
8
- const srcRoot = `${resolve(root, 'src')}/`
9
-
10
- // 檢查 lcov.info 是否存在
11
- if (!existsSync(lcovPath)) {
12
- console.error(`Coverage file not found: ${lcovPath}`)
13
- process.exit(1)
14
- }
15
-
16
- let content: string
17
- try {
18
- content = readFileSync(lcovPath, 'utf-8')
19
- } catch (error) {
20
- console.error(`Failed to read coverage file: ${error}`)
21
- process.exit(1)
22
- }
23
-
24
- const lines = content.split('\n')
25
-
26
- let currentFile: string | null = null
27
- let total = 0
28
- let hit = 0
29
-
30
- for (const line of lines) {
31
- if (line.startsWith('SF:')) {
32
- const filePath = line.slice(3).trim()
33
- const abs = resolve(root, filePath)
34
- currentFile = abs.startsWith(srcRoot) ? abs : null
35
- continue
36
- }
37
-
38
- if (!currentFile) {
39
- continue
40
- }
41
-
42
- if (line.startsWith('DA:')) {
43
- const parts = line.slice(3).split(',')
44
- if (parts.length >= 2) {
45
- total += 1
46
- const count = Number.parseInt(parts[1] ?? '0', 10)
47
- if (count > 0) {
48
- hit += 1
49
- }
50
- }
51
- }
52
- }
53
-
54
- const percent = total === 0 ? 0 : (hit / total) * 100
55
- const rounded = Math.round(percent * 100) / 100
56
-
57
- if (rounded < threshold) {
58
- console.error(
59
- `signal coverage ${rounded}% is below threshold ${threshold}%. Covered lines: ${hit}/${total}.`
60
- )
61
- process.exit(1)
62
- }
63
-
64
- console.log(`signal coverage ${rounded}% (${hit}/${total}) meets threshold ${threshold}%.`)