From da8fdd574cf21c1ed5a9142cae84ac49262fc5a4 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Thu, 28 Nov 2024 12:52:18 +0700 Subject: [PATCH] Fix up Typescript errors * MaybePromise = T | Promise no longer exported from svelte-kit * svelte/store no longer exports Invalidator type (now () => void) * svelte-exmarkdown v4 needed for correct Svelte 5 component types * svelte-exmarkdown renderer prop needs casting as Component so that Typescript won't complain about incompatible prop types * Components no longer have a .render method, instead you import the render function from Svelte and call it * svelte/compiler no longer exports walk, we're supposed to use estree-walker instead * estree-walker doesn't think nodes should have 'Element' type, as its types are designed for JS syntax trees, not Svelte component trees * Could not get Typescript to accept our componentMap in emails.ts, as each component had incompatible props. Punted by declaring its type as Record. --- frontend/package.json | 3 +- frontend/pnpm-lock.yaml | 33 +++---------------- .../ProjectConfidentialityCombobox.svelte | 3 +- .../components/Users/CreateUserModal.svelte | 5 +-- .../src/lib/email/emailRenderer.server.ts | 24 ++++++++------ frontend/src/lib/forms/Checkbox.svelte | 3 +- frontend/src/lib/forms/FormError.svelte | 3 +- frontend/src/lib/forms/FormField.svelte | 3 +- .../src/lib/forms/RadioButtonGroup.svelte | 3 +- frontend/src/lib/i18n/index.ts | 3 +- frontend/src/lib/otel/otel.server.ts | 4 +-- .../[project_code]/OpenInFlexModal.svelte | 3 +- .../project/create/+page.svelte | 4 +-- frontend/src/routes/email/+server.ts | 11 ++++--- frontend/src/routes/email/emails.ts | 3 +- 15 files changed, 49 insertions(+), 59 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a44ede266..c34fc4303 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -96,11 +96,12 @@ "@vitejs/plugin-basic-ssl": "^1.1.0", "css-tree": "^2.3.1", "e2e-mailbox": "1.1.5", + "estree-walker": "^3.0.3", "js-cookie": "^3.0.5", "just-order-by": "^1.0.0", "mjml": "^4.15.3", "set-cookie-parser": "^2.6.0", - "svelte-exmarkdown": "^3.0.5", + "svelte-exmarkdown": "^4.0.1", "svelte-intl-precompile": "^0.12.3", "sveltekit-search-params": "^2.1.2", "tus-js-client": "^4.1.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index b25eae111..24955ea91 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: e2e-mailbox: specifier: 1.1.5 version: 1.1.5 + estree-walker: + specifier: ^3.0.3 + version: 3.0.3 js-cookie: specifier: ^3.0.5 version: 3.0.5 @@ -111,8 +114,8 @@ importers: specifier: ^2.6.0 version: 2.6.0 svelte-exmarkdown: - specifier: ^3.0.5 - version: 3.0.5(svelte@5.2.8) + specifier: ^4.0.1 + version: 4.0.1(svelte@5.2.8) svelte-intl-precompile: specifier: ^0.12.3 version: 0.12.3(@babel/core@7.24.4)(svelte@5.2.8) @@ -5004,9 +5007,6 @@ packages: remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-rehype@11.1.0: - resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==} - remark-rehype@11.1.1: resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} @@ -5313,11 +5313,6 @@ packages: svelte: optional: true - svelte-exmarkdown@3.0.5: - resolution: {integrity: sha512-x0ELw7oTziBaJLFsdGZaoursycGK9HPzxRTRZ/rBi2KmseFG29ryyborJxFmeE0X6ORrEW1pRjoi8q41xpIeRQ==} - peerDependencies: - svelte: ^3.47.0 || ^4.0.0 || >=5.0.0-next.115 - svelte-exmarkdown@4.0.1: resolution: {integrity: sha512-Rbyp1qyYtGbseEwJ09cmdCeUYDwfXKv7MLTDeacb+/OCPYaTBdMy/3kvn8wF8w7v4JggyOboxhvJxoXEkmDxMQ==} peerDependencies: @@ -11673,14 +11668,6 @@ snapshots: transitivePeerDependencies: - supports-color - remark-rehype@11.1.0: - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.3 - mdast-util-to-hast: 13.1.0 - unified: 11.0.4 - vfile: 6.0.1 - remark-rehype@11.1.1: dependencies: '@types/hast': 3.0.4 @@ -12001,16 +11988,6 @@ snapshots: optionalDependencies: svelte: 5.2.8 - svelte-exmarkdown@3.0.5(svelte@5.2.8): - dependencies: - remark-gfm: 4.0.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.0 - svelte: 5.2.8 - unified: 11.0.4 - transitivePeerDependencies: - - supports-color - svelte-exmarkdown@4.0.1(svelte@5.2.8): dependencies: remark-gfm: 4.0.0 diff --git a/frontend/src/lib/components/Projects/ProjectConfidentialityCombobox.svelte b/frontend/src/lib/components/Projects/ProjectConfidentialityCombobox.svelte index c761d02e8..f1c3ffe5e 100644 --- a/frontend/src/lib/components/Projects/ProjectConfidentialityCombobox.svelte +++ b/frontend/src/lib/components/Projects/ProjectConfidentialityCombobox.svelte @@ -5,6 +5,7 @@ import { Icon } from '$lib/icons'; import Markdown from 'svelte-exmarkdown'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; export let value: boolean; @@ -21,6 +22,6 @@ {#if value}
- + } }]} />
{/if} diff --git a/frontend/src/lib/components/Users/CreateUserModal.svelte b/frontend/src/lib/components/Users/CreateUserModal.svelte index ac75bdddf..95c82c9cb 100644 --- a/frontend/src/lib/components/Users/CreateUserModal.svelte +++ b/frontend/src/lib/components/Users/CreateUserModal.svelte @@ -6,6 +6,7 @@ import CreateUser from '$lib/components/Users/CreateUser.svelte'; import Markdown from 'svelte-exmarkdown'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; import Icon from '$lib/icons/Icon.svelte'; import { createEventDispatcher } from 'svelte'; @@ -31,11 +32,11 @@
} }]} /> } }]} />
diff --git a/frontend/src/lib/email/emailRenderer.server.ts b/frontend/src/lib/email/emailRenderer.server.ts index 6d96e0e3f..780b3aaf3 100644 --- a/frontend/src/lib/email/emailRenderer.server.ts +++ b/frontend/src/lib/email/emailRenderer.server.ts @@ -1,36 +1,40 @@ -import { parse, walk } from 'svelte/compiler'; +import { parse } from 'svelte/compiler'; +import { render as svelte5Render } from 'svelte/server'; +import { walk } from 'estree-walker'; -import type { ComponentType } from 'svelte'; -import type {EmailTemplateProps} from '../../routes/email/emails'; +import type { Component } from 'svelte'; +import {EmailTemplate, type EmailTemplateProps} from '../../routes/email/emails'; import { LOCALE_CONTEXT_KEY } from '$lib/i18n'; -import type { TemplateNode } from 'svelte/types/compiler/interfaces'; import mjml2html from 'mjml'; import { readable } from 'svelte/store'; -type RenderResult = { head: string, html: string, css: string }; +type RenderResult = { head: string, html: string }; export type RenderEmailResult = { subject: string, html: string }; function getSubject(head: string): string { // using the Subject.svelte component a title should have been placed in the head. // we need to parse the head and find the title and extract the text - const {html} = parse(head, { filename: 'file.html' }); + const {html} = parse(head, { filename: 'file.html', modern: false }); + // CAUTION: modern: true will become default in Svelte 6, at which point the node.type below will change to RegularElement let subject: string | undefined; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument walk(html as Parameters[0], { - enter(node: TemplateNode, ..._) { - if (node.type === 'Element' && node.name === 'title') { + enter(node, ..._) { + if (node.type as string === 'Element' && 'name' in node && node.name === 'title') { + if ('children' in node && Array.isArray(node.children)) subject = node.children?.[0].data as string; } } } as Parameters[1]); if (!subject) throw new Error('subject not found'); + console.log(`Subject: ${subject}`); return subject; } -export function render(emailComponent: ComponentType, props: Omit, userLocale: string): RenderEmailResult { +export function render(emailComponent: Component, props: EmailTemplateProps, userLocale: string): RenderEmailResult { const context = new Map([[LOCALE_CONTEXT_KEY, readable(userLocale)]]); // eslint-disable-next-line - const result: RenderResult = (emailComponent as any).render(props, { context }) as RenderResult; + const result: RenderResult = svelte5Render((emailComponent as any), { props, context }); const mjmlResult = mjml2html(result.html, { validationLevel: 'soft' }); if (mjmlResult.errors) { console.error(mjmlResult.errors); diff --git a/frontend/src/lib/forms/Checkbox.svelte b/frontend/src/lib/forms/Checkbox.svelte index ba81955fa..76a15f01b 100644 --- a/frontend/src/lib/forms/Checkbox.svelte +++ b/frontend/src/lib/forms/Checkbox.svelte @@ -3,6 +3,7 @@ import FormFieldError from './FormFieldError.svelte'; import { randomFormId } from './utils'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; export let label: string; export let value: boolean; @@ -23,7 +24,7 @@ {#if description} {/if} diff --git a/frontend/src/lib/forms/FormError.svelte b/frontend/src/lib/forms/FormError.svelte index f025ec02f..09cc9d308 100644 --- a/frontend/src/lib/forms/FormError.svelte +++ b/frontend/src/lib/forms/FormError.svelte @@ -2,6 +2,7 @@ import Markdown from 'svelte-exmarkdown'; import type { ErrorMessage } from './types'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; export let error: ErrorMessage = undefined; export let markdown = false; @@ -11,7 +12,7 @@ {#if error} {#if markdown} - + } }]} /> {:else} {error} {/if} diff --git a/frontend/src/lib/forms/FormField.svelte b/frontend/src/lib/forms/FormField.svelte index 2f3819ea0..de252c72a 100644 --- a/frontend/src/lib/forms/FormField.svelte +++ b/frontend/src/lib/forms/FormField.svelte @@ -4,6 +4,7 @@ import { randomFormId } from './utils'; import Markdown from 'svelte-exmarkdown'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; import type { HelpLink } from '$lib/components/help'; import SupHelp from '$lib/components/help/SupHelp.svelte'; @@ -40,7 +41,7 @@ {#if description} {/if} diff --git a/frontend/src/lib/forms/RadioButtonGroup.svelte b/frontend/src/lib/forms/RadioButtonGroup.svelte index 7821febb8..33974da89 100644 --- a/frontend/src/lib/forms/RadioButtonGroup.svelte +++ b/frontend/src/lib/forms/RadioButtonGroup.svelte @@ -10,6 +10,7 @@ import FormFieldError from './FormFieldError.svelte'; import { randomFormId } from './utils'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; export let buttons: SingleRadioButton[]; export let label: string; // This is for the group as a whole @@ -44,7 +45,7 @@ {#if description} {/if} diff --git a/frontend/src/lib/i18n/index.ts b/frontend/src/lib/i18n/index.ts index e897c02e9..1963006b7 100644 --- a/frontend/src/lib/i18n/index.ts +++ b/frontend/src/lib/i18n/index.ts @@ -10,7 +10,6 @@ import { type Writable, type Unsubscriber, type Subscriber, - type Invalidator } from 'svelte/store'; import { availableLocales, registerAll } from '$locales'; import type { Get } from 'type-fest'; @@ -107,7 +106,7 @@ export function initI18n(locale: string): { t: I18n, locale: Writable } } export const locale: Readable = { - subscribe(run: Subscriber, invalidate?: Invalidator): Unsubscriber { + subscribe(run: Subscriber, invalidate?: () => void): Unsubscriber { return useLocale().subscribe(run, invalidate); } }; diff --git a/frontend/src/lib/otel/otel.server.ts b/frontend/src/lib/otel/otel.server.ts index e6e6c1330..3dd01437c 100644 --- a/frontend/src/lib/otel/otel.server.ts +++ b/frontend/src/lib/otel/otel.server.ts @@ -14,7 +14,7 @@ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { Resource } from '@opentelemetry/resources'; import { NodeSDK } from '@opentelemetry/sdk-node'; import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import type { MaybePromise, RequestEvent, NavigationEvent } from '@sveltejs/kit'; +import type { RequestEvent, NavigationEvent } from '@sveltejs/kit'; import { traceFetch as _traceFetch, traceEventAttributes, @@ -62,7 +62,7 @@ export function getRootTraceparent(): string | undefined { export async function traceRequest( event: RequestEvent, - responseBuilder: (requestSpan: Span) => MaybePromise, + responseBuilder: (requestSpan: Span) => Response | Promise, ): Promise { const tracparentContext = buildContextWithTraceparentBaggage(event); return context.with(tracparentContext, () => { diff --git a/frontend/src/routes/(authenticated)/project/[project_code]/OpenInFlexModal.svelte b/frontend/src/routes/(authenticated)/project/[project_code]/OpenInFlexModal.svelte index 097bf12b0..192556245 100644 --- a/frontend/src/routes/(authenticated)/project/[project_code]/OpenInFlexModal.svelte +++ b/frontend/src/routes/(authenticated)/project/[project_code]/OpenInFlexModal.svelte @@ -6,6 +6,7 @@ import OpenInFlexButton from './OpenInFlexButton.svelte'; import SendReceiveUrlField from './SendReceiveUrlField.svelte'; import { NewTabLinkRenderer } from '$lib/components/Markdown'; + import type {Component} from 'svelte'; export let project: Project; let modal: Modal; @@ -20,7 +21,7 @@

{$t('project_page.open_with_flex.button')}

- + } }]} />
diff --git a/frontend/src/routes/(authenticated)/project/create/+page.svelte b/frontend/src/routes/(authenticated)/project/create/+page.svelte index a5cb73764..8931ffea5 100644 --- a/frontend/src/routes/(authenticated)/project/create/+page.svelte +++ b/frontend/src/routes/(authenticated)/project/create/+page.svelte @@ -10,7 +10,7 @@ import { useNotifications } from '$lib/notify'; import { Duration, deriveAsync, deriveAsyncIfDefined } from '$lib/util/time'; import { getSearchParamValues } from '$lib/util/query-params'; - import { onMount } from 'svelte'; + import { onMount, type Component } from 'svelte'; import MemberBadge from '$lib/components/Badges/MemberBadge.svelte'; import { derived, writable, type Readable } from 'svelte/store'; import { concatAll } from '$lib/util/array'; @@ -287,7 +287,7 @@ {/each}
diff --git a/frontend/src/routes/email/+server.ts b/frontend/src/routes/email/+server.ts index 6b45c6755..2017ed3ed 100644 --- a/frontend/src/routes/email/+server.ts +++ b/frontend/src/routes/email/+server.ts @@ -2,12 +2,13 @@ import { render } from '$lib/email/emailRenderer.server'; import { json } from '@sveltejs/kit'; import type { RequestEvent } from './$types'; import { componentMap, EmailTemplate, type EmailTemplateProps } from './emails'; +import type {Component} from 'svelte'; export async function POST(event: RequestEvent): Promise { const request = await event.request.json() as EmailTemplateProps; - const {template, ...props} = request; - const component = componentMap[template]; - if (!component) throw new Error(`Invalid email template ${template}.}`); + const {...props} = request; + const component = componentMap[props.template] as unknown as Component; + if (!component) throw new Error(`Invalid email template ${props.template}.}`); return json(render(component, props, event.locals.activeLocale)); } @@ -17,7 +18,9 @@ export function GET(event: RequestEvent): Response { const {type, ...props} = { type: EmailTemplate.ForgotPassword, name: 'John Doe', + lifetime: '3 days', + template: EmailTemplate.ForgotPassword, resetUrl: 'https://example.com/reset' }; - return json(render(componentMap[type], props, event.locals.activeLocale)); + return json(render(componentMap[type] as unknown as any, props, event.locals.activeLocale)); } diff --git a/frontend/src/routes/email/emails.ts b/frontend/src/routes/email/emails.ts index 94b36ae51..451a1a4b4 100644 --- a/frontend/src/routes/email/emails.ts +++ b/frontend/src/routes/email/emails.ts @@ -1,6 +1,5 @@ import ForgotPassword from '$lib/email/ForgotPassword.svelte'; import NewAdmin from '$lib/email/NewAdmin.svelte'; -import type {ComponentType} from 'svelte'; import VerifyEmailAddress from '$lib/email/VerifyEmailAddress.svelte'; import PasswordChanged from '$lib/email/PasswordChanged.svelte'; import JoinProjectRequest from '$lib/email/JoinProjectRequest.svelte'; @@ -36,7 +35,7 @@ export const componentMap = { [EmailTemplate.CreateProjectRequest]: CreateProjectRequest, [EmailTemplate.ApproveProjectRequest]: ApproveProjectRequest, [EmailTemplate.UserAdded]: UserAdded, -} satisfies Record; +} satisfies Record; interface EmailTemplatePropsBase { template: T;