'use strict'; /** * 產生台灣時區(Asia/Taipei)的時間戳,格式固定為 `yyyy/MM/dd HH:mm:ss`。 * * 供 log 訊息統一格式 `[{階段}][{等級}][{時間}]: {訊息}` 的時間欄位使用。 * * @returns {string} 台灣時區當下時間字串,例如 `2026/06/30 12:13:04`。 */ function taipeiTimestamp() { const parts = new Intl.DateTimeFormat('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).formatToParts(new Date()); const get = (type) => parts.find((part) => part.type === type)?.value ?? ''; // 部分執行環境會把 24 時制的午夜輸出為 "24",統一正規化為 "00" const hour = get('hour') === '24' ? '00' : get('hour'); return `${get('year')}/${get('month')}/${get('day')} ${hour}:${get('minute')}:${get('second')}`; } /** * 將單一 log 訊息寫入指定輸出串流,格式統一為 `[{階段}][{等級}][{時間}]: {訊息}` 並於結尾換行。 * * 階段(stage)為選填;省略或空字串時略過開頭的 `[{階段}]` 區塊,輸出退化為 `[{等級}][{時間}]: {訊息}`。 * 一次呼叫只輸出一則訊息(一行一則)。 * * @param {NodeJS.WriteStream} stream - 目標輸出串流(process.stdout 或 process.stderr)。 * @param {string} level - 訊息等級,限 `INF`/`WRN`/`ERR`/`TRC`/`DBG`。 * @param {string} message - 要輸出的訊息內容。 * @param {string} [stage] - 階段名稱(選填)。 * @returns {void} */ function write(stream, level, message, stage) { const stagePrefix = stage ? `[${stage}]` : ''; stream.write(`${stagePrefix}[${level}][${taipeiTimestamp()}]: ${message}\n`); } /** * 輸出一般資訊(INF)層級的 log 訊息至標準輸出。 * * 訊息格式統一為 `[{階段}][INF][{台灣時間}]: {訊息}`,時間使用台灣時區(Asia/Taipei), * 格式為 `yyyy/MM/dd HH:mm:ss`,並於結尾換行;未提供 stage 時略過 `[{階段}]` 區塊。 * * @param {string} message - 要輸出的資訊內容。 * @param {string} [stage] - 階段名稱(選填),用於在訊息開頭標示所屬處理階段。 * @returns {void} * @remarks 用於回報正常流程進度(如設定值、URL、各頁取得筆數);可直接呼叫,或經 forStage() * 取得綁定階段的子記錄器後以單一 message 參數呼叫。 */ function info(message, stage) { write(process.stdout, 'INF', message, stage); } /** * 輸出錯誤(ERR)層級的 log 訊息至標準錯誤輸出(stderr)。 * * 訊息格式統一為 `[{階段}][ERR][{台灣時間}]: {訊息}`,時間使用台灣時區(Asia/Taipei), * 格式為 `yyyy/MM/dd HH:mm:ss`,並於結尾換行;未提供 stage 時略過 `[{階段}]` 區塊。 * * 僅負責輸出,不終止行程;是否結束由呼叫端(進入點)決定,以利測試與錯誤復原。 * * @param {string} message - 要輸出的錯誤描述內容。 * @param {string} [stage] - 階段名稱(選填),用於在訊息開頭標示所屬處理階段。 * @returns {void} * @remarks 由進入點在頂層捕捉到例外時呼叫(此情境無對應階段,省略 stage),輸出至 stderr 後 * 再由呼叫端決定 exit code。 */ function error(message, stage) { write(process.stderr, 'ERR', message, stage); } /** * 建立綁定特定階段(stage)的子記錄器;其 info/error 會自動帶入該階段名稱, * 呼叫端僅需傳入 message 即可輸出 `[{階段}][{等級}][{時間}]: {訊息}`。 * * @param {string} stage - 階段名稱,例如「參數檢查」「取得舊版本」「計算版本號」。 * @returns {{ info: (message: string) => void, error: (message: string) => void }} 綁定該階段的記錄器。 * @remarks 由進入點為每個處理階段建立一個子記錄器,使該階段(含其呼叫的 fetchReleases 等模組) * 輸出的每一則訊息都帶有一致的階段前綴,取代過往以分隔線區塊標示階段的做法。 */ function forStage(stage) { return { info: (message) => info(message, stage), error: (message) => error(message, stage), }; } module.exports = { info, error, forStage };