Skip to content

Commit 8a1a5ff

Browse files
authored
fix pc release packaging on github actions (#163)
* fix(pc-release): stabilize electron-builder packaging * fix(pc-updates): preserve downloaded state * fix(pc-updates): preserve downloaded state for skipped manifests * chore(pc-release): publish macos only
1 parent 86add29 commit 8a1a5ff

18 files changed

+392
-121
lines changed

.github/workflows/release-pc.yml

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ jobs:
8585
- uses: actions/setup-node@v4
8686
with:
8787
node-version: ${{ env.NODE_VERSION }}
88-
- run: pnpm install --frozen-lockfile --prefer-offline
88+
- run: pnpm install --frozen-lockfile --prefer-offline --config.node-linker=isolated
8989
- name: Build workspace dependencies
90-
run: pnpm --filter "@moryflow/pc^..." --if-present build
90+
run: pnpm --filter "@moryflow/pc..." --filter "!@moryflow/pc" --if-present build
9191
- name: Build renderer/main bundles
9292
run: pnpm --dir apps/moryflow/pc build
9393
- name: Build macOS arm64 installers
@@ -101,7 +101,7 @@ jobs:
101101
build-macos-x64:
102102
name: Build macOS x64
103103
needs: metadata
104-
runs-on: macos-13
104+
runs-on: macos-15-intel
105105
env:
106106
CSC_LINK: ${{ secrets.CSC_LINK }}
107107
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
@@ -116,9 +116,9 @@ jobs:
116116
- uses: actions/setup-node@v4
117117
with:
118118
node-version: ${{ env.NODE_VERSION }}
119-
- run: pnpm install --frozen-lockfile --prefer-offline
119+
- run: pnpm install --frozen-lockfile --prefer-offline --config.node-linker=isolated
120120
- name: Build workspace dependencies
121-
run: pnpm --filter "@moryflow/pc^..." --if-present build
121+
run: pnpm --filter "@moryflow/pc..." --filter "!@moryflow/pc" --if-present build
122122
- name: Build renderer/main bundles
123123
run: pnpm --dir apps/moryflow/pc build
124124
- name: Build macOS x64 installers
@@ -129,41 +129,12 @@ jobs:
129129
path: apps/moryflow/pc/release/${{ needs.metadata.outputs.version }}/*
130130
if-no-files-found: error
131131

132-
build-windows-x64:
133-
name: Build Windows x64
134-
needs: metadata
135-
runs-on: windows-2022
136-
env:
137-
CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
138-
CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
139-
steps:
140-
- uses: actions/checkout@v4
141-
- uses: pnpm/action-setup@v4
142-
with:
143-
version: ${{ env.PNPM_VERSION }}
144-
- uses: actions/setup-node@v4
145-
with:
146-
node-version: ${{ env.NODE_VERSION }}
147-
- run: pnpm install --frozen-lockfile --prefer-offline
148-
- name: Build workspace dependencies
149-
run: pnpm --filter "@moryflow/pc^..." --if-present build
150-
- name: Build renderer/main bundles
151-
run: pnpm --dir apps/moryflow/pc build
152-
- name: Build Windows x64 installer
153-
run: pnpm --dir apps/moryflow/pc exec electron-builder --win nsis --x64 --publish never
154-
- uses: actions/upload-artifact@v4
155-
with:
156-
name: win32-x64
157-
path: apps/moryflow/pc/release/${{ needs.metadata.outputs.version }}/*
158-
if-no-files-found: error
159-
160132
publish:
161133
name: Publish Release
162134
needs:
163135
- metadata
164136
- build-macos-arm64
165137
- build-macos-x64
166-
- build-windows-x64
167138
runs-on: ubuntu-latest
168139
permissions:
169140
contents: write
@@ -193,14 +164,16 @@ jobs:
193164
--base-url "${{ env.UPDATE_BASE_URL }}" \
194165
--input-dir ".artifacts" \
195166
--output-dir ".release-prepared" \
196-
--github-repo "${{ env.GITHUB_REPO }}"
167+
--github-repo "${{ env.GITHUB_REPO }}" \
168+
--targets "darwin-arm64,darwin-x64"
197169
- name: Smoke check generated feeds
198170
run: |
199171
pnpm --dir apps/moryflow/pc exec tsx scripts/smoke-check-update-feed.ts \
200172
--version "${{ needs.metadata.outputs.version }}" \
201173
--channel "${{ needs.metadata.outputs.channel }}" \
202174
--base-url "${{ env.UPDATE_BASE_URL }}" \
203-
--input-dir ".release-prepared"
175+
--input-dir ".release-prepared" \
176+
--targets "darwin-arm64,darwin-x64"
204177
- name: Publish GitHub release
205178
uses: softprops/action-gh-release@v2
206179
with:

apps/moryflow/pc/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Authentication tokens
22
.npmrc
3+
!.npmrc
34

45
# Build output
56
dist

apps/moryflow/pc/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# electron-builder resolves package-manager config from the app directory.
2+
# Keep packaging isolated here without changing the repo-wide hoisted baseline.
3+
node-linker=isolated

apps/moryflow/pc/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
"clsx": "^2.1.1",
150150
"cmdk": "^1.1.1",
151151
"date-fns": "^4.1.0",
152-
"electron": "^31.7.7",
152+
"electron": "31.7.7",
153153
"electron-builder": "^26.0.12",
154154
"electron-vite": "^2.2.0",
155155
"embla-carousel-react": "^8.6.0",

apps/moryflow/pc/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/moryflow/pc/scripts/prepare-release-artifacts.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type ParsedArgs = {
2121
inputDir: string;
2222
outputDir: string;
2323
githubRepo: string;
24+
targets: TargetDefinition[];
2425
};
2526

2627
type TargetBundle = {
@@ -59,14 +60,45 @@ Reads per-platform electron-builder outputs from <input-dir>/<target>, rewrites
5960
to versioned download URLs, and prepares:
6061
- <output-dir>/releases/v<version>/...
6162
- <output-dir>/channels/<channel>/...
62-
- <output-dir>/github-release-assets/...`;
63+
- <output-dir>/github-release-assets/...
64+
65+
Optional:
66+
--targets <darwin-arm64,darwin-x64,win32-x64>`;
6367

6468
const fail = (message: string): never => {
6569
throw new Error(message);
6670
};
6771

6872
const isFlag = (value: string) => value.startsWith('--');
6973

74+
const parseTargets = (value: string | undefined): TargetDefinition[] => {
75+
if (!value) {
76+
return TARGETS;
77+
}
78+
79+
const keys = value
80+
.split(',')
81+
.map((item) => item.trim())
82+
.filter(Boolean);
83+
84+
if (keys.length === 0) {
85+
fail('Missing or invalid --targets');
86+
}
87+
88+
const seen = new Set<TargetKey>();
89+
const targets: TargetDefinition[] = [];
90+
for (const key of keys) {
91+
const target = TARGETS.find((item) => item.key === key);
92+
if (!target) {
93+
fail(`Unsupported --targets entry: ${key}`);
94+
}
95+
if (seen.has(target.key)) continue;
96+
seen.add(target.key);
97+
targets.push(target);
98+
}
99+
return targets;
100+
};
101+
70102
const parseArgs = (argv: string[]): ParsedArgs => {
71103
if (argv.includes('--help') || argv.includes('-h')) {
72104
console.log(HELP_TEXT);
@@ -91,6 +123,7 @@ const parseArgs = (argv: string[]): ParsedArgs => {
91123
const inputDir = values.get('--input-dir');
92124
const outputDir = values.get('--output-dir');
93125
const githubRepo = values.get('--github-repo') ?? 'dvlin-dev/moryflow';
126+
const targets = parseTargets(values.get('--targets'));
94127

95128
if (!version) fail('Missing --version');
96129
if (!channel || (channel !== 'stable' && channel !== 'beta')) {
@@ -107,6 +140,7 @@ const parseArgs = (argv: string[]): ParsedArgs => {
107140
inputDir: path.resolve(inputDir),
108141
outputDir: path.resolve(outputDir),
109142
githubRepo,
143+
targets,
110144
};
111145
};
112146

@@ -252,7 +286,7 @@ const createManifest = async (bundles: TargetBundle[], args: ParsedArgs) => {
252286

253287
const getBundles = async (args: ParsedArgs) => {
254288
const bundles: TargetBundle[] = [];
255-
for (const target of TARGETS) {
289+
for (const target of args.targets) {
256290
const sourceDir = path.join(args.inputDir, target.artifactDir);
257291
let stats: Awaited<ReturnType<typeof fs.stat>>;
258292
try {

apps/moryflow/pc/scripts/smoke-check-update-feed.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import fs from 'node:fs/promises';
44
import path from 'node:path';
55

66
type Channel = 'stable' | 'beta';
7+
type TargetKey = 'darwin-arm64' | 'darwin-x64' | 'win32-x64';
78
type Target = {
8-
key: 'darwin-arm64' | 'darwin-x64' | 'win32-x64';
9+
key: TargetKey;
910
releaseDir: string;
1011
feedFilename: string;
1112
};
@@ -18,14 +19,45 @@ const TARGETS: Target[] = [
1819

1920
const HELP_TEXT = `Usage: pnpm exec tsx scripts/smoke-check-update-feed.ts --version <version> --channel <stable|beta> --base-url <url> --input-dir <dir>
2021
21-
Validates generated manifest.json and latest*.yml outputs.`;
22+
Validates generated manifest.json and latest*.yml outputs.
23+
24+
Optional:
25+
--targets <darwin-arm64,darwin-x64,win32-x64>`;
2226

2327
const fail = (message: string): never => {
2428
throw new Error(message);
2529
};
2630

2731
const isFlag = (value: string) => value.startsWith('--');
2832

33+
const parseTargets = (value: string | undefined): Target[] => {
34+
if (!value) {
35+
return TARGETS;
36+
}
37+
38+
const keys = value
39+
.split(',')
40+
.map((item) => item.trim())
41+
.filter(Boolean);
42+
43+
if (keys.length === 0) {
44+
fail('Missing or invalid --targets');
45+
}
46+
47+
const seen = new Set<TargetKey>();
48+
const targets: Target[] = [];
49+
for (const key of keys) {
50+
const target = TARGETS.find((item) => item.key === key);
51+
if (!target) {
52+
fail(`Unsupported --targets entry: ${key}`);
53+
}
54+
if (seen.has(target.key)) continue;
55+
seen.add(target.key);
56+
targets.push(target);
57+
}
58+
return targets;
59+
};
60+
2961
const parseArgs = (argv: string[]) => {
3062
if (argv.includes('--help') || argv.includes('-h')) {
3163
console.log(HELP_TEXT);
@@ -48,6 +80,7 @@ const parseArgs = (argv: string[]) => {
4880
const channel = values.get('--channel') as Channel | undefined;
4981
const baseUrl = values.get('--base-url');
5082
const inputDir = values.get('--input-dir');
83+
const targets = parseTargets(values.get('--targets'));
5184

5285
if (!version) fail('Missing --version');
5386
if (!channel || (channel !== 'stable' && channel !== 'beta')) {
@@ -61,6 +94,7 @@ const parseArgs = (argv: string[]) => {
6194
channel,
6295
baseUrl: baseUrl.replace(/\/+$/, ''),
6396
inputDir: path.resolve(inputDir),
97+
targets,
6498
};
6599
};
66100

@@ -105,7 +139,7 @@ const main = async () => {
105139

106140
const yaml = await loadYamlModule();
107141

108-
for (const target of TARGETS) {
142+
for (const target of args.targets) {
109143
const entry = manifest.downloads[target.key];
110144
if (!entry) {
111145
fail(`Missing manifest download entry for ${target.key}`);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import fs from 'node:fs/promises';
2+
import path from 'node:path';
3+
import { describe, expect, it } from 'vitest';
4+
5+
describe('electron-builder package manager detection', () => {
6+
it('pins the pc app directory to isolated node-linker for packaging', async () => {
7+
const npmrc = await fs.readFile(path.join(process.cwd(), '.npmrc'), 'utf8');
8+
expect(npmrc).toContain('node-linker=isolated');
9+
});
10+
11+
it('detects the pc app directory as a pnpm project', async () => {
12+
const { detect, getCollectorByPackageManager } =
13+
await import('app-builder-lib/out/node-module-collector/index.js');
14+
15+
await expect(detect({ cwd: process.cwd() })).resolves.toBe('pnpm');
16+
17+
const collector = await getCollectorByPackageManager(process.cwd());
18+
expect(collector.constructor.name).toBe('PnpmNodeModulesCollector');
19+
});
20+
});

apps/moryflow/pc/src/main/app/ipc-handlers.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ import {
9999
import { getSkillsRegistry, SKILLS_DIR } from '../skills/index.js';
100100
import { searchIndexService } from '../search-index/index.js';
101101
import { telegramChannelService } from '../channels/telegram/index.js';
102+
import { parseSkipVersionPayload } from './update-payload-validation.js';
102103

103104
type RegisterIpcHandlersOptions = {
104105
vaultWatcherController: VaultWatcherController;
@@ -125,7 +126,9 @@ type RegisterIpcHandlersOptions = {
125126
downloadUpdate: () => Promise<AppUpdateState>;
126127
restartToInstall: () => void;
127128
skipVersion: (version?: string | null) => AppUpdateSettings;
128-
subscribe: (listener: (state: AppUpdateState, settings: AppUpdateSettings) => void) => () => void;
129+
subscribe: (
130+
listener: (state: AppUpdateState, settings: AppUpdateSettings) => void
131+
) => () => void;
129132
};
130133
};
131134

@@ -337,13 +340,8 @@ export const registerIpcHandlers = ({
337340
}
338341
});
339342
ipcMain.handle('updates:skipVersion', (_event, payload) => {
340-
const version =
341-
payload?.version === null
342-
? null
343-
: typeof payload?.version === 'string'
344-
? payload.version
345-
: undefined;
346-
if (payload && 'version' in payload && payload.version !== null && typeof payload.version !== 'string') {
343+
const { isValid, version } = parseSkipVersionPayload(payload);
344+
if (!isValid) {
347345
return {
348346
ok: false,
349347
error: {

0 commit comments

Comments
 (0)