diff --git a/.eslintrc.json b/.eslintrc.json index e93b80a9047..2843ee0f99f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -57,6 +57,7 @@ "ignorePatterns": [ "packages/preload/exposedInMainWorld.d.ts", "packages/preload-docker-extension/exposedInDockerExtension.d.ts", + "packages/preload-webview/exposedInWebview.d.ts", "node_modules/**", "**/dist/**" ], diff --git a/package.json b/package.json index ea18d912be6..7de7bc57752 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ] }, "scripts": { - "build": "npm run build:main && npm run build:preload && npm run build:preload-docker-extension && npm run build:renderer && npm run build:extensions", + "build": "npm run build:main && npm run build:preload && npm run build:preload-docker-extension && npm run build:preload-webview && npm run build:renderer && npm run build:extensions", "build:main": "cd ./packages/main && vite build", "build:extensions": "npm run build:extensions:compose && npm run build:extensions:docker && npm run build:extensions:lima && npm run build:extensions:podman && npm run build:extensions:kubecontext && npm run build:extensions:kind && npm run build:extensions:registries && npm run build:extensions:kubectl-cli", "build:extensions:compose": "cd ./extensions/compose && npm run build", @@ -35,14 +35,15 @@ "build:extension-api": "cd ./packages/extension-api && vite build", "build:preload": "cd ./packages/preload && vite build", "build:preload-docker-extension": "cd ./packages/preload-docker-extension && vite build", - "build:preload:types": "dts-cb -i \"packages/preload/tsconfig.json\" -o \"packages/preload/exposedInMainWorld.d.ts\" && dts-cb -i \"packages/preload-docker-extension/tsconfig.json\" -o \"packages/preload-docker-extension/exposedInDockerExtension.d.ts\"", + "build:preload-webview": "cd ./packages/preload-webview && vite build", + "build:preload:types": "dts-cb -i \"packages/preload/tsconfig.json\" -o \"packages/preload/exposedInMainWorld.d.ts\" && dts-cb -i \"packages/preload-docker-extension/tsconfig.json\" -o \"packages/preload-docker-extension/exposedInDockerExtension.d.ts\" && dts-cb -i \"packages/preload-webview/tsconfig.json\" -o \"packages/preload-webview/exposedInWebview.d.ts\"", "build:renderer": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite -c packages/renderer/vite.config.js build", "compile": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.cjs --dir --config.asar=false", "compile:next": "cross-env MODE=production npm run build && electron-builder build --publish always --config .electron-builder.config.cjs", "compile:pull-request": "cross-env MODE=production npm run build && electron-builder build --publish never --config .electron-builder.config.cjs", "compile:current": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.cjs", "test": "npm run test:unit && npm run test:e2e", - "test:unit": "npm run test:main && npm run test:preload && npm run test:preload-docker-extension && npm run test:renderer && npm run test:tools && npm run test:extensions", + "test:unit": "npm run test:main && npm run test:preload && npm run test:preload-docker-extension && npm run test:preload-webview && npm run test:renderer && npm run test:tools && npm run test:extensions", "test:e2e": "npm run test:e2e:build && npm run test:e2e:run", "test:e2e:build": "cross-env NODE_ENV=development MODE=development DEBUG=pw:browser npm run build", "test:e2e:run": "xvfb-maybe --auto-servernum --server-args='-screen 0 1280x960x24' -- vitest run tests/src/ --pool=threads --poolOptions.threads.singleThread --no-file-parallelism", @@ -53,6 +54,7 @@ "test:main": "vitest run -r packages/main --passWithNoTests --coverage", "test:preload": "vitest run -r packages/preload --passWithNoTests --coverage", "test:preload-docker-extension": "vitest run -r packages/preload-docker-extension --passWithNoTests --coverage", + "test:preload-webview": "vitest run -r packages/preload-webview --coverage", "test:extensions": "vitest run -r extensions --passWithNoTests --coverage && npm run test:extensions:compose && npm run test:extensions:kind && npm run test:extensions:docker && npm run test:extensions:lima && npm run test:extensions:kube && npm run test:extensions:podman && npm run test:extensions:registries && npm run test:extensions:kubectl-cli", "test:extensions:kind": "vitest run -r extensions/kind --passWithNoTests --coverage ", "test:extensions:compose": "vitest run -r extensions/compose --passWithNoTests --coverage", @@ -77,6 +79,7 @@ "typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json", "typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json", "typecheck:preload-dd-extension": "tsc --noEmit -p packages/preload-docker-extension/tsconfig.json", + "typecheck:preload-webview": "tsc --noEmit -p packages/preload-webview/tsconfig.json", "typecheck:renderer": "npm run build:preload:types && tsc --noEmit -p packages/renderer/tsconfig.json", "typecheck:extensions": "npm run typecheck:extensions:compose && npm run typecheck:extensions:kind && npm run typecheck:extensions:docker && npm run typecheck:extensions:lima && npm run typecheck:extensions:kube-context && npm run typecheck:extensions:podman && npm run typecheck:extensions:registries && npm run typecheck:extensions:kubectl-cli", "typecheck:extensions:kind": "tsc --noEmit --project extensions/kind", @@ -87,7 +90,7 @@ "typecheck:extensions:podman": "tsc --noEmit --project extensions/podman", "typecheck:extensions:registries": "tsc --noEmit --project extensions/registries", "typecheck:extensions:kubectl-cli": "tsc --noEmit --project extensions/kubectl-cli", - "typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:preload-dd-extension && npm run typecheck:extensions", + "typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:preload-dd-extension && npm run typecheck:preload-webview && npm run typecheck:extensions", "website:build": "cd website && yarn run docusaurus build", "website:prod": "cd website && yarn run docusaurus build && yarn serve", "website:dev": "cd website && yarn run docusaurus start", diff --git a/packages/preload-webview/.gitignore b/packages/preload-webview/.gitignore new file mode 100644 index 00000000000..6359cb6ad72 --- /dev/null +++ b/packages/preload-webview/.gitignore @@ -0,0 +1 @@ +exposedInWebview.d.ts diff --git a/packages/preload-webview/src/index.spec.ts b/packages/preload-webview/src/index.spec.ts new file mode 100644 index 00000000000..cbfb38d8bd3 --- /dev/null +++ b/packages/preload-webview/src/index.spec.ts @@ -0,0 +1,57 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { beforeEach, expect, test, vi } from 'vitest'; +import { init } from '.'; +import * as webviewPreload from './webview-preload'; + +vi.mock('./webview-preload', async () => { + return { + WebviewPreload: vi.fn().mockImplementation(() => { + return { + init: vi.fn().mockResolvedValue(undefined), + }; + }), + }; +}); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +test('check call constructor with correct web id by parsing window.location.search', () => { + (window as any).location = { + search: '?webviewId=123', + }; + + init(); + + // expect constructor has been called with the correct webviewID + expect(webviewPreload.WebviewPreload).toHaveBeenCalledWith('123'); +}); + +test('check error if invalid window.location.search', () => { + (window as any).location = { + search: '', + }; + + // expect failure as webviewId is not defined + expect(() => init()).toThrow('The webviewId is not defined'); +}); diff --git a/packages/preload-webview/src/index.ts b/packages/preload-webview/src/index.ts new file mode 100644 index 00000000000..9084cc33c93 --- /dev/null +++ b/packages/preload-webview/src/index.ts @@ -0,0 +1,41 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { WebviewPreload } from './webview-preload'; + +/** + * @module preload + */ +export const init = () => { + // parse the query string and grab the webviewId parameter + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const webviewId = urlParams.get('webviewId') ?? undefined; + + if (!webviewId) { + throw new Error('The webviewId is not defined'); + } + // create the webviewPreload object and call the init method + const webviewPreload = new WebviewPreload(webviewId); + webviewPreload.init().catch((error: unknown) => console.error('Error while initializing the exposure', error)); +}; + +// do not call init methd in case of testing +if (!process.env.VITEST) { + init(); +} diff --git a/packages/preload-webview/src/webview-preload.spec.ts b/packages/preload-webview/src/webview-preload.spec.ts new file mode 100644 index 00000000000..7731aa9b0d5 --- /dev/null +++ b/packages/preload-webview/src/webview-preload.spec.ts @@ -0,0 +1,217 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import type { MockInstance } from 'vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { WebviewPreload } from './webview-preload'; +import type { WebviewInfo } from '../../main/src/plugin/api/webview-info'; +import type { IpcRendererEvent } from 'electron'; +import { ipcRenderer, contextBridge } from 'electron'; + +let webviewPreload: TestWebwiewPreload; + +class TestWebwiewPreload extends WebviewPreload { + async getWebviews(): Promise { + return super.getWebviews(); + } + buildApi(): unknown { + return super.buildApi(); + } + ipcRendererOn(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) { + super.ipcRendererOn(channel, listener); + } + async ipcInvoke(channel: string, ...args: unknown[]): Promise { + return super.ipcInvoke(channel, ...args); + } + changeContent() { + super.changeContent(); + } + postWebviewMessage(message: unknown) { + super.postWebviewMessage(message); + } +} + +const webviewInfo: WebviewInfo = { + id: '123', + viewType: 'test', + sourcePath: 'testPath', + icon: 'testIcon', + name: 'test', + html: 'hello world', + uuid: '12-12-12-12', + state: { foo: 'bar' }, +}; + +vi.mock('electron', async () => { + return { + contextBridge: { + exposeInMainWorld: vi.fn(), + }, + ipcRenderer: { + on: vi.fn(), + emit: vi.fn(), + handle: vi.fn(), + invoke: vi.fn(), + }, + ipcMain: { + on: vi.fn(), + emit: vi.fn(), + handle: vi.fn(), + }, + }; +}); + +let spyIpcRendererOn: MockInstance< + [channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void], + void +>; +let spyBuildApi: MockInstance<[], unknown>; +beforeEach(() => { + vi.resetAllMocks(); + webviewPreload = new TestWebwiewPreload('123'); + // mock the window object + (window as any).addEventListener = vi.fn(); + + // override the getWebviews method + const spyGetWebviews = vi.spyOn(webviewPreload, 'getWebviews'); + spyGetWebviews.mockResolvedValue([webviewInfo]); + + // override buildApi method + spyBuildApi = vi.spyOn(webviewPreload, 'buildApi'); + spyBuildApi.mockReturnValue(() => {}); + + // override ipcRendererOn + spyIpcRendererOn = vi.spyOn(webviewPreload, 'ipcRendererOn'); + spyIpcRendererOn.mockImplementation(() => {}); +}); + +test('check init method', async () => { + await webviewPreload.init(); + + // check it adds addEventListener to the window object + expect(window.addEventListener).toHaveBeenCalledWith('DOMContentLoaded', expect.any(Function)); + + // check exposure of the function to javascript + expect(vi.mocked(contextBridge.exposeInMainWorld)).toHaveBeenCalledWith( + 'acquirePodmanDesktopApi', + expect.any(Function), + ); + + // check we register 2 event listener on ipcRenderer + expect(spyIpcRendererOn).toHaveBeenCalledWith('webview-post-message', expect.any(Function)); + expect(spyIpcRendererOn).toHaveBeenCalledWith('webview-update-html', expect.any(Function)); +}); + +describe('ipcInvoke', () => { + test('check custom ipcInvoke method', async () => { + // override the ipcRenderer.invoke method + const spyIpcRendererInvoke = vi.spyOn(ipcRenderer, 'invoke'); + + const fakeResult = 'foo'; + // fake remote implementation sending no error and foo as result + spyIpcRendererInvoke.mockImplementation(() => Promise.resolve({ result: fakeResult, error: undefined })); + + const result = await webviewPreload.ipcInvoke('test', 'arg1'); + + expect(result).toStrictEqual(fakeResult); + expect(spyIpcRendererInvoke).toHaveBeenCalledWith('test', 'arg1'); + }); + + test('check custom ipcInvoke method with error', async () => { + // override the ipcRenderer.invoke method + const spyIpcRendererInvoke = vi.spyOn(ipcRenderer, 'invoke'); + + const fakeError = new Error('dummy error'); + // fake remote implementation sending no error and foo as result + spyIpcRendererInvoke.mockImplementation(() => Promise.resolve({ result: undefined, error: fakeError })); + + await expect(webviewPreload.ipcInvoke('test', 'arg1')).rejects.toThrow('dummy error'); + + expect(spyIpcRendererInvoke).toHaveBeenCalledWith('test', 'arg1'); + }); +}); + +test('check changeContent', async () => { + // spy document.write method + const spyDocumentWrite = vi.spyOn(document, 'write'); + + // override window.addEventListener to keep the callback + const spyAddEventListener = vi.spyOn(window, 'addEventListener'); + + // call changeContent it should not do anything as we're missing all conditions + webviewPreload.changeContent(); + + // check document.write method has not been called + expect(spyDocumentWrite).not.toHaveBeenCalled(); + + // call init to set the webviewInfo + await webviewPreload.init(); + + const callback: any = spyAddEventListener.mock.calls[0][1]; + + // call the callback that should call changeContent as we'll have two mandatory fields + callback(); + + // wait timeout execute + await new Promise(resolve => setTimeout(resolve, 100)); + + // check the document.write method has been called + expect(spyDocumentWrite).toHaveBeenCalledWith(` +hello world`); +}); + +test('check buildApi', async () => { + // spy postWebviewMessage + const spyPostWebviewMessage = vi.spyOn(webviewPreload, 'postWebviewMessage'); + + // spy ipcInvoke + const spyIpcInvoke = vi.spyOn(webviewPreload, 'ipcInvoke'); + spyIpcInvoke.mockImplementation(() => Promise.resolve({ result: undefined, error: undefined })); + + // remove spy on buildApi + spyBuildApi.mockRestore(); + + // init to set the webviewInfo + await webviewPreload.init(); + + const buildFunction: any = webviewPreload.buildApi(); + + // call the build function + const podmanDesktopApi = buildFunction(); + + // check the podmanDesktopApi object is returned + //get state that should be the one from the webviewInfo + expect(podmanDesktopApi.getState()).toStrictEqual(webviewInfo.state); + + //post message + podmanDesktopApi.postMessage('test'); + + expect(spyPostWebviewMessage).toHaveBeenCalledWith({ command: 'onmessage', data: 'test' }); + + // clear calls on spyIpcInvoke + spyIpcInvoke.mockClear(); + + //set state + const newFakeState = { updated: 'state' }; + podmanDesktopApi.setState(newFakeState); + + // check ipcInvoke has been called + expect(spyIpcInvoke).toHaveBeenCalledWith('webviewRegistry:update-state', webviewInfo.id, newFakeState); +}); diff --git a/packages/preload-webview/src/webview-preload.ts b/packages/preload-webview/src/webview-preload.ts new file mode 100644 index 00000000000..ac42d0b92db --- /dev/null +++ b/packages/preload-webview/src/webview-preload.ts @@ -0,0 +1,147 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import type { WebviewInfo } from '../../main/src/plugin/api/webview-info'; + +import type { IpcRendererEvent } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; + +interface ErrorMessage { + name: string; + message: string; + extra: unknown; +} + +export class WebviewPreload { + #webviewId: string; + #webviewInfo: WebviewInfo | undefined; + #domLoaded: boolean = false; + #acquiredApi: boolean = false; + + constructor(webviewId: string) { + this.#webviewId = webviewId; + } + + protected decodeError(error: ErrorMessage) { + const e = new Error(error.message); + e.name = error.name; + Object.assign(e, error.extra); + return e; + } + + protected async ipcInvoke(channel: string, ...args: unknown[]): Promise { + const { error, result } = await ipcRenderer.invoke(channel, ...args); + if (error) { + throw this.decodeError(error); + } + return result; + } + + protected postWebviewMessage(message: unknown) { + this.ipcInvoke('webviewRegistry:post-message', this.#webviewInfo?.id, message).catch((error: unknown) => + console.error('Error while posting message', error), + ); + } + + protected changeContent() { + if (!this.#webviewInfo) { + return; + } + if (!this.#domLoaded) { + return; + } + let webviewHtmlContent = ''; + if (this.#webviewInfo) { + webviewHtmlContent = this.#webviewInfo.html; + } + // use a timeout to perform the update + setTimeout(() => { + const webviewContentHtml = new DOMParser().parseFromString(webviewHtmlContent, 'text/html'); + + webviewContentHtml.documentElement.style.setProperty('font-family', 'Montserrat'); + + const htmlContent = '\n' + webviewContentHtml.documentElement.outerHTML; + + document.open(); + document.write(htmlContent); + document.close(); + }, 0); + } + + // build the function that will be exposed to the webview for getState/postMessage/setState + protected buildApi(): unknown { + return () => { + // initialize the state from the webview + let state: unknown = this.#webviewInfo?.state ?? {}; + if (this.#acquiredApi) { + throw new Error('An instance of the Podman Desktop API has already been acquired'); + } + // can only be called once; + this.#acquiredApi = true; + return Object.freeze({ + getState: () => { + return state; + }, + postMessage: (msg: unknown) => { + return this.postWebviewMessage({ command: 'onmessage', data: msg }); + }, + setState: async (newState: unknown) => { + state = newState; + // need to send back the state to the main process + this.ipcInvoke('webviewRegistry:update-state', this.#webviewInfo?.id, newState).catch((error: unknown) => { + console.error('Error while updating the state', error); + }); + }, + }); + }; + } + + protected getWebviews(): Promise { + return this.ipcInvoke('webviewRegistry:listWebviews') as Promise; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected ipcRendererOn(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) { + ipcRenderer.on(channel, listener); + } + + async init(): Promise { + window.addEventListener('DOMContentLoaded', () => { + this.#domLoaded = true; + this.changeContent(); + }); + + contextBridge.exposeInMainWorld('acquirePodmanDesktopApi', this.buildApi()); + + const webviews: WebviewInfo[] = await this.getWebviews(); + this.#webviewInfo = webviews.find(webview => webview.id === this.#webviewId); + this.changeContent(); + + // broadcast messages from the main process to the webview + this.ipcRendererOn('webview-post-message', (_, target: { message: unknown }) => { + window.dispatchEvent(new MessageEvent('message', { data: target.message })); + }); + + this.ipcRendererOn('webview-update-html', (_, html) => { + if (this.#webviewInfo) { + this.#webviewInfo.html = html; + this.changeContent(); + } + }); + } +} diff --git a/packages/preload-webview/tsconfig.json b/packages/preload-webview/tsconfig.json new file mode 100644 index 00000000000..dd34f902617 --- /dev/null +++ b/packages/preload-webview/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "sourceMap": false, + "moduleResolution": "Node", + "skipLibCheck": true, + "strict": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + + "types": ["node"], + + "baseUrl": ".", + "paths": { + "/@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "exposedInWebview.d.ts", "../../types/**/*.d.ts"] +} diff --git a/packages/preload-webview/vite.config.js b/packages/preload-webview/vite.config.js new file mode 100644 index 00000000000..8c323e1b0b8 --- /dev/null +++ b/packages/preload-webview/vite.config.js @@ -0,0 +1,65 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { chrome } from '../../.electron-vendors.cache.json'; +import { join } from 'path'; +import { builtinModules } from 'module'; +import { coverageConfig } from '../../vitest-shared-extensions.config'; + +const PACKAGE_ROOT = __dirname; +const PACKAGE_NAME = 'preload-webview'; + +/** + * @type {import('vite').UserConfig} + * @see https://vitejs.dev/config/ + */ +const config = { + mode: process.env.MODE, + root: PACKAGE_ROOT, + envDir: process.cwd(), + resolve: { + alias: { + '/@/': join(PACKAGE_ROOT, 'src') + '/', + }, + }, + build: { + sourcemap: 'inline', + target: `chrome${chrome}`, + outDir: 'dist', + assetsDir: '.', + minify: process.env.MODE !== 'development', + lib: { + entry: 'src/index.ts', + formats: ['cjs'], + }, + rollupOptions: { + external: ['electron', ...builtinModules.flatMap(p => [p, `node:${p}`])], + output: { + entryFileNames: '[name].cjs', + }, + }, + emptyOutDir: true, + reportCompressedSize: false, + }, + test: { + environment: 'jsdom', + ...coverageConfig(PACKAGE_ROOT, PACKAGE_NAME), + }, +}; + +export default config; diff --git a/scripts/watch.mjs b/scripts/watch.mjs index f53e46993cc..075bbe3d53f 100644 --- a/scripts/watch.mjs +++ b/scripts/watch.mjs @@ -1,5 +1,23 @@ #!/usr/bin/env node +/********************************************************************** + * Copyright (C) 2022-2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + import { createServer, build, createLogger } from 'vite'; import electronPath from 'electron'; import { spawn } from 'child_process'; @@ -141,6 +159,26 @@ const setupPreloadDockerExtensionPackageWatcher = ({ ws }) => }, }); + const setupPreloadWebviewPackageWatcher = ({ ws }) => + getWatcher({ + name: 'reload-page-on-preload-webview-package-change', + configFile: 'packages/preload-webview/vite.config.js', + writeBundle() { + // Generating exposedInWebview.d.ts when preload package is changed. + generateAsync({ + input: 'packages/preload-webview/tsconfig.json', + output: 'packages/preload-webview/exposedInWebview.d.ts', + }); + + if (ws) { + ws.send({ + type: 'full-reload', + }); + } + }, + }); + + /** * Start or restart App when source files are changed * @param {{ws: import('vite').WebSocketServer}} WebSocketServer @@ -192,6 +230,7 @@ const setupExtensionApiWatcher = name => { } await setupPreloadPackageWatcher(viteDevServer); await setupPreloadDockerExtensionPackageWatcher(viteDevServer); + await setupPreloadWebviewPackageWatcher(viteDevServer); await setupMainPackageWatcher(viteDevServer); } catch (e) { console.error(e);