diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml
index 00169a2e17c..34628effeb2 100644
--- a/.github/workflows/pr-check.yaml
+++ b/.github/workflows/pr-check.yaml
@@ -202,3 +202,9 @@ jobs:
- name: Run unit tests
run: yarn test
+
+ - name: Run generate-types (required to run svelte check)
+ run: yarn generate-types
+
+ - name: Run svelte check
+ run: yarn svelte:check
diff --git a/package.json b/package.json
index 98434afa23b..61d93b95eb1 100644
--- a/package.json
+++ b/package.json
@@ -43,10 +43,12 @@
"test:renderer": "vitest run -r packages/renderer --passWithNoTests",
"test:watch": "vitest watch",
"watch": "node scripts/watch.js",
+ "generate-types": "node scripts/watch.js --types-only",
"format:check": "prettier --check '{extensions,packages,tests,types}/**/*.{ts,svelte}' 'extensions/*/scripts/build.js' 'website/*.js' 'website/src/**/*.{css,tsx}'",
"format:fix": "prettier --write '{extensions,packages,tests,types}/**/*.{ts,svelte}' 'extensions/*/scripts/build.js' 'website/src/**/*.{css,tsx}'",
"lint:check": "eslint . --ext js,ts,tsx",
"lint:fix": "eslint . --fix --ext js,ts,tsx",
+ "svelte:check": "svelte-check",
"typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
"typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json",
"typecheck:renderer": "npm run build:preload:types",
diff --git a/packages/renderer/src/lib/preferences/PreferencesRegistriesEditing.svelte b/packages/renderer/src/lib/preferences/PreferencesRegistriesEditing.svelte
index 8fa79ce88de..936248d021d 100644
--- a/packages/renderer/src/lib/preferences/PreferencesRegistriesEditing.svelte
+++ b/packages/renderer/src/lib/preferences/PreferencesRegistriesEditing.svelte
@@ -305,7 +305,7 @@ const processPasswordElement = (node: HTMLInputElement, registry: Registry) => {
{
test('Should expect invalid domain', async () => {
const result = urlValidator()('my_invalid_domain');
- expect(result).toBe('Please enter a valid URL');
+ expect(result[1]).toBe('Please enter a valid URL');
});
test('Should expect valid domain', async () => {
const result = urlValidator()('valid.com');
- expect(result).toBe(true);
+ expect(result[0]).toBe(true);
});
test('Should expect valid TLD domain with more than 3 char domains', async () => {
const result = urlValidator()('foobar.mydomain.science');
- expect(result).toBe(true);
+ expect(result[0]).toBe(true);
});
diff --git a/packages/renderer/src/lib/validation/FieldValidation.ts b/packages/renderer/src/lib/validation/FieldValidation.ts
index 14f9e0aecbb..ac39d4620d8 100644
--- a/packages/renderer/src/lib/validation/FieldValidation.ts
+++ b/packages/renderer/src/lib/validation/FieldValidation.ts
@@ -25,52 +25,73 @@ interface Validation {
message?: string;
}
-export function createFieldValidator(...validators) {
- const { subscribe, set } = writable({ dirty: false, valid: false, message: null } as Validation);
+export type UpdateAction = {
+ update(value: string): void;
+};
+
+export type ActivateFunction = {
+ (_node: unknown, binding: unknown): UpdateAction;
+};
+
+export function createFieldValidator(...validators): [SvelteStore, ActivateFunction] {
+ const validation: Validation = { dirty: false, valid: false, message: null };
+ const writableObject = writable(validation);
const validator = buildValidator(validators);
- function action(node, binding) {
- function validate(value, dirty) {
+ function action(_node, binding): UpdateAction {
+ function validate(value: string, dirty: boolean) {
const result = validator(value, dirty);
- set(result);
+ writableObject.set(result);
}
validate(binding, false);
return {
- update(value) {
+ update(value: string): void {
validate(value, value !== undefined);
},
};
}
- return [{ subscribe }, action];
+ const store: SvelteStore = writableObject;
+ const tuple: [SvelteStore, ActivateFunction] = [store, action];
+ return tuple;
}
-function buildValidator(validators) {
- return function validate(value, dirty) {
+function buildValidator(
+ validators: ((value: string) => [boolean, string])[],
+): (value: string, dirty: boolean) => Validation {
+ return function validate(value: string, dirty: boolean): Validation {
if (!validators || validators.length === 0) {
return { dirty, valid: true };
}
- const failing = validators.find(v => v(value) !== true);
+ const failing = validators.map(v => v(value)).find(value => value[0] !== true);
- return {
- dirty,
- valid: !failing,
- message: failing && failing(value),
- } as Validation;
+ if (!failing) {
+ return { dirty, valid: true, message: '' };
+ } else {
+ return {
+ dirty,
+ valid: false,
+ message: failing[1],
+ };
+ }
};
}
-export function requiredValidator() {
- return function required(value) {
- return (value !== undefined && value !== '') || 'Field is required';
+export function requiredValidator(): (value: string) => [boolean, string] {
+ return function required(value: string): [boolean, string] {
+ const valid = value !== undefined && value !== '';
+ const message = valid ? '' : 'Field is required';
+ return [valid, message];
};
}
-export function urlValidator() {
- return function url(value) {
- return validator.isURL(value) || 'Please enter a valid URL';
+export function urlValidator(): (value: string) => [boolean, string] {
+ return function url(value: string): [boolean, string] {
+ const valid = validator.isURL(value);
+ const message = valid ? '' : 'Please enter a valid URL';
+ return [valid, message];
};
}
diff --git a/scripts/watch.js b/scripts/watch.js
index d713e96e858..e564b2353c8 100644
--- a/scripts/watch.js
+++ b/scripts/watch.js
@@ -1,19 +1,17 @@
#!/usr/bin/env node
-const {createServer, build, createLogger} = require('vite');
+const { createServer, build, createLogger } = require('vite');
const electronPath = require('electron');
-const {spawn} = require('child_process');
-const {generateAsync} = require('dts-for-context-bridge');
+const { spawn } = require('child_process');
+const { generateAsync } = require('dts-for-context-bridge');
const path = require('path');
/** @type 'production' | 'development'' */
-const mode = process.env.MODE = process.env.MODE || 'development';
-
+const mode = (process.env.MODE = process.env.MODE || 'development');
/** @type {import('vite').LogLevel} */
const LOG_LEVEL = 'info';
-
/** @type {import('vite').InlineConfig} */
const sharedConfig = {
mode,
@@ -34,20 +32,19 @@ const stderrFilterPatterns = [
/**
* @param {{name: string; configFile: string; writeBundle: import('rollup').OutputPlugin['writeBundle'] }} param0
*/
-const getWatcher = ({name, configFile, writeBundle}) => {
+const getWatcher = ({ name, configFile, writeBundle }) => {
return build({
...sharedConfig,
configFile,
- plugins: [{name, writeBundle}],
+ plugins: [{ name, writeBundle }],
});
};
-
/**
* Start or restart App when source files are changed
* @param {{config: {server: import('vite').ResolvedServerOptions}}} ResolvedServerOptions
*/
-const setupMainPackageWatcher = ({config: {server}}) => {
+const setupMainPackageWatcher = ({ config: { server } }) => {
// Create VITE_DEV_SERVER_URL environment variable to pass it to the main process.
{
const protocol = server.https ? 'https:' : 'http:';
@@ -74,13 +71,13 @@ const setupMainPackageWatcher = ({config: {server}}) => {
spawnProcess = null;
}
- spawnProcess = spawn(String(electronPath), ['.'], { env: {...process.env, ELECTRON_IS_DEV: 1} });
+ spawnProcess = spawn(String(electronPath), ['.'], { env: { ...process.env, ELECTRON_IS_DEV: 1 } });
- spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: true}));
+ spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), { timestamp: true }));
spawnProcess.stderr.on('data', d => {
const data = d.toString().trim();
if (!data) return;
- const mayIgnore = stderrFilterPatterns.some((r) => r.test(data));
+ const mayIgnore = stderrFilterPatterns.some(r => r.test(data));
if (mayIgnore) return;
logger.error(data, { timestamp: true });
});
@@ -91,12 +88,11 @@ const setupMainPackageWatcher = ({config: {server}}) => {
});
};
-
/**
* Start or restart App when source files are changed
* @param {{ws: import('vite').WebSocketServer}} WebSocketServer
*/
-const setupPreloadPackageWatcher = ({ws}) =>
+const setupPreloadPackageWatcher = ({ ws }) =>
getWatcher({
name: 'reload-page-on-preload-package-change',
configFile: 'packages/preload/vite.config.js',
@@ -106,14 +102,15 @@ const setupPreloadPackageWatcher = ({ws}) =>
input: 'packages/preload/tsconfig.json',
output: 'packages/preload/exposedInMainWorld.d.ts',
});
-
- ws.send({
- type: 'full-reload',
- });
+ if (ws) {
+ ws.send({
+ type: 'full-reload',
+ });
+ }
},
});
-const setupPreloadDockerExtensionPackageWatcher = ({ws}) =>
+const setupPreloadDockerExtensionPackageWatcher = ({ ws }) =>
getWatcher({
name: 'reload-page-on-preload-docker-extension-package-change',
configFile: 'packages/preload-docker-extension/vite.config.js',
@@ -124,37 +121,48 @@ const setupPreloadDockerExtensionPackageWatcher = ({ws}) =>
output: 'packages/preload-docker-extension/exposedInDockerExtension.d.ts',
});
- ws.send({
- type: 'full-reload',
- });
+ if (ws) {
+ ws.send({
+ type: 'full-reload',
+ });
+ }
},
});
-
/**
* Start or restart App when source files are changed
* @param {{ws: import('vite').WebSocketServer}} WebSocketServer
*/
- const setupExtensionApiWatcher = (name) =>{
- let spawnProcess;
- const folderName = path.resolve(__dirname, '../extensions/' + name);
+const setupExtensionApiWatcher = name => {
+ let spawnProcess;
+ const folderName = path.resolve(__dirname, '../extensions/' + name);
console.log('dirname is', folderName);
- spawnProcess = spawn('yarn', ['--cwd', folderName, 'watch'], { shell: process.platform === 'win32'} );
+ spawnProcess = spawn('yarn', ['--cwd', folderName, 'watch'], { shell: process.platform === 'win32' });
- spawnProcess.stdout.on('data', d => d.toString().trim() && console.warn(d.toString(), {timestamp: true}));
- spawnProcess.stderr.on('data', d => {
- const data = d.toString().trim();
- if (!data) return;
- console.error(data, { timestamp: true });
- });
+ spawnProcess.stdout.on('data', d => d.toString().trim() && console.warn(d.toString(), { timestamp: true }));
+ spawnProcess.stderr.on('data', d => {
+ const data = d.toString().trim();
+ if (!data) return;
+ console.error(data, { timestamp: true });
+ });
- // Stops the watch script when the application has been quit
- spawnProcess.on('exit', process.exit);
-
- };
+ // Stops the watch script when the application has been quit
+ spawnProcess.on('exit', process.exit);
+};
(async () => {
+ // grab arguments
+ const args = process.argv.slice(2);
+ const generateTypesOnly = args.includes('--types-only');
+ // If types-only is passed, we don't watch for changes but only do generation
+ if (generateTypesOnly) {
+ delete sharedConfig.build.watch;
+ await setupPreloadPackageWatcher({ ws: undefined });
+ await setupPreloadDockerExtensionPackageWatcher({ ws: undefined });
+ return;
+ }
+
try {
const viteDevServer = await createServer({
...sharedConfig,