Merge branch 'main' into pierremtb/issue3528-Add-electron-updater
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { test, expect, Page } from '@playwright/test'
|
||||
import {
|
||||
doExport,
|
||||
getUtils,
|
||||
@ -6,6 +6,7 @@ import {
|
||||
Paths,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
createProjectAndRenameIt,
|
||||
} from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
@ -45,17 +46,20 @@ test(
|
||||
'click help/keybindings from project page',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/bracket`, { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'bracket'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'focusrite_scarlett_mounting_braket.kcl'
|
||||
),
|
||||
join(dir, 'bracket', 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -64,8 +68,6 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
@ -92,17 +94,20 @@ test(
|
||||
'when code with error first loads you get errors in console',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(`${dir}/broken-code`, { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'broken-code'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/broken-code-test.kcl',
|
||||
`${dir}/broken-code/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'broken-code-test.kcl'
|
||||
),
|
||||
join(dir, 'broken-code', 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
@ -232,10 +237,6 @@ test(
|
||||
'Rename and delete projects, also spam arrow keys when renaming',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
@ -524,10 +525,6 @@ test(
|
||||
'Deleting projects, can delete individual project, can still create projects after deleting all',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
@ -535,33 +532,23 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const createProjectAndRenameIt = async (name: string) =>
|
||||
test.step(`Create and rename project ${name}`, async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type "updated project name"
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
const createProjectAndRenameItTest = async ({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) => {
|
||||
await test.step(`Create and rename project ${name}`, async () => {
|
||||
await createProjectAndRenameIt({ name, page })
|
||||
})
|
||||
}
|
||||
|
||||
// we need to create the folders so that the order is correct
|
||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||
await createProjectAndRenameIt('router-template-slate')
|
||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
||||
await createProjectAndRenameIt('bracket')
|
||||
await createProjectAndRenameIt('lego')
|
||||
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||
|
||||
await test.step('delete the middle project, i.e. the bracket project', async () => {
|
||||
const project = page.getByText('bracket')
|
||||
@ -622,10 +609,6 @@ test(
|
||||
'Can sort projects on home page',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
@ -635,33 +618,23 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const createProjectAndRenameIt = async (name: string) =>
|
||||
test.step(`Create and rename project ${name}`, async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type "updated project name"
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
const createProjectAndRenameItTest = async ({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) => {
|
||||
await test.step(`Create and rename project ${name}`, async () => {
|
||||
await createProjectAndRenameIt({ name, page })
|
||||
})
|
||||
}
|
||||
|
||||
// we need to create the folders so that the order is correct
|
||||
// creating them ahead of time with fs tools means they all have the same timestamp
|
||||
await createProjectAndRenameIt('router-template-slate')
|
||||
// await createProjectAndRenameIt('focusrite_scarlett_mounting_braket')
|
||||
await createProjectAndRenameIt('bracket')
|
||||
await createProjectAndRenameIt('lego')
|
||||
await createProjectAndRenameItTest({ name: 'router-template-slate', page })
|
||||
await createProjectAndRenameItTest({ name: 'bracket', page })
|
||||
await createProjectAndRenameItTest({ name: 'lego', page })
|
||||
|
||||
await test.step('should be shorted by modified initially', async () => {
|
||||
const lastModifiedButton = page.getByRole('button', {
|
||||
@ -748,10 +721,6 @@ test(
|
||||
'When the project folder is empty, user can create new project and open it.',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({ testInfo })
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -836,25 +805,35 @@ test(
|
||||
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await Promise.all([
|
||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
||||
fsp.mkdir(`${dir}/bracket`, { recursive: true }),
|
||||
fsp.mkdir(join(dir, 'router-template-slate'), { recursive: true }),
|
||||
fsp.mkdir(join(dir, 'bracket'), { recursive: true }),
|
||||
])
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
|
||||
`${dir}/router-template-slate/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'router-template-slate.kcl'
|
||||
),
|
||||
join(dir, 'router-template-slate', 'main.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
|
||||
`${dir}/bracket/main.kcl`
|
||||
join(
|
||||
'src',
|
||||
'wasm-lib',
|
||||
'tests',
|
||||
'executor',
|
||||
'inputs',
|
||||
'focusrite_scarlett_mounting_braket.kcl'
|
||||
),
|
||||
join(dir, 'bracket', 'main.kcl')
|
||||
),
|
||||
])
|
||||
},
|
||||
@ -1302,10 +1281,6 @@ test(
|
||||
'Settings persist across restarts',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
|
||||
)
|
||||
await test.step('We can change a user setting like theme', async () => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
@ -1682,6 +1657,7 @@ test.describe('Renaming in the file tree', () => {
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
@ -1710,3 +1686,47 @@ test.describe('Renaming in the file tree', () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
const getAllProjects = () => page.getByTestId('project-link').all()
|
||||
page.on('console', console.log)
|
||||
|
||||
await test.step('Should create and name a project called wrist brace', async () => {
|
||||
await createProjectAndRenameIt({ name: 'wrist brace', page })
|
||||
})
|
||||
|
||||
await test.step('Should go through onboarding', async () => {
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await page.getByTestId('user-settings').click()
|
||||
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||
|
||||
const numberOfOnboardingSteps = 12
|
||||
for (let clicks = 0; clicks < numberOfOnboardingSteps; clicks++) {
|
||||
await page.getByTestId('onboarding-next').click()
|
||||
}
|
||||
|
||||
await page.getByTestId('project-sidebar-toggle').click()
|
||||
})
|
||||
|
||||
await test.step('Should go home after onboarding is completed', async () => {
|
||||
await page.getByRole('button', { name: 'Go to Home' }).click()
|
||||
})
|
||||
|
||||
await test.step('Should show the original project called wrist brace', async () => {
|
||||
const projectNames = ['Tutorial Project 00', 'wrist brace']
|
||||
for (const [index, projectLink] of (await getAllProjects()).entries()) {
|
||||
await expect(projectLink).toContainText(projectNames[index])
|
||||
}
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
@ -2,10 +2,11 @@ import {
|
||||
expect,
|
||||
Page,
|
||||
Download,
|
||||
TestInfo,
|
||||
BrowserContext,
|
||||
TestInfo,
|
||||
_electron as electron,
|
||||
Locator,
|
||||
test,
|
||||
} from '@playwright/test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import os from 'os'
|
||||
@ -26,6 +27,7 @@ import {
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import { isArray } from 'lib/utils'
|
||||
|
||||
type TestColor = [number, number, number]
|
||||
export const TEST_COLORS = {
|
||||
@ -44,6 +46,9 @@ export const commonPoints = {
|
||||
num2: 14.44,
|
||||
}
|
||||
|
||||
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||
|
||||
async function waitForPageLoadWithRetry(page: Page) {
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
@ -205,7 +210,7 @@ export const wiggleMove = async (
|
||||
}
|
||||
|
||||
export const circleMove = async (
|
||||
page: any,
|
||||
page: Page,
|
||||
x: number,
|
||||
y: number,
|
||||
steps: number,
|
||||
@ -311,13 +316,19 @@ export function normaliseKclNumbers(code: string, ignoreZero = true): string {
|
||||
return replaceNumbers(code)
|
||||
}
|
||||
|
||||
export async function getUtils(page: Page) {
|
||||
export async function getUtils(page: Page, test_?: typeof test) {
|
||||
if (!test) {
|
||||
console.warn(
|
||||
'Some methods in getUtils requires test object as second argument'
|
||||
)
|
||||
}
|
||||
|
||||
// Chrome devtools protocol session only works in Chromium
|
||||
const browserType = page.context().browser()?.browserType().name()
|
||||
const cdpSession =
|
||||
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||
|
||||
return {
|
||||
const util = {
|
||||
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
|
||||
waitForPageLoad: () => waitForPageLoad(page),
|
||||
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
|
||||
@ -484,7 +495,74 @@ export async function getUtils(page: Page) {
|
||||
networkOptions
|
||||
)
|
||||
},
|
||||
|
||||
toNormalizedCode: (text: string) => {
|
||||
return text.replace(/\s+/g, '')
|
||||
},
|
||||
|
||||
createAndSelectProject: async (hasText: string) => {
|
||||
return test_?.step(
|
||||
`Create and select project with text "${hasText}"`,
|
||||
async () => {
|
||||
await page.getByTestId('home-new-file').click()
|
||||
const projectLinksPost = page.getByTestId('project-link')
|
||||
await projectLinksPost.filter({ hasText }).click()
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
editorTextMatches: async (code: string) => {
|
||||
const editor = page.locator(editorSelector)
|
||||
const editorText = await editor.textContent()
|
||||
return expect(util.toNormalizedCode(editorText || '')).toBe(
|
||||
util.toNormalizedCode(code)
|
||||
)
|
||||
},
|
||||
|
||||
pasteCodeInEditor: async (code: string) => {
|
||||
return test?.step('Paste in KCL code', async () => {
|
||||
const editor = page.locator(editorSelector)
|
||||
await editor.fill(code)
|
||||
await util.editorTextMatches(code)
|
||||
})
|
||||
},
|
||||
|
||||
clickPane: async (paneId: PaneId) => {
|
||||
return test?.step(`Open ${paneId} pane`, async () => {
|
||||
await page.getByTestId(paneId + '-pane-button').click()
|
||||
await expect(page.locator('#' + paneId + '-pane')).toBeVisible()
|
||||
})
|
||||
},
|
||||
|
||||
createNewFileAndSelect: async (name: string) => {
|
||||
return test?.step(`Create a file named ${name}, select it`, async () => {
|
||||
await page.getByTestId('create-file-button').click()
|
||||
await page.getByTestId('file-rename-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
await page
|
||||
.getByTestId('file-pane-scroll-container')
|
||||
.filter({ hasText: name })
|
||||
.click()
|
||||
})
|
||||
},
|
||||
|
||||
panesOpen: async (paneIds: PaneId[]) => {
|
||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||
await page.addInitScript(
|
||||
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
|
||||
localStorage.setItem(
|
||||
PERSIST_MODELING_CONTEXT,
|
||||
JSON.stringify({ openPanes: paneIds })
|
||||
)
|
||||
},
|
||||
{ PERSIST_MODELING_CONTEXT, paneIds }
|
||||
)
|
||||
await page.reload()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return util
|
||||
}
|
||||
|
||||
type TemplateOptions = Array<number | Array<number>>
|
||||
@ -505,7 +583,7 @@ const _makeTemplate = (
|
||||
templateParts: TemplateStringsArray,
|
||||
...options: TemplateOptions
|
||||
) => {
|
||||
const length = Math.max(...options.map((a) => (Array.isArray(a) ? a[0] : 0)))
|
||||
const length = Math.max(...options.map((a) => (isArray(a) ? a[0] : 0)))
|
||||
let reExpTemplate = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
const currentStr = templateParts.map((str, index) => {
|
||||
@ -513,7 +591,7 @@ const _makeTemplate = (
|
||||
return (
|
||||
escapeRegExp(str) +
|
||||
String(
|
||||
Array.isArray(currentOptions)
|
||||
isArray(currentOptions)
|
||||
? currentOptions[i]
|
||||
: typeof currentOptions === 'number'
|
||||
? currentOptions
|
||||
@ -733,6 +811,7 @@ export async function setup(context: BrowserContext, page: Page) {
|
||||
// kill animations, speeds up tests and reduced flakiness
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||
|
||||
// Trigger a navigation, since loading file:// doesn't.
|
||||
await page.reload()
|
||||
}
|
||||
|
||||
@ -814,3 +893,29 @@ export async function isOutOfViewInScrollContainer(
|
||||
|
||||
return isOutOfView
|
||||
}
|
||||
|
||||
export async function createProjectAndRenameIt({
|
||||
name,
|
||||
page,
|
||||
}: {
|
||||
name: string
|
||||
page: Page
|
||||
}) {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await expect(page.getByText('Successfully created')).toBeVisible()
|
||||
await expect(page.getByText('Successfully created')).not.toBeVisible()
|
||||
|
||||
await expect(page.getByText(`project-000`)).toBeVisible()
|
||||
await page.getByText(`project-000`).hover()
|
||||
await page.getByText(`project-000`).focus()
|
||||
|
||||
await page.getByLabel('sketch').first().click()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// type the name passed in
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.type(name)
|
||||
|
||||
await page.getByLabel('checkmark').last().click()
|
||||
}
|
||||
|
||||
@ -264,4 +264,69 @@ test.describe('Testing settings', () => {
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Closing settings modal should go back to the original file being viewed`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
createAndSelectProject,
|
||||
pasteCodeInEditor,
|
||||
clickPane,
|
||||
createNewFileAndSelect,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
await panesOpen([])
|
||||
|
||||
await test.step('Precondition: No projects exist', async () => {
|
||||
await expect(page.getByTestId('home-section')).toBeVisible()
|
||||
const projectLinksPre = page.getByTestId('project-link')
|
||||
await expect(projectLinksPre).toHaveCount(0)
|
||||
})
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
|
||||
await clickPane('code')
|
||||
const kclCube = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cube.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
await clickPane('files')
|
||||
await createNewFileAndSelect('2.kcl')
|
||||
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
|
||||
await test.step('Open and close settings', async () => {
|
||||
await settingsOpenButton.click()
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await test.step('Postcondition: Same file content is in editor as before settings opened', async () => {
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
|
||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
|
||||
@ -140,7 +140,7 @@
|
||||
"@types/electron": "^1.6.10",
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^22.4.2",
|
||||
"@types/node": "^22.5.0",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
@ -167,7 +167,7 @@
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"happy-dom": "^14.3.10",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^9.0.11",
|
||||
"husky": "^9.1.5",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pixelmatch": "^5.3.0",
|
||||
"pngjs": "^7.0.0",
|
||||
@ -178,7 +178,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
|
||||
@ -17,6 +17,7 @@ export default defineConfig({
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['dot'],
|
||||
['list'],
|
||||
['json', { outputFile: './test-results/report.json' }],
|
||||
['html'],
|
||||
],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { defineConfig } from '@playwright/test'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
@ -30,4 +30,24 @@ export default defineConfig({
|
||||
actionTimeout: 15_000,
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
channel: 'chrome',
|
||||
contextOptions: {
|
||||
/* Chromium is the only one with these permission types */
|
||||
permissions: ['clipboard-write', 'clipboard-read'],
|
||||
},
|
||||
launchOptions: {
|
||||
...(process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|
||||
? {
|
||||
executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
}, // or 'chrome-beta'
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@ -75,7 +75,13 @@ import {
|
||||
changeSketchArguments,
|
||||
updateStartProfileAtArgs,
|
||||
} from 'lang/std/sketch'
|
||||
import { isOverlap, normaliseAngle, roundOff, throttle } from 'lib/utils'
|
||||
import {
|
||||
isArray,
|
||||
isOverlap,
|
||||
normaliseAngle,
|
||||
roundOff,
|
||||
throttle,
|
||||
} from 'lib/utils'
|
||||
import {
|
||||
addStartProfileAt,
|
||||
createArrayExpression,
|
||||
@ -99,6 +105,7 @@ import {
|
||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -116,6 +123,8 @@ export const SEGMENT_WIDTH_PX = 1.6
|
||||
export const HIDE_SEGMENT_LENGTH = 75 // in pixels
|
||||
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
|
||||
|
||||
type Vec3Array = [number, number, number]
|
||||
|
||||
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
||||
// That mostly mean sketch elements.
|
||||
// Cameras, controls, raycasters, etc are handled by sceneInfra
|
||||
@ -384,7 +393,7 @@ export class SceneEntities {
|
||||
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
||||
if (!sketchGroup) return Promise.reject('sketchGroup not found')
|
||||
|
||||
if (!Array.isArray(sketchGroup?.value))
|
||||
if (!isArray(sketchGroup?.value))
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
@ -1838,6 +1847,7 @@ export function getSketchQuaternion(
|
||||
})
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp
|
||||
if (!zAxis) return Error('SketchGroup zAxis not found')
|
||||
|
||||
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||
}
|
||||
@ -1962,8 +1972,6 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
||||
return quaternion
|
||||
}
|
||||
|
||||
function massageFormats(a: any): Vector3 {
|
||||
return Array.isArray(a)
|
||||
? new Vector3(a[0], a[1], a[2])
|
||||
: new Vector3(a.x, a.y, a.z)
|
||||
function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
||||
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
|
||||
}
|
||||
|
||||
@ -61,6 +61,7 @@ function RenameForm({
|
||||
<label>
|
||||
<span className="sr-only">Rename file</span>
|
||||
<input
|
||||
data-testid="file-rename-field"
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
autoFocus
|
||||
@ -402,6 +403,7 @@ export const FileTreeMenu = () => {
|
||||
<>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="create-file-button"
|
||||
iconStart={{
|
||||
icon: 'filePlus',
|
||||
iconClassName: '!text-current',
|
||||
@ -417,6 +419,7 @@ export const FileTreeMenu = () => {
|
||||
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="create-folder-button"
|
||||
iconStart={{
|
||||
icon: 'folderPlus',
|
||||
iconClassName: '!text-current',
|
||||
|
||||
@ -563,7 +563,7 @@ export function createArrayExpression(
|
||||
start: 0,
|
||||
end: 0,
|
||||
digest: null,
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
elements,
|
||||
}
|
||||
}
|
||||
@ -577,7 +577,7 @@ export function createPipeExpression(
|
||||
end: 0,
|
||||
digest: null,
|
||||
body,
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: [], digest: null },
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,6 +613,7 @@ export function createObjectExpression(properties: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
digest: null,
|
||||
nonCodeMeta: nonCodeMetaEmpty(),
|
||||
properties: Object.entries(properties).map(([key, value]) => ({
|
||||
type: 'ObjectProperty',
|
||||
start: 0,
|
||||
@ -1065,3 +1066,7 @@ export async function deleteFromSelection(
|
||||
|
||||
return new Error('Selection not recognised, could not delete')
|
||||
}
|
||||
|
||||
const nonCodeMetaEmpty = () => {
|
||||
return { nonCodeNodes: {}, start: [], digest: null }
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import init, {
|
||||
parse_app_settings,
|
||||
parse_project_settings,
|
||||
default_project_settings,
|
||||
parse_project_route,
|
||||
base64_decode,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
@ -33,7 +32,6 @@ import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { TEST } from 'env'
|
||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { err } from 'lib/trap'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
@ -611,13 +609,6 @@ export function parseProjectSettings(
|
||||
return parse_project_settings(toml)
|
||||
}
|
||||
|
||||
export function parseProjectRoute(
|
||||
configuration: DeepPartial<Configuration>,
|
||||
route_str: string
|
||||
): ProjectRoute | Error {
|
||||
return parse_project_route(JSON.stringify(configuration), route_str)
|
||||
}
|
||||
|
||||
export function base64Decode(base64: string): ArrayBuffer | Error {
|
||||
try {
|
||||
const decoded = base64_decode(base64)
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
import { DeepPartial } from './types'
|
||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
export { parseProjectRoute } from 'lang/wasm'
|
||||
|
||||
export async function renameProjectDirectory(
|
||||
projectPath: string,
|
||||
@ -39,7 +38,7 @@ export async function renameProjectDirectory(
|
||||
|
||||
// Make sure the new name does not exist.
|
||||
const newPath = window.electron.path.join(
|
||||
projectPath.split('/').slice(0, -1).join('/'),
|
||||
window.electron.path.dirname(projectPath),
|
||||
newName
|
||||
)
|
||||
try {
|
||||
@ -186,9 +185,9 @@ const collectAllFilesRecursiveFrom = async (path: string) => {
|
||||
return Promise.reject(new Error(`Path ${path} is not a directory`))
|
||||
}
|
||||
|
||||
const pathParts = path.split(window.electron.path.sep)
|
||||
const name = window.electron.path.basename(path)
|
||||
let entry: FileEntry = {
|
||||
name: pathParts.slice(-1)[0],
|
||||
name: name,
|
||||
path,
|
||||
children: [],
|
||||
}
|
||||
@ -330,7 +329,6 @@ export async function getProjectInfo(projectPath: string): Promise<Project> {
|
||||
new Error(`Project path is not a directory: ${projectPath}`)
|
||||
)
|
||||
}
|
||||
|
||||
let walked = await collectAllFilesRecursiveFrom(projectPath)
|
||||
let default_file = await getDefaultKclFileForDir(projectPath, walked)
|
||||
const metadata = await window.electron.stat(projectPath)
|
||||
|
||||
79
src/lib/paths.test.ts
Normal file
79
src/lib/paths.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { parseProjectRoute } from './paths'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('testing parseProjectRoute', () => {
|
||||
it('should parse a project as a subpath of project dir', async () => {
|
||||
let config = {
|
||||
settings: {
|
||||
project: {
|
||||
directory: '/home/somebody/projects',
|
||||
},
|
||||
},
|
||||
}
|
||||
const route = '/home/somebody/projects/project'
|
||||
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||
projectName: 'project',
|
||||
projectPath: route,
|
||||
currentFileName: null,
|
||||
currentFilePath: null,
|
||||
})
|
||||
})
|
||||
it('should parse a project as the project dir', async () => {
|
||||
let config = {
|
||||
settings: {
|
||||
project: {
|
||||
directory: '/home/somebody/projects',
|
||||
},
|
||||
},
|
||||
}
|
||||
const route = '/home/somebody/projects'
|
||||
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||
projectName: null,
|
||||
projectPath: route,
|
||||
currentFileName: null,
|
||||
currentFilePath: null,
|
||||
})
|
||||
})
|
||||
it('should parse a project with file in the project dir', async () => {
|
||||
let config = {
|
||||
settings: {
|
||||
project: {
|
||||
directory: '/home/somebody/projects',
|
||||
},
|
||||
},
|
||||
}
|
||||
const route = '/home/somebody/projects/assembly/main.kcl'
|
||||
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||
projectName: 'assembly',
|
||||
projectPath: '/home/somebody/projects/assembly',
|
||||
currentFileName: 'main.kcl',
|
||||
currentFilePath: route,
|
||||
})
|
||||
})
|
||||
it('should parse a project with file in a subdir in the project dir', async () => {
|
||||
let config = {
|
||||
settings: {
|
||||
project: {
|
||||
directory: '/home/somebody/projects',
|
||||
},
|
||||
},
|
||||
}
|
||||
const route = '/home/somebody/projects/assembly/subdir/main.kcl'
|
||||
expect(await parseProjectRoute(config, route, path)).toEqual({
|
||||
projectName: 'assembly',
|
||||
projectPath: '/home/somebody/projects/assembly',
|
||||
currentFileName: 'main.kcl',
|
||||
currentFilePath: route,
|
||||
})
|
||||
})
|
||||
it('should work in the browser context', async () => {
|
||||
let config = {}
|
||||
const route = '/browser/main.kcl'
|
||||
expect(await parseProjectRoute(config, route, undefined)).toEqual({
|
||||
projectName: 'browser',
|
||||
projectPath: '/browser',
|
||||
currentFileName: 'main.kcl',
|
||||
currentFilePath: route,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,13 +1,13 @@
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { BROWSER_FILE_NAME, BROWSER_PROJECT_NAME, FILE_EXT } from './constants'
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { parseProjectRoute, readAppSettingsFile } from './desktop'
|
||||
import { readAppSettingsFile } from './desktop'
|
||||
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
||||
import { err } from 'lib/trap'
|
||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||
import { DeepPartial } from './types'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { PlatformPath } from 'path'
|
||||
|
||||
const prependRoutes =
|
||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||
@ -25,6 +25,13 @@ type OnboardingPaths = {
|
||||
|
||||
const SETTINGS = '/settings' as const
|
||||
|
||||
export type ProjectRoute = {
|
||||
projectName: string | null
|
||||
projectPath: string
|
||||
currentFileName: string | null
|
||||
currentFilePath: string | null
|
||||
}
|
||||
|
||||
export const PATHS = {
|
||||
INDEX: '/',
|
||||
HOME: '/home',
|
||||
@ -60,9 +67,64 @@ export async function getProjectMetaByRouteId(
|
||||
return Promise.reject(new Error('No configuration found'))
|
||||
}
|
||||
|
||||
const route = parseProjectRoute(configuration, id)
|
||||
const route = parseProjectRoute(configuration, id, window?.electron?.path)
|
||||
|
||||
if (err(route)) return Promise.reject(route)
|
||||
|
||||
return route
|
||||
}
|
||||
|
||||
export async function parseProjectRoute(
|
||||
configuration: DeepPartial<Configuration>,
|
||||
id: string,
|
||||
pathlib: PlatformPath | undefined
|
||||
): Promise<ProjectRoute> {
|
||||
let projectName = null
|
||||
let projectPath = ''
|
||||
let currentFileName = null
|
||||
let currentFilePath = null
|
||||
if (
|
||||
pathlib &&
|
||||
configuration.settings?.project?.directory &&
|
||||
id.startsWith(configuration.settings.project.directory)
|
||||
) {
|
||||
const relativeToRoot = pathlib.relative(
|
||||
configuration.settings.project.directory,
|
||||
id
|
||||
)
|
||||
projectName = relativeToRoot.split(pathlib.sep)[0]
|
||||
projectPath = pathlib.join(
|
||||
configuration.settings.project.directory,
|
||||
projectName
|
||||
)
|
||||
projectName = projectName === '' ? null : projectName
|
||||
} else {
|
||||
projectPath = id
|
||||
if (pathlib) {
|
||||
if (pathlib.extname(id) === '.kcl') {
|
||||
projectPath = pathlib.dirname(id)
|
||||
}
|
||||
projectName = pathlib.basename(projectPath)
|
||||
} else {
|
||||
if (id.endsWith('.kcl')) {
|
||||
projectPath = '/browser'
|
||||
projectName = 'browser'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pathlib) {
|
||||
if (projectPath !== id) {
|
||||
currentFileName = pathlib.basename(id)
|
||||
currentFilePath = id
|
||||
}
|
||||
} else {
|
||||
currentFileName = 'main.kcl'
|
||||
currentFilePath = id
|
||||
}
|
||||
return {
|
||||
projectName: projectName,
|
||||
projectPath: projectPath,
|
||||
currentFileName: currentFileName,
|
||||
currentFilePath: currentFilePath,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,9 +36,9 @@ export const settingsLoader: LoaderFunction = async ({
|
||||
configuration
|
||||
)
|
||||
if (projectPathData) {
|
||||
const { project_path } = projectPathData
|
||||
const { projectPath } = projectPathData
|
||||
const { settings: s } = await loadAndValidateSettings(
|
||||
project_path || undefined
|
||||
projectPath || undefined
|
||||
)
|
||||
return s
|
||||
}
|
||||
@ -83,48 +83,49 @@ export const fileLoader: LoaderFunction = async (
|
||||
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
|
||||
|
||||
if (!isBrowserProject && projectPathData) {
|
||||
const { project_name, project_path, current_file_name, current_file_path } =
|
||||
const { projectName, projectPath, currentFileName, currentFilePath } =
|
||||
projectPathData
|
||||
|
||||
const urlObj = new URL(routerData.request.url)
|
||||
let code = ''
|
||||
|
||||
if (!urlObj.pathname.endsWith('/settings')) {
|
||||
if (!current_file_name || !current_file_path || !project_name) {
|
||||
if (!currentFileName || !currentFilePath || !projectName) {
|
||||
return redirect(
|
||||
`${PATHS.FILE}/${encodeURIComponent(
|
||||
isDesktop()
|
||||
? (await getProjectInfo(project_path)).default_file
|
||||
? (await getProjectInfo(projectPath)).default_file
|
||||
: params.id + '/' + PROJECT_ENTRYPOINT
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
code = await window.electron.readFile(current_file_path)
|
||||
code = await window.electron.readFile(currentFilePath)
|
||||
code = normalizeLineEndings(code)
|
||||
|
||||
// Update both the state and the editor's code.
|
||||
// We explicitly do not write to the file here since we are loading from
|
||||
// the file system and not the editor.
|
||||
codeManager.updateCurrentFilePath(current_file_path)
|
||||
codeManager.updateCurrentFilePath(currentFilePath)
|
||||
codeManager.updateCodeStateEditor(code)
|
||||
}
|
||||
|
||||
// Set the file system manager to the project path
|
||||
// So that WASM gets an updated path for operations
|
||||
fileSystemManager.dir = project_path
|
||||
fileSystemManager.dir = projectPath
|
||||
|
||||
const defaultProjectData = {
|
||||
name: project_name || 'unnamed',
|
||||
path: project_path,
|
||||
name: projectName || 'unnamed',
|
||||
path: projectPath,
|
||||
children: [],
|
||||
kcl_file_count: 0,
|
||||
directory_count: 0,
|
||||
metadata: null,
|
||||
default_file: project_path,
|
||||
default_file: projectPath,
|
||||
}
|
||||
|
||||
const maybeProjectInfo = isDesktop()
|
||||
? await getProjectInfo(project_path)
|
||||
? await getProjectInfo(projectPath)
|
||||
: null
|
||||
|
||||
console.log('maybeProjectInfo', {
|
||||
@ -137,8 +138,8 @@ export const fileLoader: LoaderFunction = async (
|
||||
code,
|
||||
project: maybeProjectInfo ?? defaultProjectData,
|
||||
file: {
|
||||
name: current_file_name || '',
|
||||
path: current_file_path || '',
|
||||
name: currentFileName || '',
|
||||
path: currentFilePath || '',
|
||||
children: [],
|
||||
},
|
||||
}
|
||||
@ -187,3 +188,7 @@ export const homeLoader: LoaderFunction = async (): Promise<
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeLineEndings = (str: string, normalized = '\n') => {
|
||||
return str.replace(/\r?\n/g, normalized)
|
||||
}
|
||||
|
||||
@ -4,6 +4,13 @@ import { v4 } from 'uuid'
|
||||
|
||||
export const uuidv4 = v4
|
||||
|
||||
/**
|
||||
* A safer type guard for arrays since the built-in Array.isArray() asserts `any[]`.
|
||||
*/
|
||||
export function isArray(val: any): val is unknown[] {
|
||||
return Array.isArray(val)
|
||||
}
|
||||
|
||||
export function isOverlap(a: SourceRange, b: SourceRange) {
|
||||
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
|
||||
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
|
||||
|
||||
10
src/main.ts
10
src/main.ts
@ -11,11 +11,11 @@ import * as kittycad from '@kittycad/lib/import'
|
||||
import { updateElectronApp, UpdateSourceType } from 'update-electron-app'
|
||||
|
||||
// If it's not set, scream.
|
||||
const NODE_ENV = process.env.NODE_ENV
|
||||
if (!NODE_ENV) {
|
||||
console.error('*FOX SCREAM* process.env.NODE_ENV is not explicitly set!')
|
||||
process.exit(1)
|
||||
}
|
||||
const NODE_ENV = process.env.NODE_ENV || 'production'
|
||||
if (!process.env.NODE_ENV)
|
||||
console.warn(
|
||||
'*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production'
|
||||
)
|
||||
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
|
||||
@ -2466,11 +2466,13 @@ impl ArrayExpression {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub struct ObjectExpression {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub properties: Vec<ObjectProperty>,
|
||||
#[serde(default, skip_serializing_if = "NonCodeMeta::is_empty")]
|
||||
pub non_code_meta: NonCodeMeta,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
@ -2481,6 +2483,7 @@ impl ObjectExpression {
|
||||
start: 0,
|
||||
end: 0,
|
||||
properties,
|
||||
non_code_meta: Default::default(),
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
@ -2514,6 +2517,14 @@ impl ObjectExpression {
|
||||
}
|
||||
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||
if self
|
||||
.non_code_meta
|
||||
.non_code_nodes
|
||||
.values()
|
||||
.any(|nc| nc.iter().any(|nc| nc.value.should_cause_array_newline()))
|
||||
{
|
||||
return self.recast_multi_line(options, indentation_level, is_in_pipe);
|
||||
}
|
||||
let flat_recast = format!(
|
||||
"{{ {} }}",
|
||||
self.properties
|
||||
@ -2529,35 +2540,49 @@ impl ObjectExpression {
|
||||
.join(", ")
|
||||
);
|
||||
let max_array_length = 40;
|
||||
if flat_recast.len() > max_array_length {
|
||||
let inner_indentation = if is_in_pipe {
|
||||
options.get_indentation_offset_pipe(indentation_level + 1)
|
||||
} else {
|
||||
options.get_indentation(indentation_level + 1)
|
||||
};
|
||||
format!(
|
||||
"{{\n{}{}\n{}}}",
|
||||
inner_indentation,
|
||||
self.properties
|
||||
.iter()
|
||||
.map(|prop| {
|
||||
format!(
|
||||
"{}: {}",
|
||||
prop.key.name,
|
||||
prop.value.recast(options, indentation_level + 1, is_in_pipe)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(format!(",\n{}", inner_indentation).as_str()),
|
||||
if is_in_pipe {
|
||||
options.get_indentation_offset_pipe(indentation_level)
|
||||
} else {
|
||||
options.get_indentation(indentation_level)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
flat_recast
|
||||
let needs_multiple_lines = flat_recast.len() > max_array_length;
|
||||
if !needs_multiple_lines {
|
||||
return flat_recast;
|
||||
}
|
||||
self.recast_multi_line(options, indentation_level, is_in_pipe)
|
||||
}
|
||||
|
||||
/// Recast, but always outputs the object with newlines between each property.
|
||||
fn recast_multi_line(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||
let inner_indentation = if is_in_pipe {
|
||||
options.get_indentation_offset_pipe(indentation_level + 1)
|
||||
} else {
|
||||
options.get_indentation(indentation_level + 1)
|
||||
};
|
||||
let num_items = self.properties.len() + self.non_code_meta.non_code_nodes_len();
|
||||
let mut props = self.properties.iter();
|
||||
let format_items: Vec<_> = (0..num_items)
|
||||
.flat_map(|i| {
|
||||
if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
|
||||
noncode.iter().map(|nc| nc.format("")).collect::<Vec<_>>()
|
||||
} else {
|
||||
let prop = props.next().unwrap();
|
||||
// Use a comma unless it's the last item
|
||||
let comma = if i == num_items - 1 { "" } else { ",\n" };
|
||||
let s = format!(
|
||||
"{}: {}{comma}",
|
||||
prop.key.name,
|
||||
prop.value.recast(options, indentation_level + 1, is_in_pipe).trim()
|
||||
);
|
||||
vec![s]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
dbg!(&format_items);
|
||||
let end_indent = if is_in_pipe {
|
||||
options.get_indentation_offset_pipe(indentation_level)
|
||||
} else {
|
||||
options.get_indentation(indentation_level)
|
||||
};
|
||||
format!(
|
||||
"{{\n{inner_indentation}{}\n{end_indent}}}",
|
||||
format_items.join(&inner_indentation),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a hover value that includes the given character position.
|
||||
@ -5897,6 +5922,66 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recast_objects_no_comments() {
|
||||
let input = r#"
|
||||
const sketch002 = startSketchOn({
|
||||
plane: {
|
||||
origin: { x: 1, y: 2, z: 3 },
|
||||
x_axis: { x: 4, y: 5, z: 6 },
|
||||
y_axis: { x: 7, y: 8, z: 9 },
|
||||
z_axis: { x: 10, y: 11, z: 12 }
|
||||
}
|
||||
})
|
||||
"#;
|
||||
let expected = r#"const sketch002 = startSketchOn({
|
||||
plane: {
|
||||
origin: { x: 1, y: 2, z: 3 },
|
||||
x_axis: { x: 4, y: 5, z: 6 },
|
||||
y_axis: { x: 7, y: 8, z: 9 },
|
||||
z_axis: { x: 10, y: 11, z: 12 }
|
||||
}
|
||||
})
|
||||
"#;
|
||||
let tokens = crate::token::lexer(input).unwrap();
|
||||
let p = crate::parser::Parser::new(tokens);
|
||||
let ast = p.ast().unwrap();
|
||||
let actual = ast.recast(&FormatOptions::new(), 0);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recast_objects_with_comments() {
|
||||
use winnow::Parser;
|
||||
for (i, (input, expected, reason)) in [(
|
||||
"\
|
||||
{
|
||||
a: 1,
|
||||
// b: 2,
|
||||
c: 3
|
||||
}",
|
||||
"\
|
||||
{
|
||||
a: 1,
|
||||
// b: 2,
|
||||
c: 3
|
||||
}",
|
||||
"preserves comments",
|
||||
)]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let tokens = crate::token::lexer(input).unwrap();
|
||||
crate::parser::parser_impl::print_tokens(&tokens);
|
||||
let expr = crate::parser::parser_impl::object.parse(&tokens).unwrap();
|
||||
assert_eq!(
|
||||
expr.recast(&FormatOptions::new(), 0, false),
|
||||
expected,
|
||||
"failed test {i}, which is testing that recasting {reason}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recast_array_with_comments() {
|
||||
use winnow::Parser;
|
||||
|
||||
@ -586,22 +586,60 @@ fn object_property(i: TokenSlice) -> PResult<ObjectProperty> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Match something that separates properties of an object.
|
||||
fn property_separator(i: TokenSlice) -> PResult<()> {
|
||||
alt((
|
||||
// Normally you need a comma.
|
||||
comma_sep,
|
||||
// But, if the array is ending, no need for a comma.
|
||||
peek(preceded(opt(whitespace), close_brace)).void(),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Parse a KCL object value.
|
||||
fn object(i: TokenSlice) -> PResult<ObjectExpression> {
|
||||
pub(crate) fn object(i: TokenSlice) -> PResult<ObjectExpression> {
|
||||
let start = open_brace(i)?.start;
|
||||
ignore_whitespace(i);
|
||||
let properties = separated(0.., object_property, comma_sep)
|
||||
.context(expected(
|
||||
"a comma-separated list of key-value pairs, e.g. 'height: 4, width: 3'",
|
||||
))
|
||||
.parse_next(i)?;
|
||||
let properties: Vec<_> = repeat(
|
||||
0..,
|
||||
alt((
|
||||
terminated(non_code_node.map(NonCodeOr::NonCode), whitespace),
|
||||
terminated(object_property, property_separator).map(NonCodeOr::Code),
|
||||
)),
|
||||
)
|
||||
.context(expected(
|
||||
"a comma-separated list of key-value pairs, e.g. 'height: 4, width: 3'",
|
||||
))
|
||||
.parse_next(i)?;
|
||||
|
||||
// Sort the object's properties from the noncode nodes.
|
||||
let (properties, non_code_nodes): (Vec<_>, HashMap<usize, _>) = properties.into_iter().enumerate().fold(
|
||||
(Vec::new(), HashMap::new()),
|
||||
|(mut properties, mut non_code_nodes), (i, e)| {
|
||||
match e {
|
||||
NonCodeOr::NonCode(x) => {
|
||||
non_code_nodes.insert(i, vec![x]);
|
||||
}
|
||||
NonCodeOr::Code(x) => {
|
||||
properties.push(x);
|
||||
}
|
||||
}
|
||||
(properties, non_code_nodes)
|
||||
},
|
||||
);
|
||||
ignore_trailing_comma(i);
|
||||
ignore_whitespace(i);
|
||||
let end = close_brace(i)?.end;
|
||||
let non_code_meta = NonCodeMeta {
|
||||
non_code_nodes,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(ObjectExpression {
|
||||
start,
|
||||
end,
|
||||
properties,
|
||||
non_code_meta,
|
||||
digest: None,
|
||||
})
|
||||
}
|
||||
@ -3056,12 +3094,6 @@ e
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn print_tokens(tokens: &[Token]) {
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_linesep_no_trailing_comma() {
|
||||
let program = r#"[
|
||||
@ -3259,6 +3291,7 @@ mod snapshot_tests {
|
||||
#[test]
|
||||
fn $func_name() {
|
||||
let tokens = crate::token::lexer($test_kcl_program).unwrap();
|
||||
print_tokens(&tokens);
|
||||
let actual = match program.parse(&tokens) {
|
||||
Ok(x) => x,
|
||||
Err(e) => panic!("could not parse test: {e:?}"),
|
||||
@ -3404,4 +3437,28 @@ mod snapshot_tests {
|
||||
// B,
|
||||
]"
|
||||
);
|
||||
snapshot_test!(
|
||||
ay,
|
||||
"let props = {
|
||||
a: 1,
|
||||
// b: 2,
|
||||
c: 3,
|
||||
}"
|
||||
);
|
||||
snapshot_test!(
|
||||
az,
|
||||
"let props = {
|
||||
a: 1,
|
||||
// b: 2,
|
||||
c: 3
|
||||
}"
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[cfg(test)]
|
||||
pub(crate) fn print_tokens(tokens: &[Token]) {
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
println!("{i:.2}: ({:?}):) '{}'", tok.token_type, tok.value.replace("\n", "\\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 80,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 80,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 4,
|
||||
"end": 80,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 4,
|
||||
"end": 9,
|
||||
"name": "props",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 12,
|
||||
"end": 80,
|
||||
"properties": [
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 26,
|
||||
"end": 30,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 26,
|
||||
"end": 27,
|
||||
"name": "a",
|
||||
"digest": null
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 29,
|
||||
"end": 30,
|
||||
"value": 1,
|
||||
"raw": "1",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 65,
|
||||
"end": 69,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 65,
|
||||
"end": 66,
|
||||
"name": "c",
|
||||
"digest": null
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 68,
|
||||
"end": 69,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"1": [
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 44,
|
||||
"end": 52,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "b: 2,",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "let",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 79,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 79,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 4,
|
||||
"end": 79,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 4,
|
||||
"end": 9,
|
||||
"name": "props",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 12,
|
||||
"end": 79,
|
||||
"properties": [
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 26,
|
||||
"end": 30,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 26,
|
||||
"end": 27,
|
||||
"name": "a",
|
||||
"digest": null
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 29,
|
||||
"end": 30,
|
||||
"value": 1,
|
||||
"raw": "1",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "ObjectProperty",
|
||||
"start": 65,
|
||||
"end": 69,
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"start": 65,
|
||||
"end": 66,
|
||||
"name": "c",
|
||||
"digest": null
|
||||
},
|
||||
"value": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 68,
|
||||
"end": 69,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"1": [
|
||||
{
|
||||
"type": "NonCodeNode",
|
||||
"start": 44,
|
||||
"end": 52,
|
||||
"value": {
|
||||
"type": "blockComment",
|
||||
"value": "b: 2,",
|
||||
"style": "line"
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "let",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
@ -8,8 +8,6 @@ use parse_display::{Display, FromStr};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::settings::types::Configuration;
|
||||
|
||||
/// State management for the application.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
@ -148,99 +146,6 @@ const model = import("{}")"#,
|
||||
}
|
||||
}
|
||||
|
||||
/// Project route information.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ProjectRoute {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub project_name: Option<String>,
|
||||
pub project_path: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub current_file_name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub current_file_path: Option<String>,
|
||||
}
|
||||
|
||||
impl ProjectRoute {
|
||||
/// Get the project state from the url in the route.
|
||||
pub fn from_route(configuration: &Configuration, route: &str) -> Result<Self> {
|
||||
let path = std::path::Path::new(route);
|
||||
// Check if the default project path is in the route.
|
||||
let (project_path, project_name) = if path.starts_with(&configuration.settings.project.directory)
|
||||
&& configuration.settings.project.directory != std::path::PathBuf::default()
|
||||
{
|
||||
// Get the project name.
|
||||
if let Some(project_name) = path
|
||||
.strip_prefix(&configuration.settings.project.directory)?
|
||||
.iter()
|
||||
.next()
|
||||
{
|
||||
(
|
||||
configuration
|
||||
.settings
|
||||
.project
|
||||
.directory
|
||||
.join(project_name)
|
||||
.display()
|
||||
.to_string(),
|
||||
Some(project_name.to_string_lossy().to_string()),
|
||||
)
|
||||
} else {
|
||||
(configuration.settings.project.directory.display().to_string(), None)
|
||||
}
|
||||
} else {
|
||||
// Assume the project path is the parent directory of the file.
|
||||
let project_dir = if path.display().to_string().ends_with(".kcl") {
|
||||
path.parent()
|
||||
.ok_or_else(|| anyhow::anyhow!("Parent directory not found: {}", path.display()))?
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
if project_dir == std::path::Path::new("/") {
|
||||
(
|
||||
path.display().to_string(),
|
||||
Some(
|
||||
path.file_name()
|
||||
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
),
|
||||
)
|
||||
} else if let Some(project_name) = project_dir.file_name() {
|
||||
(
|
||||
project_dir.display().to_string(),
|
||||
Some(project_name.to_string_lossy().to_string()),
|
||||
)
|
||||
} else {
|
||||
(project_dir.display().to_string(), None)
|
||||
}
|
||||
};
|
||||
|
||||
let (current_file_name, current_file_path) = if path.display().to_string() == project_path {
|
||||
(None, None)
|
||||
} else {
|
||||
(
|
||||
Some(
|
||||
path.file_name()
|
||||
.ok_or_else(|| anyhow::anyhow!("File name not found: {}", path.display()))?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
),
|
||||
Some(path.display().to_string()),
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
project_name,
|
||||
project_path,
|
||||
current_file_name,
|
||||
current_file_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about project.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||
#[ts(export)]
|
||||
@ -535,160 +440,6 @@ impl From<std::fs::Metadata> for FileMetadata {
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_std_path() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("assembly".to_string()),
|
||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||
current_file_name: Some("main.kcl".to_string()),
|
||||
current_file_path: Some(
|
||||
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/main.kcl".to_string()
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_std_path_dir() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("assembly".to_string()),
|
||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||
current_file_name: None,
|
||||
current_file_path: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_std_path_dir_empty() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: None,
|
||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects".to_string(),
|
||||
current_file_name: None,
|
||||
current_file_path: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_outside_std_path() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/kittycad/modeling-app/main.kcl";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("modeling-app".to_string()),
|
||||
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
||||
current_file_name: Some("main.kcl".to_string()),
|
||||
current_file_path: Some("/Users/macinatormax/kittycad/modeling-app/main.kcl".to_string()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_outside_std_path_dir() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/kittycad/modeling-app";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("modeling-app".to_string()),
|
||||
project_path: "/Users/macinatormax/kittycad/modeling-app".to_string(),
|
||||
current_file_name: None,
|
||||
current_file_path: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_browser() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory = std::path::PathBuf::default();
|
||||
|
||||
let route = "/browser/main.kcl";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("browser".to_string()),
|
||||
project_path: "/browser".to_string(),
|
||||
current_file_name: Some("main.kcl".to_string()),
|
||||
current_file_path: Some("/browser/main.kcl".to_string())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_browser_no_path() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory = std::path::PathBuf::default();
|
||||
|
||||
let route = "/browser";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("browser".to_string()),
|
||||
project_path: "/browser".to_string(),
|
||||
current_file_name: None,
|
||||
current_file_path: None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_project_route_from_route_non_main_file() {
|
||||
let mut configuration = crate::settings::types::Configuration::default();
|
||||
configuration.settings.project.directory =
|
||||
std::path::PathBuf::from("/Users/macinatormax/Documents/kittycad-modeling-projects");
|
||||
|
||||
let route = "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl";
|
||||
let state = super::ProjectRoute::from_route(&configuration, route).unwrap();
|
||||
assert_eq!(
|
||||
state,
|
||||
super::ProjectRoute {
|
||||
project_name: Some("assembly".to_string()),
|
||||
project_path: "/Users/macinatormax/Documents/kittycad-modeling-projects/assembly".to_string(),
|
||||
current_file_name: Some("thing.kcl".to_string()),
|
||||
current_file_path: Some(
|
||||
"/Users/macinatormax/Documents/kittycad-modeling-projects/assembly/thing.kcl".to_string()
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_default_kcl_file_for_dir_non_exist() {
|
||||
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||
|
||||
3
src/wasm-lib/rust-toolchain.toml
Normal file
3
src/wasm-lib/rust-toolchain.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "1.80.1"
|
||||
components = ["clippy", "rustfmt"]
|
||||
@ -566,22 +566,6 @@ pub fn serialize_project_settings(val: JsValue) -> Result<JsValue, String> {
|
||||
Ok(JsValue::from_str(&toml_str))
|
||||
}
|
||||
|
||||
/// Parse the project route.
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_project_route(configuration: &str, route: &str) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let configuration: kcl_lib::settings::types::Configuration =
|
||||
serde_json::from_str(configuration).map_err(|e| e.to_string())?;
|
||||
|
||||
let route =
|
||||
kcl_lib::settings::types::file::ProjectRoute::from_route(&configuration, route).map_err(|e| e.to_string())?;
|
||||
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||
// gloo-serialize crate instead.
|
||||
JsValue::from_serde(&route).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
static ALLOWED_DECODING_FORMATS: &[data_encoding::Encoding] = &[
|
||||
data_encoding::BASE64,
|
||||
data_encoding::BASE64URL,
|
||||
|
||||
204
yarn.lock
204
yarn.lock
@ -2177,85 +2177,85 @@
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz#6b991cb44bf69e50163528ea85bed545330ba821"
|
||||
integrity sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==
|
||||
"@rollup/rollup-android-arm-eabi@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz#d941173f82f9b041c61b0dc1a2a91dcd06e4b31e"
|
||||
integrity sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.2.tgz#5d3c8c2f9742d62ba258cc378bd2d4720f0c431c"
|
||||
integrity sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==
|
||||
"@rollup/rollup-android-arm64@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz#7e7157c8543215245ceffc445134d9e843ba51c0"
|
||||
integrity sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.2.tgz#8eac8682a34a705bb6a57eb3e739fd6bbedfabed"
|
||||
integrity sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==
|
||||
"@rollup/rollup-darwin-arm64@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz#f0a18a4fc8dc6eb1e94a51fa2adb22876f477947"
|
||||
integrity sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.2.tgz#70a9953fc624bd7f645901f4250f6b5807ac7e92"
|
||||
integrity sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==
|
||||
"@rollup/rollup-darwin-x64@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz#34b7867613e5cc42d2b85ddc0424228cc33b43f0"
|
||||
integrity sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.2.tgz#8f6c4ff4c4972413ff94345080380d4e3caa3c69"
|
||||
integrity sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz#422b19ff9ae02b05d3395183d1d43b38c7c8be0b"
|
||||
integrity sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.2.tgz#5d3c0fe5ea5ddf2feb511b3cb031df17eaa7e33d"
|
||||
integrity sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz#568aa29195ef6fc57ec6ed3f518923764406a8ee"
|
||||
integrity sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.2.tgz#b7f104388b2f5624d9f8adfff10ba59af8ab8ed1"
|
||||
integrity sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==
|
||||
"@rollup/rollup-linux-arm64-gnu@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz#22309c8bcba9a73114f69165c72bc94b2fbec085"
|
||||
integrity sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.2.tgz#6d5ca6d3904309bec285ea5202d589cebb93dee4"
|
||||
integrity sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==
|
||||
"@rollup/rollup-linux-arm64-musl@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz#c93c388af6d33f082894b8a60839d7265b2b9bc5"
|
||||
integrity sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.2.tgz#4df9be1396ea9eb0ca99fd0f2e858008d7f063e3"
|
||||
integrity sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==
|
||||
"@rollup/rollup-linux-powerpc64le-gnu@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz#493c5e19e395cf3c6bd860c7139c8a903dea72b4"
|
||||
integrity sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.2.tgz#80d63c5562915a2f8616a04251fcaee0218112b0"
|
||||
integrity sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz#a2eab4346fbe5909165ce99adb935ba30c9fb444"
|
||||
integrity sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.2.tgz#ef62e9bc5cc3b84fcfe96ec0a42d1989691217b3"
|
||||
integrity sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==
|
||||
"@rollup/rollup-linux-s390x-gnu@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz#0bc49a79db4345d78d757bb1b05e73a1b42fa5c3"
|
||||
integrity sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz#6a275282a0080fee98ddd9fda0de23c4c6bafd48"
|
||||
integrity sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==
|
||||
"@rollup/rollup-linux-x64-gnu@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz#4fd36a6a41f3406d8693321b13d4f9b7658dd4b9"
|
||||
integrity sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz#64f0c704107e6b45b26dd8c2e1ff64246e4a1251"
|
||||
integrity sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==
|
||||
"@rollup/rollup-linux-x64-musl@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz#10ebb13bd4469cbad1a5d9b073bd27ec8a886200"
|
||||
integrity sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.2.tgz#bada17b0c5017ff58d0feba401c43ff5a646c693"
|
||||
integrity sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==
|
||||
"@rollup/rollup-win32-arm64-msvc@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz#2fef1a90f1402258ef915ae5a94cc91a5a1d5bfc"
|
||||
integrity sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.2.tgz#a716d862f6ac39d88bdb825e27f63aeb0387cd66"
|
||||
integrity sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==
|
||||
"@rollup/rollup-win32-ia32-msvc@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz#a18ad47a95c5f264defb60acdd8c27569f816fc1"
|
||||
integrity sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.19.2":
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.2.tgz#d67206c5f2e4b2832ce360bbbde194e96d16dc51"
|
||||
integrity sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==
|
||||
"@rollup/rollup-win32-x64-msvc@4.21.0":
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz#20c09cf44dcb082140cc7f439dd679fe4bba3375"
|
||||
integrity sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==
|
||||
|
||||
"@rushstack/eslint-patch@^1.1.0":
|
||||
version "1.10.4"
|
||||
@ -2540,10 +2540,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.7.tgz#4c620090f28ca7f905a94b706f74dc5b57b44f2f"
|
||||
integrity sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==
|
||||
|
||||
"@types/node@*", "@types/node@^22.4.2":
|
||||
version "22.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.2.tgz#55fefb1c3dba2ecd7eb76738c6b80da75760523f"
|
||||
integrity sha512-nAvM3Ey230/XzxtyDcJ+VjvlzpzoHwLsF7JaDRfoI0ytO0mVheerNmM45CtA0yOILXwXXxOrcUWH3wltX+7PSw==
|
||||
"@types/node@*", "@types/node@^22.5.0":
|
||||
version "22.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.0.tgz#10f01fe9465166b4cab72e75f60d8b99d019f958"
|
||||
integrity sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==
|
||||
dependencies:
|
||||
undici-types "~6.19.2"
|
||||
|
||||
@ -5822,10 +5822,10 @@ humanize-ms@^1.2.1:
|
||||
dependencies:
|
||||
ms "^2.0.0"
|
||||
|
||||
husky@^9.0.11:
|
||||
version "9.1.4"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.4.tgz#926fd19c18d345add5eab0a42b2b6d9a80259b34"
|
||||
integrity sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==
|
||||
husky@^9.1.5:
|
||||
version "9.1.5"
|
||||
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83"
|
||||
integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
@ -7564,10 +7564,10 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.4.23, postcss@^8.4.31, postcss@^8.4.39:
|
||||
version "8.4.40"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8"
|
||||
integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==
|
||||
postcss@^8.4.23, postcss@^8.4.31, postcss@^8.4.41:
|
||||
version "8.4.41"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
|
||||
integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.0.1"
|
||||
@ -8135,29 +8135,29 @@ rollup@^2.77.2:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
rollup@^4.13.0:
|
||||
version "4.19.2"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.2.tgz#4985cd2028965157e8d674a70e49f33aca9038eb"
|
||||
integrity sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==
|
||||
rollup@^4.20.0:
|
||||
version "4.21.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.0.tgz#28db5f5c556a5180361d35009979ccc749560b9d"
|
||||
integrity sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.5"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.19.2"
|
||||
"@rollup/rollup-android-arm64" "4.19.2"
|
||||
"@rollup/rollup-darwin-arm64" "4.19.2"
|
||||
"@rollup/rollup-darwin-x64" "4.19.2"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.19.2"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.19.2"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.19.2"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.19.2"
|
||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.19.2"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.19.2"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.19.2"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.19.2"
|
||||
"@rollup/rollup-linux-x64-musl" "4.19.2"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.19.2"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.19.2"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.19.2"
|
||||
"@rollup/rollup-android-arm-eabi" "4.21.0"
|
||||
"@rollup/rollup-android-arm64" "4.21.0"
|
||||
"@rollup/rollup-darwin-arm64" "4.21.0"
|
||||
"@rollup/rollup-darwin-x64" "4.21.0"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.21.0"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.21.0"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.21.0"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.21.0"
|
||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.21.0"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.21.0"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.21.0"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.21.0"
|
||||
"@rollup/rollup-linux-x64-musl" "4.21.0"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.21.0"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.21.0"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.21.0"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
@ -9245,14 +9245,14 @@ vite-tsconfig-paths@^4.3.2:
|
||||
globrex "^0.1.2"
|
||||
tsconfck "^3.0.3"
|
||||
|
||||
vite@^5.0.0, vite@^5.0.12:
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8"
|
||||
integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==
|
||||
vite@^5.0.0, vite@^5.4.2:
|
||||
version "5.4.2"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e"
|
||||
integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==
|
||||
dependencies:
|
||||
esbuild "^0.21.3"
|
||||
postcss "^8.4.39"
|
||||
rollup "^4.13.0"
|
||||
postcss "^8.4.41"
|
||||
rollup "^4.20.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user