diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b21678e8d..e069f58e4f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 \ No newline at end of file diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 5499be0df9..e80ce6360f 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -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 diff --git a/desktop-package/readme.md b/desktop-package/readme.md index 39f8a7cd00..89cb85b6d9 100644 --- a/desktop-package/readme.md +++ b/desktop-package/readme.md @@ -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 diff --git a/desktop/src/main/config.ts b/desktop/src/main/config.ts new file mode 100644 index 0000000000..bd14c20136 --- /dev/null +++ b/desktop/src/main/config.ts @@ -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) + } + } +} diff --git a/desktop/src/main/start.ts b/desktop/src/main/start.ts index 0d4fcd2234..d8c4547dac 100644 --- a/desktop/src/main/start.ts +++ b/desktop/src/main/start.ts @@ -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 => { } }) - 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 => { } } -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 = {} + 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() diff --git a/desktop/src/ui/index.ts b/desktop/src/ui/index.ts index 51e3482ab8..571e0a6a0d 100644 --- a/desktop/src/ui/index.ts +++ b/desktop/src/ui/index.ts @@ -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(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(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) }) diff --git a/dev/docker-compose.min.yaml b/dev/docker-compose.min.yaml index 89d13f614d..60cd6ba635 100644 --- a/dev/docker-compose.min.yaml +++ b/dev/docker-compose.min.yaml @@ -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 diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index 5d84c2d645..2bbccdcd04 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -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 diff --git a/qms-desktop-package/.env b/qms-desktop-package/.env new file mode 100644 index 0000000000..ab09a13b80 --- /dev/null +++ b/qms-desktop-package/.env @@ -0,0 +1,3 @@ +# APPLE_ID=team-member-apple-id +# APPLE_ID_APP_PASS=team-members-app-pass +# TEAM_ID=apple-dev-team-id \ No newline at end of file diff --git a/qms-desktop-package/config/rig.json b/qms-desktop-package/config/rig.json new file mode 100644 index 0000000000..b58354496a --- /dev/null +++ b/qms-desktop-package/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "package" +} diff --git a/qms-desktop-package/entitlements.mac.plist b/qms-desktop-package/entitlements.mac.plist new file mode 100644 index 0000000000..7eb1b07f18 --- /dev/null +++ b/qms-desktop-package/entitlements.mac.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-jit + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + + diff --git a/qms-desktop-package/installer.nsh b/qms-desktop-package/installer.nsh new file mode 100644 index 0000000000..65074fdaad --- /dev/null +++ b/qms-desktop-package/installer.nsh @@ -0,0 +1,3 @@ +!macro customInit + StrCpy $INSTDIR "$LOCALAPPDATA\Programs\TraceXDesktop" +!macroend \ No newline at end of file diff --git a/qms-desktop-package/package.json b/qms-desktop-package/package.json new file mode 100644 index 0000000000..77a665ad15 --- /dev/null +++ b/qms-desktop-package/package.json @@ -0,0 +1,117 @@ +{ + "name": "qms-desktop", + "version": "0.7.158", + "main": "dist/main/electron.js", + "author": "Hardcore Engineering ", + "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" + ] +} diff --git a/qms-desktop-package/readme.md b/qms-desktop-package/readme.md new file mode 100644 index 0000000000..1cccf53561 --- /dev/null +++ b/qms-desktop-package/readme.md @@ -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 diff --git a/qms-desktop-package/scripts/copy-publish-artifacts.sh b/qms-desktop-package/scripts/copy-publish-artifacts.sh new file mode 100755 index 0000000000..5f5268fae1 --- /dev/null +++ b/qms-desktop-package/scripts/copy-publish-artifacts.sh @@ -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 diff --git a/qms-desktop-package/scripts/notarize.js b/qms-desktop-package/scripts/notarize.js new file mode 100644 index 0000000000..929fc94502 --- /dev/null +++ b/qms-desktop-package/scripts/notarize.js @@ -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 + }); +}; diff --git a/qms-desktop-package/src/AppIcon.icns b/qms-desktop-package/src/AppIcon.icns new file mode 100644 index 0000000000..399e04491e Binary files /dev/null and b/qms-desktop-package/src/AppIcon.icns differ diff --git a/qms-desktop-package/src/AppIcon.ico b/qms-desktop-package/src/AppIcon.ico new file mode 100644 index 0000000000..9d12c641b8 Binary files /dev/null and b/qms-desktop-package/src/AppIcon.ico differ diff --git a/qms-desktop-package/src/AppIcon.png b/qms-desktop-package/src/AppIcon.png new file mode 100644 index 0000000000..c879bd58d5 Binary files /dev/null and b/qms-desktop-package/src/AppIcon.png differ diff --git a/qms-desktop-package/src/config/config.json b/qms-desktop-package/src/config/config.json new file mode 100644 index 0000000000..826c2786e0 --- /dev/null +++ b/qms-desktop-package/src/config/config.json @@ -0,0 +1,4 @@ +{ + "server": "https://tracex.huly.app", + "updatesChannelKey": "tracex" +} \ No newline at end of file diff --git a/rush.json b/rush.json index ec61d4dec0..2f119b68a0 100644 --- a/rush.json +++ b/rush.json @@ -2078,6 +2078,11 @@ "projectFolder": "desktop-package", "shouldPublish": false }, + { + "packageName": "qms-desktop", + "projectFolder": "qms-desktop-package", + "shouldPublish": false + }, { "packageName": "@hcengineering/diffview", "projectFolder": "plugins/diffview",