Compare commits

...

28 Commits

Author SHA1 Message Date
ac938e1f23 fix: removing console logs 2025-07-03 15:19:43 -05:00
bf9dd893f1 fix: removed debugging commment 2025-07-03 15:13:33 -05:00
03a13fd741 fix: detection logic we spoof too much :( 2025-07-03 15:10:17 -05:00
d567de1e5f fix: known urls 2025-07-03 14:40:39 -05:00
66970d674d fix: love when the results are based on runtime :thonking: 2025-07-03 14:39:32 -05:00
07d899e884 fix: restructured the test names to be more accurate 2025-07-03 13:52:34 -05:00
86e6590f4d fix: damn AI at it again catching my typtoes :( 2025-07-03 13:47:15 -05:00
340c503633 fix: updated with Jace's new envs 2025-07-03 13:39:42 -05:00
7abc119993 chore: env(). support 2025-07-03 13:24:18 -05:00
4102249bd8 fix: merging main 2025-07-03 09:07:12 -05:00
3929f2e9fb chore: unit tests 2025-07-02 16:34:33 -05:00
bab98ab5c9 chore: updated all site base urls 2025-07-02 16:25:58 -05:00
8dc1b156ff chore: updated hard coded site urls in home.tsx 2025-07-02 14:21:02 -05:00
693fd8eb31 fix: updating the signin page hard coded urls 2025-07-02 14:18:05 -05:00
14a72344e2 chore: updated all VITE_KC_SITE_BASE_URL locations, need to do raw urls next 2025-07-02 14:09:39 -05:00
e5d1cd847d chore: migrating many urls to the helper function withSiteBaseURL 2025-07-02 14:05:37 -05:00
01d294a8bb chore: helper function and unit tests for a withSiteBaseURL 2025-07-02 13:52:41 -05:00
aa460d631d fix: expected 2025-07-02 12:55:14 -05:00
23e609443b fix: AI caught my typo, RIP 2025-07-02 12:36:38 -05:00
d3aa09a20b fix: withAPIBaseURL for all urls 2025-07-02 12:31:17 -05:00
eba91e85ea fix: auto fmt 2025-07-02 11:50:30 -05:00
74f6e338f7 chore: prompt edit with base helper function 2025-07-02 11:50:17 -05:00
529c619fb5 chore: shortlinks with base api helper function 2025-07-02 11:45:35 -05:00
195000b50c chore: fixing another api base url 2025-07-02 11:43:57 -05:00
7993b5f4e8 fix: env to helper function api base url 2025-07-02 11:41:29 -05:00
fb8acbefe7 chore: moving VITE_KC_API_BASE_URL to the helper function 2025-07-02 11:35:33 -05:00
8f2a2391d1 chore: improving the withBaseURL workflow 2025-07-02 11:33:23 -05:00
e2247669f0 fix: logging information about the login 2025-07-02 10:22:42 -05:00
33 changed files with 369 additions and 121 deletions

View File

@ -6,6 +6,9 @@ URL STATUS
000 https://${BASE_URL} 000 https://${BASE_URL}
405 https://api.dev.zoo.dev/oauth2/token/revoke 405 https://api.dev.zoo.dev/oauth2/token/revoke
401 https://api.dev.zoo.dev/users 401 https://api.dev.zoo.dev/users
401 https://dev.zoo.dev
401 https://dev.zoo.dev/docs
401 https://dev.zoo.dev/docs/kcl-samples/car-wheel-assembly
301 https://discord.gg/JQEpHR7Nt2 301 https://discord.gg/JQEpHR7Nt2
404 https://github.com/KittyCAD/engine/issues/3528 404 https://github.com/KittyCAD/engine/issues/3528
404 https://github.com/KittyCAD/modeling-app/commit/${ref} 404 https://github.com/KittyCAD/modeling-app/commit/${ref}
@ -19,5 +22,4 @@ URL STATUS
302 https://stackoverflow.com/a/58436959/22753272 302 https://stackoverflow.com/a/58436959/22753272
303 https://text-to-cad.zoo.dev/dashboard 303 https://text-to-cad.zoo.dev/dashboard
307 https://zoo.dev/ 307 https://zoo.dev/
308 https://zoo.dev/docs/api/ml/generate-a-cad-model-from-text
308 https://zoo.dev/docs/kcl 308 https://zoo.dev/docs/kcl

View File

@ -53,7 +53,6 @@ import {
WASM_INIT_FAILED_TOAST_ID, WASM_INIT_FAILED_TOAST_ID,
} from '@src/lib/constants' } from '@src/lib/constants'
import { isPlaywright } from '@src/lib/isPlaywright' import { isPlaywright } from '@src/lib/isPlaywright'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { useNetworkHealthStatus } from '@src/components/NetworkHealthIndicator' import { useNetworkHealthStatus } from '@src/components/NetworkHealthIndicator'
import { useNetworkMachineStatus } from '@src/components/NetworkMachineIndicator' import { useNetworkMachineStatus } from '@src/components/NetworkMachineIndicator'
import { import {
@ -65,6 +64,7 @@ import { useModelingContext } from '@src/hooks/useModelingContext'
import { xStateValueToString } from '@src/lib/xStateValueToString' import { xStateValueToString } from '@src/lib/xStateValueToString'
import { getSelectionTypeDisplayText } from '@src/lib/selections' import { getSelectionTypeDisplayText } from '@src/lib/selections'
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
// CYCLIC REF // CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -189,7 +189,8 @@ export function App() {
() => () =>
DownloadAppToast({ DownloadAppToast({
onAccept: () => { onAccept: () => {
openWindow(`${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`) const url = withSiteBaseURL(`/${APP_DOWNLOAD_PATH}`)
openWindow(url)
.then(() => { .then(() => {
toast.dismiss(DOWNLOAD_APP_TOAST_ID) toast.dismiss(DOWNLOAD_APP_TOAST_ID)
}) })

View File

@ -5,8 +5,8 @@ import {
BillingRemaining, BillingRemaining,
BillingRemainingMode, BillingRemainingMode,
} from '@src/components/BillingRemaining' } from '@src/components/BillingRemaining'
import { type BillingActor } from '@src/machines/billingMachine' import { type BillingActor } from '@src/machines/billingMachine'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
export const BillingDialog = (props: { billingActor: BillingActor }) => { export const BillingDialog = (props: { billingActor: BillingActor }) => {
const billingContext = useSelector( const billingContext = useSelector(
@ -42,7 +42,7 @@ export const BillingDialog = (props: { billingActor: BillingActor }) => {
{!hasUnlimited && ( {!hasUnlimited && (
<a <a
className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer" className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer"
href="https://zoo.dev/design-studio-pricing" href={withSiteBaseURL('/design-studio-pricing')}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
data-testid="billing-upgrade-button" data-testid="billing-upgrade-button"

View File

@ -15,6 +15,7 @@ import {
import { onboardingStartPath } from '@src/lib/onboardingPaths' import { onboardingStartPath } from '@src/lib/onboardingPaths'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
const HelpMenuDivider = () => ( const HelpMenuDivider = () => (
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" /> <div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
@ -89,7 +90,7 @@ export function HelpMenu() {
<HelpMenuDivider /> <HelpMenuDivider />
<HelpMenuItem <HelpMenuItem
as="a" as="a"
href="https://zoo.dev/docs/kcl-samples" href={withSiteBaseURL('/docs/kcl-samples')}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -97,7 +98,7 @@ export function HelpMenu() {
</HelpMenuItem> </HelpMenuItem>
<HelpMenuItem <HelpMenuItem
as="a" as="a"
href="https://zoo.dev/docs/kcl-lang" href={withSiteBaseURL('/docs/kcl-lang')}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >

View File

@ -7,7 +7,7 @@ import {
LanguageServerClient, LanguageServerClient,
LspWorkerEventType, LspWorkerEventType,
} from '@kittycad/codemirror-lsp-client' } from '@kittycad/codemirror-lsp-client'
import { TEST } from '@src/env' import env from '@src/env'
import React, { createContext, useContext, useMemo, useState } from 'react' import React, { createContext, useContext, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import type * as LSP from 'vscode-languageserver-protocol' import type * as LSP from 'vscode-languageserver-protocol'
@ -78,7 +78,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// But the server happens async so we break this into two parts. // But the server happens async so we break this into two parts.
// Below is the client and server promise. // Below is the client and server promise.
const { lspClient: kclLspClient } = useMemo(() => { const { lspClient: kclLspClient } = useMemo(() => {
if (!token || token === '' || TEST) { if (!token || token === '' || env().TEST) {
return { lspClient: null } return { lspClient: null }
} }
@ -137,7 +137,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// We do not want to restart the server, its just wasteful. // We do not want to restart the server, its just wasteful.
const kclLSP = useMemo(() => { const kclLSP = useMemo(() => {
let plugin = null let plugin = null
if (isKclLspReady && !TEST && kclLspClient) { if (isKclLspReady && !env().TEST && kclLspClient) {
// Set up the lsp plugin. // Set up the lsp plugin.
const lsp = kcl({ const lsp = kcl({
documentUri: `file:///${PROJECT_ENTRYPOINT}`, documentUri: `file:///${PROJECT_ENTRYPOINT}`,
@ -171,7 +171,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
}, [kclLspClient, isKclLspReady]) }, [kclLspClient, isKclLspReady])
const { lspClient: copilotLspClient } = useMemo(() => { const { lspClient: copilotLspClient } = useMemo(() => {
if (!token || token === '' || TEST) { if (!token || token === '' || env().TEST) {
return { lspClient: null } return { lspClient: null }
} }
@ -213,7 +213,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// We do not want to restart the server, its just wasteful. // We do not want to restart the server, its just wasteful.
const copilotLSP = useMemo(() => { const copilotLSP = useMemo(() => {
let plugin = null let plugin = null
if (isCopilotLspReady && !TEST && copilotLspClient) { if (isCopilotLspReady && !env().TEST && copilotLspClient) {
// Set up the lsp plugin. // Set up the lsp plugin.
const lsp = copilotPlugin({ const lsp = copilotPlugin({
documentUri: `file:///${PROJECT_ENTRYPOINT}`, documentUri: `file:///${PROJECT_ENTRYPOINT}`,

View File

@ -12,6 +12,7 @@ import { reportRejection } from '@src/lib/trap'
import { commandBarActor, settingsActor } from '@src/lib/singletons' import { commandBarActor, settingsActor } from '@src/lib/singletons'
import styles from './KclEditorMenu.module.css' import styles from './KclEditorMenu.module.css'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
export const KclEditorMenu = ({ children }: PropsWithChildren) => { export const KclEditorMenu = ({ children }: PropsWithChildren) => {
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } = const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
@ -67,7 +68,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Item> <Menu.Item>
<a <a
className={styles.button} className={styles.button}
href="https://zoo.dev/docs/kcl-lang" href={withSiteBaseURL('/docs/kcl-lang')}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={openExternalBrowserIfDesktop()} onClick={openExternalBrowserIfDesktop()}
@ -108,7 +109,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Item> <Menu.Item>
<a <a
className={styles.button} className={styles.button}
href="https://zoo.dev/docs/kcl-samples" href={withSiteBaseURL('/docs/kcl-samples')}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
onClick={openExternalBrowserIfDesktop()} onClick={openExternalBrowserIfDesktop()}

View File

@ -35,7 +35,7 @@ import {
rectangularSelection, rectangularSelection,
} from '@codemirror/view' } from '@codemirror/view'
import interact from '@replit/codemirror-interact' import interact from '@replit/codemirror-interact'
import { TEST } from '@src/env' import env from '@src/env'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
@ -149,7 +149,7 @@ export const KclEditorPane = () => {
if (copilotLSP) extensions.push(copilotLSP) if (copilotLSP) extensions.push(copilotLSP)
// These extensions have proven to mess with vitest // These extensions have proven to mess with vitest
if (!TEST) { if (!env().TEST) {
extensions.push( extensions.push(
lintGutter(), lintGutter(),
lineNumbers(), lineNumbers(),

View File

@ -1,5 +1,4 @@
import { Transition } from '@headlessui/react' import { Transition } from '@headlessui/react'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
import { base64ToString } from '@src/lib/base64' import { base64ToString } from '@src/lib/base64'
@ -16,6 +15,7 @@ import { platform } from '@src/lib/utils'
import { codeManager } from '@src/lib/singletons' import { codeManager } from '@src/lib/singletons'
import { Logo } from '@src/components/Logo' import { Logo } from '@src/components/Logo'
import { useEffect } from 'react' import { useEffect } from 'react'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
/** /**
* This component is a handler that checks if a certain query parameter * This component is a handler that checks if a certain query parameter
@ -98,7 +98,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => {
> >
<Transition.Child <Transition.Child
as="div" as="div"
className={`max-w-3xl py-6 px-10 flex flex-col items-center gap-12 className={`max-w-3xl py-6 px-10 flex flex-col items-center gap-12
mx-auto border rounded-lg shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100`} mx-auto border rounded-lg shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100`}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 scale-95" enterFrom="opacity-0 scale-95"
@ -133,7 +133,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => {
buttonClasses + buttonClasses +
' text-sm border-transparent justify-center dark:bg-transparent' ' text-sm border-transparent justify-center dark:bg-transparent'
} }
to={`${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`} to={withSiteBaseURL(`/${APP_DOWNLOAD_PATH}`)}
iconEnd={{ icon: 'link', bgClassName: '!bg-transparent' }} iconEnd={{ icon: 'link', bgClassName: '!bg-transparent' }}
> >
Download desktop app Download desktop app

View File

@ -12,9 +12,9 @@ import { Popover } from '@headlessui/react'
import Tooltip from '@src/components/Tooltip' import Tooltip from '@src/components/Tooltip'
import { HelpMenu } from '@src/components/HelpMenu' import { HelpMenu } from '@src/components/HelpMenu'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_DOWNLOAD_PATH } from '@src/lib/constants' import { APP_DOWNLOAD_PATH } from '@src/lib/constants'
import { desktopAppPitchMessage } from '@src/components/DownloadAppToast' import { desktopAppPitchMessage } from '@src/components/DownloadAppToast'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
export const defaultGlobalStatusBarItems = ({ export const defaultGlobalStatusBarItems = ({
location, location,
@ -37,7 +37,7 @@ export const defaultGlobalStatusBarItems = ({
id: 'download-desktop-app', id: 'download-desktop-app',
element: 'externalLink', element: 'externalLink',
label: 'Download the app', label: 'Download the app',
href: `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`, href: withSiteBaseURL(`/${APP_DOWNLOAD_PATH}`),
icon: 'download', icon: 'download',
toolTip: { toolTip: {
children: desktopAppPitchMessage, children: desktopAppPitchMessage,

View File

@ -13,6 +13,7 @@ import { isDesktop } from '@src/lib/isDesktop'
import { PATHS } from '@src/lib/paths' import { PATHS } from '@src/lib/paths'
import { authActor } from '@src/lib/singletons' import { authActor } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
type User = Models['User_type'] type User = Models['User_type']
@ -62,7 +63,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
{ {
id: 'account', id: 'account',
Element: 'externalLink', Element: 'externalLink',
to: 'https://zoo.dev/account', to: withSiteBaseURL('/account'),
children: ( children: (
<> <>
<span className="flex-1">Manage account</span> <span className="flex-1">Manage account</span>

126
src/env.test.ts Normal file
View File

@ -0,0 +1,126 @@
import env from '@src/env'
import { vi } from 'vitest'
import { viteEnv, windowElectronProcessEnv, processEnv } from '@src/env'
describe('@src/env', () => {
describe('default export', () => {
it('should run the process.env workflow', () => {
// vite > node.js
const expected = {
NODE_ENV: 'test',
VITE_KC_API_WS_MODELING_URL:
'wss://api.dev.zoo.dev/ws/modeling/commands',
VITE_KITTYCAD_API_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_CONNECTION_TIMEOUT_MS: '5000',
VITE_KITTYCAD_API_TOKEN: 'redacted',
PROD: undefined,
TEST: 'true',
DEV: '1',
CI: 'true',
}
const actual = env()
expect(typeof actual.VITE_KITTYCAD_API_TOKEN).toBe('string')
//@ts-ignore I do not want this token in our logs for any reason.
actual.VITE_KITTYCAD_API_TOKEN = 'redacted'
//@ts-ignore need to hard code this for localhost and CI
actual.CI = 'true'
expect(actual).toStrictEqual(expected)
})
})
describe('viteEnv', () => {
it('should match the EnvironmentVariables key types*', () => {
// Do not print entire object or compare, it contains a ton of ENV vars.
// We only need to match against EnvironmentVariables
const actual = viteEnv()
expect(typeof actual.NODE_ENV).toBe('string')
// Not passed in during tests?
expect(typeof actual.VITE_KC_WS_MODELING_URL).toBe('undefined')
expect(typeof actual.VITE_KITTYCAD_API_BASE_URL).toBe('string')
expect(typeof actual.VITE_KC_SITE_BASE_URL).toBe('string')
// Not passed in during tests?
expect(typeof actual.VITE_KC_SITE_API_URL).toBe('undefined')
expect(typeof actual.VITE_KC_CONNECTION_TIMEOUT_MS).toBe('string')
expect(typeof actual.VITE_KITTYCAD_API_TOKEN).toBe('string')
expect(typeof actual.PROD).toBe('boolean')
expect(typeof actual.TEST).toBe('string')
expect(typeof actual.DEV).toBe('boolean')
// Don't check CI...
})
})
describe('windowElectronProcessEnv', () => {
it('should return undefined in vitest runtime', () => {
const expected = undefined
const actual = windowElectronProcessEnv()
expect(actual).toBe(expected)
})
describe('When mocking window', () => {
it('should match the EnvironmentVariable key types*', () => {
vi.stubGlobal('electron', {
process: {
env: {
NODE_ENV: 'test',
VITE_KC_API_WS_MODELING_URL:
'wss://api.dev.zoo.dev/ws/modeling/commands',
VITE_KITTYCAD_API_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_CONNECTION_TIMEOUT_MS: '5000',
VITE_KITTYCAD_API_TOKEN: 'redacted',
PROD: undefined,
TEST: 'true',
DEV: '1',
CI: undefined,
},
},
})
const expected = {
NODE_ENV: 'test',
VITE_KC_API_WS_MODELING_URL:
'wss://api.dev.zoo.dev/ws/modeling/commands',
VITE_KITTYCAD_API_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_CONNECTION_TIMEOUT_MS: '5000',
VITE_KITTYCAD_API_TOKEN: 'redacted',
PROD: undefined,
TEST: 'true',
DEV: '1',
CI: undefined,
}
const actual = windowElectronProcessEnv()
expect(actual).toStrictEqual(expected)
vi.unstubAllGlobals()
})
})
it('should fail on missing window.electron', () => {
// someone didn't clean up their test if this fails!
const expected = undefined
const actual = windowElectronProcessEnv()
expect(actual).toBe(expected)
expect(window.electron).toBe(expected)
})
})
describe('processEnv', () => {
it('should match the EnvironmentVariables key types*', () => {
// Do not print entire object or compare, it contains a ton of ENV vars.
// We only need to match against EnvironmentVariables
const actual = processEnv()
expect(!!actual).toBe(true)
expect(typeof actual?.NODE_ENV).toBe('string')
// Not passed in during tests?
expect(typeof actual?.VITE_KC_WS_MODELING_URL).toBe('undefined')
expect(typeof actual?.VITE_KITTYCAD_API_BASE_URL).toBe('string')
expect(typeof actual?.VITE_KC_SITE_BASE_URL).toBe('string')
// Not passed in during tests?
expect(typeof actual?.VITE_KC_SITE_API_URL).toBe('undefined')
expect(typeof actual?.VITE_KC_CONNECTION_TIMEOUT_MS).toBe('string')
expect(typeof actual?.VITE_KITTYCAD_API_TOKEN).toBe('string')
expect(typeof actual?.PROD).toBe('string')
expect(typeof actual?.TEST).toBe('string')
expect(typeof actual?.DEV).toBe('string')
// Don't check CI...
})
})
})

View File

@ -1,22 +1,88 @@
// It turns out import.meta.env is a really fucky env var passing method. type EnvironmentVariables = {
// It's purely generated by Vite and nothing else. readonly NODE_ENV: string | undefined
// For Jest tests, we use babel to deal with it (it's a Syntax error otherwise) readonly VITE_KC_API_WS_MODELING_URL: string | undefined
// @ts-ignore: TS1343 readonly VITE_KITTYCAD_API_BASE_URL: string | undefined
const env = window.electron?.process.env ?? import.meta.env readonly VITE_KC_SITE_BASE_URL: string | undefined
readonly VITE_KC_SITE_APP_URL: string | undefined
readonly VITE_KC_CONNECTION_TIMEOUT_MS: string | undefined
readonly VITE_KITTYCAD_API_TOKEN: string | undefined
readonly PROD: string | undefined
readonly TEST: string | undefined
readonly DEV: string | undefined
readonly CI: string | undefined
}
export const NODE_ENV = env.NODE_ENV as string | undefined export const viteEnv = () => {
export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as // It turns out import.meta.env is a really fucky env var passing method.
| string // It's purely generated by Vite and nothing else.
| undefined // For Jest tests, we use babel to deal with it (it's a Syntax error otherwise)
export const VITE_KITTYCAD_API_BASE_URL = env.VITE_KITTYCAD_API_BASE_URL // @ts-ignore: TS1343
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL return import.meta.env
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL }
export const VITE_KC_CONNECTION_TIMEOUT_MS =
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined export const windowElectronProcessEnv = () => {
export const VITE_KITTYCAD_API_TOKEN = env.VITE_KITTYCAD_API_TOKEN as return typeof window !== 'undefined' && typeof window.electron !== 'undefined'
| string ? window?.electron?.process?.env
| undefined : undefined
export const PROD = env.PROD as string | undefined }
export const TEST = env.TEST as string | undefined
export const DEV = env.DEV as string | undefined export const processEnv = () => {
export const CI = env.CI as string | undefined if (typeof process === 'undefined') {
// Web, no window.process or process
return undefined
} else if (
typeof process !== 'undefined' &&
typeof window !== 'undefined' &&
process.env.TEST === 'false'
) {
// Web, you made window.process, why :(, need process.env.TEST to make sure the frontend gets evaluated.
// The frontend can spoof this too :(
return undefined
}
return process.env
}
/**
* This function will work in any runtime. Note that we shouldn't be using this for any values outside of the
* EnvironmentVariables type. This is not going to replace process.env.
*
* Vite -> node.js -> bridge -> javascript
* We want to have the node.js and javascript runtime share the same code for getting these important configurations.
*/
export default (): EnvironmentVariables => {
// Compute the possible environment variables, order operation is important
// runtime (TODO) > process.env > window.electron.process.env > import.meta.env
const viteOnly = viteEnv()
const windowElectronProcessEnvOnly = windowElectronProcessEnv()
const processEnvOnly = processEnv()
const env = processEnvOnly || windowElectronProcessEnvOnly || viteOnly
// Vite uses Booleans and process.env uses strings
let PROD = env.PROD
if (typeof PROD === 'boolean') {
PROD = Number(PROD).toString()
}
let DEV = env.DEV
if (typeof DEV === 'boolean') {
DEV = Number(DEV).toString()
}
const environmentVariables: EnvironmentVariables = {
NODE_ENV: (env.NODE_ENV as string) || undefined,
VITE_KC_API_WS_MODELING_URL:
(env.VITE_KC_API_WS_MODELING_URL as string) || undefined,
VITE_KITTYCAD_API_BASE_URL:
(env.VITE_KITTYCAD_API_BASE_URL as string) || undefined,
VITE_KC_SITE_BASE_URL: (env.VITE_KC_SITE_BASE_URL as string) || undefined,
VITE_KC_SITE_APP_URL: (env.VITE_KC_SITE_APP_URL as string) || undefined,
VITE_KC_CONNECTION_TIMEOUT_MS:
(env.VITE_KC_CONNECTION_TIMEOUT_MS as string) || undefined,
VITE_KITTYCAD_API_TOKEN:
(env.VITE_KITTYCAD_API_TOKEN as string) || undefined,
PROD: PROD || undefined,
TEST: (env.TEST as string) || undefined,
DEV: DEV || undefined,
CI: (env.CI as string) || undefined,
}
return environmentVariables
}

View File

@ -1,4 +1,4 @@
import { VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { createLiteral } from '@src/lang/create' import { createLiteral } from '@src/lang/create'
import type { import type {
@ -42,7 +42,7 @@ beforeAll(async () => {
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN, token: env().VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -4,7 +4,7 @@ import { initPromise } from '@src/lang/wasmUtils'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import type { Selection } from '@src/lib/selections' import type { Selection } from '@src/lib/selections'
import { engineCommandManager, kclManager } from '@src/lib/singletons' import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement' import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement'
beforeAll(async () => { beforeAll(async () => {
@ -12,7 +12,7 @@ beforeAll(async () => {
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN, token: env().VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_WS_MODELING_URL, VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { jsAppSettings } from '@src/lib/settings/settingsUtils' import { jsAppSettings } from '@src/lib/settings/settingsUtils'
import { BSON } from 'bson' import { BSON } from 'bson'
@ -387,7 +387,7 @@ class EngineConnection extends EventTarget {
// SHOULD ONLY BE USED FOR VITESTS // SHOULD ONLY BE USED FOR VITESTS
connectLite(callback: () => void) { connectLite(callback: () => void) {
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${256}&video_res_height=${256}` const url = `${env().VITE_KC_API_WS_MODELING_URL}?video_res_width=${256}&video_res_height=${256}`
this.websocket = new WebSocket(url, []) this.websocket = new WebSocket(url, [])
this.websocket.binaryType = 'arraybuffer' this.websocket.binaryType = 'arraybuffer'
@ -400,7 +400,7 @@ class EngineConnection extends EventTarget {
this.send({ this.send({
type: 'headers', type: 'headers',
headers: { headers: {
Authorization: `Bearer ${VITE_KITTYCAD_API_TOKEN}`, Authorization: `Bearer ${env().VITE_KITTYCAD_API_TOKEN}`,
}, },
}) })
} }
@ -1531,7 +1531,7 @@ export class EngineCommandManager extends EventTarget {
additionalSettings += additionalSettings +=
'&show_grid=' + (this.settings.showScaleGrid ? 'true' : 'false') '&show_grid=' + (this.settings.showScaleGrid ? 'true' : 'false')
const pool = !this.settings.pool ? '' : `&pool=${this.settings.pool}` const pool = !this.settings.pool ? '' : `&pool=${this.settings.pool}`
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}` const url = `${env().VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}${pool}`
this.engineConnection = new EngineConnection({ this.engineConnection = new EngineConnection({
engineCommandManager: this, engineCommandManager: this,
url, url,

View File

@ -1,4 +1,4 @@
import { DEV } from '@src/env' import env from '@src/env'
import type { import type {
Actor, Actor,
AnyStateMachine, AnyStateMachine,
@ -89,7 +89,8 @@ export function createMachineCommand<
} else if ('status' in commandConfig) { } else if ('status' in commandConfig) {
const { status } = commandConfig const { status } = commandConfig
if (status === 'inactive') return null if (status === 'inactive') return null
if (status === 'development' && !(DEV || IS_STAGING_OR_DEBUG)) return null if (status === 'development' && !(env().DEV || IS_STAGING_OR_DEBUG))
return null
} }
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined const icon = ('icon' in commandConfig && commandConfig.icon) || undefined

View File

@ -1,4 +1,4 @@
import { DEV } from '@src/env' import env from '@src/env'
import isomorphicFetch from 'isomorphic-fetch' import isomorphicFetch from 'isomorphic-fetch'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
@ -28,7 +28,7 @@ export default async function crossPlatformFetch<T>(
// Add credentials: 'include' to options // Add credentials: 'include' to options
// We send the token with the headers only in development mode, DO NOT // We send the token with the headers only in development mode, DO NOT
// DO THIS IN PRODUCTION, as it is a security risk. // DO THIS IN PRODUCTION, as it is a security risk.
opts.headers = headers(DEV ? token : undefined) opts.headers = headers(env().DEV ? token : undefined)
opts.credentials = 'include' opts.credentials = 'include'
response = await fetch(url, opts) response = await fetch(url, opts)
} }

View File

@ -1,4 +1,4 @@
import { VITE_KC_SITE_APP_URL } from '@src/env' import env from '@src/env'
import { createCreateFileUrl } from '@src/lib/links' import { createCreateFileUrl } from '@src/lib/links'
@ -9,7 +9,7 @@ describe(`link creation tests`, () => {
// Converted with external online tools // Converted with external online tools
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D` const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&code=${expectedEncodedCode}&ask-open-desktop=true` const expectedLink = `${env().VITE_KC_SITE_APP_URL}/?create-file=true&name=test&code=${expectedEncodedCode}&ask-open-desktop=true`
const result = createCreateFileUrl({ code, name, isRestrictedToOrg: false }) const result = createCreateFileUrl({ code, name, isRestrictedToOrg: false })
expect(result.toString()).toBe(expectedLink) expect(result.toString()).toBe(expectedLink)

View File

@ -1,4 +1,4 @@
import { VITE_KC_SITE_APP_URL } from '@src/env' import env from '@src/env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { stringToBase64 } from '@src/lib/base64' import { stringToBase64 } from '@src/lib/base64'
@ -58,7 +58,7 @@ export async function copyFileShareLink(
* open the URL in the desktop app. * open the URL in the desktop app.
*/ */
export function createCreateFileUrl({ code, name }: FileLinkParams) { export function createCreateFileUrl({ code, name }: FileLinkParams) {
let origin = VITE_KC_SITE_APP_URL let origin = env().VITE_KC_SITE_APP_URL
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
[CREATE_FILE_URL_PARAM]: String(true), [CREATE_FILE_URL_PARAM]: String(true),
name, name,

View File

@ -1,7 +1,6 @@
import type { SelectionRange } from '@codemirror/state' import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff' import { diffLines } from 'diff'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models' import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
@ -28,7 +27,8 @@ import { uuidv4 } from '@src/lib/utils'
import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models' import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models'
import type { FileMeta } from '@src/lib/types' import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils' import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL' import { withAPIBaseURL, withSiteBaseURL } from '@src/lib/withBaseURL'
import env from '@src/env'
type KclFileMetaMap = { type KclFileMetaMap = {
[execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }> [execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }>
@ -439,7 +439,7 @@ export async function promptToEditFlow({
return Promise.reject(result) return Promise.reject(result)
} }
const oldCodeWebAppOnly = codeManager.code const oldCodeWebAppOnly = codeManager.code
const downloadLink = `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}` const downloadLink = withSiteBaseURL(`/${APP_DOWNLOAD_PATH}`)
if (!isDesktop() && Object.values(result.outputs).length > 1) { if (!isDesktop() && Object.values(result.outputs).length > 1) {
const toastId = uuidv4() const toastId = uuidv4()

View File

@ -2,7 +2,7 @@ import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
import type { NamedView } from '@rust/kcl-lib/bindings/NamedView' import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import { default_app_settings } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib' import { default_app_settings } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
import { TEST } from '@src/env' import env from '@src/env'
import { import {
defaultAppSettings, defaultAppSettings,
@ -545,7 +545,7 @@ export function getSettingInputType(setting: Setting) {
export const jsAppSettings = async (): Promise<DeepPartial<Configuration>> => { export const jsAppSettings = async (): Promise<DeepPartial<Configuration>> => {
let jsAppSettings = default_app_settings() let jsAppSettings = default_app_settings()
if (!TEST) { if (!env().TEST) {
// TODO: https://github.com/KittyCAD/modeling-app/issues/6445 // TODO: https://github.com/KittyCAD/modeling-app/issues/6445
const settings = await import('@src/lib/singletons').then((module) => const settings = await import('@src/lib/singletons').then((module) =>
module.getSettings() module.getSettings()

View File

@ -11,6 +11,7 @@ import {
pipeHasCircle, pipeHasCircle,
} from '@src/machines/modelingMachine' } from '@src/machines/modelingMachine'
import { IS_ML_EXPERIMENTAL } from '@src/lib/constants' import { IS_ML_EXPERIMENTAL } from '@src/lib/constants'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
export type ToolbarModeName = 'modeling' | 'sketching' export type ToolbarModeName = 'modeling' | 'sketching'
@ -105,7 +106,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-startSketchOn', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-sketch-startSketchOn'
),
}, },
], ],
}, },
@ -125,7 +128,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-extrude', url: withSiteBaseURL('/docs/kcl-std/functions/std-sketch-extrude'),
}, },
], ],
}, },
@ -145,7 +148,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-sweep', url: withSiteBaseURL('/docs/kcl-std/functions/std-sketch-sweep'),
}, },
], ],
}, },
@ -165,7 +168,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-loft', url: withSiteBaseURL('/docs/kcl-std/functions/std-sketch-loft'),
}, },
], ],
}, },
@ -185,11 +188,11 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-revolve', url: withSiteBaseURL('/docs/kcl-std/functions/std-sketch-revolve'),
}, },
{ {
label: 'KCL example', label: 'KCL example',
url: 'https://zoo.dev/docs/kcl-samples/ball-bearing', url: withSiteBaseURL('/docs/kcl-samples/ball-bearing'),
}, },
], ],
}, },
@ -209,7 +212,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-fillet', url: withSiteBaseURL('/docs/kcl-std/functions/std-solid-fillet'),
}, },
], ],
}, },
@ -234,7 +237,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-chamfer', url: withSiteBaseURL('/docs/kcl-std/functions/std-solid-chamfer'),
}, },
], ],
}, },
@ -253,7 +256,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-shell', url: withSiteBaseURL('/docs/kcl-std/functions/std-solid-shell'),
}, },
], ],
}, },
@ -275,7 +278,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-union', url: withSiteBaseURL('/docs/kcl-std/functions/std-solid-union'),
}, },
], ],
}, },
@ -293,7 +296,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-subtract', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-solid-subtract'
),
}, },
], ],
}, },
@ -311,7 +316,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-solid-intersect', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-solid-intersect'
),
}, },
], ],
}, },
@ -337,7 +344,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-offsetPlane', url: withSiteBaseURL('/docs/kcl-std/functions/std-offsetPlane'),
}, },
], ],
}, },
@ -368,7 +375,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-helix', url: withSiteBaseURL('/docs/kcl-std/functions/std-helix'),
}, },
], ],
}, },
@ -389,7 +396,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'API docs', label: 'API docs',
url: 'https://zoo.dev/docs/kcl-lang/modules', url: withSiteBaseURL('/docs/kcl-lang/modules'),
}, },
], ],
}, },
@ -410,7 +417,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'API docs', label: 'API docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-transform-translate', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-transform-translate'
),
}, },
], ],
}, },
@ -428,7 +437,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'API docs', label: 'API docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-transform-rotate', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-transform-rotate'
),
}, },
], ],
}, },
@ -446,7 +457,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'API docs', label: 'API docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-clone', url: withSiteBaseURL('/docs/kcl-std/functions/std-clone'),
}, },
], ],
}, },
@ -482,7 +493,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'API docs', label: 'API docs',
url: 'https://zoo.dev/docs/api/ml/generate-a-cad-model-from-text', url: withSiteBaseURL(
'/docs/api/ml/generate-a-cad-model-from-text'
),
}, },
], ],
}, },
@ -739,7 +752,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-sketch-polygon', url: withSiteBaseURL('/docs/kcl-std/functions/std-sketch-polygon'),
}, },
], ],
}, },
@ -755,7 +768,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [ links: [
{ {
label: 'KCL docs', label: 'KCL docs',
url: 'https://zoo.dev/docs/kcl-std/functions/std-transform-mirror2d', url: withSiteBaseURL(
'/docs/kcl-std/functions/std-transform-mirror2d'
),
}, },
], ],
}, },

View File

@ -1,11 +1,11 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL' import { withAPIBaseURL, withSiteBaseURL } from '@src/lib/withBaseURL'
describe('withBaseURL', () => { describe('withBaseURL', () => {
/** /**
* running in the development environment * running in the development environment
* the .env.development should load * the .env.development should load
*/ */
describe('withAPIBaseUrl', () => { describe('withAPIBaseURL', () => {
it('should return base url', () => { it('should return base url', () => {
const expected = 'https://api.dev.zoo.dev' const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('') const actual = withAPIBaseURL('')
@ -31,4 +31,31 @@ describe('withBaseURL', () => {
expect(actualEndsWith).toBe(expectedEndsWith) expect(actualEndsWith).toBe(expectedEndsWith)
}) })
}) })
describe('withSiteBaseURL', () => {
it('should return base url', () => {
const expected = 'https://dev.zoo.dev'
const actual = withSiteBaseURL('')
expect(actual).toBe(expected)
})
it('should return base url with /docs', () => {
const expected = 'https://dev.zoo.dev/docs'
const actual = withSiteBaseURL('/docs')
expect(actual).toBe(expected)
})
it('should return a longer base base url with /docs/kcl-samples/car-wheel-assembly', () => {
const expected = 'https://dev.zoo.dev/docs/kcl-samples/car-wheel-assembly'
const actual = withSiteBaseURL('/docs/kcl-samples/car-wheel-assembly')
expect(actual).toBe(expected)
})
it('should ensure base url does not have ending slash', () => {
const expected = 'https://dev.zoo.dev'
const actual = withSiteBaseURL('')
expect(actual).toBe(expected)
const expectedEndsWith = expected[expected.length - 1]
const actualEndsWith = actual[actual.length - 1]
expect(actual).toBe(expected)
expect(actualEndsWith).toBe(expectedEndsWith)
})
})
}) })

View File

@ -1,5 +1,9 @@
import { VITE_KITTYCAD_API_BASE_URL } from '@src/env' import env from '@src/env'
export function withAPIBaseURL(path: string): string { export function withAPIBaseURL(path: string): string {
return VITE_KITTYCAD_API_BASE_URL + path return env().VITE_KITTYCAD_API_BASE_URL + path
}
export function withSiteBaseURL(path: string): string {
return env().VITE_KC_SITE_BASE_URL + path
} }

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { assign, fromPromise, setup } from 'xstate' import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants' import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -34,7 +34,7 @@ export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
*/ */
const persistedCookie = getCookie(COOKIE_NAME) const persistedCookie = getCookie(COOKIE_NAME)
const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || '' const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const persistedDevToken = VITE_KITTYCAD_API_TOKEN const persistedDevToken = env().VITE_KITTYCAD_API_TOKEN
export const persistedToken = export const persistedToken =
persistedDevToken || persistedCookie || persistedLocalStorage persistedDevToken || persistedCookie || persistedLocalStorage
console.log('Initial persisted token') console.log('Initial persisted token')
@ -197,6 +197,7 @@ async function getAndSyncStoredToken(input: {
token?: string token?: string
}): Promise<string> { }): Promise<string> {
// dev mode // dev mode
const VITE_KITTYCAD_API_TOKEN = env().VITE_KITTYCAD_API_TOKEN
if (VITE_KITTYCAD_API_TOKEN) { if (VITE_KITTYCAD_API_TOKEN) {
console.log('Token used for authentication') console.log('Token used for authentication')
console.table([['api token', !!VITE_KITTYCAD_API_TOKEN]]) console.table([['api token', !!VITE_KITTYCAD_API_TOKEN]])

View File

@ -11,7 +11,7 @@ import {
engineCommandManager, engineCommandManager,
kclManager, kclManager,
} from '@src/lib/singletons' } from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { getConstraintInfoKw } from '@src/lang/std/sketch' import { getConstraintInfoKw } from '@src/lang/std/sketch'
import { getNodeFromPath } from '@src/lang/queryAst' import { getNodeFromPath } from '@src/lang/queryAst'
import type { Node } from '@rust/kcl-lib/bindings/Node' import type { Node } from '@rust/kcl-lib/bindings/Node'
@ -31,7 +31,7 @@ beforeAll(async () => {
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN, token: env().VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -1,5 +1,5 @@
import { engineCommandManager, kclManager } from '@src/lib/singletons' import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env' import env from '@src/env'
import { getModuleIdByFileName, isArray } from '@src/lib/utils' import { getModuleIdByFileName, isArray } from '@src/lib/utils'
import { vi, inject } from 'vitest' import { vi, inject } from 'vitest'
import { assertParse } from '@src/lang/wasm' import { assertParse } from '@src/lang/wasm'
@ -357,7 +357,7 @@ beforeAll(async () => {
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KITTYCAD_API_TOKEN, token: env().VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -5,6 +5,7 @@ import { reportRejection } from '@src/lib/trap'
import { typeSafeWebContentsSend } from '@src/menu/channels' import { typeSafeWebContentsSend } from '@src/menu/channels'
import type { ZooMenuItemConstructorOptions } from '@src/menu/roles' import type { ZooMenuItemConstructorOptions } from '@src/menu/roles'
import { getAutoUpdater } from '@src/updater' import { getAutoUpdater } from '@src/updater'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
export const helpRole = ( export const helpRole = (
mainWindow: BrowserWindow mainWindow: BrowserWindow
@ -26,14 +27,16 @@ export const helpRole = (
id: 'Help.KCL code samples', id: 'Help.KCL code samples',
click: () => { click: () => {
shell shell
.openExternal('https://zoo.dev/docs/kcl-samples') .openExternal(withSiteBaseURL('/docs/kcl-samples'))
.catch(reportRejection) .catch(reportRejection)
}, },
}, },
{ {
label: 'KCL Docs', label: 'KCL Docs',
click: () => { click: () => {
shell.openExternal('https://zoo.dev/docs/kcl').catch(reportRejection) shell
.openExternal(withSiteBaseURL('/docs/kcl'))
.catch(reportRejection)
}, },
}, },
{ {
@ -116,7 +119,7 @@ export const helpRole = (
{ {
label: 'Manage Account', label: 'Manage Account',
click: () => { click: () => {
shell.openExternal('https://zoo.dev/account').catch(reportRejection) shell.openExternal(withSiteBaseURL('/account')).catch(reportRejection)
}, },
}, },
], ],

View File

@ -67,6 +67,7 @@ import {
defaultGlobalStatusBarItems, defaultGlobalStatusBarItems,
} from '@src/components/StatusBar/defaultStatusBarItems' } from '@src/components/StatusBar/defaultStatusBarItems'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
type ReadWriteProjectState = { type ReadWriteProjectState = {
value: boolean value: boolean
@ -367,9 +368,9 @@ const Home = () => {
<li className="contents"> <li className="contents">
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/docs" to={withSiteBaseURL('/account')}
onClick={openExternalBrowserIfDesktop( onClick={openExternalBrowserIfDesktop(
'https://zoo.dev/account' withSiteBaseURL('/account')
)} )}
className={sidebarButtonClasses} className={sidebarButtonClasses}
iconStart={{ iconStart={{
@ -384,8 +385,8 @@ const Home = () => {
<li className="contents"> <li className="contents">
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/blog" to={withSiteBaseURL('/blog')}
onClick={openExternalBrowserIfDesktop('https://zoo.dev/blog')} onClick={openExternalBrowserIfDesktop(withSiteBaseURL('/blog'))}
className={sidebarButtonClasses} className={sidebarButtonClasses}
iconStart={{ iconStart={{
icon: 'glasses', icon: 'glasses',

View File

@ -25,12 +25,12 @@ import { systemIOActor, commandBarActor } from '@src/lib/singletons'
import type { IndexLoaderData } from '@src/lib/types' import type { IndexLoaderData } from '@src/lib/types'
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
import { import {
browserAxialFan, browserAxialFan,
browserAxialFanAfterTextToCad, browserAxialFanAfterTextToCad,
} from '@src/lib/exampleKcl' } from '@src/lib/exampleKcl'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
type BrowserOnboaringRoute = RouteObject & { type BrowserOnboaringRoute = RouteObject & {
path: keyof typeof browserOnboardingPaths path: keyof typeof browserOnboardingPaths
@ -461,7 +461,7 @@ function PromptToEditResult() {
function OnboardingConclusion() { function OnboardingConclusion() {
// Close the panes on mount, close on unmount // Close the panes on mount, close on unmount
useOnboardingPanes() useOnboardingPanes()
const downloadLink = `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}` const downloadLink = withSiteBaseURL(`/${APP_DOWNLOAD_PATH}`)
return ( return (
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-center items-center"> <div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-center items-center">

View File

@ -27,8 +27,8 @@ import {
modifiedFanHousingBrowser, modifiedFanHousingBrowser,
modifiedParametersDesktop, modifiedParametersDesktop,
} from '@src/lib/exampleKcl' } from '@src/lib/exampleKcl'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
type DesktopOnboardingRoute = RouteObject & { type DesktopOnboardingRoute = RouteObject & {
path: keyof typeof desktopOnboardingPaths path: keyof typeof desktopOnboardingPaths
@ -642,10 +642,8 @@ function OnboardingConclusion() {
project, click the Zoo button in the top left. To learn more detailed project, click the Zoo button in the top left. To learn more detailed
and advanced techniques,{' '} and advanced techniques,{' '}
<a <a
onClick={openExternalBrowserIfDesktop( onClick={openExternalBrowserIfDesktop(withSiteBaseURL('/docs'))}
`${VITE_KC_SITE_BASE_URL}/docs` href={`${withSiteBaseURL('/docs')}`}
)}
href={`${VITE_KC_SITE_BASE_URL}/docs`}
> >
check out our docs check out our docs
</a> </a>

View File

@ -6,7 +6,6 @@ import { Link } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
import { Logo } from '@src/components/Logo' import { Logo } from '@src/components/Logo'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_NAME } from '@src/lib/constants' import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
@ -15,7 +14,7 @@ import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { authActor, useSettings } from '@src/lib/singletons' import { authActor, useSettings } from '@src/lib/singletons'
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils' import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL' import { withAPIBaseURL, withSiteBaseURL } from '@src/lib/withBaseURL'
const subtleBorder = const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80' 'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
@ -37,7 +36,7 @@ const SignIn = () => {
app: { theme }, app: { theme },
} = useSettings() } = useSettings()
const signInUrl = generateSignInUrl() const signInUrl = generateSignInUrl()
const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel-assembly` const kclSampleUrl = withSiteBaseURL('/docs/kcl-samples/car-wheel-assembly')
const getThemeText = useCallback( const getThemeText = useCallback(
(shouldContrast = true) => (shouldContrast = true) =>
@ -261,7 +260,7 @@ const SignIn = () => {
<div className="flex gap-4 flex-wrap items-center"> <div className="flex gap-4 flex-wrap items-center">
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/docs/kcl-samples/pillow-block-bearing" to={withSiteBaseURL('/docs/kcl-samples/pillow-block-bearing')}
iconStart={{ iconStart={{
icon: 'settings', icon: 'settings',
bgClassName: '!bg-transparent', bgClassName: '!bg-transparent',
@ -274,7 +273,7 @@ const SignIn = () => {
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/docs/zoo-design-studio/text-to-cad" to={withSiteBaseURL('/docs/zoo-design-studio/text-to-cad')}
iconStart={{ iconStart={{
icon: 'sparkles', icon: 'sparkles',
bgClassName: '!bg-transparent', bgClassName: '!bg-transparent',
@ -297,7 +296,7 @@ const SignIn = () => {
<div className="flex gap-4 flex-wrap items-center"> <div className="flex gap-4 flex-wrap items-center">
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/design-api" to={withSiteBaseURL('/design-api')}
iconStart={{ icon: 'sketch', bgClassName: '!bg-transparent' }} iconStart={{ icon: 'sketch', bgClassName: '!bg-transparent' }}
className="!bg-primary !text-chalkboard-10 !border-transarent" className="!bg-primary !text-chalkboard-10 !border-transarent"
> >
@ -305,7 +304,7 @@ const SignIn = () => {
</ActionButton> </ActionButton>
<ActionButton <ActionButton
Element="externalLink" Element="externalLink"
to="https://zoo.dev/machine-learning-api" to={withSiteBaseURL('/machine-learning-api')}
iconStart={{ iconStart={{
icon: 'elephant', icon: 'elephant',
bgClassName: '!bg-transparent', bgClassName: '!bg-transparent',

View File

@ -1,15 +1,16 @@
import { NODE_ENV, VITE_KC_SITE_BASE_URL } from '@src/env' import env from '@src/env'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { import {
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM, IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM,
} from '@src/lib/constants' } from '@src/lib/constants'
import { PATHS } from '@src/lib/paths' import { PATHS } from '@src/lib/paths'
import { withSiteBaseURL } from '@src/lib/withBaseURL'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true' const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
export const APP_VERSION = export const APP_VERSION =
isTestEnv && NODE_ENV === 'development' isTestEnv && env().NODE_ENV === 'development'
? '11.22.33' ? '11.22.33'
: isDesktop() : isDesktop()
? // @ts-ignore ? // @ts-ignore
@ -54,7 +55,7 @@ export function generateSignInUrl() {
'?' '?'
) )
return `${VITE_KC_SITE_BASE_URL}${ return withSiteBaseURL(
PATHS.SIGN_IN `${PATHS.SIGN_IN}?callbackUrl=${encodeURIComponent(finalURL)}`
}?callbackUrl=${encodeURIComponent(finalURL)}` )
} }