diff --git a/.env.development b/.env.development index 20c146ae2f..449af161a2 100644 --- a/.env.development +++ b/.env.development @@ -2,8 +2,8 @@ NODE_ENV=development DEV=true VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands VITE_KC_API_BASE_URL=https://api.dev.zoo.dev -BASE_URL=https://api.dev.zoo.dev VITE_KC_SITE_BASE_URL=https://dev.zoo.dev +VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev VITE_KC_SKIP_AUTH=false VITE_KC_CONNECTION_TIMEOUT_MS=5000 # ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence! diff --git a/.env.production b/.env.production index 2aeee4e481..257c1c5c7a 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,8 @@ +NODE_ENV=production +DEV=false VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands VITE_KC_API_BASE_URL=https://api.zoo.dev VITE_KC_SITE_BASE_URL=https://zoo.dev +VITE_KC_SITE_APP_URL=https://app.zoo.dev VITE_KC_SKIP_AUTH=false VITE_KC_CONNECTION_TIMEOUT_MS=15000 diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 73543316fd..20e4aeb5ac 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -134,8 +134,6 @@ jobs: max_attempts: 3 command: yarn install - - run: yarn tronb:vite - - name: Prepare certificate and variables (Windows only) if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }} run: | @@ -165,8 +163,8 @@ jobs: - name: Build the app (debug) if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }} # electron-builder doesn't have a concept of release vs debug, - # this is just not doing any codesign or release yml generation - run: yarn electron-builder --config + # this is just not doing any codesign or release yml generation, and points to dev infra + run: yarn tronb:package:dev - name: Build the app (release) if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }} @@ -185,7 +183,7 @@ jobs: with: timeout_minutes: 10 max_attempts: 3 - command: yarn electron-builder --config --publish always + command: yarn tronb:package:prod - name: List artifacts in out/ run: ls -R out @@ -246,7 +244,7 @@ jobs: with: timeout_minutes: 10 max_attempts: 3 - command: yarn electron-builder --config --publish always + command: yarn tronb:package:prod - uses: actions/upload-artifact@v4 if: ${{ env.IS_RELEASE == 'true' }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9bea647b73..752b717406 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -123,9 +123,9 @@ jobs: if: steps.download-wasm.outcome == 'failure' shell: bash run: yarn build:wasm - - name: build electron + - name: build web shell: bash - run: yarn tron:package + run: yarn tronb:vite:dev # - name: Run ubuntu/chrome snapshots # if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }} # shell: bash diff --git a/README.md b/README.md index 66ec43de44..958fdf7c5f 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ This will start the application and hot-reload on changes. Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows). -To build, run `yarn tron:package`. +To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables) ## Checking out commits / Bisecting diff --git a/electron-builder.yml b/electron-builder.yml index c8bd0d09e7..be59a65079 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -75,3 +75,6 @@ publish: channel: latest releaseInfo: releaseNotesFile: release-notes.md +protocols: + - name: Zoo Studio + schemes: ['zoo-studio'] diff --git a/forge.config.ts b/forge.config.ts index c22ddf6fa0..5a079660eb 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -9,23 +9,8 @@ const rootDir = process.cwd() const config: ForgeConfig = { packagerConfig: { asar: true, - osxSign: (process.env.BUILD_RELEASE === 'true' && {}) || undefined, - osxNotarize: - (process.env.BUILD_RELEASE === 'true' && { - appleId: process.env.APPLE_ID || '', - appleIdPassword: process.env.APPLE_PASSWORD || '', - teamId: process.env.APPLE_TEAM_ID || '', - }) || - undefined, executableName: 'zoo-modeling-app', icon: path.resolve(rootDir, 'assets', 'icon'), - protocols: [ - { - name: 'Zoo Studio', - schemes: ['zoo-studio'], - }, - ], - extendInfo: 'Info.plist', // Information for file associations. }, rebuildConfig: {}, makers: [], diff --git a/interface.d.ts b/interface.d.ts index 443918f285..de55e53980 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -65,6 +65,7 @@ export interface IElectronAPI { VITE_KC_API_WS_MODELING_URL: string VITE_KC_API_BASE_URL: string VITE_KC_SITE_BASE_URL: string + VITE_KC_SITE_APP_URL: string VITE_KC_SKIP_AUTH: string VITE_KC_CONNECTION_TIMEOUT_MS: string VITE_KC_DEV_TOKEN: string diff --git a/package.json b/package.json index 3054523a37..37d18d88fa 100644 --- a/package.json +++ b/package.json @@ -103,11 +103,11 @@ "make:dev": "make dev", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "tron:start": "electron-forge start", - "tron:package": "electron-forge package", "chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'", - "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", - "tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", - "tronb:package": "electron-builder --config electron-builder.yml", + "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development", + "tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", + "tronb:package:dev": "yarn tronb:vite:dev && electron-builder --config electron-builder.yml", + "tronb:package:prod": "yarn tronb:vite:prod && electron-builder --config electron-builder.yml --publish always", "test-setup": "yarn install && yarn build:wasm", "test": "vitest --mode development", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts", @@ -116,10 +116,10 @@ "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet", "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet", "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet", - "test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", - "test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", - "test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", - "test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", + "test:playwright:electron:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", + "test:playwright:electron:windows:local": "yarn tronb:package:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", + "test:playwright:electron:macos:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", + "test:playwright:electron:ubuntu:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", "test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000", "test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000" }, diff --git a/src/components/OpenInDesktopAppHandler.tsx b/src/components/OpenInDesktopAppHandler.tsx index d0b2a347d7..3c34dad1e9 100644 --- a/src/components/OpenInDesktopAppHandler.tsx +++ b/src/components/OpenInDesktopAppHandler.tsx @@ -33,7 +33,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => { function onOpenInDesktopApp() { const newSearchParams = new URLSearchParams(globalThis.location.search) newSearchParams.delete(ASK_TO_OPEN_QUERY_PARAM) - const newURL = `${ZOO_STUDIO_PROTOCOL}${globalThis.location.pathname.replace( + const newURL = `${ZOO_STUDIO_PROTOCOL}://${globalThis.location.pathname.replace( '/', '' )}${searchParams.size > 0 ? `?${newSearchParams.toString()}` : ''}` diff --git a/src/components/ProjectSidebarMenu.tsx b/src/components/ProjectSidebarMenu.tsx index 6630266504..3004eb9cc0 100644 --- a/src/components/ProjectSidebarMenu.tsx +++ b/src/components/ProjectSidebarMenu.tsx @@ -19,7 +19,7 @@ import { commandBarActor } from 'machines/commandBarMachine' import { useSelector } from '@xstate/react' import { copyFileShareLink } from 'lib/links' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' -import { DEV } from 'env' +import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings' import { useToken } from 'machines/appMachine' const ProjectSidebarMenu = ({ @@ -112,6 +112,7 @@ function ProjectMenuPopover({ const { onProjectClose } = useLspContext() const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const makeCommandInfo = { name: 'Make', groupId: 'modeling' } + const shareCommandInfo = { name: 'share-file-link', groupId: 'code' } const findCommand = (obj: { name: string; groupId: string }) => Boolean( commands.find((c) => c.name === obj.name && c.groupId === obj.groupId) @@ -193,7 +194,7 @@ function ProjectMenuPopover({ id: 'share-link', Element: 'button', children: 'Share link to file', - disabled: !DEV, + disabled: IS_NIGHTLY_OR_DEBUG || !findCommand(shareCommandInfo), onClick: async () => { await copyFileShareLink({ token: token ?? '', diff --git a/src/env.ts b/src/env.ts index 1522a17171..55a402e920 100644 --- a/src/env.ts +++ b/src/env.ts @@ -10,6 +10,7 @@ export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as | undefined export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string +export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL as string export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined export const VITE_KC_CONNECTION_TIMEOUT_MS = env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 6f014e2afe..34eb568288 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -68,8 +68,6 @@ export const KCL_DEFAULT_DEGREE = `360` /** localStorage key for the playwright test-specific app settings file */ export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings' -export const DEFAULT_HOST = 'https://api.zoo.dev' -export const PROD_APP_URL = 'https://app.zoo.dev' export const SETTINGS_FILE_NAME = 'settings.toml' export const TOKEN_FILE_NAME = 'token.txt' export const PROJECT_SETTINGS_FILE_NAME = 'project.toml' @@ -145,7 +143,7 @@ export const VIEW_NAMES_SEMANTIC = { export const SIDEBAR_BUTTON_SUFFIX = '-pane-button' /** Custom URL protocol our desktop registers */ -export const ZOO_STUDIO_PROTOCOL = 'zoo-studio:' +export const ZOO_STUDIO_PROTOCOL = 'zoo-studio' /** * A query parameter that triggers a modal diff --git a/src/lib/kclCommands.ts b/src/lib/kclCommands.ts index 580d803444..72430fb5f5 100644 --- a/src/lib/kclCommands.ts +++ b/src/lib/kclCommands.ts @@ -1,11 +1,13 @@ import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning' import { Command, CommandArgumentOption } from './commandTypes' -import { kclManager } from './singletons' +import { codeManager, kclManager } from './singletons' import { isDesktop } from './isDesktop' import { FILE_EXT } from './constants' import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { reportRejection } from './trap' import { IndexLoaderData } from './types' +import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings' +import { copyFileShareLink } from './links' interface OnSubmitProps { sampleName: string @@ -132,21 +134,22 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] { }, }, }, - // { - // name: 'share-file-link', - // displayName: 'Share file', - // description: 'Create a link that contains a copy of the current file.', - // groupId: 'code', - // needsReview: false, - // icon: 'link', - // onSubmit: () => { - // copyFileShareLink({ - // token: commandProps.authToken, - // code: codeManager.code, - // name: commandProps.projectData.project?.name || '', - // units: commandProps.settings.defaultUnit, - // }).catch(reportRejection) - // }, - // }, + { + name: 'share-file-link', + displayName: 'Share file', + hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop', + description: 'Create a link that contains a copy of the current file.', + groupId: 'code', + needsReview: false, + icon: 'link', + onSubmit: () => { + copyFileShareLink({ + token: commandProps.authToken, + code: codeManager.code, + name: commandProps.projectData.project?.name || '', + units: commandProps.settings.defaultUnit, + }).catch(reportRejection) + }, + }, ] } diff --git a/src/lib/links.test.ts b/src/lib/links.test.ts index 9267e3cc9d..c3606f6024 100644 --- a/src/lib/links.test.ts +++ b/src/lib/links.test.ts @@ -1,3 +1,4 @@ +import { VITE_KC_SITE_APP_URL } from 'env' import { createCreateFileUrl } from './links' describe(`link creation tests`, () => { @@ -8,7 +9,7 @@ describe(`link creation tests`, () => { // Converted with external online tools const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D` - const expectedLink = `http://localhost:3000/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true` + const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true` const result = createCreateFileUrl({ code, name, units }) expect(result.toString()).toBe(expectedLink) diff --git a/src/lib/links.ts b/src/lib/links.ts index b17a104ba0..f24eb8286f 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -1,11 +1,7 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' -import { - ASK_TO_OPEN_QUERY_PARAM, - CREATE_FILE_URL_PARAM, - PROD_APP_URL, -} from './constants' +import { ASK_TO_OPEN_QUERY_PARAM, CREATE_FILE_URL_PARAM } from './constants' import { stringToBase64 } from './base64' -import { DEV, VITE_KC_API_BASE_URL } from 'env' +import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from 'env' import toast from 'react-hot-toast' import { err } from './trap' export interface FileLinkParams { @@ -51,8 +47,7 @@ export async function copyFileShareLink( * open the URL in the desktop app. */ export function createCreateFileUrl({ code, name, units }: FileLinkParams) { - // Use the dev server if we are in development mode - let origin = DEV ? 'http://localhost:3000' : PROD_APP_URL + let origin = VITE_KC_SITE_APP_URL const searchParams = new URLSearchParams({ [CREATE_FILE_URL_PARAM]: String(true), name, diff --git a/src/main.ts b/src/main.ts index b6cd1c538a..f853bc16e8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,23 +31,27 @@ let mainWindow: BrowserWindow | null = null // Check the command line arguments for a project path const args = parseCLIArgs() -// If it's not set, scream. -const NODE_ENV = process.env.NODE_ENV || 'production' -if (!process.env.NODE_ENV) - console.warn( - '*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production' - ) -// Default prod values +// @ts-ignore: TS1343 +const viteEnv = import.meta.env +const NODE_ENV = process.env.NODE_ENV || viteEnv.MODE // dotenv override when present dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] }) -process.env.VITE_KC_API_WS_MODELING_URL ??= - 'wss://api.zoo.dev/ws/modeling/commands' -process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev' -process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev' -process.env.VITE_KC_SKIP_AUTH ??= 'false' -process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000' +// default vite values based on mode +process.env.NODE_ENV ??= viteEnv.MODE +process.env.DEV ??= viteEnv.DEV + '' +process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL +process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL +process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL +process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL +process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL +process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH +process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= + viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS + +// Likely convenient to keep for debugging +console.log('process.env', process.env) /// Register our application to handle all "zoo-studio:" protocols. if (process.defaultApp) { @@ -89,22 +93,43 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => { }) } + // Deep Link: Case of a cold start from Windows or Linux + if ( + !pathToOpen && + process.argv.length > 1 && + process.argv[1].indexOf(ZOO_STUDIO_PROTOCOL + '://') > -1 + ) { + pathToOpen = process.argv[1] + console.log('Retrieved deep link from argv', pathToOpen) + } + + // Deep Link: Case of a second window opened for macOS + // @ts-ignore + if (!pathToOpen && global['openUrls'] && global['openUrls'][0]) { + // @ts-ignore + pathToOpen = global['openUrls'][0] + console.log('Retrieved deep link from open-url', pathToOpen) + } + const pathIsCustomProtocolLink = pathToOpen?.startsWith(ZOO_STUDIO_PROTOCOL) ?? false // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { const filteredPath = pathToOpen - ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) + ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL + '://', '')) : '' const fullHashBasedUrl = `${MAIN_WINDOW_VITE_DEV_SERVER_URL}/#/${filteredPath}` newWindow.loadURL(fullHashBasedUrl).catch(reportRejection) } else { if (pathIsCustomProtocolLink && pathToOpen) { // We're trying to open a custom protocol link - const filteredPath = pathToOpen - ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) - : '' + // TODO: fix the replace %3 thing + const urlNoProtocol = pathToOpen + .replace(ZOO_STUDIO_PROTOCOL + '://', '') + .replaceAll('%3D', '') + .replaceAll('%3', '') + const filteredPath = decodeURI(urlNoProtocol) const startIndex = path.join( __dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html` @@ -342,7 +367,7 @@ export function getAutoUpdater(): AppUpdater { app.on('ready', () => { // Disable auto updater on non-versioned builds - if (packageJSON.version === '0.0.0') { + if (packageJSON.version === '0.0.0' && viteEnv.MODE !== 'production') { return } @@ -459,6 +484,14 @@ function parseCLIArgs(): minimist.ParsedArgs { } function registerStartupListeners() { + // Linux and Windows from https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app + app.on('second-instance', (event, commandLine, workingDirectory) => { + // Deep Link: second instance for Windows and Linux + const url = commandLine.pop()?.slice(0, -1) + console.log('Retrieved deep link from commandLine', url) + createWindow(url) + }) + /** * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before * the app-ready event. We listen very early for open-file and remember this upon startup as path to open. @@ -478,7 +511,7 @@ function registerStartupListeners() { }) /** - * macOS: react to open-url requests. + * macOS: react to open-url requests (including Deep Link on second instances) */ const openUrls: string[] = [] // @ts-ignore diff --git a/src/preload.ts b/src/preload.ts index 6a01880046..0a6f3cf670 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -185,6 +185,7 @@ contextBridge.exposeInMainWorld('electron', { 'VITE_KC_API_WS_MODELING_URL', 'VITE_KC_API_BASE_URL', 'VITE_KC_SITE_BASE_URL', + 'VITE_KC_SITE_APP_URL', 'VITE_KC_SKIP_AUTH', 'VITE_KC_CONNECTION_TIMEOUT_MS', 'VITE_KC_DEV_TOKEN',