feat: 完成 A 群組 — 專案骨架與可重啟的服務

- packages/shared:TypeScript 專案參考建置鏈路、統一日誌模組(Asia/Taipei 格式)
- apps/api:最小 NestJS 服務,GET /health
- apps/web:最小 Next.js(App Router + Tailwind v4)首頁,套用品牌色票與亮暗模式
- scripts/dev-restart.mjs:讀 .dev-pids 以 process group 訊號安全停止舊行程、背景啟動 api/web 並輪詢健康檢查
- scripts/smoke:npm run smoke -- <群組代號> 冒煙測試框架,A 群組測試涵蓋 /health 與首頁內容
- 本機 port 3000 被既有服務佔用,web 開發埠改為 3100(已記錄於 todo.md)

npm run restart && npm run smoke -- A 皆通過(A-V)。
This commit is contained in:
Jeffery
2026-08-13 09:18:40 +08:00
parent 80b1383406
commit ae9c44dde7
29 changed files with 8991 additions and 8 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@kokorone/shared",
"version": "0.1.0",
"private": true,
"description": "api/web/mobile 三端共用型別與工具",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -b"
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./result.js";
export * from "./log.js";
+29
View File
@@ -0,0 +1,29 @@
export type LogLevel = "INF" | "WRN" | "ERR" | "TRC" | "DBG";
const TAIPEI_FORMATTER = new Intl.DateTimeFormat("en-CA", {
timeZone: "Asia/Taipei",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
export function formatTaipeiTimestamp(date: Date = new Date()): string {
const parts = TAIPEI_FORMATTER.formatToParts(date);
const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "00";
return `${get("year")}/${get("month")}/${get("day")} ${get("hour")}:${get("minute")}:${get("second")}`;
}
export function log(stage: string, level: LogLevel, message: string): void {
const line = `[${formatTaipeiTimestamp()}][${stage}][${level}]: ${message}`;
if (level === "ERR") {
console.error(line);
} else if (level === "WRN") {
console.warn(line);
} else {
console.log(line);
}
}
+13
View File
@@ -0,0 +1,13 @@
export type Result<T, E = string> = { ok: true; value: T } | { ok: false; error: E };
export function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
export function err<E>(error: E): Result<never, E> {
return { ok: false, error };
}
export function isOk<T, E>(result: Result<T, E>): result is { ok: true; value: T } {
return result.ok;
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}