Files
2026-08-19 18:50:26 -04:00

15 KiB

Contributing to MacTools

[中文] [English]

Thanks for your interest in MacTools. Please keep each contribution small and clear: explain the problem, provide verifiable changes, and avoid mixing unrelated refactors into the same pull request.

Ways to Contribute

  • Bug reports should include reproduction steps, expected behavior, actual behavior, macOS version, and relevant logs or screenshots.
  • Feature suggestions should describe the use case, target users, and expected interaction. For large plugins or interaction changes, open an issue first to align on scope.
  • Changes involving file deletion, system permissions, global shortcuts, display control, signing, or update flows should explain risks, safeguards, and rollback options.

Development Environment

  • Xcode and xcodegen are required. The project supports macOS 14.0 and later.
  • First-time setup: run make setup, then edit LocalConfig.xcconfig and fill in DEVELOPMENT_TEAM and a stable, non-placeholder BUNDLE_IDENTIFIER_PREFIX. Debug builds fail early when either value is missing so macOS cannot register a malformed duplicate app identity.
  • Use make run for local app testing. It installs the canonical Debug app at ~/Applications/MacTools Dev.app and unregisters other MacTools Dev build copies from LaunchServices.
  • Common commands: make generate generates the Xcode project, make build validates compilation, and make run installs the verified Debug bundle at ~/Applications/MacTools Dev.app before running it locally.
  • Plugin development: make run incrementally builds the app and plugins, then syncs the latest Debug plugin packages to the local development marketplace. A full sync moves packages absent from the current checkout into the recoverable Debug quarantine; a filtered PLUGIN=... sync leaves unrelated packages untouched. make sync-debug-plugins only syncs already built plugins. make build-plugin is reserved for validating dynamic plugin packages or release flows; to build one plugin, run make build-plugin PLUGIN=calendar.
  • Do not commit local or generated files: MacTools.xcodeproj, MacTools.xcworkspace, LocalConfig.xcconfig, build/, or scripts/release.local.env.

Project Structure

  • Sources/App/: app entry point, menu bar status item, settings pages, and window routing.
  • Sources/Core/: shared infrastructure such as the plugin host, dynamic plugin loading, shortcuts, permissions, logging, and updates.
  • Sources/MacToolsPluginKit/: plugin APIs, declarative UI models, and runtime context.
  • Plugins/<PluginName>/: plugin manifest, source code, bundle entry point, resources, and adjacent tests.
  • Tests/: XCTest coverage for shared App/Core logic. Plugin tests should live inside the corresponding plugin directory when possible.
  • project.yml: root XcodeGen project source, only for the App, PluginKit, and shared aggregate entry points. Plugin targets are generated automatically.
  • Plugins/<PluginName>/project.yml: optional per-plugin build overrides, only when a plugin needs extra frameworks, include paths, bundle resources, helper/tool targets, or target overrides.
  • docs/plugins/: plugin packages, catalogs, local debugging, and release flow documentation.
  • docs/icon-gallery/: checked-in menu-bar icon catalog, previews, animation frames, and archives; rendering-mode rules are documented in docs/icon-gallery.md.
  • docs/superpowers/: larger product, interaction, or implementation design documents.

Development Guidelines

  • Add new plugins under Plugins/<PluginName>/ with at least plugin.json, Sources/, and Bundle/.
  • Ordinary plugins only need to define plugin.json, source code, and a bundle entry point. make generate scans Plugins/*/plugin.json and generates local Configs/GeneratedPlugins.yml; do not edit generated files manually.
  • Features that require macOS app extensions, such as Finder Sync, must add the extension target to the root project.yml and embed it in the main app. Use the dynamic plugin only for the MacTools panel/settings surface.
  • Command workflows for adding and updating plugins are documented in the Development Steps section of docs/plugins/local-native-plugins.md.
  • Keep documentation short and task-focused. User-visible behavior changes should update README.md or the relevant file under docs/; plugin package, catalog, or release flow changes should update docs/plugins/.
  • Keep CHANGELOG.md as the canonical release history. Do not edit Sources/Resources/ReleaseHistory.json by hand; release preparation regenerates it, and python3 scripts/changelog.py export-history repairs it after intentional historical edits.
  • Icon gallery assets must explicitly declare renderingMode; use template only for black artwork on transparency, and use original for color or grayscale detail. Third-party static assets must pin their upstream revision and catalog mapping in docs/icon-gallery/sources/manifest.json, with the corresponding license under Sources/Resources/ThirdPartyNotices/. Run the gallery generation and related tests after catalog changes.
  • Plugins implement MacToolsPlugin; menu panel plugins implement PluginPrimaryPanel, and component panel plugins implement PluginComponentPanel.
  • plugin.json.id must be stable, readable, and exactly match the runtime PluginMetadata.id; each plugin package should return exactly one plugin instance.
  • Plugin display state should be expressed through PluginPanelState, PluginPanelDetail, PluginPanelControl, and related models. Do not bypass the existing panel framework.
  • Prefer PluginSettingsPage.form with declarative sections and typed controls. Use a custom form section for a complex region and PluginSettingsPage.workspace only for task-oriented managers or editors that need the full content area. Permissions, shortcuts, page chrome, search, validation, and backgrounds remain host-owned.
  • Publish executable plugin capabilities through stable PluginActionProviding definitions. Keep action discovery and execution in the host-owned registry/executor, and keep ordinary global bindings in ShortcutAssignmentService; workflows, Run Links, and Action Grid must reference those actions instead of defining parallel command, shortcut, or URL paths. The architecture and automated test matrix are documented in docs/actions-automation.md.
  • A plugin may implement PluginActionExposureProviding to veto a canonical action on a host-owned system surface such as App Intents. Treat .automatic as delegation to the host's conservative eligibility policy, never as an allowlist override. Unknown surfaces and provider failures fail closed, and the executor rechecks the live policy immediately before starting the action. Run Link policy remains a separate contract.
  • When a plugin adopts an action-surface or other newly exported PluginKit type, set its plugin.json.minHostVersion to the first compatible app release. Compatibility metadata belongs in the source manifest. When local plugin validation requires that unreleased host version, MARKETING_VERSION may predeclare it; the app release helper treats a source version ahead of the latest app tag as the default release target and remains responsible for advancing CURRENT_PROJECT_VERSION. Release tooling owns plugin package version bumps.
  • If ordinary plugin resources rarely change, prefer bundling them into the executable. If extra bundle resources are needed, declare the smallest necessary differences in the plugin's own project.yml.
  • Custom plugin settings views must reuse MacToolsPluginKit.PluginSettingsTheme and .pluginSettingsCardBackground(.standard/.recessed). Do not copy private plugin settings styles, and do not make plugins depend on Sources/App/SettingsStyle.swift.
  • Call onStateChange?() after plugin state changes. Long-running scans, file system work, and system calls should not block the main thread for extended periods.
  • User-facing copy is primarily Chinese. Keep it concise, clear, and close to native macOS wording.
  • Localize user-facing copy with .xcstrings. App/Core copy belongs under Sources/Resources/Localization, PluginKit copy under Sources/MacToolsPluginKit/Resources, and plugin copy under Plugins/<PluginName>/Resources. Plugin plugin.json files should keep displayName/summary as fallbacks and add localizedMetadata for marketplace and unloaded-plugin presentation.
  • New plugins should provide localization whenever practical, at minimum for panel copy, settings copy, permission text, and plugin metadata.
  • Prefer Apple native frameworks. When adding system frameworks, private include paths, or helper executables inside a plugin bundle, declare the smallest necessary differences in the plugin's own project.yml. Bundle resource executables that need separate signing should be listed in plugin.json.package.signPaths.
  • Plugins that use private Apple frameworks must load them dynamically at runtime and validate the required classes and selectors. Do not statically link private frameworks, and surface unsupported-system errors instead of crashing.
  • Plugins that intercept pointer events must declare Accessibility permission, stop their event tap on deactivation, and re-enable a tap disabled by macOS.

Testing

  • Behavioral changes should add or update adjacent XCTest coverage. Test files should be named <TypeName>Tests.swift.
  • Full test command: xcodebuild -project MacTools.xcodeproj -scheme MacTools -configuration Debug -derivedDataPath build/DerivedData test -quiet.
  • Single test class: append -only-testing:MacToolsTests/<TestClassName> to the full test command.
  • File system tests should use temporary directories or fake stores. Disk cleanup tests must not delete real user directories.

Pull Request Checklist

  • Keep the PR focused, and explain the purpose, verification, and user impact.
  • Prefer English for commit messages, pull request titles/descriptions, and issues.
  • Build or tests have passed. If they could not be run, explain why in the PR.
  • User-visible behavior changes are reflected in README.md or the relevant design documentation.
  • User-visible app or plugin changes include a concise English changelog fragment in changes/unreleased/*.md.
  • Plugin manifest capabilities.settings (none, form, or workspace) matches the runtime settingsPage layout.
  • High-risk features cover safety checks, error states, and missing-permission cases.
  • The PR does not include unrelated formatting, generated files, local configuration, certificates, or release credentials.

Release

  • Releases are handled by maintainers. Do not create tags, publish GitHub Releases, or commit release artifacts in ordinary contributions.
  • For GitHub-based releases, prefer Actions -> Prepare Release. Enter type, target version, and whether to release; when release is enabled, the workflow continues to the actual release workflow after bumping, committing, and creating the tag.
  • For quick releases, prefer make release. The command interactively chooses app or plugin, analyzes the next patch/minor/major version, previews the bump, then only after confirmation runs git pull --rebase, lightweight checks, version updates, commit, tag creation, and tag push.
  • App releases update MARKETING_VERSION and CURRENT_PROJECT_VERSION in Configs/AppVersion.xcconfig. The app and embedded extensions inherit this shared version config. After pushing a v*.*.* tag, the Release workflow builds, signs, notarizes, uploads the DMG, marks the stable App release as GitHub Latest, and updates the Appcast plus website download metadata.
  • Plugin releases push a plugins-* batch tag. The default auto mode uses the production catalog to find new plugins, already bumped plugins, and package-related plugin changes; it updates plugin.json.version when needed, then the Plugin Release workflow builds plugins and merges the signed catalog. Plugin batch releases are never marked as GitHub Latest. When a batch raises individual plugins' minHostVersion, publish and verify the signed plugin catalog before releasing that host version; the catalog keeps its oldest schema-compatible host floor and older apps leave newer entries unavailable.
  • On first launch, a new app version checks installed plugins and automatically updates them from the signed production catalog. It does not normally install plugins the user has not installed. The only exception is a host-declared feature-extraction migration: when an installed source plugin still owns a legacy preference, the host may runtime-validate the replacement package and update the source as one rollback-protected operation. Manually installing that replacement also coordinates retirement of an older source package, even before the legacy preference has been written.
  • Non-interactive examples: make release ARGS="--type app --version 1.0.7 --yes" or make release ARGS="--type plugin --version 1.0.10 --plugin-mode selected --plugin calendar --yes".
  • Add --dry-run to preview the steps. The working tree must be clean before a real release.
  • Before local release builds, copy scripts/release.local.env.sample to scripts/release.local.env and fill in at least DEVELOPER_ID_APPLICATION.
  • If Apple notarization is needed, store credentials first with xcrun notarytool store-credentials.
  • Version numbers default to MARKETING_VERSION and CURRENT_PROJECT_VERSION in Configs/AppVersion.xcconfig.
  • Local production builds can still use the lower-level script: ./scripts/release-local.sh; before publishing to GitHub Releases, run gh auth login, then ./scripts/release-local.sh --publish.
  • Plugin library releases are triggered by plugins-* batch tags through the Plugin Release workflow. Within one PluginKit ABI line, plugins with bumped versions are built and uploaded, then merged into that line's catalog. Changes under Sources/MacToolsPluginKit/ require rebuilding and bumping every plugin so the catalog cannot retain binaries linked against an older shared framework. The standard make release flow performs these manifest bumps in the release commit; feature PRs should not pre-bump unrelated plugins. The first release of a new ABI also rebuilds every plugin and writes a versioned catalog. MacTools through 1.1.6 keeps reading the immutable PluginKit v4 catalog at docs/plugins/v4/catalog.json; MacTools 1.2 and later use PluginKit v5 at docs/plugins/v5/catalog.json. Publish the v5 plugin batch and catalog first, wait for Pages to serve the committed signed catalog, and only then prepare or publish the 1.2 app. The app release helper and final release workflow fail closed unless that deployed catalog exactly matches the committed catalog and has a valid signature. The catalog private key, Developer ID certificate, and GitHub token must come from CI secrets or local environment variables.
  • GitHub Actions build and release configuration is documented in docs/github-actions.md; plugin catalog, package structure, and batch release flows are documented in docs/plugins/plugin-catalog.md.