{"id":20287600,"url":"https://github.com/rhettbull/applecrate","last_synced_at":"2025-04-11T09:44:49.542Z","repository":{"id":220711416,"uuid":"752396866","full_name":"RhetTbull/applecrate","owner":"RhetTbull","description":"Package your command line tools into a native macOS installer.","archived":false,"fork":false,"pushed_at":"2024-02-21T00:05:11.000Z","size":453,"stargazers_count":39,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-09T23:07:58.402Z","etag":null,"topics":["cli","command-line","installer","installer-script","installers","macos","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RhetTbull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-03T20:05:29.000Z","updated_at":"2025-02-24T18:33:27.000Z","dependencies_parsed_at":"2024-02-21T01:24:54.786Z","dependency_job_id":"efdc6b60-37c4-4733-befa-f9d8e08be838","html_url":"https://github.com/RhetTbull/applecrate","commit_stats":{"total_commits":87,"total_committers":3,"mean_commits":29.0,"dds":0.03448275862068961,"last_synced_commit":"16c357311832732a01f14c6cd1fcec1230065f21"},"previous_names":["rhettbull/applecrate"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RhetTbull%2Fapplecrate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RhetTbull%2Fapplecrate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RhetTbull%2Fapplecrate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RhetTbull%2Fapplecrate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RhetTbull","download_url":"https://codeload.github.com/RhetTbull/applecrate/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248125621,"owners_count":21051770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","installer","installer-script","installers","macos","python"],"created_at":"2024-11-14T14:40:58.315Z","updated_at":"2025-04-11T09:44:49.523Z","avatar_url":"https://github.com/RhetTbull.png","language":"Python","readme":"# AppleCrate\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n\nPackage your command line tools into a native macOS installer.\n\nAppleCrate is a tool for creating native macOS installers for your command line tools. It's useful for creating installers for command line tools written in any language. Tools written in interpreted languages like Python will need to be first processed with a tool like [pyinstaller](https://www.pyinstaller.org/) or [Ofek Lev's](https://github.com/ofek) excellent [PyApp](https://github.com/ofek/pyapp) to create a standalone executable. If you're starting a new Python command line project, I highly recommend using PyApp to create the standalone executable.\n\n## Installation\n\nSee the [latest release](https://github.com/RhetTbull/applecrate/releases/latest) for the AppleCrate installer package for your platform. This installer includes a signed binary for the `applecrate` command line tool built with [PyApp](https://ofek.dev/pyapp/latest/) and packaged with AppleCrate itself into a signed macOS installer package.\n\nNote: I've not yet figured out how to create a universal binary with PyApp so you'll need to download the correct installer package for your platform, e.g. `applecrate-0.1.7-x86_64-installer.pkg` for Intel Macs and `applecrate-0.1.7-arm64-installer.pkg` for Apple Silicon Macs.\n\nThe installer package will add the `applecrate` command line tool to `/usr/local/bin`. You can upgrade to a new version of AppleCrate by running `applecrate update`.\n\nAlternatively, you can install AppleCrate with `pip` or `pipx`:\n\nAppleCrate requires Python 3.9 or later.\n\n```bash\npip install applecrate\n```\n\nor\n\n```bash\npipx install applecrate\n```\n\n## Upgrading\n\nAppleCrate includes a self-update command to upgrade your installation to the latest version of AppleCrate. This works for both the binary installer package and the `pip` or `pipx` installation.\n\n```bash\napplecrate update\n```\n\nIf you are interested in implementing this in your own Python package, see the [implementation](https://github.com/RhetTbull/applecrate/blob/main/applecrate/selfupdate.py).\n\n## Simple Example\n\n```bash\napplecrate build \\\n--app mytool \\\n--version 1.0.0 \\\n--license LICENSE \\\n--install dist/mytool \"/usr/local/bin/{{ app }}-{{ version }}\" \\\n--link \"/usr/local/bin/{{ app }}-{{ version }}\" \"/usr/local/bin/mytool\"\n```\n\nThis will create a native macOS installer for the tool `dist/mytool` which will install it to `/usr/local/bin/mytool-1.0.0`. The installer will create a symlink to the tool at `/usr/local/bin/mytool` and will also create an uninstaller to remove the tool.\n\nYou can also use applecrate from your own Python code to create installers programmatically:\n\n```python\n\"\"\"Create a macOS installer package programmatically.\"\"\"\n\nfrom applecrate import build_installer\n\nif __name__ == \"__main__\":\n    build_installer(\n        app=\"MyApp\",\n        version=\"1.0.0\",\n        license=\"LICENSE\",\n        install=[(\"dist/myapp\", \"/usr/local/bin/myapp\")],\n        output=\"build/{{ app }}-{{ version }}.pkg\",\n        verbose=print,\n    )\n```\n\n![Screenshot](https://github.com/RhetTbull/applecrate/blob/main/screenshot.png?raw=true)\n\n## How It Works\n\nAppleCrate is a Python application that uses the `pkgbuild` and `productbuild` command line tools to create a macOS installer package. AppleCrate does not do anything that you couldn't do yourself with these tools, but it automates the process and provide a simple command line interface. Creating a macOS installer package is a multi-step process that requires the generation of multiple files such as HTML files for the welcome screen, pre and post install scripts, Distribution XML file, etc. AppleCrate takes care of all of this for you but also allows you to customize the installer by providing your own files for these steps.\n\nAppleCrate uses [Jinja2](https://jinja.palletsprojects.com/en/3.0.x/) templates to generate the files required for the installer. This allows you to use template variables in your files or command line parameters to customize the installer. For example, you can use `{{ app }}` and `{{ version }}` in your files to refer to the app name and version you provide on the command line.\n\n## Usage\n\n\u003c!--[[[cog\nfrom applecrate.cli import cli\nfrom click.testing import CliRunner\nrunner = CliRunner()\nresult = runner.invoke(cli, [\"build\", \"--help\"])\nhelp = result.output.replace(\"Usage: cli\", \"Usage: applecrate\")\ncog.out(\n    \"```\\n{}\\n```\".format(help)\n)\n]]] --\u003e\n```\nUsage: applecrate build [OPTIONS]\n\n  applecrate: A Python package for creating macOS installer packages.\n\nOptions:\n  -a, --app TEXT                  App name.\n  -v, --version TEXT              App version.\n  -I, --identifier TEXT           Unique package identifier. The OS X Installer\n                                  recognizes a package as being an upgrade to an\n                                  already-installed package only if the package\n                                  identifiers match, so it is advisable to set a\n                                  meaningful, consistent identifier when you\n                                  build the package. If not set, the identifier\n                                  will be set to 'org.opensource.{{ app }}'.\n  -l, --license FILE              Path to license file. If provided, the\n                                  installer will include a click-through license\n                                  agreement.\n  -w, --welcome FILE              Path to welcome markdown or HTML file\n  -c, --conclusion FILE           Path to conclusion markdown or HTML file\n  -u, --uninstall FILE            Path to uninstall script; if not provided, an\n                                  uninstall script will be created for you. See\n                                  also '--no-uninstall'\n  -U, --no-uninstall              Do not include an uninstall script in the\n                                  package\n  -L, --url NAME URL              Links to additional resources to include in\n                                  conclusion HTML shown after installation. For\n                                  example, the project website or documentation.\n  -b, --banner FILE               Path to optional PNG banner image for\n                                  installer package.\n  -i, --install FILE_OR_DIR DEST  Install FILE_OR_DIR to destination DEST; DEST\n                                  must be an absolute path, for example\n                                  '/usr/local/bin/app'. DEST may include\n                                  template variables {{ app }} and {{ version\n                                  }}. For example: `--install dist/app\n                                  \"/usr/local/bin/{{ app }}-{{ version }}\"` will\n                                  install the file 'dist/app' to\n                                  '/usr/local/bin/app-1.0.0' if --app=app and\n                                  --version=1.0.0.\n  -k, --link SRC TARGET           Create a symbolic link from SRC to TARGET\n                                  after installation. SRC and TARGET must be\n                                  absolute paths and both may include template\n                                  variables {{ app }} and {{ version }}. For\n                                  example: `--link \"/Library/Application\n                                  Support/{{ app }}/{{ version }}/app\"\n                                  \"/usr/local/bin/{{ app }}-{{ version }}\"`\n  -p, --pre-install FILE          Path to pre-install shell script; if not\n                                  provided, a pre-install script will be created\n                                  for you.\n  -P, --post-install FILE         Path to post-install shell script; if not\n                                  provided, a post-install script will be\n                                  created for you. If provided, the installer\n                                  will run this script after other post-install\n                                  actions.\n  -m, --chmod MODE PATH           Change the mode of PATH to MODE after\n                                  installation. PATH must be an absolute path.\n                                  PATH may contain template variables {{ app }}\n                                  and {{ version }}. MODE must be an octal\n                                  number, for example '755'.\n  -s, --sign APPLE_DEVELOPER_CERTIFICATE_ID\n                                  Sign the installer package with a developer\n                                  ID. If APPLE_DEVELOPER_CERTIFICATE_ID starts\n                                  with '$', it will be treated as an environment\n                                  variable and the value of the environment\n                                  variable will be used as the developer ID.\n  -d, --build-dir DIRECTORY       Build directory to use for building the\n                                  installer package. Default is\n                                  build/applecrate/darwin if not provided.\n  -o, --output FILE               Path to save the installer package.\n  --help                          Show this message and exit.\n\n```\n\u003c!--[[[end]]] --\u003e\n\n## Configuration\n\nThe command line tool applecrate can be configured via `pyproject.toml` or `applecrate.toml` in the current working directory or via command line options. The command line arguments will always take precedence over the configuration files. If present, `applecrate.toml` will take precedence over `pyproject.toml`. The configuration file should be in the following format:\n\n`pyproject.toml`:\n\n```toml\n[tool.applecrate]\napp = \"mytool\"\nversion = \"1.0.0\"\nlicense = \"LICENSE\"\ninstall = [\n    [\"dist/mytool\", \"/usr/local/bin/{{ app }}-{{ version }}\"],\n]\nsign = \"$APPLE_DEVELOPER_CERTIFICATE_ID\"\n```\n\n`applecrate.toml`:\n\n```toml\napp = \"mytool\"\nversion = \"1.0.0\"\nlicense = \"LICENSE\"\ninstall = [\n    [\"dist/mytool\", \"/usr/local/bin/{{ app }}-{{ version }}\"],\n]\nsign = \"$APPLE_DEVELOPER_CERTIFICATE_ID\"\n```\n\nAny command line option is a valid key in the configuration file. For example, the `--app` option can be set in the configuration file as `app = \"mytool\"`. Command line options with a dash (`-`) should be converted to underscores (`_`) in the configuration file. For example, the `--pre-install` option should be set in the configuration file as `pre_install = \"scripts/preinstall.sh\"`.\n\nIf the value of `sign` begins with a `$`, as in the example above, it will be treated as an environment variable and the value of the environment variable will be used as the certificate ID.\n\n## Template Variables\n\nDestination paths, the package identifier, the welcome and conclusion HTML files, and the pre and post install scripts can include template variables. The following template variables are available:\n\n- `app`: The name of the app.\n- `version`: The version of the app.\n- `identifier`: The package identifier.\n- `uninstall`: The path to the uninstall shell script.\n- `url`: A list of URLs to include in the installer package.\n- `install`: A list of tuples of source and destination paths to install.\n- `banner`: The path to the banner image.\n- `link`: A list of tuples of source and target paths to create symlinks post-installation.\n- `post_install`: The path to the post-install shell script.\n- `pre_install`: The path to the pre-install shell script.\n- `chmod`: A list of tuples of mode and path to change the mode of files post-installation.\n- `build_dir`: The build directory.\n- `output`: The path to the installer package.\n- `machine`: The machine architecture, for example `x86_64` or `arm64`.\n\nFor example, the default post-install script includes the following to create a symlink:\n\n```bash\n{% if link %}\n# Create links if needed\n{% for source, target in link %}\nln -s \"{{ source }}\" \"{{ target }}\"\n{% endfor %}\n{% endif %}\n```\n\nThe Jinja2 template engine will replace `{{ source }}` and `{{ target }}` with the source and target paths provided on the command line and remove the `if` and `for` blocks.\n\nSee the [Jinja2 template documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/) for more information on how to use template variables.\n\nThe package identifier and path arguments such as the build path and the output file path may also be templates but they will only have access to the `app`, `version`, and `machine` variables. Your scripts and welcome/conclusion files will have access to all of the variables.\n\nSee the [templates](https://github.com/RhetTbull/applecrate/tree/main/applecrate/templates) directory in the AppleCrate source code for examples of how to use these variables in your own scripts or welcome/conclusion files.\n\n## Utilities\n\nAppleCrate includes a few utilities for working with installer packages:\n\n```python\nfrom applecrate.pkgutil import pkg_info, pkg_files, extract_pkg\n```\n\n- `pkg_info` returns a dictionary of information about the package:\n- `pkg_files` returns a list of files in the package.\n- `extract_pkg` extracts the package to a directory.\n\n```pycon\n\u003e\u003e\u003e import os\n\u003e\u003e\u003e from applecrate.pkgutil import pkg_info, pkg_files, extract_pkg\n\u003e\u003e\u003e pkg_info(\"dist/applecrate-0.1.6-x86_64-installer.pkg\")\n{'overwrite-permissions': 'true', 'relocatable': 'false', 'identifier': 'org.rhettbull.applecrate', 'postinstall-action': 'none', 'version': '0.1.6', 'format-version': '2', 'generator-version': 'InstallCmds-830.2 (22G90)', 'auth': 'root'}\n\u003e\u003e\u003e pkg_files(\"dist/applecrate-0.1.6-x86_64-installer.pkg\")\n['Distribution', 'Resources/conclusion.html', 'Resources/welcome.html', 'applecrate.pkg/postinstall', 'applecrate.pkg/Bom', 'applecrate.pkg/custom_preinstall', 'applecrate.pkg/Payload', 'applecrate.pkg/Scripts', 'applecrate.pkg/preinstall', 'applecrate.pkg/PackageInfo', 'applecrate.pkg/usr/local/bin/applecrate', 'applecrate.pkg/Library/Application Support/applecrate/0.1.6/uninstall.sh']\n\u003e\u003e\u003e os.mkdir(\"temp\")\n\u003e\u003e\u003e extract_pkg(\"dist/applecrate-0.1.6-x86_64-installer.pkg\", \"temp\")\n\u003e\u003e\u003e\n```\n\n## To Do\n\n- [X] Add support for signing the installer with a developer certificate\n- [X] Add python API to create installers programmatically\n- [X] Tests\n- [ ] Add `applecrate check` command to check the configuration without building the installer\n- [ ] Documentation (set up mkdocs)\n- [ ] Add support for notarizing the installer\n\n## Credits\n\nHeavily inspired by [macOS Installer Builder](https://github.com/KosalaHerath/macos-installer-builder) by [Kosala Herath](https://github.com/KosalaHerath). AppleCrate is a complete rewrite in Python but borrows many ideas from macOS Installer Builder and is thus licensed under the same Apache License, Version 2.0.\n\n## License\n\nCopyright Rhet Turnbull, 2024. Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this project except in compliance with the License. You may obtain a copy of the License [here](https://github.com/RhetTbull/applecrate/blob/main/LICENSE).\n\n## Contributing\n\nContributions of all kinds are welcome! Please see [CONTRIBUTING.md](https://github.com/RhetTbull/applecrate/blob/main/CONTRIBUTING.md) for more information.\n\n## See Also\n\n[macOS-Pkg-Builder](https://github.com/ripeda/macOS-Pkg-Builder), another Python package for creating macOS installer packages. macOS-Pkg-Builder provides a programmatic interface for creating installer packages and is more flexible than AppleCrate. AppleCrate is designed to be simpler and easier to use for common use cases. I wasn't aware of macOS-Pkg-Builder when I started AppleCrate, but I might have used it to implement AppleCrate had I known about it.\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd align=\"center\" valign=\"top\" width=\"14.28%\"\u003e\u003ca href=\"https://ammarnajjar.github.io\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/7128635?v=4?s=100\" width=\"100px;\" alt=\"Ammar Najjar\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eAmmar Najjar\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/RhetTbull/applecrate/commits?author=ammarnajjar\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhettbull%2Fapplecrate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frhettbull%2Fapplecrate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhettbull%2Fapplecrate/lists"}