diff --git a/app/gui2/.snyk b/app/gui2/.snyk new file mode 100644 index 00000000000..43995318063 --- /dev/null +++ b/app/gui2/.snyk @@ -0,0 +1,20 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.25.0 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + javascript/DuplicateIfBody: + - '*': + reason: Intentional. + javascript/VueGetterDoesntReturn: + - '*': + reason: set() should not return... and TypeScript catches these errors. + javascript/MissingArgument: + - '*': + reason: TypeScript catches these errors. This warning is incorrect. + javascript/PromiseNotCaughtGeneral: + - '*': + reason: Not relevant. + javascript/IncompatibleTypesInComparison: + - '*': + reason: TypeScript catches these errors in a more sensible way. +patch: {} diff --git a/app/gui2/e2e/MockApp.vue b/app/gui2/e2e/MockApp.vue new file mode 100644 index 00000000000..0f306ceca60 --- /dev/null +++ b/app/gui2/e2e/MockApp.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/app/gui2/e2e/actions.ts b/app/gui2/e2e/actions.ts new file mode 100644 index 00000000000..374cd08c56f --- /dev/null +++ b/app/gui2/e2e/actions.ts @@ -0,0 +1,11 @@ +import { type Page } from '@playwright/test' + +// ================= +// === goToGraph === +// ================= + +/** Perform a successful login. */ +export async function goToGraph(page: Page) { + await page.goto('/') + // Originally this clicked the play button but for now that is out of scope. +} diff --git a/app/gui2/e2e/componentBrowser.spec.ts b/app/gui2/e2e/componentBrowser.spec.ts new file mode 100644 index 00000000000..2c251c89fbb --- /dev/null +++ b/app/gui2/e2e/componentBrowser.spec.ts @@ -0,0 +1,17 @@ +import { expect, test } from '@playwright/test' +import * as actions from './actions' +import * as customExpect from './customExpect' +import * as locate from './locate' + +test('component browser shows entries, and creates a new node', async ({ page }) => { + await actions.goToGraph(page) + await locate.graphEditor(page).click() + await locate.graphEditor(page).press('Enter') + await customExpect.toExist(locate.componentBrowser(page)) + await customExpect.toExist(locate.componentBrowserEntry(page)) + const nodeCount = await locate.graphNode(page).count() + await locate.componentBrowserEntry(page).last().click() + await locate.componentBrowserEntry(page).last().click({ force: true }) + await expect(locate.componentBrowser(page)).not.toBeVisible() + await expect(locate.graphNode(page)).toHaveCount(nodeCount + 1) +}) diff --git a/app/gui2/e2e/customExpect.ts b/app/gui2/e2e/customExpect.ts new file mode 100644 index 00000000000..a3b13a32ae9 --- /dev/null +++ b/app/gui2/e2e/customExpect.ts @@ -0,0 +1,9 @@ +import { expect, type Locator } from 'playwright/test' + +/** Ensures that at least one of the elements that the Locator points to, + * is an attached and visible DOM node. */ +export function toExist(locator: Locator) { + // Counter-intuitive, but correct: + // https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-visible + return expect(locator.first()).toBeVisible() +} diff --git a/app/gui2/e2e/graphNodeVisualization.spec.ts b/app/gui2/e2e/graphNodeVisualization.spec.ts new file mode 100644 index 00000000000..7c5011c54e9 --- /dev/null +++ b/app/gui2/e2e/graphNodeVisualization.spec.ts @@ -0,0 +1,18 @@ +import { expect, test } from '@playwright/test' +import * as actions from './actions' +import * as customExpect from './customExpect' +import * as locate from './locate' + +test('node can open and load visualization', async ({ page }) => { + await actions.goToGraph(page) + const node = locate.graphNode(page).last() + await node.click({ position: { x: 8, y: 8 } }) + await customExpect.toExist(locate.circularMenu(page)) + await locate.toggleVisualizationButton(page).click() + await customExpect.toExist(locate.anyVisualization(page)) + await locate.showVisualizationSelectorButton(page).click() + await page.getByText('JSON').click() + await customExpect.toExist(locate.jsonVisualization(page)) + // The default JSON viz data contains an object. + await expect(locate.jsonVisualization(page)).toContainText('{') +}) diff --git a/app/gui2/e2e/graphRenderNodes.spec.ts b/app/gui2/e2e/graphRenderNodes.spec.ts new file mode 100644 index 00000000000..1ccd24dd481 --- /dev/null +++ b/app/gui2/e2e/graphRenderNodes.spec.ts @@ -0,0 +1,10 @@ +import { test } from '@playwright/test' +import * as actions from './actions' +import * as customExpect from './customExpect' +import * as locate from './locate' + +test('graph can open and render nodes', async ({ page }) => { + await actions.goToGraph(page) + await customExpect.toExist(locate.graphEditor(page)) + await customExpect.toExist(locate.graphNode(page)) +}) diff --git a/app/gui2/e2e/locate.ts b/app/gui2/e2e/locate.ts new file mode 100644 index 00000000000..c3db8d76034 --- /dev/null +++ b/app/gui2/e2e/locate.ts @@ -0,0 +1,152 @@ +import { type Locator, type Page } from '@playwright/test' +import cssEscape from 'css.escape' + +// ============== +// === Filter === +// ============== + +class Filter { + constructor(public selector = '') {} + + visible(this: T): Omit { + return new Filter(this.selector + ':visible') as any + } + + first(this: T): Omit { + return new Filter(this.selector + ':first') as any + } + + last(this: T): Omit { + return new Filter(this.selector + ':last') as any + } + + id(this: T, id: string): Omit { + return new Filter(this.selector + '#' + cssEscape(id)) as any + } + + class(...classes: string[]) { + return new Filter(this.selector + '.' + classes.map(cssEscape).join('.')) + } + + toString() { + return this.selector + } +} + +// ================ +// === Locators === +// ================ + +// === Button locators === + +function or(a: (page: Locator | Page) => Locator, b: (page: Locator | Page) => Locator) { + return (page: Locator | Page) => a(page).or(b(page)) +} + +export function playOrOpenProjectButton(page: Locator | Page) { + return page.getByAltText('Open in editor') +} + +// === Auto-evaluation === + +export function enableAutoEvaluationButton(page: Locator | Page) { + return page.getByAltText('Enable auto-evaluation') +} + +export function disableAutoEvaluationButton(page: Locator | Page) { + return page.getByAltText('Disable auto-evaluation') +} + +export const toggleAutoEvaluationButton = or( + enableAutoEvaluationButton, + disableAutoEvaluationButton, +) + +// === Documentation === + +export function showDocumentationButton(page: Locator | Page) { + return page.getByAltText('Show documentation') +} + +export function hideDocumentationButton(page: Locator | Page) { + return page.getByAltText('Hide documentation') +} + +export const toggleDocumentationButton = or(showDocumentationButton, hideDocumentationButton) + +// === Visualization === + +export function showVisualizationButton(page: Locator | Page) { + return page.getByAltText('Show visualization') +} + +export function hideVisualizationButton(page: Locator | Page) { + return page.getByAltText('Hide visualization') +} + +export const toggleVisualizationButton = or(showVisualizationButton, hideVisualizationButton) + +// === Visualization selector === + +export function showVisualizationSelectorButton(page: Locator | Page) { + return page.getByAltText('Show visualization selector') +} + +export function hideVisualizationSelectorButton(page: Locator | Page) { + return page.getByAltText('Hide visualization selector') +} + +export const toggleVisualizationSelectorButton = or( + showVisualizationSelectorButton, + hideVisualizationSelectorButton, +) + +// === Fullscreen === + +export function enterFullscreenButton(page: Locator | Page) { + return page.getByAltText('Enter fullscreen') +} + +export function exitFullscreenButton(page: Locator | Page) { + return page.getByAltText('Exit fullscreen') +} + +export const toggleFullscreenButton = or(enterFullscreenButton, exitFullscreenButton) + +// === Data locators === + +type SanitizeClassName = T extends `${infer A}.${infer B}` + ? SanitizeClassName<`${A}${B}`> + : T extends `${infer A} ${infer B}` + ? SanitizeClassName<`${A}${B}`> + : T + +function componentLocator(className: SanitizeClassName) { + return (page: Locator | Page, filter?: (f: Filter) => { selector: string }) => { + return page.locator(`.${className}${filter?.(new Filter()) ?? ''}`) + } +} + +export const graphEditor = componentLocator('GraphEditor') +export const graphNode = componentLocator('GraphNode') +// @ts-expect-error +export const anyVisualization = componentLocator('GraphVisualization > *') +export const circularMenu = componentLocator('CircularMenu') +export const componentBrowser = componentLocator('ComponentBrowser') + +export function componentBrowserEntry( + page: Locator | Page, + filter?: (f: Filter) => { selector: string }, +) { + return page.locator(`.ComponentBrowser .component${filter?.(new Filter()) ?? ''}`) +} + +export const jsonVisualization = componentLocator('JSONVisualization') +export const tableVisualization = componentLocator('TableVisualization') +export const scatterplotVisualization = componentLocator('ScatterplotVisualization') +export const histogramVisualization = componentLocator('HistogramVisualization') +export const heatmapVisualization = componentLocator('HeatmapVisualization') +export const sqlVisualization = componentLocator('SqlVisualization') +export const geoMapVisualization = componentLocator('GeoMapVisualization') +export const imageBase64Visualization = componentLocator('ImageBase64Visualization') +export const warningsVisualization = componentLocator('WarningsVisualization') diff --git a/app/gui2/e2e/main.ts b/app/gui2/e2e/main.ts new file mode 100644 index 00000000000..2ad001b64f7 --- /dev/null +++ b/app/gui2/e2e/main.ts @@ -0,0 +1,58 @@ +import 'enso-dashboard/src/tailwind.css' +import { createPinia } from 'pinia' +import { createApp, ref } from 'vue' +import '../src/assets/base.css' +import { provideGuiConfig } from '../src/providers/guiConfig' +import { provideVisualizationConfig } from '../src/providers/visualizationConfig' +import { MockTransport, MockWebSocket } from '../src/util/net' +import { Vec2 } from '../src/util/vec2' +import MockApp from './MockApp.vue' +import { mockDataHandler, mockLSHandler } from './mockEngine' + +MockTransport.addMock('engine', mockLSHandler) +MockWebSocket.addMock('data', mockDataHandler) + +const app = createApp(MockApp) +app.use(createPinia()) +provideGuiConfig._mock( + ref({ + startup: { + project: 'Mock Project', + displayedProjectName: 'Mock Project', + }, + engine: { rpcUrl: 'mock://engine', dataUrl: 'mock://data' }, + }), + app, +) +// Required for visualization stories. +provideVisualizationConfig._mock( + { + fullscreen: false, + width: 200, + height: 150, + hide() {}, + isCircularMenuVisible: false, + nodeSize: new Vec2(200, 150), + currentType: { + module: { kind: 'Builtin' }, + name: 'Current Type', + }, + types: [ + { + module: { kind: 'Builtin' }, + name: 'Example', + }, + { + module: { kind: 'Builtin' }, + name: 'Types', + }, + { + module: { kind: 'Builtin' }, + name: 'Here', + }, + ], + updateType() {}, + }, + app, +) +app.mount('#app') diff --git a/app/gui2/e2e/mockEngine.ts b/app/gui2/e2e/mockEngine.ts new file mode 100644 index 00000000000..fdc5c70e166 --- /dev/null +++ b/app/gui2/e2e/mockEngine.ts @@ -0,0 +1,332 @@ +import * as random from 'lib0/random' +import { + Builder, + EnsoUUID, + OutboundMessage, + OutboundPayload, + VisualizationContext, + VisualizationUpdate, +} from 'shared/binaryProtocol' +import { mockDataWSHandler as originalMockDataWSHandler } from 'shared/dataServer/mock' +import type { + ContextId, + ExpressionId, + LibraryComponentGroup, + Path, + Uuid, + VisualizationConfiguration, + response, +} from 'shared/languageServerTypes' +import type { SuggestionEntry } from 'shared/languageServerTypes/suggestions' +import { uuidToBits } from 'shared/uuid' +import type { MockTransportData, WebSocketHandler } from 'src/util/net' +import type { QualifiedName } from 'src/util/qualifiedName' +import { mockFsDirectoryHandle, type FileTree } from '../src/util/convert/fsAccess' +import mockDb from '../stories/mockSuggestions.json' assert { type: 'json' } + +const mockProjectId = random.uuidv4() as Uuid +const standardBase = 'Standard.Base' as QualifiedName + +export function placeholderGroups(): LibraryComponentGroup[] { + return [ + { color: '#4D9A29', name: 'Input', library: standardBase, exports: [] }, + { color: '#B37923', name: 'Web', library: standardBase, exports: [] }, + { color: '#9735B9', name: 'Parse', library: standardBase, exports: [] }, + { color: '#4D9A29', name: 'Select', library: standardBase, exports: [] }, + { color: '#B37923', name: 'Join', library: standardBase, exports: [] }, + { color: '#9735B9', name: 'Transform', library: standardBase, exports: [] }, + { color: '#4D9A29', name: 'Output', library: standardBase, exports: [] }, + ] +} + +let mainFile = `\ +from Standard.Base import all +from Standard.Base.Runtime.Ref import Ref + +from Standard.Test import Bench + +options = Bench.options . set_warmup (Bench.phase_conf 1 2) . set_measure (Bench.phase_conf 3 2) + +collect_benches = Bench.build builder-> + range_size = 100000000 + data = 0.up_to range_size + + builder.group "Range" options group_builder-> + group_builder.specify "iterate" <| + cell = Ref.new 0 + data . each _-> + x = cell.get + cell.put x+1 + + cell.get . should_equal range_size + +main = + benches = collect_benches + result = run_main benches +` + +export function getMainFile() { + return mainFile +} + +export function setMainFile(newMainFile: string) { + return (mainFile = newMainFile) +} + +const fileTree = { + src: { + get 'Main.enso'() { + return mainFile + }, + }, +} + +const visualizations = new Map() +const visualizationExprIds = new Map() + +const encoder = new TextEncoder() +const encodeJSON = (data: unknown) => encoder.encode(JSON.stringify(data)) + +const scatterplotJson = encodeJSON({ + axis: { + x: { label: 'x-axis label', scale: 'linear' }, + y: { label: 'y-axis label', scale: 'logarithmic' }, + }, + points: { labels: 'visible' }, + data: [ + { x: 0.1, y: 0.7, label: 'foo', color: '#FF0000', shape: 'circle', size: 0.2 }, + { x: 0.4, y: 0.2, label: 'baz', color: '#0000FF', shape: 'square', size: 0.3 }, + ], +}) + +const mockVizData: Record = { + // JSON + 'Standard.Visualization.Preprocessor.default_preprocessor': scatterplotJson, + 'Standard.Visualization.Scatter_Plot.process_to_json_text': scatterplotJson, + 'Standard.Visualization.SQL.Visualization.prepare_visualization': encodeJSON({ + dialect: 'sql', + code: `SELECT * FROM \`foo\` WHERE \`a\` = ? AND b LIKE ?;`, + interpolations: [ + // eslint-disable-next-line camelcase + { enso_type: 'Data.Numbers.Number', value: '123' }, + // eslint-disable-next-line camelcase + { enso_type: 'Builtins.Main.Text', value: "a'bcd" }, + ], + }), + 'Standard.Visualization.Geo_Map.process_to_json_text': encodeJSON({ + latitude: 37.8, + longitude: -122.45, + zoom: 15, + controller: true, + showingLabels: true, // Enables presenting labels when hovering over a point. + layers: [ + { + type: 'Scatterplot_Layer', + data: [ + { + latitude: 37.8, + longitude: -122.45, + color: [255, 0, 0], + radius: 100, + label: 'an example label', + }, + ], + }, + ], + }), + 'Standard.Visualization.Histogram.process_to_json_text': encodeJSON({ + axis: { + x: { label: 'x-axis label', scale: 'linear' }, + y: { label: 'y-axis label', scale: 'logarithmic' }, + }, + color: 'rgb(1.0,0.0,0.0)', + bins: 10, + data: { + values: [0.1, 0.2, 0.1, 0.15, 0.7], + }, + }), + 'Standard.Visualization.Table.Visualization.prepare_visualization': encodeJSON({ + type: 'Matrix', + // eslint-disable-next-line camelcase + column_count: 5, + // eslint-disable-next-line camelcase + all_rows_count: 10, + json: Array.from({ length: 10 }, (_, i) => Array.from({ length: 5 }, (_, j) => `${i},${j}`)), + }), + 'Standard.Visualization.Warnings.process_to_json_text': encodeJSON([ + 'warning 1', + "warning 2!!&<>;'\x22", + ]), + // The following visualizations do not have unique transformation methods, and as such are only kept + // for posterity. + Image: encodeJSON({ + mediaType: 'image/svg+xml', + base64: `PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0\ +MCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBkPSJNMjAuMDUgMEEyMCAyMCAwIDAgMCAwIDIwLjA1IDIwLjA2IDIwLjA\ +2IDAgMSAwIDIwLjA1IDBabTAgMzYuMDVjLTguOTMgMC0xNi4xLTcuMTctMTYuMS0xNi4xIDAtOC45NCA3LjE3LTE2LjEgMTYuMS\ +0xNi4xIDguOTQgMCAxNi4xIDcuMTYgMTYuMSAxNi4xYTE2LjE4IDE2LjE4IDAgMCAxLTE2LjEgMTYuMVoiLz48cGF0aCBkPSJNM\ +jcuMTIgMTcuNzdhNC42OCA0LjY4IDAgMCAxIDIuMzkgNS45MiAxMC4yMiAxMC4yMiAwIDAgMS05LjU2IDYuODZBMTAuMiAxMC4y\ +IDAgMCAxIDkuNzcgMjAuMzZzMS41NSAyLjA4IDQuNTcgMi4wOGMzLjAxIDAgNC4zNi0xLjE0IDUuNi0yLjA4IDEuMjUtLjkzIDI\ +uMDktMyA1LjItMyAuNzMgMCAxLjQ2LjIgMS45OC40WiIvPjwvZz48ZGVmcz48Y2xpcFBhdGggaWQ9ImEiPjxwYXRoIGZpbGw9Ii\ +NmZmYiIGQ9Ik0wIDBoNDB2NDBIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=`, + }), + Heatmap: encodeJSON([ + ['A', 'B', 'C', 'D', 'A'], + ['D', 'E', 'D', 'X', 'Z'], + [50, 25, 40, 20, 10], + ]), +} + +function createMessageId(builder: Builder) { + const messageUuid = random.uuidv4() + const [leastSigBits, mostSigBits] = uuidToBits(messageUuid) + return EnsoUUID.createEnsoUUID(builder, leastSigBits, mostSigBits) +} + +function createId(id: Uuid) { + const [low, high] = uuidToBits(id) + return (builder: Builder) => EnsoUUID.createEnsoUUID(builder, low, high) +} + +function sendVizData(id: Uuid, config: VisualizationConfiguration) { + const vizData = mockVizData[`${config.visualizationModule}.${config.expression}`] + if (!vizData || !sendData) return + const builder = new Builder() + const exprId = visualizationExprIds.get(id) + const visualizationContextOffset = VisualizationContext.createVisualizationContext( + builder, + createId(id), + createId(config.executionContextId), + exprId ? createId(exprId) : null, + ) + const dataOffset = VisualizationUpdate.createDataVector(builder, vizData) + const payload = VisualizationUpdate.createVisualizationUpdate( + builder, + visualizationContextOffset, + dataOffset, + ) + const rootTable = OutboundMessage.createOutboundMessage( + builder, + createMessageId, + null, // correlationId + OutboundPayload.VISUALIZATION_UPDATE, + payload, + ) + sendData(builder.finish(rootTable).toArrayBuffer()) +} + +let sendData: ((data: string | Blob | ArrayBufferLike | ArrayBufferView) => void) | undefined + +export const mockLSHandler: MockTransportData = async (method, data, transport) => { + switch (method) { + case 'session/initProtocolConnection': + return { + contentRoots: [{ type: 'Project', id: mockProjectId }], + } satisfies response.InitProtocolConnection + case 'executionContext/create': { + const data_ = data as { + contextId: ContextId + } + setTimeout( + () => transport.emit('executionContext/executionComplete', { contextId: data_.contextId }), + 100, + ) + return { + contextId: data_.contextId, + } + } + case 'executionContext/attachVisualization': { + const data_ = data as { + visualizationId: Uuid + expressionId: ExpressionId + visualizationConfig: VisualizationConfiguration + } + visualizations.set(data_.visualizationId, data_.visualizationConfig) + visualizationExprIds.set(data_.visualizationId, data_.expressionId) + sendVizData(data_.visualizationId, data_.visualizationConfig) + return + } + case 'executionContext/detachVisualization': { + const data_ = data as { + visualizationId: Uuid + expressionId: ExpressionId + contextId: ContextId + } + visualizations.delete(data_.visualizationId) + visualizationExprIds.delete(data_.visualizationId) + return + } + case 'executionContext/modifyVisualization': { + const data_ = data as { + visualizationId: Uuid + visualizationConfig: VisualizationConfiguration + } + visualizations.set(data_.visualizationId, data_.visualizationConfig) + sendVizData(data_.visualizationId, data_.visualizationConfig) + return + } + case 'search/getSuggestionsDatabase': + return { + entries: mockDb.map((suggestion, id) => ({ + id, + suggestion: suggestion as SuggestionEntry, + })), + currentVersion: 1, + } satisfies response.GetSuggestionsDatabase + case 'runtime/getComponentGroups': + return { componentGroups: placeholderGroups() } satisfies response.GetComponentGroups + case 'executionContext/push': + case 'executionContext/pop': + case 'executionContext/recompute': + case 'capability/acquire': + return {} + case 'file/list': { + const data_ = data as { path: Path } + if (!data_.path) return Promise.reject(`'path' parameter missing in '${method}'`) + if (data_.path.rootId !== mockProjectId) + return Promise.reject( + `Only the project's 'rootId' is supported, got '${data_.path.rootId}'`, + ) + let child: FileTree | string | ArrayBuffer | undefined = fileTree + if (child) { + for (const segment of data_.path.segments) { + child = child?.[segment] + if (!child || typeof child === 'string' || child instanceof ArrayBuffer) break + } + } + if (!child) return Promise.reject(`Folder '/${data_.path.segments.join('/')}' not found.`) + if (typeof child === 'string' || child instanceof ArrayBuffer) + return Promise.reject(`File '/${data_.path.segments.join('/')}' is not a folder.`) + return { + paths: Object.entries(child).map(([name, entry]) => ({ + type: typeof entry === 'string' || entry instanceof ArrayBuffer ? 'File' : 'Directory', + name, + path: { rootId: data_.path.rootId, segments: [...data_.path.segments, name] }, + })), + } satisfies response.FileList + } + default: + return Promise.reject(`Method '${method}' not mocked`) + } +} + +const directory = mockFsDirectoryHandle(fileTree, '(root)') + +export const mockDataHandler: WebSocketHandler = originalMockDataWSHandler( + async (segments) => { + if (!segments.length) return + let file + try { + let dir = directory + for (const segment of segments.slice(0, -1)) { + dir = await dir.getDirectoryHandle(segment) + } + const fileHandle = await dir.getFileHandle(segments.at(-1)!) + file = await fileHandle.getFile() + } catch { + return + } + return await file?.arrayBuffer() + }, + (send) => (sendData = send), +) diff --git a/app/gui2/e2e/mockProjectManager.ts b/app/gui2/e2e/mockProjectManager.ts new file mode 100644 index 00000000000..80c4dd74aca --- /dev/null +++ b/app/gui2/e2e/mockProjectManager.ts @@ -0,0 +1,63 @@ +declare const projectIdBrand: unique symbol +/** A name of a project. */ +export type ProjectId = string & { [projectIdBrand]: never } +declare const projectNameBrand: unique symbol +/** A name of a project. */ +export type ProjectName = string & { [projectNameBrand]: never } +declare const utcDateTimeBrand: unique symbol +/** A name of a project. */ +export type UTCDateTime = string & { [utcDateTimeBrand]: never } + +/** A value specifying the hostname and port of a socket. */ +export interface IpWithSocket { + host: string + port: number +} + +export interface OpenProject { + engineVersion: string + languageServerJsonAddress: IpWithSocket + languageServerBinaryAddress: IpWithSocket + projectName: ProjectName + projectNormalizedName: string + projectNamespace: string +} + +/** Details for a project. */ +export interface ProjectMetadata { + name: ProjectName + namespace: string + id: ProjectId + engineVersion: string | null + created: UTCDateTime + lastOpened: UTCDateTime | null +} + +export const projects = new Map() +const openProjects = new Set() + +export const methods = { + async 'project/open'(id) { + openProjects.add(id) + const project = projects.get(id) + if (!project) throw new Error(`Cannot find project with ID ${id}.`) + return { + projectName: project.name, + projectNormalizedName: project.name, + projectNamespace: project.namespace, + languageServerJsonAddress: { host: '127.0.0.1', port: 30000 }, + languageServerBinaryAddress: { host: '127.0.0.1', port: 30001 }, + engineVersion: '', + } satisfies OpenProject + }, + async 'project/close'(id) { + openProjects.delete(id) + return {} + }, + async 'project/list'(numberOfProjects) { + const projectsList = Array.from(projects.values()) + return { + projects: numberOfProjects != null ? projectsList.slice(0, numberOfProjects) : projectsList, + } + }, +} satisfies Record Promise> diff --git a/app/gui2/e2e/pm-openrpc.json b/app/gui2/e2e/pm-openrpc.json new file mode 100644 index 00000000000..df1eb37ebd6 --- /dev/null +++ b/app/gui2/e2e/pm-openrpc.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://meta.open-rpc.org/", + "openrpc": "1.2.6", + "info": { + "title": "Enso Project Manager", + "description": "Manages (lists, opens, closes) Enso projects", + "version": "1.0.0" + }, + "methods": [ + { + "name": "project/open", + "params": [ + { "name": "projectId", "schema": { "type": "string" } }, + { + "name": "missingComponentAction", + "schema": { "enum": ["Fail", "Install", "ForceInstallBroken"] } + } + ], + "result": { + "name": "openProject", + "schema": { + "type": "object", + "properties": { + "engineVersion": { "type": "string" }, + "languageServerJsonAddress": { + "type": "object", + "properties": { + "host": { "type": "string" }, + "port": { "type": "number" } + } + }, + "languageServerBinaryAddress": { + "type": "object", + "properties": { + "host": { "type": "string" }, + "port": { "type": "number" } + } + }, + "projectName": { "type": "string" }, + "projectNormalizedName": { "type": "string" }, + "projectNamespace": { "type": "string" } + } + } + } + }, + { + "name": "project/close", + "params": [{ "name": "projectId", "schema": { "type": "string" } }], + "result": { + "name": "closeProject", + "schema": { "type": "null" } + } + }, + { + "name": "project/list", + "params": [ + { "name": "numberOfProjects", "schema": { "type": "integer" }, "required": false } + ], + "result": { + "name": "listProject", + "schema": { + "type": "object", + "properties": { + "projects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "namespace": { "type": "string" }, + "id": { "type": "string" }, + "engineVersion": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, + "created": { "type": "string" }, + "lastOpened": { "anyOf": [{ "type": "string" }, { "type": "null" }] } + } + } + } + } + } + } + } + ] +} diff --git a/app/gui2/e2e/setup.ts b/app/gui2/e2e/setup.ts new file mode 100644 index 00000000000..775741e79a5 --- /dev/null +++ b/app/gui2/e2e/setup.ts @@ -0,0 +1,38 @@ +import { Server } from '@open-rpc/server-js' +import * as random from 'lib0/random.js' +import { + methods as pmMethods, + projects, + type ProjectId, + type ProjectName, + type UTCDateTime, +} from './mockProjectManager' +import pmSpec from './pm-openrpc.json' assert { type: 'json' } + +export default function setup() { + const pm = new Server({ + transportConfigs: [ + { + type: 'WebSocketTransport', + options: { + id: 'websocket', + udp: true, + ipv6: true, + port: 30535, + middleware: [], + }, + }, + ], + openrpcDocument: pmSpec as typeof pmSpec & { openrpc: never }, + methodMapping: pmMethods, + }) + pm.start() + projects.set('mock project id 0001', { + id: random.uuidv4() as ProjectId, + created: new Date().toISOString() as UTCDateTime, + lastOpened: new Date().toISOString() as UTCDateTime, + engineVersion: '', + name: 'Mock Project Name' as ProjectName, + namespace: 'local', + }) +} diff --git a/app/gui2/e2e/tsconfig.json b/app/gui2/e2e/tsconfig.json deleted file mode 100644 index 99923aba094..00000000000 --- a/app/gui2/e2e/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "@tsconfig/node18/tsconfig.json", - "include": ["./**/*"] -} diff --git a/app/gui2/e2e/vue.spec.ts b/app/gui2/e2e/vue.spec.ts deleted file mode 100644 index d6b855800a3..00000000000 --- a/app/gui2/e2e/vue.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { expect, test } from '@playwright/test' - -// See here how to get started: -// https://playwright.dev/docs/intro -test('visits the app root url', async ({ page }) => { - await page.goto('/') - await expect(page.locator('div.greetings > h1')).toHaveText('You did it!') -}) diff --git a/app/gui2/package.json b/app/gui2/package.json index a1c553c9253..945a0ea69a0 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -64,16 +64,18 @@ "y-textarea": "^1.0.0", "y-websocket": "^1.5.0", "yjs": "^13.6.7", - "zod": "^3.22.2" + "zod": "^3.22.4" }, "devDependencies": { "@danmarshall/deckgl-typings": "^4.9.28", "@eslint/eslintrc": "^2.1.2", "@eslint/js": "^8.49.0", "@histoire/plugin-vue": "^0.17.1", - "@playwright/test": "^1.37.0", + "@open-rpc/server-js": "^1.9.4", + "@playwright/test": "^1.40.0", "@rushstack/eslint-patch": "^1.3.2", "@tsconfig/node18": "^18.2.0", + "@types/css.escape": "^1.5.2", "@types/culori": "^2.0.1", "@types/d3": "^7.4.0", "@types/hash-sum": "^1.0.0", @@ -92,6 +94,7 @@ "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", "change-case": "^4.1.2", + "css.escape": "^1.5.1", "d3": "^7.4.0", "esbuild": "^0.19.3", "eslint": "^8.49.0", @@ -100,6 +103,7 @@ "hash-wasm": "^4.10.0", "histoire": "^0.17.2", "jsdom": "^22.1.0", + "playwright": "^1.39.0", "postcss-nesting": "^12.0.1", "prettier": "^3.0.0", "prettier-plugin-organize-imports": "^3.2.3", diff --git a/app/gui2/playwright.config.ts b/app/gui2/playwright.config.ts index 4387e74534c..613bfd90cd3 100644 --- a/app/gui2/playwright.config.ts +++ b/app/gui2/playwright.config.ts @@ -1,112 +1,90 @@ -import type { PlaywrightTestConfig } from '@playwright/test' -import { devices } from '@playwright/test' +/** @file Playwright browser testing configuration. */ +/** Note that running Playwright in CI poses a number of issues: + * - `backdrop-filter: blur` is disabled, due to issues with Chromium's `--disable-gpu` flag + * (see below). + * - System validation dialogs are not reliable between computers, as they may have different + * default fonts. */ +import { defineConfig } from '@playwright/test' -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); +const DEBUG = process.env.DEBUG_E2E === 'true' -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { +export default defineConfig({ + globalSetup: './e2e/setup.ts', testDir: './e2e', - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + ...(process.env.CI ? { workers: 1 } : {}), reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:5173', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - - /* Only on CI systems run the tests headless */ - headless: !!process.env.CI, + expect: { + timeout: 5000, + toHaveScreenshot: { threshold: 0 }, }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ + use: { + headless: !DEBUG, + baseURL: 'http://localhost:5173', + trace: 'on-first-retry', + ...(DEBUG + ? {} + : { + launchOptions: { + ignoreDefaultArgs: ['--headless'], + args: [ + // Much closer to headful Chromium than classic headless. + '--headless=new', + // Required for `backdrop-filter: blur` to work. + '--use-angle=swiftshader', + // FIXME: `--disable-gpu` disables `backdrop-filter: blur`, which is not handled by + // the software (CPU) compositor. This SHOULD be fixed eventually, but this flag + // MUST stay as CI does not have a GPU. + '--disable-gpu', + // Fully disable GPU process. + '--disable-software-rasterizer', + // Disable text subpixel antialiasing. + '--font-render-hinting=none', + '--disable-skia-runtime-opts', + '--disable-system-font-check', + '--disable-font-subpixel-positioning', + '--disable-lcd-text', + ], + }, + }), + }, + // projects: [ + // { + // name: 'chromium', + // use: { + // ...devices['Desktop Chrome'], + // }, + // }, + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + // ], webServer: { - /** - * Use the dev server by default for faster feedback loop. - * Use the preview server on CI for more realistic testing. - Playwright will re-use the local server if there is already a dev-server running. - */ - command: process.env.CI ? 'vite preview --port 5173' : 'vite dev', + // The command-line flag is required + command: process.env.CI ? 'E2E=true vite preview --port 5173' : 'E2E=true vite dev', port: 5173, reuseExistingServer: !process.env.CI, }, -} - -export default config +}) diff --git a/app/gui2/shared/binaryProtocol.ts b/app/gui2/shared/binaryProtocol.ts index 69dd1ad7c34..4a2d51d38c8 100644 --- a/app/gui2/shared/binaryProtocol.ts +++ b/app/gui2/shared/binaryProtocol.ts @@ -1,4 +1,4 @@ -// automatically generated by the FlatBuffers compiler (flatc version 1.12.0), +// Automatically generated by the FlatBuffers compiler (flatc version 1.12.0), // and modified by inlining the `flatbuffers` dependency and switching from manual decoding // to a `DataView`. // Helper classes copied from https://github.com/google/flatbuffers, @@ -214,13 +214,21 @@ const FILE_IDENTIFIER_LENGTH = 4 const SIZE_PREFIX_LENGTH = 4 const TEXT_ENCODER = new TextEncoder() const TEXT_DECODER = new TextDecoder() -const NULL_PTR = 0 +export const Null = 0 as Offset -type Offset = number -type CreateOffset = ((builder: Builder) => Offset) | null | undefined +declare const offsetType: unique symbol +type OffsetConstraint = Table | number | string | Uint8Array | ArrayBuffer | OffsetConstraint[] +export type Offset = number & { + [offsetType]: T +} +type AnyOffset = Offset +export type CreateOffset = + | ((builder: Builder) => Offset) + | null + | undefined -interface IGeneratedObject { - pack(builder: Builder): Offset +interface IGeneratedObject { + pack(builder: Builder): Offset } export type Table = { @@ -239,7 +247,7 @@ export class Builder { private vtables: number[] = [] private vectorNumElems = 0 private _forceDefaults = false - private stringMaps: Map | null = null + private stringMaps: Map | null = null constructor(initialSize?: number) { initialSize ??= 1024 @@ -251,7 +259,7 @@ export class Builder { this.bb.position = 0 this.space = this.bb.view.byteLength this.minAlignment = 1 - this.vtable = null + this.vtable = [] this.vtableInUse = 0 this.isNested = false this.objectStart = 0 @@ -394,21 +402,29 @@ export class Builder { } } - addFieldOffset(voffset: number, value: Offset, defaultValue: Offset): void { + addFieldOffset( + voffset: number, + value: Offset, + defaultValue: Offset, + ): void { if (this._forceDefaults || value != defaultValue) { this.addOffset(value) this.slot(voffset) } } - addFieldStruct(voffset: number, value: Offset, defaultValue: Offset): void { + addFieldStruct( + voffset: number, + value: Offset, + defaultValue: Offset, + ): void { if (value != defaultValue) { this.nested(value) this.slot(voffset) } } - nested(obj: Offset): void { + nested(obj: AnyOffset): void { if (obj != this.offset()) { throw new TypeError('FlatBuffers: struct must be serialized inline.') } @@ -424,8 +440,8 @@ export class Builder { if (this.vtable !== null) this.vtable[voffset] = this.offset() } - offset(): Offset { - return this.bb.view.byteLength - this.space + offset(): Offset { + return (this.bb.view.byteLength - this.space) as Offset } static growByteBuffer(bb: ByteBuffer): void { @@ -441,7 +457,7 @@ export class Builder { bb.view = new DataView(newBuffer) } - addOffset(offset: Offset): void { + addOffset(offset: AnyOffset): void { this.prep(SIZEOF_INT, 0) // Ensure alignment is already done. this.writeInt32(this.offset() - offset + SIZEOF_INT) } @@ -459,7 +475,7 @@ export class Builder { this.objectStart = this.offset() } - endObject(): Offset { + endObject(): Offset { if (this.vtable == null || !this.isNested) { throw new globalThis.Error('FlatBuffers: endObject called without startObject') } @@ -517,10 +533,14 @@ export class Builder { } this.isNested = false - return vtableloc + return vtableloc as Offset } - finish(rootTable: Offset, fileIdentifier?: string, shouldIncludeSizePrefix?: boolean): Builder { + finish( + rootTable: AnyOffset, + fileIdentifier?: string, + shouldIncludeSizePrefix?: boolean, + ): Builder { const sizePrefix = shouldIncludeSizePrefix ? SIZE_PREFIX_LENGTH : 0 if (fileIdentifier) { this.prep(this.minAlignment, SIZEOF_INT + FILE_IDENTIFIER_LENGTH + sizePrefix) @@ -540,12 +560,12 @@ export class Builder { return this } - finishSizePrefixed(rootTable: Offset, fileIdentifier?: string): Builder { + finishSizePrefixed(rootTable: AnyOffset, fileIdentifier?: string): Builder { this.finish(rootTable, fileIdentifier, true) return this } - requiredField(table: Offset, field: number): void { + requiredField(table: AnyOffset, field: number): void { const tableStart = this.bb.view.byteLength - table const vtableStart = tableStart - this.bb.view.getInt32(tableStart, true) const ok = @@ -564,32 +584,29 @@ export class Builder { this.prep(alignment, elemSize * numElems) // Just in case alignment > int. } - endVector(): Offset { + endVector(): Offset { this.writeInt32(this.vectorNumElems) return this.offset() } - createSharedString(s: string | Uint8Array): Offset { + createSharedString(s: T): Offset { if (!s) { - return 0 + return 0 as Offset } if (!this.stringMaps) { this.stringMaps = new Map() } const value = this.stringMaps.get(s) if (value != null) { - return value + return value as Offset } const offset = this.createString(s) this.stringMaps.set(s, offset) - return offset + return offset as Offset } - createString(s: string | Uint8Array | ArrayBuffer | null | undefined): Offset { - if (s === null || s === undefined) { - return 0 - } - + createString(s: string | Uint8Array | ArrayBuffer | null | undefined): Offset { + if (s === null || s === undefined) return Null let utf8: string | Uint8Array | number[] if (s instanceof Uint8Array) { utf8 = s @@ -598,7 +615,6 @@ export class Builder { } else { utf8 = TEXT_ENCODER.encode(s) } - this.addInt8(0) this.startVector(1, utf8.length, 1) this.bb.position = this.space -= utf8.length @@ -608,38 +624,33 @@ export class Builder { return this.endVector() } - createObjectOffset(obj: string | IGeneratedObject | null): Offset { - if (obj === null) { - return 0 - } - - if (typeof obj === 'string') { - return this.createString(obj) - } else { - return obj.pack(this) - } + createObjectOffset( + obj: T extends string | null ? T : IGeneratedObject, + ): Offset { + if (obj === null) return Null + else if (typeof obj === 'string') return this.createString(obj) as Offset + else return obj.pack(this) as Offset } - createObjectOffsetList(list: (string | IGeneratedObject)[]): Offset[] { + createObjectOffsetList( + list: (T extends string ? string : IGeneratedObject)[], + ): Offset[] { const ret: number[] = [] - for (let i = 0; i < list.length; ++i) { const val = list[i] - if (val != null) { - ret.push(this.createObjectOffset(val)) + ret.push(this.createObjectOffset(val as any)) } else { throw new TypeError('FlatBuffers: Argument for createObjectOffsetList cannot contain null.') } } - - return ret + return ret as Offset[] } - createStructOffsetList( - list: (string | IGeneratedObject)[], + createStructOffsetList( + list: (T extends string ? string : IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void, - ): Offset { + ): Offset { startFunc(this, list.length) this.createObjectOffsetList(list.slice().reverse()) return this.endVector() @@ -654,11 +665,13 @@ export class ByteBuffer { this.view = new DataView(buffer) } - offset(bbPos: number, vtableOffset: number): Offset { + offset(bbPos: number, vtableOffset: number): Offset { const vtable = bbPos - this.view.getInt32(bbPos, true) - return vtableOffset < this.view.getInt16(vtable, true) - ? this.view.getInt16(vtable + vtableOffset, true) - : 0 + return ( + vtableOffset < this.view.getInt16(vtable, true) + ? this.view.getInt16(vtable + vtableOffset, true) + : 0 + ) as Offset } union(t: Table, offset: number): Table { @@ -678,16 +691,16 @@ export class ByteBuffer { return TEXT_DECODER.decode(this.rawMessage(offset)) } - indirect(offset: Offset): Offset { - return offset + this.view.getInt32(offset, true) + indirect(offset: number | Offset): Offset { + return (offset + this.view.getInt32(offset, true)) as Offset } - vector(offset: Offset): Offset { - return offset + this.view.getInt32(offset, true) + SIZEOF_INT // data starts after the length + vector(offset: number | Offset): Offset { + return (offset + this.view.getInt32(offset, true) + SIZEOF_INT) as Offset // data starts after the length } - vectorLength(offset: Offset): Offset { - return this.view.getInt32(offset + this.view.getInt32(offset, true), true) + vectorLength(offset: number | AnyOffset): Offset { + return this.view.getInt32(offset + this.view.getInt32(offset, true), true) as Offset } } @@ -701,6 +714,15 @@ export enum InboundPayload { CHECKSUM_BYTES_CMD = 6, } +export type AnyInboundPayload = + | None + | InitSessionCommand + | WriteFileCommand + | ReadFileCommand + | WriteBytesCommand + | ReadBytesCommand + | ChecksumBytesCommand + export enum OutboundPayload { NONE = 0, ERROR = 1, @@ -712,11 +734,23 @@ export enum OutboundPayload { CHECKSUM_BYTES_REPLY = 7, } +export type AnyOutboundPayload = + | None + | Error + | Success + | VisualizationUpdate + | FileContentsReply + | WriteBytesReply + | ReadBytesReply + | ChecksumBytesReply + export enum ErrorPayload { NONE = 0, READ_OOB = 1, } +export type AnyErrorPayload = None | ReadOutOfBoundsError + export class InboundMessage implements Table { bb!: ByteBuffer bbPos: number = 0 @@ -760,24 +794,24 @@ export class InboundMessage implements Table { builder.startObject(4) } - static addMessageId(builder: Builder, messageIdOffset: Offset) { - builder.addFieldStruct(0, messageIdOffset, 0) + static addMessageId(builder: Builder, messageIdOffset: Offset) { + builder.addFieldStruct(0, messageIdOffset, Null) } - static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { - builder.addFieldStruct(1, correlationIdOffset, 0) + static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { + builder.addFieldStruct(1, correlationIdOffset, Null) } static addPayloadType(builder: Builder, payloadType: InboundPayload) { builder.addFieldInt8(2, payloadType, InboundPayload.NONE) } - static addPayload(builder: Builder, payloadOffset: Offset) { - builder.addFieldOffset(3, payloadOffset, 0) + static addPayload(builder: Builder, payloadOffset: Offset) { + builder.addFieldOffset(3, payloadOffset, Null) } - static endInboundMessage(builder: Builder): Offset { - const offset = builder.endObject() + static endInboundMessage(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // messageId builder.requiredField(offset, 10) // payload return offset @@ -785,14 +819,14 @@ export class InboundMessage implements Table { static createInboundMessage( builder: Builder, - createMessageId: CreateOffset, - createCorrelationId: CreateOffset, + createMessageId: CreateOffset, + createCorrelationId: CreateOffset, payloadType: InboundPayload, - payloadOffset: Offset, - ): Offset { + payloadOffset: Offset, + ): Offset { InboundMessage.startInboundMessage(builder) - InboundMessage.addMessageId(builder, createMessageId?.(builder) ?? NULL_PTR) - InboundMessage.addCorrelationId(builder, createCorrelationId?.(builder) ?? NULL_PTR) + InboundMessage.addMessageId(builder, createMessageId?.(builder) ?? Null) + InboundMessage.addCorrelationId(builder, createCorrelationId?.(builder) ?? Null) InboundMessage.addPayloadType(builder, payloadType) InboundMessage.addPayload(builder, payloadOffset) return InboundMessage.endInboundMessage(builder) @@ -851,24 +885,24 @@ export class OutboundMessage implements Table { builder.startObject(4) } - static addMessageId(builder: Builder, messageIdOffset: Offset) { - builder.addFieldStruct(0, messageIdOffset, 0) + static addMessageId(builder: Builder, messageIdOffset: Offset) { + builder.addFieldStruct(0, messageIdOffset, Null) } - static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { - builder.addFieldStruct(1, correlationIdOffset, 0) + static addCorrelationId(builder: Builder, correlationIdOffset: Offset) { + builder.addFieldStruct(1, correlationIdOffset, Null) } static addPayloadType(builder: Builder, payloadType: OutboundPayload) { builder.addFieldInt8(2, payloadType, OutboundPayload.NONE) } - static addPayload(builder: Builder, payloadOffset: Offset) { - builder.addFieldOffset(3, payloadOffset, 0) + static addPayload(builder: Builder, payloadOffset: Offset) { + builder.addFieldOffset(3, payloadOffset, Null) } - static endOutboundMessage(builder: Builder): Offset { - const offset = builder.endObject() + static endOutboundMessage(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // messageId builder.requiredField(offset, 10) // payload return offset @@ -876,14 +910,14 @@ export class OutboundMessage implements Table { static createOutboundMessage( builder: Builder, - createMessageId: CreateOffset, - createCorrelationId: CreateOffset, + createMessageId: CreateOffset, + createCorrelationId: CreateOffset, payloadType: OutboundPayload, - payloadOffset: Offset, - ): Offset { + payloadOffset: Offset, + ): Offset { OutboundMessage.startOutboundMessage(builder) - OutboundMessage.addMessageId(builder, createMessageId?.(builder) ?? NULL_PTR) - OutboundMessage.addCorrelationId(builder, createCorrelationId?.(builder) ?? NULL_PTR) + OutboundMessage.addMessageId(builder, createMessageId?.(builder) ?? Null) + OutboundMessage.addCorrelationId(builder, createCorrelationId?.(builder) ?? Null) OutboundMessage.addPayloadType(builder, payloadType) OutboundMessage.addPayload(builder, payloadOffset) return OutboundMessage.endOutboundMessage(builder) @@ -907,7 +941,11 @@ export class EnsoUUID implements Table { return this.bb.view.getBigUint64(this.bbPos + 8, true) } - static createEnsoUUID(builder: Builder, leastSigBits: bigint, mostSigBits: bigint): Offset { + static createEnsoUUID( + builder: Builder, + leastSigBits: bigint, + mostSigBits: bigint, + ): Offset { builder.prep(8, 16) builder.writeInt64(mostSigBits) builder.writeInt64(leastSigBits) @@ -967,20 +1005,20 @@ export class Error implements Table { builder.addFieldInt32(0, code, 0) } - static addMessage(builder: Builder, messageOffset: Offset) { - builder.addFieldOffset(1, messageOffset, 0) + static addMessage(builder: Builder, messageOffset: Offset) { + builder.addFieldOffset(1, messageOffset, Null) } static addDataType(builder: Builder, dataType: ErrorPayload) { builder.addFieldInt8(2, dataType, ErrorPayload.NONE) } - static addData(builder: Builder, dataOffset: Offset) { - builder.addFieldOffset(3, dataOffset, 0) + static addData(builder: Builder, dataOffset: Offset) { + builder.addFieldOffset(3, dataOffset, Null) } - static endError(builder: Builder): Offset { - const offset = builder.endObject() + static endError(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 6) // message return offset } @@ -988,10 +1026,10 @@ export class Error implements Table { static createError( builder: Builder, code: number, - messageOffset: Offset, + messageOffset: Offset, dataType: ErrorPayload, - dataOffset: Offset, - ): Offset { + dataOffset: Offset, + ): Offset { Error.startError(builder) Error.addCode(builder, code) Error.addMessage(builder, messageOffset) @@ -1044,12 +1082,15 @@ export class ReadOutOfBoundsError implements Table { builder.addFieldInt64(0, fileLength, 0n) } - static endReadOutOfBoundsError(builder: Builder): Offset { - const offset = builder.endObject() + static endReadOutOfBoundsError(builder: Builder): Offset { + const offset = builder.endObject() return offset } - static createReadOutOfBoundsError(builder: Builder, fileLength: bigint): Offset { + static createReadOutOfBoundsError( + builder: Builder, + fileLength: bigint, + ): Offset { ReadOutOfBoundsError.startReadOutOfBoundsError(builder) ReadOutOfBoundsError.addFileLength(builder, fileLength) return ReadOutOfBoundsError.endReadOutOfBoundsError(builder) @@ -1088,12 +1129,12 @@ export class Success implements Table { builder.startObject(0) } - static endSuccess(builder: Builder): Offset { - const offset = builder.endObject() + static endSuccess(builder: Builder): Offset { + const offset = builder.endObject() return offset } - static createSuccess(builder: Builder): Offset { + static createSuccess(builder: Builder): Offset { Success.startSuccess(builder) return Success.endSuccess(builder) } @@ -1135,19 +1176,22 @@ export class InitSessionCommand implements Table { builder.startObject(1) } - static addIdentifier(builder: Builder, identifierOffset: Offset) { - builder.addFieldStruct(0, identifierOffset, 0) + static addIdentifier(builder: Builder, identifierOffset: Offset) { + builder.addFieldStruct(0, identifierOffset, Null) } - static endInitSessionCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endInitSessionCommand(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // identifier return offset } - static createInitSessionCommand(builder: Builder, createIdentifier: CreateOffset): Offset { + static createInitSessionCommand( + builder: Builder, + createIdentifier: CreateOffset, + ): Offset { InitSessionCommand.startInitSessionCommand(builder) - InitSessionCommand.addIdentifier(builder, createIdentifier?.(builder) ?? NULL_PTR) + InitSessionCommand.addIdentifier(builder, createIdentifier?.(builder) ?? Null) return InitSessionCommand.endInitSessionCommand(builder) } } @@ -1201,20 +1245,20 @@ export class VisualizationContext implements Table { builder.startObject(3) } - static addVisualizationId(builder: Builder, visualizationIdOffset: Offset) { - builder.addFieldStruct(0, visualizationIdOffset, 0) + static addVisualizationId(builder: Builder, visualizationIdOffset: Offset) { + builder.addFieldStruct(0, visualizationIdOffset, Null) } - static addContextId(builder: Builder, contextIdOffset: Offset) { - builder.addFieldStruct(1, contextIdOffset, 0) + static addContextId(builder: Builder, contextIdOffset: Offset) { + builder.addFieldStruct(1, contextIdOffset, Null) } - static addExpressionId(builder: Builder, expressionIdOffset: Offset) { - builder.addFieldStruct(2, expressionIdOffset, 0) + static addExpressionId(builder: Builder, expressionIdOffset: Offset) { + builder.addFieldStruct(2, expressionIdOffset, Null) } - static endVisualizationContext(builder: Builder): Offset { - const offset = builder.endObject() + static endVisualizationContext(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // visualizationId builder.requiredField(offset, 6) // contextId builder.requiredField(offset, 8) // expressionId @@ -1223,14 +1267,14 @@ export class VisualizationContext implements Table { static createVisualizationContext( builder: Builder, - createVisualizationId: CreateOffset, - createContextId: CreateOffset, - createExpressionId: CreateOffset, - ): Offset { + createVisualizationId: CreateOffset, + createContextId: CreateOffset, + createExpressionId: CreateOffset, + ): Offset { VisualizationContext.startVisualizationContext(builder) - VisualizationContext.addVisualizationId(builder, createVisualizationId?.(builder) ?? NULL_PTR) - VisualizationContext.addContextId(builder, createContextId?.(builder) ?? NULL_PTR) - VisualizationContext.addExpressionId(builder, createExpressionId?.(builder) ?? NULL_PTR) + VisualizationContext.addVisualizationId(builder, createVisualizationId?.(builder) ?? Null) + VisualizationContext.addContextId(builder, createContextId?.(builder) ?? Null) + VisualizationContext.addExpressionId(builder, createExpressionId?.(builder) ?? Null) return VisualizationContext.endVisualizationContext(builder) } } @@ -1302,15 +1346,18 @@ export class VisualizationUpdate implements Table { builder.startObject(2) } - static addVisualizationContext(builder: Builder, visualizationContextOffset: Offset) { - builder.addFieldOffset(0, visualizationContextOffset, 0) + static addVisualizationContext( + builder: Builder, + visualizationContextOffset: Offset, + ) { + builder.addFieldOffset(0, visualizationContextOffset, Null) } - static addData(builder: Builder, dataOffset: Offset) { - builder.addFieldOffset(1, dataOffset, 0) + static addData(builder: Builder, dataOffset: Offset) { + builder.addFieldOffset(1, dataOffset, Null) } - static createDataVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createDataVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1323,8 +1370,8 @@ export class VisualizationUpdate implements Table { builder.startVector(1, numElems, 1) } - static endVisualizationUpdate(builder: Builder): Offset { - const offset = builder.endObject() + static endVisualizationUpdate(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // visualizationContext builder.requiredField(offset, 6) // data return offset @@ -1332,9 +1379,9 @@ export class VisualizationUpdate implements Table { static createVisualizationUpdate( builder: Builder, - visualizationContextOffset: Offset, - dataOffset: Offset, - ): Offset { + visualizationContextOffset: Offset, + dataOffset: Offset, + ): Offset { VisualizationUpdate.startVisualizationUpdate(builder) VisualizationUpdate.addVisualizationContext(builder, visualizationContextOffset) VisualizationUpdate.addData(builder, dataOffset) @@ -1385,15 +1432,18 @@ export class Path implements Table { builder.startObject(2) } - static addRootId(builder: Builder, rootIdOffset: Offset) { - builder.addFieldStruct(0, rootIdOffset, 0) + static addRootId(builder: Builder, rootIdOffset: Offset) { + builder.addFieldStruct(0, rootIdOffset, Null) } - static addSegments(builder: Builder, segmentsOffset: Offset) { - builder.addFieldOffset(1, segmentsOffset, 0) + static addSegments(builder: Builder, segmentsOffset: Offset) { + builder.addFieldOffset(1, segmentsOffset, Null) } - static createSegmentsVector(builder: Builder, data: Offset[]): Offset { + static createSegmentsVector( + builder: Builder, + data: Offset[] | Offset[], + ): Offset { builder.startVector(4, data.length, 4) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1406,14 +1456,18 @@ export class Path implements Table { builder.startVector(4, numElems, 4) } - static endPath(builder: Builder): Offset { - const offset = builder.endObject() + static endPath(builder: Builder): Offset { + const offset = builder.endObject() return offset } - static createPath(builder: Builder, createRootId: CreateOffset, segmentsOffset: Offset): Offset { + static createPath( + builder: Builder, + createRootId: CreateOffset, + segmentsOffset: Offset, + ): Offset { Path.startPath(builder) - Path.addRootId(builder, createRootId?.(builder) ?? NULL_PTR) + Path.addRootId(builder, createRootId?.(builder) ?? Null) Path.addSegments(builder, segmentsOffset) return Path.endPath(builder) } @@ -1476,15 +1530,15 @@ export class WriteFileCommand implements Table { builder.startObject(2) } - static addPath(builder: Builder, pathOffset: Offset) { - builder.addFieldOffset(0, pathOffset, 0) + static addPath(builder: Builder, pathOffset: Offset) { + builder.addFieldOffset(0, pathOffset, Null) } - static addContents(builder: Builder, contentsOffset: Offset) { - builder.addFieldOffset(1, contentsOffset, 0) + static addContents(builder: Builder, contentsOffset: Offset) { + builder.addFieldOffset(1, contentsOffset, Null) } - static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1497,16 +1551,16 @@ export class WriteFileCommand implements Table { builder.startVector(1, numElems, 1) } - static endWriteFileCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endWriteFileCommand(builder: Builder): Offset { + const offset = builder.endObject() return offset } static createWriteFileCommand( builder: Builder, - pathOffset: Offset, - contentsOffset: Offset, - ): Offset { + pathOffset: Offset, + contentsOffset: Offset, + ): Offset { WriteFileCommand.startWriteFileCommand(builder) WriteFileCommand.addPath(builder, pathOffset) WriteFileCommand.addContents(builder, contentsOffset) @@ -1550,16 +1604,19 @@ export class ReadFileCommand implements Table { builder.startObject(1) } - static addPath(builder: Builder, pathOffset: Offset) { - builder.addFieldOffset(0, pathOffset, 0) + static addPath(builder: Builder, pathOffset: Offset) { + builder.addFieldOffset(0, pathOffset, Null) } - static endReadFileCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endReadFileCommand(builder: Builder): Offset { + const offset = builder.endObject() return offset } - static createReadFileCommand(builder: Builder, pathOffset: Offset): Offset { + static createReadFileCommand( + builder: Builder, + pathOffset: Offset, + ): Offset { ReadFileCommand.startReadFileCommand(builder) ReadFileCommand.addPath(builder, pathOffset) return ReadFileCommand.endReadFileCommand(builder) @@ -1618,11 +1675,11 @@ export class FileContentsReply implements Table { builder.startObject(1) } - static addContents(builder: Builder, contentsOffset: Offset) { - builder.addFieldOffset(0, contentsOffset, 0) + static addContents(builder: Builder, contentsOffset: Offset) { + builder.addFieldOffset(0, contentsOffset, Null) } - static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createContentsVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1635,12 +1692,15 @@ export class FileContentsReply implements Table { builder.startVector(1, numElems, 1) } - static endFileContentsReply(builder: Builder): Offset { - const offset = builder.endObject() + static endFileContentsReply(builder: Builder): Offset { + const offset = builder.endObject() return offset } - static createFileContentsReply(builder: Builder, contentsOffset: Offset): Offset { + static createFileContentsReply( + builder: Builder, + contentsOffset: Offset, + ): Offset { FileContentsReply.startFileContentsReply(builder) FileContentsReply.addContents(builder, contentsOffset) return FileContentsReply.endFileContentsReply(builder) @@ -1714,8 +1774,8 @@ export class WriteBytesCommand implements Table { builder.startObject(4) } - static addPath(builder: Builder, pathOffset: Offset) { - builder.addFieldOffset(0, pathOffset, 0) + static addPath(builder: Builder, pathOffset: Offset) { + builder.addFieldOffset(0, pathOffset, Null) } static addByteOffset(builder: Builder, byteOffset: bigint) { @@ -1726,11 +1786,11 @@ export class WriteBytesCommand implements Table { builder.addFieldInt8(2, +overwriteExisting, +false) } - static addBytes(builder: Builder, bytesOffset: Offset) { - builder.addFieldOffset(3, bytesOffset, 0) + static addBytes(builder: Builder, bytesOffset: Offset) { + builder.addFieldOffset(3, bytesOffset, Null) } - static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1743,8 +1803,8 @@ export class WriteBytesCommand implements Table { builder.startVector(1, numElems, 1) } - static endWriteBytesCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endWriteBytesCommand(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // path builder.requiredField(offset, 10) // bytes return offset @@ -1752,11 +1812,11 @@ export class WriteBytesCommand implements Table { static createWriteBytesCommand( builder: Builder, - pathOffset: Offset, + pathOffset: Offset, byteOffset: bigint, overwriteExisting: boolean, - bytesOffset: Offset, - ): Offset { + bytesOffset: Offset, + ): Offset { WriteBytesCommand.startWriteBytesCommand(builder) WriteBytesCommand.addPath(builder, pathOffset) WriteBytesCommand.addByteOffset(builder, byteOffset) @@ -1804,17 +1864,20 @@ export class WriteBytesReply implements Table { builder.startObject(1) } - static addChecksum(builder: Builder, checksumOffset: Offset) { - builder.addFieldOffset(0, checksumOffset, 0) + static addChecksum(builder: Builder, checksumOffset: Offset) { + builder.addFieldOffset(0, checksumOffset, Null) } - static endWriteBytesReply(builder: Builder): Offset { - const offset = builder.endObject() + static endWriteBytesReply(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // checksum return offset } - static createWriteBytesReply(builder: Builder, checksumOffset: Offset): Offset { + static createWriteBytesReply( + builder: Builder, + checksumOffset: Offset, + ): Offset { WriteBytesReply.startWriteBytesReply(builder) WriteBytesReply.addChecksum(builder, checksumOffset) return WriteBytesReply.endWriteBytesReply(builder) @@ -1859,17 +1922,20 @@ export class ReadBytesCommand implements Table { builder.startObject(1) } - static addSegment(builder: Builder, segmentOffset: Offset) { - builder.addFieldOffset(0, segmentOffset, 0) + static addSegment(builder: Builder, segmentOffset: Offset) { + builder.addFieldOffset(0, segmentOffset, Null) } - static endReadBytesCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endReadBytesCommand(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // segment return offset } - static createReadBytesCommand(builder: Builder, segmentOffset: Offset): Offset { + static createReadBytesCommand( + builder: Builder, + segmentOffset: Offset, + ): Offset { ReadBytesCommand.startReadBytesCommand(builder) ReadBytesCommand.addSegment(builder, segmentOffset) return ReadBytesCommand.endReadBytesCommand(builder) @@ -1926,15 +1992,15 @@ export class ReadBytesReply implements Table { builder.startObject(2) } - static addChecksum(builder: Builder, checksumOffset: Offset) { - builder.addFieldOffset(0, checksumOffset, 0) + static addChecksum(builder: Builder, checksumOffset: Offset) { + builder.addFieldOffset(0, checksumOffset, Null) } - static addBytes(builder: Builder, bytesOffset: Offset) { - builder.addFieldOffset(1, bytesOffset, 0) + static addBytes(builder: Builder, bytesOffset: Offset) { + builder.addFieldOffset(1, bytesOffset, Null) } - static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -1947,8 +2013,8 @@ export class ReadBytesReply implements Table { builder.startVector(1, numElems, 1) } - static endReadBytesReply(builder: Builder): Offset { - const offset = builder.endObject() + static endReadBytesReply(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // checksum builder.requiredField(offset, 6) // bytes return offset @@ -1956,9 +2022,9 @@ export class ReadBytesReply implements Table { static createReadBytesReply( builder: Builder, - checksumOffset: Offset, - bytesOffset: Offset, - ): Offset { + checksumOffset: Offset, + bytesOffset: Offset, + ): Offset { ReadBytesReply.startReadBytesReply(builder) ReadBytesReply.addChecksum(builder, checksumOffset) ReadBytesReply.addBytes(builder, bytesOffset) @@ -2007,17 +2073,20 @@ export class ChecksumBytesCommand implements Table { builder.startObject(1) } - static addSegment(builder: Builder, segmentOffset: Offset) { - builder.addFieldOffset(0, segmentOffset, 0) + static addSegment(builder: Builder, segmentOffset: Offset) { + builder.addFieldOffset(0, segmentOffset, Null) } - static endChecksumBytesCommand(builder: Builder): Offset { - const offset = builder.endObject() + static endChecksumBytesCommand(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // segment return offset } - static createChecksumBytesCommand(builder: Builder, segmentOffset: Offset): Offset { + static createChecksumBytesCommand( + builder: Builder, + segmentOffset: Offset, + ): Offset { ChecksumBytesCommand.startChecksumBytesCommand(builder) ChecksumBytesCommand.addSegment(builder, segmentOffset) return ChecksumBytesCommand.endChecksumBytesCommand(builder) @@ -2062,17 +2131,20 @@ export class ChecksumBytesReply implements Table { builder.startObject(1) } - static addChecksum(builder: Builder, checksumOffset: Offset) { - builder.addFieldOffset(0, checksumOffset, 0) + static addChecksum(builder: Builder, checksumOffset: Offset) { + builder.addFieldOffset(0, checksumOffset, Null) } - static endChecksumBytesReply(builder: Builder): Offset { - const offset = builder.endObject() + static endChecksumBytesReply(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // checksum return offset } - static createChecksumBytesReply(builder: Builder, checksumOffset: Offset): Offset { + static createChecksumBytesReply( + builder: Builder, + checksumOffset: Offset, + ): Offset { ChecksumBytesReply.startChecksumBytesReply(builder) ChecksumBytesReply.addChecksum(builder, checksumOffset) return ChecksumBytesReply.endChecksumBytesReply(builder) @@ -2122,11 +2194,11 @@ export class EnsoDigest implements Table { builder.startObject(1) } - static addBytes(builder: Builder, bytesOffset: Offset) { - builder.addFieldOffset(0, bytesOffset, 0) + static addBytes(builder: Builder, bytesOffset: Offset) { + builder.addFieldOffset(0, bytesOffset, Null) } - static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { + static createBytesVector(builder: Builder, data: number[] | Uint8Array): Offset { builder.startVector(1, data.length, 1) // An iterator is more type-safe, but less performant. for (let i = data.length - 1; i >= 0; i -= 1) { @@ -2139,13 +2211,13 @@ export class EnsoDigest implements Table { builder.startVector(1, numElems, 1) } - static endEnsoDigest(builder: Builder): Offset { - const offset = builder.endObject() + static endEnsoDigest(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // bytes return offset } - static createEnsoDigest(builder: Builder, bytesOffset: Offset): Offset { + static createEnsoDigest(builder: Builder, bytesOffset: Offset): Offset { EnsoDigest.startEnsoDigest(builder) EnsoDigest.addBytes(builder, bytesOffset) return EnsoDigest.endEnsoDigest(builder) @@ -2189,8 +2261,8 @@ export class FileSegment implements Table { builder.startObject(3) } - static addPath(builder: Builder, pathOffset: Offset) { - builder.addFieldOffset(0, pathOffset, 0) + static addPath(builder: Builder, pathOffset: Offset) { + builder.addFieldOffset(0, pathOffset, Null) } static addByteOffset(builder: Builder, byteOffset: bigint) { @@ -2201,18 +2273,18 @@ export class FileSegment implements Table { builder.addFieldInt64(2, length, 0n) } - static endFileSegment(builder: Builder): Offset { - const offset = builder.endObject() + static endFileSegment(builder: Builder): Offset { + const offset = builder.endObject() builder.requiredField(offset, 4) // path return offset } static createFileSegment( builder: Builder, - pathOffset: Offset, + pathOffset: Offset, byteOffset: bigint, length: bigint, - ): Offset { + ): Offset { FileSegment.startFileSegment(builder) FileSegment.addPath(builder, pathOffset) FileSegment.addByteOffset(builder, byteOffset) diff --git a/app/gui2/shared/dataServer.ts b/app/gui2/shared/dataServer.ts index 02d4659d7dc..748ce5c2e52 100644 --- a/app/gui2/shared/dataServer.ts +++ b/app/gui2/shared/dataServer.ts @@ -25,6 +25,8 @@ import { WriteBytesCommand, WriteBytesReply, WriteFileCommand, + type AnyInboundPayload, + type Offset, type Table, } from './binaryProtocol' import { uuidFromBits, uuidToBits } from './uuid' @@ -104,7 +106,7 @@ export class DataServer extends ObservableV2 { protected send( builder: Builder, payloadType: InboundPayload, - payloadOffset: number, + payloadOffset: Offset, ): Promise { const messageUuid = random.uuidv4() const rootTable = InboundMessage.createInboundMessage( diff --git a/app/gui2/shared/dataServer/mock.ts b/app/gui2/shared/dataServer/mock.ts new file mode 100644 index 00000000000..b418834d8b7 --- /dev/null +++ b/app/gui2/shared/dataServer/mock.ts @@ -0,0 +1,207 @@ +import { createSHA3 } from 'hash-wasm' +import * as random from 'lib0/random' +import { + Builder, + ByteBuffer, + ChecksumBytesCommand, + ChecksumBytesReply, + EnsoDigest, + EnsoUUID, + ErrorPayload, + Error as ErrorResponse, + FileContentsReply, + InboundMessage, + InboundPayload, + InitSessionCommand, + None, + Null, + OutboundMessage, + OutboundPayload, + Path, + ReadBytesCommand, + ReadBytesReply, + ReadFileCommand, + Success, + WriteBytesCommand, + WriteFileCommand, + type AnyOutboundPayload, + type Offset, + type Table, +} from '../binaryProtocol' +import { LanguageServerErrorCode } from '../languageServerTypes' +import { uuidToBits } from '../uuid' + +const sha3 = createSHA3(224) + +function pathSegments(path: Path) { + return Array.from({ length: path.segmentsLength() }, (_, i) => path.segments(i)) +} + +function createError(builder: Builder, code: LanguageServerErrorCode, message: string) { + const messageOffset = builder.createString(message) + return { + type: OutboundPayload.ERROR, + offset: ErrorResponse.createError(builder, code, messageOffset, ErrorPayload.NONE, Null), + } +} + +function createMessageId(builder: Builder) { + const messageUuid = random.uuidv4() + const [leastSigBits, mostSigBits] = uuidToBits(messageUuid) + return EnsoUUID.createEnsoUUID(builder, leastSigBits, mostSigBits) +} + +function createCorrelationId(messageId: EnsoUUID) { + return (builder: Builder) => + EnsoUUID.createEnsoUUID(builder, messageId.leastSigBits(), messageId.mostSigBits()) +} + +const PAYLOAD_CONSTRUCTOR = { + [InboundPayload.NONE]: None, + [InboundPayload.INIT_SESSION_CMD]: InitSessionCommand, + [InboundPayload.WRITE_FILE_CMD]: WriteFileCommand, + [InboundPayload.READ_FILE_CMD]: ReadFileCommand, + [InboundPayload.WRITE_BYTES_CMD]: WriteBytesCommand, + [InboundPayload.READ_BYTES_CMD]: ReadBytesCommand, + [InboundPayload.CHECKSUM_BYTES_CMD]: ChecksumBytesCommand, +} satisfies Record Table> + +export function mockDataWSHandler( + readFile: (segments: string[]) => Promise, + cb?: (send: (data: string | Blob | ArrayBufferLike | ArrayBufferView) => void) => void, +) { + let sentSend = false + return async ( + message: string | Blob | ArrayBufferLike | ArrayBufferView, + send: (data: string | Blob | ArrayBufferLike | ArrayBufferView) => void, + ) => { + if (!sentSend) cb?.(send) + sentSend = true + if (!(message instanceof ArrayBuffer)) return + const binaryMessage = InboundMessage.getRootAsInboundMessage(new ByteBuffer(message)) + const payloadType = binaryMessage.payloadType() + const payload = binaryMessage.payload(new PAYLOAD_CONSTRUCTOR[payloadType]()) + if (!payload) return + const builder = new Builder() + let response: { type: OutboundPayload; offset: Offset } | undefined + switch (payloadType) { + case InboundPayload.NONE: { + response = { + type: OutboundPayload.NONE, + offset: Null, + } + break + } + case InboundPayload.INIT_SESSION_CMD: { + response = { + type: OutboundPayload.SUCCESS, + offset: Success.createSuccess(builder), + } + break + } + case InboundPayload.WRITE_FILE_CMD: { + response = createError( + builder, + LanguageServerErrorCode.AccessDenied, + 'Cannot WriteFile to a read-only mock.', + ) + break + } + case InboundPayload.READ_FILE_CMD: { + const payload_ = payload as ReadFileCommand + const path = payload_.path() + if (!path) { + response = createError(builder, LanguageServerErrorCode.NotFile, 'Invalid Path') + break + } + const file = await readFile(pathSegments(path)) + if (!file) { + response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') + break + } + const contentOffset = builder.createString(file) + response = { + type: OutboundPayload.FILE_CONTENTS_REPLY, + offset: FileContentsReply.createFileContentsReply(builder, contentOffset), + } + break + } + case InboundPayload.WRITE_BYTES_CMD: { + response = createError( + builder, + LanguageServerErrorCode.AccessDenied, + 'Cannot WriteBytes to a read-only mock.', + ) + break + } + case InboundPayload.READ_BYTES_CMD: { + const payload_ = payload as ReadBytesCommand + const segment = payload_.segment() + const path = segment && segment.path() + if (!path) { + response = createError( + builder, + LanguageServerErrorCode.NotFile, + 'Invalid FileSegment or Path', + ) + break + } + const file = await readFile(pathSegments(path)) + if (!file) { + response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') + break + } + const start = Number(segment.byteOffset()) + const slice = file.slice(start, start + Number(segment.length())) + const contentOffset = builder.createString(slice) + const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary') + const checksumBytesOffset = EnsoDigest.createBytesVector(builder, digest) + const checksumOffset = EnsoDigest.createEnsoDigest(builder, checksumBytesOffset) + response = { + type: OutboundPayload.READ_BYTES_REPLY, + offset: ReadBytesReply.createReadBytesReply(builder, checksumOffset, contentOffset), + } + break + } + case InboundPayload.CHECKSUM_BYTES_CMD: { + const payload_ = payload as ChecksumBytesCommand + const segment = payload_.segment() + const path = segment && segment.path() + if (!path) { + response = createError( + builder, + LanguageServerErrorCode.NotFile, + 'Invalid FileSegment or Path', + ) + break + } + const file = await readFile(pathSegments(path)) + if (!file) { + response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') + break + } + const start = Number(segment.byteOffset()) + const slice = file.slice(start, start + Number(segment.length())) + const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary') + const bytesOffset = EnsoDigest.createBytesVector(builder, digest) + const checksumOffset = EnsoDigest.createEnsoDigest(builder, bytesOffset) + response = { + type: OutboundPayload.CHECKSUM_BYTES_REPLY, + offset: ChecksumBytesReply.createChecksumBytesReply(builder, checksumOffset), + } + break + } + } + if (!response) return + const correlationUuid = binaryMessage.messageId() + if (!correlationUuid) return + const rootTable = OutboundMessage.createOutboundMessage( + builder, + createMessageId, + createCorrelationId(correlationUuid), + response.type, + response.offset, + ) + send(builder.finish(rootTable).toArrayBuffer()) + } +} diff --git a/app/gui2/shared/websocket.ts b/app/gui2/shared/websocket.ts index d5f87c0691b..8537fab4c1c 100644 --- a/app/gui2/shared/websocket.ts +++ b/app/gui2/shared/websocket.ts @@ -46,6 +46,7 @@ const messageReconnectTimeout = 30000 const setupWS = (wsclient: WebsocketClient, ws?: WebSocket | null | undefined) => { if (wsclient.shouldConnect && (wsclient.ws === null || ws)) { + // deepcode ignore MissingClose: This is closed by `WebsocketClient` below. const websocket = ws ?? new WebSocket(wsclient.url) const binaryType = wsclient.binaryType let pingTimeout: any = null diff --git a/app/gui2/src/components/CircularMenu.vue b/app/gui2/src/components/CircularMenu.vue index 0e818f0a9bd..b245b838997 100644 --- a/app/gui2/src/components/CircularMenu.vue +++ b/app/gui2/src/components/CircularMenu.vue @@ -18,18 +18,21 @@ const emit = defineEmits<{ diff --git a/app/gui2/src/components/ComponentBrowser.vue b/app/gui2/src/components/ComponentBrowser.vue index 403a0330603..0e1233e9fc6 100644 --- a/app/gui2/src/components/ComponentBrowser.vue +++ b/app/gui2/src/components/ComponentBrowser.vue @@ -369,7 +369,7 @@ const handler = componentBrowserBindings.handler({ @wheel.stop.passive @scroll="updateScroll" > -
+
{ + junctions.points.slice(1).forEach((j, i) => { const d = j.sub(prev) const radius = Math.min(junctions.maxRadius, Math.abs(d.x), Math.abs(d.y)) const signX = Math.sign(d.x) diff --git a/app/gui2/src/components/GraphEditor/GraphNode.vue b/app/gui2/src/components/GraphEditor/GraphNode.vue index fe7f8eb6189..78789161964 100644 --- a/app/gui2/src/components/GraphEditor/GraphNode.vue +++ b/app/gui2/src/components/GraphEditor/GraphNode.vue @@ -271,6 +271,7 @@ const handlePortClick = useDoubleClick( --output-port-overlap: 0.2px; --output-port-hover-width: 8px; } + .outputPort, .outputPortHoverArea { x: calc(0px - var(--output-port-width) / 2); @@ -409,6 +410,7 @@ const handlePortClick = useDoubleClick( .GraphNode.selected .selection:hover:before { opacity: 0.3; } + .binding { user-select: none; margin-right: 10px; diff --git a/app/gui2/src/components/GraphEditor/upload.ts b/app/gui2/src/components/GraphEditor/upload.ts index 28013681808..5aa83db2f70 100644 --- a/app/gui2/src/components/GraphEditor/upload.ts +++ b/app/gui2/src/components/GraphEditor/upload.ts @@ -56,9 +56,8 @@ export class Uploader { position: Vec2, stackItem: StackItem, ): Promise { - const projectRootId = await contentRoots.then((roots) => - roots.find((root) => root.type == 'Project'), - ) + const roots = await contentRoots + const projectRootId = roots.find((root) => root.type == 'Project') if (!projectRootId) throw new Error('Unable to find project root, uploading not possible.') const instance = new Uploader( await rpc, diff --git a/app/gui2/src/components/VisualizationContainer.vue b/app/gui2/src/components/VisualizationContainer.vue index 57a134d69f4..270f2695a2f 100644 --- a/app/gui2/src/components/VisualizationContainer.vue +++ b/app/gui2/src/components/VisualizationContainer.vue @@ -118,7 +118,7 @@ const resizeBottomRight = usePointer((pos, _, type) => { @pointerdown.stop="config.hide()" @click="config.hide()" > - +
@@ -127,7 +127,11 @@ const resizeBottomRight = usePointer((pos, _, type) => { @pointerdown.stop="(config.fullscreen = !config.fullscreen), blur($event)" @click.prevent="!isClick($event) && (config.fullscreen = !config.fullscreen)" > - +
() -const emit = defineEmits<{ - 'update:preprocessor': [module: string, method: string, ...args: string[]] -}>() /** GeoMap Visualization. */ -/** - * Mapbox API access token. - * All the limits of API are listed here: https://docs.mapbox.com/api/#rate-limits - */ +/** Mapbox API access token. + * All the limits of API are listed here: https://docs.mapbox.com/api/#rate-limits */ const TOKEN = 'pk.eyJ1IjoiZW5zby1vcmciLCJhIjoiY2tmNnh5MXh2MGlyOTJ5cWdubnFxbXo4ZSJ9.3KdAcCiiXJcSM18nwk09-Q' const SCATTERPLOT_LAYER = 'Scatterplot_Layer' @@ -142,11 +141,6 @@ watchPostEffect(() => { } }) -onMounted(() => { - dataPoints.value = [] - emit('update:preprocessor', 'Standard.Visualization.Geo_Map', 'process_to_json_text') -}) - onUnmounted(() => deckgl.value?.finalize()) /** diff --git a/app/gui2/src/stores/project.ts b/app/gui2/src/stores/project.ts index f622eb69e31..27811ad8c17 100644 --- a/app/gui2/src/stores/project.ts +++ b/app/gui2/src/stores/project.ts @@ -55,14 +55,12 @@ interface LsUrls { function resolveLsUrl(config: GuiConfig): LsUrls { const engine = config.engine if (engine == null) throw new Error('Missing engine configuration') - if (engine.rpcUrl != null && engine.dataUrl != null) { return { rpcUrl: engine.rpcUrl, dataUrl: engine.dataUrl, } } - throw new Error('Incomplete engine configuration') } @@ -77,7 +75,6 @@ async function initializeLsRpcConnection( const requestManager = new RequestManager([transport]) const client = new Client(requestManager) const connection = new LanguageServer(client) - const initialization = await lsRpcWithRetries(() => connection.initProtocolConnection(clientId), { onBeforeRetry: (error, _, delay) => { console.warn( diff --git a/app/gui2/src/stores/visualization/compilerMessaging.ts b/app/gui2/src/stores/visualization/compilerMessaging.ts index 528ed7e9c87..8c92b6b5120 100644 --- a/app/gui2/src/stores/visualization/compilerMessaging.ts +++ b/app/gui2/src/stores/visualization/compilerMessaging.ts @@ -108,6 +108,7 @@ export async function compile(path: string, projectRoot: Opt, data: DataSe worker.addEventListener( 'message', async ( + // deepcode ignore InsufficientPostmessageValidation: This event is from a Worker, not another page. event: MessageEvent< // === Responses === | CompilationResultResponse @@ -157,6 +158,7 @@ export async function compile(path: string, projectRoot: Opt, data: DataSe switch (url.protocol) { case 'http:': case 'https:': { + // deepcode ignore Ssrf: This is a frontend. const response = await fetch(url) if (response.ok) { postMessage(worker_, { diff --git a/app/gui2/src/util/measurement.ts b/app/gui2/src/util/measurement.ts index 87e4239c03c..f685d18c04d 100644 --- a/app/gui2/src/util/measurement.ts +++ b/app/gui2/src/util/measurement.ts @@ -1,11 +1,6 @@ -function error(message: string): never { - throw new Error(message) -} - let _measureContext: CanvasRenderingContext2D | undefined function getMeasureContext() { - return (_measureContext ??= - document.createElement('canvas').getContext('2d') ?? error('Could not get canvas 2D context.')) + return (_measureContext ??= document.createElement('canvas').getContext('2d')!) } /** Helper function to get text width to make sure that labels on the x axis do not overlap, diff --git a/app/gui2/src/util/mutable.ts b/app/gui2/src/util/mutable.ts deleted file mode 100644 index 16c711140a3..00000000000 --- a/app/gui2/src/util/mutable.ts +++ /dev/null @@ -1,2 +0,0 @@ -/** Removes `readonly` from all keys in a type. UNSAFE. */ -export type UnsafeMutable = { -readonly [K in keyof T]: T[K] } diff --git a/app/gui2/src/util/net.ts b/app/gui2/src/util/net.ts index fce269ab8fb..cb28665d2c3 100644 --- a/app/gui2/src/util/net.ts +++ b/app/gui2/src/util/net.ts @@ -1,4 +1,3 @@ -import { Err, Ok, ResultError, rejectionToResult, type Result } from '@/util/result' import { WebSocketTransport } from '@open-rpc/client-js' import type { IJSONRPCNotificationResponse, @@ -10,6 +9,7 @@ import { wait } from 'lib0/promise' import { LsRpcError } from 'shared/languageServer' import type { Notifications } from 'shared/languageServerTypes' import { WebsocketClient } from 'shared/websocket' +import { Err, Ok, ResultError, rejectionToResult, type Result } from './result' export interface BackoffOptions { maxRetries?: number @@ -101,8 +101,8 @@ export function createWebsocketClient( } } -interface MockTransportData { - (method: string, params: any, transport: MockTransport): Promise +export interface MockTransportData { + (method: Methods, params: any, transport: MockTransport): Promise } export class MockTransport extends Transport { @@ -111,8 +111,8 @@ export class MockTransport extends Transport { super() } - static addMock(name: string, data: MockTransportData) { - MockTransport.mocks.set(name, data) + static addMock(name: string, data: MockTransportData) { + MockTransport.mocks.set(name, data as any) } connect(): Promise { return Promise.resolve() @@ -168,6 +168,7 @@ export class MockWebSocket extends EventTarget implements WebSocket { super() this.addEventListener('open', (ev) => this.onopen?.(ev)) this.addEventListener('close', (ev) => this.onclose?.(ev as CloseEvent)) + // deepcode ignore InsufficientPostmessageValidation: This is not a `postMessage`. this.addEventListener('message', (ev) => this.onmessage?.(ev as MessageEvent)) this.addEventListener('error', (ev) => this.onerror?.(ev)) setTimeout(() => this.dispatchEvent(new Event('open')), 0) diff --git a/app/gui2/src/views/ProjectView.vue b/app/gui2/src/views/ProjectView.vue index af0e6243779..795972d797d 100644 --- a/app/gui2/src/views/ProjectView.vue +++ b/app/gui2/src/views/ProjectView.vue @@ -3,7 +3,7 @@ import GraphEditor from '@/components/GraphEditor.vue' diff --git a/app/gui2/stories/MockFSWrapper.vue b/app/gui2/stories/MockFSWrapper.vue index 664eaf27d97..6b298d1ecf3 100644 --- a/app/gui2/stories/MockFSWrapper.vue +++ b/app/gui2/stories/MockFSWrapper.vue @@ -2,35 +2,8 @@ import { useProjectStore } from '@/stores/project' import { mockFsDirectoryHandle } from '@/util/convert/fsAccess' import { MockWebSocket, type WebSocketHandler } from '@/util/net' -import { createSHA3 } from 'hash-wasm' -import * as random from 'lib0/random' -import { - Builder, - ByteBuffer, - ChecksumBytesCommand, - ChecksumBytesReply, - EnsoDigest, - EnsoUUID, - ErrorPayload, - Error as ErrorResponse, - FileContentsReply, - InboundMessage, - InboundPayload, - InitSessionCommand, - None, - OutboundMessage, - OutboundPayload, - Path, - ReadBytesCommand, - ReadBytesReply, - ReadFileCommand, - Success, - WriteBytesCommand, - WriteFileCommand, - type Table, -} from 'shared/binaryProtocol' -import { LanguageServerErrorCode, type Path as LSPath } from 'shared/languageServerTypes' -import { uuidToBits } from 'shared/uuid' +import { mockDataWSHandler } from 'shared/dataServer/mock' +import { type Path as LSPath } from 'shared/languageServerTypes' import { watchEffect } from 'vue' const projectStore = useProjectStore() @@ -46,54 +19,6 @@ const props = defineProps<{ prefix?: string[] | undefined }>() -const PAYLOAD_CONSTRUCTOR = { - [InboundPayload.NONE]: None, - [InboundPayload.INIT_SESSION_CMD]: InitSessionCommand, - [InboundPayload.WRITE_FILE_CMD]: WriteFileCommand, - [InboundPayload.READ_FILE_CMD]: ReadFileCommand, - [InboundPayload.WRITE_BYTES_CMD]: WriteBytesCommand, - [InboundPayload.READ_BYTES_CMD]: ReadBytesCommand, - [InboundPayload.CHECKSUM_BYTES_CMD]: ChecksumBytesCommand, -} satisfies Record Table> - -const sha3 = createSHA3(224) - -function pathSegments(path: Path) { - return Array.from({ length: path.segmentsLength() }, (_, i) => path.segments(i)) -} - -async function readFile(dir: FileSystemDirectoryHandle, segments: string[]) { - if (!segments.length) return - try { - for (const segment of segments.slice(0, -1)) { - dir = await dir.getDirectoryHandle(segment) - } - const file = await dir.getFileHandle(segments.at(-1)!) - return await file.getFile() - } catch { - return - } -} - -function createError(builder: Builder, code: LanguageServerErrorCode, message: string) { - const messageOffset = builder.createString(message) - return { - type: OutboundPayload.ERROR, - offset: ErrorResponse.createError(builder, code, messageOffset, ErrorPayload.NONE, 0), - } -} - -function createMessageId(builder: Builder) { - const messageUuid = random.uuidv4() - const [leastSigBits, mostSigBits] = uuidToBits(messageUuid) - return EnsoUUID.createEnsoUUID(builder, leastSigBits, mostSigBits) -} - -function createCorrelationId(messageId: EnsoUUID) { - return (builder: Builder) => - EnsoUUID.createEnsoUUID(builder, messageId.leastSigBits(), messageId.mostSigBits()) -} - let resolveDataWsHandler: ((handler: WebSocketHandler) => void) | undefined let dataWsHandler: Promise = new Promise((resolve) => { resolveDataWsHandler = resolve @@ -145,134 +70,24 @@ watchEffect(async (onCleanup) => { ls.emit('file/event', [{ kind: 'Removed', path }]), ) }) - setDataWsHandler(async (message, send) => { - if (!(message instanceof ArrayBuffer)) return - const binaryMessage = InboundMessage.getRootAsInboundMessage(new ByteBuffer(message)) - const payloadType = binaryMessage.payloadType() - const payload = binaryMessage.payload(new PAYLOAD_CONSTRUCTOR[payloadType]()) - if (!payload) return - const builder = new Builder() - let response: { type: OutboundPayload; offset: number } | undefined - switch (payloadType) { - case InboundPayload.NONE: { - response = { - type: OutboundPayload.NONE, - offset: 0, + setDataWsHandler( + mockDataWSHandler(async (segments) => { + segments = segments.slice(prefixLength) + if (!segments.length) return + let file + try { + let dir = directory + for (const segment of segments.slice(0, -1)) { + dir = await dir.getDirectoryHandle(segment) } - break + const fileHandle = await dir.getFileHandle(segments.at(-1)!) + file = await fileHandle.getFile() + } catch { + return } - case InboundPayload.INIT_SESSION_CMD: { - response = { - type: OutboundPayload.SUCCESS, - offset: Success.createSuccess(builder), - } - break - } - case InboundPayload.WRITE_FILE_CMD: { - response = createError( - builder, - LanguageServerErrorCode.AccessDenied, - 'Cannot write to a read-only mock.', - ) - break - } - case InboundPayload.READ_FILE_CMD: { - const payload_ = payload as ReadFileCommand - const path = payload_.path() - if (!path) { - response = createError(builder, LanguageServerErrorCode.NotFile, 'Invalid Path') - break - } - const file = await readFile(directory, pathSegments(path).slice(prefixLength)) - if (!file) { - response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') - break - } - const contentOffset = builder.createString(await file.arrayBuffer()) - response = { - type: OutboundPayload.FILE_CONTENTS_REPLY, - offset: FileContentsReply.createFileContentsReply(builder, contentOffset), - } - break - } - case InboundPayload.WRITE_BYTES_CMD: { - response = createError( - builder, - LanguageServerErrorCode.AccessDenied, - 'Cannot write to a read-only mock.', - ) - break - } - case InboundPayload.READ_BYTES_CMD: { - const payload_ = payload as ReadBytesCommand - const segment = payload_.segment() - const path = segment && segment.path() - if (!path) { - response = createError( - builder, - LanguageServerErrorCode.NotFile, - 'Invalid FileSegment or Path', - ) - break - } - const file = await readFile(directory, pathSegments(path).slice(prefixLength)) - if (!file) { - response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') - break - } - const start = Number(segment.byteOffset()) - const slice = await file.slice(start, start + Number(segment.length())).arrayBuffer() - const contentOffset = builder.createString(slice) - const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary') - const checksumBytesOffset = EnsoDigest.createBytesVector(builder, digest) - const checksumOffset = EnsoDigest.createEnsoDigest(builder, checksumBytesOffset) - response = { - type: OutboundPayload.READ_BYTES_REPLY, - offset: ReadBytesReply.createReadBytesReply(builder, checksumOffset, contentOffset), - } - break - } - case InboundPayload.CHECKSUM_BYTES_CMD: { - const payload_ = payload as ChecksumBytesCommand - const segment = payload_.segment() - const path = segment && segment.path() - if (!path) { - response = createError( - builder, - LanguageServerErrorCode.NotFile, - 'Invalid FileSegment or Path', - ) - break - } - const file = await readFile(directory, pathSegments(path).slice(prefixLength)) - if (!file) { - response = createError(builder, LanguageServerErrorCode.FileNotFound, 'File not found') - break - } - const start = Number(segment.byteOffset()) - const slice = await file.slice(start, start + Number(segment.length())).arrayBuffer() - const digest = (await sha3).init().update(new Uint8Array(slice)).digest('binary') - const bytesOffset = EnsoDigest.createBytesVector(builder, digest) - const checksumOffset = EnsoDigest.createEnsoDigest(builder, bytesOffset) - response = { - type: OutboundPayload.CHECKSUM_BYTES_REPLY, - offset: ChecksumBytesReply.createChecksumBytesReply(builder, checksumOffset), - } - break - } - } - if (!response) return - const correlationUuid = binaryMessage.messageId() - if (!correlationUuid) return - const rootTable = OutboundMessage.createOutboundMessage( - builder, - createMessageId, - createCorrelationId(correlationUuid), - response.type, - response.offset, - ) - send(builder.finish(rootTable).toArrayBuffer()) - }) + return await file?.arrayBuffer() + }), + ) }) diff --git a/app/gui2/stories/MockProjectStoreWrapper.vue b/app/gui2/stories/MockProjectStoreWrapper.vue index d018f55c694..a2fc04d6273 100644 --- a/app/gui2/stories/MockProjectStoreWrapper.vue +++ b/app/gui2/stories/MockProjectStoreWrapper.vue @@ -38,10 +38,7 @@ function applyEdits(module: NonNullable, newText: st }) } -watchEffect(() => { - if (!projectStore.module) return - applyEdits(projectStore.module, props.modelValue) -}) +watchEffect(() => projectStore.module && applyEdits(projectStore.module, props.modelValue)) const text = computed(() => projectStore.module?.doc.contents) diff --git a/app/gui2/stories/setup.ts b/app/gui2/stories/setup.ts index ade841a4473..06354f17d9f 100644 --- a/app/gui2/stories/setup.ts +++ b/app/gui2/stories/setup.ts @@ -5,7 +5,7 @@ import { MockTransport } from '@/util/net' import type { QualifiedName } from '@/util/qualifiedName' import { Vec2 } from '@/util/vec2' import { defineSetupVue3 } from '@histoire/plugin-vue' -import { uuidv4 } from 'lib0/random' +import * as random from 'lib0/random' import { createPinia } from 'pinia' import type { LibraryComponentGroup, Uuid, response } from 'shared/languageServerTypes' import type { SuggestionEntry } from 'shared/languageServerTypes/suggestions' @@ -13,7 +13,7 @@ import { ref } from 'vue' import mockDb from './mockSuggestions.json' assert { type: 'json' } import './story.css' -const mockProjectId = uuidv4() as Uuid +const mockProjectId = random.uuidv4() as Uuid const standardBase = 'Standard.Base' as QualifiedName export function placeholderGroups(): LibraryComponentGroup[] { diff --git a/app/gui2/vite.config.ts b/app/gui2/vite.config.ts index 5dbb0903293..223e674036a 100644 --- a/app/gui2/vite.config.ts +++ b/app/gui2/vite.config.ts @@ -9,6 +9,7 @@ import { defineConfig, type Plugin } from 'vite' import topLevelAwait from 'vite-plugin-top-level-await' import * as tailwindConfig from '../ide-desktop/lib/dashboard/tailwind.config' import { createGatewayServer } from './ydoc-server' +const localServerPort = 8080 const projectManagerUrl = 'ws://127.0.0.1:30535' // https://vitejs.dev/config/ @@ -27,13 +28,16 @@ export default defineConfig({ }, resolve: { alias: { + ...(process.env.E2E === 'true' + ? { '/src/main.ts': fileURLToPath(new URL('./e2e/main.ts', import.meta.url)) } + : {}), shared: fileURLToPath(new URL('./shared', import.meta.url)), 'rust-ffi': fileURLToPath(new URL('./rust-ffi', import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, define: { - REDIRECT_OVERRIDE: JSON.stringify('http://localhost:8080'), + REDIRECT_OVERRIDE: JSON.stringify(`http://localhost:${localServerPort}`), PROJECT_MANAGER_URL: JSON.stringify(projectManagerUrl), IS_DEV_MODE: JSON.stringify(process.env.NODE_ENV !== 'production'), CLOUD_ENV: diff --git a/app/ide-desktop/lib/client/electron-builder-config.ts b/app/ide-desktop/lib/client/electron-builder-config.ts index 76e7d629936..9f52e2ee9fe 100644 --- a/app/ide-desktop/lib/client/electron-builder-config.ts +++ b/app/ide-desktop/lib/client/electron-builder-config.ts @@ -28,7 +28,6 @@ import BUILD_INFO from '../../../../build.json' assert { type: 'json' } // ============= /** The parts of the electron-builder configuration that we want to keep configurable. - * * @see `args` definition below for fields description. */ export interface Arguments { // The types come from a third-party API and cannot be changed. diff --git a/app/ide-desktop/lib/client/esbuild-config.ts b/app/ide-desktop/lib/client/esbuild-config.ts index 1ad89e1defa..1ccac264c53 100644 --- a/app/ide-desktop/lib/client/esbuild-config.ts +++ b/app/ide-desktop/lib/client/esbuild-config.ts @@ -18,7 +18,6 @@ import * as paths from './paths' * to the PM bundle root; * - `ENSO_BUILD_IDE_BUNDLED_ENGINE_VERSION` - version of the Engine (backend) that is bundled * along with this client build. - * * @see bundlerOptions */ export function bundlerOptionsFromEnv(): esbuild.BuildOptions { diff --git a/app/ide-desktop/lib/client/src/authentication.ts b/app/ide-desktop/lib/client/src/authentication.ts index 4634a4aaa78..e781bcfcc1f 100644 --- a/app/ide-desktop/lib/client/src/authentication.ts +++ b/app/ide-desktop/lib/client/src/authentication.ts @@ -57,16 +57,16 @@ * * To prepare the application to handle deep links: * - Register a custom URL protocol scheme with the OS (c.f., `electron-builder-config.ts`). - * - Define a listener for Electron {@link OPEN_URL_EVENT}s (c.f., {@link initOpenUrlListener}). + * - Define a listener for Electron `OPEN_URL_EVENT`s (c.f., {@link initOpenUrlListener}). * - Define a listener for {@link ipc.Channel.openDeepLink} events (c.f., `preload.ts`). * * Then when the user clicks on a deep link from an external source to the IDE: * - The OS redirects the user to the application. - * - The application emits an Electron {@link OPEN_URL_EVENT}. - * - The {@link OPEN_URL_EVENT} listener checks if the {@link URL} is a deep link. - * - If the {@link URL} is a deep link, the {@link OPEN_URL_EVENT} listener prevents Electron from + * - The application emits an Electron `OPEN_URL_EVENT`. + * - The `OPEN_URL_EVENT` listener checks if the {@link URL} is a deep link. + * - If the {@link URL} is a deep link, the `OPEN_URL_EVENT` listener prevents Electron from * handling the event. - * - The {@link OPEN_URL_EVENT} listener then emits an {@link ipc.Channel.openDeepLink} event. + * - The `OPEN_URL_EVENT` listener then emits an {@link ipc.Channel.openDeepLink} event. * - The {@link ipc.Channel.openDeepLink} listener registered by the dashboard receives the event. * Then it parses the {@link URL} from the event's {@link URL} argument. Then it uses the * {@link URL} to redirect the user to the dashboard, to the page specified in the {@link URL}'s @@ -93,7 +93,6 @@ const logger = contentConfig.logger /** Configure all the functionality that must be set up in the Electron app to support * authentication-related flows. Must be called in the Electron app `whenReady` event. - * * @param window - A function that returns the main Electron window. This argument is a lambda and * not a variable because the main window is not available when this function is called. This module * does not use the `window` until after it is initialized, so while the lambda may return `null` in @@ -137,7 +136,6 @@ function initOpenUrlListener(window: () => electron.BrowserWindow) { /** Handle the 'open-url' event by parsing the received URL, checking if it is a deep link, and * sending it to the appropriate BrowserWindow via IPC. - * * @param url - The URL to handle. * @param window - A function that returns the BrowserWindow to send the parsed URL to. */ export function onOpenUrl(url: URL, window: () => electron.BrowserWindow) { diff --git a/app/ide-desktop/lib/client/src/file-associations.ts b/app/ide-desktop/lib/client/src/file-associations.ts index fbaac1d6464..56cd7d71865 100644 --- a/app/ide-desktop/lib/client/src/file-associations.ts +++ b/app/ide-desktop/lib/client/src/file-associations.ts @@ -46,7 +46,6 @@ export const SOURCE_FILE_SUFFIX = fileAssociations.SOURCE_FILE_SUFFIX * * For example, this happens when the user double-clicks on a file in the file explorer and the * application is launched with the file path as an argument. - * * @param clientArgs - A list of arguments passed to the application, stripped from the initial * executable name and any electron dev mode arguments. * @returns The path to the file to open, or `null` if no file was specified. */ @@ -143,7 +142,6 @@ export function onFileOpened(event: electron.Event, path: string): string | null /** Set up the `open-file` event handler that might import a project and invoke the given callback, * if this IDE instance should load the project. See {@link onFileOpened} for more details. - * * @param setProjectToOpen - A function that will be called with the ID of the project to open. */ export function setOpenFileEventHandler(setProjectToOpen: (id: string) => void) { electron.app.on('open-file', (event, path) => { @@ -158,7 +156,6 @@ export function setOpenFileEventHandler(setProjectToOpen: (id: string) => void) * * Imports project if necessary. Returns the ID of the project to open. In case of an error, * the error message is displayed and the error is re-thrown. - * * @param openedFile - The path to the file to open. * @returns The ID of the project to open. * @throws {Error} if the project from the file cannot be opened or imported. */ diff --git a/app/ide-desktop/lib/client/src/index.ts b/app/ide-desktop/lib/client/src/index.ts index 4966e22965d..c09ea1f92d0 100644 --- a/app/ide-desktop/lib/client/src/index.ts +++ b/app/ide-desktop/lib/client/src/index.ts @@ -112,7 +112,6 @@ class App { * This method should be called before the application is ready, as it only * modifies the startup options. If the application is already initialized, * an error will be logged, and the method will have no effect. - * * @param projectId - The ID of the project to be opened on startup. */ setProjectToOpenOnStartup(projectId: string) { // Make sure that we are not initialized yet, as this method should be called before the diff --git a/app/ide-desktop/lib/client/src/log.ts b/app/ide-desktop/lib/client/src/log.ts index 1cf28124893..1a28794b06f 100644 --- a/app/ide-desktop/lib/client/src/log.ts +++ b/app/ide-desktop/lib/client/src/log.ts @@ -23,7 +23,6 @@ import * as paths from 'paths' * The path of the log file is {@link generateUniqueLogFileName automatically generated}. * * The log file is created in the {@link paths.LOGS_DIRECTORY logs directory} - * * @returns The full path of the log file. */ export function addFileLog(): string { const dirname = paths.LOGS_DIRECTORY @@ -35,7 +34,6 @@ export function addFileLog(): string { } /** Generate a unique log file name based on the current timestamp. - * * @returns The file name log file. */ export function generateUniqueLogFileName(): string { // Replace ':' with '-' because ':' is not allowed in file names. @@ -54,7 +52,6 @@ export class FileConsumer extends linkedDist.Consumer { private readonly logFileHandle: number /** Create a log consumer that writes to a file. - * * @param logPath - The path of the log file. Must be writeable. */ constructor(logPath: string) { super() diff --git a/app/ide-desktop/lib/client/src/paths.ts b/app/ide-desktop/lib/client/src/paths.ts index 7ce18b7a1f4..f5c66cc0ba6 100644 --- a/app/ide-desktop/lib/client/src/paths.ts +++ b/app/ide-desktop/lib/client/src/paths.ts @@ -20,7 +20,7 @@ export const APP_PATH = electron.app.getAppPath() /** The path of the directory in which the log files of IDE are stored. * - * This is based on the Electron `logs` directory, see {@link Electron.App.getPath}. */ + * This is based on the Electron `logs` directory, see {@link electron.app.getPath}. */ export const LOGS_DIRECTORY = electron.app.getPath('logs') /** The application assets, all files bundled with it. */ diff --git a/app/ide-desktop/lib/client/src/project-management.ts b/app/ide-desktop/lib/client/src/project-management.ts index 5d132d62c28..ef8fc4da367 100644 --- a/app/ide-desktop/lib/client/src/project-management.ts +++ b/app/ide-desktop/lib/client/src/project-management.ts @@ -31,7 +31,6 @@ const logger = config.logger /** Open a project from the given path. Path can be either a source file under the project root, * or the project bundle. If needed, the project will be imported into the Project Manager-enabled * location. - * * @returns Project ID (from Project Manager's metadata) identifying the imported project. * @throws {Error} if the path does not belong to a valid project. */ export function importProjectFromPath(openedPath: string): string { @@ -62,7 +61,6 @@ export function importProjectFromPath(openedPath: string): string { } /** Import the project from a bundle. - * * @returns Project ID (from Project Manager's metadata) identifying the imported project. */ export function importBundle(bundlePath: string): string { logger.log(`Importing project '${bundlePath}' from bundle.`) @@ -140,7 +138,6 @@ export async function uploadBundle(bundle: stream.Readable): Promise { } /** Import the project so it becomes visible to the Project Manager. - * * @param rootPath - The path to the project root. * @returns The project ID (from the Project Manager's metadata) identifying the imported project. * @throws {Error} if a race condition occurs when generating a unique project directory name. */ @@ -191,7 +188,6 @@ interface ProjectMetadata { * This function checks if the input object has the required properties and correct types * to match the {@link ProjectMetadata} interface. It can be used at runtime to validate that * a given object has the expected shape. - * * @param value - The object to check against the ProjectMetadata interface. * @returns A boolean value indicating whether the object matches * the {@link ProjectMetadata} interface. */ diff --git a/app/ide-desktop/lib/client/src/url-associations.ts b/app/ide-desktop/lib/client/src/url-associations.ts index d35b89bd81e..8eea8b78aae 100644 --- a/app/ide-desktop/lib/client/src/url-associations.ts +++ b/app/ide-desktop/lib/client/src/url-associations.ts @@ -44,7 +44,6 @@ export function registerAssociations() { * For example, this happens on Windows when the browser redirects user using our * [deep link scheme]{@link common.DEEP_LINK_SCHEME}. On macOS this is not used, as the OS * handles the URL opening by passing the `open-url` event to the application. - * * @param clientArgs - A list of arguments passed to the application, stripped from the initial * executable name and any electron dev mode arguments. * @returns The URL to open, or `null` if no file was specified. */ @@ -70,7 +69,6 @@ export function argsDenoteUrlOpenAttempt(clientArgs: string[]): URL | null { /** Handle the case where IDE is invoked with a URL to open. * * This happens on Windows when the browser redirects user using the deep link scheme. - * * @param openedUrl - The URL to open. */ export function handleOpenUrl(openedUrl: URL) { logger.log(`Opening URL '${openedUrl.toString()}'.`) @@ -103,7 +101,6 @@ export function handleOpenUrl(openedUrl: URL) { * This method registers the callback for both events. Note that on Windows it is necessary to * use {@link setAsUrlHandler} and {@link unsetAsUrlHandler} to ensure that the callback * is called. - * * @param callback - The callback to call when the application is requested to open a URL. */ export function registerUrlCallback(callback: (url: URL) => void) { // First, register the callback for the `open-url` event. This is used on macOS. @@ -155,7 +152,6 @@ export function registerUrlCallback(callback: (url: URL) => void) { * The mechanism is built on top of the Electron's * [instance lock]{@link https://www.electronjs.org/docs/api/app#apprequestsingleinstancelock} * functionality. - * * @throws {Error} An error if another instance of the application has already acquired the lock. */ export function setAsUrlHandler() { logger.log('Expecting URL callback, acquiring the lock.') diff --git a/app/ide-desktop/lib/client/tasks/computeHashes.mjs b/app/ide-desktop/lib/client/tasks/computeHashes.mjs index 2b93583335f..cc688e8cfc0 100644 --- a/app/ide-desktop/lib/client/tasks/computeHashes.mjs +++ b/app/ide-desktop/lib/client/tasks/computeHashes.mjs @@ -21,7 +21,8 @@ const CHECKSUM_TYPE = 'sha256' function getChecksum(path, type) { return new Promise( // This JSDoc annotation is required for correct types that are also type-safe. - /** @param {(value: string) => void} resolve - Fulfill the promise with the given value. */ + /** Promise handler resolving to the file's checksum. + * @param {(value: string) => void} resolve - Fulfill the promise with the given value. */ (resolve, reject) => { const hash = cryptoModule.createHash(type) const input = fs.createReadStream(path) diff --git a/app/ide-desktop/lib/content/src/remoteLog.ts b/app/ide-desktop/lib/content/src/remoteLog.ts index 0911c85683d..2a5ce0cf618 100644 --- a/app/ide-desktop/lib/content/src/remoteLog.ts +++ b/app/ide-desktop/lib/content/src/remoteLog.ts @@ -39,7 +39,6 @@ export class RemoteLogger { // === Underlying logic === /** Sends a log message to a remote server using the provided access token. - * * @param accessToken - The access token for authentication. * @param message - The message to be logged on the server. * @param metadata - Additional metadata to include in the log. diff --git a/app/ide-desktop/lib/dashboard/mock/authentication/src/authentication/cognito.ts b/app/ide-desktop/lib/dashboard/mock/authentication/src/authentication/cognito.ts index 18f9c8e4b11..8d4e59d31d6 100644 --- a/app/ide-desktop/lib/dashboard/mock/authentication/src/authentication/cognito.ts +++ b/app/ide-desktop/lib/dashboard/mock/authentication/src/authentication/cognito.ts @@ -319,7 +319,7 @@ function parseUserSession(session: cognito.CognitoUserSession): UserSession { // ============== /** A wrapper around the Amplify "sign up" endpoint that converts known errors - * to {@link SignUpError}s. */ + * to `SignUpError`s. */ async function signUp( _supportsDeepLinks: boolean, _username: string, @@ -337,7 +337,7 @@ async function signUp( // ===================== /** A wrapper around the Amplify "confirm sign up" endpoint that converts known errors - * to {@link ConfirmSignUpError}s. */ + * to `ConfirmSignUpError`s. */ async function confirmSignUp(_email: string, _code: string) { return results.Result.wrapAsync(async () => { // Ignored. @@ -353,7 +353,7 @@ async function confirmSignUp(_email: string, _code: string) { // ====================== /** A wrapper around the Amplify "current authenticated user" endpoint that converts known errors - * to {@link AmplifyError}s. */ + * to `AmplifyError`s. */ async function currentAuthenticatedUser() { const result = await results.Result.wrapAsync( // The methods are not needed. diff --git a/app/ide-desktop/lib/dashboard/package.json b/app/ide-desktop/lib/dashboard/package.json index 315e88c1d99..9a77795097e 100644 --- a/app/ide-desktop/lib/dashboard/package.json +++ b/app/ide-desktop/lib/dashboard/package.json @@ -27,8 +27,8 @@ "devDependencies": { "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@modyfi/vite-plugin-yaml": "^1.0.4", - "@playwright/experimental-ct-react": "^1.38.0", - "@playwright/test": "^1.38.0", + "@playwright/experimental-ct-react": "^1.40.0", + "@playwright/test": "^1.40.0", "@types/node": "^18.17.5", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/cognito.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/cognito.ts index 53f0b5a3e48..9500d08bc96 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/cognito.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/cognito.ts @@ -398,7 +398,6 @@ export const CURRENT_SESSION_NO_CURRENT_USER_ERROR = { /** * Convert an {@link AmplifyError} into a {@link CurrentSessionErrorKind} if it is a known error, * else re-throws the error. - * * @throws {Error} If the error is not recognized. */ export function intoCurrentSessionErrorKind(error: unknown): CurrentSessionErrorKind { @@ -473,7 +472,6 @@ export interface SignUpError extends CognitoError { /** * Convert an {@link AmplifyError} into a {@link SignUpError} if it is a known error, * else re-throws the error. - * * @throws {Error} If the error is not recognized. */ export function intoSignUpErrorOrThrow(error: AmplifyError): SignUpError { diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/controlledInput.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/controlledInput.tsx index 13e854d4516..1073827b9e7 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/controlledInput.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/components/controlledInput.tsx @@ -12,7 +12,7 @@ const DEBOUNCE_MS = 1000 // === ControlledInput === // ======================= -/** Props for an {@link Input}. */ +/** Props for a {@link ControlledInput}. */ export interface ControlledInputProps extends React.InputHTMLAttributes { value: string error?: string diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/listen.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/listen.tsx index c0220de4ee8..4895a28e989 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/listen.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/listen.tsx @@ -41,12 +41,10 @@ function isAuthEvent(value: string): value is AuthEvent { // ================================= /** Callback called in response to authentication state changes. - * * @see {@link amplify.Hub.listen}. */ export type ListenerCallback = (event: AuthEvent, data?: unknown) => void /** Unsubscribe the {@link ListenerCallback} from authentication state changes. - * * @see {@link amplify.Hub.listen}. */ type UnsubscribeFunction = () => void diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx index c4b66874785..1866624aa95 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/auth.tsx @@ -115,7 +115,7 @@ export type UserSession = FullUserSession | OfflineUserSession | PartialUserSess * signing out, etc. All interactions with the authentication API should be done through this * interface. * - * See {@link cognito.Cognito} for details on each of the authentication functions. */ + * See `Cognito` for details on each of the authentication functions. */ interface AuthContextType { goOffline: (shouldShowToast?: boolean) => Promise signUp: (email: string, password: string, organizationId: string | null) => Promise diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/session.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/session.tsx index fdfda334199..874a0e6d7e3 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/session.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/providers/session.tsx @@ -19,7 +19,7 @@ interface SessionContextType { deinitializeSession: () => void } -/** See {@link AuthContext} for safety details. */ +/** See `AuthContext` for safety details. */ const SessionContext = React.createContext( // eslint-disable-next-line no-restricted-syntax {} as SessionContextType diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/service.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/service.tsx index f329ac9b75d..b14bfc93a53 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/service.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/authentication/service.tsx @@ -1,4 +1,4 @@ -/** @file Provides an {@link AuthService} which consists of an underyling {@link Cognito} API +/** @file Provides an {@link AuthService} which consists of an underyling `Cognito` API * wrapper, along with some convenience callbacks to make URL redirects for the authentication flows * work with Electron. */ import * as amplify from '@aws-amplify/auth' @@ -193,7 +193,7 @@ function saveAccessToken(accessToken: string | null) { * handle the redirect for us. On the desktop however, we need to handle the redirect ourselves, * because it's a deep link into the app, and Amplify doesn't handle deep links. * - * All URLs that don't have a pathname that starts with {@link AUTHENTICATION_PATHNAME_BASE} will be + * All URLs that don't have a pathname that starts with `AUTHENTICATION_PATHNAME_BASE` will be * ignored by this handler. */ function setDeepLinkHandler(logger: loggerProvider.Logger, navigate: (url: string) => void) { const onDeepLink = (url: string) => { diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx index 6e0a18f6825..cfcd3da1a73 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/components/app.tsx @@ -4,7 +4,7 @@ * # Providers * * The {@link App} component is responsible for defining the global context used by child - * components. For example, it defines a {@link toast.Toaster}, which is used to display temporary + * components. For example, it defines a {@link toastify.ToastContainer}, which is used to display temporary * notifications to the user. These global components are defined at the top of the {@link App} so * that they are available to all of the child components. * diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx index c33f17a4060..557ca5c667e 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/assetsTable.tsx @@ -161,7 +161,7 @@ export interface AssetsTableState { key: backendModule.AssetId, title?: string ) => void - /** Called when the project is opened via the {@link ProjectActionButton}. */ + /** Called when the project is opened via the `ProjectActionButton`. */ doOpenManually: (projectId: backendModule.ProjectId) => void doOpenIde: ( project: backendModule.ProjectAsset, diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx index 61f086f3703..251ef597b68 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/managePermissionsModal.tsx @@ -18,7 +18,7 @@ import UserPermissions from './userPermissions' // === Constants === // ================= -/** The vertical offset of the {@link PermissionTypeSelector} from its parent element, for the +/** The vertical offset of the `PermissionTypeSelector` from its parent element, for the * input to invite new users. */ const TYPE_SELECTOR_Y_OFFSET_PX = 32 diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/newLabelModal.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/newLabelModal.tsx index a354f29f6f9..abab4256f1e 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/newLabelModal.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/newLabelModal.tsx @@ -14,7 +14,7 @@ import Modal from './modal' // === NewLabelModal === // ===================== -/** Props for a {@link ConfirmDeleteModal}. */ +/** Props for a {@link NewLabelModal}. */ export interface NewLabelModalProps { labelNames: Set eventTarget: HTMLElement diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectIcon.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectIcon.tsx index 736e1f6ba0d..3f8f0eba5f7 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectIcon.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/projectIcon.tsx @@ -30,7 +30,7 @@ const ICON_SIZE_PX = 24 const ICON_CLASSES = 'w-6 h-6' const LOADING_MESSAGE = 'Your environment is being created. It will take some time, please be patient.' -/** The corresponding {@link SpinnerState} for each {@link backendModule.ProjectState}, +/** The corresponding {@link spinner.SpinnerState} for each {@link backendModule.ProjectState}, * when using the remote backend. */ const REMOTE_SPINNER_STATE: Record = { [backendModule.ProjectState.closed]: spinner.SpinnerState.initial, @@ -42,7 +42,7 @@ const REMOTE_SPINNER_STATE: Record = { [backendModule.ProjectState.closed]: spinner.SpinnerState.initial, diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableRow.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableRow.tsx index 7ccfeb417f3..70279c3682f 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableRow.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/tableRow.tsx @@ -170,7 +170,7 @@ export default function TableRow { const result = await this.projectManager.listProjects({}) @@ -72,7 +71,6 @@ export class LocalBackend extends backend.Backend { } /** Return a list of projects belonging to the current user. - * * @throws An error if the JSON-RPC call fails. */ override async listProjects(): Promise { const result = await this.projectManager.listProjects({}) @@ -92,7 +90,6 @@ export class LocalBackend extends backend.Backend { } /** Create a project. - * * @throws An error if the JSON-RPC call fails. */ override async createProject( body: backend.CreateProjectRequestBody @@ -118,7 +115,6 @@ export class LocalBackend extends backend.Backend { } /** Close the project identified by the given project ID. - * * @throws An error if the JSON-RPC call fails. */ override async closeProject(projectId: backend.ProjectId, title: string | null): Promise { if (LocalBackend.currentlyOpeningProjectId === projectId) { @@ -138,7 +134,6 @@ export class LocalBackend extends backend.Backend { } /** Close the project identified by the given project ID. - * * @throws An error if the JSON-RPC call fails. */ override async getProjectDetails( projectId: backend.ProjectId, @@ -209,7 +204,6 @@ export class LocalBackend extends backend.Backend { } /** Prepare a project for execution. - * * @throws An error if the JSON-RPC call fails. */ override async openProject( projectId: backend.ProjectId, @@ -238,7 +232,6 @@ export class LocalBackend extends backend.Backend { } /** Change the name of a project. - * * @throws An error if the JSON-RPC call fails. */ override async projectUpdate( projectId: backend.ProjectId, @@ -278,7 +271,6 @@ export class LocalBackend extends backend.Backend { } /** Delete an arbitrary asset. - * * @throws An error if the JSON-RPC call fails. */ override async deleteAsset(assetId: backend.AssetId, title: string | null): Promise { // This is SAFE, as the only asset type on the local backend is projects. @@ -321,7 +313,8 @@ export class LocalBackend extends backend.Backend { // === Endpoints that intentionally do not work on the Local Backend === - /** @throws An error stating that the operation is intentionally unavailable on the local + /** Called for any function that does not make sense in the Local Backend. + * @throws An error stating that the operation is intentionally unavailable on the local * backend. */ invalidOperation(): never { throw new Error( diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/remoteBackend.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/remoteBackend.ts index cba0c068f5e..f8663eb40df 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/remoteBackend.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/remoteBackend.ts @@ -127,7 +127,6 @@ export class RemoteBackend extends backendModule.Backend { protected defaultVersions: Partial> = {} /** Create a new instance of the {@link RemoteBackend} API client. - * * @throws An error if the `Authorization` header is not set on the given `client`. */ constructor( private readonly client: http.Client, @@ -219,7 +218,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return organization info for the current user. - * * @returns `null` if a non-successful status code (not 200-299) was received. */ override async usersMe(): Promise { const response = await this.get( @@ -233,7 +231,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return a list of assets in a directory. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listDirectory( query: backendModule.ListDirectoryRequestParams, @@ -284,7 +281,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Create a directory. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async createDirectory( body: backendModule.CreateDirectoryRequestBody @@ -301,7 +297,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Change the name of a directory. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async updateDirectory( directoryId: backendModule.DirectoryId, @@ -324,7 +319,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Change the parent directory of an asset. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async updateAsset( assetId: backendModule.AssetId, @@ -342,7 +336,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Delete an arbitrary asset. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async deleteAsset(assetId: backendModule.AssetId, title: string | null) { const response = await this.delete(remoteBackendPaths.deleteAssetPath(assetId)) @@ -356,7 +349,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Restore an arbitrary asset from the trash. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async undoDeleteAsset( assetId: backendModule.AssetId, @@ -375,7 +367,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return a list of projects belonging to the current user. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listProjects(): Promise { const response = await this.get( @@ -399,7 +390,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Create a project. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async createProject( body: backendModule.CreateProjectRequestBody @@ -416,7 +406,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Close a project. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async closeProject( projectId: backendModule.ProjectId, @@ -435,7 +424,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return details for a project. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async getProjectDetails( projectId: backendModule.ProjectId, @@ -471,7 +459,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Prepare a project for execution. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async openProject( projectId: backendModule.ProjectId, @@ -492,7 +479,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Update the name or AMI of a project. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async projectUpdate( projectId: backendModule.ProjectId, @@ -515,7 +501,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return the resource usage of a project. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async checkResources( projectId: backendModule.ProjectId, @@ -536,7 +521,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return a list of files accessible by the current user. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listFiles(): Promise { const response = await this.get(remoteBackendPaths.LIST_FILES_PATH) @@ -548,7 +532,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Upload a file. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async uploadFile( params: backendModule.UploadFileRequestParams, @@ -591,7 +574,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Create a secret environment variable. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async createSecret( body: backendModule.CreateSecretRequestBody @@ -608,7 +590,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return a secret environment variable. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async getSecret( secretId: backendModule.SecretId, @@ -627,7 +608,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return the secret environment variables accessible by the user. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listSecrets(): Promise { const response = await this.get( @@ -641,7 +621,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Create a label used for categorizing assets. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async createTag( body: backendModule.CreateTagRequestBody @@ -658,7 +637,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return all labels accessible by the user. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listTags(): Promise { const response = await this.get(remoteBackendPaths.LIST_TAGS_PATH) @@ -670,7 +648,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Set the full list of labels for a specific asset. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async associateTag( assetId: backendModule.AssetId, @@ -695,7 +672,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Delete a label. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async deleteTag( tagId: backendModule.TagId, @@ -710,7 +686,6 @@ export class RemoteBackend extends backendModule.Backend { } /** Return a list of backend or IDE versions. - * * @throws An error if a non-successful status code (not 200-299) was received. */ override async listVersions( params: backendModule.ListVersionsRequestParams diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/error.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/error.ts index 396aa6144a8..a7910642d36 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/error.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/error.ts @@ -5,7 +5,7 @@ import type * as toastify from 'react-toastify' // === tryGetMessage === // ===================== -/** Evaluates the given type only if it the exact same type as {@link Expected}. */ +/** Evaluates the given type only if it the exact same type as `Expected`. */ type MustBe = (() => U extends T ? 1 : 2) extends () => U extends Expected ? 1 : 2 diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.ts index 7222d5265ad..d0afdb7d992 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/hooks.ts @@ -47,15 +47,14 @@ export function useToastAndLog() { /** A React hook for re-rendering a component once an asynchronous call is over. * - * This hook will take care of setting an initial value for the component state (so that it can - * render immediately), updating the state once the asynchronous call is over (to re-render the - * component), and cancelling any in-progress asynchronous calls when the component is unmounted (to - * avoid race conditions where "update 1" starts, "update 2" starts and finishes, then "update 1" - * finishes and sets the state). - * - * For further details, see: https://devtrium.com/posts/async-functions-useeffect. - * Also see: https://stackoverflow.com/questions/61751728/asynchronous-calls-with-react-usememo. + *This hook will take care of setting an initial value for the component state (so that it can + *render immediately), updating the state once the asynchronous call is over (to re-render the + *component), and cancelling any in-progress asynchronous calls when the component is unmounted (to + *avoid race conditions where "update 1" starts, "update 2" starts and finishes, then "update 1" + *finishes and sets the state). * + *For further details, see: https://devtrium.com/posts/async-functions-useeffect. + *Also see: https://stackoverflow.com/questions/61751728/asynchronous-calls-with-react-usememo. * @param initialValue - The initial value of the state controlled by this hook. * @param asyncEffect - The asynchronous function used to load the state controlled by this hook. * @param deps - The list of dependencies that, when updated, trigger the asynchronous effect. diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/newtype.ts b/app/ide-desktop/lib/dashboard/src/authentication/src/newtype.ts index fb02548033d..0f8c0fdfe06 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/newtype.ts +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/newtype.ts @@ -26,7 +26,7 @@ interface NewtypeVariant { export type Newtype = NewtypeVariant & T /** Extracts the original type out of a {@link Newtype}. - * Its only use is in {@link asNewtype}. */ + * Its only use is in {@link newtypeConstructor}. */ type UnNewtype> = T extends infer U & NewtypeVariant ? U : NotNewtype & Omit diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/providers/logger.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/providers/logger.tsx index b5f3986e321..6bdfeb29afc 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/providers/logger.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/providers/logger.tsx @@ -21,7 +21,7 @@ export interface Logger { // === LoggerContext === // ===================== -/** See {@link AuthContext} for safety details. */ +/** See `AuthContext` for safety details. */ // eslint-disable-next-line no-restricted-syntax const LoggerContext = React.createContext({} as Logger) diff --git a/app/ide-desktop/lib/dashboard/test-component/authentication/src/useRefresh/refresh.tsx b/app/ide-desktop/lib/dashboard/test-component/authentication/src/useRefresh/refresh.tsx index ef3ad8267d4..99d006856a6 100644 --- a/app/ide-desktop/lib/dashboard/test-component/authentication/src/useRefresh/refresh.tsx +++ b/app/ide-desktop/lib/dashboard/test-component/authentication/src/useRefresh/refresh.tsx @@ -7,7 +7,7 @@ import * as useRefresh from '../../../../src/authentication/src/useRefresh' // === Refresh === // =============== -/** The type of the state returned by {@link hooks.useRefresh}. */ +/** The type of the state returned by {@link useRefresh.useRefresh}. */ export type RefreshState = useRefresh.RefreshState /** Props for a {@link Refresh}. */ diff --git a/app/ide-desktop/lib/esbuild-plugin-copy-directories/src/index.js b/app/ide-desktop/lib/esbuild-plugin-copy-directories/src/index.js index d75e63e7a59..946230484bf 100644 --- a/app/ide-desktop/lib/esbuild-plugin-copy-directories/src/index.js +++ b/app/ide-desktop/lib/esbuild-plugin-copy-directories/src/index.js @@ -19,7 +19,8 @@ const NAMESPACE = NAME // This function is required. If narrowing is used instead, // TypeScript thinks `outputDir` may be `undefined` in functions. -/** @param {string} message - The message with which to throw the `Error`. +/** Throws an error. This is defined so that it is usable as an expression. + * @param {string} message - The message with which to throw the `Error`. * @returns {never} Always throws an error. * @throws {Error} Always. */ function error(message) { @@ -44,12 +45,15 @@ export default function esbuildPluginCopyDirectories(options) { let watchingPath = {} const outputDir = build.initialOptions.outdir ?? error('Output directory must be given.') - /** @param {string} root - Path to the directory to watch. */ + /** Continuously ensure that the folders are identical on both the content root, + * and the output directory. + * @param {string} root - Path to the directory to watch. */ const continuouslySync = root => { // It's theoretically possible to use a single `chokidar` instance, // however the root directory path is needed for calculating the destination path. const watcher = chokidar.watch(root, { cwd: root }) - /** @param {string} path - Path to the file to be copied. */ + /** Copies a path from the content root to the output directory. + * @param {string} path - Path to the file to be copied. */ const copy = path => { void (async () => { const source = pathModule.resolve(root, path) diff --git a/app/ide-desktop/lib/types/globals.d.ts b/app/ide-desktop/lib/types/globals.d.ts index 85210b024e5..3275003cc80 100644 --- a/app/ide-desktop/lib/types/globals.d.ts +++ b/app/ide-desktop/lib/types/globals.d.ts @@ -47,7 +47,7 @@ interface AuthenticationApi { /** Open a URL in the system browser. */ openUrlInSystemBrowser: (url: string) => void /** Set the callback to be called when the system browser redirects back to a URL in the app, - * via a deep link. See {@link setDeepLinkHandler} for details. */ + * via a deep link. See `setDeepLinkHandler` for details. */ setDeepLinkHandler: (callback: (url: string) => void) => void /** Saves the access token to a file. */ saveAccessToken: (accessToken: string | null) => void diff --git a/app/ide-desktop/utils.ts b/app/ide-desktop/utils.ts index 90a455ea65b..9cd5cba7bed 100644 --- a/app/ide-desktop/utils.ts +++ b/app/ide-desktop/utils.ts @@ -15,7 +15,6 @@ export const INDENT_SIZE = 4 // =================== /** Get the environment variable value. - * * @param name - The name of the environment variable. * @returns The value of the environment variable. * @throws {Error} If the environment variable is not set. */ @@ -29,7 +28,6 @@ export function requireEnv(name: string) { } /** Read the path from environment variable and resolve it. - * * @param name - The name of the environment variable. * @returns The resolved path. * @throws {Error} If the environment variable is not set. */ @@ -38,7 +36,6 @@ export function requireEnvResolvedPath(name: string) { } /** Read the path from environment variable and resolve it. Verify that it exists. - * * @param name - The name of the environment variable. * @returns The resolved path. * @throws {Error} If the environment variable is not set or path does not exist. */ diff --git a/package-lock.json b/package-lock.json index 2e173593870..6e0d10f9da9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,16 +61,18 @@ "y-textarea": "^1.0.0", "y-websocket": "^1.5.0", "yjs": "^13.6.7", - "zod": "^3.22.2" + "zod": "^3.22.4" }, "devDependencies": { "@danmarshall/deckgl-typings": "^4.9.28", "@eslint/eslintrc": "^2.1.2", "@eslint/js": "^8.49.0", "@histoire/plugin-vue": "^0.17.1", - "@playwright/test": "^1.37.0", + "@open-rpc/server-js": "^1.9.4", + "@playwright/test": "^1.40.0", "@rushstack/eslint-patch": "^1.3.2", "@tsconfig/node18": "^18.2.0", + "@types/css.escape": "^1.5.2", "@types/culori": "^2.0.1", "@types/d3": "^7.4.0", "@types/hash-sum": "^1.0.0", @@ -89,6 +91,7 @@ "@vue/test-utils": "^2.4.1", "@vue/tsconfig": "^0.4.0", "change-case": "^4.1.2", + "css.escape": "^1.5.1", "d3": "^7.4.0", "esbuild": "^0.19.3", "eslint": "^8.49.0", @@ -97,6 +100,7 @@ "hash-wasm": "^4.10.0", "histoire": "^0.17.2", "jsdom": "^22.1.0", + "playwright": "^1.39.0", "postcss-nesting": "^12.0.1", "prettier": "^3.0.0", "prettier-plugin-organize-imports": "^3.2.3", @@ -214,29 +218,6 @@ "name": "enso-content-config", "version": "1.0.0" }, - "app/ide-desktop/lib/content/node_modules/eslint-plugin-jsdoc": { - "version": "46.8.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.1.tgz", - "integrity": "sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A==", - "dev": true, - "dependencies": { - "@es-joy/jsdoccomment": "~0.40.1", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.0", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, "app/ide-desktop/lib/dashboard": { "name": "enso-dashboard", "version": "0.1.0", @@ -252,8 +233,8 @@ "devDependencies": { "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@modyfi/vite-plugin-yaml": "^1.0.4", - "@playwright/experimental-ct-react": "^1.38.0", - "@playwright/test": "^1.38.0", + "@playwright/experimental-ct-react": "^1.40.0", + "@playwright/test": "^1.40.0", "@types/node": "^18.17.5", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", @@ -279,29 +260,6 @@ "@esbuild/windows-x64": "^0.17.15" } }, - "app/ide-desktop/lib/dashboard/node_modules/eslint-plugin-jsdoc": { - "version": "46.8.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.1.tgz", - "integrity": "sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A==", - "dev": true, - "dependencies": { - "@es-joy/jsdoccomment": "~0.40.1", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.0", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "is-builtin-module": "^3.2.1", - "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, "app/ide-desktop/lib/dashboard/src/authentication": { "name": "enso-authentication", "version": "1.0.0", @@ -334,70 +292,6 @@ "to-ico": "^1.1.5" } }, - "app/ide-desktop/node_modules/@types/yargs": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.31.tgz", - "integrity": "sha512-bocYSx4DI8TmdlvxqGpVNXOgCNR1Jj0gNPhhAY+iz1rgKDAaYrAYdFYnhDV1IFuiuVc9HkOwyDcFxaTElF3/wg==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "app/ide-desktop/node_modules/eslint-plugin-jsdoc": { - "version": "40.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.37.0", - "comment-parser": "1.3.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "semver": "^7.3.8", - "spdx-expression-parse": "^3.0.1" - }, - "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "app/ide-desktop/node_modules/eslint-plugin-jsdoc/node_modules/@es-joy/jsdoccomment": { - "version": "0.37.1", - "dev": true, - "license": "MIT", - "dependencies": { - "comment-parser": "1.3.1", - "esquery": "^1.5.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": "^14 || ^16 || ^17 || ^18 || ^19 || ^20" - } - }, - "app/ide-desktop/node_modules/eslint-plugin-jsdoc/node_modules/comment-parser": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "app/ide-desktop/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "build/prettier": { "version": "1.0.0", "extraneous": true, @@ -2992,6 +2886,45 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@json-schema-spec/json-pointer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@json-schema-spec/json-pointer/-/json-pointer-0.1.2.tgz", + "integrity": "sha512-BYY7IavBjwsWWSmVcMz2A9mKiDD9RvacnsItgmy1xV8cmgbtxFfKmKMtkVpD7pYtkx4mIW4800yZBXueVFIWPw==", + "dev": true + }, + "node_modules/@json-schema-tools/dereferencer": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@json-schema-tools/dereferencer/-/dereferencer-1.5.1.tgz", + "integrity": "sha512-CUpdGpxNTq1ebMkrgVxS03FHfwkGiw63c+GNzqFAqwqsxR0OsR79aqK8h2ybxTIEhdwiaknSnlUgtUIy7FJ+3A==", + "dev": true, + "dependencies": { + "@json-schema-tools/reference-resolver": "^1.2.1", + "@json-schema-tools/traverse": "^1.7.5", + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/@json-schema-tools/meta-schema": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@json-schema-tools/meta-schema/-/meta-schema-1.7.0.tgz", + "integrity": "sha512-3pDzVUssW3hVnf8gvSu1sKaVIpLyvmpbxgGfkUoaBiErFKRS2CZOufHD0pUFoa5e6Cd5oa72s402nJbnDz76CA==", + "dev": true + }, + "node_modules/@json-schema-tools/reference-resolver": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@json-schema-tools/reference-resolver/-/reference-resolver-1.2.5.tgz", + "integrity": "sha512-xNQgX/ABnwvbIeexL5Czv08lXjHAL80HEUogza7E19eIL/EXD8HM4FvxG1JuTGyi5fA+sSP64C9pabELizcBBw==", + "dev": true, + "dependencies": { + "@json-schema-spec/json-pointer": "^0.1.2", + "isomorphic-fetch": "^3.0.0" + } + }, + "node_modules/@json-schema-tools/traverse": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@json-schema-tools/traverse/-/traverse-1.10.1.tgz", + "integrity": "sha512-vYY5EIxCPzEXEWL/vTjdHy4g92tv1ApUQCjPJsj9gEoXLNNVwJlwwgRZisuvgFBZ3zeLzQygrbehERSpYdmFZA==", + "dev": true + }, "node_modules/@lezer/common": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.1.0.tgz", @@ -3146,6 +3079,46 @@ } } }, + "node_modules/@open-rpc/meta-schema": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@open-rpc/meta-schema/-/meta-schema-1.14.2.tgz", + "integrity": "sha512-vD4Nbkrb7wYFRcSQf+j228LwOy1C6/KKpy5NADlpMElGrAWPRxhTa2yTi6xG+x88OHzg2+cydQ0GAD6o40KUcg==", + "dev": true + }, + "node_modules/@open-rpc/schema-utils-js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@open-rpc/schema-utils-js/-/schema-utils-js-1.15.0.tgz", + "integrity": "sha512-YHTt3n3RZo1lRy8oknn2G1y0PWlo7HWtnwLOKfvVxjauKMOmlvBbpPHQZibpzIhgt+yPe4mht1ldhKOwq2tCUw==", + "dev": true, + "dependencies": { + "@json-schema-tools/dereferencer": "1.5.1", + "@json-schema-tools/meta-schema": "^1.6.10", + "@json-schema-tools/reference-resolver": "^1.2.1", + "@open-rpc/meta-schema": "1.14.2", + "ajv": "^6.10.0", + "detect-node": "^2.0.4", + "fast-safe-stringify": "^2.0.7", + "fs-extra": "^9.0.0", + "is-url": "^1.2.4", + "isomorphic-fetch": "^3.0.0" + } + }, + "node_modules/@open-rpc/server-js": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@open-rpc/server-js/-/server-js-1.9.4.tgz", + "integrity": "sha512-8+rcewakAO/cpuSNbxJ7nMcmQwD4trCdAeXWssAbpiMj8BIpOARt/jeR7OFePUIwg20LgePQ7qsi14uVgZKr5w==", + "dev": true, + "dependencies": { + "@open-rpc/schema-utils-js": "1.15.0", + "body-parser": "^1.19.0", + "connect": "^3.7.0", + "cors": "^2.8.5", + "json-schema-faker": "^0.5.0-rcv.26", + "lodash": "^4.17.19", + "node-ipc": "9.1.1", + "ws": "^8.0.0" + } + }, "node_modules/@pinia/testing": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.3.tgz", @@ -3179,13 +3152,30 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@playwright/experimental-ct-react": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.38.0.tgz", - "integrity": "sha512-RRQi99dNWDlkdSIUJpva5T0f+Nq6JSb0fR6MatvJDJkgiARaytk57SgquTmFHglyNu4p/Qj4lRxCCIEy0uZDGA==", + "node_modules/@playwright/experimental-ct-core": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.40.0.tgz", + "integrity": "sha512-fsavfzF5RR4kuXuHpqUAZz1eWhlQLVSSHQAzFFuGklV1RCePtUPfo6Vfru/K4SfcYK6QKKmWnvBxt/3PIamQgA==", "dev": true, "dependencies": { - "@playwright/experimental-ct-core": "1.38.0", + "playwright": "1.40.0", + "playwright-core": "1.40.0", + "vite": "^4.4.10" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/experimental-ct-react": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.40.0.tgz", + "integrity": "sha512-r0JQT18zM8YISO5IsqoBYz5JgCQ3cl9Rfp/Wljuh83JQwQsRE26pJ9Ze3YOoMqwZw5NYPiiBG6z+y8n47w2X/w==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.40.0", "@vitejs/plugin-react": "^4.0.0" }, "bin": { @@ -3195,30 +3185,13 @@ "node": ">=16" } }, - "node_modules/@playwright/experimental-ct-react/node_modules/@playwright/experimental-ct-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.38.0.tgz", - "integrity": "sha512-jspVRe+D9/gM8aZ6g5TVybSKnYi9OfvUtq67WHo22OfxENXBdDe0hHHlLRQNSdmuKcE5goG8WNVZvOWjolTb/Q==", - "dev": true, - "dependencies": { - "playwright": "1.38.0", - "playwright-core": "1.38.0", - "vite": "^4.3.9" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@playwright/test": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", - "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0.tgz", + "integrity": "sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg==", "dev": true, "dependencies": { - "playwright": "1.38.0" + "playwright": "1.40.0" }, "bin": { "playwright": "cli.js" @@ -3584,6 +3557,12 @@ "version": "0.3.3", "license": "MIT" }, + "node_modules/@types/css.escape": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/css.escape/-/css.escape-1.5.2.tgz", + "integrity": "sha512-sq0h0y8i83T20MB434AZWgK3byezbBG48jllitFpVXlPOjHc5RNs3bg6rUen/EtMUjM4ZJjD4x+0aik9AXqLdQ==", + "dev": true + }, "node_modules/@types/culori": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/culori/-/culori-2.0.1.tgz", @@ -4153,9 +4132,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.24", - "dev": true, - "license": "MIT", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dependencies": { "@types/yargs-parser": "*" } @@ -5577,6 +5556,84 @@ "dev": true, "license": "MIT" }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/boolbase": { "version": "1.0.0", "license": "ISC" @@ -5937,6 +5994,15 @@ "node": ">=10.16.0" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cac": { "version": "6.7.14", "dev": true, @@ -5982,6 +6048,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -6458,6 +6530,15 @@ "upper-case": "^2.0.2" } }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "dev": true, @@ -6475,6 +6556,19 @@ "devOptional": true, "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", @@ -6585,6 +6679,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "node_modules/cssesc": { "version": "3.0.0", "dev": true, @@ -7256,6 +7356,25 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-libc": { "version": "2.0.2", "dev": true, @@ -7267,8 +7386,7 @@ "node_modules/detect-node": { "version": "2.1.0", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/diacritics": { "version": "1.3.0", @@ -7550,6 +7668,15 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/easy-stack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", + "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "dev": true, @@ -8785,6 +8912,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-pubsub": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", + "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8950,6 +9086,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "license": "ISC", @@ -9155,6 +9297,12 @@ "node": ">= 6" } }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true + }, "node_modules/fs-constants": { "version": "1.0.0", "dev": true, @@ -9436,11 +9584,18 @@ } }, "node_modules/globals": { - "version": "11.12.0", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, - "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -9948,6 +10103,31 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "dev": true, @@ -11064,6 +11244,27 @@ "version": "2.2.1", "license": "MIT" }, + "node_modules/js-message": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.5.tgz", + "integrity": "sha512-hTqHqrm7jrZ+iN93QsKcNOTSgX3F+2NSgdnF+xvf8FfhC2MPqYRzzgXQ1LlhfyIzPTS6hL6Zea0/gIb6hktkHw==", + "dev": true, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/js-queue": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.0.tgz", + "integrity": "sha512-SW0rTTG+TBPVD1Kp6HtnOr9kX3//EWA6qMlP2Y/WxbKsSNCBuJbWv3EDB5noKJBEkHYi2mDY+xqMn4Y0QHyjyg==", + "dev": true, + "dependencies": { + "easy-stack": "^1.0.0" + }, + "engines": { + "node": ">=1.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -11159,6 +11360,59 @@ "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "node_modules/json-schema-faker": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.3.tgz", + "integrity": "sha512-BeIrR0+YSrTbAR9dOMnjbFl1MvHyXnq+Wpdw1FpWZDHWKLzK229hZ5huyPcmzFUfVq1ODwf40WdGVoE266UBUg==", + "dev": true, + "dependencies": { + "json-schema-ref-parser": "^6.1.0", + "jsonpath-plus": "^7.2.0" + }, + "bin": { + "jsf": "bin/gen.cjs" + } + }, + "node_modules/json-schema-ref-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", + "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.12.1", + "ono": "^4.0.11" + } + }, + "node_modules/json-schema-ref-parser/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/json-schema-ref-parser/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-ref-parser/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "devOptional": true, @@ -11201,6 +11455,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpath-plus": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", + "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsprim": { "version": "1.4.2", "dev": true, @@ -11755,6 +12018,15 @@ "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/memorystream": { "version": "0.3.1", "dev": true, @@ -12105,6 +12377,20 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-ipc": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.1.1.tgz", + "integrity": "sha512-FAyICv0sIRJxVp3GW5fzgaf9jwwRQxAKDJlmNFUL5hOy+W4X/I5AypyHoq0DXXbo9o/gt79gj++4cMr4jVWE/w==", + "dev": true, + "dependencies": { + "event-pubsub": "4.3.0", + "js-message": "1.0.5", + "js-queue": "2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/node-releases": { "version": "2.0.13", "dev": true, @@ -12491,6 +12777,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "dependencies": { + "format-util": "^1.0.3" + } + }, "node_modules/open": { "version": "9.1.0", "dev": true, @@ -13029,12 +13324,12 @@ } }, "node_modules/playwright": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", - "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0.tgz", + "integrity": "sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw==", "dev": true, "dependencies": { - "playwright-core": "1.38.0" + "playwright-core": "1.40.0" }, "bin": { "playwright": "cli.js" @@ -13047,9 +13342,10 @@ } }, "node_modules/playwright-core": { - "version": "1.38.0", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", + "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", "dev": true, - "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -13556,6 +13852,33 @@ "node": ">=0.12" } }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rc": { "version": "1.2.8", "dev": true, @@ -14484,6 +14807,12 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "node_modules/sharp": { "version": "0.31.3", "dev": true, @@ -15538,6 +15867,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/tosource": { "version": "2.0.0-alpha.3", "resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz", @@ -16260,6 +16598,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.0", "dev": true, @@ -16703,6 +17054,15 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -16718,9 +17078,10 @@ } }, "node_modules/vite": { - "version": "4.4.9", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", + "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -17950,9 +18311,9 @@ } }, "node_modules/zod": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", - "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", "funding": { "url": "https://github.com/sponsors/colinhacks" }