Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v5

- name: Set up patched Node.js
uses: actions/setup-node@v5
with:
node-version-file: .node-version

- name: Test Node.js runtime requirements
run: npm run test:node

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
Expand Down Expand Up @@ -242,6 +250,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v5

- name: Set up patched Node.js
uses: actions/setup-node@v5
with:
node-version-file: .node-version

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
Expand Down
22 changes: 17 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up patched Node.js
uses: actions/setup-node@v5
with:
node-version-file: .node-version
- name: Test Node.js runtime requirements
run: npm run test:node
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
Expand Down Expand Up @@ -495,17 +501,23 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up patched Node.js
uses: actions/setup-node@v5
with:
node-version-file: .node-version
cache: npm
- name: Install web deployment dependencies
run: npm ci
- name: Download Web artifact
uses: actions/download-artifact@v5
with:
name: web-release
path: build/web
- name: Deploy Worker
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: npm run deploy:web

# ── Create GitHub Release ──────────────────────────────────────
create-release:
Expand Down
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.22.1
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ engine-strict=true 會讓 scripts/check-node-version.mjs 裡那句解釋 CVE 的錯誤訊息變成永遠看不到的死碼。

實測(Node 22.23.1):

npm error code EBADENGINE
npm error notsup Required: {"node":"^22.23.4 || ^24.22.1 || ^26.12.1"}
npm error notsup Actual:   {"npm":"10.9.8","node":"v22.23.1"}

> preinstall 根本沒被執行 — npm 在解析階段就 abort 了。也就是說最需要看到「這是 CVE-2026-111497,請用 22.23.4+/24.22.1+/26.12.1+」的使用者,只會看到 npm 的原文訊息。

兩個選項:

  • 移除 .npmrc 的 engine-strict,讓 engines 只當 warning、由 preinstall 給出可讀訊息(安裝仍會被擋,只是擋在 preinstall)。
  • 保留 engine-strict,但接受 preinstall 的訊息只對「已通過 engines 但語意不符」的邊角情況生效 — 那就別在 README 宣稱使用者會看到友善提示。

10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ https://github.com/YetAnotherBusDeveloper/yetanotherbusapp/releases/latest
- Flutter 3.x+
- Dart SDK
- Android Studio / VS Code
- Node.js 24.22.1(Web 工具;版本記錄於 `.node-version`)

### Clone

Expand All @@ -150,6 +151,15 @@ cd yetanotherbusapp
flutter pub get
```

Web 開發/部署工具先切換至 `.node-version` 指定的 Node.js,再執行 `npm ci`。
支援的安全版本為 Node.js `22.23.4+`、`24.22.1+`、`26.12.1+`(各自的主版本),
修補 [CVE-2026-111497 / HTTP2Shell](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)。
安裝、Web 開發與部署命令會拒絕舊版 Node.js;只更新 npm 套件無法修補 Node 內建的解碼器。
App 的 `brotli` 套件為純 Dart 實作,未連結此次受影響的原生 C 解碼器。
`npm run serve:web` 改用 Flutter Web 開發伺服器(`http://127.0.0.1:8080`)。
Wrangler 依賴的本機 `workerd` 仍內嵌舊版 Brotli,因此暫停以 `wrangler dev`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 停用 wrangler dev 之後,workers/web/index.mjs 沒有任何本地驗證途徑,而 repo 內也沒有任何針對它的測試(grep -rln "workers/web" test/ scripts/ 無命中)。

這支 Worker 不是單純靜態檔案伺服器,它處理:/.well-known/apple-app-site-association(AASA,改壞會讓 iOS Universal Links 與 App Clip 靜默失效)、assetlinks.json、sw.js/firebase-messaging-sw.js 的 no-store、字型的長快取,以及 SPA fallback。這些現在只能靠部署到正式站後人工驗證。

建議至少補一個 node --test 的單元測試,直接 import worker 的 fetch handler 打幾個 request(/sw.js、/.well-known/apple-app-site-association、未知路徑)斷言 header 與 body — 純 Node、不需要 workerd,也不會碰到 Brotli。否則這次為了安全而移除本機 dev server 的代價,是讓一條原本就沒測試的關鍵路徑變得更不可驗證。

執行本機 Worker;Wrangler 僅用於部署,由指定的安全版 Node.js 執行。

### Run

```bash
Expand Down
43 changes: 42 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,47 @@
"settingsHistoryDescription": "Search history, clearing data and privacy policy",
"settingsOnboardingDescription": "Run the first-time setup again",
"settingsAboutDescription": "Version information, contributors, community and feedback",
"languageDescriptionCompact": "Change the app language immediately.",
"themeModeDescriptionCompact": "Use light, dark or system theme.",
"mapProviderDescriptionCompact": "Change the base map, keeping the same bus data.",
"wearSyncCategoryDescriptionCompact": "Choose favorites groups to sync to your watch.",
"normalUpdateIntervalDescriptionCompact": "Arrival refresh interval; shorter uses more battery.",
"retryIntervalDescriptionCompact": "Retry after failures; waits at least the normal interval.",
"appUpdateManualCheckDescriptionCompact": "Check the selected channel for a new version.",
"searchHistoryLimitDescriptionCompact": "Keep recent routes; 0 clears and disables history.",
"clearSearchHistoryDescriptionCompact": "Clear recent routes; keep favorites and learning data.",
"clearSmartHistoryDescriptionCompact": "Reset recommendation learning; keep favorites.",
"clearRouteSelectionHistoryDescriptionCompact": "Clear route and stop selections; keep route open counts.",
"termsOfServiceDescriptionCompact": "Read the service rules and terms.",
"privacyPolicyDescriptionCompact": "Read how location, account and usage data are handled.",
"restartOnboardingDescriptionCompact": "Repeat first-time setup; keep favorites and preferences.",
"interfaceScaleDescriptionCompact": "Adjust text and interface size.",
"showWeatherDescriptionCompact": "Show local weather on Home; tap for the forecast.",
"personalizationDescriptionCompact": "Customize colors, AMOLED black mode and backgrounds.",
"wearSyncDescriptionCompact": "Sync favorites to your connected watch.",
"alwaysShowSecondsDescriptionCompact": "Show seconds when available; arrivals are estimates.",
"hapticFeedbackDescriptionCompact": "Brief vibrations for supported actions.",
"smartRecommendationsDescriptionCompact": "Suggest routes based on your habits and usual times.",
"autoFavoriteDescriptionCompact": "Automatically add frequent stops to Frequent favorites.",
"smartNotificationDescriptionCompact": "Arrival alerts at usual times; needs notifications and background location.",
"keepScreenAwakeDescriptionCompact": "Keep the screen on while viewing a route.",
"backgroundTripDescriptionCompact": "Trip alerts while locked; needs notifications and location.",
"backgroundTripIosDescriptionCompact": "Trip status while locked; needs notifications and background location.",
"favoriteWidgetRefreshHelperCompact": "Refresh at least 15 minutes apart; the system may delay it.",
"updateChannelDeveloperDescriptionCompact": "Self-built version; update checks are disabled.",
"updateChannelNightlyDescriptionCompact": "Get new features earlier; may be less stable.",
"updateChannelReleaseDescriptionCompact": "Receive published release updates only.",
"updateCheckOffDescriptionCompact": "Skip startup checks; check manually instead.",
"updateCheckNotifyDescriptionCompact": "Check at startup and notify about updates.",
"updateCheckPopupDescriptionCompact": "Check at startup and open the update dialog if available.",
"personalizationBackgroundImageDescriptionCompact": "Set page backgrounds; Home applies to all transit home tabs.",
"personalizationColorSourceDescriptionCompact": "Auto uses background colors; System uses device colors.",
"personalizationGlobalHomeDescriptionCompact": "Apply to all transit home tabs.",
"adsLockedMessageCompact": "Too many toggles; ads are locked on.",
"adsDisabledStatus": "Ads are off.",
"personalizationAmoledDescriptionCompact": "Use a black background in dark mode.",
"personalizationHomeGradientDescriptionCompact": "Adjust the Home gradient opacity.",
"personalizationPerPageDescriptionCompact": "Set a background for each page.",
"settingsTitle": "Settings",
"appearanceSectionTitle": "Appearance",
"languageLabel": "Language",
Expand Down Expand Up @@ -41,7 +82,7 @@
"themeModeLight": "Light",
"themeModeDark": "Dark",
"compactModeTitle": "Compact mode",
"compactModeDescription": "Reduce supporting descriptions on the home screen and some cards to keep common actions together. Turn it off for fuller explanations. The desktop home screen always uses a compact layout.",
"compactModeDescription": "Turn off for full descriptions. Desktop Home stays compact.",
"showWeatherTitle": "Show weather",
"showWeatherDescription": "Show temperature and weather for your current location next to the home title. Tap it to open the full forecast. Available location and network information are required. Turn it off to hide weather from the title.",
"mapProviderLabel": "Map provider",
Expand Down
Loading