[{"content":"Completing the DMG Layout with Coordinates In Part 1, we introduced the create-dmg tool and prepared a staging folder holding only the .app along with a background image.\nNow for the final piece, the layout. How large to open the DMG window, and where to place the app icon and the Applications shortcut — all of this is decided by the coordinates you pass to create-dmg. In this article, we\u0026rsquo;ll design those coordinates one by one.\nAs in Part 1, FocusTimer, the coordinate values, and so on are all examples.\nThe Full create-dmg Command First, let\u0026rsquo;s look at the complete command in full. This is the command that builds the DMG for the example app FocusTimer 1.0:\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; The last two lines are the output and the input:\nbuild/FocusTimer-1.0.dmg — the path of the DMG file to be created build/dmg-stage/ — the staging folder that held only the .app from Part 1 The lines above them that start with -- are the options that design the window and the icons. Let\u0026rsquo;s go through them one by one.\nUnderstanding the Coordinate System Before we look at the options, let\u0026rsquo;s nail down how the coordinates work.\nAll of create-dmg\u0026rsquo;s position coordinates are coordinates within the DMG window. The unit is the point explained in Part 1.\nThe origin (0, 0) is the top-left corner of the window. X increases as you move right, and Y increases as you move down. (Note that, unlike a math graph, Y increases downward.) If you set the window size to 600 400, the coordinates move within the range 0–600 horizontally and 0–400 vertically. An icon\u0026rsquo;s position coordinate points to the center of that icon — not its top-left corner, but its dead center.\nThe Options, One by One Option Value (example) Meaning --volname \u0026quot;FocusTimer 1.0\u0026quot; The volume name shown when the DMG is mounted --background build/dmg-background.png The window background image (the one prepared in Part 1) --window-pos 200 120 The position where the window opens on the screen (X, Y) --window-size 600 400 The size of the window (width, height — in points) --icon-size 100 The size of the icons inside the window (in points) --icon \u0026quot;FocusTimer.app\u0026quot; 175 190 The position of the app icon (X, Y within the window) --hide-extension \u0026quot;FocusTimer.app\u0026quot; — Hides the .app extension in the icon\u0026rsquo;s name --app-drop-link 425 190 The position of the Applications folder shortcut (X, Y within the window) --no-internet-enable — Turns off the no-longer-used \u0026ldquo;internet-enable\u0026rdquo; feature A few of these are worth calling out separately.\n--window-pos vs. --window-size — these two are easy to mix up. --window-pos is where on the screen (the desktop) the window appears when the user opens the DMG, while --window-size is the size of the window itself. The one that matters for coordinate design is --window-size.\n--icon-size 100 — the icons inside the window are drawn at a size of 100 points. You need to know this value when designing the background image too, because the 100-point space each icon occupies (= 200 pixels in the background image) must be left empty in the background.\n--icon and --app-drop-link — these two are the left and right stars of the window. --icon is the app icon the user has to drag, and --app-drop-link is the Applications folder shortcut that serves as the drop destination. With --app-drop-link, there\u0026rsquo;s no need to create a symbolic link separately — create-dmg makes and places the Applications shortcut for you.\n--hide-extension — without this, the icon shows the full FocusTimer.app with its extension underneath. Hiding it makes it appear cleanly as just FocusTimer.\n--no-internet-enable — \u0026ldquo;internet-enable\u0026rdquo; is an old feature by which an older Safari automatically processed downloaded DMGs; it\u0026rsquo;s no longer used today. Passing this option makes create-dmg skip that processing step and the related warning.\nCoordinate Design — Placing the Two Icons Now for the heart of it. We set the window size to 600 400, so within that we decide where to place the app icon and the Applications shortcut.\nIn the example command, we set them like this:\nApp icon: --icon \u0026quot;FocusTimer.app\u0026quot; 175 190 Applications shortcut: --app-drop-link 425 190 Here\u0026rsquo;s what these numbers mean:\nThe X axis (horizontal) — 175 and 425\nThe window width is 600 points, and its center is 300. Placing the two icons at 175 and 425 puts each one 125 to the left and 125 to the right of the center. In other words, the two icons are placed symmetrically about the center. With the app icon on the left and Applications on the right, this creates the natural direction of \u0026ldquo;drag from left to right.\u0026rdquo;\nThe Y axis (vertical) — both at 190\nThe window height is 400 points, and its center is 200. Placing both icons at the same 190 puts them side by side at the same height, sitting slightly above the dead center (200). Since the icon label is attached below the icon, nudging it slightly up makes it visually centered once the label is included.\nThere\u0026rsquo;s no single correct answer for the coordinates. The values above are just one balanced example of placing \u0026ldquo;two 100-point icons side by side and symmetrically in a 600×400 window.\u0026rdquo; Feel free to adjust them to fit your own background design.\nAligning the Background Image with the Coordinates Here, the @2x convention from Part 1 reappears.\nThe icon coordinates (175,190, 425,190) are the window\u0026rsquo;s point coordinates. But the background image is twice that size — 1200×800 pixels. When you draw the arrow on the background image, you have to think in terms of pixel coordinates that are the window coordinates multiplied by 2.\nBackground image pixel coordinates = window point coordinates × 2 Converting the two icons from the example into background image pixel coordinates gives this:\nElement Window coordinates (points) Background image coordinates (pixels) App icon center (175, 190) (350, 380) Applications center (425, 190) (850, 380) So when you draw the guiding arrow on the background image (1200×800), it must be drawn between pixels (350, 380) and (850, 380) — the spots of the two icons. Since the icon size is 100 points = 200 pixels, leave the 200-pixel area each icon occupies empty and place the arrow in the space between them.\nAligning the background image\u0026rsquo;s artwork with create-dmg\u0026rsquo;s icon coordinates against the same reference is the heart of DMG design. If the two are off, the arrow misses the icons or gets drawn overlapping them.\nVerification and Fine-Tuning When you run the command, build/FocusTimer-1.0.dmg is created. Double-click the resulting DMG and open it yourself.\nDoes the background fill the window completely? (If it\u0026rsquo;s shrunk into a corner → that\u0026rsquo;s the DPI pitfall from Part 1. Normalize it to 144 with sips.) Are the app icon and Applications placed nicely at the two ends of the background arrow? Are the icon labels not cut off or overlapping? The coordinates usually don\u0026rsquo;t line up perfectly on the first try. Just repeat the process a few times: tweak the numbers in --icon and --app-drop-link a little, run the command again, and check with your eyes. Once you\u0026rsquo;ve finalized the coordinates, you\u0026rsquo;ll rarely need to change them again.\nReusing for Every Release — Automation DMG design is effectively a one-time task. Once you\u0026rsquo;ve finalized the background image and the coordinates, you reuse the exact same settings for every release that follows.\nSo it\u0026rsquo;s a good idea to put this create-dmg call in as one step of your release build script. In a script, the flow usually looks like this:\nCopy the signed, notarized, and stapled .app into the staging folder (Part 1) Copy the background image and then normalize its DPI to 144 with sips (preventing the Part 1 pitfall) Run create-dmg with the coordinates designed above Clean up temporary files such as the staging folder Only the version number changes from release to release (the 1.0 in --volname, and FocusTimer-1.0.dmg in the output filename), so if you pull just that part out into a script variable, you won\u0026rsquo;t need to touch the command each time. Keep the background image path and all the coordinates as fixed values.\nWe recommend having the script automatically perform the background image\u0026rsquo;s DPI normalization (sips) every time. That way, even if you forget to normalize the DPI when you re-export the design original and swap it in, the script always corrects it for you.\nSeries Wrap-Up The two-part DMG design is complete. You now have:\n✅ (Part 1) create-dmg + a .app staging folder + a background image with @2x and DPI normalized ✅ (Part 2) Window and icon coordinate design + aligning the background pixel coordinates with the window point coordinates + automation To recap the key points:\nThe DMG window is 600×400 points, and the background image is twice that — 1200×800 pixels (@2x). The background PNG\u0026rsquo;s DPI must be normalized to 144 — only then does the background fill the window exactly. The icon coordinates are point coordinates within the window, and the background artwork coordinates are pixel coordinates twice that — align the two against the same reference. Put the design you\u0026rsquo;ve finalized into your build script and reuse it as-is for every release. A single small arrow, or an icon position off by a few points, can sway a user\u0026rsquo;s first impression. Since the .dmg window is the very place a user first meets your app, it\u0026rsquo;s well worth designing carefully, once.\nReferences create-dmg — GitHub sips — macOS command manual ","permalink":"https://hobbyworker.me/en/dev/2026-05-21-design-macos-dmg-2-layout-coordinates/","summary":"\u003ch1 id=\"completing-the-dmg-layout-with-coordinates\"\u003eCompleting the DMG Layout with Coordinates\u003c/h1\u003e\n\u003cp\u003eIn \u003ca href=\"../2026-05-20-design-macos-dmg-1-create-dmg-and-background/\"\u003ePart 1\u003c/a\u003e, we introduced the \u003ccode\u003ecreate-dmg\u003c/code\u003e tool and prepared a staging folder holding only the \u003ccode\u003e.app\u003c/code\u003e along with a background image.\u003c/p\u003e\n\u003cp\u003eNow for the final piece, the \u003cstrong\u003elayout\u003c/strong\u003e. How large to open the DMG window, and where to place the app icon and the \u003ccode\u003eApplications\u003c/code\u003e shortcut — all of this is decided by the \u003cstrong\u003ecoordinates\u003c/strong\u003e you pass to \u003ccode\u003ecreate-dmg\u003c/code\u003e. In this article, we\u0026rsquo;ll design those coordinates one by one.\u003c/p\u003e","title":"Designing a Distribution DMG for Your macOS App (2): Window and Icon Layout Coordinates, and Automation"},{"content":"The First Screen a User Sees When you distribute a macOS app yourself, the user downloads a .dmg file and double-clicks it. At that moment, a single Finder window opens. This window is the first screen where the user meets your app.\nA well-made .dmg window usually looks like this — a guide image fills the background, the app icon sits on the left, and a shortcut to the Applications folder sits on the right. The user finishes the install by dragging the app icon over to Applications. This \u0026ldquo;drag to install\u0026rdquo; flow is effectively the standard installation experience for indie macOS apps.\nThis window doesn\u0026rsquo;t just appear on its own. You have to define the background image, the window size, and the position and size of every icon yourself. This series covers how to design that .dmg window.\nThis series starts from the assumption that you already have a .app that has been signed, notarized, and stapled — in other words, an app that is one step away from distribution. That process was covered in the \u0026ldquo;Distributing a macOS App Yourself\u0026rdquo; series. A .dmg is an artifact of the direct distribution (Developer ID) channel and has nothing to do with the Mac App Store channel.\nWhat This Series Builds Over two parts, you\u0026rsquo;ll end up with the following:\n(Part 1, this article) The create-dmg tool + a staging folder that holds only the .app + a prepared background image (Part 2) Designing the coordinates of the window, icons, and drop link + aligning the background image with those coordinates + automation By the end of the series, you should have:\nA staging folder that isolates and holds only the .app A background image that follows the @2x Retina convention and has a normalized DPI A create-dmg command in which the window size and icon positions are all set by coordinates A form you can reuse as-is for every release The Example App — FocusTimer As in the previous series, we\u0026rsquo;ll use a fictional macOS app, FocusTimer (a simple timer app for managing focus time), as our example. FocusTimer, the paths, the coordinate values, and so on are all examples. In practice, adapt them to your own app.\nWhat Is a DMG? A DMG (.dmg) is a macOS disk image file. Think of it as a \u0026ldquo;virtual disk\u0026rdquo; that packs an entire folder-and-file structure inside a single file. When the user double-clicks a .dmg, macOS mounts it like a disk, and its contents open in a Finder window.\nHere\u0026rsquo;s why DMGs are used for app distribution:\nAn app (.app) is actually a folder. It\u0026rsquo;s hard to put a folder directly on the internet, but wrapping it in a DMG turns it into a single file. You can include a shortcut to the Applications folder inside the DMG window, guiding the user to install with a single drag. You can design the look of the window — its background, icon positions, and so on — making the installation experience clean. The Tool — create-dmg Building a DMG by hand means going through several steps: creating an image with hdiutil, mounting it, manipulating the Finder window with AppleScript to place the icons, then unmounting it again. It\u0026rsquo;s tedious and error-prone.\ncreate-dmg is an open-source tool that bundles this whole process into a single command. Pass it the background image, window size, and icon coordinates as options, and it produces a finished .dmg for you.\nYou already installed it back when setting up the prerequisite tools in Part 1 of the \u0026ldquo;Distributing a macOS App Yourself\u0026rdquo; series. If you haven\u0026rsquo;t yet, install it with Homebrew:\nbrew install create-dmg There are two projects named create-dmg. The one used in this article is the command-line tool installed by Homebrew (create-dmg/create-dmg). Don\u0026rsquo;t confuse it with the separate project, which has different option names.\nStep 1 — A Staging Folder That Holds Only the .app The DMG should contain just one app icon, cleanly. But when you build and export your app, the resulting folder contains not only the .app but also byproduct files such as DistributionSummary.plist and packaging logs. If you turn that folder directly into a DMG, those byproducts end up exposed in the window too.\nSo you copy only the .app into an empty folder to isolate it, and then use that folder as the raw material for the DMG. This isolation folder is commonly called a staging folder.\n# Copy only the .app into an empty folder so it isn\u0026#39;t mixed with byproducts 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; Now build/dmg-stage/ contains only FocusTimer.app. When you build the DMG, point create-dmg at this folder, and the window will show only the app icon, cleanly. (The Applications folder shortcut is generated automatically via a create-dmg option in Part 2.)\nStep 2 — Preparing the Background Image The heart of DMG window design is the background image. This is where people get stuck most often, so let\u0026rsquo;s go through it step by step.\nCanvas Size — Why 1200×800? In Part 2, you\u0026rsquo;ll set the DMG window size by passing --window-size 600 400 to create-dmg. Here, the unit of 600 and 400 is not pixels but points. Points are the logical coordinate unit that macOS uses.\nOn a Retina display, 1 point is drawn as 2 pixels. So to fill a 600×400-point window completely and sharply, the background image must be twice that size — 1200×800 pixels. This is the @2x (double-resolution) convention.\nTo summarize — the window is 600×400 points, and the background image is 1200×800 pixels. This 2× relationship comes back as an important factor when you set up coordinates in Part 2.\nWhat to Draw on the Background Here\u0026rsquo;s a common misconception worth addressing. Do not draw the app icon or the Applications folder icon into the background image. Those icons are placed onto the window as actual files by create-dmg (Part 2).\nWhat goes into the background image is decoration and guidance elements only:\nAn arrow pointing from the app icon toward the Applications folder — the most common design for guiding the user\u0026rsquo;s eye and hand to \u0026ldquo;drag this way.\u0026rdquo; If needed, a short instructional phrase such as \u0026ldquo;Drag here\u0026rdquo; A brand color or a subtle background pattern In other words, the background image is \u0026ldquo;a picture that leaves the spots where the icons will sit empty, and draws only the guide line connecting them.\u0026rdquo; You leave the two icon spots — left and right, each 100 points = 200 pixels in size (in Part 2) — empty, and place the arrow in the middle between them.\nA Key Pitfall — DPI The pitfall people fall into most with the background image is DPI (pixels per inch).\nWhen you export a PNG from an image editor, the file stores not only the pixel count (1200×800) but also a DPI value. When Finder draws the DMG background, it looks at this DPI to back-calculate \u0026ldquo;how many points this image is.\u0026rdquo; The calculation goes roughly like this:\nPoint size = pixel count ÷ (DPI ÷ 72) If the DPI is 144: 1200 ÷ (144 ÷ 72) = 1200 ÷ 2 = 600 points. It fills the window (600pt) exactly. ✅ But some image editors (e.g., Pixelmator Pro) stamp in an arbitrary DPI value (e.g., 338) on export. If the DPI is 338: 1200 ÷ (338 ÷ 72) ≈ 256 points. That\u0026rsquo;s far smaller than the window (600pt), so the background is drawn shrunk into the top-left corner of the window. ❌ The pixel count is the same 1200×800 in both cases, yet the background breaks because of one DPI value. The first time you run into this, the cause is genuinely hard to track down.\nThe solution is to force the exported PNG\u0026rsquo;s DPI to a uniform 144. One line with the sips command, built into macOS by default, does it:\nsips -s dpiWidth 144 -s dpiHeight 144 dmg-background.png Once you run this one line, no matter what DPI the editor stamped in, it\u0026rsquo;s set to 144, and 1200×800 pixels is interpreted as exactly 600×400 points.\nThe takeaway — ① make the background image 1200×800 pixels, and ② after exporting, normalize the DPI to 144 with sips. Follow these two rules and you\u0026rsquo;ll avoid the DPI pitfall.\nPart 1 Wrap-Up If you\u0026rsquo;ve followed along this far, you now have:\n✅ The create-dmg tool installed + an understanding of its role ✅ A staging folder that isolates and holds only the .app (build/dmg-stage/) ✅ A background image made at 1200×800 pixels with the DPI normalized to 144 The materials are ready. But you still don\u0026rsquo;t have a window to display the background image and place the icons in. How large to open the window, and where to place the app icon and the Applications shortcut — all of this is decided by the coordinates you pass to create-dmg.\nIn the next part, we\u0026rsquo;ll design those coordinates one by one, cover how to align the background\u0026rsquo;s pixel coordinates with the window\u0026rsquo;s point coordinates so that the arrow in the background image lands exactly between the two icons, and show how to automate this process so it can be reused for every release.\n","permalink":"https://hobbyworker.me/en/dev/2026-05-20-design-macos-dmg-1-create-dmg-and-background/","summary":"\u003ch1 id=\"the-first-screen-a-user-sees\"\u003eThe First Screen a User Sees\u003c/h1\u003e\n\u003cp\u003eWhen you distribute a macOS app yourself, the user downloads a \u003ccode\u003e.dmg\u003c/code\u003e file and double-clicks it. At that moment, \u003cstrong\u003ea single Finder window opens.\u003c/strong\u003e This window is the first screen where the user meets your app.\u003c/p\u003e\n\u003cp\u003eA well-made \u003ccode\u003e.dmg\u003c/code\u003e window usually looks like this — a guide image fills the background, the app icon sits on the left, and a shortcut to the \u003ccode\u003eApplications\u003c/code\u003e folder sits on the right. The user finishes the install by \u003cstrong\u003edragging\u003c/strong\u003e the app icon over to \u003ccode\u003eApplications\u003c/code\u003e. This \u0026ldquo;drag to install\u0026rdquo; flow is effectively the standard installation experience for indie macOS apps.\u003c/p\u003e","title":"Designing a Distribution DMG for Your macOS App (1): create-dmg and Preparing the Background Image"},{"content":"The Path That Sends a Build to the App Store In Part 1 we created the MAS build target, and in Part 2 we created the configuration files and code branching that separate the two channels. The FocusTimer MAS target is now in a shape that can be put on the App Store.\nIn this final part, we\u0026rsquo;ll set up the path for uploading that build to App Store Connect, and cover how to verify the two channels so they stay unbroken going forward, wrapping up the series.\nAs in Parts 1 and 2, FocusTimer, com.example.FocusTimer.mas, the Team ID ABCDE12345, and so on are all example values.\nStep 1 — The ExportOptions-MAS.plist for Upload In the direct distribution series, we noted that when exporting an archive for distribution, the xcodebuild -exportArchive command reads an ExportOptions.plist. The MAS channel needs a separate export configuration file.\nCreate an ExportOptions-MAS.plist in the project\u0026rsquo;s release directory.\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; Here\u0026rsquo;s what each key means.\nmethod = app-store — Means this build is exported for the Mac App Store channel. This contrasts with the direct distribution channel\u0026rsquo;s developer-id. destination = upload — Has xcodebuild -exportArchive upload the exported artifact straight to App Store Connect. There\u0026rsquo;s no need to go through Transporter or a separate upload tool as in the past. signingStyle = automatic — Xcode automatically matches the Mac App Store provisioning profile and the Apple Distribution certificate. teamID — Your Apple Developer Team ID. Once you create this file, you reuse it for every MAS release. Leave the direct distribution ExportOptions.plist as is, and keep this file alongside it.\nStep 2 — Register the App Record in App Store Connect To actually submit the app for review, there must be an app record in App Store Connect. A record is the container that holds all the information shown in the store — the app\u0026rsquo;s name, description, screenshots, price, and so on.\nCreating the record can wait until just before your actual first submission. At the one-time setup stage, it\u0026rsquo;s enough to know in advance \u0026ldquo;which items to decide and how.\u0026rdquo; In particular, the Primary Language below is hard to undo once set, so decide it carefully ahead of time.\nIn App Store Connect → My Apps → + → New App, enter the following.\nPlatform — Select macOS Primary Language — ⚠️ The item to decide most carefully. Once set, it\u0026rsquo;s very hard to change through self-service. If you plan to release the app in multiple countries, it\u0026rsquo;s common to set English (U.S.) as the primary language, because the primary language is \u0026ldquo;the baseline language shown when there\u0026rsquo;s no translation for a particular country.\u0026rdquo; App Name — The name in the primary language (e.g., FocusTimer). Names for other languages are added later separately as per-language localizations. For example, you can make Korean users see a Korean name and Japanese users see a Japanese name. Bundle ID — Select com.example.FocusTimer.mas from the dropdown. That\u0026rsquo;s the ID you registered in Part 1. Don\u0026rsquo;t confuse it with the direct distribution Bundle ID (com.example.FocusTimer) — it must be the one with .mas appended. SKU — An internal identifier string used only by you. It isn\u0026rsquo;t exposed in the store, so you can choose it freely (e.g., focustimer-mas-001). The app category must match the value you put in LSApplicationCategoryType in the Info.plist in Part 2. If you put public.app-category.productivity (Productivity) in the plist, you must also choose the \u0026ldquo;Productivity\u0026rdquo; category in App Store Connect so a mismatch warning doesn\u0026rsquo;t appear during review.\nStep 3 — Verifying Both Channel Builds You now have a single codebase with two build targets. This comes with a maintenance cost. If you normally only build the direct distribution channel, you won\u0026rsquo;t notice if the MAS target has quietly broken. For instance, you add new code but forget the #if canImport(Sparkle) branch, and only the MAS build fails to compile.\nThe most reliable way to prevent this is to archive the MAS target as well every time you make a direct distribution release. If the build succeeds, it means the branching of the two channels is intact.\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 If you see the following at the end of the output, the MAS branching is fine.\n** ARCHIVE SUCCEEDED ** I recommend slotting this command into the last step of your release automation script so it runs automatically on every release. Even when you aren\u0026rsquo;t actually uploading the MAS build to the App Store, just confirming that the archive succeeds is enough to guarantee that the two channels\u0026rsquo; branching hasn\u0026rsquo;t broken.\nSeries Recap — MAS Release One-Time Setup Complete The three-part one-time setup for releasing to the Mac App Store is now fully done. You now have:\n✅ (Part 1) A MAS-only Bundle ID + the duplicated FocusTimer MAS build target ✅ (Part 2) MAS-only entitlements and Info.plist + build settings + #if canImport(Sparkle) code branching ✅ (Part 3) An ExportOptions-MAS.plist for upload + how to register an App Store Connect record + verification of both channel builds With this, a single macOS app now has both the direct distribution (Developer ID) and Mac App Store channels. To restate the core structure:\nShare the same codebase, but split it into two build targets. The direct distribution target includes Sparkle, and the MAS target excludes it. The difference between the two is expressed through separate entitlements, Info.plist, and ExportOptions files and #if canImport(Sparkle) code branching. The initial setup takes a fair amount of effort, but this too is a structure that keeps getting reused once it\u0026rsquo;s in place. From then on, when you make a direct distribution release, you just archive the MAS build alongside it and confirm the branching is still alive. The actual App Store submission (screenshots, descriptions, handling review) is separate work done on top of this one-time setup, and it\u0026rsquo;s a topic for another post.\nReferences App Store Connect Help LSApplicationCategoryType — Apple Developer Documentation Distributing your app for beta testing and releases — Apple Developer ","permalink":"https://hobbyworker.me/en/dev/2026-05-19-distribute-macos-app-mas-3-export-and-app-store-connect/","summary":"\u003ch1 id=\"the-path-that-sends-a-build-to-the-app-store\"\u003eThe Path That Sends a Build to the App Store\u003c/h1\u003e\n\u003cp\u003eIn \u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003ePart 1\u003c/a\u003e we created the MAS build target, and in \u003ca href=\"../2026-05-18-distribute-macos-app-mas-2-build-config-and-code/\"\u003ePart 2\u003c/a\u003e we created the configuration files and code branching that separate the two channels. The \u003ccode\u003eFocusTimer MAS\u003c/code\u003e target is now in a shape that can be put on the App Store.\u003c/p\u003e\n\u003cp\u003eIn this final part, we\u0026rsquo;ll set up the \u003cstrong\u003epath for uploading that build to App Store Connect\u003c/strong\u003e, and cover how to verify the two channels so they stay unbroken going forward, wrapping up the series.\u003c/p\u003e","title":"Shipping a macOS App to the Mac App Store (3): Upload Settings and App Store Connect Registration"},{"content":"Making the Target Truly \u0026ldquo;MAS-Specific\u0026rdquo; In Part 1, we registered a MAS-only Bundle ID and duplicated the FocusTimer MAS build target. But that target is still just a copy of the direct distribution target.\nA MAS build has to differ from the direct distribution build in three ways.\nEntitlements — only the minimum set appropriate for the App Store Info.plist — drop the Sparkle keys, add App Store metadata Code — branch so it compiles even without Sparkle In this post, we\u0026rsquo;ll split all three.\nAs in Part 1, FocusTimer, com.example.FocusTimer.mas, and so on are all example values.\nStep 1 — The MAS-only Entitlements File Entitlements is a file that lists which system permissions an app requests. In the direct distribution build, Sparkle required extra permissions to install updates, but the MAS build has no Sparkle, so those permissions are unnecessary.\nCreate a new FocusTimer-MAS.entitlements file in the project root.\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; Compared with the direct distribution FocusTimer.entitlements, here\u0026rsquo;s how it differs.\nNo Sparkle-related permissions — The temporary-exception.mach-lookup.* entries from the direct distribution build (the exception that lets Sparkle communicate with the installer helper) have no reason to be in the MAS build. No network.client — With Sparkle removed, the FocusTimer app itself makes no network calls. It\u0026rsquo;s honest to drop permissions you don\u0026rsquo;t use, and unnecessary permissions are also flagged during review. The smaller the permission surface an app requests, the better. The example above is the simplest possible form, with only enough permission to \u0026ldquo;read and write files the user explicitly picks.\u0026rdquo; If your app actually uses the network or other resources, add the corresponding permissions, but never include permissions you don\u0026rsquo;t use.\nStep 2 — The MAS-only Info.plist File Likewise, create a new FocusTimer-MAS-Info.plist in the project root.\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; There are two differences from the direct distribution FocusTimer-Info.plist.\n① All the Sparkle SU* keys are dropped\nThe direct distribution Info.plist contained Sparkle configuration keys like SUFeedURL and SUPublicEDKey. The MAS Info.plist must contain none of these keys. App Store review forbids self-contained automatic updates, so even a key that merely hints at such a feature remaining in the plist can be a problem. It\u0026rsquo;s safest not to include the key at all.\n② App Store metadata keys are added\nLSApplicationCategoryType — the category the app belongs to on the App Store. The public.app-category.productivity in the example above means the \u0026ldquo;Productivity\u0026rdquo; category. This value must match the category you set in App Store Connect in Part 3, so a mismatch warning doesn\u0026rsquo;t appear during review. ITSAppUsesNonExemptEncryption — whether the app uses encryption technology subject to export regulations. If you use only standard encryption (e.g., HTTPS, standard system APIs) or no encryption at all, you can set this to false. Hardcoding this key in advance automatically clears the encryption questionnaire that appears every time you upload a build. Before setting ITSAppUsesNonExemptEncryption to false, verify that your app really doesn\u0026rsquo;t use non-standard encryption. If you\u0026rsquo;re unsure, it\u0026rsquo;s safer to consult Apple\u0026rsquo;s documentation on encryption.\nStep 3 — MAS Target Build Settings Now for the build settings update we deferred in Part 1, Section 2-3. In TARGETS → FocusTimer MAS → Build Settings, align the following.\nBuild setting key Value 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 Same as the direct distribution target The key points are these.\nINFOPLIST_FILE and CODE_SIGN_ENTITLEMENTS must point to the MAS-only files you just created. A common mistake is leaving these two lines pointing at the direct distribution files. Setting CODE_SIGN_STYLE to Automatic lets Xcode automatically issue and match the Mac App Store provisioning profile and the Apple Distribution certificate. You don\u0026rsquo;t need to manage signing yourself. If your app doesn\u0026rsquo;t use the network, don\u0026rsquo;t set ENABLE_OUTGOING_NETWORK_CONNECTIONS. This is the same reasoning as dropping network.client from the entitlements. Keep the version numbers (MARKETING_VERSION, etc.) at the same values as the direct distribution target, so the two channels\u0026rsquo; versions don\u0026rsquo;t drift apart. Step 4 — Branching the Sparkle Code (#if canImport(Sparkle)) Even after splitting the configuration files, one problem remains. Somewhere in the source code there\u0026rsquo;s import Sparkle and code that uses Sparkle, but Sparkle isn\u0026rsquo;t linked into the MAS target, so the import statement itself produces a compile error.\nThe solution is to wrap the Sparkle-related code with Swift\u0026rsquo;s conditional compilation directive #if canImport(Sparkle).\nWhy canImport — Why Not Use a Separate Flag You could also branch with a custom compilation flag like #if MAS_BUILD. But then you\u0026rsquo;d have to manually keep in sync the setting \u0026ldquo;turn the flag on for the MAS target, off for the direct distribution target.\u0026rdquo; That\u0026rsquo;s easy to get out of sync as targets multiply or settings change.\ncanImport(Sparkle) is different. It has the compiler directly check whether \u0026ldquo;Sparkle.framework is linked into this target.\u0026rdquo; Since we removed Sparkle from the MAS target\u0026rsquo;s package dependencies in Part 1, canImport(Sparkle) is automatically false in the MAS target. There\u0026rsquo;s no separate flag to keep in sync. That\u0026rsquo;s why macOS apps use this pattern as a de facto standard when separating out a MAS channel.\nBranch ① — Wrap Sparkle-Only Files Entirely A file that contains only the automatic update logic (e.g., UpdaterCoordinator.swift) gets the entire file wrapped in #if.\n#if canImport(Sparkle) import Foundation import Sparkle import Combine @Observable @MainActor final class UpdaterCoordinator { // Code wrapping the Sparkle updater … } #endif In the MAS build, this file compiles as if it were empty and has no effect at all.\nBranch ② — Wrap the Sparkle Users Too The code that creates UpdaterCoordinator and passes it to the UI also needs to be branched. Otherwise the MAS build would reference a \u0026ldquo;type that doesn\u0026rsquo;t exist.\u0026rdquo;\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: { // Menu bar icon … } } } private struct MenuBarContent: View { #if canImport(Sparkle) @Bindable var updater: UpdaterCoordinator #endif var body: some View { Button(\u0026#34;Show FocusTimer\u0026#34;) { /* … */ } #if canImport(Sparkle) Button(\u0026#34;Check for Updates…\u0026#34;) { updater.checkForUpdates() } .disabled(!updater.canCheckForUpdates) #endif // Other shared menu items … } } There are three key points.\nThe updater field declaration itself is wrapped in #if. Any view that receives updater (PreferenceView, MenuBarContent) is branched into two forms — one for when Sparkle is present and one for when it isn\u0026rsquo;t. Sparkle-only UI elements like \u0026ldquo;Check for Updates…\u0026rdquo; are also wrapped in #if. In the MAS build, this menu item doesn\u0026rsquo;t appear at all — and rightly so, the App Store edition shouldn\u0026rsquo;t have a self-update menu. Elements such as the \u0026ldquo;Enable automatic updates\u0026rdquo; toggle in the settings screen (PreferenceView) should all be wrapped with the same pattern.\nPart 2 Recap If you\u0026rsquo;ve followed along this far, you now have:\n✅ A MAS-only entitlements file FocusTimer-MAS.entitlements (minimum permissions) ✅ A MAS-only FocusTimer-MAS-Info.plist (Sparkle keys removed, App Store metadata added) ✅ The MAS target\u0026rsquo;s build settings squared away ✅ Automatic update code branched with #if canImport(Sparkle) Now the FocusTimer MAS target is truly different from the direct distribution target — it\u0026rsquo;s in a shape that can be put on the App Store. What\u0026rsquo;s left is to set up the path for actually uploading this build to App Store Connect.\nIn the next part, we\u0026rsquo;ll wrap up the series by creating an ExportOptions-MAS.plist for upload, registering an app record in App Store Connect, and covering how to verify that the two channels stay unbroken going forward.\n","permalink":"https://hobbyworker.me/en/dev/2026-05-18-distribute-macos-app-mas-2-build-config-and-code/","summary":"\u003ch1 id=\"making-the-target-truly-mas-specific\"\u003eMaking the Target Truly \u0026ldquo;MAS-Specific\u0026rdquo;\u003c/h1\u003e\n\u003cp\u003eIn \u003ca href=\"../2026-05-17-distribute-macos-app-mas-1-target-setup/\"\u003ePart 1\u003c/a\u003e, we registered a MAS-only Bundle ID and duplicated the \u003ccode\u003eFocusTimer MAS\u003c/code\u003e build target. But that target is still just a copy of the direct distribution target.\u003c/p\u003e\n\u003cp\u003eA MAS build has to differ from the direct distribution build in three ways.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eEntitlements\u003c/strong\u003e — only the minimum set appropriate for the App Store\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInfo.plist\u003c/strong\u003e — drop the Sparkle keys, add App Store metadata\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCode\u003c/strong\u003e — branch so it compiles even without Sparkle\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eIn this post, we\u0026rsquo;ll split all three.\u003c/p\u003e","title":"Shipping a macOS App to the Mac App Store (2): Splitting Configuration and Code Between Channels"},{"content":"Another Distribution Channel — the Mac App Store The previous series covered the one-time setup for distributing a macOS app directly with a Developer ID. Once you have certificates, notarization, Sparkle automatic updates, and a hosted update feed in place, you can let users download a .dmg file directly without going through the App Store.\nThis series covers the one-time setup for putting that same app on the Mac App Store (MAS) as well. The two distribution methods are not an either/or choice. You can run a single app through both the direct distribution channel and the App Store channel at the same time. The App Store has Apple handle payments, refunds, and search visibility on your behalf, and it carries higher user trust, so running it alongside direct distribution is a common choice.\nThis series assumes your app is already being distributed directly with a Developer ID and already has Sparkle automatic updates built in. That setup was covered starting from Part 1 of the \u0026ldquo;Distributing a macOS App Yourself\u0026rdquo; series. If you haven\u0026rsquo;t read it yet, I recommend going through it first.\nWhy MAS Needs \u0026ldquo;Another Target\u0026rdquo; A directly distributed app includes Sparkle automatic updates — the feature that lets the app check an update feed on its own, download a new version, and replace itself.\nBut the Mac App Store review rules forbid this behavior. An app on the App Store must not download code from outside to update itself; updates must happen only through the App Store. In other words, a build destined for MAS must not contain Sparkle.\nThe direct distribution build, however, must have Sparkle. A single build artifact cannot satisfy both requirements at once. So the solution is this:\nOne codebase, two build targets.\nChannel Build target Sparkle Distributed via Developer ID FocusTimer Included Direct distribution (.dmg) Mac App Store FocusTimer MAS Excluded App Store You share a single source codebase, but split it into two build targets, linking Sparkle in only one of them. This series covers the one-time setup for creating that second target (FocusTimer MAS).\nWhat This Series Builds Over three parts, you will put the following in place.\n(Part 1, this post) Registering a MAS-only Bundle ID + duplicating the Xcode build target (Part 2) The configuration files (entitlements and Info.plist) and code branching that separate the two channels (Part 3) An ExportOptions-MAS.plist for upload + App Store Connect registration + build verification By the end of the series, you should have:\nA MAS-only Bundle ID registered in the Apple Developer Portal A FocusTimer MAS build target with the Sparkle dependency removed A MAS-only entitlements file and Info.plist file Code branched with #if canImport(Sparkle) An ExportOptions-MAS.plist for App Store upload The Example App — FocusTimer As in the previous series, we\u0026rsquo;ll use a fictional macOS app, FocusTimer (a simple timer app for managing focus sessions), as the example. FocusTimer, the bundle identifier com.example.FocusTimer, the Team ID ABCDE12345, and so on are all example values. In practice, swap them for your own app and account information.\nThis post was written for Xcode 26.\nStep 1 — Register a MAS-only Bundle ID Why a Separate Bundle ID If the direct distribution channel\u0026rsquo;s Bundle ID is com.example.FocusTimer, the MAS channel uses a different Bundle ID.\nChannel Bundle ID Developer ID com.example.FocusTimer Mac App Store com.example.FocusTimer.mas You append .mas to the existing Bundle ID. Why bother splitting them?\nBoth channels can coexist on one Mac — If the Bundle IDs are the same, macOS treats the two apps as the same app and they conflict. If they differ, you can install the directly distributed build and the App Store build on the same machine at the same time (handy for development and testing). Separate UserDefaults domains — Settings storage is partitioned by Bundle ID, so the two channels\u0026rsquo; settings don\u0026rsquo;t get mixed up. Avoiding Launch Services conflicts — It prevents confusion when macOS decides which app to send a request like \u0026ldquo;open this app\u0026rdquo; to. Keep the direct distribution channel\u0026rsquo;s Bundle ID unchanged. It\u0026rsquo;s the identifier for your existing users, so it must not be altered. You\u0026rsquo;re registering one additional new ID for MAS.\nRegistration Procedure Sign in at developer.apple.com/account (with your Apple Developer Program account) Certificates, Identifiers \u0026amp; Profiles → Identifiers in the left menu → the + button Select App IDs → Continue → choose type App → Continue Description: Enter an easily recognizable name (e.g., FocusTimer MAS) Bundle ID: Select Explicit Enter: com.example.FocusTimer.mas Capabilities: Turn off every feature the app doesn\u0026rsquo;t use. If you don\u0026rsquo;t use iCloud, push notifications, in-app purchases, Sign in with Apple, and so on, you don\u0026rsquo;t need to enable anything. (App Sandbox is handled by entitlements on the Xcode side, so there\u0026rsquo;s no need to enable it here.) Continue → Register Verification If the Identifiers list shows an entry for FocusTimer MAS — com.example.FocusTimer.mas, registration is complete.\nStep 2 — Duplicate the Xcode Build Target Now you\u0026rsquo;ll create a FocusTimer MAS build target in the Xcode project. The fastest way is to duplicate the existing target.\n2-1. Duplicate the Target Open FocusTimer.xcodeproj in Xcode Click the project icon (blue) at the top of the Project navigator (left panel) In the TARGETS list in the center editing area, right-click FocusTimer → Duplicate When the dialog appears, select Duplicate Only The new target is created as FocusTimer copy. Double-click the name and change it to FocusTimer MAS If the prompt \u0026ldquo;Add a new Scheme?\u0026rdquo; appears, choose Activate (or add it later in Manage Schemes) 2-2. Change the Bundle ID Immediately The very first thing to do right after duplicating is to swap the Bundle ID. If you leave it as is, both targets will have the same ID.\nChange TARGETS → FocusTimer MAS → General → Identity → Bundle Identifier to the value you registered in Step 1.\ncom.example.FocusTimer.mas 2-3. A Key Pitfall — the \u0026ldquo;Cleanup Burden\u0026rdquo; Duplicate Leaves Behind This is the trickiest part of this post. Xcode\u0026rsquo;s Duplicate Target operates with \u0026ldquo;safe defaults,\u0026rdquo; but that very safety leaves behind cleanup work that requires manual attention. Right after duplicating, you need to check the following four things.\n① Make the new target include the source files\nFor safety, Duplicate automatically excludes all source files from the new target. Left as is, the FocusTimer MAS target is an empty shell — building it produces a build with no code in it. You need to fix up the target memberships so the new target includes the existing source files again.\n② Remove the Sparkle dependency from the MAS target\nDuplicate copies the original\u0026rsquo;s Swift Package dependencies verbatim. As a result, Sparkle tags along into the FocusTimer MAS target too. The whole starting point of this series was \u0026ldquo;the MAS build must not have Sparkle,\u0026rdquo; so remove Sparkle from the MAS target\u0026rsquo;s package dependency list and from the Frameworks build phase.\n③ Clean up the temporary Info.plist file\nDuplicate creates a temporary Info.plist file like FocusTimer copy-Info.plist. Since we\u0026rsquo;ll create a dedicated MAS Info.plist separately in Part 2, delete these temporary files — both the project reference and the actual file.\n④ Update the MAS target\u0026rsquo;s build settings\nYou need to align build settings such as the bundle identifier, the Info.plist path, and the entitlements path for MAS. We\u0026rsquo;ll handle this all at once in Part 2, after creating the dedicated configuration files.\nItems ① and ② can mostly be handled from the target membership and package dependency screens in the Xcode UI, but if duplicate references left behind by the duplication process don\u0026rsquo;t get cleaned up neatly, you may sometimes need to look directly at the project file (.pbxproj). Once you think the cleanup is done, the most reliable way to verify is to build both targets separately and check whether import Sparkle breaks in the MAS target build (Part 2 covers this code branching).\nPart 1 Recap If you\u0026rsquo;ve followed along this far, you now have:\n✅ A MAS-only Bundle ID (com.example.FocusTimer.mas) registered in the Apple Developer Portal ✅ A FocusTimer MAS build target created by duplication ✅ An understanding of the cleanup burden duplication leaves behind (source membership, Sparkle dependency, temporary files) The \u0026ldquo;container\u0026rdquo; — the target — has been created. But this target is still essentially just a copy of FocusTimer; it hasn\u0026rsquo;t truly become \u0026ldquo;MAS-specific\u0026rdquo; yet. A MAS build needs to use different entitlements and a different Info.plist than the direct distribution build, and the code also has to branch so it doesn\u0026rsquo;t break when Sparkle is absent.\nIn the next part, we\u0026rsquo;ll cover the configuration files and code branching that separate the two channels.\n","permalink":"https://hobbyworker.me/en/dev/2026-05-17-distribute-macos-app-mas-1-target-setup/","summary":"\u003ch1 id=\"another-distribution-channel--the-mac-app-store\"\u003eAnother Distribution Channel — the Mac App Store\u003c/h1\u003e\n\u003cp\u003eThe previous series covered the one-time setup for \u003cstrong\u003edistributing a macOS app directly with a Developer ID\u003c/strong\u003e. Once you have certificates, notarization, Sparkle automatic updates, and a hosted update feed in place, you can let users download a \u003ccode\u003e.dmg\u003c/code\u003e file directly without going through the App Store.\u003c/p\u003e\n\u003cp\u003eThis series covers the one-time setup for putting that same app on the \u003cstrong\u003eMac App Store (MAS)\u003c/strong\u003e as well. The two distribution methods are not an either/or choice. You can \u003cstrong\u003erun a single app through both the direct distribution channel and the App Store channel at the same time\u003c/strong\u003e. The App Store has Apple handle payments, refunds, and search visibility on your behalf, and it carries higher user trust, so running it alongside direct distribution is a common choice.\u003c/p\u003e","title":"Shipping a macOS App to the Mac App Store (1): Creating a Separate Build Target"},{"content":"The Last Piece — Where to Put the Updates In Part 1 we prepared the Developer ID certificate and notarization, and in Part 2 we prepared the Sparkle signing key. That means we now have a way to sign the app, notarize it, and verify the authenticity of updates.\nBut the location pointed to by SUFeedURL (https://updates.example.com/appcast.xml), which we wrote into the app\u0026rsquo;s Info.plist in Part 2, still has nothing in it. In this final part, we\u0026rsquo;ll host the update feed that goes in that spot and finish the build settings, completing the entire one-time setup.\nAs in Parts 1 and 2, all names and domains (FocusTimer, example.com, example-dev, etc.) are example values. In practice, replace them with your own information.\nWhy Keep a Separate Update Repository For automatic updates to work, two things must be available on the internet.\nappcast.xml — the update feed that tells the app which version is the latest and where to get it .dmg — the actual app installer file There\u0026rsquo;s an important constraint here. The Sparkle code inside the app fetches these files with a simple, unauthenticated HTTPS GET. This means the app on the user\u0026rsquo;s computer must be able to download them directly, without any procedure like a login.\nMany developers keep the app\u0026rsquo;s main source repository private. But release files in a private repository require authentication, so Sparkle can\u0026rsquo;t fetch them. That\u0026rsquo;s why a common structure is to split the repositories.\nMain repository (e.g., FocusTimer) — the source code. May be kept private. Update repository (e.g., FocusTimer-updates) — hosts only appcast.xml. Must be public. In this article, we\u0026rsquo;ll run the update repository on GitHub Pages, GitHub\u0026rsquo;s free static hosting.\nStep 1 — Create the Public Update Repository Create a new repository on GitHub.\nClick New repository Name: FocusTimer-updates — this name is used in the feed address shortly, so set it precisely, including capitalization. Owner: your account or an organization (example: example-dev) Visibility: Public — required, since Sparkle must fetch it without authentication. Check Add a README file (for a convenient first commit) Click Create repository Step 2 — Enable GitHub Pages Serve the repository you just created as a static site.\nGo to the repository\u0026rsquo;s Settings → Pages in the left menu Source: Deploy from a branch Branch: select main / (root) → Save After 1–2 minutes, if https://example-dev.github.io/FocusTimer-updates/ becomes reachable, it worked. At this point you already have a public address where you can put update files. But one more step remains.\nStep 3 — Connect a Custom Domain You can use the default GitHub Pages address (example-dev.github.io/...) as-is and it will work. But if you embed that address in the app\u0026rsquo;s SUFeedURL, you\u0026rsquo;ll run into trouble if you ever need to move hosting from GitHub Pages to somewhere else — because every already-distributed user\u0026rsquo;s app is still pointed at the old address.\nThe solution is to insert a layer of a domain you control. If you set SUFeedURL to your own domain, like https://updates.example.com/appcast.xml, then when you later move hosting, you only change one line of DNS configuration and existing users automatically follow along to the new location. You only have to do this setup once, and it stays valid forever.\n3-1. Add a DNS Record In the configuration screen of the DNS provider that manages your domain (example.com), add the following record.\nField Value Type CNAME Name updates Value example-dev.github.io TTL 3600 (default) This makes the subdomain updates.example.com point to GitHub Pages.\n3-2. Add a CNAME File to the Repository In the root of the update repository (FocusTimer-updates), create a file called CNAME whose content is just one line — the domain.\nupdates.example.com Commit and push this file.\n3-3. Register the Domain with GitHub Pages In the repository\u0026rsquo;s Settings → Pages → Custom domain field, enter updates.example.com and click Save. GitHub automatically issues an HTTPS certificate, and once it\u0026rsquo;s issued, check Enforce HTTPS.\n3-4. Verification DNS propagation and certificate issuance usually take about 10 minutes. After a short wait, verify with the following command.\ncurl -I https://updates.example.com/appcast.xml If the first line of the response shows HTTP/2 200, everything is fine. (If you haven\u0026rsquo;t uploaded appcast.xml yet, you may get a 404, but the domain and HTTPS connection itself can be confirmed through other paths. The key point is that https://updates.example.com returns a response.)\nFor reference, the .dmg installer file is generally uploaded to GitHub Releases rather than GitHub Pages. Putting large binaries directly in a repository bloats it. Releases are served through a different path than Pages, so leave them as-is, unrelated to the custom domain setup above.\nStep 4 — Keep the Update Repository Locally To edit and commit appcast.xml during release work, you need to have the update repository checked out locally too. If you clone it to a suitable location inside the main project folder (e.g., release/updates), the build and release scripts can handle both repositories from one place, which is convenient.\ngit clone git@github.com:example-dev/FocusTimer-updates.git release/updates When you keep one repository inside another like this, add release/updates/ to the main repository\u0026rsquo;s .gitignore so they don\u0026rsquo;t interfere with each other.\nStep 5 — ExportOptions.plist Now for the build-side settings. Xcode\u0026rsquo;s xcodebuild -exportArchive command reads a configuration file called ExportOptions.plist when exporting an archive into a .app for distribution. The contents for direct distribution (the Developer ID channel) are as follows.\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. It means \u0026ldquo;direct distribution, not the Mac App Store.\u0026rdquo; signingStyle — automatic. Lets Xcode automatically pick and use the certificate issued in Part 1. teamID — the Team ID you noted in Part 1. Create this file once inside the project (e.g., release/ExportOptions.plist) and reuse it for every release.\nStep 6 — Check the App-Side Settings Finally, let\u0026rsquo;s go over the settings that must be present in the app project itself. If you\u0026rsquo;re adding Sparkle to a new app for the first time, you can use this list as a checklist.\nInfo.plist These are the Sparkle keys added in Part 2. The Info.plist must include the following keys.\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; Double-check that SUFeedURL points to the custom domain created in Step 3, and that SUPublicEDKey matches the public key generated in Part 2.\nEntitlements If the app uses the App Sandbox, Sparkle needs exception entitlements so it can communicate with internal services to install updates. The following entries go into the FocusTimer.entitlements file.\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; At build time, Xcode automatically substitutes $(PRODUCT_BUNDLE_IDENTIFIER) with the actual bundle identifier (com.example.FocusTimer). For details on each entry, see the official Sparkle sandboxing guide.\nSandboxing is not mandatory for a directly distributed app (the sandbox is a Mac App Store requirement). If your app doesn\u0026rsquo;t use the sandbox, the mach-lookup exception above isn\u0026rsquo;t needed. However, since automatic updates use the network, the network.client entitlement and Hardened Runtime must be turned on for notarization.\nBuild Settings In Xcode\u0026rsquo;s build settings, check the following.\nCODE_SIGN_ENTITLEMENTS — the path to the entitlements file above ENABLE_HARDENED_RUNTIME = YES — a required condition for notarization ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES — allows network access for checking updates Series Wrap-Up — One-Time Setup Complete The three-part one-time setup for direct distribution is now complete. You now have the following in hand.\n✅ (Part 1) A Developer ID Application certificate + credentials for notarization ✅ (Part 2) A Sparkle EdDSA signing key pair + a backup of the private key ✅ (Part 3) A public update repository connected to a custom domain + ExportOptions.plist + app-side configuration This is everything that only needs to be done once. You won\u0026rsquo;t have to redo this work every time you ship a new version.\nFrom here on, the flow for distributing a new version repeats in much the same way each time — build the archive → export with ExportOptions.plist → sign with the Developer ID certificate → notarize with notarytool → create the .dmg → sign it with the Sparkle key → update appcast.xml → upload the .dmg to a GitHub Release. This repetitive process can mostly be automated with a single script, and that\u0026rsquo;s a topic for a separate article.\nAmong these, creating the .dmg involves design elements like background images and icon placement, so it\u0026rsquo;s covered in detail in the separate series Designing a Distribution DMG for Your macOS App.\nDirect distribution may look daunting at first because there\u0026rsquo;s a lot to set up, but the key point is that \u0026ldquo;once you set it up, it keeps getting reused.\u0026rdquo; In exchange for giving up some of the App Store\u0026rsquo;s convenience, you get to control every part of the distribution process yourself.\nReferences Sparkle official documentation Apple — Notarizing macOS software before distribution GitHub Pages documentation ","permalink":"https://hobbyworker.me/en/dev/2026-05-16-distribute-macos-app-3-update-hosting-and-build/","summary":"\u003ch1 id=\"the-last-piece--where-to-put-the-updates\"\u003eThe Last Piece — Where to Put the Updates\u003c/h1\u003e\n\u003cp\u003eIn \u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003ePart 1\u003c/a\u003e we prepared the Developer ID certificate and notarization, and in \u003ca href=\"../2026-05-15-distribute-macos-app-2-sparkle-signing-key/\"\u003ePart 2\u003c/a\u003e we prepared the Sparkle signing key. That means we now have a way to sign the app, notarize it, and verify the authenticity of updates.\u003c/p\u003e\n\u003cp\u003eBut the location pointed to by \u003ccode\u003eSUFeedURL\u003c/code\u003e (\u003ccode\u003ehttps://updates.example.com/appcast.xml\u003c/code\u003e), which we wrote into the app\u0026rsquo;s \u003ccode\u003eInfo.plist\u003c/code\u003e in Part 2, still has nothing in it. In this final part, we\u0026rsquo;ll \u003cstrong\u003ehost the update feed\u003c/strong\u003e that goes in that spot and finish the \u003cstrong\u003ebuild settings\u003c/strong\u003e, completing the entire one-time setup.\u003c/p\u003e","title":"Distributing a macOS App Yourself (3): Hosting the Update Feed and Build Settings"},{"content":"Automatic Updates, and Why You Need One More Layer of Signing In Part 1, we finished setting up the Developer ID certificate and notarization. With that, you\u0026rsquo;re ready to deliver the app to users for the first time. But an app isn\u0026rsquo;t done after a single release — you have to keep shipping new versions that fix bugs and add features.\nFor a Mac App Store app, the App Store handles updates for you. A directly distributed app doesn\u0026rsquo;t get that, so you have to build an automatic-update feature into the app yourself. On macOS, the de facto standard for this role is the open-source framework Sparkle. With Sparkle in place, the app periodically checks an \u0026ldquo;update feed (appcast),\u0026rdquo; and if a new version exists, it notifies the user, downloads it, and installs it.\nThis raises a question. You already sign the app with the Developer ID certificate created in Part 1, so why do you need yet another key?\nThe reason is that the two signatures verify different things.\nDeveloper ID certificate — used by macOS Gatekeeper to decide \u0026ldquo;is it OK to install this app?\u0026rdquo; Sparkle EdDSA key — used by Sparkle inside the app to decide \u0026ldquo;was the update file I just downloaded really made by this app\u0026rsquo;s developer?\u0026rdquo; Automatic updates are a security-sensitive operation: the app downloads a file from the internet and overwrites itself. If someone intercepts the update server or the communication path and slips in a fake file, it becomes a serious problem. To prevent this, Sparkle only accepts updates signed with a private key that only the developer holds, and refuses to install anything whose signature doesn\u0026rsquo;t match. It\u0026rsquo;s effectively a separate verification layer from the certificate.\nIn this article, we\u0026rsquo;ll create the EdDSA (Ed25519) key pair that will be used for that verification.\nAs in Part 1, all names and paths (FocusTimer, example.com, etc.) are example values. In practice, replace them with your own app\u0026rsquo;s information.\nPrerequisite — Sparkle Must Already Be Added to the App Before creating the key, the Sparkle framework must already be added as a dependency of your app project. If it isn\u0026rsquo;t yet, add it in Xcode via Swift Package Manager (SPM).\nOpen your project in Xcode → File → Add Package Dependencies… Enter the repository address in the search box: https://github.com/sparkle-project/Sparkle Set the version rule to 2.x (the latest major) and add it After doing this, build the project once so that SPM downloads the Sparkle package. The command-line tools that come bundled with it are the key to the next step.\nStep 1 — Locate the Sparkle Command-Line Tools The Sparkle package includes command-line tools used for key generation and signing. These tools live inside the folder where SPM downloaded the package, but that location varies depending on your Xcode version and DerivedData settings. So it\u0026rsquo;s safer to find it directly.\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; If a single path is printed, it worked. If nothing appears, you skipped the \u0026ldquo;build the project once\u0026rdquo; step above — run a build in Xcode and then try again.\nInside this folder are the following tools.\ngenerate_keys — generates, backs up, restores, and verifies the signing key (used in this article) sign_update — signs update files (used during actual releases) generate_appcast — generates the update feed (appcast.xml) (appears in Part 3) Step 2 — Generate the Signing Key Now create the key pair.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; Output similar to the following appears.\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; This single command creates two keys.\nPublic key — the SUPublicEDKey value shown in the output above. It is not a secret, and it is the key you will embed in the app. Private key — does not appear on screen. It is automatically stored in the macOS Keychain as an item called \u0026ldquo;Private key for signing Sparkle updates.\u0026rdquo; It is a true secret that must never be left on disk as a plaintext file. Embedding the Public Key in the App Put the public key string from the output into the app\u0026rsquo;s Info.plist. For the example app, add the following keys to 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 — the public key you just generated. The app uses this key to verify the signature of downloaded updates. SUFeedURL — the address of the update feed. This domain doesn\u0026rsquo;t exist yet; we create it in Part 3. For now, it\u0026rsquo;s just a placeholder. Because the public key is embedded in the app and only the developer holds the private key, the app will only accept updates signed with the private key. This is the core structure of Sparkle update verification.\nStep 3 — Back Up the Private Key (Absolutely!) If you skip this step, you may deeply regret it later.\nThe private key is stored in the Keychain, so it\u0026rsquo;s fine on the computer you\u0026rsquo;re using now. But if you lose the computer, the disk fails, or you reinstall macOS, this key disappears along with it.\nWhat happens if the private key is lost? Updates signed with a new key will be rejected by existing users\u0026rsquo; apps (apps with the old public key embedded). In other words, you can never send automatic updates again to users who are already running your app. Your only option is to tell each user individually, \u0026ldquo;please download the new version yourself and reinstall it.\u0026rdquo;\nSo back up the key right after you create it.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -x ~/focustimer-sparkle-private.key cat ~/focustimer-sparkle-private.key The single-line base64 string printed by cat is the private key. Example:\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0= Store this string as a secure note in a password manager such as 1Password. Give the note a name that\u0026rsquo;s easy to find later, such as FocusTimer Sparkle EdDSA Private Key.\nRight after confirming the save, delete the plaintext file left on disk.\nrm ~/focustimer-sparkle-private.key The rule is to never leave the private key sitting on disk as a plaintext file. Keep the backup only inside an encrypted password manager.\nA Key Pitfall — The % Symbol Is Not Part of the Key When you print the key with cat, the terminal (especially zsh) may append a % symbol at the end of the line.\nHn4Kp9Lr2Qs5Tv8Wx1Yz3Ab6Cd0Ef7Gh4Ij5Kl8MnQ0=% This % is just the shell\u0026rsquo;s indicator that \u0026ldquo;the output ended without a newline\u0026rdquo; — it is not part of the key. If you copy this % into your backup, the key will be broken when you restore it later. A base64 string usually ends with =, so exclude the % after the = when saving.\nStep 4 — Restoring on Another Computer When you need to build the app on a new computer, put the private key you backed up back into the Keychain.\necho \u0026#34;your_backed_up_base64_string\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 The -f option means \u0026ldquo;import the key in the file into the Keychain.\u0026rdquo; Once the restore is done, delete the plaintext file here too, right away.\nStep 5 — Verification Check that the key was installed correctly.\n\u0026#34;$SPARKLE_BIN/generate_keys\u0026#34; -p A single line containing the public key is printed. This value must exactly match the value you put into SUPublicEDKey in the Info.plist in Step 2. If they differ, the public key embedded in the app and the actual signing key are out of sync, and update verification will fail.\nPart 2 Wrap-Up If you\u0026rsquo;ve followed along this far, you now have the following.\n✅ A Sparkle EdDSA key pair generated (public key + private key) ✅ The public key embedded in the app\u0026rsquo;s Info.plist (SUPublicEDKey) ✅ The private key safely backed up in a password manager ✅ Familiarity with how to restore it on another computer The app now has a way to verify \u0026ldquo;whether a downloaded update is genuine.\u0026rdquo; But one thing is still missing. In Part 2 you wrote https://updates.example.com/appcast.xml for SUFeedURL, but there is still nothing at that address.\nIn the next part, we\u0026rsquo;ll create the public repository where the update feed (appcast.xml) and .dmg files will live, connect it to a domain we control, and finish the build settings — wrapping up the one-time setup.\n","permalink":"https://hobbyworker.me/en/dev/2026-05-15-distribute-macos-app-2-sparkle-signing-key/","summary":"\u003ch1 id=\"automatic-updates-and-why-you-need-one-more-layer-of-signing\"\u003eAutomatic Updates, and Why You Need One More Layer of Signing\u003c/h1\u003e\n\u003cp\u003eIn \u003ca href=\"../2026-05-14-distribute-macos-app-1-developer-id-certificate/\"\u003ePart 1\u003c/a\u003e, we finished setting up the Developer ID certificate and notarization. With that, you\u0026rsquo;re ready to deliver the app to users for the first time. But an app isn\u0026rsquo;t done after a single release — you have to keep shipping new versions that fix bugs and add features.\u003c/p\u003e\n\u003cp\u003eFor a Mac App Store app, the App Store handles updates for you. A directly distributed app doesn\u0026rsquo;t get that, so you have to build an \u003cstrong\u003eautomatic-update feature\u003c/strong\u003e into the app yourself. On macOS, the de facto standard for this role is the open-source framework \u003cstrong\u003e\u003ca href=\"https://sparkle-project.org/\"\u003eSparkle\u003c/a\u003e\u003c/strong\u003e. With Sparkle in place, the app periodically checks an \u0026ldquo;update feed (appcast),\u0026rdquo; and if a new version exists, it notifies the user, downloads it, and installs it.\u003c/p\u003e","title":"Distributing a macOS App Yourself (2): Creating the Sparkle Auto-Update Signing Key"},{"content":"What It Means to Distribute Directly with Developer ID There are broadly two ways to get a macOS app into users\u0026rsquo; hands. One is through the Mac App Store (MAS), and the other is direct distribution — letting users download a .dmg (or .app) file you build yourself from a website, GitHub, or similar.\nDirect distribution has clear advantages. You don\u0026rsquo;t have to wait for App Store review, there are no payment commissions, and you can ship updates whenever and however you like. In exchange, the things the App Store used to handle for you — code signing, notarization, and automatic updates — are now yours to set up.\nIf you don\u0026rsquo;t set these up, macOS\u0026rsquo;s security mechanism Gatekeeper will block the app from launching. When a user first opens the app, they see a warning like \u0026ldquo;this app cannot be opened because it is from an unidentified developer,\u0026rdquo; and most users give up on installing at that point. For the app to open with a simple double-click like any properly distributed app, you need to sign the app with a Developer ID certificate and have it notarized by Apple.\nThis series covers that one-time setup process. One bit of good news: most of the setup covered here is done only once and reused across every release.\nWhat You\u0026rsquo;ll Build in This Series Over three parts, you\u0026rsquo;ll put the following in place.\n(Part 1, this article) A Developer ID Application certificate + credentials for notarization (Part 2) An EdDSA signing key for Sparkle automatic updates (Part 3) A public repository to host the update feed + finishing the build settings By the end of the series, you should have the following in hand. For now, just skim the list; each part will fill in one item at a time.\nA Developer ID Application certificate in the macOS Keychain A notarization credentials profile stored in the Keychain An EdDSA key pair for signing Sparkle updates (public key + private key) A backup of the private key in a safe place A public GitHub repository + GitHub Pages to host the update feed (appcast) The build settings file (ExportOptions.plist) and app-side configuration The Example App — FocusTimer This series uses a fictional macOS app, FocusTimer (a simple timer app for managing focus time), as its running example. The FocusTimer name, the bundle identifier com.example.FocusTimer, the domain example.com, the certificate name, the Team ID, and so on that appear in commands and paths are all example values. When you actually follow along, swap them out for your own app name and account details.\nThis article is written for Xcode 26 and Sparkle 2.x (the automatic-update framework, which appears in Part 2). Apple often changes the layout of its screens and the placement of menus, so even if a button name is slightly different, assume the overall flow is the same.\nPrerequisites — Command-Line Tools First, install two command-line tools needed for build and release automation. This assumes you already have the macOS package manager Homebrew installed.\nbrew install gh create-dmg gh — GitHub\u0026rsquo;s official CLI. You\u0026rsquo;ll use it later to create GitHub Releases. create-dmg — A tool for building a .dmg disk image for distribution. It also generates the screen that guides users to drag the app into the Applications folder. You won\u0026rsquo;t use these right away, but release-automation scripts are often designed to check for the presence of these tools first and stop immediately if they\u0026rsquo;re missing, so it\u0026rsquo;s worth installing them in advance.\nStep 1 — Verify Your Apple Developer Program Membership To issue a Developer ID certificate and notarize an app, you need an Apple Developer Program membership. For an individual account, it\u0026rsquo;s a paid program costing $99 per year.\nIf you\u0026rsquo;re already enrolled, you just need to confirm it\u0026rsquo;s active.\nGo to developer.apple.com/account In the Membership details section, confirm the status is Active On the same screen, make a note of your Team ID (in the examples, ABCDE12345). It\u0026rsquo;s used throughout the later steps. If you haven\u0026rsquo;t enrolled yet, membership approval usually takes about a day. Certificates can\u0026rsquo;t be issued before approval, so it\u0026rsquo;s best to handle this first.\nStep 2 — Issue a Developer ID Application Certificate The Developer ID Application certificate is the certificate used to sign the .app and .dmg you distribute. macOS Gatekeeper recognizes an app signed with this certificate as \u0026ldquo;an app made by a known developer.\u0026rdquo;\nApple also has a separate Developer ID Installer certificate for signing .pkg installer packages. Since this series distributes via .dmg, we only deal with the Application certificate.\nIssuing Procedure The simplest way is through Xcode.\nLaunch Xcode → menu Xcode → Settings… (⌘,) Select the Accounts tab → click your Apple ID in the left-hand list Click the Manage Certificates… button at the bottom right In the new window that opens, click the + button at the bottom left Select Developer ID Application from the menu After a second or two, a new certificate appears in the list — click Done A certificate issued this way is automatically stored in the macOS Keychain. The certificate and its corresponding private key are stored together as a pair, and this private key is what makes signing possible.\nVerifying the Issuance In the terminal, run the following command to confirm the certificate was installed correctly.\nsecurity find-identity -v -p codesigning | grep \u0026#34;Developer ID Application\u0026#34; If a single line like the following appears, it worked.\n1) A1B2C3D4E5F6... \u0026#34;Developer ID Application: Gildong Hong (ABCDE12345)\u0026#34; The string inside the quotes is the certificate\u0026rsquo;s official name. Gildong Hong is the name registered on the Apple account, and ABCDE12345 in parentheses is the Team ID you noted in Step 1. This name is used as-is later when automating code signing, so double-check it now.\nRenewing the Certificate A Developer ID certificate is valid for 5 years. As expiration approaches, it will be marked Expired in Xcode\u0026rsquo;s Manage Certificates list. At that point, just issue a new certificate using the exact same procedure as above.\nThe good news is that apps already signed and distributed with the old certificate are not invalidated the moment it expires. Only newly created builds going forward need to be signed with the new certificate. So there\u0026rsquo;s no need to worry too much about expiration.\nStep 3 — Register an App-Specific Password for Notarization What Is Notarization? Notarization is the process of uploading your app to Apple\u0026rsquo;s servers before distribution to have it scanned for malware. If it passes the scan, Apple issues a \u0026ldquo;notarization ticket,\u0026rdquo; and that ticket must be attached to the app for Gatekeeper to open it without warnings. If signing proves \u0026ldquo;who made it,\u0026rdquo; notarization is a separate process that proves \u0026ldquo;Apple has scanned it once.\u0026rdquo;\nNotarization submission uses Apple\u0026rsquo;s notarytool command, which asks for your Apple ID password every time you submit. To avoid entering it each time, you create an app-specific password and store it in the Keychain ahead of time.\n3-1. Issue an App-Specific Password An app-specific password is a 16-character password used only for a specific purpose, in place of your main Apple ID password.\nGo to appleid.apple.com → sign in with the Apple ID enrolled in the Apple Developer Program (in the examples, developer@example.com) Go to Sign-In and Security → select App-Specific Passwords Click the + button → enter a name for the password (e.g., focustimer-notarize) Click Create → enter your Apple ID password once more A 16-character password in the format abcd-efgh-ijkl-mnop appears on screen. Once you close this screen, you cannot view the password again. Before moving to the next step, copy it briefly into a password manager or similar.\n3-2. Store It in a notarytool Profile Save the password you received as a Keychain profile. In the command below, replace the --password value with the actual password you just received before running it.\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; First argument \u0026quot;focustimer-notarize\u0026quot; — the name to give this profile. From now on, you\u0026rsquo;ll load the credentials by this name when notarizing. --apple-id — the email of the Apple Developer Program account --team-id — the Team ID from Step 1 --password — the app-specific password issued in 3-1 If the following output appears, it worked.\nCredentials saved to Keychain. Now that the credentials are stored in the Keychain, you no longer need the original app-specific password. (That said, if you plan to build on other computers too, it\u0026rsquo;s convenient to keep it in a password manager.)\n3-3. Verification Check that the saved profile actually works.\nxcrun notarytool history --keychain-profile \u0026#34;focustimer-notarize\u0026#34; If the message Successfully received submission history. appears, everything is fine. The submission history below it may be empty (which is normal, since you haven\u0026rsquo;t notarized anything yet) or may contain prior records.\nA Key Pitfall — Your Apple ID and GitHub Account Are Different This is the spot where people setting up direct distribution for the first time get stuck most often.\nApple Developer Program account — used for issuing certificates and for notarization (example: developer@example.com) GitHub account — used in Part 3 to host the update files (example: myname@gmail.com) In many cases, these two are different emails. In particular, the --apple-id of notarytool store-credentials must be the Apple Developer account email. If you enter the GitHub account email, authentication fails — and the error message isn\u0026rsquo;t friendly, so it\u0026rsquo;s hard to track down the cause.\nIf the two accounts\u0026rsquo; emails are easy to confuse, we recommend making a note of which one is for Apple and which is for GitHub. This distinction comes up repeatedly throughout the series.\nPart 1 Wrap-Up If you\u0026rsquo;ve followed along this far, you now have the following.\n✅ Command-line tools for release automation (gh, create-dmg) ✅ An active Apple Developer Program membership ✅ A Developer ID Application certificate stored in the Keychain ✅ A notarization credentials profile stored in the Keychain (focustimer-notarize) With this, you\u0026rsquo;re ready to sign and notarize the app. But almost no app is delivered to users just once and then done. You have to keep shipping new versions that fix bugs and add features. On the App Store, automatic updates are handled for you, but with direct distribution, this too is something you have to set up yourself.\nIn the next part, we\u0026rsquo;ll create the EdDSA signing key for Sparkle, the de facto standard automatic-update framework for macOS apps. It\u0026rsquo;s a verification mechanism dedicated to update files — yet another layer, separate from the certificate.\n","permalink":"https://hobbyworker.me/en/dev/2026-05-14-distribute-macos-app-1-developer-id-certificate/","summary":"\u003ch1 id=\"what-it-means-to-distribute-directly-with-developer-id\"\u003eWhat It Means to Distribute Directly with Developer ID\u003c/h1\u003e\n\u003cp\u003eThere are broadly two ways to get a macOS app into users\u0026rsquo; hands. One is through the \u003cstrong\u003eMac App Store (MAS)\u003c/strong\u003e, and the other is \u003cstrong\u003edirect distribution\u003c/strong\u003e — letting users download a \u003ccode\u003e.dmg\u003c/code\u003e (or \u003ccode\u003e.app\u003c/code\u003e) file you build yourself from a website, GitHub, or similar.\u003c/p\u003e\n\u003cp\u003eDirect distribution has clear advantages. You don\u0026rsquo;t have to wait for App Store review, there are no payment commissions, and you can ship updates whenever and however you like. In exchange, the things the App Store used to handle for you — \u003cstrong\u003ecode signing\u003c/strong\u003e, \u003cstrong\u003enotarization\u003c/strong\u003e, and \u003cstrong\u003eautomatic updates\u003c/strong\u003e — are now yours to set up.\u003c/p\u003e","title":"Distributing a macOS App Yourself (1): Developer ID Certificate and Notarization Setup"},{"content":"Overview The procedure described in this report will guide you through creating a Quick Action using Automator on macOS. This Quick Action can be used to convert text into a more filename-friendly format. The conversion process removes special characters, transforms the text to lowercase, and replaces spaces with hyphens.\nProcedure Open Automator Press Cmd + Space to open Spotlight, type \u0026ldquo;Automator,\u0026rdquo; and hit Enter. Create a new Service In the Automator app, select \u0026ldquo;Quick Action\u0026rdquo; (previously called \u0026ldquo;Service\u0026rdquo;) and click \u0026ldquo;Choose.\u0026rdquo; Configure the Quick Action At the top of the new window, change the \u0026ldquo;Workflow receives current\u0026rdquo; drop-down menu to \u0026ldquo;text.\u0026rdquo; Make sure the \u0026ldquo;in\u0026rdquo; drop-down is set to \u0026ldquo;any application.\u0026rdquo; Add a \u0026ldquo;Run Shell Script\u0026rdquo; action In the search bar on the left, type \u0026ldquo;Run Shell Script\u0026rdquo; and drag the action to the right pane. Configure the \u0026ldquo;Run Shell Script\u0026rdquo; action Change \u0026ldquo;Pass input\u0026rdquo; to \u0026ldquo;as arguments.\u0026rdquo; Paste the following script in the text box: 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 Add a \u0026ldquo;Copy to Clipboard\u0026rdquo; action In the search bar on the left, type \u0026ldquo;Copy to Clipboard\u0026rdquo; and drag the action to the right pane, below the \u0026ldquo;Run Shell Script\u0026rdquo; action. Save the Quick Action Press Cmd + S and give your Quick Action a name, e.g., \u0026ldquo;Convert Text to filename\u0026rdquo; The script is now ready to use from the right-click context menu in any text editor that supports macOS Services.\nTo use the script:\nSelect a block of text in your text editor. Right-click on the selected text. Go to \u0026ldquo;Services\u0026rdquo; or \u0026ldquo;Quick Actions\u0026rdquo; (depending on your macOS version). Select the \u0026ldquo;Convert Text to filename\u0026rdquo; action. The processed text will be copied to your clipboard, and you can paste it wherever you need to.\nConclusion Creating a macOS Quick Action using Automator is a convenient way to streamline your workflow by automating repetitive tasks, such as converting text into a filename-friendly format. This report provided a detailed procedure for creating a Quick Action that can be used across multiple applications on macOS, making it an essential tool for anyone looking to improve their productivity and simplify their text editing process.\n","permalink":"https://hobbyworker.me/en/dev/2023-04-06-convert-text-to-filename-using-automator-on-macos/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe procedure described in this report will guide you through creating a Quick Action using Automator on macOS. This Quick Action can be used to convert text into a more filename-friendly format. The conversion process removes special characters, transforms the text to lowercase, and replaces spaces with hyphens.\u003c/p\u003e\n\u003ch1 id=\"procedure\"\u003eProcedure\u003c/h1\u003e\n\u003col\u003e\n\u003cli\u003eOpen Automator\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003ePress \u003ccode\u003eCmd + Space\u003c/code\u003e to open Spotlight, type \u0026ldquo;Automator,\u0026rdquo; and hit \u003ccode\u003eEnter\u003c/code\u003e.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003eCreate a new Service\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eIn the Automator app, select \u0026ldquo;Quick Action\u0026rdquo; (previously called \u0026ldquo;Service\u0026rdquo;) and click \u0026ldquo;Choose.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003eConfigure the Quick Action\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eAt the top of the new window, change the \u0026ldquo;Workflow receives current\u0026rdquo; drop-down menu to \u0026ldquo;text.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003eMake sure the \u0026ldquo;in\u0026rdquo; drop-down is set to \u0026ldquo;any application.\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"4\"\u003e\n\u003cli\u003eAdd a \u0026ldquo;Run Shell Script\u0026rdquo; action\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eIn the search bar on the left, type \u0026ldquo;Run Shell Script\u0026rdquo; and drag the action to the right pane.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"5\"\u003e\n\u003cli\u003eConfigure the \u0026ldquo;Run Shell Script\u0026rdquo; action\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eChange \u0026ldquo;Pass input\u0026rdquo; to \u0026ldquo;as arguments.\u0026rdquo;\u003c/li\u003e\n\u003cli\u003ePaste the following script in the text box:\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e text_input in \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$@\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  echo \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$text_input\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e | sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/[^a-zA-Z0-9 ]//g\u0026#39;\u003c/span\u003e | tr \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;[:upper:]\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;[:lower:]\u0026#39;\u003c/span\u003e | sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/ /-/g\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"6\"\u003e\n\u003cli\u003eAdd a \u0026ldquo;Copy to Clipboard\u0026rdquo; action\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eIn the search bar on the left, type \u0026ldquo;Copy to Clipboard\u0026rdquo; and drag the action to the right pane, below the \u0026ldquo;Run Shell Script\u0026rdquo; action.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"7\"\u003e\n\u003cli\u003eSave the Quick Action\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003ePress \u003ccode\u003eCmd + S\u003c/code\u003e and give your Quick Action a name, e.g., \u0026ldquo;Convert Text to filename\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe script is now ready to use from the right-click context menu in any text editor that supports macOS Services.\u003c/p\u003e","title":"Convert Text to Filename Using Automator on macOS"},{"content":"Overview The realtime_trending_searches() function in the pytrends library allows you to retrieve real-time trending searches on Google. By analyzing this data, you can gain insights into the latest trends and topics that are capturing the attention of your audience, helping you to create timely, relevant, and engaging content.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving real-time trending searches data Analyzing the results Retrieve Real-Time Trending Searches Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll retrieve the current real-time trending searches on Google using the realtime_trending_searches() function.\n# Retrieve real-time trending searches data realtime_trending_searches = pytrends.realtime_trending_searches(pn=\u0026#39;US\u0026#39;) This will return a DataFrame containing the current real-time trending searches in the United States.\nAnalyzing the Results Now, we can analyze the real-time trending searches data to identify the latest trends and topics that are capturing the attention of our audience.\n# Display the top 10 real-time trending searches print(realtime_trending_searches.head(10)) This will display the top 10 real-time trending searches, providing valuable insights into the latest trends and topics that are capturing the attention of your audience.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the realtime_trending_searches() function in the pytrends library to discover real-time trending searches on Google. By analyzing this data, you can stay on top of the latest trends and topics, helping you create timely, relevant, and engaging content. This tutorial has covered the process of collecting and analyzing real-time trending search data, from setting up the pytrends request to analyzing the results. Using these insights, you can create content that resonates with your audience and capitalizes on current trends.\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/en/dev/2023-04-05-pytrends-11-discovering-realtime-trending-searches-for-uptotheminute-insights/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003erealtime_trending_searches()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve real-time trending searches on Google. By analyzing this data, you can gain insights into the latest trends and topics that are capturing the attention of your audience, helping you to create timely, relevant, and engaging content.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving real-time trending searches data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-real-time-trending-searches-data\"\u003eRetrieve Real-Time Trending Searches Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 11: Discovering Real-Time Trending Searches for Up-to-the-Minute Insights"},{"content":"Overview The suggestions() function in the pytrends library allows you to retrieve search suggestions for a specific query. By analyzing these suggestions, you can discover new keywords and trends that are relevant to your search query, helping you to create more engaging and targeted content.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving search suggestions data Analyzing the results Retrieve Search Suggestions Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll retrieve search suggestions for a specific query using the suggestions() function.\n# Retrieve search suggestions for the query \u0026#39;Python\u0026#39; suggestions = pytrends.suggestions(keyword=\u0026#39;Python\u0026#39;) This will return a list of dictionaries containing search suggestions related to the query \u0026lsquo;Python\u0026rsquo;.\nAnalyzing the Results Now, we can analyze the search suggestions data to discover new keywords and trends related to our search query.\n# Display the search suggestions for suggestion in suggestions: print(suggestion[\u0026#39;title\u0026#39;]) This will display the search suggestions related to the query \u0026lsquo;Python\u0026rsquo;, providing valuable insights into new keywords and trends that are relevant to your search query.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the suggestions() function in the pytrends library to refine your trend searches by obtaining search suggestions based on a given query. By exploring these suggestions, you can discover new keywords and trends that are relevant to your search query, helping you to create more engaging and targeted content. This tutorial has covered the process of collecting and analyzing search suggestions data, from setting up the pytrends request to analyzing the results. Using these insights, you can enhance your keyword research and content strategy.\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/en/dev/2023-04-04-pytrends-10-refining-trend-searches-with-suggestions/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003esuggestions()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve search suggestions for a specific query. By analyzing these suggestions, you can discover new keywords and trends that are relevant to your search query, helping you to create more engaging and targeted content.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving search suggestions data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-search-suggestions-data\"\u003eRetrieve Search Suggestions Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 10: Refining Trend Searches with Suggestions"},{"content":"Overview The top_charts() function in the pytrends library allows you to retrieve Google\u0026rsquo;s top charts for a specific year and category. By analyzing this data, you can discover the most popular search queries in various categories, helping you to create engaging and relevant content for your audience.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving top charts data Analyzing the results Retrieve Top Charts Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll retrieve the top charts data for a specific year and category using the top_charts() function.\n# Retrieve top charts data for 2022 top_charts = pytrends.top_charts(date=2022, hl=\u0026#39;en-US\u0026#39;, tz=360) This will return a DataFrame containing the top charts data in 2022.\nAnalyzing the Results Now, we can analyze the top charts data to identify the most popular search queries in our chosen category.\n# Display the top 10 in 2022 print(top_charts.head(10)) This will display the top 10 in 2022, providing valuable insights into the most popular search queries.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the top_charts() function in the pytrends library to analyze Google\u0026rsquo;s top charts for data-driven insights. By exploring this data, you can discover the most popular search queries in various categories, helping you to create engaging and relevant content for your audience. This tutorial has covered the process of collecting and analyzing top chart data, from setting up the pytrends request to analyzing the results. Using these insights, you can inform your content strategy and optimize your online presence.\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/en/dev/2023-04-03-pytrends-9-mastering-top-charts-analysis-for-datadriven-insights/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003etop_charts()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve Google\u0026rsquo;s top charts for a specific year and category. By analyzing this data, you can discover the most popular search queries in various categories, helping you to create engaging and relevant content for your audience.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving top charts data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-top-charts-data\"\u003eRetrieve Top Charts Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 9: Mastering Top Charts Analysis for Data-Driven Insights"},{"content":"Overview The trending_searches() function in the pytrends library allows you to retrieve the current trending searches on Google. By analyzing this data, you can gain insights into the latest trends and topics that are capturing the attention of your audience, helping you to create timely and relevant content.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving trending searches data Analyzing the results Retrieve Trending Searches Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll retrieve the current trending searches on Google using the trending_searches() function.\n# Retrieve trending searches data trending_searches = pytrends.trending_searches(pn=\u0026#39;united_states\u0026#39;) This will return a DataFrame containing the current trending searches in the United States.\nAnalyzing the Results Now, we can analyze the trending searches data to identify new opportunities for content creation and optimization.\n# Display the top 10 trending searches print(trending_searches.head(10)) This will display the top 10 trending searches, providing valuable insights into the latest trends and topics that are capturing the attention of your audience.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the trending_searches() function in the pytrends library to track trending searches on Google. By analyzing this data, you can stay ahead of the curve and discover new opportunities for content creation and optimization. This tutorial has covered the process of collecting and analyzing trending search data, from setting up the pytrends request to analyzing the results. Using these insights, you can create timely and relevant content for your audience.\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/en/dev/2023-04-02-pytrends-8-tracking-trending-searches-to-stay-ahead/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003etrending_searches()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve the current trending searches on Google. By analyzing this data, you can gain insights into the latest trends and topics that are capturing the attention of your audience, helping you to create timely and relevant content.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving trending searches data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-trending-searches-data\"\u003eRetrieve Trending Searches Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 8: Tracking Trending Searches to Stay Ahead"},{"content":"Overview The related_queries() function in the pytrends library allows you to retrieve related queries for a specific search term. By analyzing this data, you can gain insights into the questions and topics that are important to your audience, helping you to create more relevant and engaging content.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving related queries data Analyzing the results Retrieve Related Queries Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll specify the search term for our request using the build_payload() function, and then retrieve related queries data using the related_queries() function.\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() This will return a dictionary containing related queries data for the search term \u0026lsquo;Python\u0026rsquo; over the past 7 days.\nAnalyzing the Results Now, we can analyze the related queries data to identify new opportunities for content creation and optimization.\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)) This will display the top 10 rising related queries for the search term \u0026lsquo;Python\u0026rsquo;, providing valuable insights into the questions and topics that are important to your audience.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the related_queries() function in the pytrends library to uncover related queries for a given search term. By analyzing this data, you can perform in-depth analysis of your target keywords and discover new opportunities for content creation and optimization. This tutorial has covered the process of collecting and analyzing related query data, from setting up the pytrends request to analyzing the results. Using these insights, you can create more relevant and engaging content for your audience.\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/en/dev/2023-04-01-pytrends-7-uncovering-related-queries-for-indepth-analysis/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003erelated_queries()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve related queries for a specific search term. By analyzing this data, you can gain insights into the questions and topics that are important to your audience, helping you to create more relevant and engaging content.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving related queries data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-related-queries-data\"\u003eRetrieve Related Queries Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 7: Uncovering Related Queries for In-Depth Analysis"},{"content":"Overview The related_topics() function in the pytrends library allows you to retrieve related topics for a specific search term. By analyzing this data, you can discover new keywords and ideas that are relevant to your target audience, helping you to create engaging and relevant content.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving related topics data Analyzing the results Retrieve Related Topics Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll specify the search term for our request using the build_payload() function, and then retrieve related topics data using the related_topics() function.\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() This will return a dictionary containing related topics data for the search term \u0026lsquo;Python\u0026rsquo; over the past 7 days.\nAnalyzing the Results Now, we can analyze the related topics data to identify new keywords and ideas for our content strategy.\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)) This will display the top 10 rising related topics for the search term \u0026lsquo;Python\u0026rsquo;, providing valuable insights into new and emerging trends related to your keyword.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the related_topics() function in the pytrends library to investigate related topics for a given search term. By exploring this data, you can expand your keyword research and discover new opportunities to engage your target audience. This tutorial has covered the process of collecting and analyzing related topic data, from setting up the pytrends request to analyzing the results. Using these insights, you can inform your content strategy and boost your online presence.\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/en/dev/2023-03-31-pytrends-6-investigating-related-topics-to-expand-keyword-research/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003erelated_topics()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve related topics for a specific search term. By analyzing this data, you can discover new keywords and ideas that are relevant to your target audience, helping you to create engaging and relevant content.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving related topics data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"retrieve-related-topics-data\"\u003eRetrieve Related Topics Data\u003c/h1\u003e\n\u003cp\u003eFirst, we need to import the necessary libraries and set up our \u003ccode\u003epytrends\u003c/code\u003e request.\u003c/p\u003e","title":"Pytrends 6: Investigating Related Topics to Expand Keyword Research"},{"content":"Overview The interest_by_region() function in the pytrends library allows you to retrieve interest data for specific search terms across different geographic locations. By analyzing this data, you can gain valuable insights into the popularity of search terms in different regions, which can help inform your marketing and content strategies.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving interest by region data Visualizing the results Installation To install Pytrends, simply use pip:\npip install matplotlib Retrieve Interest by Region Data First, we need to import the necessary libraries and set up our pytrends request.\nfrom pytrends.request import TrendReq import pandas as pd # Set up pytrends request pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Next, we\u0026rsquo;ll specify the search term and time range for our request using the build_payload() function, and then retrieve interest by region data using the interest_by_region() function.\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) This will return a DataFrame containing interest by region data for the search term \u0026lsquo;Python\u0026rsquo; during the first quarter of 2023.\nVisualizing the Results Now, we can visualize the interest by region data using a bar plot.\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() This plot shows the interest for the search term \u0026lsquo;Python\u0026rsquo; across different countries, allowing you to identify areas where the term is particularly popular.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the interest_by_region() function in the pytrends library to analyze interest by region for specific search terms. By exploring this data, you can gain targeted insights into the popularity of search terms across different geographic locations, helping you to better understand your audience and optimize your marketing strategies. This tutorial has covered the process of collecting and analyzing interest by region data, from setting up the pytrends request to visualizing the results.\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/en/dev/2023-03-30-pytrends-5-exploring-interest-by-region-for-targeted-insights/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003einterest_by_region()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve interest data for specific search terms across different geographic locations. By analyzing this data, you can gain valuable insights into the popularity of search terms in different regions, which can help inform your marketing and content strategies.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving interest by region data\u003c/li\u003e\n\u003cli\u003eVisualizing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"installation\"\u003eInstallation\u003c/h1\u003e\n\u003cp\u003eTo install Pytrends, simply use pip:\u003c/p\u003e","title":"Pytrends 5: Exploring Interest by Region for Targeted Insights"},{"content":"Overview The get_historical_interest() function in the pytrends library allows you to retrieve hourly interest data for specific search terms within a given time frame. This can be useful for gaining a more granular understanding of search term popularity and for identifying trends that may not be visible when looking at daily or weekly data.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Retrieving historical hourly interest data Visualizing the results Installation To install Pytrends, simply use pip:\npip install matplotlib Retrieve Historical Hourly Interest Data To start, we need to import the necessary libraries and set up our pytrends request.\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) Next, we\u0026rsquo;ll specify the search terms, time range, and other parameters for our request using the get_historical_interest() function.\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) This will return a DataFrame containing hourly interest data for the search terms \u0026lsquo;Python\u0026rsquo; and \u0026lsquo;JavaScript\u0026rsquo; from March 1st to March 2nd, 2023.\nVisualizing the Results Now, we can visualize the hourly interest data using a simple line plot.\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() This plot shows the hourly interest for both \u0026lsquo;Python\u0026rsquo; and \u0026lsquo;JavaScript\u0026rsquo; over the specified time frame, allowing you to compare their popularity and identify trends.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the get_historical_interest() function in the pytrends library to retrieve historical hourly interest data from Google Trends. By diving into this data, you can gain valuable insights into the popularity of search terms and better understand consumer behavior. This tutorial has covered the process of collecting and analyzing hourly interest data, from setting up the pytrends request to visualizing the results.\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/en/dev/2023-03-29-pytrends-4-diving-into-historical-hourly-interest-data/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003eget_historical_interest()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve hourly interest data for specific search terms within a given time frame. This can be useful for gaining a more granular understanding of search term popularity and for identifying trends that may not be visible when looking at daily or weekly data.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eRetrieving historical hourly interest data\u003c/li\u003e\n\u003cli\u003eVisualizing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"installation\"\u003eInstallation\u003c/h1\u003e\n\u003cp\u003eTo install Pytrends, simply use pip:\u003c/p\u003e","title":"Pytrends 4: Diving into Historical Hourly Interest Data"},{"content":"Overview The multirange_interest_over_time() function in the pytrends library allows you to retrieve the interest in specific keywords over multiple time ranges. By analyzing this data, you can gain insights into how the interest in a keyword has evolved over different periods, helping you to make informed decisions about your content strategy and marketing efforts.\nIn this tutorial, we will cover:\nImporting the necessary libraries Setting up the pytrends request Building the list of time ranges Retrieving multi-range interest over time data Analyzing the results Building the List of Time Ranges First, we need to create a list of time ranges for which we want to analyze the interest in our keywords. In this example, we will create a list of two time ranges\ntime_ranges = [ \u0026#39;2022-09-04 2022-09-10\u0026#39;, \u0026#39;2022-09-18 2022-09-24\u0026#39;, ] Retrieve Multi-Range Interest Over Time Data Next, we need to import the necessary libraries, set up our pytrends request, and retrieve the multi-range interest over time data for our keywords and time ranges.\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() This will return a dictionary containing the interest over time data for our keywords across the specified time ranges.\nAnalyzing the Results Now, we can analyze the multi-range interest over time data to understand the performance and popularity of our keywords across different periods.\n# Display the interest over time data print(interest_over_time_data) This will display the interest over time data for each time range, providing valuable insights into the performance and popularity of our keywords across different periods.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the multirange_interest_over_time() function in the pytrends library to analyze the interest in specific keywords over multiple time ranges. By analyzing this data, you can gain a more comprehensive view of trends and understand how the interest in a keyword has evolved over different periods. This information can help you make informed decisions about your content strategy, marketing efforts, and even product development. By harnessing multi-range interest over time analysis, you can stay ahead of the competition and ensure that your content and products remain relevant and appealing to your target audience.\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/en/dev/2023-03-28-pytrends-3-harnessing-multirange-interest-over-time-analysis/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003emultirange_interest_over_time()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve the interest in specific keywords over multiple time ranges. By analyzing this data, you can gain insights into how the interest in a keyword has evolved over different periods, helping you to make informed decisions about your content strategy and marketing efforts.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eImporting the necessary libraries\u003c/li\u003e\n\u003cli\u003eSetting up the \u003ccode\u003epytrends\u003c/code\u003e request\u003c/li\u003e\n\u003cli\u003eBuilding the list of time ranges\u003c/li\u003e\n\u003cli\u003eRetrieving multi-range interest over time data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"building-the-list-of-time-ranges\"\u003eBuilding the List of Time Ranges\u003c/h1\u003e\n\u003cp\u003eFirst, we need to create a list of time ranges for which we want to analyze the interest in our keywords. In this example, we will create a list of two time ranges\u003c/p\u003e","title":"Pytrends 3: Harnessing Multi-Range Interest Over Time Analysis"},{"content":"Overview The interest_over_time() function in the pytrends library allows you to retrieve the interest in specific keywords over time. By analyzing this data, you can gain insights into how the interest in a keyword has evolved, helping you to make informed decisions about your content strategy and marketing efforts.\nIn this tutorial, we will cover:\nDefining the list of keywords Setting the time range Retrieving interest over time data Analyzing the results Define the List of Keywords First, we need to define the list of keywords for which we want to analyze the interest over time.\nkeywords = [\u0026#39;Python\u0026#39;, \u0026#39;JavaScript\u0026#39;] Set the Time Range Next, we need to set the time range for which we want to analyze the interest in our keywords. In this example, we will analyze the interest over the past year.\ntime_range = \u0026#39;2022-01-01 2023-01-31\u0026#39; Retrieve Interest Over Time Data Now, we need to retrieve the interest over time data for our keywords and time range using the interest_over_time() function.\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() This will return a DataFrame containing the interest over time data for our keywords.\nAnalyzing the Results Now, we can analyze the interest over time data to understand the performance and popularity of our keywords.\nprint(interest_over_time_data.head()) This will display the interest over time data, providing valuable insights into the performance and popularity of our keywords.\nConclusion In this post, we\u0026rsquo;ve demonstrated how to use the interest_over_time() function in the pytrends library to analyze the interest in specific keywords over time. By analyzing this data, you can gain insights into how the interest in a keyword has evolved, helping you make informed decisions about your content strategy, marketing efforts, and even product development. By harnessing interest over time analysis, you can stay ahead of the competition and ensure that your content and products remain relevant and appealing to your target audience.\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/en/dev/2023-03-27-pytrends-2-analyzing-interest-over-time/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eThe \u003ccode\u003einterest_over_time()\u003c/code\u003e function in the \u003ccode\u003epytrends\u003c/code\u003e library allows you to retrieve the interest in specific keywords over time. By analyzing this data, you can gain insights into how the interest in a keyword has evolved, helping you to make informed decisions about your content strategy and marketing efforts.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we will cover:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eDefining the list of keywords\u003c/li\u003e\n\u003cli\u003eSetting the time range\u003c/li\u003e\n\u003cli\u003eRetrieving interest over time data\u003c/li\u003e\n\u003cli\u003eAnalyzing the results\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"define-the-list-of-keywords\"\u003eDefine the List of Keywords\u003c/h1\u003e\n\u003cp\u003eFirst, we need to define the list of keywords for which we want to analyze the interest over time.\u003c/p\u003e","title":"Pytrends 2: Analyzing Interest Over Time"},{"content":"Overview rustup is the official toolchain installer and manager for the Rust programming language. It provides a convenient way to install, update, and manage multiple Rust toolchains on your system. This report will cover the installation process for various platforms, basic usage of rustup, and provide an example of managing multiple Rust environments.\nInstallation macOS and Linux To install rustup on macOS and Linux systems, open your terminal and enter the following command:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh The script will download and install the necessary components. Once completed, restart your terminal or run the following command to update your shell\u0026rsquo;s environment variables:\nsource $HOME/.cargo/env Windows For Windows users, download and run the rustup-init.exe executable from the official Rust website. Follow the on-screen instructions to complete the installation. After the installation is complete, restart your command prompt or terminal.\nUsage Installing a specific Rust version To install a specific version of Rust, use the following command:\nrustup install \u0026lt;version\u0026gt; Replace \u0026lt;version\u0026gt; with the desired Rust version, e.g., 1.52.0.\nSetting the default Rust version To set the default Rust version for new projects, use the following command:\nrustup default \u0026lt;version\u0026gt; Replace \u0026lt;version\u0026gt; with the desired Rust version, e.g., 1.52.0.\nSwitching between Rust versions To switch between different Rust versions for a specific project, navigate to the project directory and use the following command:\nrustup override set \u0026lt;version\u0026gt; Replace \u0026lt;version\u0026gt; with the desired Rust version, e.g., 1.52.0.\nUpdating Rust To update all installed Rust toolchains to their latest versions, run the following command:\nrustup update Uninstalling Rust To uninstall Rust and rustup from your system, run the following command:\nrustup self uninstall Example Suppose you are working on two Rust projects: project_old and project_new. project_old requires Rust version 1.52.0, while project_new requires the latest stable version.\nFirst, install the required Rust versions:\nrustup install 1.52.0 rustup install stable Next, navigate to the project_old directory and set the Rust version for the project:\ncd project_old rustup override set 1.52.0 Now, navigate to the project_new directory and set the Rust version for the project:\ncd project_new rustup override set stable With these configurations, each project will use the appropriate Rust version when you build or run them.\nFor example, when you run cargo build or cargo run in the project_old directory, Rust 1.52.0 will be used:\ncd project_old cargo build Similarly, when you run cargo build or cargo run in the project_new directory, the latest stable Rust version will be used:\ncd project_new cargo build With rustup, you can seamlessly work on multiple projects with different Rust version requirements without any conflicts or manual intervention.\nConclusion rustup is an essential tool for Rust developers, as it simplifies the process of managing multiple Rust environments on a single system. This report covered the installation process for various platforms, basic usage, and provided an example of managing different Rust versions for multiple projects. By using rustup, developers can ensure that their projects are always built and run using the correct Rust version, improving productivity and reducing the likelihood of version-related issues.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-26-managing-multiple-rust-environments-with-rustup/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003erustup\u003c/code\u003e is the official toolchain installer and manager for the Rust programming language. It provides a convenient way to install, update, and manage multiple Rust toolchains on your system. This report will cover the installation process for various platforms, basic usage of \u003ccode\u003erustup\u003c/code\u003e, and provide an example of managing multiple Rust environments.\u003c/p\u003e\n\u003ch1 id=\"installation\"\u003eInstallation\u003c/h1\u003e\n\u003ch2 id=\"macos-and-linux\"\u003emacOS and Linux\u003c/h2\u003e\n\u003cp\u003eTo install \u003ccode\u003erustup\u003c/code\u003e on macOS and Linux systems, open your terminal and enter the following command:\u003c/p\u003e","title":"Managing Multiple Rust Environments with rustup"},{"content":"Overview Google Trends provides valuable insights into search trends and keyword popularity. However, Google doesn\u0026rsquo;t offer an official API for accessing this data. Fortunately, the Pytrends library enables us to access Google Trends data using Python.\nIn this tutorial, we\u0026rsquo;ll walk you through the installation and setup of Pytrends, and demonstrate how to perform a simple search and interpret the results.\nPrerequisites To follow this tutorial, you should have:\nPython 3 installed Basic knowledge of Python programming Familiarity with using Python packages Installation To install Pytrends, simply use pip:\npip install pytrends Setting Up Pytrends To start using Pytrends, first import the necessary libraries and establish a connection to Google Trends:\nfrom pytrends.request import TrendReq pytrends = TrendReq(hl=\u0026#39;en-US\u0026#39;, tz=360) Here, we set the language to English (hl='en-US') and the timezone to UTC+0 (tz=360).\nPerforming a Basic Search Now, let\u0026rsquo;s perform a simple search to see the interest over time for the keyword \u0026ldquo;Python\u0026rdquo;:\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) This code snippet defines a list of keywords, sets the timeframe to the past five years (timeframe='today 5-y'), and leaves the geographic location and Google property empty. The interest_over_time() method returns a DataFrame containing the interest data.\nUnderstanding the Results The resulting DataFrame contains the search interest for the keyword \u0026ldquo;Python\u0026rdquo; over the past five years. The values represent the search interest relative to the highest point in the specified timeframe, with 100 being the peak popularity.\nConclusion In this post, we introduced Pytrends, an unofficial Google Trends API for Python, and demonstrated how to install and set it up. We performed a basic search using the library and discussed how to interpret the results.\nIn the upcoming posts, we\u0026rsquo;ll dive deeper into more advanced Pytrends functionalities, such as analyzing interest over time, exploring interest by region, and uncovering related topics and queries. Stay tuned!\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/en/dev/2023-03-26-pytrends-1-how-to-use-google-trend-unofficially-with-python/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eGoogle Trends provides valuable insights into search trends and keyword popularity. However, Google doesn\u0026rsquo;t offer an official API for accessing this data. Fortunately, the Pytrends library enables us to access Google Trends data using Python.\u003c/p\u003e\n\u003cp\u003eIn this tutorial, we\u0026rsquo;ll walk you through the installation and setup of Pytrends, and demonstrate how to perform a simple search and interpret the results.\u003c/p\u003e\n\u003ch2 id=\"prerequisites\"\u003ePrerequisites\u003c/h2\u003e\n\u003cp\u003eTo follow this tutorial, you should have:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePython 3 installed\u003c/li\u003e\n\u003cli\u003eBasic knowledge of Python programming\u003c/li\u003e\n\u003cli\u003eFamiliarity with using Python packages\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"installation\"\u003eInstallation\u003c/h1\u003e\n\u003cp\u003eTo install Pytrends, simply use pip:\u003c/p\u003e","title":"Pytrends 1: How to use Google Trend unofficially with Python"},{"content":"In this blog post, we will discuss how to deploy a Hugo static site to GitHub Pages using GitHub Actions. GitHub Actions is an automation feature provided by GitHub that allows you to create custom software development workflows directly in your GitHub repository. By using GitHub Actions, you can automate the process of building and deploying your Hugo static site to GitHub Pages with ease.\nConfiguring GitHub Pages Settings Before you can successfully deploy your Hugo static site to GitHub Pages using GitHub Actions, you need to configure the GitHub Pages settings for your project.\nGo to your GitHub project page and click on the \u0026ldquo;Settings\u0026rdquo; tab in the top right corner. Scroll down to the \u0026ldquo;Pages\u0026rdquo; section. In the \u0026ldquo;Build and deployment\u0026rdquo; settings, locate the \u0026ldquo;Source\u0026rdquo; dropdown menu. Select \u0026ldquo;GitHub Actions\u0026rdquo; from the available options. This tells GitHub Pages to use the artifacts generated by GitHub Actions for deployment. Now that you\u0026rsquo;ve configured your GitHub Pages settings, your site will be deployed using the workflow defined in the hugo.yaml file.\nSetting Up Your GitHub Pages Workflow To set up a GitHub Pages workflow with Hugo and GitHub Actions, follow these steps:\nCreate a new file named hugo.yaml in the .github/workflows/ directory of your Hugo site\u0026rsquo;s repository. Copy the following YAML configuration into the hugo.yaml file: 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 This configuration file sets up a workflow that builds your Hugo static site and deploys it to GitHub Pages when you push to the main branch or run the workflow manually from the Actions tab.\nConclusion With the addition of configuring GitHub Pages settings in your project and using the provided hugo.yaml workflow configuration, you can easily automate the process of building and deploying your Hugo static site to GitHub Pages. By utilizing GitHub Actions, you can focus on creating content and making updates to your site without having to manually deploy it each time. This setup also allows you to take advantage of the built-in CI/CD capabilities provided by GitHub Actions, further improving your development workflow.\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/en/dev/2023-03-25-deploying-a-hugo-static-site-to-github-pages-with-github-actions/","summary":"\u003cp\u003eIn this blog post, we will discuss how to deploy a Hugo static site to GitHub Pages using GitHub Actions. GitHub Actions is an automation feature provided by GitHub that allows you to create custom software development workflows directly in your GitHub repository. By using GitHub Actions, you can automate the process of building and deploying your Hugo static site to GitHub Pages with ease.\u003c/p\u003e\n\u003ch1 id=\"configuring-github-pages-settings\"\u003eConfiguring GitHub Pages Settings\u003c/h1\u003e\n\u003cp\u003eBefore you can successfully deploy your Hugo static site to GitHub Pages using GitHub Actions, you need to configure the GitHub Pages settings for your project.\u003c/p\u003e","title":"Deploying a Hugo Static Site to GitHub Pages with GitHub Actions"},{"content":"In this blog post, we will learn how to add AdBlocker detection to your Hugo blog using the PaperMod theme. We will also include a simple warning message for users who have an ad-blocker enabled, encouraging them to disable it or whitelist your website.\nOverview Here\u0026rsquo;s a quick overview of the steps we will take:\nCreate a custom CSS file to style the warning message. Create a JavaScript file to detect ad-blockers. Add a partial HTML file for the warning message. Extend the head and footer partials to include our new files. Step-by-step Guide 1. Create a custom CSS file Create a new file named custom_css.css under the assets/css/extended/ directory and paste the following CSS code:\n#adblock-warning { background-color: #f2dede; color: #a94442; border-color: #ebccd1; padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; } 2. Create a JavaScript file to detect ad-blockers Create a new file named adblock-detection.js under the static/js/ directory and paste the following JavaScript code:\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. Add a partial HTML file for the warning message Create a new file named adblock-warning.html under the layouts/partials/ directory and paste the following HTML code:\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. Extend the head and footer partials to include our new files Edit the layouts/partials/extend_head.html file and add the following line:\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; Edit the layouts/partials/extend_footer.html file and add the following line:\n\u0026lt;script src=\u0026#34;{{ \u0026#34;js/adblock-detection.js\u0026#34; | relURL }}\u0026#34; defer\u0026gt;\u0026lt;/script\u0026gt; Conclusion With the above steps completed, you have successfully added AdBlocker detection to your Hugo blog using the PaperMod theme. Visitors with an ad-blocker enabled will now see a polite warning message encouraging them to disable it or whitelist your website, helping to support your content and keep it free for everyone.\nSource code : https://github.com/hobbyworker/hugo-demo\nDemo : https://hobbyworker.me/hugo-demo\n","permalink":"https://hobbyworker.me/en/dev/2023-03-24-adding-adblocker-detection-to-your-hugo-blog-with-papermod-theme/","summary":"\u003cp\u003eIn this blog post, we will learn how to add AdBlocker detection to your Hugo blog using the PaperMod theme. We will also include a simple warning message for users who have an ad-blocker enabled, encouraging them to disable it or whitelist your website.\u003c/p\u003e\n\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eHere\u0026rsquo;s a quick overview of the steps we will take:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eCreate a custom CSS file to style the warning message.\u003c/li\u003e\n\u003cli\u003eCreate a JavaScript file to detect ad-blockers.\u003c/li\u003e\n\u003cli\u003eAdd a partial HTML file for the warning message.\u003c/li\u003e\n\u003cli\u003eExtend the head and footer partials to include our new files.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"step-by-step-guide\"\u003eStep-by-step Guide\u003c/h1\u003e\n\u003ch2 id=\"1-create-a-custom-css-file\"\u003e1. Create a custom CSS file\u003c/h2\u003e\n\u003cp\u003eCreate a new file named \u003ccode\u003ecustom_css.css\u003c/code\u003e under the \u003ccode\u003eassets/css/extended/\u003c/code\u003e directory and paste the following CSS code:\u003c/p\u003e","title":"Adding AdBlocker Detection to Your Hugo Blog with PaperMod Theme"},{"content":"In this blog post, we will learn how to use nvm (Node Version Manager) and autoenv together to manage Node.js versions and environment variables in your development workflow. This guide assumes you have already installed both nvm and autoenv on your system.\nWhy use NVM and Autoenv together? nvm is a fantastic tool for managing multiple versions of Node.js on your system, allowing you to switch between them easily. autoenv simplifies the process of managing environment variables by automatically loading them from a .env file when you enter a directory.\nBy combining the two, you can set up your development environment to automatically switch to the appropriate Node.js version and load the relevant environment variables, streamlining your workflow.\nCreating an .env file First, create an .env file in the root directory of your project. This file will contain the environment variables and the Node.js version you want to use for the project.\nHere\u0026rsquo;s an example of what the .env file might look like:\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 In this example, we\u0026rsquo;re setting the NODE_ENV, API_KEY, and PORT environment variables. We\u0026rsquo;re also specifying the path to the nvm directory and instructing it to use Node.js version 14.17.0 for the project.\nUsing NVM with Autoenv Now that you have your .env file set up, you need to configure autoenv to work with nvm. To do this, add the following line to your .autoenv.zsh or .autoenv.sh file, depending on your shell:\nsource \u0026#34;$NVM_DIR/nvm.sh\u0026#34; This line ensures that the nvm command is available when autoenv loads the .env file.\nSetting up your project With the configuration complete, navigate to your project\u0026rsquo;s root directory using the terminal. You should see a message from autoenv indicating that it has loaded the .env file:\n$ cd your_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v14.17.0 Now, the specified Node.js version and the environment variables from the .env file will be automatically set for your project.\nSwitching between projects When you navigate between projects with different .env files, autoenv and nvm will automatically adjust the Node.js version and environment variables accordingly:\n$ cd another_project_directory autoenv: autoenv: Loading .env autoenv: Switching to Node.js v12.22.1 This makes managing different Node.js versions and environments a breeze!\nConclusion By using nvm and autoenv in combination, you can greatly simplify the management of Node.js versions and environment variables for your projects. This will make your development process more efficient and ensure that you\u0026rsquo;re always using the correct settings for each project.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-23-using-nvm-and-autoenv-in-combination/","summary":"\u003cp\u003eIn this blog post, we will learn how to use \u003ccode\u003envm\u003c/code\u003e (Node Version Manager) and \u003ccode\u003eautoenv\u003c/code\u003e together to manage Node.js versions and environment variables in your development workflow. This guide assumes you have already installed both \u003ccode\u003envm\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e on your system.\u003c/p\u003e\n\u003ch1 id=\"why-use-nvm-and-autoenv-together\"\u003eWhy use NVM and Autoenv together?\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003envm\u003c/code\u003e is a fantastic tool for managing multiple versions of Node.js on your system, allowing you to switch between them easily. \u003ccode\u003eautoenv\u003c/code\u003e simplifies the process of managing environment variables by automatically loading them from a \u003ccode\u003e.env\u003c/code\u003e file when you enter a directory.\u003c/p\u003e","title":"Using NVM and Autoenv in Combination"},{"content":"In this blog post, we will discuss how to use jEnv and autoenv in combination to manage multiple Java versions and automatically set environment variables for your projects. This tutorial assumes that you have already installed jEnv and autoenv on your system. We will go over some working examples to demonstrate their usage.\nOverview of jEnv and autoenv jEnv jEnv is a command line tool that simplifies the management of multiple Java installations on your system. It allows you to switch between different Java versions easily, set a global or local version, and provides a convenient way to configure your environment.\nautoenv autoenv is a command line tool that automatically sets environment variables when you navigate to a project directory. It works by looking for a .env file in the project directory and executing its contents.\nConfiguring jEnv Before we dive into the examples, let\u0026rsquo;s briefly go over how to configure jEnv. First, add installed Java versions to jEnv using the jenv add command:\njenv add /path/to/java/version To list all added Java versions, use:\njenv versions To set a global Java version, use:\njenv global \u0026lt;version\u0026gt; To set a local Java version for a specific project, navigate to the project directory and use:\njenv local \u0026lt;version\u0026gt; Working with jEnv and autoenv Now, let\u0026rsquo;s see some working examples of using jEnv and autoenv in combination.\nExample 1: Setting the Java version for a project Create a .env file in your project directory with the following content:\n# .env export JAVA_HOME=$(jenv prefix) When you navigate to the project directory, autoenv will automatically set the JAVA_HOME environment variable to the currently active Java version as configured by jEnv.\nExample 2: Setting the Java version and other environment variables Suppose you have a project that requires a specific Java version and some additional environment variables. Create a .env file in the project directory with the following content:\n# .env jenv local \u0026lt;version\u0026gt; export JAVA_HOME=$(jenv prefix) export APP_ENV=development export API_KEY=your_api_key When you navigate to the project directory, autoenv will automatically set the local Java version, JAVA_HOME, and other required environment variables.\nExample 3: Unsetting environment variables upon leaving the project directory To unset the environment variables when leaving the project directory, create a .env.leave file in the project directory with the following content:\n# .env.leave unset JAVA_HOME unset APP_ENV unset API_KEY Now, when you navigate out of the project directory, autoenv will automatically unset the environment variables.\nConclusion By using jEnv and autoenv in combination, you can simplify the management of Java versions and environment variables for your projects. This helps to create a more consistent and predictable development environment, making it easier to work on multiple projects with different Java versions and configurations. In addition, these tools automate the process of switching between various Java environments, thus reducing the time spent on manual configuration and minimizing potential errors.\nFurthermore, by leveraging the power of jEnv and autoenv, developers can more efficiently collaborate with their teammates, as the setup process becomes streamlined and reproducible. This allows for smoother onboarding of new team members and promotes a cohesive development workflow.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-22-how-to-use-jenv-and-autoenv-in-combination/","summary":"\u003cp\u003eIn this blog post, we will discuss how to use \u003ccode\u003ejEnv\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e in combination to manage multiple Java versions and automatically set environment variables for your projects. This tutorial assumes that you have already installed \u003ccode\u003ejEnv\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e on your system. We will go over some working examples to demonstrate their usage.\u003c/p\u003e\n\u003ch1 id=\"overview-of-jenv-and-autoenv\"\u003eOverview of jEnv and autoenv\u003c/h1\u003e\n\u003ch2 id=\"jenv\"\u003ejEnv\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003ejEnv\u003c/code\u003e is a command line tool that simplifies the management of multiple Java installations on your system. It allows you to switch between different Java versions easily, set a global or local version, and provides a convenient way to configure your environment.\u003c/p\u003e","title":"How to Use jEnv and autoenv in Combination"},{"content":"In this blog post, we will discuss how to combine the power of rbenv and autoenv for managing Ruby versions and environment variables seamlessly in your projects. By the end of this post, you\u0026rsquo;ll have a clear understanding of how to use these tools together to make your Ruby development experience even better.\nNote: This tutorial assumes that you have already installed rbenv and autoenv. If you haven\u0026rsquo;t done so, please follow the installation instructions for rbenv and autoenv.\nWhy Use rbenv and autoenv Together? rbenv is a powerful tool that lets you manage multiple Ruby versions easily. On the other hand, autoenv helps you manage environment variables that are specific to a project directory. By using both tools together, you can ensure that you\u0026rsquo;re using the correct Ruby version and environment variables for each project without the need for manual intervention.\nConfiguring rbenv and autoenv Before diving into examples, let\u0026rsquo;s configure autoenv to work with rbenv. To do this, create a new .env file in your project directory, and add the following lines:\nexport RBENV_VERSION=$(cat .ruby-version) export PATH=\u0026#34;$HOME/.rbenv/shims:$PATH\u0026#34; The first line sets the RBENV_VERSION environment variable based on the contents of the .ruby-version file. The second line ensures that the rbenv shims directory is in the PATH, so the correct Ruby version is used when running Ruby commands.\nExample 1: Switching Ruby Versions Suppose you have two projects: project_a and project_b. You want to use Ruby version 2.7.4 for project_a and Ruby version 3.0.2 for project_b. Here\u0026rsquo;s how you can achieve this with rbenv and autoenv:\nCreate a .ruby-version file in each project directory: 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 Create a .env file in each project directory with the contents mentioned earlier: cp .env project_a/ cp .env project_b/ Now, when you navigate to project_a or project_b, autoenv will automatically set the RBENV_VERSION and adjust the PATH to use the correct Ruby version.\nExample 2: Managing Project-Specific Environment Variables Let\u0026rsquo;s say project_a requires the following environment variables:\nAPI_KEY: Your API key for a third-party service SECRET_KEY: A secret key for encrypting data You can add these variables to the .env file in project_a like this:\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; Now, when you navigate to project_a, autoenv will automatically set the RBENV_VERSION, adjust the PATH, and set the API_KEY and SECRET_KEY environment variables.\nConclusion By using rbenv and autoenv together, you can easily manage Ruby versions and project-specific environment variables without any manual intervention. This combination makes your Ruby development experience more efficient and less error-prone.\nDon\u0026rsquo;t forget to add your .env files to your .gitignore to avoid accidentally committing sensitive information to your version control system.\nWith this setup in place, you\u0026rsquo;ll enjoy a smooth Ruby development workflow tailored to each of your projects. Happy coding!\n","permalink":"https://hobbyworker.me/en/dev/2023-03-21-how-to-use-rbenv-and-autoenv-in-combination/","summary":"\u003cp\u003eIn this blog post, we will discuss how to combine the power of \u003ccode\u003erbenv\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e for managing Ruby versions and environment variables seamlessly in your projects. By the end of this post, you\u0026rsquo;ll have a clear understanding of how to use these tools together to make your Ruby development experience even better.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNote\u003c/strong\u003e: This tutorial assumes that you have already installed \u003ccode\u003erbenv\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e. If you haven\u0026rsquo;t done so, please follow the installation instructions for \u003ca href=\"https://hobbyworker.me/posts/2023/03/15/managing-multiple-ruby-environments-with-rbenv/\"\u003e\u003ccode\u003erbenv\u003c/code\u003e\u003c/a\u003e and \u003ca href=\"https://hobbyworker.me/posts/2023/03/19/using-autoenv-the-ultimate-shortcut-to-environment-management/\"\u003e\u003ccode\u003eautoenv\u003c/code\u003e\u003c/a\u003e.\u003c/p\u003e","title":"How to Use rbenv and autoenv in Combination"},{"content":"I. Overview In this blog post, we will explore how to use pyenv-virtualenv and autoenv together for seamless Python development. These tools can help you manage multiple Python environments and virtual environments with ease, improving your development workflow.\nII. Pyenv-virtualenv Pyenv-virtualenv is a plugin for pyenv that allows you to create and manage virtual environments for different Python versions. It helps you keep dependencies for different projects separate, ensuring that each project has access to the packages it requires without interference.\nCreating a Virtual Environment To create a new virtual environment using pyenv-virtualenv, use the following command:\npyenv virtualenv \u0026lt;python-version\u0026gt; \u0026lt;virtualenv-name\u0026gt; For example, if you want to create a virtual environment named my_project using Python 3.8.0:\npyenv virtualenv 3.8.0 my_project Listing Virtual Environments To list all the virtual environments you\u0026rsquo;ve created, use the following command:\npyenv virtualenvs Activating a Virtual Environment To activate a virtual environment, use the following command:\npyenv activate \u0026lt;virtualenv-name\u0026gt; For example:\npyenv activate my_project Deactivating a Virtual Environment To deactivate the current virtual environment, use the following command:\npyenv deactivate III. Autoenv Autoenv is a tool that automatically activates a virtual environment when you enter a directory containing a .env file. This makes it easy to switch between projects without having to remember to activate and deactivate virtual environments manually.\nSetting up Autoenv To use autoenv, you need to create a .env file in the root directory of your project. This file will contain the commands that should be executed when you enter the directory.\nFor example, let\u0026rsquo;s say you have a project located at ~/projects/my_project and you want to use the my_project virtual environment created earlier. Create a .env file in the ~/projects/my_project directory with the following content:\nsource $(pyenv root)/versions/my_project/bin/activate Using Autoenv Now, when you navigate to the project directory, autoenv will automatically activate the my_project virtual environment for you:\ncd ~/projects/my_project You should see a message indicating that the virtual environment has been activated:\nautoenv: Activating environment . . . (my_project) $ When you leave the project directory, the virtual environment will be deactivated automatically:\ncd ~ You should see a message indicating that the virtual environment has been deactivated:\nautoenv: Deactivating environment . . . $ IV. Conclusion By combining pyenv-virtualenv and autoenv, you can create a seamless development workflow for managing multiple Python projects. This approach ensures that you always use the correct virtual environment for each project, while also keeping your dependencies separate and organized.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-20-using-pyenv-virtualenv-and-autoenv-in-combination-for-python-development/","summary":"\u003ch1 id=\"i-overview\"\u003eI. Overview\u003c/h1\u003e\n\u003cp\u003eIn this blog post, we will explore how to use \u003ccode\u003epyenv-virtualenv\u003c/code\u003e and \u003ccode\u003eautoenv\u003c/code\u003e together for seamless Python development. These tools can help you manage multiple Python environments and virtual environments with ease, improving your development workflow.\u003c/p\u003e\n\u003ch1 id=\"ii-pyenv-virtualenv\"\u003eII. Pyenv-virtualenv\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003ePyenv-virtualenv\u003c/code\u003e is a plugin for \u003ccode\u003epyenv\u003c/code\u003e that allows you to create and manage virtual environments for different Python versions. It helps you keep dependencies for different projects separate, ensuring that each project has access to the packages it requires without interference.\u003c/p\u003e","title":"Using Pyenv-virtualenv and Autoenv in Combination for Python Development"},{"content":"I. Overview Autoenv is a tool that makes it easy to manage your environment variables. With it, you can automatically set environment variables when you enter a directory, and unset them when you leave. This makes it a powerful tool for managing different environments for different projects, and automating repetitive tasks. In this article, we\u0026rsquo;ll cover how to install Autoenv on different platforms and some usage examples.\nII. Installation Mac Autoenv can be easily installed on macOS with Homebrew. First, ensure you have Homebrew installed. Then, run the following command:\nbrew install autoenv Linux Autoenv can be installed on Linux systems with apt-get. Run the following command:\nsudo apt-get install autoenv Windows Autoenv can be installed on Windows using Git Bash or WSL. You can download Git Bash from the Git website or enable WSL on Windows 10. Once installed, you can follow the Linux installation instructions.\nIII. Usage Examples Once Autoenv is installed, you can use it to manage environment variables for different projects. Let\u0026rsquo;s see some examples.\nExample 1: Setting Environment Variables Suppose you are working on a Python project that requires specific environment variables to be set. You can create a .env file in the project directory with the following contents:\nexport API_KEY=my_api_key export DATABASE_URL=postgres://user:password@localhost/mydatabase Now, when you enter the project directory, Autoenv will automatically set these environment variables. You can access them from your Python code using os.environ.\nExample 2: Running Commands on Directory Entry You can also configure Autoenv to run commands when you enter a directory. For example, suppose you always want to activate a virtual environment when you enter a project directory. You can add the following line to the .env file:\nsource venv/bin/activate Now, when you enter the project directory, Autoenv will automatically activate the virtual environment.\nExample 3: Unsetting Environment Variables When you leave a directory, Autoenv can also unset the environment variables you set. This is useful for cleaning up after yourself and ensuring that environment variables are not leaked into other projects. To do this, add the following line to the .env file:\nunset API_KEY unset DATABASE_URL Now, when you leave the project directory, Autoenv will automatically unset the environment variables.\nIV. Conclusion Autoenv is a powerful tool for managing environment variables and automating repetitive tasks. With it, you can easily set and unset environment variables, run commands on directory entry, and more. By installing Autoenv on your system and using it in your projects, you can save time and streamline your workflow.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-19-using-autoenv-the-ultimate-shortcut-to-environment-management/","summary":"\u003ch1 id=\"i-overview\"\u003eI. Overview\u003c/h1\u003e\n\u003cp\u003eAutoenv is a tool that makes it easy to manage your environment variables. With it, you can automatically set environment variables when you enter a directory, and unset them when you leave. This makes it a powerful tool for managing different environments for different projects, and automating repetitive tasks. In this article, we\u0026rsquo;ll cover how to install Autoenv on different platforms and some usage examples.\u003c/p\u003e\n\u003ch1 id=\"ii-installation\"\u003eII. Installation\u003c/h1\u003e\n\u003ch2 id=\"mac\"\u003eMac\u003c/h2\u003e\n\u003cp\u003eAutoenv can be easily installed on macOS with Homebrew. First, ensure you have Homebrew installed. Then, run the following command:\u003c/p\u003e","title":"Using Autoenv: The Ultimate Shortcut to Environment Management"},{"content":"I. Overview Node Version Manager (NVM) is a useful tool for managing and switching between multiple Node.js versions. In this blog post, we\u0026rsquo;ll cover the most commonly used features of NVM, installation instructions for different platforms, and why it\u0026rsquo;s a valuable tool for developers.\nII. Installation macOS Using Homebrew:\nbrew install nvm mkdir ~/.nvm Add the following lines to your .bash_profile, .zshrc, or other shell configuration file:\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 and other Unix-based systems Using curl:\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash Or using wget:\nwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash This script will clone the NVM repository to ~/.nvm and add the necessary lines to your shell configuration file (.bashrc, .zshrc, etc.).\nIII. Usage 1. Listing available Node.js versions To see the list of available Node.js versions, run:\nnvm ls-remote 2. Installing a specific Node.js version To install a specific version, use the nvm install command followed by the version number:\nnvm install 14.17.0 3. Listing installed Node.js versions To see the list of installed Node.js versions, run:\nnvm ls 4. Switching between Node.js versions To switch to a specific Node.js version, use the nvm use command followed by the version number:\nnvm use 14.17.0 5. Setting a default Node.js version To set a default version for new shell sessions, use the nvm alias command:\nnvm alias default 14.17.0 6. Uninstalling a Node.js version To uninstall a specific Node.js version, use the nvm uninstall command followed by the version number:\nnvm uninstall 14.17.0 7. Installing the latest LTS (Long Term Support) version To install the latest LTS version, run:\nnvm install --lts 8. Updating an installed Node.js version To update an installed version to the latest patch, use the nvm reinstall-packages command:\nnvm install 14.17.0 --reinstall-packages-from=14.16.0 9. Running a script with a specific Node.js version To run a script using a specific Node.js version without switching the active version, use the nvm exec command:\nnvm exec 14.17.0 node script.js 10. Running a script with a specific Node.js version To run a command using a specific Node.js version without switching the active version, use the nvm run command:\nnvm run 14.17.0 --version IV. Conclusion NVM is a powerful tool that allows developers to manage multiple Node.js versions with ease. It enables easy switching between Node.js versions, making it simple to test applications across different environments or work on multiple projects with different Node.js requirements.\nWith the most commonly used features covered in this blog post, you should now be able to install NVM on your system, manage Node.js versions, and use the tool effectively. Happy coding!\n","permalink":"https://hobbyworker.me/en/dev/2023-03-18-managing-multiple-nodejs-environments-with-nvm/","summary":"\u003ch1 id=\"i-overview\"\u003eI. Overview\u003c/h1\u003e\n\u003cp\u003eNode Version Manager (NVM) is a useful tool for managing and switching between multiple Node.js versions. In this blog post, we\u0026rsquo;ll cover the most commonly used features of NVM, installation instructions for different platforms, and why it\u0026rsquo;s a valuable tool for developers.\u003c/p\u003e\n\u003ch1 id=\"ii-installation\"\u003eII. Installation\u003c/h1\u003e\n\u003ch2 id=\"macos\"\u003emacOS\u003c/h2\u003e\n\u003cp\u003eUsing Homebrew:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebrew install nvm\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir ~/.nvm\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAdd the following lines to your \u003ccode\u003e.bash_profile\u003c/code\u003e, \u003ccode\u003e.zshrc\u003c/code\u003e, or other shell configuration file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexport NVM_DIR\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$HOME\u003cspan style=\"color:#e6db74\"\u003e/.nvm\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e -s \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ebrew --prefix\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/opt/nvm/nvm.sh\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e . \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ebrew --prefix\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/opt/nvm/nvm.sh\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# This loads nvm\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e -s \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ebrew --prefix\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/opt/nvm/etc/bash_completion\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e . \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ebrew --prefix\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/opt/nvm/etc/bash_completion\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# This loads nvm bash_completion\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"linux-and-other-unix-based-systems\"\u003eLinux and other Unix-based systems\u003c/h2\u003e\n\u003cp\u003eUsing curl:\u003c/p\u003e","title":"Managing Multiple Node.js Environments with NVM"},{"content":"Java is an immensely popular programming language, and managing multiple Java versions can be a challenging task. That\u0026rsquo;s where jEnv comes in handy. In this blog post, we\u0026rsquo;ll cover jEnv\u0026rsquo;s most used features, including installation instructions for various platforms, and help you manage your Java versions with ease.\nI. Overview jEnv is a command-line tool that enables you to manage multiple Java versions on your system. It allows you to set your desired Java version per project, shell session, or system-wide. By using jEnv, you can avoid conflicts between different projects that require different Java versions and ensure a smoother development experience.\nII. Installation Mac To install jEnv on macOS, you can use the Homebrew package manager. If you haven\u0026rsquo;t installed Homebrew yet, you can find the installation instructions here.\nbrew install jenv After installation, add the following lines to your ~/.bash_profile, ~/.zshrc, or ~/.bashrc file, depending on your shell:\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; Linux To install jEnv on Linux, execute the following commands:\ngit clone https://github.com/jenv/jenv.git ~/.jenv After cloning the repository, add the following lines to your ~/.bashrc or ~/.zshrc file, depending on your shell:\nexport PATH=\u0026#34;$HOME/.jenv/bin:$PATH\u0026#34; eval \u0026#34;$(jenv init -)\u0026#34; III. Usage local The local command sets the Java version for a specific directory. This is useful when working on multiple projects with different Java version requirements.\njenv local 11.0.2 global The global command sets the default Java version for the entire system. This version will be used if no other version is specified by the local or shell commands.\njenv global 11.0.2 shell The shell command sets the Java version for the current shell session. This is useful when you want to temporarily use a different Java version without affecting other projects or sessions.\njenv shell 11.0.2 rehash The rehash command generates shims for all Java executables known to jEnv. This is useful after installing new Java versions or when jEnv doesn\u0026rsquo;t recognize some Java executables.\njenv rehash version The version command displays the currently active Java version.\njenv version versions The versions command lists all installed Java versions and marks the currently active version with an asterisk.\njenv versions which The which command shows the path to the Java executable of the currently active version.\njenv which java whence The whence command lists all the Java versions that contain a specific command.\njenv whence java add The add command registers a new Java version with jEnv. You need to provide the path to the Java installation directory.\njenv add /path/to/java/home IV. Conclusion jEnv is a powerful tool that simplifies Java version management. With features like local, global, and shell, you can easily switch between Java versions on a per-project or per-session basis. The rehash, version, versions, which, whence, and add commands further enhance your ability to manage and organize your Java installations. By using jEnv, you can streamline your development workflow, avoid version conflicts, and ensure that your projects run on the desired Java version. Whether you\u0026rsquo;re working on multiple Java projects or simply want to have greater control over your Java environment, jEnv is an indispensable tool for Java developers.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-17-a-practical-guide-to-using-jenv-for-java-version-management/","summary":"\u003cp\u003eJava is an immensely popular programming language, and managing multiple Java versions can be a challenging task. That\u0026rsquo;s where jEnv comes in handy. In this blog post, we\u0026rsquo;ll cover jEnv\u0026rsquo;s most used features, including installation instructions for various platforms, and help you manage your Java versions with ease.\u003c/p\u003e\n\u003ch1 id=\"i-overview\"\u003eI. Overview\u003c/h1\u003e\n\u003cp\u003ejEnv is a command-line tool that enables you to manage multiple Java versions on your system. It allows you to set your desired Java version per project, shell session, or system-wide. By using jEnv, you can avoid conflicts between different projects that require different Java versions and ensure a smoother development experience.\u003c/p\u003e","title":"A Practical Guide to Using jEnv for Java Version Management"},{"content":"In this blog post, we will discuss how to use pyenv and pyenv-virtualenv, two powerful tools that can help you manage multiple Python versions and virtual environments with ease. We will cover installation instructions for various platforms, such as Mac and Linux, and discuss the most frequently used features of these tools. By the end, you\u0026rsquo;ll have a solid understanding of how to use these tools effectively in your development workflow.\nI. Overview pyenv is a powerful version management tool for Python, allowing you to install and switch between multiple Python versions with ease. pyenv-virtualenv is an extension to pyenv that enables you to manage multiple virtual environments. These tools are particularly useful when working on multiple projects with different dependencies and Python versions.\nII. Installation Mac To install pyenv and pyenv-virtualenv on macOS, you can use Homebrew:\nbrew update brew install pyenv brew install pyenv-virtualenv After installation, add the following lines to your shell configuration file (.bashrc, .zshrc, etc.):\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 To install pyenv and pyenv-virtualenv on Linux, first clone the repositories and add them to your PATH:\ngit clone https://github.com/pyenv/pyenv.git ~/.pyenv git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv Next, add the following lines to your shell configuration file (.bashrc, .zshrc, etc.):\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. Usage 1. Install a Python version To install a specific Python version, use the install command:\npyenv install 3.9.5 2. List available Python versions To see all the installed Python versions, use the versions command:\npyenv versions 3. Set the global Python version To set the global Python version, use the global command:\npyenv global 3.9.5 4. Set the local Python version To set the local Python version for a specific project, use the local command within the project directory:\npyenv local 3.8.10 5. Check the current Python version To check the current Python version, use the version command:\npyenv version 6. Create a virtual environment To create a new virtual environment with pyenv-virtualenv, use the virtualenv command:\npyenv virtualenv 3.9.5 my-project-env 7. Activate a virtual environment To activate a virtual environment, use the activate command:\npyenv activate my-project-env 8. Deactivate a virtual environment To deactivate the current virtual environment, use the deactivate command:\npyenv deactivate 9. List available virtual environments To list all the virtual environments you\u0026rsquo;ve created, use the virtualenvs command:\npyenv virtualenvs 10. Remove a virtual environment To remove a virtual environment, use the uninstall command:\npyenv uninstall my-project-env Bonus: Rehash Whenever you install a new Python package with executable scripts, it\u0026rsquo;s essential to run the rehash command to update the shims, ensuring the new scripts are available:\npyenv rehash IV. Conclusion pyenv and pyenv-virtualenv are invaluable tools for managing multiple Python versions and virtual environments in your development workflow. With the features discussed in this guide, you\u0026rsquo;ll be well-equipped to work on multiple projects with varying dependencies and Python versions. Embrace these tools to maintain a clean and organized development environment, enhancing your productivity and reducing the risk of dependency conflicts.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-16-how-to-use-pyenv-and-pyenv-virtualenv/","summary":"\u003cp\u003eIn this blog post, we will discuss how to use \u003ccode\u003epyenv\u003c/code\u003e and \u003ccode\u003epyenv-virtualenv\u003c/code\u003e, two powerful tools that can help you manage multiple Python versions and virtual environments with ease. We will cover installation instructions for various platforms, such as Mac and Linux, and discuss the most frequently used features of these tools. By the end, you\u0026rsquo;ll have a solid understanding of how to use these tools effectively in your development workflow.\u003c/p\u003e","title":"How to Use Pyenv and Pyenv-Virtualenv"},{"content":"Ruby developers often need to work with multiple projects, each with its own specific version requirements. The need for a versatile and easy-to-use version manager is crucial. In this blog post, we will discuss rbenv, a popular Ruby environment manager that provides an elegant solution to this problem. We\u0026rsquo;ll go over the most-used features, installation instructions for various platforms, and wrap up with a conclusion.\nI. Overview rbenv is a lightweight Ruby version management tool that allows you to switch between different Ruby versions on a per-project basis or globally on your system. With rbenv, you can easily install new Ruby versions, keep them up-to-date, and maintain isolated gem sets for each version.\nSome of the most-used features of rbenv include:\nInstalling Ruby versions Setting the global Ruby version Setting a local (project-specific) Ruby version Listing installed Ruby versions Removing Ruby versions II. Installation Mac To install rbenv on macOS, you can use Homebrew:\nbrew install rbenv After installation, add rbenv to bash so that it loads every time you open a Terminal:\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 To install rbenv on a Linux-based system, follow these steps:\nUpdate your package lists: sudo apt-get update Install dependencies: sudo apt-get install -y build-essential libssl-dev libreadline-dev zlib1g-dev Clone rbenv from its GitHub repository: git clone https://github.com/rbenv/rbenv.git ~/.rbenv Add rbenv to your PATH: echo \u0026#39;export PATH=\u0026#34;$HOME/.rbenv/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc Add rbenv initialization to your shell: echo \u0026#39;eval \u0026#34;$(rbenv init -)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc Restart your shell: exec $SHELL III. Usage 1. Installing Ruby versions To install a specific Ruby version, first install the ruby-build plugin:\nbrew install ruby-build Now you can install the desired Ruby version:\nrbenv install 2.7.0 2. Setting the global Ruby version To set the global Ruby version for your system, use the global command:\nrbenv global 2.7.0 3. Setting a local (project-specific) Ruby version To set a Ruby version for a specific project, navigate to the project directory and use the local command:\ncd /path/to/your/project rbenv local 2.7.0 4. Listing installed Ruby versions To list all installed Ruby versions, use the versions command:\nrbenv versions 5. Removing Ruby versions To remove an installed Ruby version, use the uninstall command:\nrbenv uninstall 2.7.0 IV. Conclusion In summary, rbenv is an indispensable tool for Ruby developers who need to manage multiple Ruby environments. It offers a simple yet powerful way to switch between Ruby versions, manage gemsets, and ensure project-specific dependencies are maintained. With its ease of installation and cross-platform compatibility, rbenv is a must-have for any Rubyist looking to streamline their development process and keep their projects organized. Give rbenv a try, and you\u0026rsquo;ll soon wonder how you ever managed without it.\n","permalink":"https://hobbyworker.me/en/dev/2023-03-15-managing-multiple-ruby-environments-with-rbenv/","summary":"\u003cp\u003eRuby developers often need to work with multiple projects, each with its own specific version requirements. The need for a versatile and easy-to-use version manager is crucial. In this blog post, we will discuss rbenv, a popular Ruby environment manager that provides an elegant solution to this problem. We\u0026rsquo;ll go over the most-used features, installation instructions for various platforms, and wrap up with a conclusion.\u003c/p\u003e\n\u003ch1 id=\"i-overview\"\u003e\u003cstrong\u003eI. Overview\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003erbenv is a lightweight Ruby version management tool that allows you to switch between different Ruby versions on a per-project basis or globally on your system. With rbenv, you can easily install new Ruby versions, keep them up-to-date, and maintain isolated gem sets for each version.\u003c/p\u003e","title":"Managing Multiple Ruby Environments with rbenv"},{"content":"I. Introduction This development report aims to demonstrate how to use the Notion API to automate workout scheduling. Notion is an all-in-one productivity tool that allows users to create and organize databases, notes, tasks, and much more. The Notion API provides developers with programmatic access to Notion\u0026rsquo;s features, enabling the automation of various tasks.\nThe code snippet provided showcases how to use the Notion API to create workout events in a database based on a predefined schedule. The script fetches the existing events within a specific date range and creates new events for the days that do not have any scheduled events. The workout schedule is defined in the days_of_week dictionary, which maps each day of the week to a workout name and a list of tags.\nBy automating the workout scheduling process with Notion API, developers can save time and reduce manual errors. This report will provide a step-by-step guide to help you set up the Notion API and understand the code snippet\u0026rsquo;s various components.\nII. Setting up the Notion API To use the Notion API, you need to have a Notion account and an integration set up. Here\u0026rsquo;s a step-by-step guide to authenticate to the Notion API:\nCreate an integration in Notion\nLog in to Notion and navigate to the Integrations page by clicking on your profile icon on the bottom left corner of the screen and then clicking on \u0026ldquo;Integrations\u0026rdquo;. Click on the \u0026ldquo;Create a new integration\u0026rdquo; button. Give your integration a name and select the workspace where you want to use it. Click on the \u0026ldquo;Submit\u0026rdquo; button to create the integration. Get your integration token\nOnce your integration is created, you can get your integration token by clicking on the integration\u0026rsquo;s name. Copy the integration token to use it in your code. Install the Notion API package\nOpen your terminal or command prompt.\nRun the following command to install the Notion API package:\npip install notion-client Set up the Notion API client\nImport the Notion API client in your code by adding the following line at the top:\nfrom notion_client import Client Authenticate the client by adding the following line and replacing \u0026lt;API Key\u0026gt; with your integration token:\nnotion = Client(auth=\u0026#34;\u0026lt;API Key\u0026gt;\u0026#34;) Set up database details\nObtain the database ID of the database where you want to add events. Replace the database_id variable in the code snippet with your database ID. The code snippet provided uses the notion_client.Client object to interact with the Notion API. The Client object is used to authenticate the API client and provides access to various Notion resources, such as databases and pages.\nThe database_id variable is set to the ID of the Notion database where the workout events will be added. The ID can be found in the database\u0026rsquo;s URL.\nIII. Defining the Workout Schedule The workout schedule is defined in the code snippet using the following variables:\nstart_days_ago: the number of days before today\u0026rsquo;s date to start the schedule end_days_after: the number of days after today\u0026rsquo;s date to end the schedule days_of_week: a dictionary that maps each day of the week to a workout name and a list of tags. The days_of_week dictionary is used to define the workout plan for each day of the week. Each day of the week is a key in the dictionary, and the corresponding value is another dictionary that contains the workout name and a list of tags associated with the workout. For example:\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;]}, } In this example, the schedule is divided into three days, and each day has a specific workout and tags associated with it.\nThe start_days_ago and end_days_after variables are used to define the date range for the workout schedule. start_days_ago specifies how many days before today\u0026rsquo;s date to start the schedule, and end_days_after specifies how many days after today\u0026rsquo;s date to end the schedule.\nThe code snippet uses the datetime module to obtain today\u0026rsquo;s date and calculate the start and end dates for the workout schedule. The start date is obtained by subtracting start_days_ago from today\u0026rsquo;s date, and the end date is obtained by adding end_days_after to today\u0026rsquo;s date. The dates are formatted as strings in the %Y-%m-%d format.\nIV. Querying Existing Events Before creating new workout events, the code snippet fetches the existing events within the defined date range. This is done to avoid creating duplicate events on the same date.\nThe notion.databases.query() method is used to query the Notion database and retrieve the existing events. The filter parameter is used to specify the filter conditions for the query. In this case, the filter is set to retrieve events that fall within the start and end dates defined in the code snippet. Here\u0026rsquo;s an example of the filter used in the query:\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}} ] } This filter returns events that have a Date property that falls between the start_date and end_date.\nThe results attribute of the query response is used to obtain the existing events within the date range. The results are stored in the existing_events variable for later use in the code snippet.\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. Creating New Events Once the existing events within the date range are obtained, the code snippet proceeds to create new workout events for each day within the schedule date range.\nThe code snippet uses a for loop to iterate through each date in the date range. A new event is created for each day that is not a Sunday:\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 The strftime() method is used to obtain the weekday name from the date, and if the weekday name is \u0026ldquo;Sunday\u0026rdquo;, the loop skips that day and moves on to the next day.\nThe code snippet then checks whether an event already exists on the current date:\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 If an event already exists on the current date, the loop skips that day and moves on to the next day.\nIf no event exists on the current date, a new event is created with the workout name and tags associated with that day of the week:\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) The workout name and tags associated with that day of the week are obtained from the days_of_week dictionary. The new_event dictionary is used to define the properties of the new event, which includes the event name, date, and tags. The notion.pages.create() method is used to create the new event in the Notion database.\nBy creating new events for each day of the week within the schedule date range, developers can automate the workout scheduling process and ensure that the workout plan is up-to-date and accurate.\nVI. Conclusion In this project, we have demonstrated how to use the Notion API to automate the process of creating a workout schedule in a Notion database. The code snippet we provided can be used to create a workout schedule for a specific date range and automatically add new events to the database based on the defined workout plan.\nThe Notion API can be used to automate a wide range of tasks, such as managing a to-do list, tracking expenses, or organizing project tasks. With the Notion API, developers can build custom integrations and automate repetitive tasks, saving time and increasing productivity.\nOverall, the Notion API provides a powerful tool for developers to integrate Notion into their workflow and automate their tasks. As Notion continues to grow in popularity, we can expect to see more developers building integrations and automating tasks with the Notion API.\nIn the future, we plan to expand on this project by adding more features and integrations with other tools. We also plan to explore other use cases for the Notion API and see how it can be used to automate other tasks in our workflow.\nSource code : https://github.com/hobbyworker/notion-workout-scheduler\n","permalink":"https://hobbyworker.me/en/dev/2023-03-14-automating-workout-scheduling-with-notion-api/","summary":"\u003ch1 id=\"i-introduction\"\u003e\u003cstrong\u003eI. Introduction\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003eThis development report aims to demonstrate how to use the Notion API to automate workout scheduling. Notion is an all-in-one productivity tool that allows users to create and organize databases, notes, tasks, and much more. The Notion API provides developers with programmatic access to Notion\u0026rsquo;s features, enabling the automation of various tasks.\u003c/p\u003e\n\u003cp\u003eThe code snippet provided showcases how to use the Notion API to create workout events in a database based on a predefined schedule. The script fetches the existing events within a specific date range and creates new events for the days that do not have any scheduled events. The workout schedule is defined in the \u003cstrong\u003e\u003ccode\u003edays_of_week\u003c/code\u003e\u003c/strong\u003e dictionary, which maps each day of the week to a workout name and a list of tags.\u003c/p\u003e","title":"Automating Workout Scheduling with Notion API"},{"content":"Overview Homebrew is a free, open-source package manager that simplifies the installation and management of software on macOS. With Homebrew, you can easily install, update, and remove software packages that are not available in the macOS App Store. It is widely adopted by developers and power users for its simplicity and ease of use.\nInstallation Instructions Before installing Homebrew, ensure that you have the following prerequisites:\nA macOS device with the latest version of Xcode Command Line Tools installed A stable internet connection To install Homebrew, open Terminal on your Mac and run the following command:\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; This command downloads the installation script from the Homebrew repository and executes it. The installation process may take a few minutes to complete. Once it is finished, you can verify that Homebrew is installed by running:\nbrew --version If you see a version number displayed, congratulations! You have successfully installed Homebrew on your macOS device.\nHow to Use Homebrew Homebrew is built around the concept of \u0026ldquo;formulae,\u0026rdquo; which are essentially instructions for installing software packages. These formulae are written in Ruby and stored in Homebrew\u0026rsquo;s official repository, called \u0026ldquo;Homebrew/core.\u0026rdquo;\nHere are some basic Homebrew commands to get you started:\nSearch for a package: Use brew search \u0026lt;package-name\u0026gt; to search for available packages. Replace \u0026lt;package-name\u0026gt; with the name of the package you are looking for. Install a package: Use brew install \u0026lt;package-name\u0026gt; to install the desired package. Uninstall a package: Use brew uninstall \u0026lt;package-name\u0026gt; to remove an installed package. List installed packages: Use brew list to display a list of all installed packages. Update Homebrew: Use brew update to update Homebrew and its formulae to the latest version. Upgrade installed packages: Use brew upgrade to upgrade all installed packages to their latest versions. Example: Installing \u0026lsquo;wget\u0026rsquo; In this example, we will install wget, a widely-used command-line utility for downloading files from the internet. To do this, simply run the following command in Terminal:\nbrew install wget Homebrew will now download and install the wget package. Once the installation is complete, you can use wget by typing wget followed by the desired options and the URL of the file you want to download. For example, to download a sample file, you can run:\nwget https://example.com/sample-file.txt This will download the sample-file.txt from the specified URL and save it to your current working directory.\nConclusion Homebrew is a powerful and easy-to-use package manager that fills a significant gap in macOS\u0026rsquo;s software management capabilities. It simplifies the installation, updating, and removal of open-source software and tools that are not available in the App Store. With a straightforward command-line interface and a vast repository of formulae, Homebrew has become a must-have tool for macOS users, especially developers and power users.\nIn this post, we covered the basics of Homebrew, including how to install it, use it, and a practical example of installing the wget utility. By following the instructions and commands provided, you can now confidently explore and install a wide array of software packages to enhance your macOS experience. Happy brewing!\n","permalink":"https://hobbyworker.me/en/dev/2023-03-13-a-beginners-guide-to-homebrew-the-missing-package-manager-for-macos/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eHomebrew is a free, open-source package manager that simplifies the installation and management of software on macOS. With Homebrew, you can easily install, update, and remove software packages that are not available in the macOS App Store. It is widely adopted by developers and power users for its simplicity and ease of use.\u003c/p\u003e\n\u003ch1 id=\"installation-instructions\"\u003eInstallation Instructions\u003c/h1\u003e\n\u003cp\u003eBefore installing Homebrew, ensure that you have the following prerequisites:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eA macOS device with the latest version of Xcode Command Line Tools installed\u003c/li\u003e\n\u003cli\u003eA stable internet connection\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eTo install Homebrew, open Terminal on your Mac and run the following command:\u003c/p\u003e","title":"A Beginner's Guide to Homebrew: The Package Manager for macOS"},{"content":"","permalink":"https://hobbyworker.me/en/projects/","summary":"","title":"Projects"}]