EQMS-1548: TraceX desktop app (#9666)

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev
2025-08-12 18:01:10 +07:00
committed by GitHub
parent b33e4db9ff
commit 42c7de17af
21 changed files with 474 additions and 68 deletions
+106
View File
@@ -26,6 +26,7 @@ env:
common
desktop
desktop-package
qms-desktop-package
dev
models
packages
@@ -807,3 +808,108 @@ jobs:
with:
name: Huly-Linux
path: ./desktop-package/deploy/Huly-linux-*.zip
qms-dist-build:
# if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/s') }}
if: ${{ startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/tags/s') }}
needs: build
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
filter: tree:0
submodules: recursive
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Cache node modules
uses: actions/cache@v4
env:
cache-name: node
with:
path: |
common/temp
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Prepare .npmrc for GitHub Packages
run: |
echo "//npm.pkg.github.com/:_authToken=${{secrets.GITHUB_TOKEN}}" > ~/.npmrc
- name: Installing...
run: node common/scripts/install-run-rush.js install --purge
- name: Model version from git tags
run: node common/scripts/install-run-rush.js model-version
- name: Package
run: node common/scripts/install-run-rush.js package --to qms-desktop -v
- name: Package JSON
run: |
cd qms-desktop-package
cat ./package.json
- name: Install the Apple certificate and provisioning profile
env:
DEV_ID_P12_BASE64: ${{ secrets.DEV_ID_P12_BASE64 }}
DEV_ID_P12_PASSWORD: ${{ secrets.DEV_ID_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate from secret
echo -n "$DEV_ID_P12_BASE64" | base64 --decode -o $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$DEV_ID_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
- name: Build distribution's
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_APP_PASS: ${{ secrets.APPLE_ID_APP_PASS }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
cd qms-desktop-package
node ../common/scripts/install-run-rushx.js dist --linux --x64
node ../common/scripts/install-run-rushx.js dist --windows --x64 --arm64
node ../common/scripts/install-run-rushx.js dist-signed --macos --x64 --arm64
./scripts/copy-publish-artifacts.sh ${{ env.PublishTempFolder}}
- name: Publish distribution assets and version
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
r2-bucket: desktop-distro
source-dir: qms-desktop-package/${{ env.PublishTempFolder}}
destination-dir: ./
- name: Upload MacOS
uses: actions/upload-artifact@v4
with:
name: TraceX-MacOS-x64
path: ./qms-desktop-package/deploy/TraceX-macos-*-x64.dmg
- name: Upload MacOS arm64
uses: actions/upload-artifact@v4
with:
name: TraceX-MacOS-arm64
path: ./qms-desktop-package/deploy/TraceX-macos-*-arm64.dmg
- name: Upload Windows
uses: actions/upload-artifact@v4
with:
name: TraceX-Windows
path: ./qms-desktop-package/deploy/TraceX-windows-*.zip
- name: Upload Linux
uses: actions/upload-artifact@v4
with:
name: TraceX-Linux
path: ./qms-desktop-package/deploy/TraceX-linux-*.zip
+25 -1
View File
@@ -991,6 +991,9 @@ importers:
'@rush-temp/products-resources':
specifier: file:./projects/products-resources.tgz
version: file:projects/products-resources.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(@types/node@22.15.29)(babel-jest@29.7.0(@babel/core@7.23.9))(esbuild@0.24.2)(postcss-load-config@4.0.2(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3)))(postcss@8.5.3)(ts-node@10.9.2(@types/node@22.15.29)(typescript@5.8.3))
'@rush-temp/qms-desktop':
specifier: file:./projects/qms-desktop.tgz
version: file:projects/qms-desktop.tgz(webpack@5.97.1)
'@rush-temp/qms-doc-import-tool':
specifier: file:./projects/qms-doc-import-tool.tgz
version: file:projects/qms-doc-import-tool.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))
@@ -5458,6 +5461,10 @@ packages:
resolution: {integrity: sha512-B+3fRdypqBSE3ui4MgFE10DBtbZVLW8Lr+cQQDF7ti1GWp04Vbr0njLPtVvySOAab8pHun4xb2yVz8B31Hr5Nw==, tarball: file:projects/products.tgz}
version: 0.0.0
'@rush-temp/qms-desktop@file:projects/qms-desktop.tgz':
resolution: {integrity: sha512-euhJQtYB9bi4EajhGq+TYdDJkmGP5CyFcaAVBLPkmhhKENYJFcLd+E/QbDkWPAZZu7lP9EQ88yFHL5oRBOzyrw==, tarball: file:projects/qms-desktop.tgz}
version: 0.0.0
'@rush-temp/qms-doc-import-tool@file:projects/qms-doc-import-tool.tgz':
resolution: {integrity: sha512-sP2ZgSJ1hu7G2DG9l+/D/ZV2T/5TO3Mc5sEfb8zmoAnAqaZM11uPbsfhoJPalgC/gOQGEL7ZYdKhA5EJXU/v4g==, tarball: file:projects/qms-doc-import-tool.tgz}
version: 0.0.0
@@ -15174,7 +15181,7 @@ snapshots:
'@electron/notarize@2.5.0':
dependencies:
debug: 4.3.5
debug: 4.4.0
fs-extra: 9.1.0
promise-retry: 2.0.1
transitivePeerDependencies:
@@ -25048,6 +25055,23 @@ snapshots:
- supports-color
- ts-node
'@rush-temp/qms-desktop@file:projects/qms-desktop.tgz(webpack@5.97.1)':
dependencies:
'@electron/notarize': 2.5.0
'@types/node': 22.15.29
'@vercel/webpack-asset-relocator-loader': 1.7.4
cross-env: 7.0.3
dotenv: 16.0.3
electron: 36.3.1
electron-builder: 25.1.8
electron-squirrel-startup: 1.0.1
node-loader: 2.0.0(webpack@5.97.1)
typescript: 5.8.3
transitivePeerDependencies:
- electron-builder-squirrel-windows
- supports-color
- webpack
'@rush-temp/qms-doc-import-tool@file:projects/qms-doc-import-tool.tgz(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))':
dependencies:
'@types/domhandler': 2.4.5
+2 -2
View File
@@ -1,6 +1,6 @@
## Automatomatic updates
## Automatic updates
All builds are published to R2 storage bucket avaialble at https://dist.huly.io
All builds are published to R2 storage bucket available at https://dist.huly.io
To check the latest auto-updatable distributions see
+33
View File
@@ -0,0 +1,33 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import * as path from 'path'
import * as fs from 'fs'
interface PackedConfig {
server?: string
updatesChannelKey?: string
}
const configPath = path.join(process.resourcesPath, 'config/config.json')
export function readPackedConfig (): PackedConfig | undefined {
if (fs.existsSync(configPath)) {
try {
return JSON.parse(fs.readFileSync(configPath, 'utf8')) as PackedConfig
} catch (err) {
console.log('Failed to read packed config', err)
}
}
}
+46 -27
View File
@@ -31,7 +31,7 @@ import autoUpdater from './updater'
import { generateId } from '@hcengineering/core'
import { DownloadItem } from '@hcengineering/desktop-downloads'
import { rebuildJumpList, setupWindowsSpecific } from './windowsSpecificSetup'
import { readPackedConfig } from './config'
let mainWindow: BrowserWindow | undefined
let winBadge: any
@@ -47,6 +47,9 @@ const preloadScriptPath = path.join(app.getAppPath(), 'dist', 'main', 'preload.j
const defaultWidth = 1440
const defaultHeight = 960
const packedConfig = readPackedConfig()
log.info('packed config', packedConfig)
const envPath = path.join(app.getAppPath(), isDev ? '.env-dev' : '.env')
console.log('do loading env from', envPath)
dotenvConfig({
@@ -74,7 +77,7 @@ function readServerUrl (): string {
return process.env.FRONT_URL ?? 'http://huly.local:8087'
}
return ((settings as any).get('server', process.env.FRONT_URL) as string) ?? 'https://huly.app'
return ((settings as any).get('server') as string) ?? packedConfig?.server ?? process.env.FRONT_URL ?? 'https://huly.app'
}
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
@@ -94,8 +97,8 @@ const disabledFeatures = [
app.commandLine.appendSwitch('disable-features', disabledFeatures.join(','))
function setupWindowTitleBar(windowOptions: Electron.BrowserWindowConstructorOptions): void {
if (isWindows) {
function setupWindowTitleBar (windowOptions: Electron.BrowserWindowConstructorOptions): void {
if (isWindows) {
// on Windows we use frameless window with custom hand-made title bar
windowOptions.frame = false
} else {
@@ -269,31 +272,31 @@ const createWindow = async (): Promise<void> => {
}
})
function sendWindowMaximizedMessage(maximized: boolean): void {
function sendWindowMaximizedMessage (maximized: boolean): void {
mainWindow?.webContents.send('window-state-changed', maximized ? 'maximized' : 'unmaximized')
}
mainWindow.on('blur', () => {
mainWindow?.webContents.send('window-focus-loss')
});
})
mainWindow.on('maximize', () => {
sendWindowMaximizedMessage(true)
});
})
mainWindow.on('unmaximize', () => {
sendWindowMaximizedMessage(false)
});
})
mainWindow.on('enter-full-screen', () => {
sendWindowMaximizedMessage(true)
});
})
mainWindow.on('leave-full-screen', () => {
if (mainWindow) {
sendWindowMaximizedMessage(mainWindow.isMaximized())
if (mainWindow != null) {
sendWindowMaximizedMessage(mainWindow.isMaximized())
}
});
})
if (isMac) {
mainWindow.on('close', (event) => {
@@ -309,12 +312,12 @@ const createWindow = async (): Promise<void> => {
}
}
function sendCommand(cmd: string, ...args: any[]): void {
function sendCommand (cmd: string, ...args: any[]): void {
mainWindow?.webContents.send(cmd, ...args)
}
function activateWindow (): void {
if (mainWindow) {
if (mainWindow != null) {
if (mainWindow.isMinimized()) {
mainWindow.restore()
}
@@ -373,7 +376,23 @@ ipcMain.on('set-combined-config', (_event: any, config: Config) => {
setupCookieHandler(config)
const updatesUrl = process.env.DESKTOP_UPDATES_URL ?? config.DESKTOP_UPDATES_URL ?? 'https://dist.huly.io'
const updatesChannel = process.env.DESKTOP_UPDATES_CHANNEL ?? config.DESKTOP_UPDATES_CHANNEL ?? 'huly'
// NOTE: env format is: default_value;key1:value1;key2:value2...
const updatesChannels = (process.env.DESKTOP_UPDATES_CHANNEL ?? config.DESKTOP_UPDATES_CHANNEL ?? 'huly').split(';').map(c => c.trim().split(':'))
const updateChannelsMap: Record<string, string> = {}
for (const channelInfo of updatesChannels) {
if (channelInfo.length === 1) {
updateChannelsMap.default = channelInfo[0]
} else if (channelInfo.length === 2) {
const [key, value] = channelInfo
updateChannelsMap[key] = value
}
}
const updatesChannelKey = packedConfig?.updatesChannelKey ?? 'default'
const updatesChannel = updateChannelsMap[updatesChannelKey] ?? updateChannelsMap.default ?? 'huly'
log.info('updates channels', updatesChannels)
log.info('updates channel', updatesChannelKey, updatesChannel)
autoUpdater.setFeedURL({
provider: 'generic',
@@ -414,35 +433,35 @@ ipcMain.on('set-front-cookie', function (event: any, host: string, name: string,
})
ipcMain.handle('window-minimize', () => {
mainWindow?.minimize();
});
mainWindow?.minimize()
})
ipcMain.handle('window-maximize', () => {
if (mainWindow) {
if (mainWindow != null) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
mainWindow.unmaximize()
} else {
mainWindow.maximize();
mainWindow.maximize()
}
}
});
})
ipcMain.handle('window-close', () => {
mainWindow?.close();
});
mainWindow?.close()
})
ipcMain.handle('get-is-os-using-dark-theme', () => {
return nativeTheme.shouldUseDarkColors;
});
return nativeTheme.shouldUseDarkColors
})
ipcMain.handle('menu-action', async (_event: any, action: MenuBarAction) => {
dipatchMenuBarAction(mainWindow, action)
});
})
if (isWindows) {
ipcMain.on('rebuild-user-jump-list', (_event: any, spares: JumpListSpares) => {
rebuildJumpList(spares)
});
})
}
const gotTheLock = app.requestSingleInstanceLock()
+33 -36
View File
@@ -3,7 +3,7 @@ import { loveId } from '@hcengineering/love'
import { timeId } from '@hcengineering/time'
import { getEmbeddedLabel, getMetadata, translate } from '@hcengineering/platform'
import presentation, { MessageBox, setDownloadProgress } from '@hcengineering/presentation'
import presentation, { MessageBox, setDownloadProgress, getClient } from '@hcengineering/presentation'
import setting, { settingId } from '@hcengineering/setting'
import {
closePanel,
@@ -19,25 +19,21 @@ import {
import { handleDownloadItem } from '@hcengineering/desktop-downloads'
import notification, { notificationId } from '@hcengineering/notification'
import { inboxId } from '@hcengineering/inbox'
import { workbenchId, logOut } from '@hcengineering/workbench'
import workbench, { workbenchId, logOut } from '@hcengineering/workbench'
import { encodeObjectURI } from '@hcengineering/view'
import { resolveLocation } from '@hcengineering/notification-resources'
import { isOwnerOrMaintainer } from '@hcengineering/core'
import { isOwnerOrMaintainer, getCurrentAccount } from '@hcengineering/core'
import { configurePlatform } from './platform'
import { setupTitleBarMenu } from './titleBarMenu'
import { defineScreenShare, defineGetDisplayMedia } from './screenShare'
import { CommandLogout, CommandSelectWorkspace, CommandOpenSettings, CommandOpenInbox, CommandOpenPlanner, CommandOpenOffice, CommandOpenApplication, LaunchApplication, NotificationParams } from './types'
import { ipcMainExposed } from './typesUtils'
import { themeStore } from '@hcengineering/theme'
import { getClient } from '@hcengineering/presentation'
import type { Application } from '@hcengineering/workbench'
import { isAllowedToRole } from '@hcengineering/workbench-resources'
import workbench from '@hcengineering/workbench'
import { getCurrentAccount } from '@hcengineering/core'
function currentOsIsWindows(): boolean {
function currentOsIsWindows (): boolean {
return (window as any).windowsPlatform === true
}
@@ -45,12 +41,11 @@ defineScreenShare()
defineGetDisplayMedia()
window.addEventListener('DOMContentLoaded', () => {
const ipcMain = ipcMainExposed()
if (currentOsIsWindows()) {
const titleBarRoot = document.getElementById('desktop-app-titlebar-root')
if (titleBarRoot) {
if (titleBarRoot != null) {
const menuBar = setupTitleBarMenu(ipcMain, titleBarRoot)
themeStore.subscribe((themeOptions) => {
@@ -63,49 +58,51 @@ window.addEventListener('DOMContentLoaded', () => {
void ipcMain.isOsUsingDarkTheme().then((isDarkTheme) => {
menuBar.setTheme(isDarkTheme ? 'dark' : 'light')
}).catch(() => {
menuBar.setTheme('light'); // fallback
menuBar.setTheme('light') // fallback
})
}
}
const onWorkbenchConnect = currentOsIsWindows() ? async () => {
const client = getClient()
const account = getCurrentAccount()
const excludedApps = getMetadata(workbench.metadata.ExcludedApplications) ?? []
const onWorkbenchConnect = currentOsIsWindows()
? async () => {
const client = getClient()
const account = getCurrentAccount()
const excludedApps = getMetadata(workbench.metadata.ExcludedApplications) ?? []
const applications: Application[] = client
.getModel()
.findAllSync<Application>(workbench.class.Application, { hidden: false })
.filter((it: Application) => !excludedApps.includes(it._id))
.filter((it: Application) => isAllowedToRole(it.accessLevel, account))
const applications: Application[] = client
.getModel()
.findAllSync<Application>(workbench.class.Application, { hidden: false })
.filter((it: Application) => !excludedApps.includes(it._id))
.filter((it: Application) => isAllowedToRole(it.accessLevel, account))
const tasks: LaunchApplication[] = [];
const tasks: LaunchApplication[] = []
for (const application of applications) {
for (const application of applications) {
const title = await translate(application.label, {})
tasks.push({
title: title,
title,
id: application._id,
alias: application.alias,
});
alias: application.alias
})
}
ipcMain.rebuildJumpList({
applications: tasks,
settingsLabel: await translate(setting.string.Settings, {}),
inboxLabel: await translate(notification.string.Inbox, {})
})
}
ipcMain.rebuildJumpList({
applications: tasks,
settingsLabel: await translate(setting.string.Settings, {}),
inboxLabel: await translate(notification.string.Inbox, {}),
})
} : undefined
: undefined
void configurePlatform(onWorkbenchConnect).then((parameters) => {
const windowTitle = document.getElementById('application-title-bar-caption')
if (windowTitle) {
if (windowTitle != null) {
windowTitle.textContent = parameters.getBranding().getTitle()
}
createApp(document.body)
})
function openScreen(screenId: any): void {
function openScreen (screenId: any): void {
closePopup()
closePanel()
const loc = getCurrentResolvedLocation()
@@ -115,9 +112,9 @@ window.addEventListener('DOMContentLoaded', () => {
loc.path.length = 3
navigate(loc)
}
ipcMain.on(CommandOpenSettings, () => {
openScreen(settingId)
openScreen(settingId)
})
ipcMain.on(CommandOpenInbox, () => {
@@ -127,7 +124,7 @@ window.addEventListener('DOMContentLoaded', () => {
ipcMain.on(CommandOpenOffice, () => {
openScreen(loveId)
})
ipcMain.on(CommandOpenPlanner, () => {
openScreen(timeId)
})
+1 -1
View File
@@ -198,7 +198,7 @@ services:
- SIGN_URL=http://huly.local:4006
# - ANALYTICS_COLLECTOR_URL=http://huly.local:4017
- DESKTOP_UPDATES_URL=https://dist.huly.io
- DESKTOP_UPDATES_CHANNEL=dev
- DESKTOP_UPDATES_CHANNEL=dev;tracex:dev-tracex
- BRANDING_URL=http://huly.local:8087/branding.json
- STREAM_URL=http://huly.local:1080/recording
# - DISABLE_SIGNUP=true
+1 -1
View File
@@ -252,7 +252,7 @@ services:
- SIGN_URL=http://huly.local:4006
# - ANALYTICS_COLLECTOR_URL=http://huly.local:4017
- DESKTOP_UPDATES_URL=https://dist.huly.io
- DESKTOP_UPDATES_CHANNEL=dev
- DESKTOP_UPDATES_CHANNEL=dev;tracex:dev-tracex
- BRANDING_URL=http://huly.local:8087/branding.json
- STREAM_URL=http://huly.local:1080/recording
- COMMUNICATION_API_ENABLED=true
+3
View File
@@ -0,0 +1,3 @@
# APPLE_ID=team-member-apple-id
# APPLE_ID_APP_PASS=team-members-app-pass
# TEAM_ID=apple-dev-team-id
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "package"
}
@@ -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.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
+3
View File
@@ -0,0 +1,3 @@
!macro customInit
StrCpy $INSTDIR "$LOCALAPPDATA\Programs\TraceXDesktop"
!macroend
+117
View File
@@ -0,0 +1,117 @@
{
"name": "qms-desktop",
"version": "0.7.158",
"main": "dist/main/electron.js",
"author": "Hardcore Engineering <hey@huly.io>",
"template": "@hcengineering/default-package",
"scripts": {
"_phase:package": "rushx package",
"package": "rushx bump && rm -rf ./dist && cp -r ../desktop/dist . && cp ../desktop/.env ./dist && cp src/AppIcon.png ./dist/ui/public && cp src/AppIcon.ico ./dist/ui/public && echo 'done'",
"dist": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false NODE_ENV=production VERSION=$(node ../common/scripts/show_tag.js) MODEL_VERSION=$(node ../common/scripts/show_version.js) electron-builder build -p onTag",
"dist-local": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false NODE_ENV=production VERSION=$(node ../common/scripts/show_tag.js) MODEL_VERSION=$(node ../common/scripts/show_version.js) electron-builder build",
"dist-signed": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=true NODE_ENV=production VERSION=$(node ../common/scripts/show_tag.js) MODEL_VERSION=$(node ../common/scripts/show_version.js) electron-builder build -c.afterSign=scripts/notarize.js -p onTag",
"dist-mac-win": "cross-env CSC_IDENTITY_AUTO_DISCOVERY=false NODE_ENV=production VERSION=$(node ../common/scripts/show_tag.js) MODEL_VERSION=$(node ../common/scripts/show_version.js) electron-builder build --windows --x64",
"format": "echo done",
"bump": "bump-package-version"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@hcengineering/desktop": "^0.6.0",
"@vercel/webpack-asset-relocator-loader": "^1.7.3",
"node-loader": "~2.0.0",
"cross-env": "~7.0.3",
"typescript": "^5.8.3",
"electron": "^36.3.1",
"@types/node": "^22.15.29",
"electron-builder": "^25.1.8",
"@electron/notarize": "^2.3.2"
},
"dependencies": {
"electron-squirrel-startup": "~1.0.0",
"dotenv": "~16.0.0"
},
"homepage": "https://tracex.co/",
"productName": "TraceX Desktop",
"description": "TraceX Desktop experience",
"build": {
"productName": "TraceX",
"appId": "app.huly.TraceX",
"directories": {
"output": "deploy"
},
"files": [
"./dist/**"
],
"extraResources": [
{
"from": "./src/config/",
"to": "./config/",
"filter": [
"**/*.json"
]
}
],
"mac": {
"category": "public.app-category.utilities",
"artifactName": "TraceX-macos-${version}-${arch}.${ext}",
"target": "default",
"icon": "./src/AppIcon.icns",
"hardenedRuntime": true,
"entitlements": "./entitlements.mac.plist",
"entitlementsInherit": "./entitlements.mac.plist",
"notarize": false,
"publish": {
"provider": "generic",
"url": "https://dist.huly.io",
"channel": "qms-latest"
}
},
"win": {
"target": [
"zip",
"nsis"
],
"artifactName": "TraceX-windows-${version}.${ext}",
"verifyUpdateCodeSignature": false,
"icon": "./src/AppIcon.ico",
"publish": {
"provider": "generic",
"url": "https://dist.huly.io",
"channel": "qms-latest"
},
"extraResources": [
{
"from": "./dist/ui/public/icons/",
"to": "./icons/",
"filter": [
"**/*.ico"
]
}
]
},
"nsis": {
"include": "installer.nsh",
"installerIcon": "./src/AppIcon.ico",
"uninstallerIcon": "./src/AppIcon.ico"
},
"linux": {
"artifactName": "TraceX-linux-${version}.${ext}",
"target": [
"AppImage",
"deb",
"zip"
],
"category": "Utility",
"publish": {
"provider": "generic",
"url": "https://dist.huly.io",
"channel": "qms-latest"
}
}
},
"keywords": [
"electron",
"typescript",
"svelte"
]
}
+9
View File
@@ -0,0 +1,9 @@
## Automatic updates
All builds are published to R2 storage bucket available at https://dist.huly.io
To check the latest auto-updatable distributions see
* MacOS: https://dist.huly.io/qms-latest-mac.yml
* Linux: https://dist.huly.io/qms-latest-linux.yml
* Windows: https://dist.huly.io/qms-latest.yml
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
SRC_FOLDER=deploy
TARGET_FOLDER=$1
CHANNEL=qms-latest
set -e
if [ -d "$TARGET_FOLDER" ]; then rm -Rf $TARGET_FOLDER; fi
mkdir $TARGET_FOLDER
cp $SRC_FOLDER/*.blockmap $TARGET_FOLDER
cp $SRC_FOLDER/*.dmg $TARGET_FOLDER
cp $SRC_FOLDER/*.zip $TARGET_FOLDER
cp $SRC_FOLDER/*.AppImage $TARGET_FOLDER
cp $SRC_FOLDER/*.deb $TARGET_FOLDER
cp $SRC_FOLDER/*.exe $TARGET_FOLDER
cp $SRC_FOLDER/$CHANNEL.yml $TARGET_FOLDER
cp $SRC_FOLDER/$CHANNEL-mac.yml $TARGET_FOLDER
cp $SRC_FOLDER/$CHANNEL-linux.yml $TARGET_FOLDER
# Create version-specific description files
rawVersion=$(node ../common/scripts/show_tag.js)
version=${rawVersion:1:${#rawVersion}-2}
cp $SRC_FOLDER/$CHANNEL.yml $TARGET_FOLDER/qms-${version}.yml
cp $SRC_FOLDER/$CHANNEL-mac.yml $TARGET_FOLDER/qms-${version}-mac.yml
cp $SRC_FOLDER/$CHANNEL-linux.yml $TARGET_FOLDER/qms-${version}-linux.yml
+41
View File
@@ -0,0 +1,41 @@
require('dotenv').config();
const { notarize } = require('@electron/notarize');
async function retryNotarize(options, retries = 5, delay = 5000) {
for (let i = 0; i < retries; i++) {
try {
console.log(`Attempt ${i + 1} to notarize...`);
await notarize(options);
console.log('Notarization successful');
return;
} catch (error) {
console.error(`Notarization attempt ${i + 1} failed:`, error);
if (i < retries - 1) {
console.log(`Retrying in ${delay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Increase delay for the next retry
} else {
console.log('All notarization attempts failed...');
// Add any necessary teardown logic here
throw error;
}
}
}
}
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
return;
}
const appName = context.packager.appInfo.productFilename;
console.log('Starting custom notarization process...');
await retryNotarize({
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_APP_PASS,
teamId: process.env.TEAM_ID
});
};
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 KiB

@@ -0,0 +1,4 @@
{
"server": "https://tracex.huly.app",
"updatesChannelKey": "tracex"
}
+5
View File
@@ -2078,6 +2078,11 @@
"projectFolder": "desktop-package",
"shouldPublish": false
},
{
"packageName": "qms-desktop",
"projectFolder": "qms-desktop-package",
"shouldPublish": false
},
{
"packageName": "@hcengineering/diffview",
"projectFolder": "plugins/diffview",