{
    "title": "How to Launch VS Code Projects via macOS Spotlight",
    "slug": "how-to-launch-vs-code-projects-via-macos-spotlight",
    "excerpt": "I recently traded my Linux setup for a MacBook Pro, but I wasn't willing to give up my custom project launchers. With a little help from Claude, I've ported my original Bash script to macOS so I can open any project instantly using Cmd + Space.",
    "body": "A while ago, I shared a [Bash script for GNOME](https://corenominal.com/blog/posts/gnome-menu-entries-for-visual-studio-code-projects) that generated individual `.desktop` launchers for my VS Code projects. It was a massive productivity win: I could hit the **Super** key, type a project name, and jump straight into the code without digging through directories.\n\nI've since moved over to a new Apple MacBook Pro (*honestly, perhaps the best machine I've ever owned, but that's a post for another time*) and replicating that workflow was my first priority. Since I'm still finding my \"macOS legs,\" I teamed up with **Claude** to port the logic over.\n\nThe goal remains the same, but the implementation is native to macOS. Instead of `.desktop` files, the script now generates lightweight `.app` wrappers inside `~/Applications/ProjectLaunchers`. This allows **Spotlight (Cmd + Space)** to index my projects perfectly, giving me the same \"search-and-launch\" speed I had on Linux.\n\nHere is the script:\n\n```\n#!/usr/bin/env bash\n\nif [ \"$(uname -s)\" != \"Darwin\" ]; then\n    echo \"This script requires macOS.\" >&2\n    exit 1\nfi\n\nPROJECTS_DIR=\"$HOME/Projects\"\nAPP_DIR=\"$HOME/Applications/ProjectLaunchers\"\n\n# Safety check: ensure APP_DIR is set and not root\nif [ -z \"$APP_DIR\" ] || [ \"$APP_DIR\" = \"/\" ]; then\n    echo \"Invalid APP_DIR: '$APP_DIR'\" >&2\n    exit 1\nfi\n\n# If directory exists, empty its contents (keep the directory itself).\nif [ -d \"$APP_DIR\" ]; then\n    echo \"Cleaning existing launchers in: $APP_DIR\"\n    # Remove all children of APP_DIR safely\n    find \"$APP_DIR\" -mindepth 1 -maxdepth 1 -exec rm -rf {} +\nelse\n    mkdir -p \"$APP_DIR\"\nfi\n\nVSCODE_APP=\"/Applications/Visual Studio Code.app\"\nVSCODE_ICON=\"$VSCODE_APP/Contents/Resources/Code.icns\"\n\nif [ ! -d \"$VSCODE_APP\" ]; then\n    echo \"⚠️ Visual Studio Code not found in /Applications. Icons will be generic.\"\n    VSCODE_ICON=\"\"\nfi\n\nfind \"$PROJECTS_DIR\" -type f -name \".projectname\" | while read -r project_file; do\n    project_dir=$(dirname \"$project_file\")\n    project_short_name=$(head -n 1 \"$project_file\" | tr -d '[:space:]')\n    [ -z \"$project_short_name\" ] && continue\n\n    APP_PATH=\"$APP_DIR/$project_short_name.app\"\n\n    # Prefer building a small AppleScript-based app (avoids creating an\n    # Intel-only launcher binary which triggers Rosetta prompts on Apple\n    # Silicon). Fall back to a simple bundle if osacompile isn't present.\n    if command -v osacompile >/dev/null 2>&1; then\n        TMP_AS=\"$TMPDIR/${project_short_name// /_}-$RANDOM.applescript\"\n        cat > \"$TMP_AS\" <<AS\non run\n    tell application \"Visual Studio Code\"\n        open POSIX file \"$project_dir\"\n        activate\n    end tell\nend run\nAS\n        # Compile AppleScript to an app bundle\n        /usr/bin/osacompile -o \"$APP_PATH\" \"$TMP_AS\" >/dev/null 2>&1 || true\n        rm -f \"$TMP_AS\"\n\n        # If compilation failed, fall back to shell-bundle creation below\n        if [ ! -d \"$APP_PATH\" ]; then\n            echo \"Warning: osacompile failed for $project_short_name; falling back to shell launcher.\" >&2\n        else\n            # Copy VS Code icon into the compiled app and update plist\n            if [ -n \"$VSCODE_ICON\" ] && [ -f \"$VSCODE_ICON\" ]; then\n                mkdir -p \"$APP_PATH/Contents/Resources\"\n                cp \"$VSCODE_ICON\" \"$APP_PATH/Contents/Resources/Code.icns\"\n                PLIST=\"$APP_PATH/Contents/Info.plist\"\n                if [ -f \"$PLIST\" ] && command -v /usr/libexec/PlistBuddy >/dev/null 2>&1; then\n                    /usr/libexec/PlistBuddy -c \"Set :CFBundleName $project_short_name\" \"$PLIST\" 2>/dev/null || /usr/libexec/PlistBuddy -c \"Add :CFBundleName string $project_short_name\" \"$PLIST\" 2>/dev/null\n                    /usr/libexec/PlistBuddy -c \"Set :CFBundleDisplayName $project_short_name\" \"$PLIST\" 2>/dev/null || /usr/libexec/PlistBuddy -c \"Add :CFBundleDisplayName string $project_short_name\" \"$PLIST\" 2>/dev/null\n                    ID=\"com.user.$(echo \"$project_short_name\" | tr ' ' '_')\"\n                    /usr/libexec/PlistBuddy -c \"Set :CFBundleIdentifier $ID\" \"$PLIST\" 2>/dev/null || /usr/libexec/PlistBuddy -c \"Add :CFBundleIdentifier string $ID\" \"$PLIST\" 2>/dev/null\n                        /usr/libexec/PlistBuddy -c \"Set :CFBundleIconFile Code\" \"$PLIST\" 2>/dev/null || /usr/libexec/PlistBuddy -c \"Add :CFBundleIconFile string Code\" \"$PLIST\" 2>/dev/null\n                        /usr/libexec/PlistBuddy -c \"Set :CFBundleIconName Code\" \"$PLIST\" 2>/dev/null || /usr/libexec/PlistBuddy -c \"Add :CFBundleIconName string Code\" \"$PLIST\" 2>/dev/null\n                        # Remove the default applet icon if present so the new icon is used\n                        rm -f \"$APP_PATH/Contents/Resources/applet.icns\" \"$APP_PATH/Contents/Resources/applet.rsrc\" 2>/dev/null || true\n                fi\n\n                LSREGISTER=\"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister\"\n                if [ -x \"$LSREGISTER\" ]; then\n                    \"$LSREGISTER\" -f \"$APP_PATH\" >/dev/null 2>&1 || true\n                fi\n                xattr -rd com.apple.quarantine \"$APP_PATH\" 2>/dev/null || true\n            fi\n\n            echo \"Created: $project_short_name.app -> $project_dir\"\n            continue\n        fi\n    fi\n\n    # Fallback: create a minimal bundle that runs a shell script via the system shell.\n    CONTENTS=\"$APP_PATH/Contents\"\n    mkdir -p \"$CONTENTS/MacOS\" \"$CONTENTS/Resources\"\n\n    EXECUTABLE=\"$CONTENTS/MacOS/launcher\"\n    cat > \"$EXECUTABLE\" <<SH\n#!/usr/bin/env bash\nopen -a \"Visual Studio Code\" -- \"$project_dir\"\nSH\n    chmod +x \"$EXECUTABLE\"\n\n    cat > \"$CONTENTS/Info.plist\" <<PLIST\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleName</key>\n    <string>$project_short_name</string>\n    <key>CFBundleDisplayName</key>\n    <string>$project_short_name</string>\n    <key>CFBundleIdentifier</key>\n    <string>com.user.$(echo \"$project_short_name\" | tr ' ' '_')</string>\n    <key>CFBundleVersion</key>\n    <string>1.0</string>\n    <key>CFBundleExecutable</key>\n    <string>launcher</string>\n    <key>CFBundlePackageType</key>\n    <string>APPL</string>\n    <key>CFBundleIconFile</key>\n    <string>Code</string>\n</dict>\n</plist>\nPLIST\n\n    if [ -n \"$VSCODE_ICON\" ] && [ -f \"$VSCODE_ICON\" ]; then\n        cp \"$VSCODE_ICON\" \"$CONTENTS/Resources/Code.icns\"\n    fi\n\n    LSREGISTER=\"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister\"\n    if [ -x \"$LSREGISTER\" ]; then\n        \"$LSREGISTER\" -f \"$APP_PATH\" >/dev/null 2>&1 || true\n    fi\n    xattr -rd com.apple.quarantine \"$APP_PATH\" 2>/dev/null || true\n\n    echo \"Created: $project_short_name.app -> $project_dir\"\ndone\n\necho \"✅ Done! Find your launchers in: $APP_DIR\"\n```",
    "tags": [],
    "published_at": "2026-03-25 10:09:24",
    "url": "https://corenominal.com/blog/posts/how-to-launch-vs-code-projects-via-macos-spotlight",
    "featured_image": "https://corenominal.com/media/og-f237d7f4-ceed-4d3a-a577-8be556974fd2.png"
}