diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bf836d1afd..3a29eaf0cf 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,8 +9,15 @@ on:
workflow_dispatch:
inputs:
tag_version:
+ type: boolean
description: "Tag Version"
- required: true
+ mas:
+ type: boolean
+ description: "Build for Mac App Store"
+ build_version:
+ type: string
+ description: "Build Version, only available when mas is true"
+
# https://docs.github.com/en/enterprise-cloud@latest/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-build
@@ -32,6 +39,9 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
+ exclude:
+ - os: ${{ github.event.inputs.mas == 'true' && 'ubuntu-latest' }}
+ - os: ${{ github.event.inputs.mas == 'true' && 'windows-latest' }}
permissions:
id-token: write
@@ -41,13 +51,13 @@ jobs:
steps:
- name: Check out Git repository Fully
uses: actions/checkout@v4
- if: github.event.inputs.tag_version != '' || github.ref_type == 'tag'
+ if: github.event.inputs.tag_version == 'true' || github.ref_type == 'tag'
with:
fetch-depth: 0
lfs: true
- name: Check out Git repository
uses: actions/checkout@v4
- if: github.event.inputs.tag_version == '' && github.ref_type != 'tag'
+ if: github.event.inputs.tag_version != 'true' && github.ref_type != 'tag'
with:
fetch-depth: 1
lfs: true
@@ -73,17 +83,23 @@ jobs:
if: runner.os == 'macOS'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
+ BUILD_CERTIFICATE_MAS_BASE64: ${{ secrets.BUILD_CERTIFICATE_MAS_BASE64 }}
+ BUILD_CERTIFICATE_MASPKG_BASE64: ${{ secrets.BUILD_CERTIFICATE_MASPKG_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
+ CERTIFICATE_MAS_PATH=$RUNNER_TEMP/build_certificate_mas.p12
+ CERTIFICATE_MASPKG_PATH=$RUNNER_TEMP/build_certificate_maspkg.p12
PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
+ echo -n "$BUILD_CERTIFICATE_MAS_BASE64" | base64 --decode -o $CERTIFICATE_MAS_PATH
+ echo -n "$BUILD_CERTIFICATE_MASPKG_BASE64" | base64 --decode -o $CERTIFICATE_MASPKG_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
@@ -93,8 +109,11 @@ jobs:
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security import $CERTIFICATE_MAS_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
+ security import $CERTIFICATE_MASPKG_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
+ security find-identity $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
@@ -111,14 +130,24 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Build (macOS)
- if: matrix.os == 'macos-latest'
+ if: matrix.os == 'macos-latest' && github.event.inputs.mas != 'true'
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db
+ OSX_SIGN_KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db
+ OSX_SIGN_IDENTITY: ${{ secrets.OSX_SIGN_IDENTITY }}
run: npm exec turbo run //#build:macos
+ - name: Build (Mac App Store)
+ if: matrix.os == 'macos-latest' && github.event.inputs.mas == 'true'
+ env:
+ OSX_SIGN_KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db
+ OSX_SIGN_IDENTITY: ${{ secrets.OSX_SIGN_IDENTITY_MAS }}
+ OSX_SIGN_PROVISIONING_PROFILE_PATH: ${{ runner.temp }}/build_pp.provisionprofile
+ BUILD_VERSION: ${{ github.event.inputs.build_version }}
+ run: npm exec turbo run //#build:mas
+
- name: Build Renderer
if: matrix.os == 'ubuntu-latest'
run: pnpm build:render
@@ -138,6 +167,7 @@ jobs:
out/make/**/*.exe
out/make/**/*.AppImage
out/make/**/*.yml
+ out/make/**/*.pkg
dist/manifest.yml
dist/*.tar.gz
retention-days: 90
@@ -153,8 +183,19 @@ jobs:
out/make/**/*arm64.dmg
retention-days: 90
+ - name: Upload file (pkg)
+ uses: actions/upload-artifact@v4
+ if: matrix.os == 'macos-latest'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ name: mas-pkg
+ path: |
+ out/make/**/*.pkg
+ retention-days: 90
+
- name: Generate artifact attestation
- if: github.ref_type == 'tag' || github.event.inputs.tag_version != ''
+ if: github.ref_type == 'tag' || github.event.inputs.tag_version == 'true'
continue-on-error: true
uses: actions/attest-build-provenance@v1
with:
@@ -168,7 +209,7 @@ jobs:
dist/*.tar.gz
- run: npx changelogithub
- if: github.ref_type == 'tag' || github.event.inputs.tag_version != ''
+ if: github.ref_type == 'tag' || github.event.inputs.tag_version == 'true'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -178,7 +219,7 @@ jobs:
uses: ./.github/actions/setup-version
- name: Create Release Draft
- if: github.ref_type == 'tag' || github.event.inputs.tag_version != ''
+ if: github.ref_type == 'tag' || github.event.inputs.tag_version == 'true'
uses: softprops/action-gh-release@v2
with:
name: v${{ steps.version.outputs.APP_VERSION }}
diff --git a/apps/renderer/src/modules/auth/LoginModalContent.tsx b/apps/renderer/src/modules/auth/LoginModalContent.tsx
index 1289916c44..81b714860f 100644
--- a/apps/renderer/src/modules/auth/LoginModalContent.tsx
+++ b/apps/renderer/src/modules/auth/LoginModalContent.tsx
@@ -45,43 +45,47 @@ export const LoginModalContent = (props: LoginModalContentProps) => {
-
-
- {Object.entries(authProviders || [])
- .filter(([key]) => key !== "credential")
- .map(([key, provider]) => (
-
-
- {
- loginHandler(key, "app")
- }}
- >
-
-
-
-
-
- {t("login.continueWith", { provider: provider.name })}
-
-
-
- ))}
-
+ {!process.mas && (
+ <>
+
+
+ {Object.entries(authProviders || [])
+ .filter(([key]) => key !== "credential")
+ .map(([key, provider]) => (
+
+
+ {
+ loginHandler(key, "app")
+ }}
+ >
+
+
+
+
+
+ {t("login.continueWith", { provider: provider.name })}
+
+
+
+ ))}
+
+ >
+ )}
>
)
if (isMobile) {
diff --git a/apps/renderer/src/pages/settings/(settings)/profile.tsx b/apps/renderer/src/pages/settings/(settings)/profile.tsx
index 496c695273..8352b7f98c 100644
--- a/apps/renderer/src/pages/settings/(settings)/profile.tsx
+++ b/apps/renderer/src/pages/settings/(settings)/profile.tsx
@@ -1,5 +1,9 @@
+import { Button } from "@follow/components/ui/button/index.js"
import { Divider } from "@follow/components/ui/divider/Divider.js"
+import { Label } from "@follow/components/ui/label/index.js"
+import { signOut } from "@follow/shared/auth"
+import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { AccountManagement } from "~/modules/profile/account-management"
import { EmailManagement } from "~/modules/profile/email-management"
import { ProfileSettingForm } from "~/modules/profile/profile-setting-form"
@@ -17,6 +21,7 @@ export const loader = defineSettingPageData({
})
export function Component() {
+ const { present } = useModalStack()
return (
<>
@@ -30,6 +35,39 @@ export function Component() {
+ {/* TODO: Temporary fake account deletion feature */}
+ {process.mas && (
+
+
+
+
+ )}
>
diff --git a/build/entitlements.mas.child.plist b/build/entitlements.mas.child.plist
new file mode 100644
index 0000000000..d8dc69e808
--- /dev/null
+++ b/build/entitlements.mas.child.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.inherit
+
+
+
diff --git a/build/entitlements.mas.plist b/build/entitlements.mas.plist
new file mode 100644
index 0000000000..81caf94414
--- /dev/null
+++ b/build/entitlements.mas.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.files.user-selected.read-write
+
+ com.apple.security.files.bookmarks.app-scope
+
+ com.apple.security.network.client
+
+ ITSAppUsesNonExemptEncryption
+
+
+
diff --git a/forge.config.cts b/forge.config.cts
index a1a57906dc..13da196e46 100644
--- a/forge.config.cts
+++ b/forge.config.cts
@@ -1,3 +1,5 @@
+import "dotenv/config"
+
import crypto from "node:crypto"
import fs, { readdirSync } from "node:fs"
import { cp, readdir } from "node:fs/promises"
@@ -5,6 +7,7 @@ import path, { resolve } from "node:path"
import { FuseV1Options, FuseVersion } from "@electron/fuses"
import { MakerDMG } from "@electron-forge/maker-dmg"
+import { MakerPKG } from "@electron-forge/maker-pkg"
import { MakerSquirrel } from "@electron-forge/maker-squirrel"
import { MakerZIP } from "@electron-forge/maker-zip"
import { FusesPlugin } from "@electron-forge/plugin-fuses"
@@ -14,6 +17,8 @@ import setLanguages from "electron-packager-languages"
import yaml from "js-yaml"
import { rimraf, rimrafSync } from "rimraf"
+const platform = process.argv[process.argv.indexOf("--platform") + 1]
+
const artifactRegex = /.*\.(?:exe|dmg|AppImage|zip)$/
const platformNamesMap = {
darwin: "macos",
@@ -34,7 +39,7 @@ async function cleanSources(buildPath, _electronVersion, platform, _arch, callba
// folders & files to be included in the app
const appItems = new Set(["dist", "node_modules", "package.json", "resources"])
- if (platform === "darwin") {
+ if (platform === "darwin" || platform === "mas") {
const frameworkResourcePath = resolve(
buildPath,
"../../Frameworks/Electron Framework.framework/Versions/A/Resources",
@@ -87,6 +92,8 @@ const ignorePattern = new RegExp(`^/node_modules/(?!${[...keepModules].join("|")
const config: ForgeConfig = {
packagerConfig: {
+ appCategoryType: "public.app-category.news",
+ buildVersion: process.env.BUILD_VERSION || undefined,
appBundleId: "is.follow",
icon: "resources/icon",
extraResource: ["./resources/app-update.yml"],
@@ -105,15 +112,28 @@ const config: ForgeConfig = {
ignore: [ignorePattern],
prune: true,
+ osxSign: {
+ optionsForFile:
+ platform === "mas"
+ ? (filePath) => {
+ const entitlements = filePath.includes(".app/")
+ ? "build/entitlements.mas.child.plist"
+ : "build/entitlements.mas.plist"
+ return {
+ hardenedRuntime: false,
+ entitlements,
+ }
+ }
+ : () => ({
+ entitlements: "build/entitlements.mac.plist",
+ }),
+ keychain: process.env.OSX_SIGN_KEYCHAIN_PATH,
+ identity: process.env.OSX_SIGN_IDENTITY,
+ provisioningProfile: process.env.OSX_SIGN_PROVISIONING_PROFILE_PATH,
+ },
...(process.env.APPLE_ID &&
process.env.APPLE_PASSWORD &&
process.env.APPLE_TEAM_ID && {
- osxSign: {
- optionsForFile: () => ({
- entitlements: "build/entitlements.mac.plist",
- }),
- keychain: process.env.KEYCHAIN_PATH,
- },
osxNotarize: {
appleId: process.env.APPLE_ID!,
appleIdPassword: process.env.APPLE_PASSWORD!,
@@ -124,34 +144,37 @@ const config: ForgeConfig = {
rebuildConfig: {},
makers: [
new MakerZIP({}, ["darwin"]),
- new MakerDMG({
- overwrite: true,
- background: "static/dmg-background.png",
- icon: "static/dmg-icon.icns",
- iconSize: 160,
- additionalDMGOptions: {
- window: {
- size: {
- width: 660,
- height: 400,
+ new MakerDMG(
+ {
+ overwrite: true,
+ background: "static/dmg-background.png",
+ icon: "static/dmg-icon.icns",
+ iconSize: 160,
+ additionalDMGOptions: {
+ window: {
+ size: {
+ width: 660,
+ height: 400,
+ },
},
},
+ contents: (opts) => [
+ {
+ x: 180,
+ y: 170,
+ type: "file",
+ path: (opts as any).appPath,
+ },
+ {
+ x: 480,
+ y: 170,
+ type: "link",
+ path: "/Applications",
+ },
+ ],
},
- contents: (opts) => [
- {
- x: 180,
- y: 170,
- type: "file",
- path: (opts as any).appPath,
- },
- {
- x: 480,
- y: 170,
- type: "link",
- path: "/Applications",
- },
- ],
- }),
+ ["darwin", "mas"],
+ ),
new MakerSquirrel({
name: "Follow",
setupIcon: "resources/icon.ico",
@@ -167,6 +190,13 @@ const config: ForgeConfig = {
],
},
}),
+ new MakerPKG(
+ {
+ name: "Follow",
+ keychain: process.env.KEYCHAIN_PATH,
+ },
+ ["mas"],
+ ),
],
plugins: [
// Fuses are used to enable/disable various Electron functionality
diff --git a/package.json b/package.json
index e192829729..021d6ff79a 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"analyze:web": "analyzer=1 vite build",
"build": "electron-vite build && electron-forge make",
"build:macos": "electron-vite build && electron-forge make --arch=x64 --platform=darwin && electron-forge make --arch=arm64 --platform=darwin && tsx scripts/merge-yml.ts",
+ "build:mas": "electron-vite build && electron-forge make --arch=universal --platform=mas",
"build:render": "vite build -c vite.config.electron-render.ts",
"build:rn-web": "rm -rf out/rn-web && cross-env RN_BUILD=1 vite build",
"build:web": "rm -rf out/web && cross-env WEB_BUILD=1 vite build",
@@ -52,6 +53,7 @@
"@egoist/tailwindcss-icons": "1.9.0",
"@electron-forge/cli": "7.6.1",
"@electron-forge/maker-dmg": "7.6.1",
+ "@electron-forge/maker-pkg": "7.6.1",
"@electron-forge/maker-squirrel": "7.6.1",
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-fuses": "7.6.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2114eae409..f2b9a99641 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -73,6 +73,9 @@ importers:
'@electron-forge/maker-dmg':
specifier: 7.6.1
version: 7.6.1
+ '@electron-forge/maker-pkg':
+ specifier: 7.6.1
+ version: 7.6.1
'@electron-forge/maker-squirrel':
specifier: 7.6.1
version: 7.6.1
@@ -2433,6 +2436,10 @@ packages:
resolution: {integrity: sha512-D7cJRE6CGeovLZhu2dRqUm3w/AlkTURYJYgFuUsgwpBuviQKgJd8quZar6IeZ/l83y4Z1dghKb8D3aAj+bRvNQ==}
engines: {node: '>= 16.4.0'}
+ '@electron-forge/maker-pkg@7.6.1':
+ resolution: {integrity: sha512-AmbfMqwgi40w7AW5AuGJhDedM4XOiF1hb3aH1OX5KM4ZzNtlRB5ThKArALxt6YxosBfjS5FF2RBXNjb7qtuiCQ==}
+ engines: {node: '>= 16.4.0'}
+
'@electron-forge/maker-squirrel@7.6.1':
resolution: {integrity: sha512-7EMLcl0QM5GfdY+enfauEqV6ZW14A1S6Eqoev812FXGTm88G8Ik0tPRw6SsIaI8R++YqxsbdCGTQjzdJWY0bJA==}
engines: {node: '>= 16.4.0'}
@@ -16258,6 +16265,15 @@ snapshots:
- bluebird
- supports-color
+ '@electron-forge/maker-pkg@7.6.1':
+ dependencies:
+ '@electron-forge/maker-base': 7.6.1
+ '@electron-forge/shared-types': 7.6.1
+ '@electron/osx-sign': 1.3.2
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+
'@electron-forge/maker-squirrel@7.6.1':
dependencies:
'@electron-forge/maker-base': 7.6.1
diff --git a/turbo.json b/turbo.json
index 52fd871896..1a4296c0fe 100644
--- a/turbo.json
+++ b/turbo.json
@@ -11,6 +11,9 @@
"//#build:macos": {
"outputs": ["dist/**", "out/**"]
},
+ "//#build:mas": {
+ "outputs": ["dist/**", "out/**"]
+ },
"//#format:check": {},
"//#lint": {},
"test": {},