Skip to content

Commit 42c7de1

Browse files
authored
EQMS-1548: TraceX desktop app (#9666)
Signed-off-by: Alexey Zinoviev <[email protected]>
1 parent b33e4db commit 42c7de1

File tree

21 files changed

+474
-68
lines changed

21 files changed

+474
-68
lines changed

.github/workflows/main.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ env:
2626
common
2727
desktop
2828
desktop-package
29+
qms-desktop-package
2930
dev
3031
models
3132
packages
@@ -807,3 +808,108 @@ jobs:
807808
with:
808809
name: Huly-Linux
809810
path: ./desktop-package/deploy/Huly-linux-*.zip
811+
812+
qms-dist-build:
813+
# if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/s') }}
814+
if: ${{ startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/s') }}
815+
needs: build
816+
runs-on: macos-latest
817+
timeout-minutes: 60
818+
steps:
819+
- uses: actions/checkout@v4
820+
with:
821+
fetch-depth: 0
822+
filter: tree:0
823+
submodules: recursive
824+
825+
- uses: actions/setup-node@v4
826+
with:
827+
node-version-file: '.nvmrc'
828+
829+
- name: Cache node modules
830+
uses: actions/cache@v4
831+
env:
832+
cache-name: node
833+
with:
834+
path: |
835+
common/temp
836+
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
837+
restore-keys: |
838+
${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
839+
840+
- name: Prepare .npmrc for GitHub Packages
841+
run: |
842+
echo "//npm.pkg.github.com/:_authToken=${{secrets.GITHUB_TOKEN}}" > ~/.npmrc
843+
844+
- name: Installing...
845+
run: node common/scripts/install-run-rush.js install --purge
846+
- name: Model version from git tags
847+
run: node common/scripts/install-run-rush.js model-version
848+
- name: Package
849+
run: node common/scripts/install-run-rush.js package --to qms-desktop -v
850+
- name: Package JSON
851+
run: |
852+
cd qms-desktop-package
853+
cat ./package.json
854+
- name: Install the Apple certificate and provisioning profile
855+
env:
856+
DEV_ID_P12_BASE64: ${{ secrets.DEV_ID_P12_BASE64 }}
857+
DEV_ID_P12_PASSWORD: ${{ secrets.DEV_ID_P12_PASSWORD }}
858+
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
859+
run: |
860+
# create variables
861+
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
862+
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
863+
864+
# import certificate from secret
865+
echo -n "$DEV_ID_P12_BASE64" | base64 --decode -o $CERTIFICATE_PATH
866+
867+
# create temporary keychain
868+
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
869+
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
870+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
871+
872+
# import certificate to keychain
873+
security import $CERTIFICATE_PATH -P "$DEV_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
874+
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
875+
security list-keychain -d user -s $KEYCHAIN_PATH
876+
- name: Build distribution's
877+
env:
878+
APPLE_ID: ${{ secrets.APPLE_ID }}
879+
APPLE_ID_APP_PASS: ${{ secrets.APPLE_ID_APP_PASS }}
880+
TEAM_ID: ${{ secrets.TEAM_ID }}
881+
run: |
882+
cd qms-desktop-package
883+
node ../common/scripts/install-run-rushx.js dist --linux --x64
884+
node ../common/scripts/install-run-rushx.js dist --windows --x64 --arm64
885+
node ../common/scripts/install-run-rushx.js dist-signed --macos --x64 --arm64
886+
./scripts/copy-publish-artifacts.sh ${{ env.PublishTempFolder}}
887+
- name: Publish distribution assets and version
888+
uses: ryand56/r2-upload-action@latest
889+
with:
890+
r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
891+
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
892+
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
893+
r2-bucket: desktop-distro
894+
source-dir: qms-desktop-package/${{ env.PublishTempFolder}}
895+
destination-dir: ./
896+
- name: Upload MacOS
897+
uses: actions/upload-artifact@v4
898+
with:
899+
name: TraceX-MacOS-x64
900+
path: ./qms-desktop-package/deploy/TraceX-macos-*-x64.dmg
901+
- name: Upload MacOS arm64
902+
uses: actions/upload-artifact@v4
903+
with:
904+
name: TraceX-MacOS-arm64
905+
path: ./qms-desktop-package/deploy/TraceX-macos-*-arm64.dmg
906+
- name: Upload Windows
907+
uses: actions/upload-artifact@v4
908+
with:
909+
name: TraceX-Windows
910+
path: ./qms-desktop-package/deploy/TraceX-windows-*.zip
911+
- name: Upload Linux
912+
uses: actions/upload-artifact@v4
913+
with:
914+
name: TraceX-Linux
915+
path: ./qms-desktop-package/deploy/TraceX-linux-*.zip

common/config/rush/pnpm-lock.yaml

Lines changed: 25 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop-package/readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
## Automatomatic updates
1+
## Automatic updates
22

3-
All builds are published to R2 storage bucket avaialble at https://dist.huly.io
3+
All builds are published to R2 storage bucket available at https://dist.huly.io
44

55
To check the latest auto-updatable distributions see
66

desktop/src/main/config.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Copyright © 2025 Hardcore Engineering Inc.
3+
//
4+
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License. You may
6+
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
//
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
import * as path from 'path'
16+
import * as fs from 'fs'
17+
18+
interface PackedConfig {
19+
server?: string
20+
updatesChannelKey?: string
21+
}
22+
23+
const configPath = path.join(process.resourcesPath, 'config/config.json')
24+
25+
export function readPackedConfig (): PackedConfig | undefined {
26+
if (fs.existsSync(configPath)) {
27+
try {
28+
return JSON.parse(fs.readFileSync(configPath, 'utf8')) as PackedConfig
29+
} catch (err) {
30+
console.log('Failed to read packed config', err)
31+
}
32+
}
33+
}

desktop/src/main/start.ts

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import autoUpdater from './updater'
3131
import { generateId } from '@hcengineering/core'
3232
import { DownloadItem } from '@hcengineering/desktop-downloads'
3333
import { rebuildJumpList, setupWindowsSpecific } from './windowsSpecificSetup'
34-
34+
import { readPackedConfig } from './config'
3535

3636
let mainWindow: BrowserWindow | undefined
3737
let winBadge: any
@@ -47,6 +47,9 @@ const preloadScriptPath = path.join(app.getAppPath(), 'dist', 'main', 'preload.j
4747
const defaultWidth = 1440
4848
const defaultHeight = 960
4949

50+
const packedConfig = readPackedConfig()
51+
log.info('packed config', packedConfig)
52+
5053
const envPath = path.join(app.getAppPath(), isDev ? '.env-dev' : '.env')
5154
console.log('do loading env from', envPath)
5255
dotenvConfig({
@@ -74,7 +77,7 @@ function readServerUrl (): string {
7477
return process.env.FRONT_URL ?? 'http://huly.local:8087'
7578
}
7679

77-
return ((settings as any).get('server', process.env.FRONT_URL) as string) ?? 'https://huly.app'
80+
return ((settings as any).get('server') as string) ?? packedConfig?.server ?? process.env.FRONT_URL ?? 'https://huly.app'
7881
}
7982

8083
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
@@ -94,8 +97,8 @@ const disabledFeatures = [
9497

9598
app.commandLine.appendSwitch('disable-features', disabledFeatures.join(','))
9699

97-
function setupWindowTitleBar(windowOptions: Electron.BrowserWindowConstructorOptions): void {
98-
if (isWindows) {
100+
function setupWindowTitleBar (windowOptions: Electron.BrowserWindowConstructorOptions): void {
101+
if (isWindows) {
99102
// on Windows we use frameless window with custom hand-made title bar
100103
windowOptions.frame = false
101104
} else {
@@ -269,31 +272,31 @@ const createWindow = async (): Promise<void> => {
269272
}
270273
})
271274

272-
function sendWindowMaximizedMessage(maximized: boolean): void {
275+
function sendWindowMaximizedMessage (maximized: boolean): void {
273276
mainWindow?.webContents.send('window-state-changed', maximized ? 'maximized' : 'unmaximized')
274277
}
275278

276279
mainWindow.on('blur', () => {
277280
mainWindow?.webContents.send('window-focus-loss')
278-
});
281+
})
279282

280283
mainWindow.on('maximize', () => {
281284
sendWindowMaximizedMessage(true)
282-
});
285+
})
283286

284287
mainWindow.on('unmaximize', () => {
285288
sendWindowMaximizedMessage(false)
286-
});
289+
})
287290

288291
mainWindow.on('enter-full-screen', () => {
289292
sendWindowMaximizedMessage(true)
290-
});
293+
})
291294

292295
mainWindow.on('leave-full-screen', () => {
293-
if (mainWindow) {
294-
sendWindowMaximizedMessage(mainWindow.isMaximized())
296+
if (mainWindow != null) {
297+
sendWindowMaximizedMessage(mainWindow.isMaximized())
295298
}
296-
});
299+
})
297300

298301
if (isMac) {
299302
mainWindow.on('close', (event) => {
@@ -309,12 +312,12 @@ const createWindow = async (): Promise<void> => {
309312
}
310313
}
311314

312-
function sendCommand(cmd: string, ...args: any[]): void {
315+
function sendCommand (cmd: string, ...args: any[]): void {
313316
mainWindow?.webContents.send(cmd, ...args)
314317
}
315318

316319
function activateWindow (): void {
317-
if (mainWindow) {
320+
if (mainWindow != null) {
318321
if (mainWindow.isMinimized()) {
319322
mainWindow.restore()
320323
}
@@ -373,7 +376,23 @@ ipcMain.on('set-combined-config', (_event: any, config: Config) => {
373376
setupCookieHandler(config)
374377

375378
const updatesUrl = process.env.DESKTOP_UPDATES_URL ?? config.DESKTOP_UPDATES_URL ?? 'https://dist.huly.io'
376-
const updatesChannel = process.env.DESKTOP_UPDATES_CHANNEL ?? config.DESKTOP_UPDATES_CHANNEL ?? 'huly'
379+
// NOTE: env format is: default_value;key1:value1;key2:value2...
380+
const updatesChannels = (process.env.DESKTOP_UPDATES_CHANNEL ?? config.DESKTOP_UPDATES_CHANNEL ?? 'huly').split(';').map(c => c.trim().split(':'))
381+
const updateChannelsMap: Record<string, string> = {}
382+
for (const channelInfo of updatesChannels) {
383+
if (channelInfo.length === 1) {
384+
updateChannelsMap.default = channelInfo[0]
385+
} else if (channelInfo.length === 2) {
386+
const [key, value] = channelInfo
387+
updateChannelsMap[key] = value
388+
}
389+
}
390+
391+
const updatesChannelKey = packedConfig?.updatesChannelKey ?? 'default'
392+
const updatesChannel = updateChannelsMap[updatesChannelKey] ?? updateChannelsMap.default ?? 'huly'
393+
394+
log.info('updates channels', updatesChannels)
395+
log.info('updates channel', updatesChannelKey, updatesChannel)
377396

378397
autoUpdater.setFeedURL({
379398
provider: 'generic',
@@ -414,35 +433,35 @@ ipcMain.on('set-front-cookie', function (event: any, host: string, name: string,
414433
})
415434

416435
ipcMain.handle('window-minimize', () => {
417-
mainWindow?.minimize();
418-
});
436+
mainWindow?.minimize()
437+
})
419438

420439
ipcMain.handle('window-maximize', () => {
421-
if (mainWindow) {
440+
if (mainWindow != null) {
422441
if (mainWindow.isMaximized()) {
423-
mainWindow.unmaximize();
442+
mainWindow.unmaximize()
424443
} else {
425-
mainWindow.maximize();
444+
mainWindow.maximize()
426445
}
427446
}
428-
});
447+
})
429448

430449
ipcMain.handle('window-close', () => {
431-
mainWindow?.close();
432-
});
450+
mainWindow?.close()
451+
})
433452

434453
ipcMain.handle('get-is-os-using-dark-theme', () => {
435-
return nativeTheme.shouldUseDarkColors;
436-
});
454+
return nativeTheme.shouldUseDarkColors
455+
})
437456

438457
ipcMain.handle('menu-action', async (_event: any, action: MenuBarAction) => {
439458
dipatchMenuBarAction(mainWindow, action)
440-
});
459+
})
441460

442461
if (isWindows) {
443462
ipcMain.on('rebuild-user-jump-list', (_event: any, spares: JumpListSpares) => {
444463
rebuildJumpList(spares)
445-
});
464+
})
446465
}
447466

448467
const gotTheLock = app.requestSingleInstanceLock()

0 commit comments

Comments
 (0)