diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 0607e0910..235a6dd3b 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -32,6 +32,7 @@ env: VITE_OPENPANEL_CLIENT_ID: ${{ vars.VITE_OPENPANEL_CLIENT_ID }} VITE_OPENPANEL_API_URL: ${{ vars.VITE_OPENPANEL_API_URL }} VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 jobs: @@ -137,14 +138,11 @@ jobs: - name: Build - Vite working-directory: apps/desktop run: pnpm build:electron-vite ${{ env.PROD == 'false' && '--mode staging' || '' }} - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Build - Windows and Linux - if: runner.os != 'macOS' + if: runner.os == 'Linux' || (runner.os == 'Windows' && github.event.inputs.store != 'true') working-directory: apps/desktop run: pnpm build:electron-forge ${{ env.PROD == 'false' && '--mode=staging' || '' }} - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Build - macOS if: runner.os == 'macOS' && github.event.inputs.store != 'true' @@ -174,12 +172,15 @@ jobs: working-directory: apps/desktop run: pnpm build:electron-forge:mas + - name: Build - Microsoft Store + if: runner.os == 'Windows' && github.event.inputs.store == 'true' + working-directory: apps/desktop + run: pnpm build:electron-forge:ms + - name: Build - Renderer if: runner.os == 'Linux' working-directory: apps/desktop run: pnpm build:render - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Upload file (macos-arm64-dmg) uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index e6b9088ef..625406b86 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ apps/desktop/src/renderer/dev-dist tsconfig.tsbuildinfo buildServer.json -**/**/generated-routes.ts \ No newline at end of file +**/**/generated-routes.ts + +apps/desktop/build/appxmanifest.xml diff --git a/apps/desktop/build/appxmanifest-template.xml b/apps/desktop/build/appxmanifest-template.xml new file mode 100644 index 000000000..a902bf35a --- /dev/null +++ b/apps/desktop/build/appxmanifest-template.xml @@ -0,0 +1,43 @@ + + + + + {packageDisplayName} + {publisherDisplayName} + No description entered + assets\SampleAppx.50x50.png + + + + + + + + + + + + + + + + + + + {protocol} + + + + diff --git a/apps/desktop/forge.config.cts b/apps/desktop/forge.config.cts index 88b5499e7..dca34077f 100644 --- a/apps/desktop/forge.config.cts +++ b/apps/desktop/forge.config.cts @@ -212,6 +212,7 @@ const config: ForgeConfig = { packageDisplayName: "Folo - Follow everything in one place", devCert: "build/dev.pfx", assets: "static/appx", + manifest: "build/appxmanifest.xml", // @ts-ignore publisherDisplayName: "Natural Selection Labs", identityName: "NaturalSelectionLabs.Follow-Yourfavoritesinoneinbo", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index d5264b1dc..f1f22aa16 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -17,6 +17,7 @@ "build:electron-forge": "electron-forge make", "build:electron-forge:macos": "electron-forge make --arch=x64 --platform=darwin && electron-forge make --arch=arm64 --platform=darwin && tsx scripts/merge-yml.ts", "build:electron-forge:mas": "electron-forge make --arch=universal --platform=mas", + "build:electron-forge:ms": "tsx scripts/generate-appx-manifest.ts && electron-forge make --platform=win32", "build:electron-vite": "electron-vite build", "build:render": "vite build -c vite.config.electron-render.ts", "build:web": "rm -rf out/web && cross-env WEB_BUILD=1 vite build", diff --git a/apps/desktop/scripts/generate-appx-manifest.ts b/apps/desktop/scripts/generate-appx-manifest.ts new file mode 100644 index 000000000..6fcd511d6 --- /dev/null +++ b/apps/desktop/scripts/generate-appx-manifest.ts @@ -0,0 +1,110 @@ +#!/usr/bin/env tsx + +import fs from "node:fs" + +import path from "pathe" + +interface AppXManifestConfig { + packageName: string + packageDisplayName: string + publisherDisplayName: string + identityName: string + version: string + publisher: string + packageBackgroundColor: string + protocols: string[] + description: string + appBundleId: string +} + +function generateAppXManifest(config: AppXManifestConfig, templatePath: string): string { + // Read template file + const template = fs.readFileSync(templatePath, "utf-8") + + // Generate protocol extensions + const protocolExtensions = config.protocols + .map( + (protocol) => ` + + + `, + ) + .join("") + + // Replace template variables + const manifest = template + .replaceAll("{identityName}", config.identityName) + .replaceAll("{publisherName}", config.publisher) + .replaceAll("{packageVersion}", config.version) + .replaceAll("{packageDisplayName}", config.packageDisplayName) + .replaceAll("{publisherDisplayName}", config.publisherDisplayName) + .replaceAll("{packageName}", config.packageName) + .replaceAll("{packageExecutable}", `app\\${config.packageName}.exe`) + .replaceAll("{packageBackgroundColor}", config.packageBackgroundColor) + .replaceAll("{packageDescription}", config.description) + .replaceAll("{protocol}", protocolExtensions) + + return manifest +} + +async function main() { + try { + // Read package.json to get app information + const packageJsonPath = path.resolve(process.cwd(), "package.json") + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) + + // Get mode from command line arguments + const mode = process.argv.find((arg) => arg.startsWith("--mode"))?.split("=")[1] + const isStaging = mode === "staging" + + // Parse version for AppX format (must be x.x.x.x) + const { version } = packageJson + const versionParts = version.split(".") + // Ensure we have 4 parts for AppX version format + while (versionParts.length < 4) { + versionParts.push("0") + } + const appxVersion = versionParts.slice(0, 4).join(".") + + const config: AppXManifestConfig = { + packageName: "Folo", + packageDisplayName: isStaging + ? "Folo Staging - Follow everything in one place" + : "Folo - Follow everything in one place", + publisherDisplayName: "Natural Selection Labs", + identityName: "NaturalSelectionLabs.Follow-Yourfavoritesinoneinbo", + version: appxVersion, + publisher: "CN=7CBBEB6A-9B0E-4387-BAE3-576D0ACA279E", + packageBackgroundColor: "#FF5C00", + protocols: ["folo", "follow"], + description: "Follow everything in one place.", + appBundleId: "is.follow", + } + + // Template file path + const templatePath = path.resolve(process.cwd(), "build/appxmanifest-template.xml") + + if (!fs.existsSync(templatePath)) { + throw new Error(`Template file not found: ${templatePath}`) + } + + const manifest = generateAppXManifest(config, templatePath) + + // Ensure output directory exists + const outputDir = path.resolve(process.cwd(), "build") + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }) + } + + // Write manifest file + const outputPath = path.resolve(outputDir, "appxmanifest.xml") + fs.writeFileSync(outputPath, manifest, "utf-8") + } catch (error) { + console.error("❌ Failed to generate AppX manifest:", error) + process.exit(1) + } +} + +main() + +export { type AppXManifestConfig, generateAppXManifest }