Compare commits

...

21 Commits

Author SHA1 Message Date
cd672d52f6 Merge branch 'main' into pierremtb/issue7657-Allow-all-sweeps-to-work-on-variable-less-profiles 2025-07-04 11:33:24 -04:00
597f4a1d54 Bump vite from 5.4.18 to 5.4.19 in the security-major group (#7685)
* Bump vite from 5.4.18 to 5.4.19 in the security-major group

Bumps the security-major group with 1 update: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.4.18 to 5.4.19
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.19/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.19/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.19
  dependency-type: direct:development
  dependency-group: security-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-07-03 19:36:15 -04:00
c814c90a9e Rebuild when the environment changes (#7688) 2025-07-03 17:05:59 -04:00
64832f9046 Bump esbuild from 0.25.3 to 0.25.4 in the security group (#7684)
* Bump esbuild from 0.25.3 to 0.25.4 in the security group

Bumps the security group with 1 update: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.25.3 to 0.25.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.4
  dependency-type: direct:development
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update snapshots

* Update snapshots

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-03 20:16:46 +00:00
172e01529c Add basic addLoft and addRevolve tests, will have to see how to distribute coverage 2025-07-03 15:45:32 -04:00
5a4a32c044 Add addSweep test 2025-07-03 15:00:21 -04:00
b955184191 Lint & complete addExtrude tests 2025-07-03 14:31:06 -04:00
d7914219da We going 2025-07-03 13:58:10 -04:00
ead4c1286b Add other test case 2025-07-03 13:41:31 -04:00
34494f3bba Consolidate KittyCAD API token environment variables (#7665)
* Consolidate KittyCAD API token environment variables

* Remove duplicate variable in type definition

* Remove unnecessary intermediate steps

* Keep base label for concatenation functions
2025-07-03 13:15:21 -04:00
a0fe33260e WIP new util function 2025-07-03 12:15:20 -04:00
8955b5fcd3 Merge branch 'main' into pierremtb/issue7657-Allow-all-sweeps-to-work-on-variable-less-profiles 2025-07-03 11:28:57 -04:00
df6256266c [Chore] All api urls are now using the helper function (#7672)
* fix: logging information about the login

* chore: improving the withBaseURL workflow

* chore: moving VITE_KC_API_BASE_URL to the helper function

* fix: env to helper function api base url

* chore: fixing another api base url

* chore: shortlinks with base api helper function

* chore: prompt edit with base helper function

* fix: auto fmt

* fix: withAPIBaseURL for all urls

* fix: AI caught my typo, RIP

* fix: expected

* fix: renaming this so it is less specific to environment

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-07-03 13:54:03 +00:00
5708b8c64b Add unit test createPathToNodeForLastVariable 2025-07-03 08:50:08 -04:00
5b8284e737 Add unit tests for createVariableExpressionsArray 2025-07-03 07:44:29 -04:00
dd9b0ec5f0 Codespell 2025-07-02 19:38:34 -04:00
c467568ee4 Add unit tests for getVariableExprsFromSelection 2025-07-02 19:26:28 -04:00
cb976ec31b Fix circ dep 2025-07-02 17:42:40 -04:00
cc9eb65456 Fix test 2025-07-02 17:00:17 -04:00
a589f56e73 Merge branch 'main' into pierremtb/issue7657-Allow-all-sweeps-to-work-on-variable-less-profiles 2025-07-02 12:42:38 -04:00
4c1564e2b0 WIP: Allow all sweeps to work on variable-less profiles
Fixes #7657
2025-07-01 16:41:14 -04:00
42 changed files with 1066 additions and 372 deletions

View File

@ -3,17 +3,17 @@
NODE_ENV=development
DEV=true
# App
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
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_WASM_URL="optional way of overriding the wasm url, particular for unit tests which need this if you running not on the default 3000 port"
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app"
#token="required token for playwright. TODO: clean up env vars in #3973"
#VITE_WASM_URL="optional override of Wasm URL if not on default port 3000"
#VITE_KITTYCAD_API_TOKEN="required for testing, optional to skip auth in the app"
FAIL_ON_CONSOLE_ERRORS=true
# KCL
RUST_BACKTRACE=1
PYO3_PYTHON=/usr/local/bin/python3
#KITTYCAD_API_TOKEN="required token for engine testing"
FAIL_ON_CONSOLE_ERRORS=true
#KITTYCAD_API_TOKEN=$VITE_KITTYCAD_API_TOKEN

View File

@ -1,6 +1,8 @@
NODE_ENV=production
# App
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KITTYCAD_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_CONNECTION_TIMEOUT_MS=15000

View File

@ -157,7 +157,7 @@ jobs:
timeout_minutes: 5
max_attempts: 5
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -169,7 +169,7 @@ jobs:
if: always()
run: npm run test:snapshots -- --last-failed --update-snapshots
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -284,7 +284,7 @@ jobs:
timeout_minutes: 5
max_attempts: 5
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -410,7 +410,7 @@ jobs:
max_attempts: 9
env:
FAIL_ON_CONSOLE_ERRORS: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@ -62,7 +62,7 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a npm run test:unit
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}

View File

@ -65,7 +65,7 @@ If you're not a Zoo employee you won't be able to access the dev environment, yo
### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KITTYCAD_API_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome
@ -96,7 +96,7 @@ To package the app for your platform with electron-builder, run `npm run tronb:p
Prepare these system dependencies:
- Set $token from https://zoo.dev/account/api-tokens
- Set `$VITE_KITTYCAD_API_TOKEN` from https://zoo.dev/account/api-tokens
#### Snapshot tests (Google Chrome on Ubuntu only)
@ -259,7 +259,7 @@ If the application needs to overwrite the known file on disk use this pattern. T
- `npm run circular-deps:overwrite`
- `npm run url-checker:overwrite`
#### Diff baseline and current
#### Diff baseline and current
These commands will write a /tmp/ file on disk and compare it to the known file in the repository. This command will also be used in the CI CD pipeline for automated checks

View File

@ -49,7 +49,7 @@ RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) .env*
.PHONY: build
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js

View File

@ -1,6 +1,5 @@
import { expect, test } from '@e2e/playwright/zoo-test'
// test file is for testing auth functionality
test.describe('Authentication tests', () => {
test(
`The user can sign out and back in`,
@ -13,22 +12,12 @@ test.describe('Authentication tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.projectSection.waitFor()
// This is only needed as an override to test-utils' setup() for this test
await page.addInitScript(() => {
localStorage.setItem('TOKEN_PERSIST_KEY', '')
})
await test.step('Click on sign out and expect sign in page', async () => {
await toolbar.userSidebarButton.click()
await toolbar.signOutButton.click()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step("Refresh doesn't log the user back in", async () => {
await page.reload()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step('Click on sign in and cancel, click again and expect different code', async () => {
await signInPage.signInButton.click()
await expect(signInPage.userCode).toBeVisible()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -17,7 +17,7 @@ import dotenv from 'dotenv'
const NODE_ENV = process.env.NODE_ENV || 'development'
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
export const token = process.env.token || ''
export const token = process.env.VITE_KITTYCAD_API_TOKEN || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'

5
interface.d.ts vendored
View File

@ -73,13 +73,12 @@ export interface IElectronAPI {
process: {
env: {
IS_PLAYWRIGHT: string
VITE_KC_DEV_TOKEN: string
VITE_KITTYCAD_API_TOKEN: string
VITE_KC_API_WS_MODELING_URL: string
VITE_KC_API_BASE_URL: string
VITE_KITTYCAD_API_BASE_URL: string
VITE_KC_SITE_BASE_URL: string
VITE_KC_SITE_APP_URL: string
VITE_KC_CONNECTION_TIMEOUT_MS: string
VITE_KC_DEV_TOKEN: string
NODE_ENV: string
PROD: string
DEV: string

234
package-lock.json generated
View File

@ -149,7 +149,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^5.4.18",
"vite": "^5.4.19",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2",
@ -3652,9 +3652,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
"integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
"integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
"cpu": [
"ppc64"
],
@ -3668,9 +3668,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz",
"integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
"integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
"cpu": [
"arm"
],
@ -3684,9 +3684,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz",
"integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
"integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
"cpu": [
"arm64"
],
@ -3700,9 +3700,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz",
"integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
"integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
"cpu": [
"x64"
],
@ -3716,9 +3716,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz",
"integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
"integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
"cpu": [
"arm64"
],
@ -3732,9 +3732,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz",
"integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
"integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
"cpu": [
"x64"
],
@ -3748,9 +3748,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz",
"integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
"integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
"cpu": [
"arm64"
],
@ -3764,9 +3764,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz",
"integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
"integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
"cpu": [
"x64"
],
@ -3780,9 +3780,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz",
"integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
"integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
"cpu": [
"arm"
],
@ -3796,9 +3796,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz",
"integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
"integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
"cpu": [
"arm64"
],
@ -3812,9 +3812,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz",
"integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
"integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
"cpu": [
"ia32"
],
@ -3828,9 +3828,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz",
"integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
"integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
"cpu": [
"loong64"
],
@ -3844,9 +3844,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz",
"integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
"integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
"cpu": [
"mips64el"
],
@ -3860,9 +3860,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz",
"integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
"integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
"cpu": [
"ppc64"
],
@ -3876,9 +3876,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz",
"integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
"integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
"cpu": [
"riscv64"
],
@ -3892,9 +3892,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz",
"integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
"integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
"cpu": [
"s390x"
],
@ -3908,9 +3908,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz",
"integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
"integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
"cpu": [
"x64"
],
@ -3924,9 +3924,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz",
"integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
"integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
"cpu": [
"arm64"
],
@ -3940,9 +3940,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz",
"integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
"integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
"cpu": [
"x64"
],
@ -3956,9 +3956,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz",
"integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
"integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
"cpu": [
"arm64"
],
@ -3972,9 +3972,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz",
"integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
"integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
"cpu": [
"x64"
],
@ -3988,9 +3988,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz",
"integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
"integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
"cpu": [
"x64"
],
@ -4004,9 +4004,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz",
"integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
"integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
"cpu": [
"arm64"
],
@ -4020,9 +4020,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz",
"integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
"integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
"cpu": [
"ia32"
],
@ -4036,9 +4036,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz",
"integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
"integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
"cpu": [
"x64"
],
@ -13219,9 +13219,9 @@
"optional": true
},
"node_modules/esbuild": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -13231,31 +13231,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.3",
"@esbuild/android-arm": "0.25.3",
"@esbuild/android-arm64": "0.25.3",
"@esbuild/android-x64": "0.25.3",
"@esbuild/darwin-arm64": "0.25.3",
"@esbuild/darwin-x64": "0.25.3",
"@esbuild/freebsd-arm64": "0.25.3",
"@esbuild/freebsd-x64": "0.25.3",
"@esbuild/linux-arm": "0.25.3",
"@esbuild/linux-arm64": "0.25.3",
"@esbuild/linux-ia32": "0.25.3",
"@esbuild/linux-loong64": "0.25.3",
"@esbuild/linux-mips64el": "0.25.3",
"@esbuild/linux-ppc64": "0.25.3",
"@esbuild/linux-riscv64": "0.25.3",
"@esbuild/linux-s390x": "0.25.3",
"@esbuild/linux-x64": "0.25.3",
"@esbuild/netbsd-arm64": "0.25.3",
"@esbuild/netbsd-x64": "0.25.3",
"@esbuild/openbsd-arm64": "0.25.3",
"@esbuild/openbsd-x64": "0.25.3",
"@esbuild/sunos-x64": "0.25.3",
"@esbuild/win32-arm64": "0.25.3",
"@esbuild/win32-ia32": "0.25.3",
"@esbuild/win32-x64": "0.25.3"
"@esbuild/aix-ppc64": "0.25.4",
"@esbuild/android-arm": "0.25.4",
"@esbuild/android-arm64": "0.25.4",
"@esbuild/android-x64": "0.25.4",
"@esbuild/darwin-arm64": "0.25.4",
"@esbuild/darwin-x64": "0.25.4",
"@esbuild/freebsd-arm64": "0.25.4",
"@esbuild/freebsd-x64": "0.25.4",
"@esbuild/linux-arm": "0.25.4",
"@esbuild/linux-arm64": "0.25.4",
"@esbuild/linux-ia32": "0.25.4",
"@esbuild/linux-loong64": "0.25.4",
"@esbuild/linux-mips64el": "0.25.4",
"@esbuild/linux-ppc64": "0.25.4",
"@esbuild/linux-riscv64": "0.25.4",
"@esbuild/linux-s390x": "0.25.4",
"@esbuild/linux-x64": "0.25.4",
"@esbuild/netbsd-arm64": "0.25.4",
"@esbuild/netbsd-x64": "0.25.4",
"@esbuild/openbsd-arm64": "0.25.4",
"@esbuild/openbsd-x64": "0.25.4",
"@esbuild/sunos-x64": "0.25.4",
"@esbuild/win32-arm64": "0.25.4",
"@esbuild/win32-ia32": "0.25.4",
"@esbuild/win32-x64": "0.25.4"
}
},
"node_modules/escalade": {
@ -25132,11 +25132,10 @@
"optional": true
},
"node_modules/vite": {
"version": "5.4.18",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
"version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@ -26640,10 +26639,25 @@
"vscode-uri": "^3.1.0"
},
"devDependencies": {
"@types/node": "^22.14.1",
"@types/node": "^24.0.7",
"ts-node": "^10.9.2"
}
},
"packages/codemirror-lsp-client/node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"dependencies": {
"undici-types": "~7.8.0"
}
},
"packages/codemirror-lsp-client/node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true
},
"rust/kcl-language-server": {
"version": "0.0.0",
"license": "MIT",
@ -26661,7 +26675,7 @@
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3",
"esbuild": "^0.25.3",
"esbuild": "^0.25.4",
"glob": "^11.0.1",
"mocha": "^11.1.0",
"typescript": "^5.8.3"

View File

@ -227,7 +227,7 @@
"ts-node": "^10.0.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.30.1",
"vite": "^5.4.18",
"vite": "^5.4.19",
"vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2",

View File

@ -123,7 +123,7 @@
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3",
"esbuild": "^0.25.3",
"esbuild": "^0.25.4",
"glob": "^11.0.1",
"mocha": "^11.1.0",
"typescript": "^5.8.3"

View File

@ -4,6 +4,8 @@
URL STATUS
000 https://${BASE_URL}
405 https://api.dev.zoo.dev/oauth2/token/revoke
401 https://api.dev.zoo.dev/users
301 https://discord.gg/JQEpHR7Nt2
404 https://github.com/KittyCAD/engine/issues/3528
404 https://github.com/KittyCAD/modeling-app/commit/${ref}

View File

@ -7,7 +7,7 @@ import {
LanguageServerClient,
LspWorkerEventType,
} from '@kittycad/codemirror-lsp-client'
import { TEST, VITE_KC_API_BASE_URL } from '@src/env'
import { TEST } from '@src/env'
import React, { createContext, useContext, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import type * as LSP from 'vscode-languageserver-protocol'
@ -28,6 +28,7 @@ import type { FileEntry } from '@src/lib/project'
import { codeManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { useToken } from '@src/lib/singletons'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
return []
@ -85,7 +86,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: KclWorkerOptions = {
wasmUrl: wasmUrl(),
token: token,
apiBaseUrl: VITE_KC_API_BASE_URL,
apiBaseUrl: withAPIBaseURL(''),
}
lspWorker.postMessage({
worker: LspWorker.Kcl,
@ -178,7 +179,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: CopilotWorkerOptions = {
wasmUrl: wasmUrl(),
token: token,
apiBaseUrl: VITE_KC_API_BASE_URL,
apiBaseUrl: withAPIBaseURL(''),
}
lspWorker.postMessage({
worker: LspWorker.Copilot,

View File

@ -8,12 +8,14 @@ export const NODE_ENV = env.NODE_ENV as string | undefined
export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
| string
| undefined
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL
export const VITE_KITTYCAD_API_BASE_URL = env.VITE_KITTYCAD_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL
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 VITE_KC_DEV_TOKEN = env.VITE_KC_DEV_TOKEN as string | undefined
export const VITE_KITTYCAD_API_TOKEN = env.VITE_KITTYCAD_API_TOKEN as
| string
| 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

View File

@ -2,9 +2,12 @@ import type { Node } from '@rust/kcl-lib/bindings/Node'
import {
createArrayExpression,
createCallExpressionStdLibKw,
createIdentifier,
createLabeledArg,
createLiteral,
createLiteralMaybeSuffix,
createLocalName,
createObjectExpression,
createPipeExpression,
createPipeSubstitution,
@ -14,12 +17,19 @@ import {
} from '@src/lang/create'
import {
addSketchTo,
createPathToNodeForLastVariable,
createVariableExpressionsArray,
deleteSegmentFromPipeExpression,
moveValueIntoNewVariable,
setCallInAst,
sketchOnExtrudedFace,
splitPipedProfile,
} from '@src/lang/modifyAst'
import { findUsesOfTagInPipe } from '@src/lang/queryAst'
import {
findUsesOfTagInPipe,
getNodeFromPath,
getVariableExprsFromSelection,
} from '@src/lang/queryAst'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import type { Artifact } from '@src/lang/std/artifactGraph'
import { codeRefFromRange } from '@src/lang/std/artifactGraph'
@ -31,6 +41,7 @@ import { enginelessExecutor } from '@src/lib/testHelpers'
import { err } from '@src/lib/trap'
import { deleteFromSelection } from '@src/lang/modifyAst/deleteFromSelection'
import { assertNotErr } from '@src/unitTestUtils'
import type { Selections } from '@src/lib/selections'
beforeAll(async () => {
await initPromise
@ -917,3 +928,212 @@ extrude001 = extrude(part001, length = 5)
expect(result instanceof Error).toBe(true)
})
})
describe('Testing createVariableExpressionsArray', () => {
it('should return null for any number of pipe substitutions', () => {
const onePipe = [createPipeSubstitution()]
const twoPipes = [createPipeSubstitution(), createPipeSubstitution()]
const threePipes = [
createPipeSubstitution(),
createPipeSubstitution(),
createPipeSubstitution(),
]
expect(createVariableExpressionsArray(onePipe)).toBeNull()
expect(createVariableExpressionsArray(twoPipes)).toBeNull()
expect(createVariableExpressionsArray(threePipes)).toBeNull()
})
it('should create a variable expressions for one variable', () => {
const oneVariableName = [createLocalName('var1')]
const expr = createVariableExpressionsArray(oneVariableName)
if (expr?.type !== 'Name') {
throw new Error(`Expected Literal type, got ${expr?.type}`)
}
expect(expr.name.name).toBe('var1')
})
it('should create an array of variable expressions for two variables', () => {
const twoVariableNames = [createLocalName('var1'), createLocalName('var2')]
const exprs = createVariableExpressionsArray(twoVariableNames)
if (exprs?.type !== 'ArrayExpression') {
throw new Error('Expected ArrayExpression type')
}
expect(exprs.elements).toHaveLength(2)
if (
exprs.elements[0].type !== 'Name' ||
exprs.elements[1].type !== 'Name'
) {
throw new Error(
`Expected elements to be of type Name, got ${exprs.elements[0].type} and ${exprs.elements[1].type}`
)
}
expect(exprs.elements[0].name.name).toBe('var1')
expect(exprs.elements[1].name.name).toBe('var2')
})
// This would catch the issue at https://github.com/KittyCAD/modeling-app/issues/7669
// TODO: fix function to get this test to pass
// it('should create one expr if the array of variable names are the same', () => {
// const twoVariableNames = [createLocalName('var1'), createLocalName('var1')]
// const expr = createVariableExpressionsArray(twoVariableNames)
// if (expr?.type !== 'Name') {
// throw new Error(`Expected Literal type, got ${expr?.type}`)
// }
// expect(expr.name.name).toBe('var1')
// })
it('should create an array of variable expressions for one variable and a pipe', () => {
const oneVarOnePipe = [createPipeSubstitution(), createLocalName('var1')]
const exprs = createVariableExpressionsArray(oneVarOnePipe)
if (exprs?.type !== 'ArrayExpression') {
throw new Error('Expected ArrayExpression type')
}
expect(exprs.elements).toHaveLength(2)
expect(exprs.elements[0].type).toBe('PipeSubstitution')
if (exprs.elements[1].type !== 'Name') {
throw new Error(
`Expected elements[1] to be of type Name, got ${exprs.elements[1].type}`
)
}
expect(exprs.elements[1].name.name).toBe('var1')
})
})
describe('Testing createPathToNodeForLastVariable', () => {
it('should create a path to the last variable in the array', () => {
const circleProfileInVar = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
extrude001 = extrude(profile001, length = 5)
`
const ast = assertParse(circleProfileInVar)
const path = createPathToNodeForLastVariable(ast, false)
expect(path.length).toEqual(4)
// Verify we can get the right node
const node = getNodeFromPath<any>(ast, path)
if (err(node)) {
throw node
}
// With the expected range
const startOfExtrudeIndex = circleProfileInVar.indexOf('extrude(')
expect(node.node.start).toEqual(startOfExtrudeIndex)
expect(node.node.end).toEqual(circleProfileInVar.length - 1)
})
it('should create a path to the first kwarg in the last expression', () => {
const circleProfileInVar = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
extrude001 = extrude(profile001, length = 123)
`
const ast = assertParse(circleProfileInVar)
const path = createPathToNodeForLastVariable(ast, true)
expect(path.length).toEqual(7)
// Verify we can get the right node
const node = getNodeFromPath<any>(ast, path)
if (err(node)) {
throw node
}
// With the expected range
const startOfKwargIndex = circleProfileInVar.indexOf('123')
expect(node.node.start).toEqual(startOfKwargIndex)
expect(node.node.end).toEqual(startOfKwargIndex + 3)
})
})
describe('Testing setCallInAst', () => {
it('should push an extrude call with variable on variable profile', () => {
const code = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
`
const ast = assertParse(code)
const exprs = createVariableExpressionsArray([
createLocalName('profile001'),
])
const call = createCallExpressionStdLibKw('extrude', exprs, [
createLabeledArg('length', createLiteral(5)),
])
const pathToNode = setCallInAst(ast, call)
if (err(pathToNode)) {
throw pathToNode
}
const newCode = recast(ast)
expect(newCode).toContain(code)
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 5)`)
})
it('should push an extrude call in pipe is selection was in variable-less pipe', async () => {
const code = `startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
`
const ast = assertParse(code)
const { artifactGraph } = await enginelessExecutor(ast)
const artifact = artifactGraph.values().find((a) => a.type === 'path')
if (!artifact) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: [
{
codeRef: artifact.codeRef,
artifact,
},
],
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
const exprs = createVariableExpressionsArray(variableExprs.exprs)
const call = createCallExpressionStdLibKw('extrude', exprs, [
createLabeledArg('length', createLiteral(5)),
])
const lastPathToNode = variableExprs.paths.pop()
const pathToNode = setCallInAst(ast, call, undefined, lastPathToNode)
if (err(pathToNode)) {
throw pathToNode
}
const newCode = recast(ast)
expect(newCode).toContain(code)
expect(newCode).toContain(`|> extrude(length = 5)`)
})
it('should push an extrude call with variable if selection was in variable pipe', async () => {
const code = `profile001 = startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
`
const ast = assertParse(code)
const { artifactGraph } = await enginelessExecutor(ast)
const artifact = artifactGraph.values().find((a) => a.type === 'path')
if (!artifact) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: [
{
codeRef: artifact.codeRef,
artifact,
},
],
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
const exprs = createVariableExpressionsArray(variableExprs.exprs)
const call = createCallExpressionStdLibKw('extrude', exprs, [
createLabeledArg('length', createLiteral(5)),
])
const lastPathToNode = variableExprs.paths.pop()
const pathToNode = setCallInAst(ast, call, undefined, lastPathToNode)
if (err(pathToNode)) {
throw pathToNode
}
const newCode = recast(ast)
expect(newCode).toContain(code)
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 5)`)
})
})

View File

@ -1209,3 +1209,83 @@ export function insertVariableAndOffsetPathToNode(
}
}
}
// Create an array expression for variables,
// or keep it null if all are PipeSubstitutions
export function createVariableExpressionsArray(sketches: Expr[]): Expr | null {
let exprs: Expr | null = null
if (sketches.every((s) => s.type === 'PipeSubstitution')) {
// Keeping null so we don't even put it the % sign
} else if (sketches.length === 1) {
exprs = sketches[0]
} else {
exprs = createArrayExpression(sketches)
}
return exprs
}
// Create a path to node to the last variable declaroator of an ast
// Optionally, can point to the first kwarg of the CallExpressionKw
export function createPathToNodeForLastVariable(
ast: Node<Program>,
toFirstKwarg = true
): PathToNode {
const argIndex = 0 // first kwarg for all sweeps here
const pathToCall: PathToNode = [
['body', ''],
[ast.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
if (toFirstKwarg) {
pathToCall.push(
['arguments', 'CallExpressionKw'],
[argIndex, ARG_INDEX_FIELD],
['arg', LABELED_ARG_FIELD]
)
}
return pathToCall
}
export function setCallInAst(
ast: Node<Program>,
call: Node<CallExpressionKw>,
nodeToEdit?: PathToNode,
lastPathToNode?: PathToNode
): Error | PathToNode {
let pathToNode: PathToNode | undefined
if (nodeToEdit) {
const result = getNodeFromPath<CallExpressionKw>(
ast,
nodeToEdit,
'CallExpressionKw'
)
if (err(result)) {
return result
}
Object.assign(result.node, call)
pathToNode = nodeToEdit
} else {
if (!call.unlabeled && lastPathToNode) {
const pipe = getNodeFromPath<PipeExpression>(
ast,
lastPathToNode,
'PipeExpression'
)
if (err(pipe)) {
return pipe
}
pipe.node.body.push(call)
pathToNode = lastPathToNode
} else {
const name = findUniqueName(ast, call.callee.name.name)
const declaration = createVariableDeclaration(name, call)
ast.body.push(declaration)
pathToNode = createPathToNodeForLastVariable(ast)
}
}
return pathToNode
}

View File

@ -1,4 +1,4 @@
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { createLiteral } from '@src/lang/create'
import type {
@ -40,10 +40,9 @@ import { isOverlap } from '@src/lib/utils'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KC_DEV_TOKEN,
token: VITE_KITTYCAD_API_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -0,0 +1,252 @@
import {
type Artifact,
assertParse,
type CodeRef,
type Program,
recast,
} from '@src/lang/wasm'
import type { Selection, Selections } from '@src/lib/selections'
import { enginelessExecutor } from '@src/lib/testHelpers'
import { err } from '@src/lib/trap'
import {
addExtrude,
addLoft,
addRevolve,
addSweep,
} from '@src/lang/modifyAst/sweeps'
import { stringToKclExpression } from '@src/lib/kclHelpers'
import type { Node } from '@rust/kcl-lib/bindings/Node'
async function getAstAndArtifactGraph(code: string) {
const ast = assertParse(code)
if (err(ast)) throw ast
const { artifactGraph } = await enginelessExecutor(ast)
return { ast, artifactGraph }
}
function createSelectionFromPathArtifact(
artifacts: (Artifact & { codeRef: CodeRef })[]
): Selections {
const graphSelections = artifacts.map(
(artifact) =>
({
codeRef: artifact.codeRef,
artifact,
}) as Selection
)
return {
graphSelections,
otherSelections: [],
}
}
async function getAstAndSketchSelections(code: string) {
const { ast, artifactGraph } = await getAstAndArtifactGraph(code)
const artifacts = [...artifactGraph.values()].filter((a) => a.type === 'path')
if (artifacts.length === 0) {
throw new Error('Artifact not found in the graph')
}
const sketches = createSelectionFromPathArtifact(artifacts)
return { ast, sketches }
}
async function getKclCommandValue(value: string) {
const result = await stringToKclExpression(value)
if (err(result) || 'errors' in result) {
throw new Error(`Couldn't create kcl expression`)
}
return result
}
async function runNewAstAndCheckForSweep(ast: Node<Program>) {
const { artifactGraph } = await enginelessExecutor(ast)
console.log('artifactGraph', artifactGraph)
const sweepArtifact = artifactGraph.values().find((a) => a.type === 'sweep')
expect(sweepArtifact).toBeDefined()
}
describe('Testing addExtrude', () => {
it('should push a call in pipe if selection was in variable-less pipe', async () => {
const code = `startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
const length = await getKclCommandValue('1')
const result = addExtrude({ ast, sketches, length })
if (err(result)) throw result
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(`|> extrude(length = 1)`)
await runNewAstAndCheckForSweep(result.modifiedAst)
})
it('should push a call with variable if selection was in variable profile', async () => {
const code = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
const length = await getKclCommandValue('2')
const result = addExtrude({ ast, sketches, length })
if (err(result)) throw result
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 2)`)
await runNewAstAndCheckForSweep(result.modifiedAst)
})
it('should push a call with variable if selection was in variable pipe', async () => {
const code = `profile001 = startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
const length = await getKclCommandValue('3')
const result = addExtrude({ ast, sketches, length })
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst)
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(`extrude001 = extrude(profile001, length = 3)`)
})
it('should push a call with many compatible optional args if asked', async () => {
const code = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
const length = await getKclCommandValue('10')
const bidirectionalLength = await getKclCommandValue('20')
const twistAngle = await getKclCommandValue('30')
const result = addExtrude({
ast,
sketches,
length,
bidirectionalLength,
twistAngle,
})
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst)
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(`extrude001 = extrude(
profile001,
length = 10,
bidirectionalLength = 20,
twistAngle = 30,
)`)
})
// TODO: missing edit flow test
// TODO: missing multi-profile test
})
describe('Testing addSweep', () => {
it('should push a call with variable and all compatible optional args', async () => {
const code = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
sketch002 = startSketchOn(XZ)
profile002 = startProfile(sketch002, at = [0, 0])
|> xLine(length = -5)
|> tangentialArc(endAbsolute = [-20, 5])
`
const { ast, artifactGraph } = await getAstAndArtifactGraph(code)
const artifact1 = artifactGraph.values().find((a) => a.type === 'path')
const artifact2 = [...artifactGraph.values()].findLast(
(a) => a.type === 'path'
)
if (!artifact1 || !artifact2) {
throw new Error('Artifact not found in the graph')
}
const sketches = createSelectionFromPathArtifact([artifact1])
const path = createSelectionFromPathArtifact([artifact2])
const sectional = true
const relativeTo = 'sketchPlane'
const result = addSweep({
ast,
sketches,
path,
sectional,
relativeTo,
})
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst)
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(`sweep001 = sweep(
profile001,
path = profile002,
sectional = true,
relativeTo = 'sketchPlane',
)`)
})
// TODO: missing edit flow test
// TODO: missing multi-profile test
})
describe('Testing addLoft', () => {
it('should push a call with variable and all optional args if asked', async () => {
const code = `sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [0, 0], radius = 30)
plane001 = offsetPlane(XZ, offset = 50)
sketch002 = startSketchOn(plane001)
profile002 = circle(sketch002, center = [0, 0], radius = 20)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
expect(sketches.graphSelections).toHaveLength(2)
const vDegree = await getKclCommandValue('3')
const result = addLoft({
ast,
sketches,
vDegree,
})
if (err(result)) throw result
const newCode = recast(result.modifiedAst)
expect(newCode).toContain(code)
expect(newCode).toContain(
`loft001 = loft([profile001, profile002], vDegree = 3)`
)
// Don't think we can find the artifact here for loft?
})
// TODO: missing edit flow test
})
describe('Testing addRevolve', () => {
it('should push a call with variable and compatible optional args if asked', async () => {
const code = `sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [3, 0], radius = 1)
`
const { ast, sketches } = await getAstAndSketchSelections(code)
expect(sketches.graphSelections).toHaveLength(1)
const result = addRevolve({
ast,
sketches,
angle: await getKclCommandValue('1'),
axisOrEdge: 'Axis',
axis: 'X',
edge: undefined,
symmetric: false,
bidirectionalAngle: await getKclCommandValue('2'),
})
if (err(result)) throw result
await runNewAstAndCheckForSweep(result.modifiedAst)
const newCode = recast(result.modifiedAst)
console.log(newCode)
expect(newCode).toContain(code)
expect(newCode).toContain(`revolve001 = revolve(
profile001,
angle = 1,
axis = X,
bidirectionalAngle = 2,
)`)
})
// TODO: missing edit flow test
// TODO: missing multi-profile test
})

View File

@ -1,35 +1,28 @@
import type { Node } from '@rust/kcl-lib/bindings/Node'
import {
createArrayExpression,
createCallExpressionStdLibKw,
createLabeledArg,
createLiteral,
createLocalName,
createVariableDeclaration,
findUniqueName,
} from '@src/lang/create'
import { insertVariableAndOffsetPathToNode } from '@src/lang/modifyAst'
import {
createVariableExpressionsArray,
insertVariableAndOffsetPathToNode,
setCallInAst,
} from '@src/lang/modifyAst'
import {
getEdgeTagCall,
mutateAstWithTagForSketchSegment,
} from '@src/lang/modifyAst/addEdgeTreatment'
import {
getNodeFromPath,
getSketchExprsFromSelection,
getVariableExprsFromSelection,
valueOrVariable,
} from '@src/lang/queryAst'
import { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import type {
CallExpressionKw,
Expr,
PathToNode,
Program,
VariableDeclaration,
} from '@src/lang/wasm'
import type { PathToNode, Program, VariableDeclaration } from '@src/lang/wasm'
import type { KclCommandValue } from '@src/lib/commandTypes'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from '@src/lib/constants'
import type { Selections } from '@src/lib/selections'
import { err } from '@src/lib/trap'
@ -60,13 +53,13 @@ export function addExtrude({
// 2. Prepare unlabeled and labeled arguments
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
const sketchesExprList = getSketchExprsFromSelection(
const variableExpressions = getVariableExprsFromSelection(
sketches,
modifiedAst,
nodeToEdit
)
if (err(sketchesExprList)) {
return sketchesExprList
if (err(variableExpressions)) {
return variableExpressions
}
// Extra labeled args expressions
@ -85,7 +78,7 @@ export function addExtrude({
? [createLabeledArg('twistAngle', valueOrVariable(twistAngle))]
: []
const sketchesExpr = createSketchExpression(sketchesExprList)
const sketchesExpr = createVariableExpressionsArray(variableExpressions.exprs)
const call = createCallExpressionStdLibKw('extrude', sketchesExpr, [
createLabeledArg('length', valueOrVariable(length)),
...symmetricExpr,
@ -114,27 +107,10 @@ export function addExtrude({
// 3. If edit, we assign the new function call declaration to the existing node,
// otherwise just push to the end
let pathToNode: PathToNode | undefined
if (nodeToEdit) {
const result = getNodeFromPath<CallExpressionKw>(
modifiedAst,
nodeToEdit,
'CallExpressionKw'
)
if (err(result)) {
return result
}
Object.assign(result.node, call)
pathToNode = nodeToEdit
} else {
const name = findUniqueName(
modifiedAst,
KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE
)
const declaration = createVariableDeclaration(name, call)
modifiedAst.body.push(declaration)
pathToNode = createPathToNode(modifiedAst)
const lastPath = variableExpressions.paths.pop() // TODO: check if this is correct
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
if (err(pathToNode)) {
return pathToNode
}
return {
@ -168,13 +144,13 @@ export function addSweep({
// 2. Prepare unlabeled and labeled arguments
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
const sketchesExprList = getSketchExprsFromSelection(
const variableExprs = getVariableExprsFromSelection(
sketches,
modifiedAst,
nodeToEdit
)
if (err(sketchesExprList)) {
return sketchesExprList
if (err(variableExprs)) {
return variableExprs
}
// Find the path declaration for the labeled argument
@ -196,7 +172,7 @@ export function addSweep({
? [createLabeledArg('relativeTo', createLiteral(relativeTo))]
: []
const sketchesExpr = createSketchExpression(sketchesExprList)
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
const call = createCallExpressionStdLibKw('sweep', sketchesExpr, [
createLabeledArg('path', pathExpr),
...sectionalExpr,
@ -205,27 +181,10 @@ export function addSweep({
// 3. If edit, we assign the new function call declaration to the existing node,
// otherwise just push to the end
let pathToNode: PathToNode | undefined
if (nodeToEdit) {
const result = getNodeFromPath<CallExpressionKw>(
modifiedAst,
nodeToEdit,
'CallExpressionKw'
)
if (err(result)) {
return result
}
Object.assign(result.node, call)
pathToNode = nodeToEdit
} else {
const name = findUniqueName(
modifiedAst,
KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP
)
const declaration = createVariableDeclaration(name, call)
modifiedAst.body.push(declaration)
pathToNode = createPathToNode(modifiedAst)
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
if (err(pathToNode)) {
return pathToNode
}
return {
@ -255,13 +214,13 @@ export function addLoft({
// 2. Prepare unlabeled and labeled arguments
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
const sketchesExprList = getSketchExprsFromSelection(
const variableExprs = getVariableExprsFromSelection(
sketches,
modifiedAst,
nodeToEdit
)
if (err(sketchesExprList)) {
return sketchesExprList
if (err(variableExprs)) {
return variableExprs
}
// Extra labeled args expressions
@ -269,7 +228,7 @@ export function addLoft({
? [createLabeledArg('vDegree', valueOrVariable(vDegree))]
: []
const sketchesExpr = createSketchExpression(sketchesExprList)
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
const call = createCallExpressionStdLibKw('loft', sketchesExpr, [
...vDegreeExpr,
])
@ -281,25 +240,10 @@ export function addLoft({
// 3. If edit, we assign the new function call declaration to the existing node,
// otherwise just push to the end
let pathToNode: PathToNode | undefined
if (nodeToEdit) {
const result = getNodeFromPath<CallExpressionKw>(
modifiedAst,
nodeToEdit,
'CallExpressionKw'
)
if (err(result)) {
return result
}
Object.assign(result.node, call)
pathToNode = nodeToEdit
} else {
const name = findUniqueName(modifiedAst, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
const declaration = createVariableDeclaration(name, call)
modifiedAst.body.push(declaration)
const toFirstKwarg = !!vDegree
pathToNode = createPathToNode(modifiedAst, toFirstKwarg)
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
if (err(pathToNode)) {
return pathToNode
}
return {
@ -339,13 +283,13 @@ export function addRevolve({
// 2. Prepare unlabeled and labeled arguments
// Map the sketches selection into a list of kcl expressions to be passed as unlabelled argument
const sketchesExprList = getSketchExprsFromSelection(
const variableExprs = getVariableExprsFromSelection(
sketches,
modifiedAst,
nodeToEdit
)
if (err(sketchesExprList)) {
return sketchesExprList
if (err(variableExprs)) {
return variableExprs
}
// Retrieve axis expression depending on mode
@ -372,7 +316,7 @@ export function addRevolve({
]
: []
const sketchesExpr = createSketchExpression(sketchesExprList)
const sketchesExpr = createVariableExpressionsArray(variableExprs.exprs)
const call = createCallExpressionStdLibKw('revolve', sketchesExpr, [
createLabeledArg('angle', valueOrVariable(angle)),
createLabeledArg('axis', getAxisResult.generatedAxis),
@ -399,27 +343,10 @@ export function addRevolve({
// 3. If edit, we assign the new function call declaration to the existing node,
// otherwise just push to the end
let pathToNode: PathToNode | undefined
if (nodeToEdit) {
const result = getNodeFromPath<CallExpressionKw>(
modifiedAst,
nodeToEdit,
'CallExpressionKw'
)
if (err(result)) {
return result
}
Object.assign(result.node, call)
pathToNode = nodeToEdit
} else {
const name = findUniqueName(
modifiedAst,
KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE
)
const declaration = createVariableDeclaration(name, call)
modifiedAst.body.push(declaration)
pathToNode = createPathToNode(modifiedAst)
const lastPath = variableExprs.paths.pop() // TODO: check if this is correct
const pathToNode = setCallInAst(modifiedAst, call, nodeToEdit, lastPath)
if (err(pathToNode)) {
return pathToNode
}
return {
@ -430,40 +357,6 @@ export function addRevolve({
// Utilities
function createSketchExpression(sketches: Expr[]) {
let sketchesExpr: Expr | null = null
if (sketches.every((s) => s.type === 'PipeSubstitution')) {
// Keeping null so we don't even put it the % sign
} else if (sketches.length === 1) {
sketchesExpr = sketches[0]
} else {
sketchesExpr = createArrayExpression(sketches)
}
return sketchesExpr
}
function createPathToNode(
modifiedAst: Node<Program>,
toFirstKwarg = true
): PathToNode {
const argIndex = 0 // first kwarg for all sweeps here
const pathToCall: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
if (toFirstKwarg) {
pathToCall.push(
['arguments', 'CallExpressionKw'],
[argIndex, ARG_INDEX_FIELD],
['arg', LABELED_ARG_FIELD]
)
}
return pathToCall
}
export function getAxisExpressionAndIndex(
axisOrEdge: 'Axis' | 'Edge',
axis: string | undefined,

View File

@ -4,16 +4,15 @@ import { initPromise } from '@src/lang/wasmUtils'
import { err } from '@src/lib/trap'
import type { Selection } from '@src/lib/selections'
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KC_DEV_TOKEN,
token: VITE_KITTYCAD_API_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -15,6 +15,7 @@ import {
findAllPreviousVariables,
findUsesOfTagInPipe,
getNodeFromPath,
getVariableExprsFromSelection,
hasSketchPipeBeenExtruded,
isCursorInFunctionDefinition,
isNodeSafeToReplace,
@ -27,7 +28,7 @@ import { topLevelRange } from '@src/lang/util'
import type { Identifier, PathToNode } from '@src/lang/wasm'
import { assertParse, recast } from '@src/lang/wasm'
import { initPromise } from '@src/lang/wasmUtils'
import { type Selection } from '@src/lib/selections'
import type { Selections, Selection } from '@src/lib/selections'
import { enginelessExecutor } from '@src/lib/testHelpers'
import { err } from '@src/lib/trap'
@ -778,3 +779,184 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
expect(result).toEqual(false)
})
})
describe('Testing getVariableExprsFromSelection', () => {
it('should find the variable expr in a simple profile selection', async () => {
const circleProfileInVar = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
`
const ast = assertParse(circleProfileInVar)
const { artifactGraph } = await enginelessExecutor(ast)
const artifact = artifactGraph.values().find((a) => a.type === 'path')
if (!artifact) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: [
{
codeRef: artifact.codeRef,
artifact,
},
],
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
expect(variableExprs.exprs).toHaveLength(1)
if (variableExprs.exprs[0].type !== 'Name') {
throw new Error(`Expected Name, got ${variableExprs.exprs[0].type}`)
}
expect(variableExprs.exprs[0].name.name).toEqual('profile001')
expect(variableExprs.paths).toHaveLength(1)
expect(variableExprs.paths[0]).toEqual([
['body', ''],
[1, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
])
})
it('should return the pipe substitution symbol in a variable-less simple profile selection', async () => {
const circleProfileInVar = `startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
`
const ast = assertParse(circleProfileInVar)
const { artifactGraph } = await enginelessExecutor(ast)
const artifact = artifactGraph.values().find((a) => a.type === 'path')
if (!artifact) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: [
{
codeRef: artifact.codeRef,
artifact,
},
],
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
expect(variableExprs.exprs).toHaveLength(1)
expect(variableExprs.exprs[0].type).toEqual('PipeSubstitution')
expect(variableExprs.paths).toHaveLength(1)
expect(variableExprs.paths[0]).toEqual([
['body', ''],
[0, 'index'],
['expression', 'ExpressionStatement'],
['body', 'PipeExpression'],
[1, 'index'],
])
})
it('should find the variable exprs in a multi profile selection ', async () => {
const circleProfileInVar = `sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 1)
profile002 = circle(sketch001, center = [2, 2], radius = 1)
`
const ast = assertParse(circleProfileInVar)
const { artifactGraph } = await enginelessExecutor(ast)
const artifacts = [...artifactGraph.values()].filter(
(a) => a.type === 'path'
)
if (!artifacts || artifacts.length !== 2) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: artifacts.map((artifact) => {
return {
codeRef: artifact.codeRef,
artifact,
}
}),
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
expect(variableExprs.exprs).toHaveLength(2)
if (variableExprs.exprs[0].type !== 'Name') {
throw new Error(`Expected Name, got ${variableExprs.exprs[0].type}`)
}
if (variableExprs.exprs[1].type !== 'Name') {
throw new Error(`Expected Name, got ${variableExprs.exprs[1].type}`)
}
expect(variableExprs.exprs[0].name.name).toEqual('profile001')
expect(variableExprs.exprs[1].name.name).toEqual('profile002')
expect(variableExprs.paths).toHaveLength(2)
expect(variableExprs.paths[0]).toEqual([
['body', ''],
[1, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
])
expect(variableExprs.paths[1]).toEqual([
['body', ''],
[2, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
])
})
it('should return the pipe substitution symbol and a variable name in a complex multi profile selection', async () => {
const circleProfileInVar = `startSketchOn(XY)
|> circle(center = [0, 0], radius = 1)
profile002 = circle(sketch001, center = [2, 2], radius = 1)
`
const ast = assertParse(circleProfileInVar)
const { artifactGraph } = await enginelessExecutor(ast)
const artifacts = [...artifactGraph.values()].filter(
(a) => a.type === 'path'
)
if (!artifacts || artifacts.length !== 2) {
throw new Error('Artifact not found in the graph')
}
const selections: Selections = {
graphSelections: artifacts.map((artifact) => {
return {
codeRef: artifact.codeRef,
artifact,
}
}),
otherSelections: [],
}
const variableExprs = getVariableExprsFromSelection(selections, ast)
if (err(variableExprs)) throw variableExprs
expect(variableExprs.exprs).toHaveLength(2)
if (variableExprs.exprs[0].type !== 'PipeSubstitution') {
throw new Error(
`Expected PipeSubstitution, got ${variableExprs.exprs[0].type}`
)
}
if (variableExprs.exprs[1].type !== 'Name') {
throw new Error(`Expected Name, got ${variableExprs.exprs[1].type}`)
}
expect(variableExprs.exprs[1].name.name).toEqual('profile002')
expect(variableExprs.paths).toHaveLength(2)
expect(variableExprs.paths[0]).toEqual([
['body', ''],
[0, 'index'],
['expression', 'ExpressionStatement'],
['body', 'PipeExpression'],
[1, 'index'],
])
expect(variableExprs.paths[1]).toEqual([
['body', ''],
[1, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
])
})
})

View File

@ -1042,19 +1042,21 @@ export const valueOrVariable = (variable: KclCommandValue) => {
// Go from a selection of sketches to a list of KCL expressions that
// can be used to create KCL sweep call declarations.
export function getSketchExprsFromSelection(
export function getVariableExprsFromSelection(
selection: Selections,
ast: Node<Program>,
nodeToEdit?: PathToNode
): Error | Expr[] {
const sketches: Expr[] = selection.graphSelections.flatMap((s) => {
): Error | { exprs: Expr[]; paths: PathToNode[] } {
const paths: PathToNode[] = []
const exprs: Expr[] = []
for (const s of selection.graphSelections) {
const sketchVariable = getNodeFromPath<VariableDeclarator>(
ast,
s?.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(sketchVariable)) {
return []
continue
}
if (sketchVariable.node.id) {
@ -1071,22 +1073,27 @@ export function getSketchExprsFromSelection(
name === result.node.id.name
) {
// Pointing to same variable case
return createPipeSubstitution()
paths.push(nodeToEdit)
exprs.push(createPipeSubstitution())
continue
}
}
// Pointing to different variable case
return createLocalName(name)
} else {
// No variable case
return createPipeSubstitution()
paths.push(sketchVariable.deepPath)
exprs.push(createLocalName(name))
continue
}
})
if (sketches.length === 0) {
// No variable case
paths.push(sketchVariable.deepPath)
exprs.push(createPipeSubstitution())
}
if (exprs.length === 0) {
return new Error("Couldn't map selections to program references")
}
return sketches
return { exprs, paths }
}
// Go from the sketches argument in a KCL sweep call declaration

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KC_API_WS_MODELING_URL, VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
import { BSON } from 'bson'
@ -400,7 +400,7 @@ class EngineConnection extends EventTarget {
this.send({
type: 'headers',
headers: {
Authorization: `Bearer ${VITE_KC_DEV_TOKEN}`,
Authorization: `Bearer ${VITE_KITTYCAD_API_TOKEN}`,
},
})
}

View File

@ -1,4 +1,3 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { UAParser } from 'ua-parser-js'
import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo'
@ -11,6 +10,7 @@ import { isDesktop } from '@src/lib/isDesktop'
import type RustContext from '@src/lib/rustContext'
import screenshot from '@src/lib/screenshot'
import { APP_VERSION } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
/* eslint-disable suggest-no-throw/suggest-no-throw --
* All the throws in CoreDumpManager are intentional and should be caught and handled properly
@ -35,7 +35,7 @@ export class CoreDumpManager {
codeManager: CodeManager
rustContext: RustContext
token: string | undefined
baseUrl: string = VITE_KC_API_BASE_URL
baseUrl: string = withAPIBaseURL('')
constructor(
engineCommandManager: EngineCommandManager,

View File

@ -26,6 +26,7 @@ import { err } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types'
import { getInVariableCase } from '@src/lib/utils'
import { IS_STAGING } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function renameProjectDirectory(
projectPath: string,
@ -697,7 +698,9 @@ export const readTokenFile = async () => {
export const writeTokenFile = async (token: string) => {
const tokenFilePath = await getTokenFilePath()
if (err(token)) return Promise.reject(token)
return window.electron.writeFile(tokenFilePath, token)
const result = window.electron.writeFile(tokenFilePath, token)
console.log('token written to disk')
return result
}
export const writeTelemetryFile = async (content: string) => {
@ -722,12 +725,9 @@ export const setState = async (state: Project | undefined): Promise<void> => {
appStateStore = state
}
export const getUser = async (
token: string,
hostname: string
): Promise<Models['User_type']> => {
export const getUser = async (token: string): Promise<Models['User_type']> => {
try {
const user = await fetch(`${hostname}/users/me`, {
const user = await fetch(withAPIBaseURL('/users/me'), {
headers: new Headers({
Authorization: `Bearer ${token}`,
}),

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from '@src/env'
import { VITE_KC_SITE_APP_URL } from '@src/env'
import toast from 'react-hot-toast'
import { stringToBase64 } from '@src/lib/base64'
@ -7,6 +7,7 @@ import {
CREATE_FILE_URL_PARAM,
} from '@src/lib/constants'
import { err } from '@src/lib/trap'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export interface FileLinkParams {
code: string
@ -96,7 +97,7 @@ export async function createShortlink(
if (password) {
body.password = password
}
const response = await fetch(`${VITE_KC_API_BASE_URL}/user/shortlinks`, {
const response = await fetch(withAPIBaseURL('/user/shortlinks'), {
method: 'POST',
headers: {
'Content-type': 'application/json',

View File

@ -1,7 +1,7 @@
import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff'
import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
@ -28,6 +28,7 @@ import { uuidv4 } from '@src/lib/utils'
import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models'
import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
type KclFileMetaMap = {
[execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }>
@ -77,7 +78,7 @@ async function submitTextToCadRequest(
})
const response = await fetch(
`${VITE_KC_API_BASE_URL}/ml/text-to-cad/multi-file/iteration`,
withAPIBaseURL('/ml/text-to-cad/multi-file/iteration'),
{
method: 'POST',
headers: {
@ -304,7 +305,7 @@ export async function getPromptToEditResult(
id: string,
token?: string
): Promise<Models['TextToCadMultiFileIteration_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/async/operations/' + id
const url = withAPIBaseURL(`/async/operations/${id}`)
const data: Models['TextToCadMultiFileIteration_type'] | Error =
await crossPlatformFetch(
url,
@ -340,7 +341,7 @@ export async function doPromptEdit({
;(window as any).process = {
env: {
ZOO_API_TOKEN: token,
ZOO_HOST: VITE_KC_API_BASE_URL,
ZOO_HOST: withAPIBaseURL(''),
},
}
try {

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
import EditorManager from '@src/editor/manager'
import { KclManager } from '@src/lang/KclSingleton'
@ -171,7 +171,7 @@ const appMachine = setup({
systemId: BILLING,
input: {
...BILLING_CONTEXT_DEFAULTS,
urlUserService: VITE_KC_API_BASE_URL,
urlUserService: withAPIBaseURL(''),
},
}),
],

View File

@ -1,5 +1,4 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL } from '@src/env'
import toast from 'react-hot-toast'
import type { NavigateFunction } from 'react-router-dom'
import {
@ -19,6 +18,7 @@ import { err, reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils'
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
import { joinOSPaths } from '@src/lib/paths'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function submitTextToCadPrompt(
prompt: string,
@ -32,7 +32,7 @@ export async function submitTextToCadPrompt(
kcl_version: kclManager.kclVersion,
}
// Glb has a smaller footprint than gltf, should we want to render it.
const url = VITE_KC_API_BASE_URL + '/ai/text-to-cad/glb?kcl=true'
const url = withAPIBaseURL('/ai/text-to-cad/glb?kcl=true')
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url,
{
@ -58,7 +58,7 @@ export async function getTextToCadResult(
id: string,
token?: string
): Promise<Models['TextToCad_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id
const url = withAPIBaseURL(`/user/text-to-cad/${id}`)
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url,
{

View File

@ -1,14 +1,13 @@
import type { Models } from '@kittycad/lib/dist/types/src'
import { VITE_KC_API_BASE_URL } from '@src/env'
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function sendTelemetry(
id: string,
feedback: Models['MlFeedback_type'],
token?: string
): Promise<void> {
const url =
VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id + '?feedback=' + feedback
const url = withAPIBaseURL(`/user/text-to-cad/${id}?feedback=${feedback}`)
await crossPlatformFetch(
url,
{

View File

@ -0,0 +1,34 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL'
describe('withBaseURL', () => {
/**
* running in the development environment
* the .env.development should load
*/
describe('withAPIBaseUrl', () => {
it('should return base url', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
})
it('should return base url with /users', () => {
const expected = 'https://api.dev.zoo.dev/users'
const actual = withAPIBaseURL('/users')
expect(actual).toBe(expected)
})
it('should return a longer base url with /oauth2/token/revoke', () => {
const expected = 'https://api.dev.zoo.dev/oauth2/token/revoke'
const actual = withAPIBaseURL('/oauth2/token/revoke')
expect(actual).toBe(expected)
})
it('should ensure base url does not have ending slash', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
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,5 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { VITE_KITTYCAD_API_BASE_URL } from '@src/env'
export default function withBaseUrl(path: string): string {
return VITE_KC_API_BASE_URL + path
export function withAPIBaseURL(path: string): string {
return VITE_KITTYCAD_API_BASE_URL + path
}

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -10,10 +10,7 @@ import {
} from '@src/lib/desktop'
import { isDesktop } from '@src/lib/isDesktop'
import { markOnce } from '@src/lib/performance'
import {
default as withBaseURL,
default as withBaseUrl,
} from '@src/lib/withBaseURL'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
import { ACTOR_IDS } from '@src/machines/machineConstants'
export interface UserContext {
@ -31,11 +28,21 @@ export type Events =
}
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
/**
* Determine which token do we have persisted to initialize the auth machine
*/
const persistedCookie = getCookie(COOKIE_NAME)
const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const persistedDevToken = VITE_KITTYCAD_API_TOKEN
export const persistedToken =
VITE_KC_DEV_TOKEN ||
getCookie(COOKIE_NAME) ||
localStorage?.getItem(TOKEN_PERSIST_KEY) ||
''
persistedDevToken || persistedCookie || persistedLocalStorage
console.log('Initial persisted token')
console.table([
['cookie', !!persistedCookie],
['local storage', !!persistedLocalStorage],
['api token', !!persistedDevToken],
])
export const authMachine = setup({
types: {} as {
@ -132,7 +139,7 @@ export const authMachine = setup({
async function getUser(input: { token?: string }) {
const token = await getAndSyncStoredToken(input)
const url = withBaseURL('/user')
const url = withAPIBaseURL('/user')
const headers: { [key: string]: string } = {
'Content-Type': 'application/json',
}
@ -141,7 +148,7 @@ async function getUser(input: { token?: string }) {
if (token) headers['Authorization'] = `Bearer ${token}`
const userPromise = isDesktop()
? getUserDesktop(token, VITE_KC_API_BASE_URL)
? getUserDesktop(token)
: fetch(url, {
method: 'GET',
credentials: 'include',
@ -190,12 +197,24 @@ async function getAndSyncStoredToken(input: {
token?: string
}): Promise<string> {
// dev mode
if (VITE_KC_DEV_TOKEN) return VITE_KC_DEV_TOKEN
if (VITE_KITTYCAD_API_TOKEN) {
console.log('Token used for authentication')
console.table([['api token', !!VITE_KITTYCAD_API_TOKEN]])
return VITE_KITTYCAD_API_TOKEN
}
const token =
input.token && input.token !== ''
? input.token
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const inputToken = input.token && input.token !== '' ? input.token : ''
const cookieToken = getCookie(COOKIE_NAME)
const localStorageToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const token = inputToken || cookieToken || localStorageToken
console.log('Token used for authentication')
console.table([
['persisted token', !!inputToken],
['cookie', !!cookieToken],
['local storage', !!localStorageToken],
['api token', !!VITE_KITTYCAD_API_TOKEN],
])
if (token) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
@ -221,7 +240,7 @@ async function logout() {
if (token) {
try {
await fetch(withBaseUrl('/oauth2/token/revoke'), {
await fetch(withAPIBaseURL('/oauth2/token/revoke'), {
method: 'POST',
credentials: 'include',
headers: {
@ -244,7 +263,7 @@ async function logout() {
}
}
return fetch(withBaseUrl('/logout'), {
return fetch(withAPIBaseURL('/logout'), {
method: 'POST',
credentials: 'include',
})

View File

@ -11,7 +11,7 @@ import {
engineCommandManager,
kclManager,
} from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { getConstraintInfoKw } from '@src/lang/std/sketch'
import { getNodeFromPath } from '@src/lang/queryAst'
import type { Node } from '@rust/kcl-lib/bindings/Node'
@ -29,10 +29,9 @@ import { removeSingleConstraintInfo } from '@src/lang/modifyAst'
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KC_DEV_TOKEN,
token: VITE_KITTYCAD_API_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -72,7 +72,7 @@ import {
addRevolve,
addSweep,
getAxisExpressionAndIndex,
} from '@src/lang/modifyAst/addSweep'
} from '@src/lang/modifyAst/sweeps'
import {
applyIntersectFromTargetOperatorSelections,
applySubtractFromTargetOperatorSelections,

View File

@ -1,5 +1,5 @@
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env'
import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { getModuleIdByFileName, isArray } from '@src/lib/utils'
import { vi, inject } from 'vitest'
import { assertParse } from '@src/lang/wasm'
@ -355,10 +355,9 @@ cases.push(
beforeAll(async () => {
await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => {
engineCommandManager.start({
token: VITE_KC_DEV_TOKEN,
token: VITE_KITTYCAD_API_TOKEN,
width: 256,
height: 256,
setMediaStream: () => {},

View File

@ -71,7 +71,7 @@ dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
// default vite values based on mode
process.env.NODE_ENV ??= viteEnv.MODE
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_KITTYCAD_API_BASE_URL ??= viteEnv.VITE_KITTYCAD_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_CONNECTION_TIMEOUT_MS ??=

View File

@ -289,11 +289,11 @@ contextBridge.exposeInMainWorld('electron', {
exposeProcessEnvs([
'NODE_ENV',
'VITE_KC_API_WS_MODELING_URL',
'VITE_KC_API_BASE_URL',
'VITE_KITTYCAD_API_BASE_URL',
'VITE_KC_SITE_BASE_URL',
'VITE_KC_SITE_APP_URL',
'VITE_KC_CONNECTION_TIMEOUT_MS',
'VITE_KC_DEV_TOKEN',
'VITE_KITTYCAD_API_TOKEN',
'IS_PLAYWRIGHT',

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon'
import { Logo } from '@src/components/Logo'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
@ -15,6 +15,7 @@ import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils'
import { authActor, useSettings } from '@src/lib/singletons'
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
@ -54,7 +55,7 @@ const SignIn = () => {
const signInDesktop = async () => {
// We want to invoke our command to login via device auth.
const userCodeToDisplay = await window.electron
.startDeviceFlow(VITE_KC_API_BASE_URL + location.search)
.startDeviceFlow(withAPIBaseURL(location.search))
.catch(reportError)
if (!userCodeToDisplay) {
console.error('No user code received while trying to log in')