diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..822ec88
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,50 @@
+name: build
+
+on:
+ pull_request:
+ branches: [ main ]
+ push:
+ branches: [ main ]
+ schedule:
+ - cron: '0 9 * * *'
+ workflow_dispatch:
+
+jobs:
+
+ macos-build:
+
+ runs-on: inseven-macos-14
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+
+ - name: Install dependencies
+ run: scripts/install-dependencies.sh
+
+ - name: Build and test
+ env:
+ APPLE_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.PERSONAL_APPLE_DISTRIBUTION_CERTIFICATE_BASE64 }}
+ APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.PERSONAL_APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD }}
+ MACOS_DEVELOPER_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.PERSONAL_MACOS_DEVELOPER_INSTALLER_CERTIFICATE_BASE64 }}
+ MACOS_DEVELOPER_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.PERSONAL_MACOS_DEVELOPER_INSTALLER_CERTIFICATE_PASSWORD }}
+
+ APPLE_API_KEY_BASE64: ${{ secrets.PERSONAL_APPLE_API_KEY_BASE64 }}
+ APPLE_API_KEY_ISSUER_ID: ${{ secrets.PERSONAL_APPLE_API_KEY_ISSUER_ID }}
+ APPLE_API_KEY_ID: ${{ secrets.PERSONAL_APPLE_API_KEY_ID }}
+
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ RELEASE: ${{ github.ref == 'refs/heads/main' }}
+
+ run: |
+ scripts/build.sh
+
+ - name: Archive the binary
+ uses: actions/upload-artifact@v4
+ with:
+ path: build/build-*.zip
+ if-no-files-found: error
diff --git a/.gitmodules b/.gitmodules
index e6acfdd..e4a644c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,9 @@
[submodule "interact"]
path = interact
url = git@github.com:inseven/interact.git
+[submodule "scripts/changes"]
+ path = scripts/changes
+ url = https://github.com/jbmorley/changes.git
+[submodule "scripts/build-tools"]
+ path = scripts/build-tools
+ url = https://github.com/jbmorley/build-tools.git
diff --git a/ExportOptions.plist b/ExportOptions.plist
new file mode 100644
index 0000000..e724516
--- /dev/null
+++ b/ExportOptions.plist
@@ -0,0 +1,27 @@
+
+
+
+
+ destination
+ export
+ installerSigningCertificate
+ C44BC9291462AE6729496E2CAC18CE475688F8CD
+ manageAppVersionAndBuildNumber
+
+ method
+ app-store
+ provisioningProfiles
+
+ uk.co.jbmorley.thoughts.apps.appstore
+ Thoughts Mac App Store Profile
+
+ signingCertificate
+ 91DADFE184A1526FA94D4513D5B4C75E1DB3B252
+ signingStyle
+ manual
+ teamID
+ QS82QFHKWB
+ uploadSymbols
+
+
+
diff --git a/Thoughts.xcodeproj/project.pbxproj b/Thoughts.xcodeproj/project.pbxproj
index 077b429..1b7a1f2 100644
--- a/Thoughts.xcodeproj/project.pbxproj
+++ b/Thoughts.xcodeproj/project.pbxproj
@@ -512,6 +512,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Thoughts/Info.plist;
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
@@ -532,15 +533,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Thoughts/Thoughts.entitlements;
- CODE_SIGN_STYLE = Automatic;
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
+ CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Thoughts/Preview Content\"";
- DEVELOPMENT_TEAM = QS82QFHKWB;
+ DEVELOPMENT_TEAM = "";
+ "DEVELOPMENT_TEAM[sdk=macosx*]" = QS82QFHKWB;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Thoughts/Info.plist;
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
@@ -550,6 +554,8 @@
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = uk.co.jbmorley.thoughts.apps.appstore;
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Thoughts Mac App Store Profile";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
diff --git a/Thoughts/Info.plist b/Thoughts/Info.plist
index 57dbbb8..a80edd5 100644
--- a/Thoughts/Info.plist
+++ b/Thoughts/Info.plist
@@ -15,5 +15,7 @@
+ ITSAppUsesNonExemptEncryption
+
diff --git a/Thoughts_Mac_App_Store_Profile.provisionprofile b/Thoughts_Mac_App_Store_Profile.provisionprofile
new file mode 100644
index 0000000..ac1cbba
Binary files /dev/null and b/Thoughts_Mac_App_Store_Profile.provisionprofile differ
diff --git a/scripts/build-tools b/scripts/build-tools
new file mode 160000
index 0000000..c0f02d8
--- /dev/null
+++ b/scripts/build-tools
@@ -0,0 +1 @@
+Subproject commit c0f02d8da114357ce680a632355f295327565db5
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..22018e1
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,173 @@
+#!/bin/bash
+
+# Copyright (c) 2024 Jason Morley
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+set -e
+set -o pipefail
+set -x
+set -u
+
+SCRIPTS_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+
+ROOT_DIRECTORY="${SCRIPTS_DIRECTORY}/.."
+BUILD_DIRECTORY="${ROOT_DIRECTORY}/build"
+TEMPORARY_DIRECTORY="${ROOT_DIRECTORY}/temp"
+
+KEYCHAIN_PATH="${TEMPORARY_DIRECTORY}/temporary.keychain"
+ARCHIVE_PATH="${BUILD_DIRECTORY}/Thoughts.xcarchive"
+ENV_PATH="${ROOT_DIRECTORY}/.env"
+
+RELEASE_SCRIPT_PATH="${SCRIPTS_DIRECTORY}/release.sh"
+
+IOS_XCODE_PATH=${IOS_XCODE_PATH:-/Applications/Xcode.app}
+MACOS_XCODE_PATH=${MACOS_XCODE_PATH:-/Applications/Xcode.app}
+
+source "${SCRIPTS_DIRECTORY}/environment.sh"
+
+# Check that the GitHub command is available on the path.
+which gh || (echo "GitHub cli (gh) not available on the path." && exit 1)
+
+# Process the command line arguments.
+POSITIONAL=()
+RELEASE=${RELEASE:-false}
+while [[ $# -gt 0 ]]
+do
+ key="$1"
+ case $key in
+ -r|--release)
+ RELEASE=true
+ shift
+ ;;
+ *)
+ POSITIONAL+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# Generate a random string to secure the local keychain.
+export TEMPORARY_KEYCHAIN_PASSWORD=`openssl rand -base64 14`
+
+# Source the .env file if it exists to make local development easier.
+if [ -f "$ENV_PATH" ] ; then
+ echo "Sourcing .env..."
+ source "$ENV_PATH"
+fi
+
+function xcode_project {
+ xcodebuild \
+ -project Thoughts.xcodeproj "$@"
+}
+
+function build_scheme {
+ # Disable code signing for the build server.
+ xcode_project \
+ -scheme "$1" \
+ CODE_SIGN_IDENTITY="" \
+ CODE_SIGNING_REQUIRED=NO \
+ CODE_SIGNING_ALLOWED=NO "${@:2}"
+}
+
+cd "$ROOT_DIRECTORY"
+
+# Select the correct Xcode.
+sudo xcode-select --switch "$MACOS_XCODE_PATH"
+
+# List the available schemes.
+xcode_project -list
+
+# Clean up the build directory.
+if [ -d "$BUILD_DIRECTORY" ] ; then
+ rm -r "$BUILD_DIRECTORY"
+fi
+mkdir -p "$BUILD_DIRECTORY"
+
+# Create the a new keychain.
+if [ -d "$TEMPORARY_DIRECTORY" ] ; then
+ rm -rf "$TEMPORARY_DIRECTORY"
+fi
+mkdir -p "$TEMPORARY_DIRECTORY"
+echo "$TEMPORARY_KEYCHAIN_PASSWORD" | build-tools create-keychain "$KEYCHAIN_PATH" --password
+
+function cleanup {
+
+ # Cleanup the temporary files, keychain and keys.
+ cd "$ROOT_DIRECTORY"
+ build-tools delete-keychain "$KEYCHAIN_PATH"
+ rm -rf "$TEMPORARY_DIRECTORY"
+ rm -rf ~/.appstoreconnect/private_keys
+}
+
+trap cleanup EXIT
+
+# Determine the version and build number.
+VERSION_NUMBER=`changes version`
+BUILD_NUMBER=`build-number.swift`
+
+# Import the certificates into our dedicated keychain.
+echo "$APPLE_DISTRIBUTION_CERTIFICATE_PASSWORD" | build-tools import-base64-certificate --password "$KEYCHAIN_PATH" "$APPLE_DISTRIBUTION_CERTIFICATE_BASE64"
+echo "$MACOS_DEVELOPER_INSTALLER_CERTIFICATE_PASSWORD" | build-tools import-base64-certificate --password "$KEYCHAIN_PATH" "$MACOS_DEVELOPER_INSTALLER_CERTIFICATE_BASE64"
+
+# Install the provisioning profiles.
+build-tools install-provisioning-profile "Thoughts_Mac_App_Store_Profile.provisionprofile"
+
+# Build and archive the macOS project.
+sudo xcode-select --switch "$MACOS_XCODE_PATH"
+xcode_project \
+ -scheme "Thoughts" \
+ -config Release \
+ -archivePath "$ARCHIVE_PATH" \
+ OTHER_CODE_SIGN_FLAGS="--keychain=\"${KEYCHAIN_PATH}\"" \
+ CURRENT_PROJECT_VERSION=$BUILD_NUMBER \
+ MARKETING_VERSION=$VERSION_NUMBER \
+ clean archive
+xcodebuild \
+ -archivePath "$ARCHIVE_PATH" \
+ -exportArchive \
+ -exportPath "$BUILD_DIRECTORY" \
+ -exportOptionsPlist "ExportOptions.plist"
+
+APP_BASENAME="Thoughts.app"
+APP_PATH="$BUILD_DIRECTORY/$APP_BASENAME"
+PKG_PATH="$BUILD_DIRECTORY/Thoughts.pkg"
+
+# Install the private key.
+mkdir -p ~/.appstoreconnect/private_keys/
+echo -n "$APPLE_API_KEY_BASE64" | base64 --decode -o ~/".appstoreconnect/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8"
+
+# Archive the build directory.
+ZIP_BASENAME="build-${VERSION_NUMBER}-${BUILD_NUMBER}.zip"
+ZIP_PATH="${BUILD_DIRECTORY}/${ZIP_BASENAME}"
+pushd "${BUILD_DIRECTORY}"
+zip -r "${ZIP_BASENAME}" .
+popd
+
+if $RELEASE ; then
+
+ changes \
+ release \
+ --skip-if-empty \
+ --pre-release \
+ --push \
+ --exec "${RELEASE_SCRIPT_PATH}" \
+ "${PKG_PATH}" "${ZIP_PATH}"
+
+fi
diff --git a/scripts/changes b/scripts/changes
new file mode 160000
index 0000000..f68484b
--- /dev/null
+++ b/scripts/changes
@@ -0,0 +1 @@
+Subproject commit f68484bb984f324acca17e69e15f0bed94ee6cbd
diff --git a/scripts/environment.sh b/scripts/environment.sh
new file mode 100755
index 0000000..51caabe
--- /dev/null
+++ b/scripts/environment.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# Copyright (c) 2024 Jason Morley
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+SCRIPTS_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+ROOT_DIRECTORY="${SCRIPTS_DIRECTORY}/.."
+
+export PYTHONUSERBASE="${ROOT_DIRECTORY}/.local/python"
+mkdir -p "$PYTHONUSERBASE"
+export PATH="${PYTHONUSERBASE}/bin":$PATH
+
+export PATH=$PATH:"${SCRIPTS_DIRECTORY}/changes"
+export PATH=$PATH:"${SCRIPTS_DIRECTORY}/build-tools"
+export PATH=$PATH:"${ROOT_DIRECTORY}/diligence/scripts"
diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh
new file mode 100755
index 0000000..755e2a6
--- /dev/null
+++ b/scripts/install-dependencies.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+# Copyright (c) 2024 Jason Morley
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+set -e
+set -o pipefail
+set -x
+set -u
+
+SCRIPTS_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+ROOT_DIRECTORY="${SCRIPTS_DIRECTORY}/.."
+CHANGES_DIRECTORY="${SCRIPTS_DIRECTORY}/changes"
+BUILD_TOOLS_DIRECTORY="${SCRIPTS_DIRECTORY}/build-tools"
+
+ENVIRONMENT_PATH="${SCRIPTS_DIRECTORY}/environment.sh"
+
+if [ -d "${ROOT_DIRECTORY}/.local" ] ; then
+ rm -r "${ROOT_DIRECTORY}/.local"
+fi
+source "${ENVIRONMENT_PATH}"
+
+# Install the Python dependencies
+pip3 install --user pipenv
+PIPENV_PIPFILE="$CHANGES_DIRECTORY/Pipfile" pipenv install
+PIPENV_PIPFILE="$BUILD_TOOLS_DIRECTORY/Pipfile" pipenv install
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 0000000..712f371
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# Copyright (c) 2016-2024 Jason Morley
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+set -e
+set -o pipefail
+set -x
+
+# This script expects the macOS PKG as the first argument, and any additional files to be attached to the GitHub release
+# to be passed as subsequent arguments.
+
+# Upload the macOS build.
+xcrun altool --upload-app \
+ -f "$1" \
+ --primary-bundle-id "uk.co.jbmorley.thoughts.apps.appstore" \
+ --apiKey "$APPLE_API_KEY_ID" \
+ --apiIssuer "$APPLE_API_KEY_ISSUER_ID" \
+ --type macos
+
+# Actually make the release.
+FLAGS=()
+if $CHANGES_INITIAL_DEVELOPMENT ; then
+ FLAGS+=("--prerelease")
+elif $CHANGES_PRE_RELEASE ; then
+ FLAGS+=("--prerelease")
+fi
+gh release create "$CHANGES_TAG" --title "$CHANGES_QUALIFIED_TITLE" --notes-file "$CHANGES_NOTES_FILE" "${FLAGS[@]}"
+
+# Upload the attachments.
+for attachment in "$@"
+do
+ gh release upload "$CHANGES_TAG" "$attachment"
+done