desktop: ready app for mac store submission (#1057)

* desktop: add support for publishing to mac app store

* ci: add step for deployment to testflight

* ci: temporarily run build only on macos

* config: cache `build:desktop` output in nx

* ci: use `macos-12` instead of `macos-latest`

* ci: no need to install `jq`

* ci: build & deploy manually

* ci: set env_vars using `env` key

* ci: install packages before building

* ci: build electron bundle before deployment

* ci: install distribution signing cert

* web: disable app store offending stuff for macos

* ci: enable deployment to testflight

* ci: remove cert installation to keychain

* desktop: pass entitlement files through plutil

* ci: use a single cert for everything

* ci: fix altool command for uploading macOS package

* ci: use env_vars for apiKey & apiIssuer

* ci: install provisionprofile

* ci: install provisioning profile in `./apps/web/desktop` dir

* desktop: change bundleId to `org.streetwriters.notesnook`

* ci: deploy & build in separate steps

* ci: add jobs for linux & windows releasing

* ci: checkout repo before anything else

* ci: add `GH_TOKEN` to env & use ps syntax on windows

* ci: get app store versions only for mac app

* ci: correct order of steps

Co-authored-by: ammarahm-ed <ammarahmed6506@gmail.com>
This commit is contained in:
Abdullah Atta
2022-09-28 12:00:33 +05:00
committed by GitHub
parent b3ea6523ae
commit 5646b9249e
13 changed files with 394 additions and 162 deletions

View File

@@ -3,11 +3,21 @@ name: Publish @notesnook/desktop
on:
workflow_dispatch:
inputs:
release:
publish-apple:
type: boolean
required: true
default: false
description: "Release after successful build?"
default: true
description: "Publish on Mac App Store?"
publish-snap:
type: boolean
required: true
default: true
description: "Publish on Snapcraft?"
publish-github:
type: boolean
required: true
default: true
description: "Publish on GitHub releases?"
jobs:
build:
@@ -52,15 +62,173 @@ jobs:
name: build
path: apps/web/build/**/*
publish:
name: Publish
build-macos:
name: Build macOS
needs: build
runs-on: ${{ matrix.os }}
runs-on: macos-12
# Platforms to build on/for
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup notarization
run: |
mkdir -p ~/private_keys/
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
- name: Collect app metadata
id: app_metadata
working-directory: ./apps/web/desktop
run: |
echo ::set-output name=apple_app_id::$(cat package.json | jq -r .appAppleId)
echo ::set-output name=app_bundle_id::$(cat package.json | jq -r .build.appId)
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
echo ::set-output name=bundle_version::$(cat package.json | jq -r .build.mac.bundleVersion)
- name: Get App Store Version
id: appstore
uses: streetwriters/appstore-connect-app-version@develop
with:
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
key-id: ${{ secrets.api_key_id }}
issuer-id: ${{ secrets.api_key_issuer_id }}
private-key-raw: ${{ secrets.api_key }}
platform: "MAC_OS"
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Download build
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/desktop/build
- name: Install packages
run: npm i
working-directory: ./apps/web/desktop
- name: Build Electron wrapper
run: npm run build
working-directory: ./apps/web/desktop
- name: Install provisioning profile
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
working-directory: ./apps/web/desktop
- name: Build app for Mac App Store
if: inputs.publish-apple && steps.appstore.outputs.app-version-latest != steps.app_metadata.outputs.app_version
env:
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
npx electron-builder --mac mas --universal -p never
working-directory: ./apps/web/desktop
- name: Build zip and dmg
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --mac zip dmg -p always
else
npx electron-builder --mac zip dmg -p never
fi
working-directory: ./apps/web/desktop
- name: Deploy to Testflight
if: inputs.publish-apple && steps.appstore.outputs.app-version-latest != steps.app_metadata.outputs.app_version
env:
API_KEY_ID: ${{ secrets.api_key_id }}
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
run: |
package=$(find ./dist/mas-universal/notesnook*.pkg)
echo "Uploading ${package} using altool..."
xcrun altool --upload-package $package -t osx --apiKey $API_KEY_ID --apiIssuer $API_KEY_ISSUER_ID --apple-id ${{ steps.app_metadata.outputs.apple_app_id }} --bundle-id ${{ steps.app_metadata.outputs.app_bundle_id }}--bundle-short-version-string ${{ steps.app_metadata.outputs.app_version }} --bundle-version ${{ steps.app_metadata.outputs.bundle_version }}
working-directory: ./apps/web/desktop
build-linux:
name: Build for Linux
needs: build
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Collect app metadata
id: app_metadata
working-directory: ./apps/web/desktop
run: |
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
- name: Setup Snapcraft Auth
if: inputs.publish-snap
run: echo "SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.snapcraft_token }}" >> $GITHUB_ENV
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v1
if: inputs.publish-snap
- name: Get version info from Snapcraft
if: inputs.publish-snap
id: snapcraft
run:
echo ::set-output name=is_published::$(snapcraft status notesnook | grep ${{ steps.app_metadata.outputs.app_version }} | wc -l)
echo $is_published
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Download build
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/desktop/build
- name: Install packages
run: npm i
working-directory: ./apps/web/desktop
- name: Build Electron wrapper
run: npm run build
working-directory: ./apps/web/desktop
- name: Build snap
if: inputs.publish-snap && steps.snapcraft.outputs.is_published <= 0
run: |
npx electron-builder --linux snap -p never
working-directory: ./apps/web/desktop
- name: Build AppImage deb and rpm
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --linux AppImage deb rpm -p always
else
npx electron-builder --linux AppImage deb rpm -p never
fi
working-directory: ./apps/web/desktop
- name: Publish on Snapcraft
if: inputs.publish-snap && steps.snapcraft.outputs.is_published <= 0
run: |
snapcraft upload --release=stable ./dist/notesnook_linux_amd64.snap
working-directory: ./apps/web/desktop
build-windows:
name: Build for Windows
needs: build
runs-on: windows-latest
steps:
- name: Check out Git repository
@@ -77,41 +245,21 @@ jobs:
name: build
path: ./apps/web/desktop/build
- name: Setup Snapcraft Auth
run: echo "SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.snapcraft_token }}" >> $GITHUB_ENV
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v1
if: startsWith(matrix.os, 'ubuntu')
- name: Prepare for app notarization (macOS)
if: startsWith(matrix.os, 'macos')
# Import Apple API key for app notarization on macOS
run: |
mkdir -p ~/private_keys/
echo '${{ secrets.api_key }}' > ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@master
with:
package_root: ./apps/web/desktop
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.GITHUB_TOKEN }}
build_script_name: "build"
# macOS code signing certificate
mac_certs: ${{ secrets.mac_certs }}
mac_certs_password: ${{ secrets.mac_certs_password }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ inputs.release }}
env:
# macOS notarization API key
API_KEY_ID: ${{ secrets.api_key_id }}
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
- name: Publish on Snapcraft
if: startsWith(matrix.os, 'ubuntu')
run: snapcraft upload --release=stable ./dist/notesnook_linux_amd64.snap
- name: Install packages
run: npm i
working-directory: ./apps/web/desktop
- name: Build Electron wrapper
run: npm run build
working-directory: ./apps/web/desktop
- name: Build app
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if (${{ inputs.publish-github }} == true) {
npx electron-builder --win -p always
} else {
npx electron-builder --win -p never
}
working-directory: ./apps/web/desktop

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>53CWBG3QUC.org.streetwriters.notesnook</string>
<key>com.apple.developer.team-identifier</key>
<string>53CWBG3QUC</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>53CWBG3QUC.org.streetwriters.notesnook</string>
</array>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>/Documents/Notesnook/</string>
</array>
</dict>
</plist>

View File

@@ -3,6 +3,7 @@
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.2.0",
"appAppleId": "1544027013",
"private": true,
"main": "./build/electron.js",
"homepage": "https://notesnook.com/",
@@ -35,7 +36,7 @@
"url": "https://streetwriters.co"
},
"build": {
"appId": "com.streetwriters.notesnook",
"appId": "org.streetwriters.notesnook",
"productName": "Notesnook",
"copyright": "Copyright © 2022 Streetwriters (Private) Ltd.",
"artifactName": "notesnook_${os}_${arch}.${ext}",
@@ -68,6 +69,8 @@
"afterSign": "electron-builder-notarize",
"afterPack": "./scripts/removeLocales.js",
"mac": {
"bundleVersion": "220",
"minimumSystemVersion": "10.14.0",
"target": [
{
"target": "dmg",
@@ -109,6 +112,12 @@
"icon": "assets/icons/app.icns",
"title": "Install Notesnook"
},
"mas": {
"entitlements": "assets/entitlements.mas.plist",
"entitlementsInherit": "assets/entitlements.mas.inherit.plist",
"entitlementsLoginHelper": "assets/entitlements.mas.loginhelper.plist",
"hardenedRuntime": true
},
"win": {
"target": [
"nsis",

View File

@@ -21,6 +21,7 @@ const { contextBridge, ipcRenderer } = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld("os", process.platform);
contextBridge.exposeInMainWorld("api", {
send: (channel, data) => {
// whitelist channels

View File

@@ -1,24 +1,22 @@
{
"applinks":{
"apps":[
],
"details":[
{
"appID":"53CWBG3QUC.com.streetwriters.notesnook",
"paths":[
"*",
"/account/verified"
],
"components":[
{
"/":"/account/verified"
},
{
"/":"*"
}
]
}
]
}
}
"applinks": {
"apps": [],
"details": [
{
"appID": "53CWBG3QUC.org.streetwriters.notesnook",
"paths": [
"*",
"/account/verified"
],
"components": [
{
"/": "/account/verified"
},
{
"/": "*"
}
]
}
]
}
}

View File

@@ -40,6 +40,7 @@ import { db } from "../../../common/db";
import { useCheckoutStore } from "./store";
import { getCurrencySymbol } from "./helpers";
import { Theme } from "@notesnook/theme";
import { isMacApp } from "../../../utils/platform";
type BuyDialogProps = {
couponCode?: string;
@@ -283,7 +284,13 @@ function TrialOrUpgrade(props: TrialOrUpgradeProps) {
{formatPeriod(plan.period)}
</Text>
)}
{user ? (
{isMacApp() ? (
<>
<Text variant={"subBody"} mt={2} sx={{ textAlign: "center" }}>
You cannot upgrade from the macOS app.
</Text>
</>
) : user ? (
<>
<Button
variant="primary"

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Text, Flex, Box } from "@theme-ui/components";
import { isMacApp } from "../../../utils/platform";
import {
Accent,
Android,
@@ -74,6 +75,7 @@ type Section = {
info?: string;
pro?: boolean;
features?: Feature[];
isVisible?: () => boolean;
};
const sections: Section[] = [
@@ -120,6 +122,7 @@ const sections: Section[] = [
title: "100% cross platform",
detail: "Notesnook is available on all major platforms — for everyone.",
columns: 8,
isVisible: () => !isMacApp(),
features: [
{
id: "ios",
@@ -367,86 +370,90 @@ export function Features() {
pt={6}
bg="background"
>
{sections.map((section) => (
<Flex
key={section.title}
px={6}
pb={50}
sx={{ flexDirection: "column" }}
>
{section.pro && (
<Flex
bg="bgSecondary"
px={2}
py="2px"
sx={{ borderRadius: 50, alignSelf: "start" }}
mb={1}
>
<Pro color="primary" size={16} />
<Text variant="body" ml={"2px"} sx={{ color: "primary" }}>
Pro
</Text>
</Flex>
)}
<Text variant="body" sx={{ fontSize: "1.3rem" }}>
{section.title}
</Text>
<Text
variant="body"
mt={1}
sx={{ fontSize: "title", color: "fontTertiary" }}
{sections.map((section) => {
if (section.isVisible && !section.isVisible()) return null;
return (
<Flex
key={section.title}
px={6}
pb={50}
sx={{ flexDirection: "column" }}
>
{section.detail}
</Text>
{section.features && (
<Box
sx={{
display: "grid",
gridTemplateColumns: section.columns
? "1fr ".repeat(section.columns)
: "1fr 1fr 1fr",
gap: 3
}}
mt={4}
>
{section.features.map((feature) => (
<Flex
key={feature.id}
sx={{ flexDirection: "column", alignItems: "start" }}
>
{feature.icon && (
<feature.icon size={20} color="text" sx={{ mb: 1 }} />
)}
{feature.pro && (
<Flex
sx={{ alignItems: "center", justifyContent: "center" }}
>
<Pro color="primary" size={14} />
<Text
variant="subBody"
ml={"2px"}
sx={{ color: "primary" }}
>
Pro
</Text>
</Flex>
)}
{feature.title && (
<Text variant="body" sx={{ fontSize: "subtitle" }}>
{feature.title}
</Text>
)}
</Flex>
))}
</Box>
)}
{section.info && (
<Text mt={1} variant="subBody">
{section.info}
{section.pro && (
<Flex
bg="bgSecondary"
px={2}
py="2px"
sx={{ borderRadius: 50, alignSelf: "start" }}
mb={1}
>
<Pro color="primary" size={16} />
<Text variant="body" ml={"2px"} sx={{ color: "primary" }}>
Pro
</Text>
</Flex>
)}
<Text variant="body" sx={{ fontSize: "1.3rem" }}>
{section.title}
</Text>
)}
</Flex>
))}
<Text
variant="body"
mt={1}
sx={{ fontSize: "title", color: "fontTertiary" }}
>
{section.detail}
</Text>
{section.features && (
<Box
sx={{
display: "grid",
gridTemplateColumns: section.columns
? "1fr ".repeat(section.columns)
: "1fr 1fr 1fr",
gap: 3
}}
mt={4}
>
{section.features.map((feature) => (
<Flex
key={feature.id}
sx={{ flexDirection: "column", alignItems: "start" }}
>
{feature.icon && (
<feature.icon size={20} color="text" sx={{ mb: 1 }} />
)}
{feature.pro && (
<Flex
sx={{ alignItems: "center", justifyContent: "center" }}
>
<Pro color="primary" size={14} />
<Text
variant="subBody"
ml={"2px"}
sx={{ color: "primary" }}
>
Pro
</Text>
</Flex>
)}
{feature.title && (
<Text variant="body" sx={{ fontSize: "subtitle" }}>
{feature.title}
</Text>
)}
</Flex>
))}
</Box>
)}
{section.info && (
<Text mt={1} variant="subBody">
{section.info}
</Text>
)}
</Flex>
);
})}
</Flex>
);
}

View File

@@ -18,6 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export function getPlatform() {
if (window.os) return window.os;
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform,
macosPlatforms = ["Macintosh", "MacIntel", "MacPPC", "Mac68K"],
@@ -124,6 +126,10 @@ export function isDesktop() {
return "api" in window;
}
export function isMacApp() {
return true; // window.os === "darwin";
}
export function isTesting() {
return !!process.env.REACT_APP_TEST;
}

View File

@@ -54,7 +54,7 @@ import { appVersion } from "../utils/version";
import { CHECK_IDS } from "@notesnook/core/common";
import Tip from "../components/tip";
import Toggle from "../components/toggle";
import { isDesktop } from "../utils/platform";
import { isDesktop, isMacApp } from "../utils/platform";
import Vault from "../common/vault";
import { isUserPremium } from "../hooks/use-is-user-premium";
import { Slider } from "@theme-ui/components";
@@ -103,9 +103,13 @@ const otherItems = [
link: "https://discord.com/invite/zQBK97EE22"
},
{
title: "Download for iOS & Android",
description: "Notesnook is available on Android & iOS",
link: "https://notesnook.com/"
title: isMacApp() ? "Download for iOS" : "Download for iOS & Android",
description: isMacApp()
? "Notesnook is also available on iOS"
: "Notesnook is available on Android & iOS",
link: isMacApp()
? "https://apps.apple.com/us/app/notesnook-take-private-notes/id1544027013"
: "https://notesnook.com/"
},
{
title: "Documentation",

View File

@@ -3,7 +3,7 @@
"default": {
"runner": "@nrwl/nx-cloud",
"options": {
"cacheableOperations": ["build", "build:test"],
"cacheableOperations": ["build", "build:test", "build:desktop"],
"accessToken": "ZWM3ODgxNDgtNGUzZC00MjQ0LWE3MzMtZDdhMzE3ZGY2MWFlfHJlYWQ="
}
}