From 6415e33d02eaf88001330d328c8bfe8f10eda934 Mon Sep 17 00:00:00 2001 From: vjcharles Date: Thu, 19 Feb 2026 22:41:32 -0500 Subject: [PATCH 1/2] prompt security audit and tighten wildcard settings as recommended --- .claude/settings.local.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e3a77d1..7023359 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,6 @@ { "permissions": { "allow": [ - "Bash(powershell -Command:*)", "Bash(powershell -ExecutionPolicy Bypass -File \".\\\\scripts\\\\validate-webview-member-order.ps1\" -PluginName TestWebView)", "Bash(Get-ChildItem -Path \"R:\\\\_VST_Development_2026\\\\audio-plugin-coder\\\\.claude\\\\templates\\\\webview\" -Recurse -File)", "Bash(Select-Object -ExpandProperty FullName)", @@ -15,7 +14,6 @@ "Bash(powershell.exe -Command \"Copy-Item -Path ''R:\\\\_VST_Development_2026\\\\audio-plugin-coder\\\\build\\\\plugins\\\\nf_gnarly\\\\nf_gnarly_artefacts\\\\Release\\\\VST3\\\\NF Gnarly.vst3'' -Destination ''C:\\\\Program Files\\\\Common Files\\\\VST3\\\\'' -Recurse -Force; Write-Host ''VST3 copied successfully''\")", "Bash(powershell.exe -ExecutionPolicy Bypass -Command \"Set-Location ''R:\\\\_VST_Development_2026\\\\audio-plugin-coder''; cmake --build build --config Release --target nf_gnarly_VST3 2>&1 | Select-String -Pattern ''\\(error|Finished|Creating\\)'' | Select-Object -First 20\")", "Bash(powershell.exe -ExecutionPolicy Bypass -Command \"Set-Location ''R:\\\\_VST_Development_2026\\\\audio-plugin-coder''; cmake --build build --config Release --target nf_gnarly_VST3 2>&1 | Select-String -Pattern ''\\(error|warning|Finished\\)'' | Select-Object -First 10\")", - "Bash(grep:*)", "Bash(New-Item -ItemType Directory -Force -Path \"plugins\\\\CloudWash\\\\.ideas\")", "Bash(Get-ChildItem -Path \".claude\" -Recurse -File)", "Bash(Select-Object FullName)", @@ -40,10 +38,7 @@ "Bash(Select-Object -First 100)", "Bash(Start-Sleep -Seconds 5)", "Bash(findstr:*)", - "Bash(powershell:*)", "Bash(dir scripts:*)", - "Bash(python:*)", - "Bash(node -e:*)", "Bash(ls:*)", "Bash(Get-ChildItem -Path \"R:\\\\_VST_Development_2026\\\\audio-plugin-coder\\\\plugins\\\\CloudWash\" -Recurse -Filter \"*.html\")", "WebFetch(domain:github.com)", @@ -75,6 +70,21 @@ "Bash(git rm:*)", "Bash(Test-Path \"plugins\\\\CloudWash\\\\Org_Code\")", "Bash(git config:*)" + ], + "deny": [ + "Bash(curl*)", + "Bash(wget*)", + "Bash(Invoke-WebRequest*)", + "Bash(Invoke-Expression*)", + "Bash(iex *)", + "Bash(*| bash*)", + "Bash(*| sh*)", + "Bash(*~/.ssh*)", + "Bash(*~/.aws*)", + "Bash(*base64 -d*)", + "Bash(node -e:*)", + "Bash(python -c:*)", + "Bash(python3 -c:*)" ] } } From 35c17264c21c0f37099f4b0e1be717ba5b97af8d Mon Sep 17 00:00:00 2001 From: vjcharles Date: Thu, 19 Feb 2026 23:20:57 -0500 Subject: [PATCH 2/2] macos support first pass --- .claude/rules/agent.md | 33 +- .claude/rules/file-naming-conventions.md | 14 +- .claude/rules/juce-build-protocols.md | 22 +- README.md | 47 ++- notes/PLAN1-macos.md | 107 +++++ notes/PLAN1-results.md | 56 +++ scripts/backup.sh | 75 ++++ scripts/build-and-install.sh | 178 +++++++++ scripts/error-detection.sh | 289 ++++++++++++++ scripts/installer/create-macos-installer.sh | 244 ++++++++++++ scripts/preview-design.sh | 76 ++++ scripts/rollback.sh | 69 ++++ scripts/state-management.sh | 413 ++++++++++++++++++++ scripts/system-check.sh | 123 ++++++ 14 files changed, 1711 insertions(+), 35 deletions(-) create mode 100644 notes/PLAN1-macos.md create mode 100644 notes/PLAN1-results.md create mode 100755 scripts/backup.sh create mode 100755 scripts/build-and-install.sh create mode 100755 scripts/error-detection.sh create mode 100755 scripts/installer/create-macos-installer.sh create mode 100755 scripts/preview-design.sh create mode 100755 scripts/rollback.sh create mode 100755 scripts/state-management.sh create mode 100755 scripts/system-check.sh diff --git a/.claude/rules/agent.md b/.claude/rules/agent.md index de67dfa..c81ee98 100644 --- a/.claude/rules/agent.md +++ b/.claude/rules/agent.md @@ -1,14 +1,22 @@ # APC AGENT (Master Dispatcher) **Role:** You are the Lead Architect of the audio-plugin-coder (APC). -**System:** Windows 11 | VS Code | JUCE 8 | Visage | WebView | CMake. +**System:** Windows 11 / macOS | VS Code | JUCE 8 | Visage | WebView | CMake. ## ⚠️ CRITICAL RULES (ANTI-HALLUCINATION) ### 1. OS & Shell Protocol -* **No Bash/Linux:** NEVER use `mkdir -p`, `rm`, `cp`. -* **PowerShell Only:** Use `New-Item`, `Remove-Item`, `Copy-Item`. -* **Path Separators:** Always use backslashes (`\`) for paths in commands. +Detect the current platform and use the appropriate shell and commands. + +* **Windows (PowerShell):** + * Use `New-Item`, `Remove-Item`, `Copy-Item`. + * Path separators: backslashes (`\`). + * Script extension: `.ps1` +* **macOS (Bash/Zsh):** + * Use `mkdir -p`, `rm`, `cp -R`. + * Path separators: forward slashes (`/`). + * Script extension: `.sh` +* **NEVER mix shells:** Do not run PowerShell commands on macOS or Bash commands on Windows. ### 2. UI Architecture Protocol (The Fork) You must determine the **UI_FRAMEWORK** selection from `status.json` before generating code. @@ -26,10 +34,17 @@ You must determine the **UI_FRAMEWORK** selection from `status.json` before gene ### 3. Build Protocol * **NEVER** run `cmake` manually. + +**Windows:** * **Preview (Visage):** `powershell -ExecutionPolicy Bypass -File .\scripts\preview-design.ps1 -PluginName ` * **Preview (WebView):** Open `plugins/[Name]/Design/index.html` in Edge/Chrome. * **Full Build:** `powershell -ExecutionPolicy Bypass -File .\scripts\build-and-install.ps1 -PluginName ` +**macOS:** +* **Preview (Visage):** `bash scripts/preview-design.sh ` +* **Preview (WebView):** Open `plugins/[Name]/Design/index.html` in Safari/Chrome. +* **Full Build:** `bash scripts/build-and-install.sh ` + ## 🛑 PHASE GATING PROTOCOL (STRICT) **You are strictly forbidden from "rushing ahead."** @@ -38,8 +53,8 @@ You must determine the **UI_FRAMEWORK** selection from `status.json` before gene * **Check Framework:** If `ui_framework` is "visage", do not suggest HTML. * **Use State Management:** Import `scripts/state-management.ps1` and use `Test-PluginState` for validation. 2. **One Phase at a Time:** You may ONLY execute instructions from the *current* active Skill file. -3. **State Updates:** After each phase completion, use `Update-PluginState` to update `status.json`. -4. **Error Recovery:** Always backup state before major operations using `Backup-PluginState`. +3. **State Updates:** After each phase completion, update `status.json` using `Update-PluginState` (Windows) or `update_plugin_state` (macOS). +4. **Error Recovery:** Always backup state before major operations using `Backup-PluginState` (Windows) or `backup_plugin_state` (macOS). 5. **Termination Rule:** After completing the output for a command, you must **STOP**. Do not auto-start the next phase. ## 📂 FILE SYSTEM PROTOCOL @@ -250,8 +265,10 @@ Applying documented solution... * **State:** `ship_complete` ### MAINTENANCE -* **Backup:** `/backup [Name]` -> `powershell -File .\scripts\backup.ps1 ...` -* **Rollback:** `/rollback [Name]` -> `powershell -File .\scripts\rollback.ps1 ...` +* **Backup (Windows):** `/backup [Name]` -> `powershell -File .\scripts\backup.ps1 ...` +* **Backup (macOS):** `/backup [Name]` -> `bash scripts/backup.sh ...` +* **Rollback (Windows):** `/rollback [Name]` -> `powershell -File .\scripts\rollback.ps1 ...` +* **Rollback (macOS):** `/rollback [Name]` -> `bash scripts/rollback.sh ...` --- **DEFAULT BEHAVIOR:** diff --git a/.claude/rules/file-naming-conventions.md b/.claude/rules/file-naming-conventions.md index a35d866..85dd023 100644 --- a/.claude/rules/file-naming-conventions.md +++ b/.claude/rules/file-naming-conventions.md @@ -128,7 +128,8 @@ Design\ 2. **Framework Awareness:** File creation depends on UI framework selection 3. **Version Control:** Use Git for tracking changes, not file versioning in Source\ 4. **State Tracking:** Always update `status.json` after each phase -5. **Path Separators:** Use backslashes (\) in PowerShell commands and Windows paths +5. **Path Separators:** Use backslashes (`\`) on Windows (PowerShell), forward slashes (`/`) on macOS/Linux (Bash) +6. **Script Equivalents:** Every `.ps1` script in `scripts/` has a `.sh` counterpart for macOS/Linux ## 🔗 Cross-Skill References @@ -145,14 +146,15 @@ Design\ - **`..claude\guides\state-management-guide.md`** - State management documentation ### Script Files -- **`scripts\state-management.ps1`** - Core state management PowerShell module +- **`scripts\state-management.ps1`** - Core state management PowerShell module (Windows) +- **`scripts/state-management.sh`** - Core state management Bash module (macOS/Linux) - **`scripts\validate-state-management.ps1`** - State management validation script ### State Operations -- **State Initialization:** `New-PluginState` function -- **State Updates:** `Update-PluginState` function with validation -- **State Validation:** `Test-PluginState` function for prerequisites -- **Error Recovery:** `Backup-PluginState` and `Restore-PluginState` functions +- **State Initialization:** `New-PluginState` (Windows) / `new_plugin_state` (macOS) +- **State Updates:** `Update-PluginState` (Windows) / `update_plugin_state` (macOS) +- **State Validation:** `Test-PluginState` (Windows) / `test_plugin_state` (macOS) +- **Error Recovery:** `Backup-PluginState` / `backup_plugin_state` and `Restore-PluginState` / `restore_plugin_state` ## 🛠️ Validation diff --git a/.claude/rules/juce-build-protocols.md b/.claude/rules/juce-build-protocols.md index e44d7e3..1f8cd2d 100644 --- a/.claude/rules/juce-build-protocols.md +++ b/.claude/rules/juce-build-protocols.md @@ -1,18 +1,24 @@ # JUCE 8 CRITICAL SYSTEM PROTOCOLS -**REQUIRED READING:** strict constraints for Windows 11 & APC Monorepo. +**REQUIRED READING:** strict constraints for Windows 11 / macOS & APC Monorepo. ## 1. ⚠️ GOLDEN BUILD RULES (HIGHEST PRIORITY) ### A. The "One-Script" Rule -- **NEVER** run cmake, msbuild, or cl.exe manually. -- **NEVER** try to copy VST3 files manually. +- **NEVER** run cmake, msbuild, cl.exe, or xcodebuild manually. +- **NEVER** try to copy VST3/AU files manually. - **ALWAYS** use the master script for Building, Installing, and Repairing. -- **COMMAND:** .\scripts\build-and-install.ps1 -PluginName "TailSync" +- **Windows:** `.\scripts\build-and-install.ps1 -PluginName "TailSync"` +- **macOS:** `bash scripts/build-and-install.sh TailSync` ### B. Monorepo & Path Logic -- **Root Context:** All build operations must happen from the Repository Root _nps/). +- **Root Context:** All build operations must happen from the Repository Root. - **Subdirectories:** NEVER run commands inside plugins/[Name]/. -- **Environment:** +- **Environment (Windows):** - OS: **Windows 11** - - Shell: **PowerShell** (Bashrmmkdir -p are FORBIDDEN). - - Create Folders: New-Item -ItemType Directory -Force -Path "..." + - Shell: **PowerShell** + - Create Folders: `New-Item -ItemType Directory -Force -Path "..."` +- **Environment (macOS):** + - OS: **macOS 10.13+** + - Shell: **Bash/Zsh** + - Create Folders: `mkdir -p "..."` + - CMake Generator: **Xcode** (universal binary: x86_64 + arm64) --- ## 2. 📂 FILE STRUCTURE & WEBVIEW ### A. WebView/GUI Architecture diff --git a/README.md b/README.md index afa8228..193435b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![JUCE](https://img.shields.io/badge/JUCE-8.0-blue.svg)](https://juce.com/) -[![Platform](https://img.shields.io/badge/Platform-Windows%2011-0078D4.svg)](https://www.microsoft.com/windows) +[![Platform](https://img.shields.io/badge/Platform-Windows%2011%20%7C%20macOS-0078D4.svg)](https://github.com/Noizefield/audio-plugin-coder) [![Sponsor](https://img.shields.io/badge/Sponsor-Project-pink.svg?style=social&logo=heart)](https://github.com/sponsors/Noizefield) ## About Audio Plugin Coder @@ -49,28 +49,47 @@ Instead of manually juggling DSP architecture, UI frameworks, build systems, sta ### Prerequisites -- Windows 11 or Linux (tested with Mint Linux) (macOS not yet tested) +**Windows:** +- Windows 11 - PowerShell 7+ - Visual Studio 2022 (with C++ development tools) - CMake 3.22+ - Git + +**macOS:** +- macOS 10.13+ (universal binary: Intel + Apple Silicon) +- Xcode (with command line tools) +- CMake 3.22+ +- Git +- jq (`brew install jq`) - for state management scripts + +**Linux:** +- Tested with Mint Linux +- CMake 3.22+, GCC/Clang, Git + +**All platforms:** - **An LLM coding agent** (Claude Code, Antigravity, Kilo, Codex, Cursor) ### Installation 1. **Clone the repository (with submodules):** -```powershell +```bash git clone --recursive https://github.com/Noizefield/audio-plugin-coder.git cd audio-plugin-coder ``` -Or clone normally and run setup: +Or clone normally and run setup (Windows): ```powershell git clone https://github.com/Noizefield/audio-plugin-coder.git cd audio-plugin-coder .\scripts\setup.ps1 ``` +Verify your environment (macOS): +```bash +bash scripts/system-check.sh +``` + ### Bridge Templates (FFGL & Max/MSP) If you are specifically interested in building **FFGL Visual Plugins** or **Max for Live Externals**, use the included One-Click Setup script for Windows: @@ -207,8 +226,11 @@ audio-plugin-coder/ │ ├── Source/ # C++ code │ └── status.json # State tracking ├── scripts/ # Build automation -│ ├── state-management.ps1 -│ └── build-and-install.ps1 +│ ├── build-and-install.ps1 # Windows build script +│ ├── build-and-install.sh # macOS build script +│ ├── state-management.ps1 # Windows state management +│ ├── state-management.sh # macOS state management +│ └── installer/ # Platform-specific installers └── build/ # Compilation artifacts ``` @@ -280,7 +302,7 @@ APC includes an **auto-capture system** that learns from problems: APC works with any LLM-based coding agent that supports: - Custom workflows/slash commands - File system access -- PowerShell execution +- Shell execution (PowerShell on Windows, Bash on macOS/Linux) **Tested with:** - ✅ Claude Code (Anthropic) @@ -290,11 +312,10 @@ APC works with any LLM-based coding agent that supports: ## 🛠️ Technology Stack -- **JUCE 8** - Audio plugin framework -- **CMake** - Build system -- **PowerShell** - Automation scripting - **JUCE 8** - Audio plugin framework (includes DSP, GUI, etc.) -- **WebView2** - Chromium-based web UI +- **CMake** - Build system (Visual Studio on Windows, Xcode on macOS) +- **PowerShell / Bash** - Automation scripting (platform-specific) +- **WebView2 / WKWebView** - Web UI (Windows / macOS) - **YAML** - Knowledge base format - **Markdown** - Documentation and workflows @@ -324,7 +345,7 @@ Comprehensive documentation is available in the [`docs/`](docs/) directory: - [x] Windows support - [x] GitHub Actions CI/CD - [x] Comprehensive documentation -- [ ] macOS local build support +- [x] macOS local build support - [x] Linux local build support - [x] visage (GUI) support (https://github.com/VitalAudio/visage) - [x] FFGL bridge templates (VJ plugins for Resolume, VDMX, etc.) @@ -340,7 +361,7 @@ I am an independent developer pouring hundreds of hours (and significant API cos Developing a framework that works across different AI agents means constantly testing against paid tiers of Claude, Gemini, and others. I often run out of "Plan" usage just testing a single workflow improvement. -If APC saves you time, helps you learn JUCE, or helps you ship a plugin, please consider supporting the development. It helps cover API costs and accelerates macOS support! +If APC saves you time, helps you learn JUCE, or helps you ship a plugin, please consider supporting the development. It helps cover API costs and accelerates new features! ☕ Buy Me a Coffee / Sponsor on GitHub diff --git a/notes/PLAN1-macos.md b/notes/PLAN1-macos.md new file mode 100644 index 0000000..3fe9389 --- /dev/null +++ b/notes/PLAN1-macos.md @@ -0,0 +1,107 @@ +# macOS Support for Audio Plugin Coder + +## Context +APC is a JUCE 8-based audio plugin generator that currently only supports Windows 11 (PowerShell scripts, Visual Studio, Inno Setup). The CMake build system and CI/CD workflows already support macOS, but all operational scripts (build, preview, install, system-check, backup, rollback, installer) are PowerShell-only. This plan adds standalone Bash equivalents for macOS. + +## Scope + +### New Files to Create (8 files) + +1. **`scripts/build-and-install.sh`** — macOS master build script + - CMake configure with `-G Xcode -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13` + - Build VST3, AU, and Standalone targets + - Install VST3 to `~/Library/Audio/Plug-Ins/VST3/` + - Install AU to `~/Library/Audio/Plug-Ins/Components/` + - Install Standalone to `/Applications/` (optional) + - Read `status.json` for framework detection (visage vs webview) + - Skip Windows-specific icon embedding and PluginVal (not available on macOS out of the box) + +2. **`scripts/preview-design.sh`** — macOS GUI preview + - Build Standalone target with Xcode generator + - Launch `.app` bundle via `open` command + - Read `status.json` for framework flags + +3. **`scripts/system-check.sh`** — macOS dependency validation + - Detect macOS version via `sw_vers` + - Check for Xcode (`xcode-select -p`) + - Check for CMake + - Check for JUCE in `_tools/JUCE` + - Check for Python + - Output JSON (matching PowerShell script format) + +4. **`scripts/backup.sh`** — macOS plugin backup + - Port of `backup.ps1`: stage files, exclude build artifacts, zip to `_backups/` + - Uses `zip` command instead of `Compress-Archive` + +5. **`scripts/rollback.sh`** — macOS plugin rollback + - Port of `rollback.ps1`: find backup by version, restore + - Uses `unzip` instead of `Expand-Archive` + +6. **`scripts/installer/create-macos-installer.sh`** — DMG creator + - Create a DMG containing VST3 bundle, AU component, Standalone app + - Use `hdiutil` for DMG creation + - Include a simple background/layout for drag-to-install UX + - Output to `dist/PluginName-Version-macOS.dmg` + +7. **`scripts/state-management.sh`** — Bash port of state management + - Functions: `get_plugin_state`, `update_plugin_state`, `test_plugin_state`, `backup_plugin_state` + - Uses `jq` for JSON manipulation (will check for `jq` availability) + - Sourced by other scripts (`. scripts/state-management.sh`) + +8. **`scripts/error-detection.sh`** — Bash port of error detection + - Functions: `parse_build_errors`, `find_known_issue` + - Pattern matching against known-issues.yaml + - Lighter weight than PowerShell version (no auto-execution of fixes) + +### Files to Modify (3 files) + +9. **`.claude/rules/agent.md`** — Update agent rules + - Change "System" line to include macOS + - Replace absolute "PowerShell Only" / "No Bash" rules with platform-aware rules + - Add macOS build commands alongside Windows commands + - Update Build Protocol section with macOS equivalents + +10. **`.claude/rules/juce-build-protocols.md`** — Update build protocols + - Update Section 1.B to acknowledge both shells + - Clarify macOS commands in Section 3.D (already partially there) + +11. **`.claude/rules/file-naming-conventions.md`** — Minor update + - Note that path separators are `/` on macOS/Linux, `\` on Windows + - Note `.sh` script equivalents exist + +### Files NOT Changed +- `CMakeLists.txt` — already cross-platform +- `templates/CMakeLists.txt.template` — already cross-platform +- Plugin `CMakeLists.txt` files — already cross-platform +- `.github/workflows/` — already have macOS jobs +- Windows `.ps1` scripts — kept as-is, untouched + +## Implementation Order + +1. `scripts/state-management.sh` (dependency for other scripts) +2. `scripts/error-detection.sh` (dependency for build script) +3. `scripts/system-check.sh` (standalone, good first test) +4. `scripts/build-and-install.sh` (core script) +5. `scripts/preview-design.sh` (depends on build infra) +6. `scripts/backup.sh` and `scripts/rollback.sh` (simple ports) +7. `scripts/installer/create-macos-installer.sh` (DMG creation) +8. Update `.claude/rules/` documentation (agent.md, juce-build-protocols.md, file-naming-conventions.md) + +## Key Technical Decisions + +- **Xcode generator** for CMake (matches CI/CD and produces universal binaries) +- **Universal binary** support: `x86_64;arm64` via `CMAKE_OSX_ARCHITECTURES` +- **Deployment target**: macOS 10.13 (matches CI workflow) +- **jq dependency**: Required for JSON manipulation in Bash; `system-check.sh` will verify it's installed (available via `brew install jq`) +- **No PluginVal**: The PowerShell pluginval integration downloads a Windows binary; macOS version exists but we'll skip automated validation for now (can be added later) +- **DMG installer**: Uses `hdiutil` (built into macOS), no third-party tools needed +- **AU plugin format**: macOS builds include AudioUnit in addition to VST3 + +## Verification + +1. Run `bash scripts/system-check.sh` on macOS — should output JSON with platform/tool detection +2. Run `bash scripts/build-and-install.sh PluginName` — should configure, build, and install VST3+AU +3. Run `bash scripts/preview-design.sh PluginName` — should build and launch standalone +4. Run `bash scripts/backup.sh PluginName 1.0` — should create zip in `_backups/` +5. Run `bash scripts/installer/create-macos-installer.sh PluginName 1.0.0` — should produce DMG in `dist/` +6. Verify agent rules are platform-aware and don't block Bash usage on macOS diff --git a/notes/PLAN1-results.md b/notes/PLAN1-results.md new file mode 100644 index 0000000..a1c36ee --- /dev/null +++ b/notes/PLAN1-results.md @@ -0,0 +1,56 @@ +All done. Here's a summary of everything implemented: + + New Files Created (8) + + #: 1 + File: scripts/state-management.sh + Description: Bash port of state management — jq-based JSON manipulation, all functions ported + ──────────────────────────────────────── + #: 2 + File: scripts/error-detection.sh + Description: Error detection with macOS compiler patterns (clang/ld instead of MSVC) + ──────────────────────────────────────── + #: 3 + File: scripts/system-check.sh + Description: macOS dependency validation — checks Xcode, CMake, Python, JUCE, jq; outputs JSON + ──────────────────────────────────────── + #: 4 + File: scripts/build-and-install.sh + Description: Master build script — Xcode generator, universal binary (x86_64+arm64), installs VST3 to ~/Library/Audio/Plug-Ins/VST3/, AU to + ~/Library/Audio/Plug-Ins/Components/ + ──────────────────────────────────────── + #: 5 + File: scripts/preview-design.sh + Description: Builds Standalone and launches .app via open -W + ──────────────────────────────────────── + #: 6 + File: scripts/backup.sh + Description: Clean zip backup using zip, excludes build artifacts + ──────────────────────────────────────── + #: 7 + File: scripts/rollback.sh + Description: Restore from backup using unzip, handles nested folder structure + ──────────────────────────────────────── + #: 8 + File: scripts/installer/create-macos-installer.sh + Description: DMG creator using hdiutil, includes VST3/AU/Standalone + install.command helper + + Files Modified (3) + + ┌──────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────┐ + │ File │ Changes │ + ├──────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤ + │ .claude/rules/agent.md │ Platform-aware OS/shell rules, macOS build commands, dual-platform maintenance commands │ + ├──────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤ + │ .claude/rules/juce-build-protocols.md │ Dual-platform build commands and environment sections │ + ├──────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤ + │ .claude/rules/file-naming-conventions.md │ Platform-aware path separators, .sh script equivalents noted │ + └──────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────┘ + + Key Details + + - All 8 scripts pass bash -n syntax validation + - system-check.sh tested live — outputs valid JSON with platform/tool detection + - All scripts are chmod +x + - CMake uses -G Xcode -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 + - No Windows .ps1 scripts were modified diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..b07f947 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# APC Plugin Backup (macOS/Linux) +# Creates a clean ZIP backup of a plugin, excluding build artifacts. +# +# Usage: bash scripts/backup.sh + +set -euo pipefail + +# --- PARSE ARGUMENTS --- +PLUGIN_NAME="${1:-}" +VERSION="${2:-}" + +if [[ -z "$PLUGIN_NAME" || -z "$VERSION" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_PATH="$(cd "$SCRIPT_DIR/.." && pwd)" +SOURCE_DIR="$ROOT_PATH/plugins/$PLUGIN_NAME" +BACKUP_ROOT="$ROOT_PATH/_backups" + +# Strip 'v' prefix if user typed "v1.0" +CLEAN_VER="${VERSION#v}" +TIMESTAMP="$(date +%Y%m%d_%H%M)" +ZIP_NAME="${PLUGIN_NAME}_v${CLEAN_VER}_${TIMESTAMP}.zip" +TARGET_ZIP="$BACKUP_ROOT/$PLUGIN_NAME/$ZIP_NAME" + +# --- VALIDATION --- +if [[ ! -d "$SOURCE_DIR" ]]; then + echo "Error: Plugin '$PLUGIN_NAME' not found at $SOURCE_DIR" >&2 + exit 1 +fi + +# --- CREATE TEMP STAGING AREA --- +TEMP_DIR="$(mktemp -d)" +STAGE_DIR="$TEMP_DIR/$PLUGIN_NAME" +mkdir -p "$STAGE_DIR" + +echo "--- BACKUP: $PLUGIN_NAME ($VERSION) ---" +echo "Staging files..." + +# --- COPY SOURCE --- +cp -R "$SOURCE_DIR"/* "$STAGE_DIR"/ 2>/dev/null || true +# Also copy dotfiles/dot-directories (like .ideas) +cp -R "$SOURCE_DIR"/.[!.]* "$STAGE_DIR"/ 2>/dev/null || true + +# --- CLEAN STAGING AREA (Remove Artifacts) --- +EXCLUSIONS=( + "build" + "cmake-build-*" + ".vs" + "*.user" + "*.suo" + "*.ncb" + "*.db" + "*.ipch" + ".DS_Store" +) + +for pattern in "${EXCLUSIONS[@]}"; do + find "$STAGE_DIR" -name "$pattern" -exec rm -rf {} + 2>/dev/null || true +done + +# --- ZIP --- +mkdir -p "$BACKUP_ROOT/$PLUGIN_NAME" + +echo "Compressing to: $TARGET_ZIP" +(cd "$TEMP_DIR" && zip -rq "$TARGET_ZIP" "$PLUGIN_NAME") + +# --- CLEANUP --- +rm -rf "$TEMP_DIR" + +echo "SUCCESS! Backup saved to: $TARGET_ZIP" diff --git a/scripts/build-and-install.sh b/scripts/build-and-install.sh new file mode 100755 index 0000000..c2a4386 --- /dev/null +++ b/scripts/build-and-install.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# APC Master Builder (macOS) +# Configures, builds, and installs audio plugins using Xcode generator. +# +# Usage: bash scripts/build-and-install.sh [--no-install] [--skip-tests] + +set -euo pipefail + +# --- PARSE ARGUMENTS --- +PLUGIN_NAME="" +NO_INSTALL=false +SKIP_TESTS=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --no-install) NO_INSTALL=true; shift ;; + --skip-tests) SKIP_TESTS=true; shift ;; + -*) echo "Unknown option: $1" >&2; exit 1 ;; + *) PLUGIN_NAME="$1"; shift ;; + esac +done + +if [[ -z "$PLUGIN_NAME" ]]; then + echo "Usage: $0 [--no-install] [--skip-tests]" >&2 + exit 1 +fi + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_PATH="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$ROOT_PATH/build" +PLUGIN_DIR="$ROOT_PATH/plugins/$PLUGIN_NAME" +STATUS_JSON="$PLUGIN_DIR/status.json" + +# --- IMPORT MODULES --- +# shellcheck source=state-management.sh +. "$SCRIPT_DIR/state-management.sh" +# shellcheck source=error-detection.sh +. "$SCRIPT_DIR/error-detection.sh" + +# --- DETECT FRAMEWORK --- +USE_VISAGE=false +if [[ -f "$STATUS_JSON" ]] && command -v jq &>/dev/null; then + fw="$(jq -r '.ui_framework // "pending"' "$STATUS_JSON" 2>/dev/null || echo "pending")" + if [[ "$fw" == "visage" ]]; then + USE_VISAGE=true + fi +fi + +echo "--- APC BUILDER: $PLUGIN_NAME ---" +if $USE_VISAGE; then + echo "Framework: visage" +fi + +# --- VALIDATE PREREQUISITES --- +if [[ -f "$STATUS_JSON" ]] && command -v jq &>/dev/null; then + current_phase="$(jq -r '.current_phase // ""' "$STATUS_JSON" 2>/dev/null || echo "")" + if [[ "$current_phase" != "code" && "$current_phase" != "ship" && "$current_phase" != "complete" ]] && ! $SKIP_TESTS; then + echo "WARNING: Plugin implementation not marked as complete. Use --skip-tests to override." + fi +fi + +# --- 1. CONFIGURE --- +echo "Configuring build..." +VISAGE_FLAG="" +if $USE_VISAGE; then + VISAGE_FLAG="-DAPC_ENABLE_VISAGE:BOOL=ON" +fi + +CONFIG_OUTPUT="" +CONFIG_OUTPUT=$(cmake -S "$ROOT_PATH" -B "$BUILD_DIR" \ + -G Xcode \ + -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ + --fresh \ + $VISAGE_FLAG 2>&1) || { + echo "ERROR: CMake configuration failed" >&2 + echo "$CONFIG_OUTPUT" >&2 + + if command -v jq &>/dev/null; then + errors="$(parse_build_errors "$CONFIG_OUTPUT")" + error_count="$(echo "$errors" | jq 'length')" + if (( error_count > 0 )); then + known="$(find_known_issue "$errors" 2>/dev/null || true)" + if [[ -n "$known" ]]; then + echo "Known issue detected: $(echo "$known" | jq -r '.title')" + else + new_issue_from_error "$errors" "$CONFIG_OUTPUT" 2>/dev/null || true + fi + fi + fi + exit 1 +} + +# --- 2. BUILD VST3 --- +echo "Compiling VST3..." +VST3_OUTPUT="" +VST3_OUTPUT=$(cmake --build "$BUILD_DIR" --config Release --target "${PLUGIN_NAME}_VST3" 2>&1) || { + echo "ERROR: VST3 build failed" >&2 + echo "$VST3_OUTPUT" >&2 + + if command -v jq &>/dev/null; then + errors="$(parse_build_errors "$VST3_OUTPUT")" + error_count="$(echo "$errors" | jq 'length')" + if (( error_count > 0 )); then + known="$(find_known_issue "$errors" 2>/dev/null || true)" + if [[ -n "$known" ]]; then + echo "Known issue detected: $(echo "$known" | jq -r '.title')" + else + new_issue_from_error "$errors" "$VST3_OUTPUT" 2>/dev/null || true + fi + fi + fi + exit 1 +} + +# --- 3. BUILD AU (AudioUnit) --- +echo "Compiling AudioUnit..." +AU_OUTPUT="" +AU_OUTPUT=$(cmake --build "$BUILD_DIR" --config Release --target "${PLUGIN_NAME}_AU" 2>&1) || { + echo "WARNING: AudioUnit build failed (non-fatal)" >&2 + echo "$AU_OUTPUT" >&2 +} + +# --- 4. BUILD STANDALONE --- +echo "Compiling Standalone..." +STANDALONE_OUTPUT="" +STANDALONE_OUTPUT=$(cmake --build "$BUILD_DIR" --config Release --target "${PLUGIN_NAME}_Standalone" 2>&1) || { + echo "WARNING: Standalone build failed (non-fatal)" >&2 + echo "$STANDALONE_OUTPUT" >&2 +} + +# --- 5. INSTALL --- +if ! $NO_INSTALL; then + echo "Installing plugins..." + + # Find and install VST3 + VST3_BUNDLE="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.vst3" -type d | head -1)" + if [[ -n "$VST3_BUNDLE" ]]; then + VST3_DEST="$HOME/Library/Audio/Plug-Ins/VST3/${PLUGIN_NAME}.vst3" + if [[ -d "$VST3_DEST" ]]; then + rm -rf "$VST3_DEST" + fi + cp -R "$VST3_BUNDLE" "$VST3_DEST" + echo "INSTALLED VST3 to: $VST3_DEST" + else + echo "WARNING: VST3 bundle not found in build output" + fi + + # Find and install AU + AU_BUNDLE="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.component" -type d 2>/dev/null | head -1 || true)" + if [[ -n "$AU_BUNDLE" ]]; then + AU_DEST="$HOME/Library/Audio/Plug-Ins/Components/${PLUGIN_NAME}.component" + if [[ -d "$AU_DEST" ]]; then + rm -rf "$AU_DEST" + fi + cp -R "$AU_BUNDLE" "$AU_DEST" + echo "INSTALLED AU to: $AU_DEST" + fi + + # Report Standalone location + STANDALONE_APP="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.app" -type d 2>/dev/null | head -1 || true)" + if [[ -n "$STANDALONE_APP" ]]; then + echo "STANDALONE built at: $STANDALONE_APP" + echo "Tip: Copy to /Applications/ if desired." + fi +fi + +# --- 6. UPDATE BUILD STATUS --- +if command -v jq &>/dev/null && [[ -f "$STATUS_JSON" ]]; then + timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + update_plugin_state "$PLUGIN_DIR" \ + "validation.build_completed=true" \ + "validation.build_timestamp=$timestamp" \ + 2>/dev/null || true +fi + +echo "Build process complete!" diff --git a/scripts/error-detection.sh b/scripts/error-detection.sh new file mode 100755 index 0000000..b90a468 --- /dev/null +++ b/scripts/error-detection.sh @@ -0,0 +1,289 @@ +#!/usr/bin/env bash +# Error Detection and Handling Module for APC Build Process (macOS/Linux) +# Implements error detection and known issue matching. +# Source this file: . scripts/error-detection.sh + +# --- HELPERS --- + +_check_jq_error() { + if ! command -v jq &>/dev/null; then + echo "WARNING: jq is required for error detection. Install with: brew install jq" >&2 + return 1 + fi +} + +# --- PATH RESOLUTION --- +_ERROR_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_ERROR_REPO_ROOT="$(cd "$_ERROR_SCRIPT_DIR/.." && pwd)" + +# --- ERROR PATTERNS --- + +# CMake error patterns +CMAKE_PATTERNS=( + "CMake Error" + "Could not find" + "Target.*already exists" + "duplicate target" + "undefined reference" + "linking failed" + "compilation failed" +) + +# JUCE/WebView error patterns +JUCE_PATTERNS=( + "WebView2.*not found" + "juce_gui_extra.*not found" + "JUCE_WEB_BROWSER.*undefined" + "WebBrowserComponent.*error" + "Canvas.*not supported" +) + +# Clang/linker error patterns (macOS equivalents of MSVC patterns) +COMPILER_PATTERNS=( + "error:.*" + "fatal error:.*" + "ld: error:" + "Undefined symbols for architecture" + "linker command failed" + "clang: error:" +) + +# --- FUNCTIONS --- + +parse_build_errors() { + # Parse build output for known error patterns + # Usage: parse_build_errors "build output string" + # Output: JSON array of {pattern, line, category} + local output="$1" + local errors="[]" + + while IFS= read -r line; do + [[ -z "$line" ]] && continue + + # Check CMake patterns + for pattern in "${CMAKE_PATTERNS[@]}"; do + if echo "$line" | grep -qE "$pattern"; then + errors=$(echo "$errors" | jq --arg p "$pattern" --arg l "$line" --arg c "cmake" \ + '. += [{"pattern": $p, "line": $l, "category": $c}]') + break + fi + done + + # Check JUCE patterns + for pattern in "${JUCE_PATTERNS[@]}"; do + if echo "$line" | grep -qE "$pattern"; then + errors=$(echo "$errors" | jq --arg p "$pattern" --arg l "$line" --arg c "webview" \ + '. += [{"pattern": $p, "line": $l, "category": $c}]') + break + fi + done + + # Check compiler patterns + for pattern in "${COMPILER_PATTERNS[@]}"; do + if echo "$line" | grep -qE "$pattern"; then + errors=$(echo "$errors" | jq --arg p "$pattern" --arg l "$line" --arg c "build" \ + '. += [{"pattern": $p, "line": $l, "category": $c}]') + break + fi + done + done <<< "$output" + + echo "$errors" +} + +find_known_issue() { + # Search known issues database for matching error + # Usage: find_known_issue + # Output: JSON object {id, title, solution, resolution_file} or empty string + _check_jq_error || return 1 + local errors_json="$1" + + # Try multiple paths for known issues + local known_issues_path="" + local candidates=( + "$_ERROR_REPO_ROOT/.claude/troubleshooting/known-issues.yaml" + "$_ERROR_REPO_ROOT/.kilocode/troubleshooting/known-issues.yaml" + ) + for path in "${candidates[@]}"; do + if [[ -f "$path" ]]; then + known_issues_path="$path" + break + fi + done + + if [[ -z "$known_issues_path" ]]; then + echo "WARNING: Known issues database not found" >&2 + return 1 + fi + + local yaml_content + yaml_content="$(cat "$known_issues_path")" + + # Extract error lines from JSON + local error_lines + error_lines="$(echo "$errors_json" | jq -r '.[].line')" + + # Simple YAML pattern matching (no YAML parser needed) + local current_id="" current_title="" current_resolution_file="" + local in_patterns=false + + while IFS= read -r line; do + # Detect issue ID + if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*id:[[:space:]]*(.*) ]]; then + current_id="${BASH_REMATCH[1]}" + current_title="" + current_resolution_file="" + in_patterns=false + fi + + # Detect title + if [[ "$line" =~ ^[[:space:]]*title:[[:space:]]*(.*) ]]; then + current_title="${BASH_REMATCH[1]}" + current_title="${current_title//\"/}" # strip quotes + fi + + # Detect resolution file + if [[ "$line" =~ ^[[:space:]]*resolution_file:[[:space:]]*(.*) ]]; then + current_resolution_file="${BASH_REMATCH[1]}" + fi + + # Enter error_patterns section + if [[ "$line" =~ ^[[:space:]]*error_patterns: ]]; then + in_patterns=true + continue + fi + + # Exit patterns section on next key + if $in_patterns && [[ "$line" =~ ^[[:space:]]*[a-z_]+: ]] && [[ ! "$line" =~ ^[[:space:]]*- ]]; then + in_patterns=false + fi + + # Match patterns + if $in_patterns && [[ "$line" =~ ^[[:space:]]*-[[:space:]]*(.*) ]]; then + local pattern="${BASH_REMATCH[1]}" + pattern="${pattern//\"/}" # strip quotes + + # Check if any error line matches this pattern + while IFS= read -r error_line; do + if echo "$error_line" | grep -qF "$pattern"; then + # Found a match - read solution if available + local solution="" + if [[ -n "$current_resolution_file" ]]; then + local res_dir + res_dir="$(dirname "$known_issues_path")" + local res_path="$res_dir/$current_resolution_file" + if [[ -f "$res_path" ]]; then + solution="$(cat "$res_path")" + fi + fi + + # Output match as JSON + jq -n --arg id "$current_id" \ + --arg title "$current_title" \ + --arg solution "$solution" \ + --arg res_file "$current_resolution_file" \ + '{id: $id, title: $title, solution: $solution, resolution_file: $res_file}' + return 0 + fi + done <<< "$error_lines" + fi + done <<< "$yaml_content" + + # No match found + return 1 +} + +new_issue_from_error() { + # Auto-capture a new issue from build errors + # Usage: new_issue_from_error + _check_jq_error || return 1 + local errors_json="$1" + local build_output="$2" + + local category + category="$(echo "$errors_json" | jq -r '.[0].category // "build"')" + + # Find known issues dir + local issues_dir="" + local candidates=( + "$_ERROR_REPO_ROOT/.claude/troubleshooting" + "$_ERROR_REPO_ROOT/.kilocode/troubleshooting" + ) + for path in "${candidates[@]}"; do + if [[ -d "$path" ]]; then + issues_dir="$path" + break + fi + done + + if [[ -z "$issues_dir" ]]; then + echo "WARNING: Troubleshooting directory not found" >&2 + return 1 + fi + + # Generate issue ID + local existing_count=0 + if [[ -d "$issues_dir/resolutions" ]]; then + existing_count="$(ls "$issues_dir/resolutions/"*.md 2>/dev/null | wc -l | tr -d ' ')" + fi + local new_id="${category}-$(printf '%03d' $((existing_count + 1)))" + + # Get error summary + local error_summary + error_summary="$(echo "$errors_json" | jq -r '.[0].line // "Unknown error"' | head -c 100)" + + local today + today="$(date +%Y-%m-%d)" + + # Build YAML entry + local new_issue + new_issue="- id: $new_id + title: \"[Auto] $error_summary\" + category: $category + severity: high + symptoms:" + + # Add first 5 error lines as symptoms + local symptoms + symptoms="$(echo "$errors_json" | jq -r '.[0:5][].line')" + while IFS= read -r line; do + [[ -n "$line" ]] && new_issue="$new_issue + - \"$line\"" + done <<< "$symptoms" + + new_issue="$new_issue + error_patterns:" + + # Add first 3 patterns + local patterns + patterns="$(echo "$errors_json" | jq -r '.[0:3][].pattern')" + while IFS= read -r line; do + [[ -n "$line" ]] && new_issue="$new_issue + - \"$line\"" + done <<< "$patterns" + + new_issue="$new_issue + resolution_status: investigating + resolution_file: resolutions/$new_id.md + frequency: 1 + last_occurred: $today + attempts_before_resolution: 1" + + # Append to known-issues.yaml + echo "" >> "$issues_dir/known-issues.yaml" + echo "$new_issue" >> "$issues_dir/known-issues.yaml" + + # Create resolution document from template if available + local template_path="$issues_dir/_template.md" + if [[ -f "$template_path" ]]; then + mkdir -p "$issues_dir/resolutions" + local res_file="$issues_dir/resolutions/$new_id.md" + sed -e "s/\[auto-generated-id\]/$new_id/g" \ + -e "s/\[Issue Title\]/[Auto] $error_summary/g" \ + -e "s/\[date\]/$(date '+%Y-%m-%d %H:%M')/g" \ + "$template_path" > "$res_file" + fi + + echo "Issue logged as $new_id" + echo "See: $issues_dir/resolutions/$new_id.md" +} diff --git a/scripts/installer/create-macos-installer.sh b/scripts/installer/create-macos-installer.sh new file mode 100755 index 0000000..b26d325 --- /dev/null +++ b/scripts/installer/create-macos-installer.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# APC macOS Installer Creator +# Creates a DMG containing VST3, AU, and Standalone builds. +# +# Usage: bash scripts/installer/create-macos-installer.sh [CompanyName] + +set -euo pipefail + +# --- PARSE ARGUMENTS --- +PLUGIN_NAME="${1:-}" +VERSION="${2:-}" +COMPANY_NAME="${3:-APC}" + +if [[ -z "$PLUGIN_NAME" || -z "$VERSION" ]]; then + echo "Usage: $0 [CompanyName]" >&2 + exit 1 +fi + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_PATH="$(cd "$SCRIPT_DIR/../.." && pwd)" +BUILD_DIR="$ROOT_PATH/build" +DIST_DIR="$ROOT_PATH/dist" +PLUGIN_DIR="$ROOT_PATH/plugins/$PLUGIN_NAME" + +DMG_NAME="${PLUGIN_NAME}-${VERSION}-macOS.dmg" +DMG_PATH="$DIST_DIR/$DMG_NAME" + +echo "========================================" +echo " Creating macOS Installer" +echo " Plugin: $PLUGIN_NAME" +echo " Version: $VERSION" +echo "========================================" +echo "" + +# --- CHECK PREREQUISITES --- + +# Check for build artifacts +VST3_BUNDLE="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.vst3" -type d 2>/dev/null | head -1 || true)" +AU_BUNDLE="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.component" -type d 2>/dev/null | head -1 || true)" +STANDALONE_APP="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.app" -type d 2>/dev/null | head -1 || true)" + +if [[ -z "$VST3_BUNDLE" ]]; then + echo "ERROR: VST3 build not found. Please build the plugin first." >&2 + echo "Run: bash scripts/build-and-install.sh $PLUGIN_NAME" >&2 + exit 1 +fi + +echo "[OK] Build artifacts found" +echo " VST3: $VST3_BUNDLE" +[[ -n "$AU_BUNDLE" ]] && echo " AU: $AU_BUNDLE" +[[ -n "$STANDALONE_APP" ]] && echo " Standalone: $STANDALONE_APP" + +# --- CREATE LICENSE FILE --- + +LICENSE_PATH="$DIST_DIR/LICENSE.txt" +if [[ ! -f "$LICENSE_PATH" ]]; then + echo "Creating license file..." + mkdir -p "$DIST_DIR" + CURRENT_YEAR="$(date +%Y)" + cat > "$LICENSE_PATH" << EOLICENSE +================================================================================ + $PLUGIN_NAME END USER LICENSE AGREEMENT +================================================================================ + +IMPORTANT: PLEASE READ THIS LICENSE CAREFULLY BEFORE USING THIS SOFTWARE. + +1. GRANT OF LICENSE + This software is licensed, not sold. By installing or using this software, + you agree to be bound by the terms of this agreement. + +2. PERMITTED USE + - You may install and use this software on multiple computers + - You may use this software for commercial and non-commercial purposes + - You may create and distribute audio content using this software + +3. RESTRICTIONS + - You may not reverse engineer, decompile, or disassemble this software + - You may not redistribute or resell this software + - You may not remove or alter any copyright notices + +4. DISCLAIMER OF WARRANTY + THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND. + +5. LIMITATION OF LIABILITY + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DAMAGES ARISING FROM + THE USE OF THIS SOFTWARE. + +================================================================================ +By installing this software, you acknowledge that you have read, understood, +and agree to be bound by these terms. + +Copyright (c) $CURRENT_YEAR $COMPANY_NAME +================================================================================ +EOLICENSE + echo "License file created: $LICENSE_PATH" +fi + +# --- CREATE DMG STAGING AREA --- + +echo "Preparing DMG contents..." +STAGING_DIR="$(mktemp -d)" +DMG_CONTENTS="$STAGING_DIR/$PLUGIN_NAME" +mkdir -p "$DMG_CONTENTS" + +# Copy VST3 +if [[ -n "$VST3_BUNDLE" ]]; then + mkdir -p "$DMG_CONTENTS/VST3" + cp -R "$VST3_BUNDLE" "$DMG_CONTENTS/VST3/" + echo " Added VST3" +fi + +# Copy AU +if [[ -n "$AU_BUNDLE" ]]; then + mkdir -p "$DMG_CONTENTS/AU" + cp -R "$AU_BUNDLE" "$DMG_CONTENTS/AU/" + echo " Added AudioUnit" +fi + +# Copy Standalone +if [[ -n "$STANDALONE_APP" ]]; then + cp -R "$STANDALONE_APP" "$DMG_CONTENTS/" + echo " Added Standalone" +fi + +# Copy License +cp "$LICENSE_PATH" "$DMG_CONTENTS/LICENSE.txt" + +# Copy Documentation if it exists +if [[ -d "$PLUGIN_DIR/Documentation" ]]; then + cp -R "$PLUGIN_DIR/Documentation" "$DMG_CONTENTS/Documentation" + echo " Added Documentation" +fi + +# Create README for installation +cat > "$DMG_CONTENTS/INSTALL.txt" << EOINSTALL +$PLUGIN_NAME v$VERSION - Installation Guide +================================================================================ + +AUTOMATIC INSTALLATION: + Run the included install.command file (double-click it). + +MANUAL INSTALLATION: + + VST3 Plugin: + Copy "$PLUGIN_NAME.vst3" from the VST3 folder to: + ~/Library/Audio/Plug-Ins/VST3/ + + AudioUnit Plugin: + Copy "$PLUGIN_NAME.component" from the AU folder to: + ~/Library/Audio/Plug-Ins/Components/ + + Standalone Application: + Drag "$PLUGIN_NAME.app" to your Applications folder. + +After installation, restart your DAW to detect the new plugin. + +================================================================================ +Created by $COMPANY_NAME +EOINSTALL + +# Create install.command helper script +cat > "$DMG_CONTENTS/install.command" << 'EOINSTALLSCRIPT' +#!/usr/bin/env bash +# Installer helper for +EOINSTALLSCRIPT + +cat >> "$DMG_CONTENTS/install.command" << EOINSTALLSCRIPT +# $PLUGIN_NAME + +set -e +SCRIPT_DIR="\$(cd "\$(dirname "\$0")" && pwd)" + +echo "Installing $PLUGIN_NAME..." + +# Install VST3 +if [[ -d "\$SCRIPT_DIR/VST3/${PLUGIN_NAME}.vst3" ]]; then + VST3_DIR="\$HOME/Library/Audio/Plug-Ins/VST3" + mkdir -p "\$VST3_DIR" + rm -rf "\$VST3_DIR/${PLUGIN_NAME}.vst3" + cp -R "\$SCRIPT_DIR/VST3/${PLUGIN_NAME}.vst3" "\$VST3_DIR/" + echo " Installed VST3 to \$VST3_DIR/" +fi + +# Install AU +if [[ -d "\$SCRIPT_DIR/AU/${PLUGIN_NAME}.component" ]]; then + AU_DIR="\$HOME/Library/Audio/Plug-Ins/Components" + mkdir -p "\$AU_DIR" + rm -rf "\$AU_DIR/${PLUGIN_NAME}.component" + cp -R "\$SCRIPT_DIR/AU/${PLUGIN_NAME}.component" "\$AU_DIR/" + echo " Installed AU to \$AU_DIR/" +fi + +# Install Standalone +if [[ -d "\$SCRIPT_DIR/${PLUGIN_NAME}.app" ]]; then + cp -R "\$SCRIPT_DIR/${PLUGIN_NAME}.app" "/Applications/" 2>/dev/null || { + echo " Could not copy to /Applications (permission denied). Copying to ~/Applications instead." + mkdir -p "\$HOME/Applications" + cp -R "\$SCRIPT_DIR/${PLUGIN_NAME}.app" "\$HOME/Applications/" + } + echo " Installed Standalone" +fi + +echo "" +echo "Installation complete! Restart your DAW to detect the plugin." +echo "Press any key to close..." +read -n 1 +EOINSTALLSCRIPT + +chmod +x "$DMG_CONTENTS/install.command" + +# --- CREATE DMG --- + +echo "Creating DMG..." +mkdir -p "$DIST_DIR" + +# Remove existing DMG if present +rm -f "$DMG_PATH" + +# Create DMG using hdiutil +hdiutil create \ + -volname "$PLUGIN_NAME $VERSION" \ + -srcfolder "$DMG_CONTENTS" \ + -ov \ + -format UDZO \ + "$DMG_PATH" + +# --- CLEANUP --- +rm -rf "$STAGING_DIR" + +# --- VERIFY OUTPUT --- +if [[ -f "$DMG_PATH" ]]; then + FILE_SIZE="$(du -h "$DMG_PATH" | cut -f1)" + echo "" + echo "========================================" + echo " Installer Created Successfully!" + echo "========================================" + echo " File: $DMG_PATH" + echo " Size: $FILE_SIZE" + echo "========================================" +else + echo "ERROR: DMG file not found at expected location: $DMG_PATH" >&2 + exit 1 +fi diff --git a/scripts/preview-design.sh b/scripts/preview-design.sh new file mode 100755 index 0000000..f3d4ed4 --- /dev/null +++ b/scripts/preview-design.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# APC GUI Preview (macOS) +# Builds and launches the Standalone target for instant GUI preview. +# +# Usage: bash scripts/preview-design.sh + +set -euo pipefail + +# --- PARSE ARGUMENTS --- +PLUGIN_NAME="${1:-}" +if [[ -z "$PLUGIN_NAME" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_PATH="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$ROOT_PATH/build" +PLUGIN_DIR="$ROOT_PATH/plugins/$PLUGIN_NAME" +STATUS_JSON="$PLUGIN_DIR/status.json" + +# --- DETECT FRAMEWORK --- +USE_VISAGE=false +FRAMEWORK_NAME="webview" +if [[ -f "$STATUS_JSON" ]] && command -v jq &>/dev/null; then + fw="$(jq -r '.ui_framework // "pending"' "$STATUS_JSON" 2>/dev/null || echo "pending")" + if [[ "$fw" == "visage" ]]; then + USE_VISAGE=true + FRAMEWORK_NAME="visage" + fi +fi + +echo "--- APC PREVIEW: $PLUGIN_NAME ---" +echo "Framework: $FRAMEWORK_NAME" + +# --- 1. CONFIGURE --- +echo "Configuring..." +VISAGE_FLAG="" +if $USE_VISAGE; then + VISAGE_FLAG="-DAPC_ENABLE_VISAGE:BOOL=ON" +fi + +cmake -S "$ROOT_PATH" -B "$BUILD_DIR" \ + -G Xcode \ + -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ + --fresh \ + $VISAGE_FLAG + +# Verify Visage flag in cache if applicable +if $USE_VISAGE && [[ -f "$BUILD_DIR/CMakeCache.txt" ]]; then + if ! grep -q "APC_ENABLE_VISAGE:BOOL=ON" "$BUILD_DIR/CMakeCache.txt"; then + echo "ERROR: APC_ENABLE_VISAGE is OFF in CMakeCache.txt. Reconfigure with -DAPC_ENABLE_VISAGE=ON." >&2 + exit 1 + fi +fi + +# --- 2. BUILD STANDALONE --- +echo "Compiling Standalone..." +cmake --build "$BUILD_DIR" --config Release --target "${PLUGIN_NAME}_Standalone" + +# --- 3. LAUNCH --- +STANDALONE_APP="$(find "$BUILD_DIR" -name "${PLUGIN_NAME}.app" -type d 2>/dev/null | head -1 || true)" + +if [[ -n "$STANDALONE_APP" ]]; then + echo "Launching..." + open -W "$STANDALONE_APP" + EXIT_CODE=$? + if [[ $EXIT_CODE -ne 0 ]]; then + echo "WARNING: CRASH DETECTED (exit code $EXIT_CODE). Check Console.app for crash logs." + fi +else + echo "ERROR: Standalone .app not found." >&2 + exit 1 +fi diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100755 index 0000000..9d53ecc --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# APC Plugin Rollback (macOS/Linux) +# Restores a plugin from a ZIP backup. +# +# Usage: bash scripts/rollback.sh + +set -euo pipefail + +# --- PARSE ARGUMENTS --- +PLUGIN_NAME="${1:-}" +VERSION="${2:-}" + +if [[ -z "$PLUGIN_NAME" || -z "$VERSION" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_PATH="$(cd "$SCRIPT_DIR/.." && pwd)" +PLUGIN_DIR="$ROOT_PATH/plugins/$PLUGIN_NAME" +BACKUP_ROOT="$ROOT_PATH/_backups/$PLUGIN_NAME" + +# Strip 'v' prefix +CLEAN_VER="${VERSION#v}" + +echo "--- ROLLBACK: $PLUGIN_NAME to v$CLEAN_VER ---" + +# --- FIND BACKUP ZIP --- +if [[ ! -d "$BACKUP_ROOT" ]]; then + echo "Error: No backups found for $PLUGIN_NAME" >&2 + exit 1 +fi + +# Find zips matching the version, sorted newest first +TARGET_ZIP="$(ls -t "$BACKUP_ROOT"/*v${CLEAN_VER}*.zip 2>/dev/null | head -1)" + +if [[ -z "$TARGET_ZIP" ]]; then + echo "Error: Could not find a backup zip for version $CLEAN_VER in $BACKUP_ROOT" >&2 + exit 1 +fi + +echo "Found Backup: $(basename "$TARGET_ZIP")" + +# --- PERFORM ROLLBACK --- + +# Remove current folder if it exists +if [[ -d "$PLUGIN_DIR" ]]; then + echo "Clearing current source..." + rm -rf "$PLUGIN_DIR" +fi + +# Re-create empty folder +mkdir -p "$PLUGIN_DIR" + +# Unzip +echo "Restoring from Zip..." +unzip -qo "$TARGET_ZIP" -d "$PLUGIN_DIR" + +# Fix: If the zip created a nested folder (PluginName/PluginName/Source/...), +# move contents up one level +if [[ -d "$PLUGIN_DIR/$PLUGIN_NAME/Source" ]]; then + echo "Adjusting folder structure..." + mv "$PLUGIN_DIR/$PLUGIN_NAME"/* "$PLUGIN_DIR"/ 2>/dev/null || true + mv "$PLUGIN_DIR/$PLUGIN_NAME"/.[!.]* "$PLUGIN_DIR"/ 2>/dev/null || true + rmdir "$PLUGIN_DIR/$PLUGIN_NAME" 2>/dev/null || true +fi + +echo "SUCCESS! $PLUGIN_NAME rolled back to v$CLEAN_VER" diff --git a/scripts/state-management.sh b/scripts/state-management.sh new file mode 100755 index 0000000..d9d9678 --- /dev/null +++ b/scripts/state-management.sh @@ -0,0 +1,413 @@ +#!/usr/bin/env bash +# APC State Management Module (macOS/Linux) +# Provides standardized state management for APC plugin development workflow. +# Source this file: . scripts/state-management.sh + +set -euo pipefail + +# --- PATH RESOLUTION --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# --- SCHEMA CONSTANTS --- +STATE_PHASES=("ideation" "plan" "design" "code" "ship" "complete") +STATE_FRAMEWORKS=("visage" "webview" "pending") +STATE_REQUIRED_FIELDS=("plugin_name" "version" "current_phase" "ui_framework" "complexity_score" "created_at" "last_modified" "phase_history" "validation" "framework_selection" "error_recovery") +STATE_VALIDATION_FIELDS=("creative_brief_exists" "parameter_spec_exists" "architecture_defined" "ui_framework_selected" "design_complete" "code_complete" "tests_passed" "ship_ready") + +# --- HELPERS --- + +_check_jq() { + if ! command -v jq &>/dev/null; then + echo "ERROR: jq is required for state management. Install with: brew install jq" >&2 + return 1 + fi +} + +_iso_date() { + date -u +"%Y-%m-%dT%H:%M:%SZ" +} + +_array_contains() { + local needle="$1"; shift + for item in "$@"; do + [[ "$item" == "$needle" ]] && return 0 + done + return 1 +} + +_phase_index() { + local phase="$1" + for i in "${!STATE_PHASES[@]}"; do + [[ "${STATE_PHASES[$i]}" == "$phase" ]] && echo "$i" && return 0 + done + echo "-1" +} + +# --- FUNCTIONS --- + +new_plugin_state() { + # Initialize a new plugin state from template + # Usage: new_plugin_state + _check_jq || return 1 + local plugin_name="$1" + local plugin_path="$2" + + # Try multiple locations for the template + local template_path="" + local candidates=( + "$REPO_ROOT/.kilocode/templates/status-template.json" + "$REPO_ROOT/templates/status-template.json" + "$SCRIPT_DIR/../templates/status-template.json" + ) + for path in "${candidates[@]}"; do + if [[ -f "$path" ]]; then + template_path="$path" + break + fi + done + + if [[ -z "$template_path" ]]; then + echo "WARNING: Status template not found." >&2 + return 1 + fi + + local now + now="$(_iso_date)" + local status_path="$plugin_path/status.json" + + jq --arg name "$plugin_name" --arg now "$now" \ + '.plugin_name = $name | .created_at = $now | .last_modified = $now' \ + "$template_path" > "$status_path" + + echo "Initialized state for $plugin_name" +} + +get_plugin_state() { + # Read and return plugin state JSON + # Usage: get_plugin_state + _check_jq || return 1 + local plugin_path="$1" + local status_path="$plugin_path/status.json" + + if [[ ! -f "$status_path" ]]; then + return 1 + fi + + cat "$status_path" +} + +get_state_field() { + # Get a specific field from state using jq query + # Usage: get_state_field + # Example: get_state_field "plugins/MyPlugin" ".ui_framework" + _check_jq || return 1 + local plugin_path="$1" + local query="$2" + local status_path="$plugin_path/status.json" + + if [[ ! -f "$status_path" ]]; then + return 1 + fi + + jq -r "$query" "$status_path" +} + +update_plugin_state() { + # Update plugin state with key=value pairs, optional phase and framework + # Usage: update_plugin_state [--phase ] [--framework ] [key=value ...] + _check_jq || return 1 + local plugin_path="$1"; shift + local status_path="$plugin_path/status.json" + local phase="" + local framework="" + local -a updates=() + + if [[ ! -f "$status_path" ]]; then + echo "WARNING: Status file not found at $status_path" >&2 + return 1 + fi + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + --phase) phase="$2"; shift 2 ;; + --framework) framework="$2"; shift 2 ;; + *=*) updates+=("$1"); shift ;; + *) shift ;; + esac + done + + local jq_filter="." + local now + now="$(_iso_date)" + + # Apply key=value updates (supports dot notation like "validation.build_completed=true") + for update in "${updates[@]}"; do + local key="${update%%=*}" + local value="${update#*=}" + + # Convert dot notation to jq path + local jq_path + jq_path="$(echo "$key" | sed 's/\./"."/g')" + jq_path=".\"$jq_path\"" + + # Detect value type + if [[ "$value" == "true" || "$value" == "false" ]]; then + jq_filter="$jq_filter | $jq_path = $value" + elif [[ "$value" =~ ^[0-9]+$ ]]; then + jq_filter="$jq_filter | $jq_path = $value" + elif [[ "$value" == "null" ]]; then + jq_filter="$jq_filter | $jq_path = null" + else + jq_filter="$jq_filter | $jq_path = \"$value\"" + fi + done + + # Update phase if specified + if [[ -n "$phase" ]]; then + if ! _array_contains "$phase" "${STATE_PHASES[@]}"; then + echo "WARNING: Invalid phase: $phase" >&2 + return 1 + fi + local fw_selected + fw_selected="${framework:-$(jq -r '.ui_framework' "$status_path")}" + jq_filter="$jq_filter | .current_phase = \"$phase\" | .last_modified = \"$now\"" + jq_filter="$jq_filter | .phase_history += [{\"phase\": \"$phase\", \"completed_at\": \"$now\", \"framework_selected\": \"$fw_selected\"}]" + fi + + # Update framework if specified + if [[ -n "$framework" ]]; then + if ! _array_contains "$framework" "${STATE_FRAMEWORKS[@]}"; then + echo "WARNING: Invalid framework: $framework" >&2 + return 1 + fi + jq_filter="$jq_filter | .ui_framework = \"$framework\" | .framework_selection.decision = \"$framework\" | .last_modified = \"$now\"" + fi + + # Apply updates + local tmp_file="${status_path}.tmp" + if jq "$jq_filter" "$status_path" > "$tmp_file"; then + # Validate before saving + if test_state_schema "$tmp_file"; then + mv "$tmp_file" "$status_path" + echo "Updated state${phase:+: $phase}${framework:+ ($framework)}" + return 0 + else + rm -f "$tmp_file" + echo "WARNING: State validation failed during update." >&2 + return 1 + fi + else + rm -f "$tmp_file" + echo "WARNING: jq filter failed." >&2 + return 1 + fi +} + +test_plugin_state() { + # Validate plugin state prerequisites + # Usage: test_plugin_state [--required-phase ] [--required-files ...] + _check_jq || return 1 + local plugin_path="$1"; shift + local required_phase="" + local -a required_files=() + + while [[ $# -gt 0 ]]; do + case "$1" in + --required-phase) required_phase="$2"; shift 2 ;; + --required-files) shift; while [[ $# -gt 0 && "$1" != --* ]]; do required_files+=("$1"); shift; done ;; + *) shift ;; + esac + done + + local status_path="$plugin_path/status.json" + if [[ ! -f "$status_path" ]]; then + echo "WARNING: Status file not found" >&2 + return 1 + fi + + # Schema validation + if ! test_state_schema "$status_path"; then + echo "WARNING: State schema validation failed" >&2 + return 1 + fi + + # Phase prerequisite check + if [[ -n "$required_phase" ]]; then + local current_phase + current_phase="$(jq -r '.current_phase' "$status_path")" + local required_idx current_idx + required_idx="$(_phase_index "$required_phase")" + current_idx="$(_phase_index "$current_phase")" + + if (( current_idx < required_idx )); then + echo "WARNING: Cannot proceed: Current phase '$current_phase' must complete '$required_phase' first" >&2 + return 1 + fi + fi + + # Required files check + for file in "${required_files[@]}"; do + if [[ ! -f "$plugin_path/$file" ]]; then + echo "WARNING: Required file missing: $file" >&2 + return 1 + fi + done + + return 0 +} + +test_state_schema() { + # Validate state JSON against schema + # Usage: test_state_schema + _check_jq || return 1 + local file="$1" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + # Check required fields exist + for field in "${STATE_REQUIRED_FIELDS[@]}"; do + if ! jq -e "has(\"$field\")" "$file" &>/dev/null; then + echo "WARNING: Missing required field: $field" >&2 + return 1 + fi + done + + # Check phase validity + local phase + phase="$(jq -r '.current_phase' "$file")" + if ! _array_contains "$phase" "${STATE_PHASES[@]}"; then + echo "WARNING: Invalid phase: $phase" >&2 + return 1 + fi + + # Check framework validity + local fw + fw="$(jq -r '.ui_framework' "$file")" + if ! _array_contains "$fw" "${STATE_FRAMEWORKS[@]}"; then + echo "WARNING: Invalid framework: $fw" >&2 + return 1 + fi + + # Check validation fields + for field in "${STATE_VALIDATION_FIELDS[@]}"; do + if ! jq -e ".validation | has(\"$field\")" "$file" &>/dev/null; then + echo "WARNING: Missing validation field: $field" >&2 + return 1 + fi + done + + return 0 +} + +backup_plugin_state() { + # Create a backup of status.json + # Usage: backup_plugin_state + _check_jq || return 1 + local plugin_path="$1" + local status_path="$plugin_path/status.json" + + if [[ ! -f "$status_path" ]]; then + return 1 + fi + + local backup_dir="$plugin_path/_state_backups" + mkdir -p "$backup_dir" + + local timestamp + timestamp="$(date +%Y%m%d_%H%M%S)" + local backup_file="$backup_dir/status_backup_${timestamp}.json" + + cp "$status_path" "$backup_file" + echo "State backed up to $backup_file" + + # Update error recovery info in current state + local tmp_file="${status_path}.tmp" + jq --arg bf "$backup_file" \ + '.error_recovery.last_backup = $bf | .error_recovery.rollback_available = true' \ + "$status_path" > "$tmp_file" && mv "$tmp_file" "$status_path" + + echo "$backup_file" +} + +restore_plugin_state() { + # Restore status.json from a backup + # Usage: restore_plugin_state [backup_file] + _check_jq || return 1 + local plugin_path="$1" + local backup_file="${2:-}" + local status_path="$plugin_path/status.json" + local backup_dir="$plugin_path/_state_backups" + + if [[ ! -d "$backup_dir" ]]; then + echo "WARNING: No backup directory found" >&2 + return 1 + fi + + if [[ -n "$backup_file" && -f "$backup_file" ]]; then + local source="$backup_file" + else + # Get latest backup + local source + source="$(ls -t "$backup_dir"/status_backup_*.json 2>/dev/null | head -1)" + if [[ -z "$source" ]]; then + echo "WARNING: No backup files found" >&2 + return 1 + fi + fi + + echo "Restoring state from $source" + cp "$source" "$status_path" + + # Update error recovery info + local now + now="$(_iso_date)" + local tmp_file="${status_path}.tmp" + jq --arg msg "Rollback performed from $source at $now" \ + '.error_recovery.rollback_available = false | .error_recovery.error_log += [$msg]' \ + "$status_path" > "$tmp_file" && mv "$tmp_file" "$status_path" + + echo "State restored" +} + +add_state_error() { + # Append an error message to error_recovery.error_log + # Usage: add_state_error + _check_jq || return 1 + local plugin_path="$1" + local error_message="$2" + local status_path="$plugin_path/status.json" + + if [[ ! -f "$status_path" ]]; then + return 1 + fi + + local now + now="$(_iso_date)" + local tmp_file="${status_path}.tmp" + jq --arg msg "$now: $error_message" \ + '.error_recovery.error_log += [$msg]' \ + "$status_path" > "$tmp_file" && mv "$tmp_file" "$status_path" +} + +set_plugin_framework() { + # Set the UI framework for a plugin + # Usage: set_plugin_framework + _check_jq || return 1 + local plugin_path="$1" + local framework="$2" + local rationale="$3" + + if [[ "$framework" != "visage" && "$framework" != "webview" ]]; then + echo "ERROR: Framework must be 'visage' or 'webview'" >&2 + return 1 + fi + + update_plugin_state "$plugin_path" \ + --framework "$framework" \ + "framework_selection.rationale=$rationale" \ + "framework_selection.implementation_strategy=single-pass" +} diff --git a/scripts/system-check.sh b/scripts/system-check.sh new file mode 100755 index 0000000..a523363 --- /dev/null +++ b/scripts/system-check.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# APC System Check (macOS) +# Validates all dependencies required for building audio plugins. +# Usage: bash scripts/system-check.sh [--check-all] + +set -euo pipefail + +# --- HELPERS --- + +version_gte() { + # Returns 0 if $1 >= $2 (semantic version comparison) + local v1="$1" v2="$2" + if [[ "$(printf '%s\n' "$v2" "$v1" | sort -V | head -n1)" == "$v2" ]]; then + return 0 + fi + return 1 +} + +json_bool() { + if [[ "$1" == "true" ]]; then echo "true"; else echo "false"; fi +} + +# --- CHECK FUNCTIONS --- + +check_platform() { + local platform="macos" + local ver="" + if command -v sw_vers &>/dev/null; then + ver="$(sw_vers -productVersion 2>/dev/null || echo "unknown")" + fi + echo "{\"platform\":\"$platform\",\"version\":\"$ver\"}" +} + +check_xcode() { + if xcode-select -p &>/dev/null; then + local xcode_path + xcode_path="$(xcode-select -p)" + # Get Xcode version if available + local ver="unknown" + if command -v xcodebuild &>/dev/null; then + ver="$(xcodebuild -version 2>/dev/null | head -1 | sed 's/Xcode //' || echo "unknown")" + fi + echo "{\"found\":true,\"version\":\"$ver\",\"path\":\"$xcode_path\",\"ok\":true}" + else + echo "{\"found\":false,\"error\":\"Xcode command line tools not installed. Run: xcode-select --install\"}" + fi +} + +check_cmake() { + local min_ver="3.22" + if command -v cmake &>/dev/null; then + local ver + ver="$(cmake --version 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "0.0.0")" + local ok="false" + if version_gte "$ver" "$min_ver"; then + ok="true" + fi + echo "{\"found\":true,\"version\":\"$ver\",\"ok\":$ok}" + else + echo "{\"found\":false}" + fi +} + +check_python() { + local min_ver="3.8" + local py_cmd="" + + if command -v python3 &>/dev/null; then + py_cmd="python3" + elif command -v python &>/dev/null; then + py_cmd="python" + fi + + if [[ -n "$py_cmd" ]]; then + local out + out="$($py_cmd --version 2>&1 || echo "")" + local ver + ver="$(echo "$out" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "0.0.0")" + local ok="false" + if version_gte "$ver" "$min_ver"; then + ok="true" + fi + echo "{\"found\":true,\"version\":\"$ver\",\"ok\":$ok}" + else + echo "{\"found\":false}" + fi +} + +check_juce() { + local path="./_tools/JUCE" + if [[ -f "$path/modules/juce_core/juce_core.h" ]]; then + echo "{\"found\":true,\"path\":\"$path\",\"ok\":true}" + else + echo "{\"found\":false,\"path\":\"$path\"}" + fi +} + +check_jq() { + if command -v jq &>/dev/null; then + local ver + ver="$(jq --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' || echo "unknown")" + echo "{\"found\":true,\"version\":\"$ver\",\"ok\":true}" + else + echo "{\"found\":false,\"error\":\"Install with: brew install jq\"}" + fi +} + +check_all() { + echo "{" + echo " \"platform\": $(check_platform)," + echo " \"xcode\": $(check_xcode)," + echo " \"cmake\": $(check_cmake)," + echo " \"python\": $(check_python)," + echo " \"juce\": $(check_juce)," + echo " \"jq\": $(check_jq)" + echo "}" +} + +# --- MAIN --- +case "${1:---check-all}" in + --check-all) check_all ;; + *) check_all ;; +esac