Fix kcl file opening on Windows (double click) on second instance (#5420)
* WIP: Double-clicking on .kcl file on Windows redirects to the home page if the app is already open Fixes #5412 * Add deep link test case for linux * Add mac tests * Lint and win tests * Fix e2e tests * Logs everywhere * windows weird? yup * More logzzz maybe it's not windows * Remove :/// replacement. Add catch log * Fix and clean up * FIx lint * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * More lint * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Clean up tests further --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB  | 
							
								
								
									
										82
									
								
								src/commandLineArgs.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/commandLineArgs.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
import { getPathOrUrlFromArgs, parseCLIArgs } from 'commandLineArgs'
 | 
			
		||||
 | 
			
		||||
const linuxDeepLinkArgv = [
 | 
			
		||||
  '/tmp/.mount_Zoo Movq3t0x/zoo-modeling-app',
 | 
			
		||||
  '--no-sandbox',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
  'zoo-studio://?create-file=true&name=deeplinks&code=cGxhbmUwMDEgPSBvZmZzZXRQbGFuZSgnWFonLCBvZmZzZXQgPSA1KQ%3D%3D',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const linuxNoPathArgv = [
 | 
			
		||||
  '/tmp/.mount_Zoo MogQS2hd/zoo-modeling-app',
 | 
			
		||||
  '--no-sandbox',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const linuxPathArgv = [
 | 
			
		||||
  '/tmp/.mount_Zoo MogQS2hd/zoo-modeling-app',
 | 
			
		||||
  '--no-sandbox',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
  '/home/pierremtb/Documents/zoo-modeling-app-projects/project-001/main.kcl',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const winDeepLinkArgv = [
 | 
			
		||||
  'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
  'zoo-studio:///?create-file=true&name=deeplinkscopy&code=cGxhbmUwMDEgPSBvZmZzZXRQbGFuZSgnWFonLCBvZmZzZXQgPSA1KQo%3D',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const winNoPathArgv = [
 | 
			
		||||
  'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const winPathArgv = [
 | 
			
		||||
  'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
 | 
			
		||||
  '--allow-file-access-from-files',
 | 
			
		||||
  'C:\\Users\\pierr\\Documents\\zoo-modeling-app-projects\\deeplink\\main.kcl',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
// macos doesn't uses the open-url scheme so is different so no macDeepLinkArgv
 | 
			
		||||
 | 
			
		||||
const macNoPathArgv = [
 | 
			
		||||
  '/Applications/Zoo Modeling App.app/Contents/MacOS/Zoo Modeling App',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const macPathArgv = [
 | 
			
		||||
  '/Applications/Zoo Modeling App.app/Contents/MacOS/Zoo Modeling App',
 | 
			
		||||
  '/Users/pierremtb/Documents/zoo-modeling-app-projects/loft/main.kcl',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
describe('getPathOrUrlFromArgs', () => {
 | 
			
		||||
  ;[
 | 
			
		||||
    ['linux', linuxDeepLinkArgv],
 | 
			
		||||
    ['windows', winDeepLinkArgv],
 | 
			
		||||
    // macos doesn't uses the open-url scheme so is different
 | 
			
		||||
  ].map(([os, argv]) => {
 | 
			
		||||
    it(`should parse second-instance deep link argv on ${os}`, () => {
 | 
			
		||||
      const args = parseCLIArgs(argv as string[])
 | 
			
		||||
      expect(getPathOrUrlFromArgs(args)).toContain('zoo-studio://')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  ;[
 | 
			
		||||
    ['linux', linuxPathArgv],
 | 
			
		||||
    ['windows', winPathArgv],
 | 
			
		||||
    ['mac', macPathArgv],
 | 
			
		||||
  ].map(([os, argv]) => {
 | 
			
		||||
    it(`should parse path argv on ${os}`, () => {
 | 
			
		||||
      const args = parseCLIArgs(argv as string[])
 | 
			
		||||
      expect(getPathOrUrlFromArgs(args)).toContain('main.kcl')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  ;[
 | 
			
		||||
    ['linux', linuxNoPathArgv],
 | 
			
		||||
    ['windows', winNoPathArgv],
 | 
			
		||||
    ['mac', macNoPathArgv],
 | 
			
		||||
  ].map(([os, argv]) => {
 | 
			
		||||
    it(`should return undefined without path argv on ${os}`, () => {
 | 
			
		||||
      const args = parseCLIArgs(argv as string[])
 | 
			
		||||
      expect(getPathOrUrlFromArgs(args)).toBeUndefined()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
import minimist from 'minimist'
 | 
			
		||||
import yargs from 'yargs'
 | 
			
		||||
import { hideBin } from 'yargs/helpers'
 | 
			
		||||
 | 
			
		||||
const argv = yargs(hideBin(process.argv))
 | 
			
		||||
export const argvFromYargs = yargs(hideBin(process.argv))
 | 
			
		||||
  .option('telemetry', {
 | 
			
		||||
    alias: 't',
 | 
			
		||||
    type: 'boolean',
 | 
			
		||||
@ -9,4 +10,20 @@ const argv = yargs(hideBin(process.argv))
 | 
			
		||||
  })
 | 
			
		||||
  .parse()
 | 
			
		||||
 | 
			
		||||
export default argv
 | 
			
		||||
// TODO: find a better way to merge minimist and yargs parsers.
 | 
			
		||||
 | 
			
		||||
export function parseCLIArgs(argv: string[]): minimist.ParsedArgs {
 | 
			
		||||
  return minimist(argv, {
 | 
			
		||||
    // Treat all double-hyphenated arguments without equal signs as boolean
 | 
			
		||||
    boolean: true,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getPathOrUrlFromArgs(
 | 
			
		||||
  args: minimist.ParsedArgs
 | 
			
		||||
): string | undefined {
 | 
			
		||||
  if (args._.length > 1) {
 | 
			
		||||
    return args._[1]
 | 
			
		||||
  }
 | 
			
		||||
  return undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										47
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								src/main.ts
									
									
									
									
									
								
							@ -17,23 +17,27 @@ import { Bonjour, Service } from 'bonjour-service'
 | 
			
		||||
// @ts-ignore: TS1343
 | 
			
		||||
import * as kittycad from '@kittycad/lib/import'
 | 
			
		||||
import electronUpdater, { type AppUpdater } from 'electron-updater'
 | 
			
		||||
import minimist from 'minimist'
 | 
			
		||||
import getCurrentProjectFile from 'lib/getCurrentProjectFile'
 | 
			
		||||
import os from 'node:os'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
import { ZOO_STUDIO_PROTOCOL } from 'lib/constants'
 | 
			
		||||
import argvFromYargs from './commandLineArgs'
 | 
			
		||||
import {
 | 
			
		||||
  argvFromYargs,
 | 
			
		||||
  getPathOrUrlFromArgs,
 | 
			
		||||
  parseCLIArgs,
 | 
			
		||||
} from './commandLineArgs'
 | 
			
		||||
 | 
			
		||||
import * as packageJSON from '../package.json'
 | 
			
		||||
 | 
			
		||||
let mainWindow: BrowserWindow | null = null
 | 
			
		||||
 | 
			
		||||
// Check the command line arguments for a project path
 | 
			
		||||
const args = parseCLIArgs()
 | 
			
		||||
const args = parseCLIArgs(process.argv)
 | 
			
		||||
 | 
			
		||||
// @ts-ignore: TS1343
 | 
			
		||||
const viteEnv = import.meta.env
 | 
			
		||||
const NODE_ENV = process.env.NODE_ENV || viteEnv.MODE
 | 
			
		||||
const IS_PLAYWRIGHT = process.env.IS_PLAYWRIGHT
 | 
			
		||||
 | 
			
		||||
// dotenv override when present
 | 
			
		||||
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
 | 
			
		||||
@ -50,7 +54,8 @@ 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)
 | 
			
		||||
console.log('Environment vars', process.env)
 | 
			
		||||
console.log('Parsed CLI args', args)
 | 
			
		||||
 | 
			
		||||
/// Register our application to handle all "zoo-studio:" protocols.
 | 
			
		||||
const singleInstanceLock = app.requestSingleInstanceLock()
 | 
			
		||||
@ -68,7 +73,7 @@ if (process.defaultApp) {
 | 
			
		||||
// Must be done before ready event.
 | 
			
		||||
// Checking against this lock is needed for Windows and Linux, see
 | 
			
		||||
// https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app#windows-and-linux-code
 | 
			
		||||
if (!singleInstanceLock && !process.env.IS_PLAYWRIGHT) {
 | 
			
		||||
if (!singleInstanceLock && !IS_PLAYWRIGHT) {
 | 
			
		||||
  app.quit()
 | 
			
		||||
} else {
 | 
			
		||||
  registerStartupListeners()
 | 
			
		||||
@ -100,12 +105,14 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Deep Link: Case of a cold start from Windows or Linux
 | 
			
		||||
  const zooProtocolArg = process.argv.find((a) =>
 | 
			
		||||
    a.startsWith(ZOO_STUDIO_PROTOCOL + '://')
 | 
			
		||||
  )
 | 
			
		||||
  if (!pathToOpen && zooProtocolArg) {
 | 
			
		||||
    pathToOpen = zooProtocolArg
 | 
			
		||||
    console.log('Retrieved deep link from argv', pathToOpen)
 | 
			
		||||
  const pathOrUrl = getPathOrUrlFromArgs(args)
 | 
			
		||||
  if (
 | 
			
		||||
    !pathToOpen &&
 | 
			
		||||
    pathOrUrl &&
 | 
			
		||||
    pathOrUrl.startsWith(ZOO_STUDIO_PROTOCOL + '://')
 | 
			
		||||
  ) {
 | 
			
		||||
    pathToOpen = pathOrUrl
 | 
			
		||||
    console.log('Retrieved deep link from CLI args', pathToOpen)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Deep Link: Case of a second window opened for macOS
 | 
			
		||||
@ -431,7 +438,8 @@ const getProjectPathAtStartup = async (
 | 
			
		||||
  // If we are in development mode, we don't want to load a project at
 | 
			
		||||
  // startup.
 | 
			
		||||
  // Since the args passed are always '.'
 | 
			
		||||
  if (NODE_ENV !== 'production') {
 | 
			
		||||
  // aka Forge for yarn tron:start live dev or playwright tests, but not dev packaged apps
 | 
			
		||||
  if (MAIN_WINDOW_VITE_DEV_SERVER_URL || IS_PLAYWRIGHT) {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -484,17 +492,18 @@ const getProjectPathAtStartup = async (
 | 
			
		||||
  return null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseCLIArgs(): minimist.ParsedArgs {
 | 
			
		||||
  return minimist(process.argv, {})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
    // Likely convenient to keep for debugging
 | 
			
		||||
    console.log(
 | 
			
		||||
      'Parsed CLI args from second instance',
 | 
			
		||||
      parseCLIArgs(commandLine)
 | 
			
		||||
    )
 | 
			
		||||
    const pathOrUrl = getPathOrUrlFromArgs(parseCLIArgs(commandLine))
 | 
			
		||||
    console.log('Retrieved path or deep link from second-instance', pathOrUrl)
 | 
			
		||||
    createWindow(pathOrUrl)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user