mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 08:53:31 +03:00
E2E tests (#8239)
- Closes #8179 # Important Notes - ⚠️ These tests are currently *not run* on any CI workflow. - There is some unused code for mocking the PM. This has been intentionally kept, as this may be useful in the future. Note that this may be useful for testing the dashboard, however the dashboard is currently only tested in cloud mode - that is, without the backend switcher, and with only the remote backend available. As such, currently it uses HTTP API mocks, and no PM mock.
This commit is contained in:
parent
8df47a7d65
commit
9b7e3d0f16
20
app/gui2/.snyk
Normal file
20
app/gui2/.snyk
Normal file
@ -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: {}
|
33
app/gui2/e2e/MockApp.vue
Normal file
33
app/gui2/e2e/MockApp.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import App from '../src/App.vue'
|
||||
import MockProjectStoreWrapper from '../stories/MockProjectStoreWrapper.vue'
|
||||
import { getMainFile, setMainFile } from './mockEngine'
|
||||
|
||||
const mainFile = computed({
|
||||
get() {
|
||||
return getMainFile()
|
||||
},
|
||||
set(value) {
|
||||
setMainFile(value)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MockProjectStoreWrapper v-model="mainFile"><App :config="{}" /></MockProjectStoreWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:is(.viewport) {
|
||||
color: var(--color-text);
|
||||
font-family: 'M PLUS 1', sans-serif;
|
||||
font-size: 11.5px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
11
app/gui2/e2e/actions.ts
Normal file
11
app/gui2/e2e/actions.ts
Normal file
@ -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.
|
||||
}
|
17
app/gui2/e2e/componentBrowser.spec.ts
Normal file
17
app/gui2/e2e/componentBrowser.spec.ts
Normal file
@ -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)
|
||||
})
|
9
app/gui2/e2e/customExpect.ts
Normal file
9
app/gui2/e2e/customExpect.ts
Normal file
@ -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()
|
||||
}
|
18
app/gui2/e2e/graphNodeVisualization.spec.ts
Normal file
18
app/gui2/e2e/graphNodeVisualization.spec.ts
Normal file
@ -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('{')
|
||||
})
|
10
app/gui2/e2e/graphRenderNodes.spec.ts
Normal file
10
app/gui2/e2e/graphRenderNodes.spec.ts
Normal file
@ -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))
|
||||
})
|
152
app/gui2/e2e/locate.ts
Normal file
152
app/gui2/e2e/locate.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { type Locator, type Page } from '@playwright/test'
|
||||
import cssEscape from 'css.escape'
|
||||
|
||||
// ==============
|
||||
// === Filter ===
|
||||
// ==============
|
||||
|
||||
class Filter {
|
||||
constructor(public selector = '') {}
|
||||
|
||||
visible<T extends { selector: string }>(this: T): Omit<T, 'visible'> {
|
||||
return new Filter(this.selector + ':visible') as any
|
||||
}
|
||||
|
||||
first<T extends { selector: string }>(this: T): Omit<T, 'first' | 'last'> {
|
||||
return new Filter(this.selector + ':first') as any
|
||||
}
|
||||
|
||||
last<T extends { selector: string }>(this: T): Omit<T, 'first' | 'last'> {
|
||||
return new Filter(this.selector + ':last') as any
|
||||
}
|
||||
|
||||
id<T extends { selector: string }>(this: T, id: string): Omit<T, 'id'> {
|
||||
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 string> = T extends `${infer A}.${infer B}`
|
||||
? SanitizeClassName<`${A}${B}`>
|
||||
: T extends `${infer A} ${infer B}`
|
||||
? SanitizeClassName<`${A}${B}`>
|
||||
: T
|
||||
|
||||
function componentLocator<T extends string>(className: SanitizeClassName<T>) {
|
||||
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')
|
58
app/gui2/e2e/main.ts
Normal file
58
app/gui2/e2e/main.ts
Normal file
@ -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')
|
332
app/gui2/e2e/mockEngine.ts
Normal file
332
app/gui2/e2e/mockEngine.ts
Normal file
@ -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<Uuid, VisualizationConfiguration>()
|
||||
const visualizationExprIds = new Map<Uuid, ExpressionId>()
|
||||
|
||||
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<string, Uint8Array> = {
|
||||
// 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),
|
||||
)
|
63
app/gui2/e2e/mockProjectManager.ts
Normal file
63
app/gui2/e2e/mockProjectManager.ts
Normal file
@ -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<string, ProjectMetadata>()
|
||||
const openProjects = new Set<string>()
|
||||
|
||||
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<string, (...params: any[]) => Promise<unknown>>
|
83
app/gui2/e2e/pm-openrpc.json
Normal file
83
app/gui2/e2e/pm-openrpc.json
Normal file
@ -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" }] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
38
app/gui2/e2e/setup.ts
Normal file
38
app/gui2/e2e/setup.ts
Normal file
@ -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',
|
||||
})
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"include": ["./**/*"]
|
||||
}
|
@ -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!')
|
||||
})
|
@ -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",
|
||||
|
@ -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
|
||||
})
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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<DataServerEvents> {
|
||||
protected send<T = void>(
|
||||
builder: Builder,
|
||||
payloadType: InboundPayload,
|
||||
payloadOffset: number,
|
||||
payloadOffset: Offset<AnyInboundPayload>,
|
||||
): Promise<T> {
|
||||
const messageUuid = random.uuidv4()
|
||||
const rootTable = InboundMessage.createInboundMessage(
|
||||
|
207
app/gui2/shared/dataServer/mock.ts
Normal file
207
app/gui2/shared/dataServer/mock.ts
Normal file
@ -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<InboundPayload, new () => Table>
|
||||
|
||||
export function mockDataWSHandler(
|
||||
readFile: (segments: string[]) => Promise<ArrayBuffer | null | undefined>,
|
||||
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<AnyOutboundPayload> } | 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())
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -18,18 +18,21 @@ const emit = defineEmits<{
|
||||
<ToggleIcon
|
||||
icon="no_auto_replay"
|
||||
class="icon-container button no-auto-evaluate-button"
|
||||
:alt="`${props.isAutoEvaluationDisabled ? 'Enable' : 'Disable'} auto-evaluation`"
|
||||
:modelValue="props.isAutoEvaluationDisabled"
|
||||
@update:modelValue="emit('update:isAutoEvaluationDisabled', $event)"
|
||||
/>
|
||||
<ToggleIcon
|
||||
icon="docs"
|
||||
class="icon-container button docs-button"
|
||||
:alt="`${props.isDocsVisible ? 'Hide' : 'Show'} documentation`"
|
||||
:modelValue="props.isDocsVisible"
|
||||
@update:modelValue="emit('update:isDocsVisible', $event)"
|
||||
/>
|
||||
<ToggleIcon
|
||||
icon="eye"
|
||||
class="icon-container button visualization-button"
|
||||
:alt="`${props.isVisualizationVisible ? 'Hide' : 'Show'} visualization`"
|
||||
:modelValue="props.isVisualizationVisible"
|
||||
@update:modelValue="emit('update:isVisualizationVisible', $event)"
|
||||
/>
|
||||
|
@ -369,7 +369,7 @@ const handler = componentBrowserBindings.handler({
|
||||
@wheel.stop.passive
|
||||
@scroll="updateScroll"
|
||||
>
|
||||
<div class="list-variant" style="">
|
||||
<div class="list-variant">
|
||||
<div
|
||||
v-for="item in visibleComponents"
|
||||
:key="item.component.suggestionId"
|
||||
|
@ -8,7 +8,10 @@ import {
|
||||
previousNodeDictatedPlacement,
|
||||
type Environment,
|
||||
} from '@/components/ComponentBrowser/placement.ts'
|
||||
import GraphEdges from '@/components/GraphEditor/GraphEdges.vue'
|
||||
import GraphNodes from '@/components/GraphEditor/GraphNodes.vue'
|
||||
import { Uploader, uploadedExpression } from '@/components/GraphEditor/upload'
|
||||
import GraphMouse from '@/components/GraphMouse.vue'
|
||||
import PlusButton from '@/components/PlusButton.vue'
|
||||
import TopBar from '@/components/TopBar.vue'
|
||||
import { provideGraphNavigator } from '@/providers/graphNavigator'
|
||||
@ -26,9 +29,6 @@ import { Vec2 } from '@/util/vec2'
|
||||
import * as set from 'lib0/set'
|
||||
import type { ExprId, NodeMetadata } from 'shared/yjsModel.ts'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import GraphEdges from './GraphEditor/GraphEdges.vue'
|
||||
import GraphNodes from './GraphEditor/GraphNodes.vue'
|
||||
import GraphMouse from './GraphMouse.vue'
|
||||
|
||||
const EXECUTION_MODES = ['design', 'live']
|
||||
// Difference in position between the component browser and a node for the input of the component browser to
|
||||
@ -436,7 +436,7 @@ function handleNodeOutputPortDoubleClick(id: ExprId) {
|
||||
<template>
|
||||
<div
|
||||
ref="viewportNode"
|
||||
class="GraphEditor"
|
||||
class="GraphEditor viewport"
|
||||
:class="{ draggingEdge: graphStore.unconnectedEdge != null }"
|
||||
:style="groupColors"
|
||||
v-on.="graphNavigator.events"
|
||||
|
@ -327,7 +327,7 @@ function pathElements(junctions: JunctionPoints): { start: Vec2; elements: Eleme
|
||||
const start = junctions.points[0]
|
||||
if (start == null) return { start: Vec2.Zero, elements: [] }
|
||||
let prev = start
|
||||
junctions.points.slice(1).map((j, i) => {
|
||||
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)
|
||||
|
@ -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;
|
||||
|
@ -56,9 +56,8 @@ export class Uploader {
|
||||
position: Vec2,
|
||||
stackItem: StackItem,
|
||||
): Promise<Uploader> {
|
||||
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,
|
||||
|
@ -118,7 +118,7 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
@pointerdown.stop="config.hide()"
|
||||
@click="config.hide()"
|
||||
>
|
||||
<SvgIcon class="icon" name="eye" />
|
||||
<SvgIcon class="icon" name="eye" alt="Hide visualization" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
@ -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)"
|
||||
>
|
||||
<SvgIcon class="icon" :name="config.fullscreen ? 'exit_fullscreen' : 'fullscreen'" />
|
||||
<SvgIcon
|
||||
class="icon"
|
||||
:name="config.fullscreen ? 'exit_fullscreen' : 'fullscreen'"
|
||||
:alt="config.fullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
|
||||
/>
|
||||
</button>
|
||||
<div class="icon-container">
|
||||
<button
|
||||
@ -135,7 +139,13 @@ const resizeBottomRight = usePointer((pos, _, type) => {
|
||||
@pointerdown.stop="isSelectorVisible = !isSelectorVisible"
|
||||
@click.prevent="!isClick($event) && (isSelectorVisible = !isSelectorVisible)"
|
||||
>
|
||||
<SvgIcon class="icon" :name="config.icon ?? 'columns_increasing'" />
|
||||
<SvgIcon
|
||||
class="icon"
|
||||
:name="config.icon ?? 'columns_increasing'"
|
||||
:alt="
|
||||
isSelectorVisible ? 'Hide visualization selector' : 'Show visualization selector'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
<VisualizationSelector
|
||||
v-if="isSelectorVisible"
|
||||
|
@ -2,6 +2,10 @@
|
||||
export const name = 'Geo Map'
|
||||
export const icon = 'compass'
|
||||
export const inputType = 'Standard.Table.Data.Table.Table'
|
||||
export const defaultPreprocessor = [
|
||||
'Standard.Visualization.Geo_Map',
|
||||
'process_to_json_text',
|
||||
] as const
|
||||
export const scripts = [
|
||||
// mapbox-gl does not have an ESM release.
|
||||
'https://api.tiles.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js',
|
||||
@ -86,19 +90,14 @@ declare var deck: typeof import('deck.gl')
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { VisualizationContainer } from '@/util/visualizationBuiltins'
|
||||
import type { Deck } from 'deck.gl'
|
||||
import { computed, onMounted, onUnmounted, ref, watchPostEffect } from 'vue'
|
||||
import { computed, onUnmounted, ref, watchPostEffect } from 'vue'
|
||||
|
||||
const props = defineProps<{ data: Data }>()
|
||||
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())
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -108,6 +108,7 @@ export async function compile(path: string, projectRoot: Opt<Uuid>, 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<Uuid>, 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<FetchResultWorkerResponse>(worker_, {
|
||||
|
@ -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,
|
||||
|
@ -1,2 +0,0 @@
|
||||
/** Removes `readonly` from all keys in a type. UNSAFE. */
|
||||
export type UnsafeMutable<T> = { -readonly [K in keyof T]: T[K] }
|
@ -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<E> {
|
||||
maxRetries?: number
|
||||
@ -101,8 +101,8 @@ export function createWebsocketClient(
|
||||
}
|
||||
}
|
||||
|
||||
interface MockTransportData {
|
||||
(method: string, params: any, transport: MockTransport): Promise<any>
|
||||
export interface MockTransportData<Methods extends string = string> {
|
||||
(method: Methods, params: any, transport: MockTransport): Promise<any>
|
||||
}
|
||||
|
||||
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<Methods extends string>(name: string, data: MockTransportData<Methods>) {
|
||||
MockTransport.mocks.set(name, data as any)
|
||||
}
|
||||
connect(): Promise<any> {
|
||||
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<any>))
|
||||
this.addEventListener('error', (ev) => this.onerror?.(ev))
|
||||
setTimeout(() => this.dispatchEvent(new Event('open')), 0)
|
||||
|
@ -3,7 +3,7 @@ import GraphEditor from '@/components/GraphEditor.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<GraphEditor></GraphEditor>
|
||||
<GraphEditor />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
@ -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<InboundPayload, new () => 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<WebSocketHandler> = 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()
|
||||
}),
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -38,10 +38,7 @@ function applyEdits(module: NonNullable<typeof projectStore.module>, 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)
|
||||
|
||||
|
@ -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[] {
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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. */
|
||||
|
@ -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<string> {
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
|
@ -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.')
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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<HTMLInputElement> {
|
||||
value: string
|
||||
error?: string
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<boolean>
|
||||
signUp: (email: string, password: string, organizationId: string | null) => Promise<boolean>
|
||||
|
@ -19,7 +19,7 @@ interface SessionContextType {
|
||||
deinitializeSession: () => void
|
||||
}
|
||||
|
||||
/** See {@link AuthContext} for safety details. */
|
||||
/** See `AuthContext` for safety details. */
|
||||
const SessionContext = React.createContext<SessionContextType>(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
{} as SessionContextType
|
||||
|
@ -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) => {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -14,7 +14,7 @@ import Modal from './modal'
|
||||
// === NewLabelModal ===
|
||||
// =====================
|
||||
|
||||
/** Props for a {@link ConfirmDeleteModal}. */
|
||||
/** Props for a {@link NewLabelModal}. */
|
||||
export interface NewLabelModalProps {
|
||||
labelNames: Set<string>
|
||||
eventTarget: HTMLElement
|
||||
|
@ -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, spinner.SpinnerState> = {
|
||||
[backendModule.ProjectState.closed]: spinner.SpinnerState.initial,
|
||||
@ -42,7 +42,7 @@ const REMOTE_SPINNER_STATE: Record<backendModule.ProjectState, spinner.SpinnerSt
|
||||
[backendModule.ProjectState.provisioned]: spinner.SpinnerState.loadingSlow,
|
||||
[backendModule.ProjectState.opened]: spinner.SpinnerState.done,
|
||||
}
|
||||
/** The corresponding {@link SpinnerState} for each {@link backendModule.ProjectState},
|
||||
/** The corresponding {@link spinner.SpinnerState} for each {@link backendModule.ProjectState},
|
||||
* when using the local backend. */
|
||||
const LOCAL_SPINNER_STATE: Record<backendModule.ProjectState, spinner.SpinnerState> = {
|
||||
[backendModule.ProjectState.closed]: spinner.SpinnerState.initial,
|
||||
|
@ -170,7 +170,7 @@ export default function TableRow<T, State = never, RowState = never, Key extends
|
||||
/** This is SAFE, as the type is defined such that they MUST be
|
||||
* present if it is specified as a generic parameter.
|
||||
* See the type definitions of {@link TableRowProps} and
|
||||
* {@link TableProps}. */
|
||||
* `TableProps`. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
state={state!}
|
||||
rowState={rowState}
|
||||
|
@ -73,7 +73,7 @@ interface AssetRowsDragPayloadItem {
|
||||
readonly asset: backend.AnyAsset
|
||||
}
|
||||
|
||||
/** Data for a {@link DragEvent} started from an {@link AssetsTable}. */
|
||||
/** Data for a {@link DragEvent} started from an `AssetsTable`. */
|
||||
export type AssetRowsDragPayload = readonly AssetRowsDragPayloadItem[]
|
||||
|
||||
// ========================
|
||||
|
@ -67,7 +67,7 @@ interface AssetEvents {
|
||||
deleteLabel: AssetDeleteLabelEvent
|
||||
}
|
||||
|
||||
/** A type to ensure that {@link AssetEvents} contains every {@link AssetLEventType}. */
|
||||
/** A type to ensure that {@link AssetEvents} contains every {@link AssetEventType}. */
|
||||
// This is meant only as a sanity check, so it is allowed to break lint rules.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type SanityCheck<
|
||||
|
@ -47,7 +47,6 @@ export class LocalBackend extends backend.Backend {
|
||||
return backend.DirectoryId('')
|
||||
}
|
||||
/** Return a list of assets in a directory.
|
||||
*
|
||||
* @throws An error if the JSON-RPC call fails. */
|
||||
override async listDirectory(): Promise<backend.AnyAsset[]> {
|
||||
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<backend.ListedProject[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
// 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(
|
||||
|
@ -127,7 +127,6 @@ export class RemoteBackend extends backendModule.Backend {
|
||||
protected defaultVersions: Partial<Record<backendModule.VersionType, DefaultVersionInfo>> = {}
|
||||
|
||||
/** 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<backendModule.UserOrOrganization | null> {
|
||||
const response = await this.get<backendModule.UserOrOrganization>(
|
||||
@ -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<backendModule.ListedProject[]> {
|
||||
const response = await this.get<ListProjectsResponseBody>(
|
||||
@ -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<backendModule.File[]> {
|
||||
const response = await this.get<ListFilesResponseBody>(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<backendModule.SecretInfo[]> {
|
||||
const response = await this.get<ListSecretsResponseBody>(
|
||||
@ -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<backendModule.Label[]> {
|
||||
const response = await this.get<ListTagsResponseBody>(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
|
||||
|
@ -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<T, Expected> = (<U>() => U extends T ? 1 : 2) extends <U>() => U extends Expected
|
||||
? 1
|
||||
: 2
|
||||
|
@ -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.
|
||||
|
@ -26,7 +26,7 @@ interface NewtypeVariant<TypeName extends string> {
|
||||
export type Newtype<T, TypeName extends string> = NewtypeVariant<TypeName> & 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 Newtype<unknown, string>> = T extends infer U & NewtypeVariant<T['_$type']>
|
||||
? U
|
||||
: NotNewtype & Omit<T, '_$type'>
|
||||
|
@ -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<Logger>({} as Logger)
|
||||
|
||||
|
@ -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}. */
|
||||
|
@ -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)
|
||||
|
2
app/ide-desktop/lib/types/globals.d.ts
vendored
2
app/ide-desktop/lib/types/globals.d.ts
vendored
@ -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
|
||||
|
@ -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. */
|
||||
|
679
package-lock.json
generated
679
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user