[{"content":"用座標完成 DMG 版面配置 在第 1 篇中，我們介紹了 create-dmg 工具，並準備了僅含 .app 的暫存資料夾和背景圖片。\n現在來到最後一塊拼圖——版面配置。DMG 視窗要多大、應用程式圖示和 Applications 捷徑各放在哪裡，這一切都由傳給 create-dmg 的座標來決定。本文將逐一設計這些座標。\n與第 1 篇相同，FocusTimer、座標值等均為範例。\ncreate-dmg 完整指令 首先來看完整指令的全貌。這是為範例應用程式 FocusTimer 1.0 製作 DMG 的指令：\ncreate-dmg \\ --volname \u0026#34;FocusTimer 1.0\u0026#34; \\ --background build/dmg-background.png \\ --window-pos 200 120 \\ --window-size 600 400 \\ --icon-size 100 \\ --icon \u0026#34;FocusTimer.app\u0026#34; 175 190 \\ --hide-extension \u0026#34;FocusTimer.app\u0026#34; \\ --app-drop-link 425 190 \\ --no-internet-enable \\ \u0026#34;build/FocusTimer-1.0.dmg\u0026#34; \\ \u0026#34;build/dmg-stage/\u0026#34; 最後兩行是輸出和輸入：\nbuild/FocusTimer-1.0.dmg — 將要產生的 DMG 檔案路徑 build/dmg-stage/ — 第 1 篇中僅存放了 .app 的暫存資料夾 其上以 -- 開頭的行是設計視窗和圖示的選項。我們逐一來看。\n理解座標系 在看各選項之前，先搞清楚座標是如何運作的。\ncreate-dmg 的位置座標均為 DMG 視窗內部的座標，單位是第 1 篇中介紹的點 (point)。\n原點 (0, 0) 是視窗的左上角。 X 向右增大，Y 向下增大。（注意：與數學座標系不同，Y 向下增大。） 若視窗大小設為 600 400，座標在橫向 0~600、縱向 0~400 範圍內移動。 圖示的位置座標指向該圖示的中心點，而非圖示的左上角。\n各選項逐一解析 選項 值（範例） 含義 --volname \u0026quot;FocusTimer 1.0\u0026quot; DMG 掛載時顯示的卷冊名稱 --background build/dmg-background.png 視窗背景圖片（第 1 篇中準備的） --window-pos 200 120 視窗在螢幕上開啟的位置（X, Y） --window-size 600 400 視窗大小（寬, 高 — 單位：點） --icon-size 100 視窗內圖示的大小（單位：點） --icon \u0026quot;FocusTimer.app\u0026quot; 175 190 應用程式圖示的位置（視窗內 X, Y） --hide-extension \u0026quot;FocusTimer.app\u0026quot; — 隱藏圖示名稱中的 .app 副檔名 --app-drop-link 425 190 Applications 資料夾捷徑的位置（視窗內 X, Y） --no-internet-enable — 關閉已棄用的「網際網路啟用」功能 以下幾點值得特別說明：\n--window-pos 與 --window-size — 這兩個容易混淆。--window-pos 是使用者開啟 DMG 時視窗出現在螢幕（桌面）的哪個位置，--window-size 是視窗本身的大小。座標設計中重要的是 --window-size。\n--icon-size 100 — 視窗內的圖示以 100 點的大小繪製。設計背景圖片時也需要知道這個值，因為圖示佔據的 100 點（= 背景圖片中的 200 像素）空間需要在背景中留空。\n--icon 與 --app-drop-link — 這兩個是視窗左右兩側的主角。--icon 是使用者需要拖動的應用程式圖示，--app-drop-link 是拖放目標——Applications 資料夾捷徑。使用 --app-drop-link 無需單獨建立符號連結，create-dmg 會自動建立並放置 Applications 捷徑。\n--hide-extension — 不加此選項，圖示下方會顯示包含副檔名的 FocusTimer.app。隱藏後只會整潔地顯示 FocusTimer。\n--no-internet-enable — 「網際網路啟用 (internet-enable)」是舊版 Safari 自動處理下載 DMG 的舊功能，現已棄用。新增此選項後，create-dmg 會跳過該處理步驟及相關警告。\n座標設計 — 放置兩個圖示 現在來到核心部分。視窗大小設為 600 400，在其中決定應用程式圖示和 Applications 捷徑各放在哪裡。\n範例指令中的設定如下：\n應用程式圖示：--icon \u0026quot;FocusTimer.app\u0026quot; 175 190 Applications 捷徑：--app-drop-link 425 190 這些數字的含義解析如下：\nX 軸（橫向）— 175 與 425\n視窗寬度為 600 點，其中心為 300。將兩個圖示分別置於 175 和 425，則兩者分別距中心向左 125、向右 125。即兩個圖示以中心為基準左右對稱排列。應用程式圖示在左、Applications 在右，形成「從左向右拖放」的自然方向感。\nY 軸（縱向）— 兩者均為 190\n視窗高度為 400 點，其中心為 200。將兩個圖示置於相同的 190，則並排在同一高度，略高於正中心（200）。由於圖示名稱標籤在圖示下方，稍微上移後，包含名稱標籤在內視覺上恰好居中。\n座標沒有唯一正確的答案。上述值只是「在 600×400 視窗中左右對稱並排放置兩個 100 點圖示」的一種均衡範例。請根據你自己的背景設計自由調整。\n背景圖片與座標對齊 這裡再次用到第 1 篇的 @2x 慣例。\n圖示座標（175,190、425,190）是視窗的點座標。而背景圖片是其 2 倍大小的 1200×800 像素。在背景圖片中繪製箭頭時，需要以視窗座標乘以 2 的像素座標來思考：\n背景圖片像素座標 = 視窗點座標 × 2 將範例中兩個圖示轉換為背景圖片像素座標如下：\n元素 視窗座標（點） 背景圖片座標（像素） 應用程式圖示中心 (175, 190) (350, 380) Applications 中心 (425, 190) (850, 380) 因此，在背景圖片（1200×800）上繪製視線引導箭頭時，必須繪製在兩個圖示位置——像素 (350, 380) 與 (850, 380) 之間。圖示大小為 100 點 = 200 像素，因此留出每個圖示佔據的 200 像素區域，將箭頭放在中間的空間裡即可。\n使背景圖片的圖示與 create-dmg 的圖示座標以相同基準對齊，正是 DMG 設計的核心。若兩者錯位，箭頭就會偏離圖示或疊在圖示上。\n驗證與微調 執行指令後，build/FocusTimer-1.0.dmg 會產生。請雙按開啟產生的 DMG 親自查看。\n背景是否完整填滿了視窗？（若縮在角落 → 第 1 篇的 DPI 陷阱，用 sips 規範化為 144） 應用程式圖示和 Applications 是否恰好落在背景箭頭的兩端？ 圖示名稱標籤是否被截斷或重疊？ 座標第一次很難一步到位。稍微調整 --icon 和 --app-drop-link 的數值，重新執行指令，肉眼確認，反覆幾次即可。一旦確定好座標，之後幾乎不需要再改動。\n每次發布重複使用 — 自動化 DMG 設計實質上是一次性工作。背景圖片和座標一旦確定，之後所有發布都使用完全相同的設定。\n因此，建議將 create-dmg 呼叫作為發布建置腳本的一個步驟。腳本中通常是這樣的流程：\n將簽署、公證、staple 完成的 .app 複製到暫存資料夾（第 1 篇） 複製背景圖片後用 sips 將 DPI 規範化為 144（防止第 1 篇的陷阱） 使用上面設計的座標執行 create-dmg 清理暫存資料夾等暫時性檔案 每次發布只有版本號碼（--volname 中的 1.0，輸出檔案名稱中的 FocusTimer-1.0.dmg）會變化，因此只需將那部分提取為腳本變數，就無需每次修改指令。背景圖片路徑和所有座標設為固定值。\n建議讓腳本每次自動執行背景圖片的 DPI 規範化（sips）。這樣即使重新匯出設計原稿替換後忘記規範化 DPI，腳本也會始終自動修正。\n系列小結 歷經兩篇的 DMG 設計全部完成。現在你已經準備好以下內容：\n✅ (第 1 篇) create-dmg + .app 暫存資料夾 + @2x 且 DPI 規範化的背景圖片 ✅ (第 2 篇) 視窗與圖示座標設計 + 背景像素座標與視窗點座標對齊 + 自動化 核心要點再次總結：\nDMG 視窗是 600×400 點，背景圖片是其 2 倍的 1200×800 像素（@2x）。 背景 PNG 的 DPI 必須規範化為 144 — 這樣背景才能精確填滿視窗。 圖示座標是視窗內部的點座標，背景圖示座標是其 2 倍的像素座標 — 以相同基準對齊兩者。 將確定好的設計放入建置腳本，每次發布時原樣重複使用。 一支小小的箭頭、幾個點的圖示位置偏差，都會影響使用者的第一印象。.dmg 視窗是使用者與你的應用程式初次相遇的地方，值得用心設計一次。\n參考資料 create-dmg — GitHub sips — macOS 指令手冊 ","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-21-design-macos-dmg-2-layout-coordinates/","summary":"\u003ch1 id=\"用座標完成-dmg-版面配置\"\u003e用座標完成 DMG 版面配置\u003c/h1\u003e\n\u003cp\u003e在\u003ca href=\"../2026-05-20-design-macos-dmg-1-create-dmg-and-background/\"\u003e第 1 篇\u003c/a\u003e中，我們介紹了 \u003ccode\u003ecreate-dmg\u003c/code\u003e 工具，並準備了僅含 \u003ccode\u003e.app\u003c/code\u003e 的暫存資料夾和背景圖片。\u003c/p\u003e\n\u003cp\u003e現在來到最後一塊拼圖——\u003cstrong\u003e版面配置\u003c/strong\u003e。DMG 視窗要多大、應用程式圖示和 \u003ccode\u003eApplications\u003c/code\u003e 捷徑各放在哪裡，這一切都由傳給 \u003ccode\u003ecreate-dmg\u003c/code\u003e 的\u003cstrong\u003e座標\u003c/strong\u003e來決定。本文將逐一設計這些座標。\u003c/p\u003e","title":"macOS 應用程式分發用 DMG 設計 (2): 視窗與圖示座標設計及自動化"},{"content":"使用者最先看到的介面 直接分發 macOS 應用程式時，使用者會下載 .dmg 檔案並雙按開啟。那一刻，一個 Finder 視窗彈出。這個視窗就是使用者與你的應用程式初次相遇的第一個介面。\n一個製作精良的 .dmg 視窗通常長這樣——背景鋪著引導圖示，左側放著應用程式圖示，右側放著 Applications 資料夾捷徑。使用者將應用程式圖示拖到 Applications 一側即完成安裝。這種「拖放安裝」是 macOS 獨立開發者應用程式的事實標準安裝體驗。\n這個視窗不會自動產生。背景圖片、視窗大小、圖示的位置和尺寸，都需要你親自指定。本系列介紹如何設計這個 .dmg 視窗。\n本系列以已完成簽署、公證和 staple 的 .app 在手為前提，即處於分發前一步的應用程式。該過程在 Developer ID 直接分發系列中介紹過。.dmg 是直接分發（Developer ID）渠道的產出物，與 Mac App Store 渠道無關。\n本系列將完成的內容 共兩篇，最終你將具備以下內容：\n(第 1 篇，本文) create-dmg 工具 + 僅含 .app 的暫存資料夾 + 背景圖片準備 (第 2 篇) 視窗、圖示和拖放目標的座標設計 + 背景圖片與座標對齊 + 自動化 系列結束時，你應該已經準備好以下內容：\n僅隔離存放 .app 的暫存資料夾 符合 @2x 視網膜螢幕慣例且 DPI 已規範化的背景圖片 視窗大小和圖示位置均以座標確定的 create-dmg 指令 可在每次發布時原樣重複使用的形態 範例應用程式 — FocusTimer 與前幾個系列相同，以虛構的 macOS 應用程式 FocusTimer（管理專注時間的簡單計時器應用程式）為例。FocusTimer、路徑、座標值等均為範例，實際使用時請替換為你自己的應用程式。\n什麼是 DMG DMG（.dmg）是 macOS 的磁碟映像檔 (disk image) 檔案。可以將其理解為一個「虛擬磁碟」——單個檔案內部完整包含了資料夾和檔案結構。使用者雙按 .dmg 後，macOS 會像掛載磁碟一樣將其掛載，其內容以 Finder 視窗的形式開啟。\n在應用程式分發中使用 DMG 的原因：\n應用程式（.app）實際上是一個資料夾。將資料夾直接發布到網際網路比較困難，但用 DMG 包裹後就變成了單個檔案。 可以在 DMG 視窗內放入 Applications 資料夾捷徑，引導使用者一次拖放即可完成安裝。 可以設計視窗的外觀——背景、圖示位置等，讓安裝體驗更加簡潔。 工具 — create-dmg 手動製作 DMG 需要經過多個步驟：用 hdiutil 建立映像檔、掛載、用 AppleScript 操作 Finder 視窗放置圖示，再卸載等。繁瑣且容易出錯。\ncreate-dmg 是將這一流程濃縮為單條指令的開源工具。將背景圖片、視窗大小、圖示座標作為選項傳入，它就會產生完成的 .dmg。\n在 Developer ID 直接分發系列第 1 篇中安裝前置工具時已經安裝過了。若尚未安裝，請透過 Homebrew 安裝：\nbrew install create-dmg 名為 create-dmg 的專案有兩個。本文使用的是透過 Homebrew 安裝的命令列工具（create-dmg/create-dmg）。請勿與選項名稱不同的另一個專案混淆。\n第 1 步 — 僅含 .app 的暫存資料夾 DMG 中應該整潔地只放一個應用程式圖示。然而建置和匯出應用程式後，結果資料夾中除了 .app 之外，還有 DistributionSummary.plist、打包記錄等副產物檔案。直接將這個資料夾製作成 DMG，副產物也會暴露在視窗中。\n因此，需要將 .app 單獨複製到空資料夾中隔離，以該資料夾作為製作 DMG 的原料。這個隔離用資料夾通常稱為暫存 (staging) 資料夾。\n# 將 .app 單獨複製到空資料夾中，避免與副產物混在一起 DMG_STAGE=\u0026#34;build/dmg-stage\u0026#34; rm -rf \u0026#34;$DMG_STAGE\u0026#34; mkdir -p \u0026#34;$DMG_STAGE\u0026#34; cp -R \u0026#34;build/export/FocusTimer.app\u0026#34; \u0026#34;$DMG_STAGE/\u0026#34; 現在 build/dmg-stage/ 中只有 FocusTimer.app 一個檔案。製作 DMG 時將 create-dmg 指向此資料夾，視窗中就會整潔地只顯示應用程式圖示。（Applications 資料夾捷徑將在第 2 篇中透過 create-dmg 選項自動產生。）\n第 2 步 — 準備背景圖片 DMG 視窗設計的核心是背景圖片。這裡是最容易卡住的地方，我們一步步來看。\n畫布尺寸 — 為何是 1200×800 第 2 篇中會向 create-dmg 傳遞 --window-size 600 400 來設定 DMG 視窗大小。此時 600 和 400 的單位不是像素，而是點 (point)。點是 macOS 使用的邏輯座標單位。\n在視網膜螢幕 (Retina) 顯示器上，1 點對應 2 個像素。因此，要完整清晰地填滿 600×400 點的視窗，背景圖片必須是其 2 倍大小，即 1200×800 像素。這就是 @2x（2 倍解析度）慣例。\n總結：視窗是 600×400 點，背景圖片是 1200×800 像素。 這個 2 倍關係在第 2 篇設定座標時還會再次用到，非常重要。\n背景中繪製什麼 這裡糾正一個常見誤解：背景圖片中不要繪製應用程式圖示或 Applications 資料夾圖示。 這些圖示由 create-dmg 作為實際檔案放置在視窗上（第 2 篇）。\n背景圖片中只放裝飾和引導元素：\n從應用程式圖示指向 Applications 資料夾的箭頭 — 最常見的設計，引導使用者的視線和手「向這個方向拖放」。 如有需要，可加上「拖到此處」等簡短引導文字 品牌色或淡淡的背景紋理 也就是說，背景圖片是**「留空圖示放置位，只繪製連接兩者的引導線的圖」**。左右各留出圖示將要佔用的位置（第 2 篇中各為 100 點 = 200 像素），在中間放置箭頭。\n關鍵陷阱 — DPI 背景圖片中最常踩到的陷阱是 DPI（每英吋像素數）。\n用圖像編輯器匯出 PNG 時，檔案中除了像素數（1200×800）之外，還會儲存 DPI 值。Finder 在繪製 DMG 背景時會讀取這個 DPI，反算出「該圖片是多少點的尺寸」。計算大致如下：\n點尺寸 = 像素數 ÷ (DPI ÷ 72) 若 DPI 為 144：1200 ÷ (144 ÷ 72) = 1200 ÷ 2 = 600 點。恰好填滿視窗（600 點）。✅ 然而，某些圖像編輯器（如 Pixelmator Pro）匯出時會嵌入任意 DPI 值（如 338）。若 DPI 為 338：1200 ÷ (338 ÷ 72) ≈ 256 點。比視窗（600 點）小得多，背景就會縮小成一團，塞在視窗左上角。❌ 像素數同樣是 1200×800，卻因為一個 DPI 值導致背景顯示異常。第一次遇到時真的很難找到原因。\n解決方案是將匯出 PNG 的 DPI 強制統一為 144。macOS 內建的 sips 指令一行即可搞定：\nsips -s dpiWidth 144 -s dpiHeight 144 dmg-background.png 執行這一行後，無論編輯器嵌入了何種 DPI，都會被設為 144，1200×800 像素將被準確解讀為 600×400 點。\n要點 — 背景圖片 ① 製作為 1200×800 像素，② 匯出後用 sips 將 DPI 規範化為 144。遵守這兩點就能避開 DPI 陷阱。\n第 1 篇小結 跟到這裡，你現在已經準備好以下內容：\n✅ 安裝了 create-dmg 工具，並了解其作用 ✅ 僅隔離存放 .app 的暫存資料夾（build/dmg-stage/） ✅ 以 1200×800 像素製作且 DPI 已規範化為 144 的背景圖片 素材已經準備就緒。但還沒有一個視窗來顯示背景圖片並放置圖示。視窗要多大、應用程式圖示和 Applications 捷徑各放在哪裡——這一切都由傳給 create-dmg 的座標來決定。\n下一篇將逐一設計這些座標，介紹如何讓背景像素座標與視窗點座標對齊以使背景中的箭頭恰好落在兩個圖示之間，以及如何自動化整個流程以在每次發布時重複使用。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-20-design-macos-dmg-1-create-dmg-and-background/","summary":"\u003ch1 id=\"使用者最先看到的介面\"\u003e使用者最先看到的介面\u003c/h1\u003e\n\u003cp\u003e直接分發 macOS 應用程式時，使用者會下載 \u003ccode\u003e.dmg\u003c/code\u003e 檔案並雙按開啟。那一刻，\u003cstrong\u003e一個 Finder 視窗彈出\u003c/strong\u003e。這個視窗就是使用者與你的應用程式初次相遇的第一個介面。\u003c/p\u003e\n\u003cp\u003e一個製作精良的 \u003ccode\u003e.dmg\u003c/code\u003e 視窗通常長這樣——背景鋪著引導圖示，左側放著應用程式圖示，右側放著 \u003ccode\u003eApplications\u003c/code\u003e 資料夾捷徑。使用者將應用程式圖示\u003cstrong\u003e拖到\u003c/strong\u003e \u003ccode\u003eApplications\u003c/code\u003e 一側即完成安裝。這種「拖放安裝」是 macOS 獨立開發者應用程式的事實標準安裝體驗。\u003c/p\u003e","title":"macOS 應用程式分發用 DMG 設計 (1): create-dmg 與背景圖片準備"},{"content":"將建置傳送到 App Store 的路徑 第 1 篇建立了 MAS 建置目標，第 2 篇建立了區分兩個渠道的設定檔和程式碼分支。FocusTimer MAS 目標現在已成為可以上架 App Store 的形態。\n本篇（最終篇）將搭建將該建置上傳到 App Store Connect 的路徑，並介紹如何驗證兩個渠道在未來不會被破壞，從而收尾本系列。\n與第 1、2 篇相同，FocusTimer、com.example.FocusTimer.mas、Team ID ABCDE12345 等均為範例值。\n第 1 步 — 用於上傳的 ExportOptions-MAS.plist 在直接分發系列中，我們提到將封存匯出為分發用時，xcodebuild -exportArchive 指令會讀取 ExportOptions.plist。MAS 渠道需要獨立的匯出設定檔。\n在專案的發布目錄中建立 ExportOptions-MAS.plist：\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;method\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;app-store\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;destination\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;upload\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;signingStyle\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;automatic\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;teamID\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;ABCDE12345\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; 各金鑰的含義如下：\nmethod = app-store — 表示將此建置匯出為 Mac App Store 渠道用途，與直接分發渠道的 developer-id 形成對比。 destination = upload — xcodebuild -exportArchive 會將匯出的產物直接上傳到 App Store Connect，無需像以前那樣經過 Transporter 或其他上傳工具。 signingStyle = automatic — Xcode 會自動匹配 Mac App Store 佈建設定檔和 Apple Distribution 憑證。 teamID — 你的 Apple Developer Team ID。 建立此檔案一次後，即可在所有 MAS 發布中重複使用。保留直接分發用的 ExportOptions.plist 不變，將此檔案與其並排放置。\n第 2 步 — 在 App Store Connect 註冊應用程式記錄 要實際提交應用程式審核，App Store Connect 中必須有應用程式記錄 (record)。記錄是容納應用程式在 App Store 中展示的所有資訊的容器，包括應用程式名稱、描述、截圖、價格等。\n應用程式記錄可以在實際首次提交前再建立。在一次性準備階段，只需提前了解「需要確定哪些項目、如何確定」即可。特別是下面的 Primary Language，一旦設定便難以更改，請提前慎重決定。\n在 App Store Connect → My Apps → + → New App 中填寫以下內容：\nPlatform — 選擇 macOS Primary Language（主要語言） — ⚠️ 最需要慎重決定的項目。 一旦設定，透過自助服務更改非常困難。若計劃在多個國家發布，通常將**英語（英語，美國）**設為主要語言，因為主要語言是「特定國家沒有對應翻譯時顯示的基準語言」。 App Name（應用程式名稱） — 以主要語言為基準的名稱（如 FocusTimer）。其他語言的名稱稍後透過各語言在地化 (localization) 單獨新增。例如，可以讓韓語使用者看到韓語名稱，日語使用者看到日語名稱。 Bundle ID — 從下拉選單中選擇 com.example.FocusTimer.mas。這正是第 1 篇中註冊的那個 ID。請不要與直接分發用 Bundle ID（com.example.FocusTimer）混淆——必須選擇帶有 .mas 後綴的那個。 SKU — 僅供你自己使用的內部識別字串，不會在 App Store 中公開，可自由設定（如 focustimer-mas-001）。 應用程式分類必須與第 2 篇中在 Info.plist 的 LSApplicationCategoryType 中填寫的值一致。若在 plist 中填寫了 public.app-category.productivity（效率工具），則在 App Store Connect 中也必須選擇「效率工具」類，否則審核階段會出現不符警告。\n第 3 步 — 驗證兩個渠道的建置 現在同一程式碼庫擁有了兩個建置目標。這伴隨著維護成本。平時只建置直接分發渠道，MAS 目標可能在不知不覺間已經損壞。例如，新增程式碼時遺漏了 #if canImport(Sparkle) 分支，導致只有 MAS 建置編譯失敗。\n防止這種情況最可靠的方法是每次製作直接分發發布時，同時也對 MAS 目標進行封存。若建置成功，則說明兩個渠道的分支完好無損。\nxcodebuild -project FocusTimer.xcodeproj -scheme \u0026#34;FocusTimer MAS\u0026#34; \\ -configuration Release \\ -destination \u0026#39;platform=macOS\u0026#39; \\ -archivePath build/FocusTimer-MAS.xcarchive \\ archive 若輸出末尾顯示如下內容，則 MAS 分支正常：\n** ARCHIVE SUCCEEDED ** 建議將此指令嵌入發布自動化腳本的最後一步，使其在每次發布時自動執行。即使當時並未實際將 MAS 建置上傳到 App Store，僅確認封存是否成功就足以保證兩個渠道的分支沒有被破壞。\n系列小結 — MAS 發布一次性準備完成 歷經三篇的 Mac App Store 發布一次性準備工作全部完成。現在你已經準備好以下內容：\n✅ (第 1 篇) MAS 專用 Bundle ID + 複製的 FocusTimer MAS 建置目標 ✅ (第 2 篇) MAS 專用 entitlements 和 Info.plist + 建置設定 + #if canImport(Sparkle) 程式碼分支 ✅ (第 3 篇) 用於上傳的 ExportOptions-MAS.plist + App Store Connect 記錄註冊方法 + 兩個渠道建置驗證 至此，同一個 macOS 應用程式同時具備了直接分發（Developer ID）和 Mac App Store 兩個渠道。核心結構再次總結如下：\n共享同一程式碼庫，但分為兩個建置目標。 直接分發目標包含 Sparkle，MAS 目標排除 Sparkle。 兩者的差異透過獨立的 entitlements、Info.plist、ExportOptions 檔案和 #if canImport(Sparkle) 程式碼分支來體現。 初次設定時工作量較大，但這同樣是一次建置、持續重複使用的結構。之後只需在製作直接分發發布時同時封存 MAS 建置，確認分支是否仍然正常即可。實際提交 App Store（截圖、描述、應對審核）是在此一次性準備之上進行的獨立工作，將在另一篇文章中介紹。\n參考資料 App Store Connect 說明 LSApplicationCategoryType — Apple Developer Documentation Distributing your app for beta testing and releases — Apple Developer ","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-19-distribute-macos-app-mas-3-export-and-app-store-connect/","summary":"\u003ch1 id=\"將建置傳送到-app-store-的路徑\"\u003e將建置傳送到 App Store 的路徑\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003e第 1 篇\u003c/a\u003e建立了 MAS 建置目標，\u003ca href=\"../2026-05-18-distribute-macos-app-mas-2-build-config-and-code/\"\u003e第 2 篇\u003c/a\u003e建立了區分兩個渠道的設定檔和程式碼分支。\u003ccode\u003eFocusTimer MAS\u003c/code\u003e 目標現在已成為可以上架 App Store 的形態。\u003c/p\u003e\n\u003cp\u003e本篇（最終篇）將搭建\u003cstrong\u003e將該建置上傳到 App Store Connect 的路徑\u003c/strong\u003e，並介紹如何驗證兩個渠道在未來不會被破壞，從而收尾本系列。\u003c/p\u003e","title":"macOS 應用程式上架 Mac App Store (3): 上傳設定與 App Store Connect 註冊"},{"content":"讓目標真正成為「MAS 專用」 在第 1 篇中，我們註冊了 MAS 專用 Bundle ID 並複製了 FocusTimer MAS 建置目標。但該目標目前仍只是直接分發目標的副本。\nMAS 建置必須與直接分發建置在三個方面有所不同：\n權限 (entitlements) — 僅保留適合 App Store 的最小權限 Info.plist — 移除 Sparkle 金鑰，新增 App Store 中繼資料 程式碼 — 分支處理，確保沒有 Sparkle 時也能編譯 本文將逐一處理這三點。\n與第 1 篇相同，FocusTimer、com.example.FocusTimer.mas 等均為範例值。\n第 1 步 — MAS 專用 entitlements 檔案 entitlements 是列出應用程式請求哪些系統權限的檔案。直接分發建置中，Sparkle 為了安裝更新需要額外權限；但 MAS 建置沒有 Sparkle，因此那些權限是不必要的。\n在專案根目錄建立新檔案 FocusTimer-MAS.entitlements：\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;com.apple.security.app-sandbox\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.files.user-selected.read-write\u0026lt;/key\u0026gt; \u0026lt;true/\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; 與直接分發用 FocusTimer.entitlements 相比，差異如下：\n無 Sparkle 相關權限 — 直接分發建置中的 temporary-exception.mach-lookup.*（Sparkle 與安裝輔助程式通訊所需的例外）在 MAS 建置中沒有存在的理由。 無 network.client — 移除 Sparkle 後，FocusTimer 本體不進行任何網路呼叫。不使用的權限理應移除，審核中多餘權限也可能被指出。應用程式請求的權限面越小越好。 上述範例是僅擁有「讀寫使用者主動選擇的檔案」權限的最簡形式。若你的應用程式實際使用了網路或其他資源，請相應新增對應權限，但絕對不要包含不使用的權限。\n第 2 步 — MAS 專用 Info.plist 檔案 同樣，在專案根目錄建立新檔案 FocusTimer-MAS-Info.plist：\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;LSApplicationCategoryType\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;public.app-category.productivity\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;ITSAppUsesNonExemptEncryption\u0026lt;/key\u0026gt; \u0026lt;false/\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; 與直接分發用 FocusTimer-Info.plist 相比，有兩點不同：\n① 去掉所有 Sparkle 的 SU* 金鑰\n直接分發用 Info.plist 中包含 SUFeedURL、SUPublicEDKey 等 Sparkle 設定金鑰。MAS 用 Info.plist 中一個都不應有。App Store 審核禁止自帶自動更新機制，即使 plist 中僅殘留暗示此功能的金鑰也可能成為問題。不包含這些金鑰才是安全的做法。\n② 新增 App Store 中繼資料金鑰\nLSApplicationCategoryType — 應用程式在 App Store 中所屬的分類。上述範例中的 public.app-category.productivity 表示「效率工具」分類。該值必須與第 3 篇中在 App Store Connect 設定的分類一致，否則審核階段會出現不符警告。 ITSAppUsesNonExemptEncryption — 應用程式是否使用受出口管制的加密技術。若僅使用標準加密（如 HTTPS、標準系統 API）或不使用加密，可設為 false。提前寫入此金鑰，可自動通過每次上傳建置時出現的加密調查問卷。 將 ITSAppUsesNonExemptEncryption 設為 false 之前，請確認你的應用程式確實未使用非標準加密。若不確定，建議參考 Apple 的加密相關文件。\n第 3 步 — MAS 目標建置設定 這是第 1 篇 2-3 節中推遲的建置設定更新。在 TARGETS → FocusTimer MAS → Build Settings 中對齊以下設定：\n建置設定金鑰 值 PRODUCT_BUNDLE_IDENTIFIER com.example.FocusTimer.mas INFOPLIST_FILE FocusTimer-MAS-Info.plist CODE_SIGN_ENTITLEMENTS FocusTimer-MAS.entitlements ENABLE_APP_SANDBOX YES ENABLE_HARDENED_RUNTIME YES ENABLE_USER_SELECTED_FILES readwrite CODE_SIGN_STYLE Automatic MARKETING_VERSION、CURRENT_PROJECT_VERSION 與直接分發目標保持一致 核心要點如下：\nINFOPLIST_FILE 和 CODE_SIGN_ENTITLEMENTS 必須指向剛才建立的 MAS 專用檔案。常見的錯誤是將這兩行保持為直接分發用檔案不變。 將 CODE_SIGN_STYLE 設為 Automatic，Xcode 會自動頒發和匹配 Mac App Store 佈建設定檔和 Apple Distribution 憑證，無需手動管理簽署。 若應用程式不使用網路，則不設定 ENABLE_OUTGOING_NETWORK_CONNECTIONS，這與在 entitlements 中移除 network.client 的邏輯一致。 版本號碼（MARKETING_VERSION 等）保持與直接分發目標相同的值，以免兩個渠道的版本號碼出現差異。 第 4 步 — Sparkle 程式碼分支（#if canImport(Sparkle)） 即使拆分了設定檔，還有一個問題未解決：原始碼中某處有 import Sparkle 及使用 Sparkle 的程式碼，而 MAS 目標中未連結 Sparkle，因此 import 陳述式本身會產生編譯錯誤。\n解決方案是用 Swift 的條件編譯指令 #if canImport(Sparkle) 包裹 Sparkle 相關程式碼。\n為何用 canImport — 不使用自訂旗標的原因 也可以用 #if MAS_BUILD 等自訂編譯旗標來分支。但這樣就需要人工同步「MAS 目標開啟該旗標，直接分發目標關閉該旗標」的設定。隨著目標增多或設定變化，容易出現不一致。\ncanImport(Sparkle) 則不同。它由編譯器直接檢查「Sparkle.framework 是否連結到此目標」。由於第 1 篇中已從 MAS 目標的套件相依性中刪除了 Sparkle，在 MAS 目標中 canImport(Sparkle) 會自動為假 (false)，無需單獨同步任何旗標。這就是 macOS 應用程式分離 MAS 渠道時將此模式作為事實標準的原因。\n分支 ① — 完全包裹僅含 Sparkle 程式碼的檔案 僅包含自動更新邏輯的檔案（如 UpdaterCoordinator.swift）整體用 #if 包裹：\n#if canImport(Sparkle) import Foundation import Sparkle import Combine @Observable @MainActor final class UpdaterCoordinator { // 包裹 Sparkle 更新器的程式碼 … } #endif 在 MAS 建置中，此檔案會像空檔案一樣編譯，不產生任何影響。\n分支 ② — 同時包裹使用 Sparkle 的程式碼 建立 UpdaterCoordinator 並將其傳遞給介面的程式碼也需要分支。否則 MAS 建置會參照「不存在的型別」。\n@main struct FocusTimerApp: App { @State private var viewModel = CompositionRoot.makeContentViewModel() #if canImport(Sparkle) @State private var updater = UpdaterCoordinator() #endif var body: some Scene { Settings { #if canImport(Sparkle) PreferenceView(viewModel: viewModel, updater: updater) #else PreferenceView(viewModel: viewModel) #endif } MenuBarExtra { #if canImport(Sparkle) MenuBarContent(updater: updater) #else MenuBarContent() #endif } label: { // 選單列圖示 … } } } private struct MenuBarContent: View { #if canImport(Sparkle) @Bindable var updater: UpdaterCoordinator #endif var body: some View { Button(\u0026#34;顯示 FocusTimer\u0026#34;) { /* … */ } #if canImport(Sparkle) Button(\u0026#34;檢查更新…\u0026#34;) { updater.checkForUpdates() } .disabled(!updater.canCheckForUpdates) #endif // 其他共用選單項目 … } } 要點有三：\nupdater 欄位宣告本身用 #if 包裹。 接收 updater 的介面（PreferenceView、MenuBarContent）分為有 Sparkle 和無 Sparkle 兩種形式。 「檢查更新…」等 Sparkle 專用 UI 元素也用 #if 包裹。在 MAS 建置中，這些選單項目不會出現——App Store 版不應有自帶更新選單，這是正確的。 設定介面（PreferenceView）中的「啟用自動更新」開關等元素也都以相同的模式包裹即可。\n第 2 篇小結 跟到這裡，你現在已經準備好以下內容：\n✅ MAS 專用權限檔案 FocusTimer-MAS.entitlements（最小權限） ✅ MAS 專用 FocusTimer-MAS-Info.plist（移除 Sparkle 金鑰，新增 App Store 中繼資料） ✅ MAS 目標建置設定已對齊 ✅ 用 #if canImport(Sparkle) 完成了自動更新程式碼的分支 現在，FocusTimer MAS 目標已真正有別於直接分發目標，成為可以上架 App Store 的形態。剩下的工作是搭建將此建置實際上傳到 App Store Connect 的路徑。\n下一篇將建立用於上傳的 ExportOptions-MAS.plist、在 App Store Connect 中註冊應用程式記錄，並介紹如何驗證兩個渠道在未來不會被破壞，從而收尾本系列。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-18-distribute-macos-app-mas-2-build-config-and-code/","summary":"\u003ch1 id=\"讓目標真正成為mas-專用\"\u003e讓目標真正成為「MAS 專用」\u003c/h1\u003e\n\u003cp\u003e在\u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003e第 1 篇\u003c/a\u003e中，我們註冊了 MAS 專用 Bundle ID 並複製了 \u003ccode\u003eFocusTimer MAS\u003c/code\u003e 建置目標。但該目標目前仍只是直接分發目標的副本。\u003c/p\u003e\n\u003cp\u003eMAS 建置必須與直接分發建置在三個方面有所不同：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e權限 (entitlements)\u003c/strong\u003e — 僅保留適合 App Store 的最小權限\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInfo.plist\u003c/strong\u003e — 移除 Sparkle 金鑰，新增 App Store 中繼資料\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e程式碼\u003c/strong\u003e — 分支處理，確保沒有 Sparkle 時也能編譯\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e本文將逐一處理這三點。\u003c/p\u003e","title":"macOS 應用程式上架 Mac App Store (2): 區分兩個渠道的設定與程式碼分支"},{"content":"另一個分發渠道 — Mac App Store 前一系列介紹了用 Developer ID 直接分發 macOS 應用程式的一次性準備工作。配齊憑證、公證、Sparkle 自動更新和更新 Feed 託管後，使用者無需經過 App Store 即可直接下載 .dmg 檔案。\n本系列介紹將同一應用程式上架 Mac App Store (MAS) 的一次性準備工作。兩種分發方式並非二選一，可以同時運營直接分發渠道和 App Store 渠道。App Store 由 Apple 代勞處理付款、退款和搜尋曝光，使用者信任度也更高，因此與直接分發並行運營的情況很常見。\n本系列以應用程式已透過 Developer ID 直接分發、且已整合 Sparkle 自動更新為前提。該設定從 Developer ID 直接分發系列第 1 篇開始介紹。若尚未閱讀，建議先行查閱。\n為何 MAS 需要「另一個目標」 直接分發的應用程式包含 Sparkle 自動更新——應用程式自行檢查更新 Feed，下載新版本並替換自身的功能。\n然而，Mac App Store 的審核規定禁止此類行為。上架 App Store 的應用程式不得從外部下載程式碼更新自身，更新必須且只能透過 App Store 進行。也就是說，提交 MAS 的建置中不能包含 Sparkle。\n但直接分發建置必須包含 Sparkle。同一建置產物無法同時滿足兩種要求。因此，解決方案是：\n同一程式碼庫，兩個建置目標。\n渠道 建置目標 Sparkle 分發途徑 Developer ID FocusTimer 包含 直接分發（.dmg） Mac App Store FocusTimer MAS 排除 App Store 共享同一份原始碼，將建置目標分為兩個，只在其中一個中連結 Sparkle。本系列介紹建立第二個目標（FocusTimer MAS）的一次性準備工作。\n本系列將完成的內容 共三篇，逐步建置以下內容：\n(第 1 篇，本文) 註冊 MAS 專用 Bundle ID + 複製 Xcode 建置目標 (第 2 篇) 區分兩個渠道的設定檔（entitlements 和 Info.plist）與程式碼分支 (第 3 篇) 用於上傳的 ExportOptions-MAS.plist + App Store Connect 註冊 + 建置驗證 系列結束時，你應該已經準備好以下內容：\n在 Apple Developer Portal 註冊的 MAS 專用 Bundle ID 已移除 Sparkle 相依性的 FocusTimer MAS 建置目標 MAS 專用 entitlements 檔案和 Info.plist 檔案 用 #if canImport(Sparkle) 分支的程式碼 用於 App Store 上傳的 ExportOptions-MAS.plist 範例應用程式 — FocusTimer 與前一系列相同，以虛構的 macOS 應用程式 FocusTimer（管理專注時間的簡單計時器應用程式）為例。FocusTimer、Bundle Identifier com.example.FocusTimer、Team ID ABCDE12345 等均為範例值，實際使用時請替換為你自己的應用程式和帳戶資訊。\n本文基於 Xcode 26 編寫。\n第 1 步 — 註冊 MAS 專用 Bundle ID 為何需要獨立的 Bundle ID 若直接分發渠道的 Bundle ID 為 com.example.FocusTimer，則 MAS 渠道使用不同的 Bundle ID。\n渠道 Bundle ID Developer ID com.example.FocusTimer Mac App Store com.example.FocusTimer.mas 在原有 Bundle ID 後追加 .mas。為何要分開？\n同一台 Mac 上兩個渠道可以共存 — Bundle ID 相同時，macOS 會將兩個應用程式識別為同一個並產生衝突。若不同，直接分發版和 App Store 版可以同時安裝在同一台機器上（便於開發和測試）。 UserDefaults 網域隔離 — 設定儲存空間以 Bundle ID 為單位劃分，兩個渠道的設定不會混淆。 避免 Launch Services 衝突 — 防止 macOS 在處理「開啟此應用程式」等請求時產生的混亂。 請保持直接分發渠道的 Bundle ID 不變。這是識別現有使用者的基準，不能更改。MAS 用的是額外註冊的一個新 ID。\n註冊流程 登入 developer.apple.com/account（使用 Apple Developer Program 帳戶） Certificates, Identifiers \u0026amp; Profiles → 左側選單 Identifiers → + 按鈕 選擇 App IDs → Continue → 類型選擇 App → Continue Description：輸入易於識別的名稱（如 FocusTimer MAS） Bundle ID： 選擇 Explicit（明確） 輸入：com.example.FocusTimer.mas Capabilities：關閉應用程式不使用的所有功能。若不使用 iCloud、推播通知、App 內購買、Sign in with Apple 等，無需開啟任何項目。（App Sandbox 由 Xcode 端的 entitlements 處理，此處無需開啟。） Continue → Register 驗證 若 Identifiers 清單中顯示 FocusTimer MAS — com.example.FocusTimer.mas 條目，則註冊完成。\n第 2 步 — 複製 Xcode 建置目標 現在在 Xcode 專案中建立 FocusTimer MAS 建置目標。最快的方法是複製現有目標。\n2-1. 複製 (Duplicate) 目標 在 Xcode 中開啟 FocusTimer.xcodeproj 點按 Project navigator（左側面板）最上方的專案圖示（藍色） 在中間編輯區域的 TARGETS 清單中按右鍵點按 FocusTimer → Duplicate 彈出對話框時選擇 Duplicate Only 新目標名稱為 FocusTimer copy，雙按名稱將其改為 FocusTimer MAS 若彈出「是否新增 Scheme？」提示，選擇 Activate（或稍後在 Manage Schemes 中新增） 2-2. 立即修改 Bundle ID 複製後最先要做的是替換 Bundle ID。若保持不變，兩個目標將擁有相同的 ID。\n將 TARGETS → FocusTimer MAS → General → Identity → Bundle Identifier 改為第 1 步中註冊的值：\ncom.example.FocusTimer.mas 2-3. 關鍵陷阱 — Duplicate 留下的「清理負擔」 這是本篇中最棘手的部分。Xcode 的 Duplicate Target 以**「安全預設值」**運行，但這種安全反而留下了需要手動處理的清理工作。複製完成後需要檢查以下四點：\n① 讓新目標包含原始碼檔案\n為了安全，Duplicate 會自動將所有原始碼檔案從新目標中排除。若保持不變，FocusTimer MAS 目標就是一個空殼，建置時不會包含任何程式碼。需要重新整理目標成員關係，使新目標重新包含現有原始碼檔案。\n② 從 MAS 目標中移除 Sparkle 相依性\nDuplicate 會原樣複製原始目標的 Swift Package 相依性。因此 Sparkle 也會隨之進入 FocusTimer MAS 目標。本系列的出發點正是「MAS 建置中不能有 Sparkle」，所以需要從 MAS 目標的套件相依性清單和 Frameworks 建置階段中刪除 Sparkle。\n③ 清理暫時性 Info.plist 檔案\nDuplicate 會建立類似 FocusTimer copy-Info.plist 的暫時性 Info.plist 檔案。由於我們將在第 2 篇中單獨建立 MAS 專用的 Info.plist，這些暫時性檔案（包括專案參照和實際檔案）應全部刪除。\n④ 更新 MAS 目標的建置設定\nBundle Identifier、Info.plist 路徑、entitlements 路徑等建置設定需要調整為 MAS 專用。此工作將在第 2 篇建立專用設定檔後統一處理。\n①·② 大多可透過 Xcode UI 中的目標成員關係和套件相依性介面處理，但若複製過程留下的重複參照無法徹底清除，有時需要直接查看專案檔案（.pbxproj）。清理完成後，分別建置兩個目標，確認 MAS 目標建置中 import Sparkle 是否報錯，是最可靠的驗證方式（第 2 篇中介紹此程式碼分支）。\n第 1 篇小結 跟到這裡，你現在已經準備好以下內容：\n✅ 在 Apple Developer Portal 註冊了 MAS 專用 Bundle ID（com.example.FocusTimer.mas） ✅ 透過複製建立了 FocusTimer MAS 建置目標 ✅ 了解了複製留下的清理負擔（原始碼成員關係、Sparkle 相依性、暫時性檔案） 目標這個「容器」已經建立好了。但這個目標目前本質上還只是 FocusTimer 的副本，並未真正成為「MAS 專用」。MAS 建置需要使用與直接分發建置不同的權限 (entitlements) 和不同的 Info.plist，程式碼也需要分支以便在沒有 Sparkle 時不報錯。\n下一篇將介紹區分兩個渠道的設定檔與程式碼分支。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-17-distribute-macos-app-mas-1-target-setup/","summary":"\u003ch1 id=\"另一個分發渠道--mac-app-store\"\u003e另一個分發渠道 — Mac App Store\u003c/h1\u003e\n\u003cp\u003e前一系列介紹了用 \u003cstrong\u003eDeveloper ID 直接分發\u003c/strong\u003e macOS 應用程式的一次性準備工作。配齊憑證、公證、Sparkle 自動更新和更新 Feed 託管後，使用者無需經過 App Store 即可直接下載 \u003ccode\u003e.dmg\u003c/code\u003e 檔案。\u003c/p\u003e","title":"macOS 應用程式上架 Mac App Store (1): 建立獨立建置目標"},{"content":"最後一塊拼圖 — 更新檔案放在哪裡 第 1 篇完成了 Developer ID 憑證和公證的設定，第 2 篇完成了 Sparkle 簽署金鑰的準備。至此，我們已具備簽署應用程式、公證應用程式，以及驗證更新真實性的全部手段。\n然而，第 2 篇中寫入應用程式 Info.plist 的 SUFeedURL（https://updates.example.com/appcast.xml）所指向的位置，目前還什麼都沒有。本篇（最終篇）將託管更新 Feed，並完成建置設定，收尾整個一次性準備工作。\n與第 1、2 篇相同，所有名稱和網域名稱（FocusTimer、example.com、example-dev 等）均為範例值，實際使用時請替換為你自己的資訊。\n為何要單獨建立更新儲存庫 自動更新要正常運作，網際網路上必須有以下兩樣內容：\nappcast.xml — 更新 Feed，告知應用程式哪個版本是最新的以及從哪裡取得 .dmg — 實際的應用程式安裝檔案 這裡有一個重要限制：應用程式內的 Sparkle 透過簡單的、無認證的 HTTPS GET 請求取得這些檔案。也就是說，使用者電腦上的應用程式必須能夠不經任何登入等驗證流程直接下載。\n很多開發者將應用程式的主原始碼儲存庫設為私有 (private)。然而私有儲存庫的發布檔案需要認證，Sparkle 無法取得。因此，常見的做法是分離儲存庫：\n主儲存庫（如 FocusTimer）— 原始碼，可設為私有。 更新儲存庫（如 FocusTimer-updates）— 僅託管 appcast.xml，必須為公開 (public)。 本文將使用 GitHub 免費的靜態託管服務 GitHub Pages 運營更新儲存庫。\n第 1 步 — 建立公開更新儲存庫 在 GitHub 上建立新儲存庫。\n點按 New repository 名稱：FocusTimer-updates — 該名稱稍後會用於 Feed 位址，請精確設定，包括大小寫。 擁有者 (Owner)：你的帳戶或組織（範例：example-dev） 可見性：Public — 必須為公開，Sparkle 需要無認證即可存取。 勾選 Add a README file（方便建立第一個提交） 點按 Create repository 第 2 步 — 啟用 GitHub Pages 將剛建立的儲存庫作為靜態網站發布。\n進入儲存庫的 Settings → 左側選單選擇 Pages Source：選擇 Deploy from a branch Branch：選擇 main / (root) → 點按 Save 等待 1~2 分鐘，若 https://example-dev.github.io/FocusTimer-updates/ 可以存取，則表示成功。 至此，已有了一個可以存放更新檔案的公開位址。但還有最後一步。\n第 3 步 — 連接自訂網域 直接使用 GitHub Pages 的預設位址（example-dev.github.io/...）也可以正常運作。但如果將該位址硬編碼到應用程式的 SUFeedURL 中，日後若需要將託管遷移到 GitHub Pages 以外的地方，就會遇到麻煩——因為所有已分發使用者的應用程式仍指向舊位址。\n解決方案是中間加一層自己控制的網域名稱。將 SUFeedURL 設為 https://updates.example.com/appcast.xml 等自己的網域名稱後，日後遷移託管時只需修改一行 DNS 設定，現有使用者即可自動跟隨到新位置。這個設定只需做一次，永久有效。\n3-1. 新增 DNS 記錄 在管理網域名稱（example.com）的 DNS 服務商的設定介面中，新增以下記錄：\n欄位 值 Type CNAME Name updates Value example-dev.github.io TTL 3600（預設值） 這樣，updates.example.com 子網域就會指向 GitHub Pages。\n3-2. 在儲存庫中新增 CNAME 檔案 在更新儲存庫（FocusTimer-updates）的根目錄建立名為 CNAME 的檔案，內容只寫一行網域名稱：\nupdates.example.com 提交並推送此檔案。\n3-3. 在 GitHub Pages 中註冊網域 在儲存庫的 Settings → Pages → Custom domain 欄位中輸入 updates.example.com 並點按 Save。GitHub 會自動頒發 HTTPS 憑證，憑證頒發完成後勾選 Enforce HTTPS。\n3-4. 驗證 DNS 傳播和憑證頒發通常需要約 10 分鐘。稍等片刻後用以下指令驗證：\ncurl -I https://updates.example.com/appcast.xml 若回應第一行顯示 HTTP/2 200，則表示正常。（若尚未上傳 appcast.xml，可能會回傳 404，但網域名稱和 HTTPS 連線本身可透過其他方式確認。關鍵是 https://updates.example.com 能夠回傳回應。）\n補充說明：.dmg 安裝檔案通常上傳到 GitHub Releases，而非 GitHub Pages。直接將大型二進位檔案放入儲存庫會使儲存庫體積膨脹。Releases 的服務路徑與 Pages 不同，因此與上述自訂網域設定無關，保持現狀即可。\n第 4 步 — 在本地保存更新儲存庫 發布時需要編輯並提交 appcast.xml，因此要在本地也複製一份更新儲存庫。將其複製到主專案資料夾內的適當位置（如 release/updates），建置和發布腳本就能在同一個地方操作兩個儲存庫，非常方便。\ngit clone git@github.com:example-dev/FocusTimer-updates.git release/updates 將一個儲存庫巢狀在另一個儲存庫中時，請將 release/updates/ 新增到主儲存庫的 .gitignore 中，以避免相互干擾。\n第 5 步 — ExportOptions.plist 接下來是建置端的設定。Xcode 的 xcodebuild -exportArchive 指令在將封存匯出為分發用 .app 時，會讀取名為 ExportOptions.plist 的設定檔。直接分發（Developer ID 渠道）的內容如下：\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE plist PUBLIC \u0026#34;-//Apple//DTD PLIST 1.0//EN\u0026#34; \u0026#34;http://www.apple.com/DTDs/PropertyList-1.0.dtd\u0026#34;\u0026gt; \u0026lt;plist version=\u0026#34;1.0\u0026#34;\u0026gt; \u0026lt;dict\u0026gt; \u0026lt;key\u0026gt;method\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;developer-id\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;signingStyle\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;automatic\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;teamID\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;ABCDE12345\u0026lt;/string\u0026gt; \u0026lt;/dict\u0026gt; \u0026lt;/plist\u0026gt; method — developer-id，表示「直接分發，而非 Mac App Store」。 signingStyle — automatic，讓 Xcode 自動選用第 1 篇中申請的憑證。 teamID — 第 1 篇中記下的 Team ID。 在專案中建立此檔案一次（如 release/ExportOptions.plist），即可在所有後續發布中重複使用。\n第 6 步 — 檢查應用程式端設定 最後整理一下應用程式專案本身必須包含的設定。首次為新應用程式整合 Sparkle 時，可將此清單作為檢查清單使用。\nInfo.plist 這些是第 2 篇中加入的 Sparkle 金鑰。Info.plist 必須包含以下金鑰：\n\u0026lt;key\u0026gt;SUFeedURL\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;https://updates.example.com/appcast.xml\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; 請再次確認 SUFeedURL 指向第 3 步建立的自訂網域，且 SUPublicEDKey 與第 2 篇中產生的公開金鑰一致。\n權限 (Entitlements) 若應用程式使用 App Sandbox，Sparkle 需要額外的例外權限以便與內部服務通訊來安裝更新。將以下條目新增到 FocusTimer.entitlements 檔案中：\n\u0026lt;key\u0026gt;com.apple.security.app-sandbox\u0026lt;/key\u0026gt;\u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.network.client\u0026lt;/key\u0026gt;\u0026lt;true/\u0026gt; \u0026lt;key\u0026gt;com.apple.security.temporary-exception.mach-lookup.global-name\u0026lt;/key\u0026gt; \u0026lt;array\u0026gt; \u0026lt;string\u0026gt;$(PRODUCT_BUNDLE_IDENTIFIER)-spks\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;$(PRODUCT_BUNDLE_IDENTIFIER)-spki\u0026lt;/string\u0026gt; \u0026lt;/array\u0026gt; 建置時，Xcode 會將 $(PRODUCT_BUNDLE_IDENTIFIER) 自動替換為實際的 Bundle Identifier（com.example.FocusTimer）。詳細條目請參考 Sparkle 官方沙箱指南。\n對於直接分發的應用程式，沙箱並非必須（沙箱是 Mac App Store 的要求）。若應用程式不使用沙箱，則無需上述 mach-lookup 例外。但由於自動更新需要使用網路，network.client 權限和 Hardened Runtime 必須開啟，以滿足公證要求。\n建置設定 (Build Settings) 在 Xcode 的建置設定中確認以下項目：\nCODE_SIGN_ENTITLEMENTS — 上述權限檔案的路徑 ENABLE_HARDENED_RUNTIME = YES — 公證的必要條件 ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES — 允許用於檢查更新的網路存取 系列小結 — 一次性準備工作完成 歷經三篇的直接分發一次性準備工作全部完成。現在你已經準備好以下內容：\n✅ (第 1 篇) Developer ID Application 憑證 + 公證用憑據 ✅ (第 2 篇) Sparkle EdDSA 簽署金鑰對 + 私密金鑰備份 ✅ (第 3 篇) 連接到自訂網域的公開更新儲存庫 + ExportOptions.plist + 應用程式端設定 以上是只需做一次的設定。這些工作不需要在每次發布新版本時重複進行。\n從現在起，分發新版本的流程每次大致相同——建置封存 → 用 ExportOptions.plist 匯出 → 用 Developer ID 憑證簽署 → 用 notarytool 公證 → 建立 .dmg → 用 Sparkle 金鑰簽署 → 更新 appcast.xml → 將 .dmg 上傳到 GitHub Release。這一重複流程大多可以用一個腳本自動化，那是另一篇文章的主題。\n其中**製作 .dmg**涉及背景圖片和圖示排列等設計要素，將在獨立系列 macOS 應用程式分發用 DMG 設計 中詳細介紹。\n直接分發初次設定時看似繁瑣，但核心在於**「一旦建置好，便可持續重複使用」**。放棄 App Store 的部分便利，換來的是對整個分發流程的完全掌控。\n參考資料 Sparkle 官方文件 Apple — Notarizing macOS software before distribution GitHub Pages 文件 ","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-16-distribute-macos-app-3-update-hosting-and-build/","summary":"\u003ch1 id=\"最後一塊拼圖--更新檔案放在哪裡\"\u003e最後一塊拼圖 — 更新檔案放在哪裡\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003e第 1 篇\u003c/a\u003e完成了 Developer ID 憑證和公證的設定，\u003ca href=\"../2026-05-15-distribute-macos-app-2-sparkle-signing-key/\"\u003e第 2 篇\u003c/a\u003e完成了 Sparkle 簽署金鑰的準備。至此，我們已具備簽署應用程式、公證應用程式，以及驗證更新真實性的全部手段。\u003c/p\u003e","title":"macOS 應用程式自主分發 (3): 託管更新 Feed 與建置設定"},{"content":"自動更新，以及為何還需要額外一層簽署 在第 1 篇中，我們完成了 Developer ID 憑證和公證的設定。至此，應用程式已具備首次交付給使用者的條件。但應用程式並非發布一次就結束——你需要持續推出修復錯誤、增加功能的新版本。\n對於 Mac App Store 應用程式，更新由 App Store 全權處理。直接分發的應用程式則不然，你必須在應用程式內自行整合自動更新功能。在 macOS 上，承擔這一角色的事實標準是開源框架 Sparkle。整合 Sparkle 後，應用程式會定期檢查「更新 Feed (appcast)」，如有新版本則通知使用者並自動下載安裝。\n這裡產生了一個疑問：第 1 篇已經用 Developer ID 憑證對應用程式進行了簽署，為什麼還需要另一把金鑰？\n原因在於兩種簽署驗證的對象不同：\nDeveloper ID 憑證 — macOS Gatekeeper 用於判斷「是否允許安裝此應用程式」 Sparkle EdDSA 金鑰 — 應用程式內的 Sparkle 用於判斷「剛下載的更新檔案是否真的由該應用程式的開發者製作」 自動更新是一種安全敏感的操作——應用程式從網際網路下載檔案並覆蓋自身。如果有人攔截更新伺服器或通訊路徑並植入偽造檔案，後果將非常嚴重。為此，Sparkle 只接受用開發者獨有的私密金鑰簽署的更新，簽署不符則拒絕安裝。這是獨立於憑證之外的額外驗證層。\n本文將建立用於該驗證的 EdDSA (Ed25519) 金鑰對。\n與第 1 篇相同，所有名稱和路徑（FocusTimer、example.com 等）均為範例值，實際使用時請替換為你自己的應用程式資訊。\n前提 — 應用程式中必須已加入 Sparkle 建立金鑰之前，應用程式專案中必須已將 Sparkle 框架加入為相依性。若尚未加入，請在 Xcode 中透過 Swift Package Manager (SPM) 加入。\n在 Xcode 中開啟專案 → File → Add Package Dependencies… 在搜尋框中輸入儲存庫位址：https://github.com/sparkle-project/Sparkle 將版本規則設為 2.x（最新主要版本）並加入 完成後建置一次專案，SPM 會下載 Sparkle 套件。隨套件附帶的命令列工具是下一步的關鍵。\n第 1 步 — 找到 Sparkle 命令列工具 Sparkle 套件內附有用於金鑰產生和簽署的命令列工具。這些工具位於 SPM 下載套件的資料夾中，但其路徑因 Xcode 版本和 DerivedData 設定而異，因此直接搜尋更為穩妥。\nSPARKLE_BIN=$(find ~/Library/Developer/Xcode/DerivedData \\ -path \u0026#34;*/artifacts/sparkle/Sparkle/bin\u0026#34; -type d 2\u0026gt;/dev/null | head -1) echo \u0026#34;$SPARKLE_BIN\u0026#34; 若輸出一行路徑則表示成功。若無任何輸出，說明跳過了前面「建置一次專案」的步驟，請先在 Xcode 中執行一次建置後再試。\n該資料夾內包含以下工具：\ngenerate_keys — 產生、備份、還原和驗證簽署金鑰（本文使用） sign_update — 對更新檔案進行簽署（實際發布時使用） generate_appcast — 產生更新 Feed（appcast.xml）（第 3 篇中出現） 第 2 步 — 產生簽署金鑰 現在建立金鑰對。\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; 輸出類似如下內容：\nGenerating a new signing key... A key has been generated and saved in your keychain. Add the SUPublicEDKey key to the Info.plist of each app for which you intend to use Sparkle... \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; 這一條指令會產生兩個金鑰：\n公開金鑰 (public key) — 輸出中顯示的 SUPublicEDKey 值。這不是秘密，是需要嵌入應用程式的金鑰。 私密金鑰 (private key) — 不會顯示在螢幕上。自動以「Private key for signing Sparkle updates」為名儲存到 macOS 鑰匙圈中。這是絕對不能以明文檔案形式留在磁碟上的真正秘密。 將公開金鑰嵌入應用程式 將輸出的公開金鑰字串寫入應用程式的 Info.plist。以範例應用程式為例，在 FocusTimer-Info.plist 中加入以下金鑰：\n\u0026lt;key\u0026gt;SUFeedURL\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;https://updates.example.com/appcast.xml\u0026lt;/string\u0026gt; \u0026lt;key\u0026gt;SUPublicEDKey\u0026lt;/key\u0026gt; \u0026lt;string\u0026gt;5vT3kQbA9mZ0wR1yX8cD2eF4gH6jK7lN0pS2uV5xW8c=\u0026lt;/string\u0026gt; SUPublicEDKey — 剛產生的公開金鑰。應用程式使用此金鑰驗證已下載更新的簽署。 SUFeedURL — 更新 Feed 的位址。該網域名稱目前尚不存在，將在第 3 篇中建立。現在只是先佔個位置。 由於公開金鑰嵌入在應用程式中，而私密金鑰只有開發者持有，因此應用程式只會接受用私密金鑰簽署的更新。這就是 Sparkle 更新驗證的核心結構。\n第 3 步 — 備份私密金鑰（務必執行！） 跳過此步驟，日後可能追悔莫及。\n私密金鑰存儲在鑰匙圈中，在當前使用的電腦上沒有問題。但如果電腦遺失、磁碟損壞或重新安裝 macOS，金鑰也會隨之消失。\n私密金鑰遺失會怎樣？用新金鑰簽署的更新將被現有使用者的應用程式（嵌入了舊公開金鑰的應用程式）拒絕驗證。也就是說，將永遠無法向已在使用應用程式的使用者推送自動更新，只能逐一通知使用者「請手動下載新版本並重新安裝」。\n因此，金鑰建立後請立即備份。\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -x ~/focustimer-sparkle-private.key cat ~/focustimer-sparkle-private.key cat 輸出的單行 base64 字串即為私密金鑰，範例如下：\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0= 將此字串以安全備忘錄 (secure note) 的形式儲存到 1Password 等密碼管理工具中。 備忘錄名稱建議使用便於日後查找的名稱，例如 FocusTimer Sparkle EdDSA Private Key。\n確認儲存完畢後，立即刪除磁碟上的明文檔案。\nrm ~/focustimer-sparkle-private.key 原則是不將私密金鑰以明文檔案形式留在磁碟上。備份只保存在加密的密碼管理工具中。\n關鍵陷阱 — % 符號不是金鑰的一部分 用 cat 輸出金鑰時，終端機（尤其是 zsh）可能在行末附加 % 符號。\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=% 這個 % 只是 Shell 用於提示「輸出結束時無換行符號」的標記，不是金鑰的一部分。如果將 % 一同複製到備份中，日後還原時金鑰會損壞。base64 字串通常以 = 結尾，請在儲存時去掉 = 後面的 %。\n第 4 步 — 在其他電腦上還原 需要在新電腦上建置應用程式時，將備份的私密金鑰重新匯入鑰匙圈。\necho \u0026#34;備份的_base64_字串\u0026#34; \u0026gt; ~/focustimer-sparkle-private.key \u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -f ~/focustimer-sparkle-private.key rm ~/focustimer-sparkle-private.key -f 選項的含義是「將檔案中的金鑰匯入鑰匙圈」。還原完成後，同樣立即刪除明文檔案。\n第 5 步 — 驗證 確認金鑰是否已正確安裝。\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -p 輸出單行公開金鑰。此值必須與第 2 步中寫入 Info.plist 的 SUPublicEDKey 完全一致。若不一致，說明應用程式中嵌入的公開金鑰與實際簽署金鑰不符，更新驗證將會失敗。\n第 2 篇小結 跟到這裡，你現在已經準備好以下內容：\n✅ 產生了 Sparkle EdDSA 金鑰對（公開金鑰 + 私密金鑰） ✅ 將公開金鑰嵌入應用程式的 Info.plist（SUPublicEDKey） ✅ 將私密金鑰安全備份到密碼管理工具 ✅ 了解了在其他電腦上還原金鑰的方法 應用程式現在已具備驗證「下載的更新是否真實」的手段。但有一件事仍未完成：第 2 篇中為 SUFeedURL 填寫的 https://updates.example.com/appcast.xml，該位址目前還什麼都沒有。\n下一篇將建立用於存放更新 Feed（appcast.xml）和 .dmg 檔案的公開儲存庫，將其連接到我們自己控制的網域名稱，並完成建置設定，從而結束全部一次性準備工作。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-15-distribute-macos-app-2-sparkle-signing-key/","summary":"\u003ch1 id=\"自動更新以及為何還需要額外一層簽署\"\u003e自動更新，以及為何還需要額外一層簽署\u003c/h1\u003e\n\u003cp\u003e在\u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003e第 1 篇\u003c/a\u003e中，我們完成了 Developer ID 憑證和公證的設定。至此，應用程式已具備首次交付給使用者的條件。但應用程式並非發布一次就結束——你需要持續推出修復錯誤、增加功能的新版本。\u003c/p\u003e","title":"macOS 應用程式自主分發 (2): 建立 Sparkle 自動更新簽署金鑰"},{"content":"用 Developer ID 自主分發意味著什麼 將 macOS 應用程式交付給使用者，大致有兩條路。一條是透過 Mac App Store (MAS)，另一條是由開發者自行製作 .dmg（或 .app）檔案，讓使用者從網站或 GitHub 等地方下載的直接分發 (direct distribution)。\n直接分發有明顯的優勢：不必等待 App Store 審核，沒有支付手續費，可以隨時以自己想要的方式發布更新。但相應地，原本由 App Store 代勞的事情——程式碼簽署、公證 (notarization)、自動更新——都需要開發者自行建置。\n如果不建置這些，macOS 的安全機制 Gatekeeper 會阻止應用程式啟動。使用者第一次開啟應用程式時會看到類似「無法開啟此 App，因為它來自身份不明的開發者」的警告，大多數使用者就此放棄安裝。要讓應用程式像正規分發的應用程式那樣雙按即可開啟，就必須用 Developer ID 憑證簽署應用程式，並獲得 Apple 的公證。\n本系列介紹的正是這套一次性準備流程。好消息是，這裡涉及的設定大多只需做一次，即可在所有後續發布中重複使用。\n本系列將完成的內容 共三篇，逐步建置以下內容：\n(第 1 篇，本文) Developer ID Application 憑證 + 公證用憑據 (第 2 篇) 用於 Sparkle 自動更新的 EdDSA 簽署金鑰 (第 3 篇) 託管更新 Feed 的公開儲存庫 + 收尾建置設定 系列結束時，你應該已經準備好以下內容。現在先瀏覽一下清單，每篇逐一完成即可。\nmacOS 鑰匙圈 (Keychain) 中的 Developer ID Application 憑證 存儲在鑰匙圈中的公證用憑據設定檔 用於 Sparkle 更新簽署的 EdDSA 金鑰對（公開金鑰 + 私密金鑰） 私密金鑰備份到安全位置 用於託管更新 Feed (appcast) 的公開 GitHub 儲存庫 + GitHub Pages 建置設定檔 (ExportOptions.plist) 與應用程式端設定 範例應用程式 — FocusTimer 本系列以虛構的 macOS 應用程式 FocusTimer（一款管理專注時間的簡單計時器應用程式）為例。指令和路徑中出現的 FocusTimer、Bundle Identifier com.example.FocusTimer、網域名稱 example.com、憑證名稱、Team ID 等均為範例值。實際操作時請替換為你自己的應用程式名稱和帳戶資訊。\n本文基於 Xcode 26 和 Sparkle 2.x（自動更新框架，第 2 篇中會用到）編寫。Apple 時常調整介面佈局和選單位置，若按鈕名稱略有不同，整體流程不變。\n前提條件 — 命令列工具 首先安裝建置和發布自動化所需的兩個命令列工具。前提是已安裝 macOS 套件管理器 Homebrew。\nbrew install gh create-dmg gh — GitHub 官方 CLI，稍後用於建立 GitHub Release。 create-dmg — 用於製作分發用 .dmg 磁碟映像檔的工具，同時產生引導使用者將應用程式拖入 Applications 資料夾的介面。 雖然現在還用不到，但發布自動化腳本通常會在開頭檢查這些工具是否存在，找不到就立即終止，因此提前安裝好。\n第 1 步 — 確認 Apple Developer Program 要申請 Developer ID 憑證並對應用程式進行公證，需要 Apple Developer Program 會員資格。個人帳戶每年收費 $99。\n如果已加入，只需確認狀態是否為活躍即可。\n訪問 developer.apple.com/account 在 Membership details 部分確認狀態為 Active 在同一頁面記下你的 Team ID（範例中為 ABCDE12345），後續步驟中會反覆用到。 若尚未加入，會員資格審批通常需要約一天時間。審批完成前無法申請憑證，建議優先處理。\n第 2 步 — 申請 Developer ID Application 憑證 Developer ID Application 憑證是對分發用 .app 和 .dmg 進行簽署時使用的憑證。macOS Gatekeeper 會將用此憑證簽署的應用程式識別為「已知開發者製作的應用程式」。\nApple 還有一種用於對 .pkg 安裝套件簽署的 Developer ID Installer 憑證，兩者不同。本系列以 .dmg 方式分發，因此只涉及 Application 憑證。\n申請流程 最簡單的方式是透過 Xcode 操作。\n啟動 Xcode → 選單 Xcode → Settings… (⌘,) 選擇 Accounts 標籤 → 在左側清單中點按你的 Apple ID 點按右下角的 Manage Certificates… 按鈕 在新彈出的視窗左下角點按 + 按鈕 從選單中選擇 Developer ID Application 一兩秒後新憑證出現在清單中，點按 Done 如此申請的憑證會自動儲存到 macOS 鑰匙圈 (Keychain) 中。憑證與對應的私密金鑰成對儲存，有了這把私密金鑰才能進行簽署。\n確認申請結果 在終端機執行以下指令，確認憑證是否已正確安裝。\nsecurity find-identity -v -p codesigning | grep \u0026#34;Developer ID Application\u0026#34; 如果輸出如下一行，則表示成功。\n1) A1B2C3D4E5F6... \u0026#34;Developer ID Application: Gildong Hong (ABCDE12345)\u0026#34; 引號內的字串是憑證的正式名稱。Gildong Hong 是註冊在 Apple 帳戶中的姓名，括號內的 ABCDE12345 是第 1 步記下的 Team ID。這個名稱在後續自動化程式碼簽署時會原樣使用，請再次核對。\n憑證續期 Developer ID 憑證的有效期限為 5 年。臨近到期時，Xcode 的 Manage Certificates 清單中會顯示 Expired。屆時按照相同步驟重新申請新憑證即可。\n好消息是，已用舊憑證簽署並分發的應用程式不會在憑證到期時立即失效。只有之後新建的建置才需要用新憑證簽署。因此無需過分擔心到期問題。\n第 3 步 — 註冊用於公證的 App 專用密碼 什麼是公證 (notarization) 公證是在分發前將應用程式上傳至 Apple 伺服器進行惡意軟體掃描的流程。通過掃描後，Apple 會頒發「公證票據」，應用程式附帶此票據後，Gatekeeper 才會無警告地開啟應用程式。如果說簽署證明「誰製作了它」，那麼公證就是「Apple 已掃描過一次」的獨立證明流程。\n公證提交使用 Apple 的 notarytool 指令，每次提交都會要求輸入 Apple ID 密碼。為避免每次手動輸入，需要建立一個 App 專用密碼 (App-Specific Password) 並提前存入鑰匙圈。\n3-1. 申請 App 專用密碼 App 專用密碼是一種 16 碼密碼，用於替代主 Apple ID 密碼，僅供特定用途使用。\n訪問 appleid.apple.com → 使用加入了 Apple Developer Program 的 Apple ID 登入（範例中為 developer@example.com） 登入與安全性 (Sign-In and Security) → 選擇 App 專用密碼 (App-Specific Passwords) 點按 + 按鈕 → 輸入密碼名稱（如 focustimer-notarize） 點按 建立 (Create) → 再次輸入 Apple ID 密碼 螢幕上顯示 abcd-efgh-ijkl-mnop 格式的 16 碼密碼。 關閉此頁面後將無法再次查看該密碼。 在進入下一步之前，請先將其複製到密碼管理工具等安全位置。\n3-2. 存入 notarytool 設定檔 將申請到的密碼儲存為鑰匙圈設定檔。執行以下指令前，請將 --password 的值替換為剛才獲得的實際密碼。\nxcrun notarytool store-credentials \u0026#34;focustimer-notarize\u0026#34; \\ --apple-id \u0026#34;developer@example.com\u0026#34; \\ --team-id \u0026#34;ABCDE12345\u0026#34; \\ --password \u0026#34;abcd-efgh-ijkl-mnop\u0026#34; 第一個參數 \u0026quot;focustimer-notarize\u0026quot; — 為此設定檔指定的名稱，之後公證時將透過此名稱載入憑據。 --apple-id — Apple Developer Program 帳戶的電子郵件 --team-id — 第 1 步中的 Team ID --password — 3-1 中申請的 App 專用密碼 出現如下輸出即為成功。\nCredentials saved to Keychain. 憑據已存入鑰匙圈，App 專用密碼的原始文字不再需要保留。（但若計劃在其他電腦上也進行建置，建議保存在密碼管理工具中以備用。）\n3-3. 驗證 確認已儲存的設定檔是否實際可用。\nxcrun notarytool history --keychain-profile \u0026#34;focustimer-notarize\u0026#34; 若出現 Successfully received submission history. 的訊息，則表示正常。其下的提交歷程可能為空（因為尚未公證過任何內容，這是正常的），也可能有之前的記錄。\n關鍵陷阱 — Apple ID 與 GitHub 帳戶是不同的 這是首次設定直接分發時最容易卡住的地方。\nApple Developer Program 帳戶 — 用於申請憑證和公證（範例：developer@example.com） GitHub 帳戶 — 第 3 篇中用於託管更新檔案（範例：myname@gmail.com） 兩者通常使用不同的電子郵件。特別注意：notarytool store-credentials 的 --apple-id 必須填寫 Apple Developer 帳戶電子郵件。若填寫 GitHub 帳戶電子郵件，認證會失敗，而且錯誤訊息不夠友善，難以找到原因。\n若兩個帳戶的電子郵件容易混淆，建議記錄下哪個是 Apple 用、哪個是 GitHub 用。這一區別在本系列中會反覆出現。\n第 1 篇小結 跟到這裡，你現在已經準備好以下內容：\n✅ 發布自動化用命令列工具（gh、create-dmg） ✅ 活躍狀態的 Apple Developer Program 會員資格 ✅ 存儲在鑰匙圈中的 Developer ID Application 憑證 ✅ 存儲在鑰匙圈中的公證用憑據設定檔（focustimer-notarize） 至此，簽署和公證應用程式的準備工作已完成。但幾乎沒有應用程式只發布一次就結束。你需要不斷推出修復錯誤、增加功能的新版本。如果是 App Store 應用程式，自動更新由 App Store 代勞；而直接分發則需要自行建置這一機制。\n下一篇將為 macOS 應用程式事實上的標準自動更新框架 Sparkle 建立 EdDSA 簽署金鑰。這是獨立於憑證之外、專門用於驗證更新檔案的機制。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2026-05-14-distribute-macos-app-1-developer-id-certificate/","summary":"\u003ch1 id=\"用-developer-id-自主分發意味著什麼\"\u003e用 Developer ID 自主分發意味著什麼\u003c/h1\u003e\n\u003cp\u003e將 macOS 應用程式交付給使用者，大致有兩條路。一條是透過 \u003cstrong\u003eMac App Store (MAS)\u003c/strong\u003e，另一條是由開發者自行製作 \u003ccode\u003e.dmg\u003c/code\u003e（或 \u003ccode\u003e.app\u003c/code\u003e）檔案，讓使用者從網站或 GitHub 等地方下載的\u003cstrong\u003e直接分發 (direct distribution)\u003c/strong\u003e。\u003c/p\u003e","title":"macOS 應用程式自主分發 (1): Developer ID 憑證與公證準備"},{"content":"概述 本報告所描述的步驟將引導您在 macOS 中使用 Automator 建立一個快速動作。這個快速動作可用於將文字轉換為更適合檔案名稱的格式。轉換過程會移除特殊字元，將文字轉換為小寫，並將空格替換為連字號。\n步驟 開啟 Automator 按 Cmd + Space 開啟 Spotlight，輸入「Automator」，然後按 Enter。 建立新的服務 在 Automator 應用程式中，選擇「Quick Action」（舊版稱為「Service」），然後按一下「Choose」。 設定快速動作 在新視窗頂端，將「Workflow receives current」下拉選單變更為「text」。 確認「in」下拉選單已設定為「any application」。 新增「Run Shell Script」動作 在左側搜尋欄中輸入「Run Shell Script」，並將動作拖曳至右側面板。 設定「Run Shell Script」動作 將「Pass input」變更為「as arguments」。 在文字方塊中貼上以下腳本： for text_input in \u0026#34;$@\u0026#34; do echo \u0026#34;$text_input\u0026#34; | sed \u0026#39;s/[^a-zA-Z0-9 ]//g\u0026#39; | tr \u0026#39;[:upper:]\u0026#39; \u0026#39;[:lower:]\u0026#39; | sed \u0026#39;s/ /-/g\u0026#39; done 新增「Copy to Clipboard」動作 在左側搜尋欄中輸入「Copy to Clipboard」，並將動作拖曳至右側面板中「Run Shell Script」動作的下方。 儲存快速動作 按 Cmd + S，為您的快速動作命名，例如「將文字轉換為檔案名稱」。 現在，腳本已準備好，可從任何支援 macOS 服務的文字編輯器的右鍵快捷選單中使用。\n使用腳本的方法：\n在文字編輯器中選取一段文字。 在選取的文字上按一下右鍵。 前往「Services」（服務）或「Quick Actions」（快速動作）（依 macOS 版本而定）。 選擇「將文字轉換為檔案名稱」動作。 處理後的文字將複製到剪貼簿，您可以將其貼上到任何需要的地方。\n結論 在 macOS 中使用 Automator 建立快速動作，是透過自動化重複性工作（例如將文字轉換為適合檔案名稱的格式）來簡化工作流程的便利方式。本報告提供了詳細的步驟說明，協助您建立可在各種 macOS 應用程式中使用的快速動作，對於任何希望提升生產力並簡化文字編輯流程的人來說，這都是不可或缺的工具。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-06-convert-text-to-filename-using-automator-on-macos/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e本報告所描述的步驟將引導您在 macOS 中使用 Automator 建立一個快速動作。這個快速動作可用於將文字轉換為更適合檔案名稱的格式。轉換過程會移除特殊字元，將文字轉換為小寫，並將空格替換為連字號。\u003c/p\u003e","title":"在 macOS 上使用 Automator 將文字轉換為檔案名稱"},{"content":"概述 pytrends 程式庫中的 realtime_trending_searches() 函式可讓您取得 Google 上的即時熱門搜尋。透過分析這些資料，您可以洞察吸引受眾目光的最新趨勢與主題，協助您建立即時、貼切且吸引人的內容。\n本教學將涵蓋以下內容：\n匯入所需的程式庫 設定 pytrends 請求 取得即時熱門搜尋資料 分析結果 取得即時熱門搜尋資料 首先，我們需要匯入所需的程式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接著，我們將使用 realtime_trending_searches() 函式取得 Google 上目前的即時熱門搜尋。\n# Retrieve real-time trending searches data realtime_trending_searches = pytrends.realtime_trending_searches(pn=\u0026#39;US\u0026#39;) 這將回傳一個 DataFrame，其中包含美國目前的即時熱門搜尋。\n分析結果 現在，我們可以分析即時熱門搜尋資料，以找出吸引受眾目光的最新趨勢與主題。\n# Display the top 10 real-time trending searches print(realtime_trending_searches.head(10)) 這段程式碼將顯示前 10 個即時熱門搜尋，為您提供吸引受眾目光的最新趨勢與主題的寶貴洞察。\n結論 本文示範了如何使用 pytrends 程式庫中的 realtime_trending_searches() 函式發掘 Google 上的即時熱門搜尋。透過分析這些資料，您可以掌握最新的趨勢與主題，協助您建立即時、貼切且吸引人的內容。本教學涵蓋了從設定 pytrends 請求到分析結果的完整流程，包括收集與分析即時熱門搜尋資料的各個步驟。善用這些洞察，您可以建立能與受眾產生共鳴並把握當前趨勢的內容。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-05-pytrends-11-discovering-realtime-trending-searches-for-uptotheminute-insights/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 程式庫中的 \u003ccode\u003erealtime_trending_searches()\u003c/code\u003e 函式可讓您取得 Google 上的即時熱門搜尋。透過分析這些資料，您可以洞察吸引受眾目光的最新趨勢與主題，協助您建立即時、貼切且吸引人的內容。\u003c/p\u003e","title":"Pytrends 11: 發掘即時熱門搜尋以獲得最新洞察"},{"content":"概述 pytrends 程式庫中的 suggestions() 函式可讓您取得特定查詢的搜尋建議。透過分析這些建議，您可以發現與搜尋查詢相關的新關鍵字與趨勢，協助您建立更吸引人且更具針對性的內容。\n本教學將涵蓋以下內容：\n匯入所需的程式庫 設定 pytrends 請求 取得搜尋建議資料 分析結果 取得搜尋建議資料 首先，我們需要匯入所需的程式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接著，我們將使用 suggestions() 函式取得特定查詢的搜尋建議。\n# Retrieve search suggestions for the query \u0026#39;Python\u0026#39; suggestions = pytrends.suggestions(keyword=\u0026#39;Python\u0026#39;) 這將回傳一個字典清單，其中包含與查詢「Python」相關的搜尋建議。\n分析結果 現在，我們可以分析搜尋建議資料，以發現與搜尋查詢相關的新關鍵字與趨勢。\n# Display the search suggestions for suggestion in suggestions: print(suggestion[\u0026#39;title\u0026#39;]) 這段程式碼將顯示與查詢「Python」相關的搜尋建議，為您提供與搜尋查詢相關的新關鍵字與趨勢的寶貴洞察。\n結論 本文示範了如何使用 pytrends 程式庫中的 suggestions() 函式，透過取得基於給定查詢的搜尋建議來精煉趨勢搜尋。透過探索這些建議，您可以發現與搜尋查詢相關的新關鍵字與趨勢，協助您建立更吸引人且更具針對性的內容。本教學涵蓋了從設定 pytrends 請求到分析結果的完整流程，包括收集與分析搜尋建議資料的各個步驟。善用這些洞察，您可以強化關鍵字研究與內容策略。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-04-pytrends-10-refining-trend-searches-with-suggestions/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 程式庫中的 \u003ccode\u003esuggestions()\u003c/code\u003e 函式可讓您取得特定查詢的搜尋建議。透過分析這些建議，您可以發現與搜尋查詢相關的新關鍵字與趨勢，協助您建立更吸引人且更具針對性的內容。\u003c/p\u003e","title":"Pytrends 10: 透過搜尋建議精煉趨勢搜尋"},{"content":"概述 pytrends 程式庫中的 top_charts() 函式可讓您取得 Google 針對特定年份與類別的熱門排行榜。透過分析這些資料，您可以發掘各類別中最熱門的搜尋查詢，協助您為受眾建立吸引人且貼切的內容。\n本教學將涵蓋以下內容：\n匯入所需的程式庫 設定 pytrends 請求 取得熱門排行榜資料 分析結果 取得熱門排行榜資料 首先，我們需要匯入所需的程式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接著，我們將使用 top_charts() 函式取得特定年份與類別的熱門排行榜資料。\n# Retrieve top charts data for 2022 top_charts = pytrends.top_charts(date=2022, hl=\u0026#39;en-US\u0026#39;, tz=360) 這將回傳一個 DataFrame，其中包含 2022 年的熱門排行榜資料。\n分析結果 現在，我們可以分析熱門排行榜資料，以找出所選類別中最熱門的搜尋查詢。\n# Display the top 10 in 2022 print(top_charts.head(10)) 這段程式碼將顯示 2022 年的前 10 名，為您提供最熱門搜尋查詢的寶貴洞察。\n結論 本文示範了如何使用 pytrends 程式庫中的 top_charts() 函式分析 Google 的熱門排行榜，以獲得資料驅動的洞察。透過探索這些資料，您可以發掘各類別中最熱門的搜尋查詢，協助您為受眾建立吸引人且貼切的內容。本教學涵蓋了從設定 pytrends 請求到分析結果的完整流程，包括收集與分析熱門排行榜資料的各個步驟。善用這些洞察，您可以為內容策略提供參考並最佳化您的線上影響力。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-03-pytrends-9-mastering-top-charts-analysis-for-datadriven-insights/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 程式庫中的 \u003ccode\u003etop_charts()\u003c/code\u003e 函式可讓您取得 Google 針對特定年份與類別的熱門排行榜。透過分析這些資料，您可以發掘各類別中最熱門的搜尋查詢，協助您為受眾建立吸引人且貼切的內容。\u003c/p\u003e","title":"Pytrends 9: 掌握熱門排行榜分析以獲得資料驅動的洞察"},{"content":"概述 pytrends 程式庫中的 trending_searches() 函式可讓您取得 Google 上目前的熱門搜尋。透過分析這些資料，您可以洞察吸引受眾目光的最新趨勢與主題，協助您建立即時且貼切的內容。\n本教學將涵蓋以下內容：\n匯入所需的程式庫 設定 pytrends 請求 取得熱門搜尋資料 分析結果 取得熱門搜尋資料 首先，我們需要匯入所需的程式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接著，我們將使用 trending_searches() 函式取得 Google 上目前的熱門搜尋。\n# Retrieve trending searches data trending_searches = pytrends.trending_searches(pn=\u0026#39;united_states\u0026#39;) 這將回傳一個 DataFrame，其中包含美國目前的熱門搜尋。\n分析結果 現在，我們可以分析熱門搜尋資料，以找出內容創作與最佳化的新機會。\n# Display the top 10 trending searches print(trending_searches.head(10)) 這段程式碼將顯示前 10 個熱門搜尋，為您提供吸引受眾目光的最新趨勢與主題的寶貴洞察。\n結論 本文示範了如何使用 pytrends 程式庫中的 trending_searches() 函式追蹤 Google 上的熱門搜尋。透過分析這些資料，您可以保持領先，並發現內容創作與最佳化的新機會。本教學涵蓋了從設定 pytrends 請求到分析結果的完整流程，包括收集與分析熱門搜尋資料的各個步驟。善用這些洞察，您可以為受眾建立即時且貼切的內容。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-02-pytrends-8-tracking-trending-searches-to-stay-ahead/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 程式庫中的 \u003ccode\u003etrending_searches()\u003c/code\u003e 函式可讓您取得 Google 上目前的熱門搜尋。透過分析這些資料，您可以洞察吸引受眾目光的最新趨勢與主題，協助您建立即時且貼切的內容。\u003c/p\u003e\n\u003cp\u003e本教學將涵蓋以下內容：\u003c/p\u003e","title":"Pytrends 8: 追蹤熱門搜尋以保持領先"},{"content":"概述 pytrends 程式庫中的 related_queries() 函式可讓您取得特定搜尋詞的相關查詢。透過分析這些資料，您可以洞察受眾所關心的問題與主題，協助您建立更貼切、更吸引人的內容。\n本教學將涵蓋以下內容：\n匯入所需的程式庫 設定 pytrends 請求 取得相關查詢資料 分析結果 取得相關查詢資料 首先，我們需要匯入所需的程式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接著，我們將使用 build_payload() 函式指定搜尋詞，再透過 related_queries() 函式取得相關查詢資料。\nkeywords = [\u0026#39;Python\u0026#39;] # Build payload pytrends.build_payload(keywords, timeframe=\u0026#39;now 7-d\u0026#39;, geo=\u0026#39;\u0026#39;) # Retrieve related queries data related_queries = pytrends.related_queries() 這將回傳一個字典，其中包含搜尋詞「Python」在過去 7 天內的相關查詢資料。\n分析結果 現在，我們可以分析相關查詢資料，以找出內容創作與最佳化的新機會。\n# Extract the related queries for the keyword \u0026#39;Python\u0026#39; python_related_queries = related_queries[keywords[0]][\u0026#39;rising\u0026#39;] # Display the top 10 rising related queries print(python_related_queries.head(10)) 這段程式碼將顯示搜尋詞「Python」前 10 個快速上升的相關查詢，為您提供受眾所關心的問題與主題的寶貴洞察。\n結論 本文示範了如何使用 pytrends 程式庫中的 related_queries() 函式，發掘給定搜尋詞的相關查詢。透過分析這些資料，您可以對目標關鍵字進行深入分析，並發現內容創作與最佳化的新機會。本教學涵蓋了從設定 pytrends 請求到分析結果的完整流程，包括收集與分析相關查詢資料的各個步驟。善用這些洞察，您可以為受眾建立更貼切、更吸引人的內容。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-04-01-pytrends-7-uncovering-related-queries-for-indepth-analysis/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 程式庫中的 \u003ccode\u003erelated_queries()\u003c/code\u003e 函式可讓您取得特定搜尋詞的相關查詢。透過分析這些資料，您可以洞察受眾所關心的問題與主題，協助您建立更貼切、更吸引人的內容。\u003c/p\u003e\n\u003cp\u003e本教學將涵蓋以下內容：\u003c/p\u003e","title":"Pytrends 7: 發掘相關查詢以進行深入分析"},{"content":"概述 pytrends 函式庫中的 related_topics() 函數允許您擷取特定搜尋詞的相關主題。透過分析這些資料，您可以發現與目標受眾相關的新關鍵字和創意，幫助您建立有吸引力且切題的內容。\n在本教學中，我們將介紹：\n匯入必要的函式庫 設定 pytrends 請求 擷取相關主題資料 分析結果 擷取相關主題資料 首先，我們需要匯入必要的函式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接下來，我們將使用 build_payload() 函數指定搜尋詞，然後使用 related_topics() 函數擷取相關主題資料。\nkeywords = [\u0026#39;Python\u0026#39;] # Build payload pytrends.build_payload(keywords, timeframe=\u0026#39;now 7-d\u0026#39;, geo=\u0026#39;\u0026#39;) # Retrieve related topics data related_topics = pytrends.related_topics() 這將返回一個字典，其中包含搜尋詞「Python」在過去 7 天內的相關主題資料。\n分析結果 現在，我們可以分析相關主題資料，以識別用於內容策略的新關鍵字和創意。\n# Extract the related topics for the keyword \u0026#39;Python\u0026#39; python_related_topics = related_topics[keywords[0]][\u0026#39;top\u0026#39;] # Display the top 10 rising related topics print(python_related_topics.head(10)) 這將顯示搜尋詞「Python」的前 10 個上升相關主題，為您提供有關與您的關鍵字相關的新興趨勢的寶貴洞察。\n結論 在本文中，我們示範了如何使用 pytrends 函式庫中的 related_topics() 函數調查指定搜尋詞的相關主題。透過探索這些資料，您可以擴展關鍵字研究並發現吸引目標受眾的新機會。本教學涵蓋了從設定 pytrends 請求到分析結果的整個收集和分析相關主題資料的過程。善用這些洞察，您可以為內容策略提供參考並提升您的線上影響力。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-31-pytrends-6-investigating-related-topics-to-expand-keyword-research/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 函式庫中的 \u003ccode\u003erelated_topics()\u003c/code\u003e 函數允許您擷取特定搜尋詞的相關主題。透過分析這些資料，您可以發現與目標受眾相關的新關鍵字和創意，幫助您建立有吸引力且切題的內容。\u003c/p\u003e","title":"Pytrends 6: 調查相關主題以擴展關鍵字研究"},{"content":"概述 pytrends 函式庫中的 interest_by_region() 函數允許您擷取特定搜尋詞在不同地理位置的興趣資料。透過分析這些資料，您可以獲得不同地區搜尋詞受歡迎程度的寶貴洞察，從而為行銷和內容策略提供參考。\n在本教學中，我們將介紹：\n匯入必要的函式庫 設定 pytrends 請求 擷取按地區劃分的興趣資料 視覺化結果 安裝 要安裝 Pytrends，只需使用 pip：\npip install matplotlib 擷取按地區劃分的興趣資料 首先，我們需要匯入必要的函式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq import pandas as pd # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接下來，我們將使用 build_payload() 函數指定搜尋詞和時間範圍，然後使用 interest_by_region() 函數擷取按地區劃分的興趣資料。\nkeywords = [\u0026#39;Python\u0026#39;] timeframe = \u0026#39;2023-01-01 2023-03-31\u0026#39; # Build payload pytrends.build_payload(keywords, timeframe=timeframe, geo=\u0026#39;\u0026#39;) # Retrieve interest by region data region_interest = pytrends.interest_by_region(resolution=\u0026#39;COUNTRY\u0026#39;, inc_low_vol=True, inc_geo_code=False) 這將返回一個 DataFrame，其中包含搜尋詞「Python」在 2023 年第一季按地區劃分的興趣資料。\n視覺化結果 現在，我們可以使用長條圖來視覺化按地區劃分的興趣資料。\nimport matplotlib.pyplot as plt # Sort the data by interest value region_interest = region_interest.sort_values(by=\u0026#39;Python\u0026#39;, ascending=False) # Plot the interest by region data plt.figure(figsize=(12, 6)) plt.bar(region_interest.index, region_interest[\u0026#39;Python\u0026#39;]) plt.xlabel(\u0026#39;Country\u0026#39;) plt.ylabel(\u0026#39;Interest\u0026#39;) plt.title(\u0026#39;Interest by Region for Python (Q1 2023)\u0026#39;) plt.xticks(rotation=90) plt.show() 此圖顯示了搜尋詞「Python」在不同國家／地區的興趣，讓您能夠識別該詞特別受歡迎的地區。\n結論 在本文中，我們示範了如何使用 pytrends 函式庫中的 interest_by_region() 函數分析特定搜尋詞按地區劃分的興趣。透過探索這些資料，您可以獲得不同地理位置搜尋詞受歡迎程度的精準洞察，幫助您更好地了解受眾並最佳化行銷策略。本教學涵蓋了從設定 pytrends 請求到視覺化結果的整個收集和分析按地區劃分的興趣資料的過程。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-30-pytrends-5-exploring-interest-by-region-for-targeted-insights/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 函式庫中的 \u003ccode\u003einterest_by_region()\u003c/code\u003e 函數允許您擷取特定搜尋詞在不同地理位置的興趣資料。透過分析這些資料，您可以獲得不同地區搜尋詞受歡迎程度的寶貴洞察，從而為行銷和內容策略提供參考。\u003c/p\u003e","title":"Pytrends 5: 探索各地區興趣以獲得精準洞察"},{"content":"概述 pytrends 函式庫中的 get_historical_interest() 函數允許您擷取特定搜尋詞在指定時間範圍內的每小時興趣資料。這有助於更細緻地了解搜尋詞的受歡迎程度，並識別在查看每日或每週資料時可能不明顯的趨勢。\n在本教學中，我們將介紹：\n匯入必要的函式庫 設定 pytrends 請求 擷取歷史每小時興趣資料 視覺化結果 安裝 要安裝 Pytrends，只需使用 pip：\npip install matplotlib 擷取歷史每小時興趣資料 首先，我們需要匯入必要的函式庫並設定 pytrends 請求。\nfrom pytrends.request import TrendReq import pandas as pd import matplotlib.pyplot as plt # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 接下來，我們將使用 get_historical_interest() 函數指定搜尋詞、時間範圍和其他請求參數。\nkeywords = [\u0026#39;Python\u0026#39;, \u0026#39;JavaScript\u0026#39;] # Retrieve hourly interest data hourly_interest = pytrends.get_historical_interest(keywords, year_start=2023, month_start=3, day_start=1, hour_start=0, year_end=2023, month_end=3, day_end=2, hour_end=0, cat=0, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;, sleep=0) 這將返回一個 DataFrame，其中包含搜尋詞「Python」和「JavaScript」從 2023 年 3 月 1 日到 3 月 2 日的每小時興趣資料。\n視覺化結果 現在，我們可以使用簡單的折線圖來視覺化每小時興趣資料。\n# Plot the hourly interest data plt.figure(figsize=(12, 6)) plt.plot(hourly_interest.index, hourly_interest[\u0026#39;Python\u0026#39;], label=\u0026#39;Python\u0026#39;) plt.plot(hourly_interest.index, hourly_interest[\u0026#39;JavaScript\u0026#39;], label=\u0026#39;JavaScript\u0026#39;) plt.xlabel(\u0026#39;Hour\u0026#39;) plt.ylabel(\u0026#39;Interest\u0026#39;) plt.title(\u0026#39;Hourly Interest for Python and JavaScript\u0026#39;) plt.legend() plt.show() 此圖顯示了「Python」和「JavaScript」在指定時間範圍內的每小時興趣，使您可以比較它們的受歡迎程度並識別趨勢。\n結論 在本文中，我們示範了如何使用 pytrends 函式庫中的 get_historical_interest() 函數從 Google Trends 擷取歷史每小時興趣資料。透過深入研究這些資料，您可以獲得有關搜尋詞受歡迎程度的寶貴洞察，並更好地理解消費者行為。本教學涵蓋了從設定 pytrends 請求到視覺化結果的整個收集和分析每小時興趣資料的過程。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-29-pytrends-4-diving-into-historical-hourly-interest-data/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 函式庫中的 \u003ccode\u003eget_historical_interest()\u003c/code\u003e 函數允許您擷取特定搜尋詞在指定時間範圍內的每小時興趣資料。這有助於更細緻地了解搜尋詞的受歡迎程度，並識別在查看每日或每週資料時可能不明顯的趨勢。\u003c/p\u003e","title":"Pytrends 4: 深入探索歷史每小時興趣資料"},{"content":"概述 pytrends 函式庫中的 multirange_interest_over_time() 函數允許您擷取特定關鍵字在多個時間範圍內的興趣。透過分析這些資料，您可以洞察關鍵字在不同時期內興趣的演變情況，從而為內容策略和行銷工作做出明智的決策。\n在本教學中，我們將介紹：\n匯入必要的函式庫 設定 pytrends 請求 建立時間範圍清單 擷取多時間範圍的隨時間變化興趣資料 分析結果 建立時間範圍清單 首先，我們需要建立一個時間範圍清單，用於分析關鍵字的興趣。在此範例中，我們將建立一個包含兩個時間範圍的清單\ntime_ranges = [ \u0026#39;2022-09-04 2022-09-10\u0026#39;, \u0026#39;2022-09-18 2022-09-24\u0026#39;, ] 擷取多時間範圍的隨時間變化興趣資料 接下來，我們需要匯入必要的函式庫，設定 pytrends 請求，並擷取關鍵字和時間範圍的多時間範圍隨時間變化興趣資料。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) # Define the list of keywords keywords = [\u0026#39;pizza\u0026#39;, \u0026#39;bagel\u0026#39;] # Build the payload pytrends.build_payload(keywords, timeframe=time_ranges) # Retrieve multi-range interest over time data interest_over_time_data = pytrends.multirange_interest_over_time() 這將返回一個字典，其中包含關鍵字在指定時間範圍內的隨時間變化興趣資料。\n分析結果 現在，我們可以分析多時間範圍的隨時間變化興趣資料，以了解關鍵字在不同時期的表現和受歡迎程度。\n# Display the interest over time data print(interest_over_time_data) 這將顯示每個時間範圍的隨時間變化興趣資料，為我們關鍵字在不同時期的表現和受歡迎程度提供寶貴的洞察。\n結論 在本文中，我們示範了如何使用 pytrends 函式庫中的 multirange_interest_over_time() 函數分析特定關鍵字在多個時間範圍內的興趣。透過分析這些資料，您可以獲得更全面的趨勢視角，並了解關鍵字在不同時期內興趣的演變情況。這些資訊可以幫助您就內容策略、行銷工作甚至產品開發做出明智的決策。善用多時間範圍的隨時間變化興趣分析，您可以保持競爭優勢，並確保您的內容和產品始終與目標受眾相關且具有吸引力。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-28-pytrends-3-harnessing-multirange-interest-over-time-analysis/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 函式庫中的 \u003ccode\u003emultirange_interest_over_time()\u003c/code\u003e 函數允許您擷取特定關鍵字在多個時間範圍內的興趣。透過分析這些資料，您可以洞察關鍵字在不同時期內興趣的演變情況，從而為內容策略和行銷工作做出明智的決策。\u003c/p\u003e","title":"Pytrends 3: 運用多時間範圍的隨時間變化興趣分析"},{"content":"概述 pytrends 函式庫中的 interest_over_time() 函數允許您擷取特定關鍵字隨時間變化的興趣。透過分析這些資料，您可以洞察關鍵字興趣的演變情況，從而為內容策略和行銷工作做出明智的決策。\n在本教學中，我們將介紹：\n定義關鍵字清單 設定時間範圍 擷取隨時間變化的興趣資料 分析結果 定義關鍵字清單 首先，我們需要定義要分析其隨時間變化興趣的關鍵字清單。\nkeywords = [\u0026#39;Python\u0026#39;, \u0026#39;JavaScript\u0026#39;] 設定時間範圍 接下來，我們需要設定要分析關鍵字興趣的時間範圍。在此範例中，我們將分析過去一年的興趣。\ntime_range = \u0026#39;2022-01-01 2023-01-31\u0026#39; 擷取隨時間變化的興趣資料 現在，我們需要使用 interest_over_time() 函數擷取關鍵字和時間範圍的隨時間變化興趣資料。\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) # Build the payload pytrends.build_payload(keywords, cat=0, timeframe=time_range, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;) # Retrieve interest over time data interest_over_time_data = pytrends.interest_over_time() 這將返回一個包含關鍵字隨時間變化興趣資料的 DataFrame。\n分析結果 現在，我們可以分析隨時間變化的興趣資料，以了解關鍵字的表現和受歡迎程度。\nprint(interest_over_time_data.head()) 這將顯示隨時間變化的興趣資料，為我們關鍵字的表現和受歡迎程度提供寶貴的洞察。\n結論 在本文中，我們示範了如何使用 pytrends 函式庫中的 interest_over_time() 函數分析特定關鍵字隨時間變化的興趣。透過分析這些資料，您可以洞察關鍵字興趣的演變情況，幫助您就內容策略、行銷工作甚至產品開發做出明智的決策。善用隨時間變化的興趣分析，您可以保持競爭優勢，並確保您的內容和產品始終與目標受眾相關且具有吸引力。\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-27-pytrends-2-analyzing-interest-over-time/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epytrends\u003c/code\u003e 函式庫中的 \u003ccode\u003einterest_over_time()\u003c/code\u003e 函數允許您擷取特定關鍵字隨時間變化的興趣。透過分析這些資料，您可以洞察關鍵字興趣的演變情況，從而為內容策略和行銷工作做出明智的決策。\u003c/p\u003e","title":"Pytrends 2: 分析隨時間變化的關鍵字興趣"},{"content":"概述 Google Trends提供了關於搜尋趨勢與關鍵字熱門程度的寶貴洞察。然而，Google並未提供存取此資料的官方API。幸好，Pytrends程式庫讓我們能夠使用Python存取Google Trends資料。\n在本教學中，我們將逐步引導您完成Pytrends的安裝與設定，並示範如何執行簡單的搜尋及解讀結果。\n前置條件 若要跟著本教學操作，您應具備：\n已安裝Python 3 具備Python程式設計的基礎知識 熟悉Python套件的使用 安裝 若要安裝Pytrends，只需使用pip：\npip install pytrends 設定Pytrends 若要開始使用Pytrends，請先匯入必要的程式庫，並建立與Google Trends的連線：\nfrom pytrends.request import TrendReq pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) 在這裡，我們將語言設定為英文（hl='en-US'），時區設定為UTC+0（tz=360）。\n執行基本搜尋 現在，讓我們執行一個簡單的搜尋，查看關鍵字「Python」隨時間的搜尋興趣變化：\nkeywords = [\u0026#39;Python\u0026#39;] pytrends.build_payload(keywords, timeframe=\u0026#39;today 5-y\u0026#39;, geo=\u0026#39;\u0026#39;, gprop=\u0026#39;\u0026#39;) interest_over_time_df = pytrends.interest_over_time() print(interest_over_time_df) 這段程式碼定義了一個關鍵字清單，將時間範圍設定為過去五年（timeframe='today 5-y'），並將地理位置和Google屬性留空。interest_over_time()方法會回傳一個包含興趣資料的DataFrame。\n理解結果 回傳的DataFrame包含關鍵字「Python」在過去五年的搜尋興趣資料。這些數值代表相對於指定時間範圍內最高點的搜尋興趣，100代表熱門程度的最高峰。\n結論 在本文中，我們介紹了Pytrends——一個非官方的Python版Google Trends API，並示範了如何安裝和設定它。我們使用該程式庫執行了基本搜尋，並討論了如何解讀結果。\n在接下來的文章中，我們將深入探討更進階的Pytrends功能，例如分析隨時間變化的興趣、依地區探索興趣，以及發現相關主題與查詢。敬請期待！\nNOTE : pytrends uses an unofficial API. Please use here for issues.\nSAMPLE CODE : https://github.com/hobbyworker/google-trend-for-python\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-26-pytrends-1-how-to-use-google-trend-unofficially-with-python/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003eGoogle Trends提供了關於搜尋趨勢與關鍵字熱門程度的寶貴洞察。然而，Google並未提供存取此資料的官方API。幸好，Pytrends程式庫讓我們能夠使用Python存取Google Trends資料。\u003c/p\u003e","title":"Pytrends 1: 如何用Python非官方存取Google趨勢"},{"content":"概述 rustup是Rust程式語言的官方工具鏈安裝程式與管理器。它提供了一種便利的方式，讓您在系統上安裝、更新及管理多個Rust工具鏈。本報告將涵蓋各平台的安裝流程、rustup的基本用法，並提供管理多個Rust環境的範例。\n安裝 macOS和Linux 若要在macOS和Linux系統上安裝rustup，請開啟終端機並輸入以下命令：\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh 腳本將下載並安裝必要的元件。完成後，請重新啟動終端機，或執行以下命令更新Shell的環境變數：\nsource $HOME/.cargo/env Windows Windows使用者請從Rust官方網站下載並執行rustup-init.exe執行檔。依照畫面上的指示完成安裝。安裝完成後，請重新啟動命令提示字元或終端機。\n用法 安裝特定版本的Rust 若要安裝特定版本的Rust，請使用以下命令：\nrustup install \u0026lt;version\u0026gt; 將\u0026lt;version\u0026gt;替換為所需的Rust版本，例如1.52.0。\n設定預設的Rust版本 若要為新專案設定預設的Rust版本，請使用以下命令：\nrustup default \u0026lt;version\u0026gt; 將\u0026lt;version\u0026gt;替換為所需的Rust版本，例如1.52.0。\n切換Rust版本 若要在特定專案中切換至不同的Rust版本，請切換到該專案目錄並使用以下命令：\nrustup override set \u0026lt;version\u0026gt; 將\u0026lt;version\u0026gt;替換為所需的Rust版本，例如1.52.0。\n更新Rust 若要將所有已安裝的Rust工具鏈更新至最新版本，請執行以下命令：\nrustup update 解除安裝Rust 若要從系統中解除安裝Rust與rustup，請執行以下命令：\nrustup self uninstall 範例 假設您正在開發兩個Rust專案：project_old和project_new。project_old需要Rust版本1.52.0，而project_new需要最新的穩定版本。\n首先，安裝所需的Rust版本：\nrustup install 1.52.0 rustup install stable 接著，切換到project_old目錄並設定該專案的Rust版本：\ncd project_old rustup override set 1.52.0 然後，切換到project_new目錄並設定該專案的Rust版本：\ncd project_new rustup override set stable 完成這些設定後，每個專案在建置或執行時都會使用對應的Rust版本。\n例如，當您在project_old目錄中執行cargo build或cargo run時，將使用Rust 1.52.0：\ncd project_old cargo build 同樣地，當您在project_new目錄中執行cargo build或cargo run時，將使用最新穩定版的Rust：\ncd project_new cargo build 使用rustup，您可以在具有不同Rust版本需求的多個專案之間無縫切換，無需任何衝突或手動介入。\n結論 rustup是Rust開發者不可或缺的工具，因為它簡化了在單一系統上管理多個Rust環境的流程。本報告涵蓋了各平台的安裝流程、基本用法，並提供了管理多個專案中不同Rust版本的範例。透過使用rustup，開發者可以確保專案始終以正確的Rust版本建置及執行，提升生產力並降低版本相關問題發生的可能性。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-26-managing-multiple-rust-environments-with-rustup/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003erustup\u003c/code\u003e是Rust程式語言的官方工具鏈安裝程式與管理器。它提供了一種便利的方式，讓您在系統上安裝、更新及管理多個Rust工具鏈。本報告將涵蓋各平台的安裝流程、\u003ccode\u003erustup\u003c/code\u003e的基本用法，並提供管理多個Rust環境的範例。\u003c/p\u003e","title":"使用rustup管理多個Rust環境"},{"content":"在本篇文章中，我們將討論如何使用GitHub Actions將Hugo靜態網站部署到GitHub Pages。GitHub Actions是GitHub提供的自動化功能，可讓您直接在GitHub儲存庫中建立自訂的軟體開發工作流程。透過使用GitHub Actions，您可以輕鬆自動化建置並將Hugo靜態網站部署到GitHub Pages的整個流程。\n設定GitHub Pages 在使用GitHub Actions成功將Hugo靜態網站部署到GitHub Pages之前，您需要先為專案設定GitHub Pages。\n前往您的GitHub專案頁面，點選右上角的「Settings」頁籤。 向下捲動至「Pages」區塊。 在「Build and deployment」設定中，找到「Source」下拉選單。 從可用選項中選擇「GitHub Actions」。這會告知GitHub Pages使用由GitHub Actions產生的成品進行部署。 完成GitHub Pages設定後，您的網站將使用hugo.yaml檔案中定義的工作流程進行部署。\n設定GitHub Pages工作流程 若要使用Hugo和GitHub Actions設定GitHub Pages工作流程，請依照以下步驟操作：\n在Hugo網站儲存庫的.github/workflows/目錄中，建立名為hugo.yaml的新檔案。 將以下YAML設定複製到hugo.yaml檔案中： name: Deploy Hugo site to Pages on: # Runs on pushes targeting the default branch push: branches: - main # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: false # Default to bash defaults: run: shell: bash jobs: # Build job build: runs-on: ubuntu-latest env: HUGO_VERSION: 0.111.3 steps: - name: Install Hugo CLI run: | wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \\ \u0026amp;\u0026amp; sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Install Dart Sass Embedded run: sudo snap install dart-sass-embedded - name: Checkout uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - name: Setup Pages id: pages uses: actions/configure-pages@v3 - name: Install Node.js dependencies run: \u0026#34;[[ -f package-lock.json || -f npm-shrinkwrap.json ]] \u0026amp;\u0026amp; npm ci || true\u0026#34; - name: Build with Hugo env: # For maximum backward compatibility with Hugo modules HUGO_ENVIRONMENT: production HUGO_ENV: production run: | hugo \\ --gc \\ --minify \\ --baseURL \u0026#34;${{ steps.pages.outputs.base_url }}/\u0026#34; - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: ./public # Deployment job deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 此設定檔會建立一個工作流程，當您推送至main分支或從Actions頁籤手動執行工作流程時，它將建置您的Hugo靜態網站並部署至GitHub Pages。\n結論 透過設定專案中的GitHub Pages，並使用提供的hugo.yaml工作流程設定，您可以輕鬆自動化建置並將Hugo靜態網站部署到GitHub Pages的整個流程。善用GitHub Actions，您可以專注於創作內容及更新網站，而無需每次手動部署。這套設定也讓您能夠充分利用GitHub Actions提供的內建CI/CD功能，進一步改善您的開發工作流程。\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-25-deploying-a-hugo-static-site-to-github-pages-with-github-actions/","summary":"\u003cp\u003e在本篇文章中，我們將討論如何使用GitHub Actions將Hugo靜態網站部署到GitHub Pages。GitHub Actions是GitHub提供的自動化功能，可讓您直接在GitHub儲存庫中建立自訂的軟體開發工作流程。透過使用GitHub Actions，您可以輕鬆自動化建置並將Hugo靜態網站部署到GitHub Pages的整個流程。\u003c/p\u003e","title":"使用GitHub Actions將Hugo靜態網站部署到GitHub Pages"},{"content":"在本篇文章中，我們將學習如何使用PaperMod主題為您的Hugo部落格新增廣告攔截器偵測功能。我們也會加入一則簡單的警告訊息，提示啟用廣告攔截器的使用者停用它，或將您的網站加入白名單。\n概述 以下是我們將進行的步驟概覽：\n建立自訂CSS檔案，為警告訊息設定樣式。 建立JavaScript檔案，用於偵測廣告攔截器。 新增部分HTML檔案，用於顯示警告訊息。 擴充head與footer區塊，以包含我們的新檔案。 逐步指南 1. 建立自訂CSS檔案 在assets/css/extended/目錄下建立名為custom_css.css的新檔案，並貼上以下CSS程式碼：\n#adblock-warning { background-color: #f2dede; color: #a94442; border-color: #ebccd1; padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; } 2. 建立JavaScript檔案以偵測廣告攔截器 在static/js/目錄下建立名為adblock-detection.js的新檔案，並貼上以下JavaScript程式碼：\nfunction detectAdBlocker() { const adBlockTest = document.createElement(\u0026#39;div\u0026#39;); adBlockTest.innerHTML = \u0026#39;\u0026amp;nbsp;\u0026#39;; adBlockTest.className = \u0026#39;ad ads adbadge doubleclick BannerAd adsbox ad-placeholder ad-placement\u0026#39;; document.body.appendChild(adBlockTest); window.setTimeout(() =\u0026gt; { if (adBlockTest.offsetHeight === 0) { document.getElementById(\u0026#39;adblock-warning\u0026#39;).style.display = \u0026#39;block\u0026#39;; console.log(\u0026#39;Ad-blocker detected.\u0026#39;); } else { console.log(\u0026#39;No ad-blocker detected.\u0026#39;); } adBlockTest.remove(); }, 100); } window.addEventListener(\u0026#39;load\u0026#39;, detectAdBlocker); 3. 新增用於警告訊息的部分HTML檔案 在layouts/partials/目錄下建立名為adblock-warning.html的新檔案，並貼上以下HTML程式碼：\n\u0026lt;div id=\u0026#34;adblock-warning\u0026#34; style=\u0026#34;display:none;\u0026#34;\u0026gt; \u0026lt;p\u0026gt;Please consider disabling your ad-blocker or whitelisting our website. Ads help support our content and keep it free for everyone. Thank you!\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; 4. 擴充head與footer區塊以包含新檔案 編輯layouts/partials/extend_head.html檔案，並加入以下這行：\n\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;{{ \u0026#34;css/extended/custom_css.css\u0026#34; | relURL }}\u0026#34;\u0026gt; 編輯layouts/partials/extend_footer.html檔案，並加入以下這行：\n\u0026lt;script src=\u0026#34;{{ \u0026#34;js/adblock-detection.js\u0026#34; | relURL }}\u0026#34; defer\u0026gt;\u0026lt;/script\u0026gt; 結論 完成上述步驟後，您已成功使用PaperMod主題為Hugo部落格新增廣告攔截器偵測功能。啟用廣告攔截器的訪客現在將看到一則禮貌的警告訊息，鼓勵他們停用廣告攔截器或將您的網站加入白名單，從而協助支持您的內容並讓所有人免費閱讀。\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-24-adding-adblocker-detection-to-your-hugo-blog-with-papermod-theme/","summary":"\u003cp\u003e在本篇文章中，我們將學習如何使用PaperMod主題為您的Hugo部落格新增廣告攔截器偵測功能。我們也會加入一則簡單的警告訊息，提示啟用廣告攔截器的使用者停用它，或將您的網站加入白名單。\u003c/p\u003e","title":"為使用PaperMod主題的Hugo部落格新增廣告攔截器偵測功能"},{"content":"在本篇文章中，我們將學習如何將nvm（Node版本管理器）與autoenv結合使用，以管理開發工作流程中的Node.js版本及環境變數。本指南假設您已在系統上安裝了nvm與autoenv。\n為何要同時使用NVM和Autoenv？ nvm是一個出色的工具，可在系統上管理多個Node.js版本，讓您輕鬆地在它們之間切換。autoenv則透過在進入目錄時自動從.env檔案載入環境變數，簡化了環境變數的管理流程。\n結合這兩個工具，您可以設定開發環境，讓它自動切換至適當的Node.js版本並載入相關的環境變數，從而精簡您的工作流程。\n建立.env檔案 首先，在您的專案根目錄中建立一個.env檔案。此檔案將包含專案所需的環境變數及Node.js版本。\n以下是.env檔案的範例：\nexport NODE_ENV=development export API_KEY=your_api_key_here export PORT=3000 export NVM_DIR=\u0026#34;$HOME/.nvm\u0026#34; nvm use 14.17.0 在這個範例中，我們設定了NODE_ENV、API_KEY和PORT環境變數。我們也指定了nvm目錄的路徑，並指示其在專案中使用Node.js版本14.17.0。\n結合使用NVM和Autoenv 設定好.env檔案後，您需要設定autoenv以與nvm協同運作。請根據您使用的Shell，在.autoenv.zsh或.autoenv.sh檔案中加入以下這行：\nsource \u0026#34;$NVM_DIR/nvm.sh\u0026#34; 這行設定確保autoenv載入.env檔案時，nvm命令是可用的。\n設定您的專案 完成設定後，使用終端機切換到專案的根目錄。您應該會看到來自autoenv的訊息，表示它已載入.env檔案：\n$ cd your_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v14.17.0 現在，指定的Node.js版本及.env檔案中的環境變數將自動套用到您的專案。\n在專案之間切換 當您在擁有不同.env檔案的專案之間切換時，autoenv與nvm會自動調整Node.js版本及環境變數：\n$ cd another_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v12.22.1 這讓管理不同的Node.js版本與環境變得輕而易舉！\n結論 透過結合使用nvm與autoenv，您可以大幅簡化管理專案Node.js版本及環境變數的過程。這將使您的開發流程更加高效，並確保每個專案始終使用正確的設定。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-23-using-nvm-and-autoenv-in-combination/","summary":"\u003cp\u003e在本篇文章中，我們將學習如何將\u003ccode\u003envm\u003c/code\u003e（Node版本管理器）與\u003ccode\u003eautoenv\u003c/code\u003e結合使用，以管理開發工作流程中的Node.js版本及環境變數。本指南假設您已在系統上安裝了\u003ccode\u003envm\u003c/code\u003e與\u003ccode\u003eautoenv\u003c/code\u003e。\u003c/p\u003e","title":"結合使用NVM和Autoenv"},{"content":"在這篇文章中，我們將討論如何結合使用jEnv和autoenv來管理多個Java版本，並自動設定專案的環境變數。本教學假設你已在系統上安裝了jEnv和autoenv。我們將透過一些實際範例來示範它們的用法。\njEnv與autoenv概述 jEnv jEnv是一個命令列工具，能夠簡化在系統上管理多個Java安裝的流程。它讓你可以輕鬆切換不同的Java版本、設定全域或本機版本，並提供便利的環境設定方式。\nautoenv autoenv是一個命令列工具，當你切換到專案目錄時會自動設定環境變數。它透過尋找專案目錄中的.env檔案並執行其內容來運作。\n設定jEnv 在深入範例之前，先簡要介紹如何設定jEnv。首先，使用jenv add指令將已安裝的Java版本加入jEnv：\njenv add /path/to/java/version 若要列出所有已加入的Java版本，請使用：\njenv versions 若要設定全域Java版本，請使用：\njenv global \u0026lt;version\u0026gt; 若要為特定專案設定本機Java版本，請切換到該專案目錄後使用：\njenv local \u0026lt;version\u0026gt; 搭配使用jEnv與autoenv 現在，來看一些結合使用jEnv和autoenv的實際範例。\nExample 1: 為專案設定Java版本 在你的專案目錄中建立一個.env檔案，內容如下：\n# .env export JAVA_HOME=$(jenv prefix) 當你切換到該專案目錄時，autoenv會自動將JAVA_HOME環境變數設定為jEnv所設定的目前使用中Java版本。\nExample 2: 設定Java版本及其他環境變數 假設你有一個需要特定Java版本和一些額外環境變數的專案。在專案目錄中建立一個.env檔案，內容如下：\n# .env jenv local \u0026lt;version\u0026gt; export JAVA_HOME=$(jenv prefix) export APP_ENV=development export API_KEY=your_api_key 當你切換到該專案目錄時，autoenv會自動設定本機Java版本、JAVA_HOME及其他所需的環境變數。\nExample 3: 離開專案目錄時取消設定環境變數 若要在離開專案目錄時取消設定環境變數，請在專案目錄中建立一個.env.leave檔案，內容如下：\n# .env.leave unset JAVA_HOME unset APP_ENV unset API_KEY 現在，當你離開專案目錄時，autoenv會自動取消設定這些環境變數。\n結論 透過結合使用jEnv和autoenv，你可以簡化專案的Java版本和環境變數管理。這有助於建立更一致且可預期的開發環境，使你更容易在使用不同Java版本和設定的多個專案之間切換工作。此外，這些工具能自動化在各Java環境之間切換的流程，從而減少手動設定所需的時間，並將潛在錯誤降至最低。\n此外，透過善用jEnv和autoenv的強大功能，開發者可以更有效率地與團隊成員協作，因為設定流程變得更加簡化且可重現。這讓新成員的加入更加順暢，並促進一致的開發工作流程。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-22-how-to-use-jenv-and-autoenv-in-combination/","summary":"\u003cp\u003e在這篇文章中，我們將討論如何結合使用\u003ccode\u003ejEnv\u003c/code\u003e和\u003ccode\u003eautoenv\u003c/code\u003e來管理多個Java版本，並自動設定專案的環境變數。本教學假設你已在系統上安裝了\u003ccode\u003ejEnv\u003c/code\u003e和\u003ccode\u003eautoenv\u003c/code\u003e。我們將透過一些實際範例來示範它們的用法。\u003c/p\u003e","title":"如何結合使用jEnv與autoenv"},{"content":"在這篇文章中，我們將討論如何結合rbenv與autoenv的功能，在專案中無縫管理Ruby版本和環境變數。讀完本文後，你將清楚地了解如何同時使用這兩個工具，讓你的Ruby開發體驗更加完善。\n注意：本教學假設你已安裝rbenv和autoenv。如果尚未安裝，請參考rbenv和autoenv的安裝說明。\n為什麼要同時使用rbenv和autoenv？ rbenv是一個讓你輕鬆管理多個Ruby版本的強大工具。另一方面，autoenv幫助你管理特定於專案目錄的環境變數。同時使用這兩個工具，你可以確保在每個專案中使用正確的Ruby版本和環境變數，而無需手動介入。\n設定rbenv與autoenv 在深入範例之前，先來設定autoenv以與rbenv搭配使用。請在你的專案目錄下建立一個新的.env檔案，並加入以下幾行：\nexport RBENV_VERSION=$(cat .ruby-version) export PATH=\u0026#34;$HOME/.rbenv/shims:$PATH\u0026#34; 第一行根據.ruby-version檔案的內容設定RBENV_VERSION環境變數。第二行確保rbenv shims目錄在PATH中，使得執行Ruby指令時能使用正確的Ruby版本。\nExample 1: 切換Ruby版本 假設你有兩個專案：project_a和project_b。你想對project_a使用Ruby 2.7.4，對project_b使用Ruby 3.0.2。以下是如何用rbenv和autoenv達成這個目標：\n在每個專案目錄下建立一個.ruby-version檔案： echo \u0026#34;2.7.4\u0026#34; \u0026gt; project_a/.ruby-version echo \u0026#34;3.0.2\u0026#34; \u0026gt; project_b/.ruby-version 在每個專案目錄下建立一個.env檔案，內容如前所述： cp .env project_a/ cp .env project_b/ 現在，當你切換到project_a或project_b時，autoenv會自動設定RBENV_VERSION並調整PATH以使用正確的Ruby版本。\nExample 2: 管理專案專屬的環境變數 假設project_a需要以下環境變數：\nAPI_KEY：第三方服務的API金鑰 SECRET_KEY：用於加密資料的密鑰 你可以將這些變數加入project_a的.env檔案中：\nexport RBENV_VERSION=$(cat .ruby-version) export PATH=\u0026#34;$HOME/.rbenv/shims:$PATH\u0026#34; export API_KEY=\u0026#34;your_api_key_here\u0026#34; export SECRET_KEY=\u0026#34;your_secret_key_here\u0026#34; 現在，當你切換到project_a時，autoenv會自動設定RBENV_VERSION、調整PATH，並設定API_KEY和SECRET_KEY環境變數。\n結論 透過同時使用rbenv和autoenv，你可以輕鬆管理Ruby版本和專案專屬的環境變數，而無需任何手動介入。這個組合讓你的Ruby開發體驗更有效率、更不容易出錯。\n別忘了將.env檔案加入.gitignore，以避免不小心將敏感資訊提交到版本控制系統中。\n完成這些設定後，你將享受到為每個專案量身打造的順暢Ruby開發工作流程。祝你編碼愉快！\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-21-how-to-use-rbenv-and-autoenv-in-combination/","summary":"\u003cp\u003e在這篇文章中，我們將討論如何結合\u003ccode\u003erbenv\u003c/code\u003e與\u003ccode\u003eautoenv\u003c/code\u003e的功能，在專案中無縫管理Ruby版本和環境變數。讀完本文後，你將清楚地了解如何同時使用這兩個工具，讓你的Ruby開發體驗更加完善。\u003c/p\u003e","title":"如何結合使用rbenv與autoenv"},{"content":"I. 概述 在這篇文章中，我們將探討如何同時使用pyenv-virtualenv和autoenv來實現順暢的Python開發。這些工具可以幫助你輕鬆管理多個Python環境與虛擬環境，改善你的開發工作流程。\nII. Pyenv-virtualenv Pyenv-virtualenv是pyenv的一個外掛程式，讓你可以為不同的Python版本建立並管理虛擬環境。它能將不同專案的相依套件隔離開來，確保每個專案都能存取所需的套件而不互相干擾。\n建立虛擬環境 若要使用pyenv-virtualenv建立新的虛擬環境，請使用以下指令：\npyenv virtualenv \u0026lt;python-version\u0026gt; \u0026lt;virtualenv-name\u0026gt; 例如，若要使用Python 3.8.0建立名為my_project的虛擬環境：\npyenv virtualenv 3.8.0 my_project 列出虛擬環境 若要列出所有已建立的虛擬環境，請使用以下指令：\npyenv virtualenvs 啟用虛擬環境 若要啟用虛擬環境，請使用以下指令：\npyenv activate \u0026lt;virtualenv-name\u0026gt; 例如：\npyenv activate my_project 停用虛擬環境 若要停用目前的虛擬環境，請使用以下指令：\npyenv deactivate III. Autoenv Autoenv是一個工具，當你進入含有.env檔案的目錄時，會自動啟用虛擬環境。這讓你在專案之間切換時無需手動啟用與停用虛擬環境，十分便利。\n設定Autoenv 若要使用autoenv，你需要在專案的根目錄下建立一個.env檔案。這個檔案包含進入該目錄時應執行的指令。\n例如，假設你有一個位於~/projects/my_project的專案，並想使用先前建立的my_project虛擬環境。請在~/projects/my_project目錄下建立一個.env檔案，內容如下：\nsource $(pyenv root)/versions/my_project/bin/activate 使用Autoenv 現在，當你切換到專案目錄時，autoenv會自動為你啟用my_project虛擬環境：\ncd ~/projects/my_project 你應該會看到一則訊息，表示虛擬環境已啟用：\nautoenv: Activating environment . . . (my_project) $ 當你離開專案目錄時，虛擬環境會自動停用：\ncd ~ 你應該會看到一則訊息，表示虛擬環境已停用：\nautoenv: Deactivating environment . . . $ IV. 結論 透過結合pyenv-virtualenv和autoenv，你可以打造出順暢的開發工作流程，用於管理多個Python專案。這種方式確保你為每個專案都使用正確的虛擬環境，同時也保持相依套件的隔離與組織。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-20-using-pyenv-virtualenv-and-autoenv-in-combination-for-python-development/","summary":"\u003ch1 id=\"i-概述\"\u003eI. 概述\u003c/h1\u003e\n\u003cp\u003e在這篇文章中，我們將探討如何同時使用\u003ccode\u003epyenv-virtualenv\u003c/code\u003e和\u003ccode\u003eautoenv\u003c/code\u003e來實現順暢的Python開發。這些工具可以幫助你輕鬆管理多個Python環境與虛擬環境，改善你的開發工作流程。\u003c/p\u003e","title":"結合Pyenv-virtualenv與Autoenv用於Python開發"},{"content":"I. 概述 Autoenv是一個讓你輕鬆管理環境變數的工具。有了它，你可以在進入目錄時自動設定環境變數，並在離開時取消設定。這使它成為一個強大的工具，能夠為不同的專案管理不同的環境，並將重複性工作自動化。在這篇文章中，我們將介紹如何在不同平台上安裝Autoenv，以及一些使用範例。\nII. 安裝 Mac Autoenv可以透過Homebrew輕鬆安裝在macOS上。首先，確認你已安裝Homebrew。接著執行以下指令：\nbrew install autoenv Linux Autoenv可以用apt-get安裝在Linux系統上。執行以下指令：\nsudo apt-get install autoenv Windows Autoenv可以透過Git Bash或WSL安裝在Windows上。你可以從Git網站下載Git Bash，或在Windows 10上啟用WSL。安裝完成後，請依照Linux的安裝說明進行操作。\nIII. 使用範例 Autoenv安裝完成後，你可以用它來管理不同專案的環境變數。來看看一些範例。\nExample 1: 設定環境變數 假設你正在開發一個需要設定特定環境變數的Python專案。你可以在專案目錄下建立一個.env檔案，內容如下：\nexport API_KEY=my_api_key export DATABASE_URL=postgres://user:password@localhost/mydatabase 現在，當你進入專案目錄時，Autoenv會自動設定這些環境變數。你可以在Python程式碼中透過os.environ來存取它們。\nExample 2: 在進入目錄時執行指令 你也可以設定Autoenv在進入目錄時執行指令。例如，假設你每次進入專案目錄時都想自動啟用虛擬環境。你可以在.env檔案中加入以下這行：\nsource venv/bin/activate 現在，當你進入專案目錄時，Autoenv會自動啟用虛擬環境。\nExample 3: 取消設定環境變數 當你離開目錄時，Autoenv也可以取消你所設定的環境變數。這對於清理自己的工作環境、避免環境變數洩漏至其他專案非常有用。要做到這點，請在.env檔案中加入以下幾行：\nunset API_KEY unset DATABASE_URL 現在，當你離開專案目錄時，Autoenv會自動取消環境變數的設定。\nIV. 結論 Autoenv是一個用於管理環境變數和自動化重複性工作的強大工具。有了它，你可以輕鬆設定與取消環境變數、在進入目錄時執行指令等。在系統上安裝Autoenv並應用於你的專案，可以節省時間並簡化工作流程。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-19-using-autoenv-the-ultimate-shortcut-to-environment-management/","summary":"\u003ch1 id=\"i-概述\"\u003eI. 概述\u003c/h1\u003e\n\u003cp\u003eAutoenv是一個讓你輕鬆管理環境變數的工具。有了它，你可以在進入目錄時自動設定環境變數，並在離開時取消設定。這使它成為一個強大的工具，能夠為不同的專案管理不同的環境，並將重複性工作自動化。在這篇文章中，我們將介紹如何在不同平台上安裝Autoenv，以及一些使用範例。\u003c/p\u003e","title":"使用Autoenv：環境管理的終極捷徑"},{"content":"I. 概述 Node版本管理器（NVM）是一個管理並切換多個Node.js版本的實用工具。在這篇文章中，我們將介紹NVM最常用的功能、不同平台的安裝說明，以及它為何是開發者不可或缺的工具。\nII. 安裝 macOS 使用Homebrew：\nbrew install nvm mkdir ~/.nvm 在你的.bash_profile、.zshrc或其他Shell設定檔中加入以下幾行：\nexport NVM_DIR=\u0026#34;$HOME/.nvm\u0026#34; [ -s \u0026#34;$(brew --prefix)/opt/nvm/nvm.sh\u0026#34; ] \u0026amp;\u0026amp; . \u0026#34;$(brew --prefix)/opt/nvm/nvm.sh\u0026#34; # This loads nvm [ -s \u0026#34;$(brew --prefix)/opt/nvm/etc/bash_completion\u0026#34; ] \u0026amp;\u0026amp; . \u0026#34;$(brew --prefix)/opt/nvm/etc/bash_completion\u0026#34; # This loads nvm bash_completion Linux及其他Unix系統 使用curl：\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 或使用wget：\nwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 此腳本會將NVM儲存庫複製到~/.nvm，並在你的Shell設定檔（.bashrc、.zshrc等）中加入必要的設定行。\nIII. 使用方法 1. 列出可用的Node.js版本 若要查看可用的Node.js版本清單，請執行：\nnvm ls-remote 2. 安裝特定的Node.js版本 若要安裝特定版本，請使用nvm install指令並加上版本號：\nnvm install 14.17.0 3. 列出已安裝的Node.js版本 若要查看已安裝的Node.js版本清單，請執行：\nnvm ls 4. 在Node.js版本之間切換 若要切換到特定的Node.js版本，請使用nvm use指令並加上版本號：\nnvm use 14.17.0 5. 設定預設的Node.js版本 若要為新的Shell工作階段設定預設版本，請使用nvm alias指令：\nnvm alias default 14.17.0 6. 移除Node.js版本 若要移除特定的Node.js版本，請使用nvm uninstall指令並加上版本號：\nnvm uninstall 14.17.0 7. 安裝最新的LTS（長期支援）版本 若要安裝最新的LTS版本，請執行：\nnvm install --lts 8. 更新已安裝的Node.js版本 若要將已安裝的版本更新至最新修補版，請使用nvm reinstall-packages指令：\nnvm install 14.17.0 --reinstall-packages-from=14.16.0 9. 使用特定Node.js版本執行腳本 若要使用特定Node.js版本執行腳本而不切換目前版本，請使用nvm exec指令：\nnvm exec 14.17.0 node script.js 10. 使用特定Node.js版本執行指令 若要使用特定Node.js版本執行指令而不切換目前版本，請使用nvm run指令：\nnvm run 14.17.0 --version IV. 結論 NVM是一個強大的工具，讓開發者能夠輕鬆管理多個Node.js版本。它能在Node.js版本之間輕鬆切換，使得在不同環境中測試應用程式，或同時處理有不同Node.js需求的多個專案變得簡單。\n透過本文所介紹的最常用功能，你現在應該能夠在系統上安裝NVM、管理Node.js版本，並有效地使用這個工具。祝你編碼愉快！\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-18-managing-multiple-nodejs-environments-with-nvm/","summary":"\u003ch1 id=\"i-概述\"\u003eI. 概述\u003c/h1\u003e\n\u003cp\u003eNode版本管理器（NVM）是一個管理並切換多個Node.js版本的實用工具。在這篇文章中，我們將介紹NVM最常用的功能、不同平台的安裝說明，以及它為何是開發者不可或缺的工具。\u003c/p\u003e","title":"用NVM管理多個Node.js環境"},{"content":"Java 是一門極受歡迎的程式語言，而管理多個 Java 版本可能是一項頗具挑戰的任務。這正是 jEnv 派上用場的時候。本文將介紹 jEnv 最常用的功能，包含各平台的安裝說明，幫助您輕鬆管理 Java 版本。\nI. 概述 jEnv 是一款命令列工具，讓您能夠管理系統上的多個 Java 版本。它允許您以專案為單位、以 shell 工作階段為單位，或在整個系統範圍內設定所需的 Java 版本。使用 jEnv，您可以避免需要不同 Java 版本的不同專案之間發生衝突，確保更順暢的開發體驗。\nII. 安裝 Mac 要在 macOS 上安裝 jEnv，您可以使用 Homebrew 套件管理器。若您尚未安裝 Homebrew，可以在這裡找到安裝說明。\nbrew install jenv 安裝完成後，依照您使用的 shell，在 ~/.bash_profile、~/.zshrc 或 ~/.bashrc 檔案中加入以下幾行：\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; Linux 要在 Linux 上安裝 jEnv，請執行以下指令：\ngit clone https://github.com/jenv/jenv.git ~/.jenv 複製儲存庫後，依照您使用的 shell，在 ~/.bashrc 或 ~/.zshrc 檔案中加入以下幾行：\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; III. 使用方法 local local 指令為特定目錄設定 Java 版本。當您需要在多個有不同 Java 版本需求的專案上工作時，這個指令非常有用。\njenv local 11.0.2 global global 指令為整個系統設定預設的 Java 版本。若 local 或 shell 指令未指定其他版本，則會使用此版本。\njenv global 11.0.2 shell shell 指令為目前的 shell 工作階段設定 Java 版本。當您想要暫時使用不同的 Java 版本而不影響其他專案或工作階段時，這個指令非常有用。\njenv shell 11.0.2 rehash rehash 指令為 jEnv 已知的所有 Java 可執行檔產生 shim。在安裝新的 Java 版本後，或當 jEnv 無法識別某些 Java 可執行檔時，這個指令很有用。\njenv rehash version version 指令顯示目前啟用的 Java 版本。\njenv version versions versions 指令列出所有已安裝的 Java 版本，並以星號標示目前啟用的版本。\njenv versions which which 指令顯示目前啟用版本的 Java 可執行檔路徑。\njenv which java whence whence 指令列出包含特定指令的所有 Java 版本。\njenv whence java add add 指令在 jEnv 中註冊新的 Java 版本。您需要提供 Java 安裝目錄的路徑。\njenv add /path/to/java/home IV. 結論 jEnv 是一款簡化 Java 版本管理的強大工具。透過 local、global 和 shell 等功能，您可以輕鬆地以專案或工作階段為單位切換 Java 版本。rehash、version、versions、which、whence 和 add 等指令進一步增強了您管理和整理 Java 安裝的能力。使用 jEnv，您可以簡化開發工作流程、避免版本衝突，並確保專案在所需的 Java 版本上運行。無論您是在處理多個 Java 專案，還是僅僅希望對 Java 環境有更大的掌控，jEnv 都是 Java 開發者不可或缺的工具。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-17-a-practical-guide-to-using-jenv-for-java-version-management/","summary":"\u003cp\u003eJava 是一門極受歡迎的程式語言，而管理多個 Java 版本可能是一項頗具挑戰的任務。這正是 jEnv 派上用場的時候。本文將介紹 jEnv 最常用的功能，包含各平台的安裝說明，幫助您輕鬆管理 Java 版本。\u003c/p\u003e","title":"使用 jEnv 進行 Java 版本管理的實務指南"},{"content":"本文將介紹如何使用 pyenv 和 pyenv-virtualenv——兩款強大的工具，能幫助您輕鬆管理多個 Python 版本和虛擬環境。我們將涵蓋 Mac 和 Linux 等各平台的安裝說明，並討論這些工具最常用的功能。讀完本文，您將對如何在開發工作流程中有效使用這些工具有扎實的理解。\nI. 概述 pyenv 是一款強大的 Python 版本管理工具，讓您能輕鬆安裝和切換多個 Python 版本。pyenv-virtualenv 是 pyenv 的擴充套件，讓您能管理多個虛擬環境。這些工具在處理具有不同相依性和 Python 版本的多個專案時特別有用。\nII. 安裝 Mac 要在 macOS 上安裝 pyenv 和 pyenv-virtualenv，您可以使用 Homebrew：\nbrew update brew install pyenv brew install pyenv-virtualenv 安裝完成後，在您的 shell 設定檔（.bashrc、.zshrc 等）中加入以下幾行：\nif command -v pyenv 1\u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then eval \u0026#34;$(pyenv init -)\u0026#34; fi if command -v pyenv-virtualenv-init 1\u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; fi Linux 要在 Linux 上安裝 pyenv 和 pyenv-virtualenv，請先複製儲存庫並將其加入 PATH：\ngit clone https://github.com/pyenv/pyenv.git ~/.pyenv git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv 接著，在您的 shell 設定檔（.bashrc、.zshrc 等）中加入以下幾行：\nexport PYENV_ROOT=\u0026#34;$HOME/.pyenv\u0026#34; export PATH=\u0026#34;$PYENV_ROOT/bin:$PATH\u0026#34; eval \u0026#34;$(pyenv init -)\u0026#34; eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; III. 使用方法 1. 安裝 Python 版本 要安裝特定的 Python 版本，請使用 install 指令：\npyenv install 3.9.5 2. 列出可用的 Python 版本 要查看所有已安裝的 Python 版本，請使用 versions 指令：\npyenv versions 3. 設定全域 Python 版本 要設定全域 Python 版本，請使用 global 指令：\npyenv global 3.9.5 4. 設定本機 Python 版本 要為特定專案設定本機 Python 版本，請在專案目錄內使用 local 指令：\npyenv local 3.8.10 5. 確認目前的 Python 版本 要確認目前的 Python 版本，請使用 version 指令：\npyenv version 6. 建立虛擬環境 要使用 pyenv-virtualenv 建立新的虛擬環境，請使用 virtualenv 指令：\npyenv virtualenv 3.9.5 my-project-env 7. 啟用虛擬環境 要啟用虛擬環境，請使用 activate 指令：\npyenv activate my-project-env 8. 停用虛擬環境 要停用目前的虛擬環境，請使用 deactivate 指令：\npyenv deactivate 9. 列出可用的虛擬環境 要列出所有已建立的虛擬環境，請使用 virtualenvs 指令：\npyenv virtualenvs 10. 移除虛擬環境 要移除虛擬環境，請使用 uninstall 指令：\npyenv uninstall my-project-env Bonus: Rehash 每當您安裝含有可執行腳本的新 Python 套件時，都必須執行 rehash 指令來更新 shim，確保新腳本可以正常使用：\npyenv rehash IV. Conclusion pyenv 和 pyenv-virtualenv 是在開發工作流程中管理多個 Python 版本和虛擬環境的寶貴工具。善用本指南所介紹的功能，您將能夠在具有不同相依性和 Python 版本的多個專案之間游刃有餘地工作。善用這些工具，維持簡潔有序的開發環境，提升您的生產力，同時降低相依性衝突的風險。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-16-how-to-use-pyenv-and-pyenv-virtualenv/","summary":"\u003cp\u003e本文將介紹如何使用 \u003ccode\u003epyenv\u003c/code\u003e 和 \u003ccode\u003epyenv-virtualenv\u003c/code\u003e——兩款強大的工具，能幫助您輕鬆管理多個 Python 版本和虛擬環境。我們將涵蓋 Mac 和 Linux 等各平台的安裝說明，並討論這些工具最常用的功能。讀完本文，您將對如何在開發工作流程中有效使用這些工具有扎實的理解。\u003c/p\u003e","title":"如何使用 pyenv 和 pyenv-virtualenv"},{"content":"Ruby 開發者經常需要同時處理多個專案，每個專案都有各自特定的版本需求。因此，一個多功能且易於使用的版本管理工具顯得格外重要。本文將介紹 rbenv——一款廣受歡迎的 Ruby 環境管理器，為這個問題提供了優雅的解決方案。我們將討論最常用的功能、各平台的安裝說明，並以結論作為收尾。\nI. 概述 rbenv 是一款輕量級的 Ruby 版本管理工具，讓您能夠以專案為單位或在整個系統範圍內切換不同的 Ruby 版本。透過 rbenv，您可以輕鬆安裝新的 Ruby 版本、保持版本更新，並為每個版本維護獨立的 gem set。\nrbenv 最常用的功能包括：\n安裝 Ruby 版本 設定全域 Ruby 版本 設定本機（專案專用）Ruby 版本 列出已安裝的 Ruby 版本 移除 Ruby 版本 II. 安裝 Mac 要在 macOS 上安裝 rbenv，您可以使用 Homebrew：\nbrew install rbenv 安裝完成後，將 rbenv 加入 bash，使其在每次開啟終端機時自動載入：\necho \u0026#39;if which rbenv \u0026gt; /dev/null; then eval \u0026#34;$(rbenv init -)\u0026#34;; fi\u0026#39; \u0026gt;\u0026gt; ~/.zshrc source ~/.zshrc Linux 要在 Linux 系統上安裝 rbenv，請依照以下步驟操作：\n更新套件清單： sudo apt-get update 安裝相依套件： sudo apt-get install -y build-essential libssl-dev libreadline-dev zlib1g-dev 從 GitHub 儲存庫複製 rbenv： git clone https://github.com/rbenv/rbenv.git ~/.rbenv 將 rbenv 加入 PATH： echo \u0026#39;export PATH=\u0026#34;$HOME/.rbenv/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc 將 rbenv 初始化加入 shell： echo \u0026#39;eval \u0026#34;$(rbenv init -)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc 重新啟動 shell： exec $SHELL III. 使用方法 1. 安裝 Ruby 版本 要安裝特定的 Ruby 版本，請先安裝 ruby-build 外掛：\nbrew install ruby-build 接著即可安裝所需的 Ruby 版本：\nrbenv install 2.7.0 2. 設定全域 Ruby 版本 要為系統設定全域 Ruby 版本，請使用 global 指令：\nrbenv global 2.7.0 3. 設定本機（專案專用）Ruby 版本 要為特定專案設定 Ruby 版本，請切換到該專案目錄並使用 local 指令：\ncd /path/to/your/project rbenv local 2.7.0 4. 列出已安裝的 Ruby 版本 要列出所有已安裝的 Ruby 版本，請使用 versions 指令：\nrbenv versions 5. 移除 Ruby 版本 要移除已安裝的 Ruby 版本，請使用 uninstall 指令：\nrbenv uninstall 2.7.0 IV. 結論 總之，對於需要管理多個 Ruby 環境的 Ruby 開發者而言，rbenv 是不可或缺的工具。它提供了一種簡單而強大的方式來切換 Ruby 版本、管理 gem set，並確保專案特定的相依性得到妥善維護。由於安裝簡便且具備跨平台相容性，rbenv 是任何希望簡化開發流程、保持專案整潔有序的 Rubyist 的必備工具。試試 rbenv 吧，您很快就會發現沒有它之前是怎麼過來的。\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-15-managing-multiple-ruby-environments-with-rbenv/","summary":"\u003cp\u003eRuby 開發者經常需要同時處理多個專案，每個專案都有各自特定的版本需求。因此，一個多功能且易於使用的版本管理工具顯得格外重要。本文將介紹 rbenv——一款廣受歡迎的 Ruby 環境管理器，為這個問題提供了優雅的解決方案。我們將討論最常用的功能、各平台的安裝說明，並以結論作為收尾。\u003c/p\u003e","title":"用 rbenv 管理多個 Ruby 環境"},{"content":"I. 簡介 本開發報告旨在示範如何使用 Notion API 自動安排健身時程。Notion 是一款多合一的生產力工具，讓使用者能夠建立和整理資料庫、筆記、任務等。Notion API 為開發者提供以程式方式存取 Notion 功能的途徑，進而實現各種任務的自動化。\n所提供的程式碼片段展示了如何使用 Notion API，根據預先定義的時程在資料庫中建立健身事件。該腳本會擷取特定日期範圍內的現有事件，並為沒有任何排定事件的日子建立新事件。健身計畫定義在 days_of_week 字典中，它將每週的每一天對應到健身名稱和標籤清單。\n透過使用 Notion API 將健身排程流程自動化，開發者可以節省時間並減少人工錯誤。本報告將提供逐步指南，協助您設定 Notion API 並理解程式碼片段的各個組成部分。\nII. 設定 Notion API 要使用 Notion API，您需要有一個 Notion 帳號並設定好整合。以下是驗證 Notion API 的逐步指南：\n在 Notion 建立整合\n登入 Notion，點擊畫面左下角的個人頭像，再點擊「整合」，進入整合頁面。 點擊「建立新整合」按鈕。 為您的整合命名，並選擇要使用的工作區。 點擊「提交」按鈕以建立整合。 取得整合權杖\n整合建立後，點擊整合名稱即可取得整合權杖。 複製整合權杖以便在程式碼中使用。 安裝 Notion API 套件\n開啟終端機或命令提示字元。\n執行以下指令安裝 Notion API 套件：\npip install notion-client 設定 Notion API 用戶端\n在程式碼頂部加入以下這行來匯入 Notion API 用戶端：\nfrom notion_client import Client 加入以下這行並將 \u0026lt;API Key\u0026gt; 替換為您的整合權杖來驗證用戶端：\nnotion = Client(auth=\u0026#34;\u0026lt;API Key\u0026gt;\u0026#34;) 設定資料庫詳細資訊\n取得您要新增事件的資料庫的資料庫 ID。 將程式碼片段中的 database_id 變數替換為您的資料庫 ID。 所提供的程式碼片段使用 notion_client.Client 物件與 Notion API 互動。Client 物件用於驗證 API 用戶端，並提供存取各種 Notion 資源（如資料庫和頁面）的途徑。\ndatabase_id 變數設定為健身事件將被新增到的 Notion 資料庫的 ID。該 ID 可以在資料庫的 URL 中找到。\nIII. 定義健身計畫 健身計畫在程式碼片段中使用以下變數定義：\nstart_days_ago：在今天日期之前開始計畫的天數。 end_days_after：在今天日期之後結束計畫的天數。 days_of_week：將每週每一天對應到健身名稱和標籤清單的字典。 days_of_week 字典用於定義每週每一天的健身計畫。每週的每一天都是字典中的一個鍵，對應的值是另一個字典，包含健身名稱和與健身相關的標籤清單。例如：\ndays_of_week = { \u0026#34;Monday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 1\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;chest\u0026#34;, \u0026#34;triceps\u0026#34;]}, \u0026#34;Tuesday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 2\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;back\u0026#34;, \u0026#34;biceps\u0026#34;]}, \u0026#34;Wednesday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 3\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;lower body\u0026#34;, \u0026#34;shoulder\u0026#34;]}, \u0026#34;Thursday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 1\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;chest\u0026#34;, \u0026#34;triceps\u0026#34;]}, \u0026#34;Friday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 2\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;back\u0026#34;, \u0026#34;biceps\u0026#34;]}, \u0026#34;Saturday\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;DAY 3\u0026#34;, \u0026#34;tag\u0026#34;: [\u0026#34;lower body\u0026#34;, \u0026#34;shoulder\u0026#34;]}, } 在這個範例中，計畫分為三天，每天都有對應的特定健身內容和標籤。\nstart_days_ago 和 end_days_after 變數用於定義健身計畫的日期範圍。start_days_ago 指定從今天日期幾天前開始排程，end_days_after 指定從今天日期幾天後結束排程。\n程式碼片段使用 datetime 模組來取得今天的日期，並計算健身計畫的開始和結束日期。開始日期是今天日期減去 start_days_ago 得到的，結束日期是今天日期加上 end_days_after 得到的。日期格式為 %Y-%m-%d 格式的字串。\nIV. 查詢現有事件 在建立新的健身事件之前，程式碼片段會擷取定義日期範圍內的現有事件。這樣做是為了避免在同一日期建立重複的事件。\nnotion.databases.query() 方法用於查詢 Notion 資料庫並擷取現有事件。filter 參數用於指定查詢的篩選條件。在此案例中，篩選器設定為擷取屬於程式碼片段中定義的開始和結束日期之間的事件。以下是查詢中使用的篩選器範例：\n{ \u0026#34;and\u0026#34;: [ {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_after\u0026#34;: start_date}}, {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_before\u0026#34;: end_date}} ] } 此篩選器會傳回 Date 屬性介於 start_date 和 end_date 之間的事件。\n查詢回應的 results 屬性用於取得日期範圍內的現有事件。結果儲存在 existing_events 變數中，供程式碼片段稍後使用。\nexisting_events = notion.databases.query( **{ \u0026#34;database_id\u0026#34;: database_id, \u0026#34;filter\u0026#34;: { \u0026#34;and\u0026#34;: [ {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_after\u0026#34;: start_date}}, {\u0026#34;property\u0026#34;: \u0026#34;Date\u0026#34;, \u0026#34;date\u0026#34;: {\u0026#34;on_or_before\u0026#34;: end_date}} ] }, } ).get(\u0026#34;results\u0026#34;) V. 建立新事件 取得日期範圍內的現有事件後，程式碼片段接著為計畫日期範圍內的每一天建立新的健身事件。\n程式碼片段使用 for 迴圈來迭代日期範圍內的每個日期。為每個非星期日的日子建立新事件：\nfor date in (datetime.now() + timedelta(days=n) for n in range(-start_days_ago, end_days_after + 1)): # skip Sundays if date.strftime(\u0026#34;%A\u0026#34;) == \u0026#34;Sunday\u0026#34;: continue strftime() 方法用於從日期取得星期名稱，若星期名稱為「Sunday」，迴圈會跳過該天並繼續下一天。\n接著，程式碼片段檢查當天是否已存在事件：\nif any(event.get(\u0026#34;properties\u0026#34;).get(\u0026#34;Date\u0026#34;).get(\u0026#34;date\u0026#34;).get(\u0026#34;start\u0026#34;) == date.strftime(\u0026#34;%Y-%m-%d\u0026#34;) for event in existing_events): print(f\u0026#34;Event already exists for {date.strftime(\u0026#39;%Y-%m-%d\u0026#39;)}. Skipping.\u0026#34;) continue 若當天已存在事件，迴圈會跳過該天並繼續下一天。\n若當天不存在事件，則使用與該星期幾相關的健身名稱和標籤建立新事件：\nday_of_week = date.strftime(\u0026#34;%A\u0026#34;) event_name = days_of_week[day_of_week][\u0026#34;name\u0026#34;] event_tag = days_of_week[day_of_week][\u0026#34;tag\u0026#34;] new_event = { \u0026#34;Name\u0026#34;: {\u0026#34;title\u0026#34;: [{\u0026#34;text\u0026#34;: {\u0026#34;content\u0026#34;: event_name}}]}, \u0026#34;Date\u0026#34;: {\u0026#34;date\u0026#34;: {\u0026#34;start\u0026#34;: date.strftime(\u0026#34;%Y-%m-%d\u0026#34;)}}, \u0026#34;Tag\u0026#34;: {\u0026#34;multi_select\u0026#34;: [{\u0026#34;name\u0026#34;: tag} for tag in event_tag]}, } notion.pages.create(parent={\u0026#34;database_id\u0026#34;: database_id}, properties=new_event) 與該星期幾相關的健身名稱和標籤從 days_of_week 字典中取得。new_event 字典用於定義新事件的屬性，包括事件名稱、日期和標籤。notion.pages.create() 方法用於在 Notion 資料庫中建立新事件。\n透過在計畫日期範圍內為每週的每一天建立新事件，開發者可以將健身排程流程自動化，確保健身計畫保持最新且正確。\nVI. 結論 在這個專案中，我們示範了如何使用 Notion API 自動建立 Notion 資料庫中健身計畫的流程。所提供的程式碼片段可用於建立特定日期範圍的健身計畫，並依據定義好的健身計畫自動向資料庫新增事件。\nNotion API 可用於自動化各種任務，例如管理待辦事項清單、追蹤支出或整理專案任務。透過 Notion API，開發者可以建立自訂整合並將重複性任務自動化，從而節省時間並提升生產力。\n總的來說，Notion API 為開發者提供了一個強大的工具，能將 Notion 整合到工作流程中並實現任務自動化。隨著 Notion 持續普及，我們可以期待看到更多開發者利用 Notion API 建立整合和自動化任務。\n未來，我們計畫透過增加更多功能和與其他工具的整合來擴展這個專案。我們也計畫探索 Notion API 的其他使用情境，看看如何將它應用於自動化工作流程中的其他任務。\nSource code : https://github.com/hobbyworker/notion-workout-scheduler\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-14-automating-workout-scheduling-with-notion-api/","summary":"\u003ch1 id=\"i-簡介\"\u003e\u003cstrong\u003eI. 簡介\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003e本開發報告旨在示範如何使用 Notion API 自動安排健身時程。Notion 是一款多合一的生產力工具，讓使用者能夠建立和整理資料庫、筆記、任務等。Notion API 為開發者提供以程式方式存取 Notion 功能的途徑，進而實現各種任務的自動化。\u003c/p\u003e","title":"用 Notion API 自動化健身排程"},{"content":"概述 Homebrew 是一款免費的開源套件管理器，能簡化在 macOS 上安裝和管理軟體的流程。使用 Homebrew，您可以輕鬆安裝、更新和移除 macOS App Store 中沒有提供的軟體套件。由於其簡便易用的特性，它深受開發者和進階使用者的喜愛。\n安裝方式 在安裝 Homebrew 之前，請先確認您具備以下條件：\n已安裝最新版 Xcode Command Line Tools 的 macOS 裝置 穩定的網路連線 要安裝 Homebrew，請在 Mac 上開啟終端機並執行以下指令：\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; 此指令會從 Homebrew 的儲存庫下載安裝腳本並執行。安裝過程可能需要幾分鐘。完成後，您可以執行以下指令來確認 Homebrew 是否安裝成功：\nbrew --version 若畫面上顯示版本號碼，恭喜您！已成功在 macOS 裝置上安裝 Homebrew。\n如何使用 Homebrew Homebrew 是以「formulae」為核心概念建構的，formulae 本質上是安裝軟體套件的指令。這些 formulae 以 Ruby 撰寫，並存放在 Homebrew 的官方儲存庫「Homebrew/core」中。\n以下是一些幫助您入門的基本 Homebrew 指令：\n搜尋套件： 使用 brew search \u0026lt;package-name\u0026gt; 搜尋可用的套件。將 \u0026lt;package-name\u0026gt; 替換為您要尋找的套件名稱。 安裝套件： 使用 brew install \u0026lt;package-name\u0026gt; 安裝所需的套件。 移除套件： 使用 brew uninstall \u0026lt;package-name\u0026gt; 移除已安裝的套件。 列出已安裝的套件： 使用 brew list 顯示所有已安裝套件的清單。 更新 Homebrew： 使用 brew update 將 Homebrew 及其 formulae 更新至最新版本。 升級已安裝的套件： 使用 brew upgrade 將所有已安裝的套件升級至最新版本。 Example: 安裝 \u0026lsquo;wget\u0026rsquo; 在這個範例中，我們將安裝 wget——一個廣泛使用的命令列工具，用來從網際網路下載檔案。只需在終端機中執行以下指令即可：\nbrew install wget Homebrew 接著會下載並安裝 wget 套件。安裝完成後，您可以輸入 wget，接著加上所需的選項與欲下載檔案的 URL 來使用它。例如，若要下載一個範例檔案，可以執行：\nwget https://example.com/sample-file.txt 這會從指定的 URL 下載 sample-file.txt，並儲存到您目前的工作目錄中。\n結論 Homebrew 是一款強大且易於使用的套件管理器，填補了 macOS 軟體管理能力上的重要空缺。它簡化了安裝、更新和移除 App Store 中沒有提供的開源軟體與工具的流程。憑藉直覺的命令列介面和龐大的 formulae 儲存庫，Homebrew 已成為 macOS 使用者（尤其是開發者和進階使用者）不可或缺的工具。\n本文介紹了 Homebrew 的基礎知識，包括如何安裝、使用，以及安裝 wget 工具的實際範例。按照所提供的說明與指令，您現在可以自信地探索並安裝各式各樣的軟體套件，進一步提升您的 macOS 使用體驗。祝您使用愉快！\n","permalink":"https://hobbyworker.me/zh-hant/dev/2023-03-13-a-beginners-guide-to-homebrew-the-missing-package-manager-for-macos/","summary":"\u003ch1 id=\"概述\"\u003e概述\u003c/h1\u003e\n\u003cp\u003eHomebrew 是一款免費的開源套件管理器，能簡化在 macOS 上安裝和管理軟體的流程。使用 Homebrew，您可以輕鬆安裝、更新和移除 macOS App Store 中沒有提供的軟體套件。由於其簡便易用的特性，它深受開發者和進階使用者的喜愛。\u003c/p\u003e","title":"Homebrew 入門指南：macOS 的套件管理器"},{"content":"","permalink":"https://hobbyworker.me/zh-hant/projects/","summary":"","title":"專案"}]