From b3abaf8f1ac63ac3e7c7c934c6ffb5c10d52d695 Mon Sep 17 00:00:00 2001 From: Luca Peruzzo Date: Sun, 24 Nov 2024 19:22:14 +0100 Subject: [PATCH] fix reactivity --- dist/login/DefaultPage.svelte | 135 -------- dist/login/DefaultPage.svelte.d.ts | 12 - dist/login/KcContext/KcContext.d.ts | 1 - dist/login/KcContext/KcContext.js | 1 - dist/login/KcContext/index.d.ts | 1 - dist/login/KcContext/index.js | 1 - dist/login/Template.svelte | 222 ------------ dist/login/Template.svelte.d.ts | 6 - dist/login/Template.useInitialize.d.ts | 14 - dist/login/Template.useInitialize.js | 55 --- dist/login/TemplateProps.d.ts | 19 - dist/login/TemplateProps.js | 1 - ...ddRemoveButtonsMultiValuedAttribute.svelte | 57 --- ...oveButtonsMultiValuedAttribute.svelte.d.ts | 17 - dist/login/components/FieldErrors.svelte | 28 -- dist/login/components/FieldErrors.svelte.d.ts | 12 - dist/login/components/GroupLabel.svelte | 67 ---- dist/login/components/GroupLabel.svelte.d.ts | 14 - dist/login/components/InputFieldByType.svelte | 44 --- .../components/InputFieldByType.svelte.d.ts | 4 - .../components/InputFieldByTypeProps.d.ts | 15 - .../login/components/InputFieldByTypeProps.js | 1 - dist/login/components/InputTag.svelte | 100 ------ dist/login/components/InputTag.svelte.d.ts | 7 - dist/login/components/InputTagSelects.svelte | 104 ------ .../components/InputTagSelects.svelte.d.ts | 4 - dist/login/components/PasswordWrapper.svelte | 40 --- .../components/PasswordWrapper.svelte.d.ts | 12 - dist/login/components/SelectTag.svelte | 72 ---- dist/login/components/SelectTag.svelte.d.ts | 4 - dist/login/components/TermsAcceptance.svelte | 54 --- .../components/TermsAcceptance.svelte.d.ts | 12 - dist/login/components/TextareaTag.svelte | 40 --- dist/login/components/TextareaTag.svelte.d.ts | 4 - .../components/UserProfileFormFields.svelte | 104 ------ .../UserProfileFormFields.svelte.d.ts | 6 - .../UserProfileFormFieldsProps.d.ts | 26 -- .../components/UserProfileFormFieldsProps.js | 2 - dist/login/components/inputLabel.d.ts | 3 - dist/login/components/inputLabel.js | 12 - dist/login/i18n/i18n.d.ts | 15 - dist/login/i18n/i18n.js | 1 - dist/login/i18n/i18nBuilder.d.ts | 18 - dist/login/i18n/i18nBuilder.js | 26 -- dist/login/i18n/index.d.ts | 1 - dist/login/i18n/index.js | 1 - dist/login/i18n/useI18n.d.ts | 28 -- dist/login/i18n/useI18n.js | 65 ---- dist/login/lib/useUserProfileForm.d.ts | 66 ---- dist/login/lib/useUserProfileForm.js | 65 ---- dist/login/pages/Code.svelte | 45 --- dist/login/pages/Code.svelte.d.ts | 6 - dist/login/pages/Login.svelte | 224 ------------ dist/login/pages/Login.svelte.d.ts | 6 - dist/login/pages/PageProps.d.ts | 9 - dist/login/pages/PageProps.js | 1 - dist/login/pages/Register.svelte | 136 -------- dist/login/pages/Register.svelte.d.ts | 11 - dist/tools/useConst.d.ts | 1 - dist/tools/useConst.js | 4 - dist/tools/useInsertLinkTags.d.ts | 12 - dist/tools/useInsertLinkTags.js | 62 ---- dist/tools/useInsertScriptTags.d.ts | 29 -- dist/tools/useInsertScriptTags.js | 77 ----- dist/tools/useReducer.d.ts | 2 - dist/tools/useReducer.js | 7 - dist/tools/useSetClassName.d.ts | 4 - dist/tools/useSetClassName.js | 15 - dist/tools/useState.d.ts | 2 - dist/tools/useState.js | 7 - package.json | 16 +- scripts/build.ts | 11 + scripts/link-in-app.ts | 187 ++++++++++ scripts/link-in-starter.ts | 114 ++++++ scripts/shared/run.ts | 8 + scripts/tools/Deferred.ts | 41 +++ scripts/tools/StatefulObservable.ts | 54 +++ scripts/tools/fs.existsAsync.ts | 11 + scripts/tools/fs.rm.ts | 43 +++ scripts/tools/getThisCodebaseRootDirPath.ts | 22 ++ scripts/tools/removeNodeModules.ts | 27 ++ scripts/tools/waitForThrottle.ts | 45 +++ scripts/watch.ts | 68 ++++ src/bin/add-story.ts | 121 +++++++ src/bin/core.ts | 10 + src/bin/eject-page.ts | 326 ++++++++++++++++++ .../boilerplate/KcContext.ts | 11 + .../boilerplate/KcPage.svelte | 32 ++ .../boilerplate/KcPageStory.ts | 0 .../boilerplate/i18n.ts | 11 + src/bin/initialize-account-theme/index.ts | 1 + .../initialize-account-theme.ts | 87 +++++ ...pdateAccountThemeImplementationInConfig.ts | 93 +++++ src/bin/main.ts | 36 ++ src/bin/tools/SemVer.ts | 107 ++++++ src/bin/tools/String.prototype.replaceAll.ts | 31 ++ src/bin/tools/crawl.ts | 32 ++ src/bin/tools/fs.rmSync.ts | 34 ++ src/bin/tools/getThisCodebaseRootDirPath.ts | 19 + src/bin/tools/kebabCaseToSnakeCase.ts | 7 + src/bin/tools/nodeModulesBinDirPath.ts | 38 ++ src/bin/tools/readThisNpmPackageVersion.ts | 22 ++ src/bin/tools/runPrettier.ts | 118 +++++++ src/bin/tools/transformCodebase.ts | 81 +++++ src/bin/tools/transformCodebase_async.ts | 82 +++++ src/bin/tsconfig.json | 20 ++ src/bin/update-kc-gen.ts | 124 +++++++ ...ddRemoveButtonsMultiValuedAttribute.svelte | 2 +- src/lib/login/components/FieldErrors.svelte | 33 +- .../login/components/InputFieldByType.svelte | 24 +- src/lib/login/components/InputTag.svelte | 15 +- .../login/components/InputTagSelects.svelte | 7 + .../login/components/PasswordWrapper.svelte | 6 +- .../components/UserProfileFormFields.svelte | 33 +- src/lib/login/lib/useUserProfileForm.ts | 6 +- yarn.lock | 209 ++++++++++- 116 files changed, 2376 insertions(+), 2328 deletions(-) delete mode 100644 dist/login/DefaultPage.svelte delete mode 100644 dist/login/DefaultPage.svelte.d.ts delete mode 100644 dist/login/KcContext/KcContext.d.ts delete mode 100644 dist/login/KcContext/KcContext.js delete mode 100644 dist/login/KcContext/index.d.ts delete mode 100644 dist/login/KcContext/index.js delete mode 100644 dist/login/Template.svelte delete mode 100644 dist/login/Template.svelte.d.ts delete mode 100644 dist/login/Template.useInitialize.d.ts delete mode 100644 dist/login/Template.useInitialize.js delete mode 100644 dist/login/TemplateProps.d.ts delete mode 100644 dist/login/TemplateProps.js delete mode 100644 dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte delete mode 100644 dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte.d.ts delete mode 100644 dist/login/components/FieldErrors.svelte delete mode 100644 dist/login/components/FieldErrors.svelte.d.ts delete mode 100644 dist/login/components/GroupLabel.svelte delete mode 100644 dist/login/components/GroupLabel.svelte.d.ts delete mode 100644 dist/login/components/InputFieldByType.svelte delete mode 100644 dist/login/components/InputFieldByType.svelte.d.ts delete mode 100644 dist/login/components/InputFieldByTypeProps.d.ts delete mode 100644 dist/login/components/InputFieldByTypeProps.js delete mode 100644 dist/login/components/InputTag.svelte delete mode 100644 dist/login/components/InputTag.svelte.d.ts delete mode 100644 dist/login/components/InputTagSelects.svelte delete mode 100644 dist/login/components/InputTagSelects.svelte.d.ts delete mode 100644 dist/login/components/PasswordWrapper.svelte delete mode 100644 dist/login/components/PasswordWrapper.svelte.d.ts delete mode 100644 dist/login/components/SelectTag.svelte delete mode 100644 dist/login/components/SelectTag.svelte.d.ts delete mode 100644 dist/login/components/TermsAcceptance.svelte delete mode 100644 dist/login/components/TermsAcceptance.svelte.d.ts delete mode 100644 dist/login/components/TextareaTag.svelte delete mode 100644 dist/login/components/TextareaTag.svelte.d.ts delete mode 100644 dist/login/components/UserProfileFormFields.svelte delete mode 100644 dist/login/components/UserProfileFormFields.svelte.d.ts delete mode 100644 dist/login/components/UserProfileFormFieldsProps.d.ts delete mode 100644 dist/login/components/UserProfileFormFieldsProps.js delete mode 100644 dist/login/components/inputLabel.d.ts delete mode 100644 dist/login/components/inputLabel.js delete mode 100644 dist/login/i18n/i18n.d.ts delete mode 100644 dist/login/i18n/i18n.js delete mode 100644 dist/login/i18n/i18nBuilder.d.ts delete mode 100644 dist/login/i18n/i18nBuilder.js delete mode 100644 dist/login/i18n/index.d.ts delete mode 100644 dist/login/i18n/index.js delete mode 100644 dist/login/i18n/useI18n.d.ts delete mode 100644 dist/login/i18n/useI18n.js delete mode 100644 dist/login/lib/useUserProfileForm.d.ts delete mode 100644 dist/login/lib/useUserProfileForm.js delete mode 100644 dist/login/pages/Code.svelte delete mode 100644 dist/login/pages/Code.svelte.d.ts delete mode 100644 dist/login/pages/Login.svelte delete mode 100644 dist/login/pages/Login.svelte.d.ts delete mode 100644 dist/login/pages/PageProps.d.ts delete mode 100644 dist/login/pages/PageProps.js delete mode 100644 dist/login/pages/Register.svelte delete mode 100644 dist/login/pages/Register.svelte.d.ts delete mode 100644 dist/tools/useConst.d.ts delete mode 100644 dist/tools/useConst.js delete mode 100644 dist/tools/useInsertLinkTags.d.ts delete mode 100644 dist/tools/useInsertLinkTags.js delete mode 100644 dist/tools/useInsertScriptTags.d.ts delete mode 100644 dist/tools/useInsertScriptTags.js delete mode 100644 dist/tools/useReducer.d.ts delete mode 100644 dist/tools/useReducer.js delete mode 100644 dist/tools/useSetClassName.d.ts delete mode 100644 dist/tools/useSetClassName.js delete mode 100644 dist/tools/useState.d.ts delete mode 100644 dist/tools/useState.js create mode 100644 scripts/build.ts create mode 100644 scripts/link-in-app.ts create mode 100644 scripts/link-in-starter.ts create mode 100644 scripts/shared/run.ts create mode 100644 scripts/tools/Deferred.ts create mode 100644 scripts/tools/StatefulObservable.ts create mode 100644 scripts/tools/fs.existsAsync.ts create mode 100644 scripts/tools/fs.rm.ts create mode 100644 scripts/tools/getThisCodebaseRootDirPath.ts create mode 100644 scripts/tools/removeNodeModules.ts create mode 100644 scripts/tools/waitForThrottle.ts create mode 100644 scripts/watch.ts create mode 100644 src/bin/add-story.ts create mode 100644 src/bin/core.ts create mode 100644 src/bin/eject-page.ts create mode 100644 src/bin/initialize-account-theme/boilerplate/KcContext.ts create mode 100644 src/bin/initialize-account-theme/boilerplate/KcPage.svelte create mode 100644 src/bin/initialize-account-theme/boilerplate/KcPageStory.ts create mode 100644 src/bin/initialize-account-theme/boilerplate/i18n.ts create mode 100644 src/bin/initialize-account-theme/index.ts create mode 100644 src/bin/initialize-account-theme/initialize-account-theme.ts create mode 100644 src/bin/initialize-account-theme/updateAccountThemeImplementationInConfig.ts create mode 100644 src/bin/main.ts create mode 100644 src/bin/tools/SemVer.ts create mode 100644 src/bin/tools/String.prototype.replaceAll.ts create mode 100644 src/bin/tools/crawl.ts create mode 100644 src/bin/tools/fs.rmSync.ts create mode 100644 src/bin/tools/getThisCodebaseRootDirPath.ts create mode 100644 src/bin/tools/kebabCaseToSnakeCase.ts create mode 100644 src/bin/tools/nodeModulesBinDirPath.ts create mode 100644 src/bin/tools/readThisNpmPackageVersion.ts create mode 100644 src/bin/tools/runPrettier.ts create mode 100644 src/bin/tools/transformCodebase.ts create mode 100644 src/bin/tools/transformCodebase_async.ts create mode 100644 src/bin/tsconfig.json create mode 100644 src/bin/update-kc-gen.ts diff --git a/dist/login/DefaultPage.svelte b/dist/login/DefaultPage.svelte deleted file mode 100644 index 7e085e6..0000000 --- a/dist/login/DefaultPage.svelte +++ /dev/null @@ -1,135 +0,0 @@ - - -{#if lazyComponent} - {#await lazyComponent then { default: LazyComponent }} - - {/await} -{/if} diff --git a/dist/login/DefaultPage.svelte.d.ts b/dist/login/DefaultPage.svelte.d.ts deleted file mode 100644 index 19018cb..0000000 --- a/dist/login/DefaultPage.svelte.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -type DefaultPageProps = PageProps & { - UserProfileFormFields: Component; - doMakeUserConfirmPassword: boolean; -}; -import type { PageProps } from './pages/PageProps'; -import type { UserProfileFormFieldsProps } from './components/UserProfileFormFieldsProps'; -import type { Component } from 'svelte'; -import type { I18n } from './i18n'; -import type { KcContext } from './KcContext'; -declare const DefaultPage: Component; -type DefaultPage = ReturnType; -export default DefaultPage; diff --git a/dist/login/KcContext/KcContext.d.ts b/dist/login/KcContext/KcContext.d.ts deleted file mode 100644 index 34a1cec..0000000 --- a/dist/login/KcContext/KcContext.d.ts +++ /dev/null @@ -1 +0,0 @@ -export type { KcContext } from 'keycloakify/login/KcContext'; diff --git a/dist/login/KcContext/KcContext.js b/dist/login/KcContext/KcContext.js deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/login/KcContext/KcContext.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/login/KcContext/index.d.ts b/dist/login/KcContext/index.d.ts deleted file mode 100644 index 2df246f..0000000 --- a/dist/login/KcContext/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './KcContext'; diff --git a/dist/login/KcContext/index.js b/dist/login/KcContext/index.js deleted file mode 100644 index 2df246f..0000000 --- a/dist/login/KcContext/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './KcContext'; diff --git a/dist/login/Template.svelte b/dist/login/Template.svelte deleted file mode 100644 index 6323252..0000000 --- a/dist/login/Template.svelte +++ /dev/null @@ -1,222 +0,0 @@ - - -{#if $isReadyToRender} -
-
-
- {msgStr('loginTitleHtml', realm.displayNameHtml)} -
-
- -
-
- {#if enabledLanguages.length > 1} -
-
-
- - - - -
-
-
- {/if} - {#snippet node()} - {#if !(auth !== undefined && auth.showUsername && !auth.showResetCredentials)} -

{@render headerNode?.()}

- {:else} -
- - - - - -
- {/if} - {/snippet} - {#if displayRequiredFields} -
-
- - * - {msgStr('requiredFields')} - -
-
{@render node()}
-
- {:else} - {@render node()} - {/if} -
-
-
- - {#if displayMessage && message !== undefined && (message.type !== 'warning' || !isAppInitiatedAction)} -
-
- {#if message.type === 'success'} - - {:else if message.type === 'warning'} - - {:else if message.type === 'error'} - - {:else if message.type === 'info'} - - {/if} -
- {@html message.summary} -
- {/if} - {@render children?.()} - {#if auth !== undefined && auth.showTryAnotherWayLink} -
- -
- {/if} - {@render socialProvidersNode?.()} - {#if displayInfo} -
-
- {@render infoNode?.()} -
-
- {/if} -
-
-
-
-{/if} diff --git a/dist/login/Template.svelte.d.ts b/dist/login/Template.svelte.d.ts deleted file mode 100644 index 0faa15a..0000000 --- a/dist/login/Template.svelte.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { TemplateProps } from './TemplateProps'; -import type { I18n } from './i18n'; -import type { KcContext } from './KcContext'; -declare const Template: import("svelte").Component, {}, "">; -type Template = ReturnType; -export default Template; diff --git a/dist/login/Template.useInitialize.d.ts b/dist/login/Template.useInitialize.d.ts deleted file mode 100644 index 576aef9..0000000 --- a/dist/login/Template.useInitialize.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type KcContextLike = { - url: { - resourcesCommonPath: string; - resourcesPath: string; - ssoLoginInOtherTabsUrl: string; - }; - scripts?: string[]; -}; -export declare function useInitialize(params: { - kcContext: KcContextLike; - doUseDefaultCss: boolean; -}): { - isReadyToRender: import("svelte/store").Readable; -}; diff --git a/dist/login/Template.useInitialize.js b/dist/login/Template.useInitialize.js deleted file mode 100644 index bdd11b4..0000000 --- a/dist/login/Template.useInitialize.js +++ /dev/null @@ -1,55 +0,0 @@ -import { useInsertLinkTags } from '../tools/useInsertLinkTags'; -import { useInsertScriptTags } from '../tools/useInsertScriptTags'; -import { assert } from 'keycloakify/tools/assert'; -import { onMount } from 'svelte'; -assert(); -assert(); -export function useInitialize(params) { - const { kcContext, doUseDefaultCss } = params; - const { url, scripts } = kcContext; - const { areAllStyleSheetsLoaded } = useInsertLinkTags({ - componentOrHookName: 'Template', - hrefs: !doUseDefaultCss - ? [] - : [ - `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`, - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`, - `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`, - `${url.resourcesCommonPath}/lib/pficon/pficon.css`, - `${url.resourcesPath}/css/login.css`, - ], - }); - const { insertScriptTags } = useInsertScriptTags({ - componentOrHookName: 'Template', - scriptTags: [ - // NOTE: The importmap is added in by the FTL script because it's too late to add it here. - { - type: 'module', - src: `${url.resourcesPath}/js/menu-button-links.js`, - }, - ...(scripts === undefined - ? [] - : scripts.map((src) => ({ - type: 'text/javascript', - src, - }))), - { - type: 'module', - textContent: ` - import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js"; - - checkCookiesAndSetTimer("${url.ssoLoginInOtherTabsUrl}"); - `, - }, - ], - }); - onMount(() => { - const unsubscriber = areAllStyleSheetsLoaded.subscribe((isReadyToRender) => { - if (isReadyToRender) { - insertScriptTags(); - } - }); - return () => unsubscriber(); - }); - return { isReadyToRender: areAllStyleSheetsLoaded }; -} diff --git a/dist/login/TemplateProps.d.ts b/dist/login/TemplateProps.d.ts deleted file mode 100644 index 311a355..0000000 --- a/dist/login/TemplateProps.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { ClassKey } from 'keycloakify/login/lib/kcClsx'; -import type { Snippet } from 'svelte'; -export type TemplateProps = { - kcContext: KcContext; - i18n: I18n; - doUseDefaultCss: boolean; - classes?: Partial>; - children: Snippet; - displayInfo?: boolean; - displayMessage?: boolean; - displayRequiredFields?: boolean; - showAnotherWayIfPresent?: boolean; - headerNode: Snippet; - socialProvidersNode?: Snippet | null; - infoNode?: Snippet | null; - documentTitle?: string; - bodyClassName?: string; -}; -export type { ClassKey }; diff --git a/dist/login/TemplateProps.js b/dist/login/TemplateProps.js deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/login/TemplateProps.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte b/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte deleted file mode 100644 index 8aede3e..0000000 --- a/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - -{#if hasRemove} - - {#if hasAdd} | {/if} -{/if} -{#if hasAdd} - -{/if} diff --git a/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte.d.ts b/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte.d.ts deleted file mode 100644 index ee4991b..0000000 --- a/dist/login/components/AddRemoveButtonsMultiValuedAttribute.svelte.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type FormAction } from '../lib/useUserProfileForm'; -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { EventDispatcher } from 'svelte'; -import type { I18n } from '../i18n'; -declare const AddRemoveButtonsMultiValuedAttribute: import("svelte").Component<{ - attribute: Attribute; - values: string[]; - fieldIndex: number; - dispatchFormAction: EventDispatcher<{ - formAction: Extract; - }>; - i18n: I18n; -}, {}, "">; -type AddRemoveButtonsMultiValuedAttribute = ReturnType; -export default AddRemoveButtonsMultiValuedAttribute; diff --git a/dist/login/components/FieldErrors.svelte b/dist/login/components/FieldErrors.svelte deleted file mode 100644 index 677f55e..0000000 --- a/dist/login/components/FieldErrors.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -{#if displayableErrors.length !== 0} - - {#each displayableErrors as displayableError, i} - {@const { errorMessage } = displayableError} - {@render errorMessage()} - {#if displayableErrors.length - 1 !== i}
{/if} - {/each} -
-{/if} diff --git a/dist/login/components/FieldErrors.svelte.d.ts b/dist/login/components/FieldErrors.svelte.d.ts deleted file mode 100644 index 778cd8a..0000000 --- a/dist/login/components/FieldErrors.svelte.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -type FieldErrorProps = { - attribute: Attribute; - displayableErrors: FormFieldError[]; - fieldIndex?: number; - kcClsx: KcClsx; -}; -import type { FormFieldError } from '../lib/useUserProfileForm'; -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -declare const FieldErrors: import("svelte").Component; -type FieldErrors = ReturnType; -export default FieldErrors; diff --git a/dist/login/components/GroupLabel.svelte b/dist/login/components/GroupLabel.svelte deleted file mode 100644 index bc03911..0000000 --- a/dist/login/components/GroupLabel.svelte +++ /dev/null @@ -1,67 +0,0 @@ - - -{#if isGrouplabel} - {@const groupDisplayHeader = attribute.group?.displayHeader ?? ''} - {@const groupDisplayDescription = attribute.group?.displayDescription ?? ''} - {@const groupHeaderText = - groupDisplayHeader !== '' - ? advancedMsg(groupDisplayHeader) - : createRawSnippet(() => ({ render: () => attribute.group?.name ?? '' }))} -
-
- -
- {#if groupDisplayDescription !== ''} - {@const groupDescriptionText = advancedMsg(groupDisplayDescription)} -
- -
- {/if} -
-{/if} diff --git a/dist/login/components/GroupLabel.svelte.d.ts b/dist/login/components/GroupLabel.svelte.d.ts deleted file mode 100644 index f68ae7e..0000000 --- a/dist/login/components/GroupLabel.svelte.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -type GroupLabelProps = { - attribute: Attribute; - groupNameRef: { - current: string; - }; - i18n: I18n; - kcClsx: KcClsx; -}; -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -import type { I18n } from '../i18n'; -declare const GroupLabel: import("svelte").Component; -type GroupLabel = ReturnType; -export default GroupLabel; diff --git a/dist/login/components/InputFieldByType.svelte b/dist/login/components/InputFieldByType.svelte deleted file mode 100644 index dbcb26e..0000000 --- a/dist/login/components/InputFieldByType.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - -{#if inputType === 'textarea'} - -{:else if ['select', 'multiselect'].includes(inputType)} - -{:else if ['select-radiobuttons', 'multiselect-checkboxes'].includes(inputType)} - -{:else} - - {#if valueOrValues instanceof Array} - {#each valueOrValues as _, i} - - {/each} - {:else} - {#snippet inputNode()} - - {/snippet} - {#if ['password', 'password-confirm'].includes(attribute.name)} - - {@render inputNode()} - - {:else} - {@render inputNode()} - {/if} - {/if} -{/if} diff --git a/dist/login/components/InputFieldByType.svelte.d.ts b/dist/login/components/InputFieldByType.svelte.d.ts deleted file mode 100644 index cc0aa88..0000000 --- a/dist/login/components/InputFieldByType.svelte.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { InputFieldByTypeProps } from './InputFieldByTypeProps'; -declare const InputFieldByType: import("svelte").Component; -type InputFieldByType = ReturnType; -export default InputFieldByType; diff --git a/dist/login/components/InputFieldByTypeProps.d.ts b/dist/login/components/InputFieldByTypeProps.d.ts deleted file mode 100644 index 2459640..0000000 --- a/dist/login/components/InputFieldByTypeProps.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { FormAction, FormFieldError } from '../lib/useUserProfileForm'; -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -import type { EventDispatcher } from 'svelte'; -import type { I18n } from '../i18n'; -export type InputFieldByTypeProps = { - attribute: Attribute; - valueOrValues: string | string[]; - displayableErrors: FormFieldError[]; - dispatchFormAction: EventDispatcher<{ - formAction: FormAction; - }>; - i18n: I18n; - kcClsx: KcClsx; -}; diff --git a/dist/login/components/InputFieldByTypeProps.js b/dist/login/components/InputFieldByTypeProps.js deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/login/components/InputFieldByTypeProps.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/login/components/InputTag.svelte b/dist/login/components/InputTag.svelte deleted file mode 100644 index 3f6bd4c..0000000 --- a/dist/login/components/InputTag.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - - error.fieldIndex === fieldIndex) !== undefined} - disabled={attribute.readOnly} - autocomplete={attribute.autocomplete as 'on'} - placeholder={attribute.annotations.inputTypePlaceholder === undefined - ? undefined - : advancedMsgStr(attribute.annotations.inputTypePlaceholder)} - pattern={attribute.annotations.inputTypePattern} - size={attribute.annotations.inputTypeSize === undefined - ? undefined - : parseInt(`${attribute.annotations.inputTypeSize}`)} - maxlength={attribute.annotations.inputTypeMaxlength === undefined - ? undefined - : parseInt(`${attribute.annotations.inputTypeMaxlength}`)} - minlength={attribute.annotations.inputTypeMinlength === undefined - ? undefined - : parseInt(`${attribute.annotations.inputTypeMinlength}`)} - max={attribute.annotations.inputTypeMax} - min={attribute.annotations.inputTypeMin} - step={attribute.annotations.inputTypeStep} - {...html5DataAnnotations} - onchange={(event) => - dispatchFormAction('formAction', { - action: 'update', - name: attribute.name, - valueOrValues: (() => { - if (fieldIndex !== undefined) { - assert(valueOrValues instanceof Array); - - return valueOrValues.map((value, i) => { - if (i === fieldIndex) { - return event.currentTarget.value; - } - - return value; - }); - } - - return event.currentTarget.value; - })(), - })} - onblur={() => - dispatchFormAction('formAction', { - action: 'focus lost', - name: attribute.name, - fieldIndex: fieldIndex, - })} -/> -{#if fieldIndex !== undefined && valueOrValues instanceof Array} - {@const values = valueOrValues} - - -{/if} diff --git a/dist/login/components/InputTag.svelte.d.ts b/dist/login/components/InputTag.svelte.d.ts deleted file mode 100644 index 8a40213..0000000 --- a/dist/login/components/InputTag.svelte.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -type InputTagProps = InputFieldByTypeProps & { - fieldIndex?: number; -}; -import type { InputFieldByTypeProps } from './InputFieldByTypeProps'; -declare const InputTag: import("svelte").Component; -type InputTag = ReturnType; -export default InputTag; diff --git a/dist/login/components/InputTagSelects.svelte b/dist/login/components/InputTagSelects.svelte deleted file mode 100644 index 8a834ab..0000000 --- a/dist/login/components/InputTagSelects.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - -{#each options as option} -
- - dispatchFormAction('formAction', { - action: 'update', - name: attribute.name, - valueOrValues: (() => { - const isChecked = event.currentTarget.checked; - - if (valueOrValues instanceof Array) { - const newValues = [...valueOrValues]; - - if (isChecked) { - newValues.push(option); - } else { - newValues.splice(newValues.indexOf(option), 1); - } - - return newValues; - } - - return event.currentTarget.checked ? option : ''; - })(), - })} - onblur={() => - dispatchFormAction('formAction', { - action: 'focus lost', - name: attribute.name, - fieldIndex: undefined, - })} - /> - -
-{/each} diff --git a/dist/login/components/InputTagSelects.svelte.d.ts b/dist/login/components/InputTagSelects.svelte.d.ts deleted file mode 100644 index 64728be..0000000 --- a/dist/login/components/InputTagSelects.svelte.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { InputFieldByTypeProps } from './InputFieldByTypeProps'; -declare const InputTagSelects: import("svelte").Component; -type InputTagSelects = ReturnType; -export default InputTagSelects; diff --git a/dist/login/components/PasswordWrapper.svelte b/dist/login/components/PasswordWrapper.svelte deleted file mode 100644 index acfd769..0000000 --- a/dist/login/components/PasswordWrapper.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - -
- {@render children?.()} - -
diff --git a/dist/login/components/PasswordWrapper.svelte.d.ts b/dist/login/components/PasswordWrapper.svelte.d.ts deleted file mode 100644 index ef2c45c..0000000 --- a/dist/login/components/PasswordWrapper.svelte.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -type $$ComponentProps = { - kcClsx: KcClsx; - i18n: I18n; - passwordInputId: string; - children: Snippet; -}; -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -import type { Snippet } from 'svelte'; -import type { I18n } from '../i18n'; -declare const PasswordWrapper: import("svelte").Component<$$ComponentProps, {}, "">; -type PasswordWrapper = ReturnType; -export default PasswordWrapper; diff --git a/dist/login/components/SelectTag.svelte b/dist/login/components/SelectTag.svelte deleted file mode 100644 index 334b0d5..0000000 --- a/dist/login/components/SelectTag.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - diff --git a/dist/login/components/SelectTag.svelte.d.ts b/dist/login/components/SelectTag.svelte.d.ts deleted file mode 100644 index 810110c..0000000 --- a/dist/login/components/SelectTag.svelte.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { InputFieldByTypeProps } from './InputFieldByTypeProps'; -declare const SelectTag: import("svelte").Component; -type SelectTag = ReturnType; -export default SelectTag; diff --git a/dist/login/components/TermsAcceptance.svelte b/dist/login/components/TermsAcceptance.svelte deleted file mode 100644 index 4e4fe03..0000000 --- a/dist/login/components/TermsAcceptance.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -
-
- {@render msg('termsTitle')()} -
{@render msg('termsText')()}
-
-
-
-
- onAreTermsAcceptedValueChange(e.currentTarget.checked)} - aria-invalid={messagesPerField.existsError('termsAccepted')} - /> - -
- {#if messagesPerField.existsError('termsAccepted')} -
- - {@html messagesPerField.get('termsAccepted')} - -
- {/if} -
diff --git a/dist/login/components/TermsAcceptance.svelte.d.ts b/dist/login/components/TermsAcceptance.svelte.d.ts deleted file mode 100644 index f0125cf..0000000 --- a/dist/login/components/TermsAcceptance.svelte.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -import type { KcContext } from '../KcContext'; -import type { I18n } from '../i18n'; -declare const TermsAcceptance: import("svelte").Component<{ - i18n: I18n; - kcClsx: KcClsx; - messagesPerField: Pick; - areTermsAccepted: boolean; - onAreTermsAcceptedValueChange: (areTermsAccepted: boolean) => void; -}, {}, "">; -type TermsAcceptance = ReturnType; -export default TermsAcceptance; diff --git a/dist/login/components/TextareaTag.svelte b/dist/login/components/TextareaTag.svelte deleted file mode 100644 index b87ec71..0000000 --- a/dist/login/components/TextareaTag.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/dist/login/components/TextareaTag.svelte.d.ts b/dist/login/components/TextareaTag.svelte.d.ts deleted file mode 100644 index d33b84d..0000000 --- a/dist/login/components/TextareaTag.svelte.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { InputFieldByTypeProps } from './InputFieldByTypeProps'; -declare const TextareaTag: import("svelte").Component; -type TextareaTag = ReturnType; -export default TextareaTag; diff --git a/dist/login/components/UserProfileFormFields.svelte b/dist/login/components/UserProfileFormFields.svelte deleted file mode 100644 index 6652815..0000000 --- a/dist/login/components/UserProfileFormFields.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - -{#each $formState.formFieldStates as formFieldState} - {@const { attribute, displayableErrors, valueOrValues } = formFieldState} - - {#if beforeField} - {@render beforeField({ attribute, dispatchFormAction, displayableErrors, valueOrValues, kcClsx, i18n })} - {/if} -
-
- - {#if attribute.required} - * - {/if} -
-
- {#if attribute.annotations.inputHelperTextBefore !== undefined} -
- {@render advancedMsg(attribute.annotations.inputHelperTextBefore)()} -
- {/if} - - - {#if attribute.annotations.inputHelperTextAfter !== undefined} -
- {@render advancedMsg(attribute.annotations.inputHelperTextAfter)()} -
- {/if} - - {#if afterField} - {@render afterField({ attribute, dispatchFormAction, displayableErrors, valueOrValues, kcClsx, i18n })} - {/if} - -
-
-{/each} diff --git a/dist/login/components/UserProfileFormFields.svelte.d.ts b/dist/login/components/UserProfileFormFields.svelte.d.ts deleted file mode 100644 index 203f665..0000000 --- a/dist/login/components/UserProfileFormFields.svelte.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { UserProfileFormFieldsProps } from './UserProfileFormFieldsProps'; -import type { I18n } from '../i18n'; -import type { KcContext } from '../KcContext'; -declare const UserProfileFormFields: import("svelte").Component, {}, "">; -type UserProfileFormFields = ReturnType; -export default UserProfileFormFields; diff --git a/dist/login/components/UserProfileFormFieldsProps.d.ts b/dist/login/components/UserProfileFormFieldsProps.d.ts deleted file mode 100644 index a577459..0000000 --- a/dist/login/components/UserProfileFormFieldsProps.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { type FormAction, type FormFieldError } from '../lib/useUserProfileForm'; -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { KcClsx } from 'keycloakify/login/lib/kcClsx'; -import type { EventDispatcher, Snippet } from 'svelte'; -export type UserProfileFormFieldsProps = { - kcContext: Extract; - i18n: I18n; - kcClsx: KcClsx; - onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void; - doMakeUserConfirmPassword: boolean; - beforeField?: Snippet<[BeforeAfterFieldProps]> | null; - afterField?: Snippet<[BeforeAfterFieldProps]> | null; -}; -type BeforeAfterFieldProps = { - attribute: Attribute; - dispatchFormAction: EventDispatcher<{ - formAction: FormAction; - }>; - displayableErrors: FormFieldError[]; - valueOrValues: string | string[]; - kcClsx: KcClsx; - i18n: I18n; -}; -export {}; diff --git a/dist/login/components/UserProfileFormFieldsProps.js b/dist/login/components/UserProfileFormFieldsProps.js deleted file mode 100644 index 9db0cc9..0000000 --- a/dist/login/components/UserProfileFormFieldsProps.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import {} from '../lib/useUserProfileForm'; diff --git a/dist/login/components/inputLabel.d.ts b/dist/login/components/inputLabel.d.ts deleted file mode 100644 index 857e584..0000000 --- a/dist/login/components/inputLabel.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Attribute } from 'keycloakify/login/KcContext'; -import type { I18n } from '../i18n'; -export declare const inputLabel: (i18n: I18n, attribute: Attribute, option: string) => import("svelte").Snippet<[]>; diff --git a/dist/login/components/inputLabel.js b/dist/login/components/inputLabel.js deleted file mode 100644 index 35acd10..0000000 --- a/dist/login/components/inputLabel.js +++ /dev/null @@ -1,12 +0,0 @@ -import { createRawSnippet } from 'svelte'; -export const inputLabel = (i18n, attribute, option) => { - const { advancedMsg } = i18n; - if (attribute.annotations.inputOptionLabels !== undefined) { - const { inputOptionLabels } = attribute.annotations; - return advancedMsg(inputOptionLabels[option] ?? option); - } - if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) { - return advancedMsg(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`); - } - return createRawSnippet(() => ({ render: () => option })); -}; diff --git a/dist/login/i18n/i18n.d.ts b/dist/login/i18n/i18n.d.ts deleted file mode 100644 index a98712f..0000000 --- a/dist/login/i18n/i18n.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { GenericI18n_noJsx } from 'keycloakify/login/i18n/noJsx/GenericI18n_noJsx'; -import { type MessageKey as MessageKey_defaultSet } from 'keycloakify/login/i18n/messages_defaultSet/types'; -import type { Snippet } from 'svelte'; -/** INTERNAL: DO NOT IMPORT THIS */ -export type GenericI18n_svelte = GenericI18n_noJsx & { - /** - * Same as msgStr but returns a Snippet with the html string rendered as html. - */ - msg: (key: MessageKey_defaultSet, ...args: (string | undefined)[]) => Snippet; - /** - * Same as advancedMsgStr but returns a Snippet with the html string rendered as html. - */ - advancedMsg: (key: string, ...args: (string | undefined)[]) => Snippet; -}; -export type I18n = GenericI18n_svelte; diff --git a/dist/login/i18n/i18n.js b/dist/login/i18n/i18n.js deleted file mode 100644 index e513f86..0000000 --- a/dist/login/i18n/i18n.js +++ /dev/null @@ -1 +0,0 @@ -import {} from 'keycloakify/login/i18n/messages_defaultSet/types'; diff --git a/dist/login/i18n/i18nBuilder.d.ts b/dist/login/i18n/i18nBuilder.d.ts deleted file mode 100644 index 8b7b02f..0000000 --- a/dist/login/i18n/i18nBuilder.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { LanguageTag as LanguageTag_defaultSet, MessageKey as MessageKey_defaultSet } from 'keycloakify/login/i18n/messages_defaultSet/types'; -import { type ReturnTypeOfCreateUseI18n } from './useI18n'; -export type I18nBuilder = Omit<{ - withThemeName: () => I18nBuilder; - withExtraLanguages: (extraLanguageTranslations: { - [LanguageTag in LanguageTag_notInDefaultSet]: { - label: string; - getMessages: () => Promise<{ - default: Record; - }>; - }; - }) => I18nBuilder; - withCustomTranslations: (messagesByLanguageTag_themeDefined: Partial<{ - [LanguageTag in LanguageTag_defaultSet | LanguageTag_notInDefaultSet]: Record>; - }>) => I18nBuilder; - build: () => ReturnTypeOfCreateUseI18n; -}, ExcludedMethod>; -export declare const i18nBuilder: I18nBuilder; diff --git a/dist/login/i18n/i18nBuilder.js b/dist/login/i18n/i18nBuilder.js deleted file mode 100644 index 16f238f..0000000 --- a/dist/login/i18n/i18nBuilder.js +++ /dev/null @@ -1,26 +0,0 @@ -import { createUseI18n } from './useI18n'; -function createI18nBuilder(params) { - const i18nBuilder = { - withThemeName: () => createI18nBuilder({ - extraLanguageTranslations: params.extraLanguageTranslations, - messagesByLanguageTag_themeDefined: params.messagesByLanguageTag_themeDefined, - }), - withExtraLanguages: (extraLanguageTranslations) => createI18nBuilder({ - extraLanguageTranslations, - messagesByLanguageTag_themeDefined: params.messagesByLanguageTag_themeDefined, - }), - withCustomTranslations: (messagesByLanguageTag_themeDefined) => createI18nBuilder({ - extraLanguageTranslations: params.extraLanguageTranslations, - messagesByLanguageTag_themeDefined, - }), - build: () => createUseI18n({ - extraLanguageTranslations: params.extraLanguageTranslations, - messagesByLanguageTag_themeDefined: params.messagesByLanguageTag_themeDefined, - }), - }; - return i18nBuilder; -} -export const i18nBuilder = createI18nBuilder({ - extraLanguageTranslations: {}, - messagesByLanguageTag_themeDefined: {}, -}); diff --git a/dist/login/i18n/index.d.ts b/dist/login/i18n/index.d.ts deleted file mode 100644 index e82230f..0000000 --- a/dist/login/i18n/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './i18n'; diff --git a/dist/login/i18n/index.js b/dist/login/i18n/index.js deleted file mode 100644 index e82230f..0000000 --- a/dist/login/i18n/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './i18n'; diff --git a/dist/login/i18n/useI18n.d.ts b/dist/login/i18n/useI18n.d.ts deleted file mode 100644 index c44644a..0000000 --- a/dist/login/i18n/useI18n.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type LanguageTag as LanguageTag_defaultSet, type MessageKey as MessageKey_defaultSet } from 'keycloakify/login/i18n/messages_defaultSet/types'; -import { type KcContextLike } from 'keycloakify/login/i18n/noJsx/getI18n'; -import type { Readable } from 'svelte/store'; -import type { GenericI18n_svelte } from './i18n'; -export type ReturnTypeOfCreateUseI18n = { - useI18n: (params: { - kcContext: KcContextLike; - }) => { - i18n: Readable>; - }; - ofTypeI18n: GenericI18n_svelte; -}; -export type { KcContextLike }; -export declare function createUseI18n(params: { - extraLanguageTranslations: { - [languageTag in LanguageTag_notInDefaultSet]: { - label: string; - getMessages: () => Promise<{ - default: Record; - }>; - }; - }; - messagesByLanguageTag_themeDefined: Partial<{ - [languageTag in LanguageTag_defaultSet | LanguageTag_notInDefaultSet]: { - [key in MessageKey_themeDefined]: string | Record; - }; - }>; -}): ReturnTypeOfCreateUseI18n; diff --git a/dist/login/i18n/useI18n.js b/dist/login/i18n/useI18n.js deleted file mode 100644 index 93f3731..0000000 --- a/dist/login/i18n/useI18n.js +++ /dev/null @@ -1,65 +0,0 @@ -import { useState } from '../../tools/useState'; -import {} from 'keycloakify/login/i18n/messages_defaultSet/types'; -import { createGetI18n } from 'keycloakify/login/i18n/noJsx/getI18n'; -import { createRawSnippet, onMount } from 'svelte'; -import { Reflect } from 'tsafe/Reflect'; -export function createUseI18n(params) { - const { extraLanguageTranslations, messagesByLanguageTag_themeDefined } = params; - const { withSvelte } = (() => { - const cache = new WeakMap(); - function renderHtmlString(params) { - const { htmlString, msgKey } = params; - return createRawSnippet(() => ({ - render: () => `
${htmlString}
`, - })); - } - function withSvelte(i18n_noJsx) { - use_cache: { - const i18n = cache.get(i18n_noJsx); - if (i18n === undefined) { - break use_cache; - } - return i18n; - } - const i18n = { - ...i18n_noJsx, - msg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.msgStr(msgKey, ...args), msgKey }), - advancedMsg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.advancedMsgStr(msgKey, ...args), msgKey }), - }; - cache.set(i18n_noJsx, i18n); - return i18n; - } - return { withSvelte }; - })(); - add_style: { - const attributeName = 'data-kc-i18n'; - // Check if already exists in head - if (document.querySelector(`style[${attributeName}]`) !== null) { - break add_style; - } - const styleElement = document.createElement('style'); - styleElement.attributes.setNamedItem(document.createAttribute(attributeName)); - styleElement.textContent = `[data-kc-msg] { display: inline-block; }`; - document.head.prepend(styleElement); - } - const { getI18n } = createGetI18n({ extraLanguageTranslations, messagesByLanguageTag_themeDefined }); - function useI18n(params) { - const { kcContext } = params; - const { i18n, prI18n_currentLanguage } = getI18n({ kcContext }); - const [i18n_toReturn, setI18n_toReturn] = useState(withSvelte(i18n)); - onMount(() => { - let isActive = true; - prI18n_currentLanguage?.then((i18n) => { - if (!isActive) { - return; - } - setI18n_toReturn(withSvelte(i18n)); - }); - return () => { - isActive = false; - }; - }); - return { i18n: i18n_toReturn }; - } - return { useI18n, ofTypeI18n: Reflect() }; -} diff --git a/dist/login/lib/useUserProfileForm.d.ts b/dist/login/lib/useUserProfileForm.d.ts deleted file mode 100644 index 2a547ee..0000000 --- a/dist/login/lib/useUserProfileForm.d.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { Attribute, PasswordPolicies, Validators } from 'keycloakify/login/KcContext'; -import * as reactlessApi from 'keycloakify/login/lib/getUserProfileApi'; -import { type EventDispatcher, type Snippet } from 'svelte'; -import { type Readable } from 'svelte/store'; -import type { I18n } from '../i18n'; -export { getButtonToDisplayForMultivaluedAttributeField } from 'keycloakify/login/lib/getUserProfileApi'; -export type FormFieldError = { - errorMessage: Snippet; - errorMessageStr: string; - source: FormFieldError.Source; - fieldIndex: number | undefined; -}; -export declare namespace FormFieldError { - type Source = Source.Validator | Source.PasswordPolicy | Source.Server | Source.Other; - namespace Source { - type Validator = { - type: 'validator'; - name: keyof Validators; - }; - type PasswordPolicy = { - type: 'passwordPolicy'; - name: keyof PasswordPolicies; - }; - type Server = { - type: 'server'; - }; - type Other = { - type: 'other'; - rule: 'passwordConfirmMatchesPassword' | 'requiredField'; - }; - } -} -export type FormFieldState = { - attribute: Attribute; - displayableErrors: FormFieldError[]; - valueOrValues: string | string[]; -}; -export type FormState = { - isFormSubmittable: boolean; - formFieldStates: FormFieldState[]; -}; -export type FormAction = { - action: 'update'; - name: string; - valueOrValues: string | string[]; - /** Default false */ - displayErrorsImmediately?: boolean; -} | { - action: 'focus lost'; - name: string; - fieldIndex: number | undefined; -}; -export type KcContextLike = reactlessApi.KcContextLike; -export type I18nLike = Pick; -export type ParamsOfUseUserProfileForm = { - kcContext: KcContextLike; - doMakeUserConfirmPassword: boolean; - i18n: I18nLike; -}; -export type ReturnTypeOfUseUserProfileForm = { - formState: Readable; - dispatchFormAction: EventDispatcher<{ - formAction: FormAction; - }>; -}; -export declare function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm; diff --git a/dist/login/lib/useUserProfileForm.js b/dist/login/lib/useUserProfileForm.js deleted file mode 100644 index ca5d47a..0000000 --- a/dist/login/lib/useUserProfileForm.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -import { useState } from '../../tools/useState'; -import * as reactlessApi from 'keycloakify/login/lib/getUserProfileApi'; -import { createRawSnippet, onMount } from 'svelte'; -import { derived } from 'svelte/store'; -import { assert } from 'tsafe/assert'; -export { getButtonToDisplayForMultivaluedAttributeField } from 'keycloakify/login/lib/getUserProfileApi'; -{ - assert(); -} -{ - assert(); -} -{ - assert(); -} -{ - assert(); -} -{ - assert(); -} -{ - assert(); -} -export function useUserProfileForm(params) { - const { doMakeUserConfirmPassword, i18n, kcContext } = params; - const api = reactlessApi.getUserProfileApi({ - kcContext, - doMakeUserConfirmPassword, - }); - const [formState_reactless, setFormState_reactless] = useState(api.getFormState()); - onMount(() => { - const { unsubscribe } = api.subscribeToFormState(() => { - setFormState_reactless(api.getFormState()); - }); - return () => unsubscribe(); - }); - const { advancedMsg, advancedMsgStr } = i18n; - const formState = derived(formState_reactless, ($formState) => ({ - isFormSubmittable: $formState.isFormSubmittable, - formFieldStates: $formState.formFieldStates.map((formFieldState_reactless) => ({ - attribute: formFieldState_reactless.attribute, - valueOrValues: formFieldState_reactless.valueOrValues, - displayableErrors: formFieldState_reactless.displayableErrors.map((formFieldError_reactless) => ({ - errorMessage: createRawSnippet(() => ({ - render: () => `${advancedMsg(...formFieldError_reactless.advancedMsgArgs)}`, - })), - errorMessageStr: advancedMsgStr(...formFieldError_reactless.advancedMsgArgs), - source: formFieldError_reactless.source, - fieldIndex: formFieldError_reactless.fieldIndex, - })), - })), - })); - return { - formState, - dispatchFormAction: ((_, { ...args }) => { - if (args) { - api.dispatchFormAction(args); - return true; - } - return false; - }), - }; -} diff --git a/dist/login/pages/Code.svelte b/dist/login/pages/Code.svelte deleted file mode 100644 index d647a92..0000000 --- a/dist/login/pages/Code.svelte +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/dist/login/pages/Code.svelte.d.ts b/dist/login/pages/Code.svelte.d.ts deleted file mode 100644 index 63657de..0000000 --- a/dist/login/pages/Code.svelte.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { PageProps } from './PageProps'; -import type { KcContext } from '../KcContext'; -import type { I18n } from '../i18n'; -declare const Code: import("svelte").Component, {}, "">; -type Code = ReturnType; -export default Code; diff --git a/dist/login/pages/Login.svelte b/dist/login/pages/Login.svelte deleted file mode 100644 index d357ecb..0000000 --- a/dist/login/pages/Login.svelte +++ /dev/null @@ -1,224 +0,0 @@ - - - diff --git a/dist/login/pages/Login.svelte.d.ts b/dist/login/pages/Login.svelte.d.ts deleted file mode 100644 index 7392f9b..0000000 --- a/dist/login/pages/Login.svelte.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { PageProps } from './PageProps'; -import type { I18n } from '../i18n'; -import type { KcContext } from '../KcContext'; -declare const Login: import("svelte").Component, {}, "">; -type Login = ReturnType; -export default Login; diff --git a/dist/login/pages/PageProps.d.ts b/dist/login/pages/PageProps.d.ts deleted file mode 100644 index 17d3379..0000000 --- a/dist/login/pages/PageProps.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type ClassKey, type TemplateProps } from '../TemplateProps'; -import type { Component } from 'svelte'; -export type PageProps = { - Template: Component>; - kcContext: NarrowedKcContext; - i18n: I18n; - doUseDefaultCss: boolean; - classes?: Partial>; -}; diff --git a/dist/login/pages/PageProps.js b/dist/login/pages/PageProps.js deleted file mode 100644 index 982a913..0000000 --- a/dist/login/pages/PageProps.js +++ /dev/null @@ -1 +0,0 @@ -import {} from '../TemplateProps'; diff --git a/dist/login/pages/Register.svelte b/dist/login/pages/Register.svelte deleted file mode 100644 index df0e3b9..0000000 --- a/dist/login/pages/Register.svelte +++ /dev/null @@ -1,136 +0,0 @@ - - - diff --git a/dist/login/pages/Register.svelte.d.ts b/dist/login/pages/Register.svelte.d.ts deleted file mode 100644 index a03a144..0000000 --- a/dist/login/pages/Register.svelte.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { UserProfileFormFieldsProps } from '../components/UserProfileFormFieldsProps'; -import type { PageProps } from './PageProps'; -import type { Component } from 'svelte'; -import type { I18n } from '../i18n'; -import type { KcContext } from '../KcContext'; -declare const Register: Component & { - UserProfileFormFields: Component; - doMakeUserConfirmPassword: boolean; -}, {}, "">; -type Register = ReturnType; -export default Register; diff --git a/dist/tools/useConst.d.ts b/dist/tools/useConst.d.ts deleted file mode 100644 index 2641cf5..0000000 --- a/dist/tools/useConst.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare function useConst(getValue: () => T): T; diff --git a/dist/tools/useConst.js b/dist/tools/useConst.js deleted file mode 100644 index c53ddce..0000000 --- a/dist/tools/useConst.js +++ /dev/null @@ -1,4 +0,0 @@ -export function useConst(getValue) { - const value = getValue(); - return value; -} diff --git a/dist/tools/useInsertLinkTags.d.ts b/dist/tools/useInsertLinkTags.d.ts deleted file mode 100644 index 12e2d6d..0000000 --- a/dist/tools/useInsertLinkTags.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * NOTE: The component that use this hook can only be mounded once! - * And can't rerender with different hrefs. - * If it's mounted again the page will be reloaded. - * This simulates the behavior of a server rendered page that imports css stylesheet in the head. - */ -export declare function useInsertLinkTags(params: { - componentOrHookName: string; - hrefs: string[]; -}): { - areAllStyleSheetsLoaded: import("svelte/store").Readable; -}; diff --git a/dist/tools/useInsertLinkTags.js b/dist/tools/useInsertLinkTags.js deleted file mode 100644 index 3f48b2c..0000000 --- a/dist/tools/useInsertLinkTags.js +++ /dev/null @@ -1,62 +0,0 @@ -import { onMount } from 'svelte'; -import { id } from 'tsafe/id'; -import { useConst } from './useConst'; -import { useReducer } from './useReducer'; -const alreadyMountedComponentOrHookNames = new Set(); -/** - * NOTE: The component that use this hook can only be mounded once! - * And can't rerender with different hrefs. - * If it's mounted again the page will be reloaded. - * This simulates the behavior of a server rendered page that imports css stylesheet in the head. - */ -export function useInsertLinkTags(params) { - const { hrefs, componentOrHookName } = params; - onMount(() => { - const isAlreadyMounted = alreadyMountedComponentOrHookNames.has(componentOrHookName); - if (isAlreadyMounted) { - reload: { - if (new URL(window.location.href).searchParams.get('viewMode') === 'docs') { - // NOTE: Special case for Storybook, we want to avoid infinite reload loop. - break reload; - } - window.location.reload(); - } - return; - } - alreadyMountedComponentOrHookNames.add(componentOrHookName); - }); - const [areAllStyleSheetsLoaded, setAllStyleSheetsLoaded] = useReducer(() => true, false); - const refPrAllStyleSheetLoaded = useConst(() => ({ - current: id(undefined), - })); - onMount(() => { - let isActive = true; - (refPrAllStyleSheetLoaded.current ??= (async () => { - let lastMountedHtmlElement = undefined; - const prs = []; - for (const href of hrefs) { - const htmlElement = document.createElement('link'); - prs.push(new Promise((resolve) => htmlElement.addEventListener('load', () => resolve()))); - htmlElement.rel = 'stylesheet'; - htmlElement.href = href; - if (lastMountedHtmlElement !== undefined) { - lastMountedHtmlElement.insertAdjacentElement('afterend', htmlElement); - } - else { - document.head.prepend(htmlElement); - } - lastMountedHtmlElement = htmlElement; - } - await Promise.all(prs); - })()).then(() => { - if (!isActive) { - return; - } - setAllStyleSheetsLoaded(); - }); - return () => { - isActive = false; - }; - }); - return { areAllStyleSheetsLoaded }; -} diff --git a/dist/tools/useInsertScriptTags.d.ts b/dist/tools/useInsertScriptTags.d.ts deleted file mode 100644 index 58d2109..0000000 --- a/dist/tools/useInsertScriptTags.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -export type ScriptTag = ScriptTag.TextContent | ScriptTag.Src; -export declare namespace ScriptTag { - type Common = { - type: 'text/javascript' | 'module'; - }; - export type TextContent = Common & { - textContent: string | (() => string); - }; - export type Src = Common & { - src: string; - }; - export {}; -} -/** - * NOTE: The component that use this hook can only be mounded once! - * And can't rerender with different scriptTags. - * If it's mounted again the page will be reloaded. - * This simulates the behavior of a server rendered page that imports javascript in the head. - * - * The returned function is supposed to be called in a useEffect and - * will not download the scripts multiple times event if called more than once (react strict mode). - * - */ -export declare function useInsertScriptTags(params: { - componentOrHookName: string; - scriptTags: ScriptTag[]; -}): { - insertScriptTags: () => void; -}; diff --git a/dist/tools/useInsertScriptTags.js b/dist/tools/useInsertScriptTags.js deleted file mode 100644 index 32759dc..0000000 --- a/dist/tools/useInsertScriptTags.js +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -import { onMount } from 'svelte'; -import { assert } from 'tsafe/assert'; -const alreadyMountedComponentOrHookNames = new Set(); -/** - * NOTE: The component that use this hook can only be mounded once! - * And can't rerender with different scriptTags. - * If it's mounted again the page will be reloaded. - * This simulates the behavior of a server rendered page that imports javascript in the head. - * - * The returned function is supposed to be called in a useEffect and - * will not download the scripts multiple times event if called more than once (react strict mode). - * - */ -export function useInsertScriptTags(params) { - const { scriptTags, componentOrHookName } = params; - onMount(() => { - const isAlreadyMounted = alreadyMountedComponentOrHookNames.has(componentOrHookName); - if (isAlreadyMounted) { - reload: { - if (new URL(window.location.href).searchParams.get('viewMode') === 'docs') { - // NOTE: Special case for Storybook, we want to avoid infinite reload loop. - break reload; - } - window.location.reload(); - } - return; - } - alreadyMountedComponentOrHookNames.add(componentOrHookName); - }); - let areScriptsInserted = false; - const insertScriptTags = () => { - if (areScriptsInserted) { - return; - } - scriptTags.forEach((scriptTag) => { - // NOTE: Avoid loading same script twice. (Like jQuery for example) - { - const scripts = document.getElementsByTagName('script'); - for (let i = 0; i < scripts.length; i++) { - const script = scripts[i]; - if ('textContent' in scriptTag) { - const textContent = typeof scriptTag.textContent === 'function' ? scriptTag.textContent() : scriptTag.textContent; - if (script.textContent === textContent) { - return; - } - continue; - } - if ('src' in scriptTag) { - if (script.getAttribute('src') === scriptTag.src) { - return; - } - continue; - } - assert(false); - } - } - const htmlElement = document.createElement('script'); - htmlElement.type = scriptTag.type; - (() => { - if ('textContent' in scriptTag) { - const textContent = typeof scriptTag.textContent === 'function' ? scriptTag.textContent() : scriptTag.textContent; - htmlElement.textContent = textContent; - return; - } - if ('src' in scriptTag) { - htmlElement.src = scriptTag.src; - return; - } - assert(false); - })(); - document.head.appendChild(htmlElement); - }); - areScriptsInserted = true; - }; - return { insertScriptTags }; -} diff --git a/dist/tools/useReducer.d.ts b/dist/tools/useReducer.d.ts deleted file mode 100644 index e8eb1c3..0000000 --- a/dist/tools/useReducer.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { type Readable } from 'svelte/store'; -export declare const useReducer: (reducer: (state: T, action: R) => T, initialState: T) => [Readable, (action: R) => void]; diff --git a/dist/tools/useReducer.js b/dist/tools/useReducer.js deleted file mode 100644 index 8a33c1b..0000000 --- a/dist/tools/useReducer.js +++ /dev/null @@ -1,7 +0,0 @@ -import { derived, writable } from 'svelte/store'; -export const useReducer = (reducer, initialState) => { - const state = writable(initialState); - const dispatch = (action) => state.update((currentState) => reducer(currentState, action)); - const readableState = derived(state, ($state) => $state); - return [readableState, dispatch]; -}; diff --git a/dist/tools/useSetClassName.d.ts b/dist/tools/useSetClassName.d.ts deleted file mode 100644 index 2ce62f0..0000000 --- a/dist/tools/useSetClassName.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export declare function useSetClassName(params: { - qualifiedName: 'html' | 'body'; - className: string | undefined; -}): void; diff --git a/dist/tools/useSetClassName.js b/dist/tools/useSetClassName.js deleted file mode 100644 index 28a8f64..0000000 --- a/dist/tools/useSetClassName.js +++ /dev/null @@ -1,15 +0,0 @@ -import { onMount } from 'svelte'; -export function useSetClassName(params) { - const { qualifiedName, className } = params; - onMount(() => { - if (className && className !== '') { - const htmlClassList = document.getElementsByTagName(qualifiedName)[0].classList; - const tokens = className.split(' '); - htmlClassList.add(...tokens); - // Clean up when the component is destroyed - return () => { - htmlClassList.remove(...tokens); - }; - } - }); -} diff --git a/dist/tools/useState.d.ts b/dist/tools/useState.d.ts deleted file mode 100644 index a178e4a..0000000 --- a/dist/tools/useState.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { type Readable } from 'svelte/store'; -export declare const useState: (initialState: T) => [Readable, (newState: T) => void]; diff --git a/dist/tools/useState.js b/dist/tools/useState.js deleted file mode 100644 index e89a0e8..0000000 --- a/dist/tools/useState.js +++ /dev/null @@ -1,7 +0,0 @@ -import { derived, writable } from 'svelte/store'; -export const useState = (initialState) => { - const state = writable(initialState); - const dispatch = (newState) => state.set(newState); - const readableState = derived(state, ($state) => $state); - return [readableState, dispatch]; -}; diff --git a/package.json b/package.json index 16b5cf5..88af5a4 100644 --- a/package.json +++ b/package.json @@ -174,9 +174,9 @@ "package.json" ], "scripts": { - "dev": "vite dev", - "build": "vite build && npm run package", - "preview": "vite preview", + "build": "tsx scripts/build.ts", + "watch": "tsx scripts/watch.ts", + "link-in-starter": "tsx scripts/link-in-starter.ts", "package": "svelte-kit sync && rm -rf dist && svelte-package && publint", "prepublishOnly": "npm run package", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -194,6 +194,8 @@ "@sveltejs/package": "^2.3.7", "@sveltejs/vite-plugin-svelte": "^4.0.1", "@types/eslint": "^9.6.1", + "@types/node": "^22.9.3", + "cli-select": "^1.1.2", "eslint": "9.11.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", @@ -207,8 +209,10 @@ "publint": "^0.2.12", "svelte": "^5.2.1", "svelte-check": "^4.0.8", - "typescript": "^5.5.4", + "tsx": "^4.19.2", + "typescript": "^5.6.3", "typescript-eslint": "^8.14.0", - "vite": "^5.4.11" + "vite": "^5.4.11", + "zod": "^3.23.8" } -} +} \ No newline at end of file diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..fda1d59 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,11 @@ +import chalk from 'chalk'; +import { run } from './shared/run'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; + +console.log(chalk.cyan(`Building @keycloakify/svelte...`)); + +const startTime = Date.now(); + +run('npx svelte-kit sync && rm -rf dist && npx svelte-package && npx publint', { cwd: getThisCodebaseRootDirPath() }); + +console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)); diff --git a/scripts/link-in-app.ts b/scripts/link-in-app.ts new file mode 100644 index 0000000..602532d --- /dev/null +++ b/scripts/link-in-app.ts @@ -0,0 +1,187 @@ +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import { join as pathJoin, relative as pathRelative } from 'path'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; + +const singletonDependencies: string[] = ['keycloakify', 'typescript', '@sveltejs/vite-plugin-svelte', 'svelte']; + +// For example [ "@emotion" ] it's more convenient than +// having to list every sub emotion packages (@emotion/css @emotion/utils ...) +// in singletonDependencies +const namespaceSingletonDependencies: string[] = []; + +const rootDirPath = getThisCodebaseRootDirPath(); + +const commonThirdPartyDeps = [ + ...namespaceSingletonDependencies + .map((namespaceModuleName) => + fs + .readdirSync(pathJoin(rootDirPath, 'node_modules', namespaceModuleName)) + .map((submoduleName) => `${namespaceModuleName}/${submoduleName}`), + ) + .reduce((prev, curr) => [...prev, ...curr], []), + ...singletonDependencies, +]; + +const yarnGlobalDirPath = pathJoin(rootDirPath, '.yarn_home'); + +fs.rmSync(yarnGlobalDirPath, { recursive: true, force: true }); +fs.mkdirSync(yarnGlobalDirPath); + +const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => { + const { targetModuleName, cwd } = params; + + if (targetModuleName === undefined) { + const packageJsonFilePath = pathJoin(cwd, 'package.json'); + + const packageJson = JSON.parse(fs.readFileSync(packageJsonFilePath).toString('utf8')); + + delete packageJson['packageManager']; + + fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(packageJson, null, 2))); + } + + const cmd = ['yarn', 'link', ...(targetModuleName !== undefined ? [targetModuleName] : ['--no-bin-links'])].join(' '); + + console.log(`$ cd ${pathRelative(rootDirPath, cwd) || '.'} && ${cmd}`); + + execSync(cmd, { + cwd, + env: { + ...process.env, + ...(os.platform() === 'win32' + ? { + USERPROFILE: yarnGlobalDirPath, + LOCALAPPDATA: yarnGlobalDirPath, + } + : { HOME: yarnGlobalDirPath }), + }, + }); +}; + +const testAppPaths = (() => { + const [, , ...testAppNames] = process.argv; + + return testAppNames + .map((testAppName) => { + const testAppPath = pathJoin(rootDirPath, '..', testAppName); + + if (fs.existsSync(testAppPath)) { + return testAppPath; + } + + console.warn(`Skipping ${testAppName} since it cant be found here: ${testAppPath}`); + + return undefined; + }) + .filter((path): path is string => path !== undefined); +})(); + +if (testAppPaths.length === 0) { + console.error('No test app to link into!'); + process.exit(-1); +} + +testAppPaths.forEach((testAppPath) => { + const packageJsonFilePath = pathJoin(testAppPath, 'package.json'); + + const packageJsonContent = fs.readFileSync(packageJsonFilePath); + + const parsedPackageJson = JSON.parse(packageJsonContent.toString('utf8')) as { + scripts?: Record; + }; + + let hasPostInstallOrPrepareScript = false; + + if (parsedPackageJson.scripts !== undefined) { + for (const scriptName of ['postinstall', 'prepare']) { + if (parsedPackageJson.scripts[scriptName] === undefined) { + continue; + } + + hasPostInstallOrPrepareScript = true; + + delete parsedPackageJson.scripts[scriptName]; + } + } + + if (hasPostInstallOrPrepareScript) { + fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), 'utf8')); + } + + const restorePackageJson = () => { + if (!hasPostInstallOrPrepareScript) { + return; + } + + fs.writeFileSync(packageJsonFilePath, packageJsonContent); + }; + + try { + execSync('yarn install', { cwd: testAppPath }); + } catch (error) { + restorePackageJson(); + + throw error; + } + + restorePackageJson(); +}); + +console.log('=== Linking common dependencies ==='); + +const total = commonThirdPartyDeps.length; +let current = 0; + +commonThirdPartyDeps.forEach((commonThirdPartyDep) => { + current++; + + console.log(`${current}/${total} ${commonThirdPartyDep}`); + + const localInstallPath = pathJoin( + ...[ + rootDirPath, + 'node_modules', + ...(commonThirdPartyDep.startsWith('@') ? commonThirdPartyDep.split('/') : [commonThirdPartyDep]), + ], + ); + + execYarnLink({ cwd: localInstallPath }); +}); + +commonThirdPartyDeps.forEach((commonThirdPartyDep) => + testAppPaths.forEach((testAppPath) => + execYarnLink({ + cwd: testAppPath, + targetModuleName: commonThirdPartyDep, + }), + ), +); + +console.log('=== Linking in house dependencies ==='); + +execYarnLink({ cwd: pathJoin(rootDirPath) }); + +testAppPaths.forEach((testAppPath) => + execYarnLink({ + cwd: testAppPath, + targetModuleName: JSON.parse(fs.readFileSync(pathJoin(rootDirPath, 'package.json')).toString('utf8'))['name'], + }), +); + +testAppPaths.forEach((testAppPath) => { + const { scripts = {} } = JSON.parse(fs.readFileSync(pathJoin(testAppPath, 'package.json')).toString('utf8')) as { + scripts?: Record; + }; + + for (const scriptName of ['postinstall', 'prepare']) { + if (scripts[scriptName] === undefined) { + continue; + } + + execSync(`yarn run ${scriptName}`, { cwd: testAppPath }); + } +}); + +export {}; diff --git a/scripts/link-in-starter.ts b/scripts/link-in-starter.ts new file mode 100644 index 0000000..2263cee --- /dev/null +++ b/scripts/link-in-starter.ts @@ -0,0 +1,114 @@ +import chalk from 'chalk'; +import * as child_process from 'child_process'; +import cliSelect from 'cli-select'; +import * as fs from 'fs'; +import { join as pathJoin, sep as pathSep } from 'path'; +import { run } from './shared/run'; +import { Deferred } from './tools/Deferred'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; +import { removeNodeModules } from './tools/removeNodeModules'; + +(async () => { + const parentDirPath = pathJoin(getThisCodebaseRootDirPath(), '..'); + + const { starterName } = await (async () => { + const starterNames = fs + .readdirSync(parentDirPath) + .filter( + (basename) => + basename.includes('starter') && + basename.includes('svelte') && + fs.statSync(pathJoin(parentDirPath, basename)).isDirectory(), + ); + + if (starterNames.length === 0) { + console.log(chalk.red(`No starter found. Keycloakify Svelte starter found in ${parentDirPath}`)); + process.exit(-1); + } + + const starterName = await (async () => { + if (starterNames.length === 1) { + return starterNames[0]; + } + + console.log(chalk.cyan(`\nSelect a starter to link in:`)); + + const { value } = await cliSelect({ + values: starterNames.map((starterName) => `..${pathSep}${starterName}`), + }).catch(() => { + process.exit(-1); + }); + + return value.split(pathSep)[1]; + })(); + + return { starterName }; + })(); + + const startTime = Date.now(); + + console.log(chalk.cyan(`\n\nLinking in ..${pathSep}${starterName}...`)); + + removeNodeModules({ + nodeModulesDirPath: pathJoin(getThisCodebaseRootDirPath(), 'node_modules'), + }); + + fs.rmSync(pathJoin(getThisCodebaseRootDirPath(), 'dist'), { + recursive: true, + force: true, + }); + fs.rmSync(pathJoin(getThisCodebaseRootDirPath(), '.yarn_home'), { + recursive: true, + force: true, + }); + + run('yarn install'); + + const dInitialCompilationCompleted = new Deferred(); + + { + const child = child_process.spawn('npx', ['tsx', pathJoin(getThisCodebaseRootDirPath(), 'scripts', 'watch.ts')], { + shell: true, + cwd: getThisCodebaseRootDirPath(), + }); + + const SVELTE_BUILD_WATCHING_FOR_FILE_CHANGES = 'Watching src/lib for changes...'; + + const onData = (data: Buffer) => { + if (!data.toString('utf8').includes(SVELTE_BUILD_WATCHING_FOR_FILE_CHANGES)) { + process.stdout.write(data); + return; + } + + child.stdout.off('data', onData); + + child.stdout.on('data', (data) => process.stdout.write(data)); + + dInitialCompilationCompleted.resolve(); + }; + + child.stdout.on('data', onData); + + child.stderr.on('data', (data) => process.stderr.write(data)); + + child.on('exit', (code) => process.exit(code ?? 0)); + } + + await dInitialCompilationCompleted.pr; + + const starterDirPath = pathJoin(parentDirPath, starterName); + + removeNodeModules({ + nodeModulesDirPath: pathJoin(starterDirPath, 'node_modules'), + }); + + run('yarn install', { cwd: pathJoin('..', starterName) }); + + run(`npx tsx ${pathJoin('scripts', 'link-in-app.ts')} ${starterName}`); + + const durationSeconds = Math.round((Date.now() - startTime) / 1000); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + console.log(chalk.green(`\n\nLinked in ${starterName} in ${durationSeconds}s`)); +})(); diff --git a/scripts/shared/run.ts b/scripts/shared/run.ts new file mode 100644 index 0000000..85eb3cc --- /dev/null +++ b/scripts/shared/run.ts @@ -0,0 +1,8 @@ +import chalk from 'chalk'; +import * as child_process from 'child_process'; + +export function run(command: string, options?: { cwd: string }) { + console.log(chalk.grey(`$ ${command}`)); + + child_process.execSync(command, { stdio: 'inherit', ...options }); +} diff --git a/scripts/tools/Deferred.ts b/scripts/tools/Deferred.ts new file mode 100644 index 0000000..fcc18a9 --- /dev/null +++ b/scripts/tools/Deferred.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-namespace */ +import { overwriteReadonlyProp } from 'tsafe/lab/overwriteReadonlyProp'; + +export class Deferred { + public readonly pr: Promise; + + /** NOTE: Does not need to be called bound to instance*/ + public readonly resolve: (value: T) => void; + public readonly reject: (error: any) => void; + + constructor() { + let resolve!: (value: T) => void; + let reject!: (error: any) => void; + + this.pr = new Promise((resolve_, reject_) => { + resolve = (value) => { + overwriteReadonlyProp(this, 'isPending', false); + resolve_(value); + }; + + reject = (error) => { + overwriteReadonlyProp(this, 'isPending', false); + reject_(error); + }; + }); + + this.resolve = resolve; + this.reject = reject; + } + + public readonly isPending: boolean = true; +} + +export namespace Deferred { + export type Unpack> = T extends Deferred ? U : never; +} + +export class VoidDeferred extends Deferred { + public declare readonly resolve: () => void; +} diff --git a/scripts/tools/StatefulObservable.ts b/scripts/tools/StatefulObservable.ts new file mode 100644 index 0000000..4541131 --- /dev/null +++ b/scripts/tools/StatefulObservable.ts @@ -0,0 +1,54 @@ +export type StatefulObservable = { + current: T; + subscribe: (next: (data: T) => void) => Subscription; +}; + +export type StatefulReadonlyObservable = { + readonly current: T; + subscribe: (next: (data: T) => void) => Subscription; +}; + +export type Subscription = { + unsubscribe(): void; +}; + +export function createStatefulObservable(getInitialValue: () => T): StatefulObservable { + const nextFunctions: ((data: T) => void)[] = []; + + const { get, set } = (() => { + let wrappedState: [T] | undefined = undefined; + + return { + get: () => { + if (wrappedState === undefined) { + wrappedState = [getInitialValue()]; + } + return wrappedState[0]; + }, + set: (data: T) => { + wrappedState = [data]; + + nextFunctions.forEach((next) => next(data)); + }, + }; + })(); + + return Object.defineProperty( + { + current: null as any as T, + subscribe: (next: (data: T) => void) => { + nextFunctions.push(next); + + return { + unsubscribe: () => nextFunctions.splice(nextFunctions.indexOf(next), 1), + }; + }, + }, + 'current', + { + enumerable: true, + get, + set, + }, + ); +} diff --git a/scripts/tools/fs.existsAsync.ts b/scripts/tools/fs.existsAsync.ts new file mode 100644 index 0000000..b64c624 --- /dev/null +++ b/scripts/tools/fs.existsAsync.ts @@ -0,0 +1,11 @@ +import * as fs from 'fs/promises'; + +export async function existsAsync(path: string) { + try { + await fs.stat(path); + return true; + } catch (error) { + if ((error as Error & { code: string }).code === 'ENOENT') return false; + throw error; + } +} diff --git a/scripts/tools/fs.rm.ts b/scripts/tools/fs.rm.ts new file mode 100644 index 0000000..89d2f84 --- /dev/null +++ b/scripts/tools/fs.rm.ts @@ -0,0 +1,43 @@ +import * as fs from 'fs/promises'; +import { join as pathJoin } from 'path'; +import { SemVer } from '../../src/bin/tools/SemVer'; + +/** + * Polyfill of fs.rm(dirPath, { "recursive": true }) + * For older version of Node + */ +export async function rm(dirPath: string, options: { recursive: true; force?: true }) { + if (SemVer.compare(SemVer.parse(process.version), SemVer.parse('14.14.0')) > 0) { + return fs.rm(dirPath, options); + } + + const { force = true } = options; + + if (force && !(await checkDirExists(dirPath))) { + return; + } + + const removeDir_rec = async (dirPath: string) => + Promise.all( + (await fs.readdir(dirPath)).map(async (basename) => { + const fileOrDirpath = pathJoin(dirPath, basename); + + if ((await fs.lstat(fileOrDirpath)).isDirectory()) { + await removeDir_rec(fileOrDirpath); + } else { + await fs.unlink(fileOrDirpath); + } + }), + ); + + await removeDir_rec(dirPath); +} + +async function checkDirExists(dirPath: string) { + try { + await fs.access(dirPath, fs.constants.F_OK); + return true; + } catch { + return false; + } +} diff --git a/scripts/tools/getThisCodebaseRootDirPath.ts b/scripts/tools/getThisCodebaseRootDirPath.ts new file mode 100644 index 0000000..5d93ea3 --- /dev/null +++ b/scripts/tools/getThisCodebaseRootDirPath.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as url from 'url'; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +function getThisCodebaseRootDirPath_rec(dirPath: string): string { + if (fs.existsSync(path.join(dirPath, 'package.json'))) { + return dirPath; + } + return getThisCodebaseRootDirPath_rec(path.join(dirPath, '..')); +} + +let result: string | undefined = undefined; + +export function getThisCodebaseRootDirPath(): string { + if (result !== undefined) { + return result; + } + + return (result = getThisCodebaseRootDirPath_rec(__dirname)); +} diff --git a/scripts/tools/removeNodeModules.ts b/scripts/tools/removeNodeModules.ts new file mode 100644 index 0000000..e8b4eae --- /dev/null +++ b/scripts/tools/removeNodeModules.ts @@ -0,0 +1,27 @@ +import * as fs from 'fs'; +import { crawl } from '../../src/bin/tools/crawl'; + +export function removeNodeModules(params: { nodeModulesDirPath: string }) { + const { nodeModulesDirPath } = params; + + try { + fs.rmSync(nodeModulesDirPath, { recursive: true, force: true }); + } catch { + // NOTE: This is a workaround for windows + // we can't remove locked executables. + + crawl({ + dirPath: nodeModulesDirPath, + returnedPathsType: 'absolute', + }).forEach((filePath) => { + try { + fs.rmSync(filePath, { force: true }); + } catch (error) { + if (filePath.endsWith('.exe')) { + return; + } + throw error; + } + }); + } +} diff --git a/scripts/tools/waitForThrottle.ts b/scripts/tools/waitForThrottle.ts new file mode 100644 index 0000000..01fe710 --- /dev/null +++ b/scripts/tools/waitForThrottle.ts @@ -0,0 +1,45 @@ +import { Deferred } from './Deferred'; +import { createStatefulObservable } from './StatefulObservable'; + +export function createWaitForThrottle(params: { delay: number }) { + const { delay } = params; + + const obsCurr = createStatefulObservable<{ timer: ReturnType; startTime: number } | undefined>( + () => undefined, + ); + + function waitForThrottle(): Promise { + const dOut = new Deferred(); + + const timerCallback = () => { + obsCurr.current = undefined; + dOut.resolve(); + }; + + if (obsCurr.current !== undefined) { + clearTimeout(obsCurr.current.timer); + + obsCurr.current.timer = setTimeout(timerCallback, delay - (Date.now() - obsCurr.current.startTime)); + + return dOut.pr; + } else { + const startTime = Date.now(); + + obsCurr.current = { + timer: setTimeout(timerCallback, delay), + startTime, + }; + } + + return dOut.pr; + } + + const obsIsDebouncing = createStatefulObservable(() => false); + + obsCurr.subscribe((curr) => (obsIsDebouncing.current = curr !== undefined)); + + return { + waitForThrottle, + obsIsDebouncing, + }; +} diff --git a/scripts/watch.ts b/scripts/watch.ts new file mode 100644 index 0000000..ab7a491 --- /dev/null +++ b/scripts/watch.ts @@ -0,0 +1,68 @@ +import chalk from 'chalk'; +import * as child_process from 'child_process'; +import chokidar from 'chokidar'; +import { EventEmitter } from 'events'; +import * as fs from 'fs'; +import { join as pathJoin } from 'path'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; +import { createWaitForThrottle } from './tools/waitForThrottle'; + +(async () => { + const postBuild = () => { + { + const packageJsonFilePath = pathJoin(getThisCodebaseRootDirPath(), 'package.json'); + + const parsedPackageJson = JSON.parse(fs.readFileSync(packageJsonFilePath).toString('utf8')) as { + version: string; + }; + + parsedPackageJson.version = `0.0.0-rc.${Date.now()}`; + + fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), 'utf8')); + } + + console.log('@keycloakify/svelte Build complete'); + console.log(chalk.cyan(`@keycloakify/svelte Watching for file changes...`)); + }; + + const eeBuildComplete = new EventEmitter(); + + { + const child = child_process.spawn('npx', ['svelte-package', '--watch'], { + shell: true, + cwd: getThisCodebaseRootDirPath(), + }); + + child.stdout.on('data', (data) => { + if (data.toString('utf8').includes('Watching for file changes')) { + eeBuildComplete.emit(''); + return; + } + + process.stdout.write(data); + }); + + child.stderr.on('data', (data) => process.stderr.write(data)); + + child.on('exit', (code) => process.exit(code ?? 0)); + } + + eeBuildComplete.on('', () => postBuild()); + + await new Promise((resolve) => eeBuildComplete.once('', resolve)); + + const { waitForThrottle } = createWaitForThrottle({ delay: 400 }); + + chokidar + .watch( + ['bin', 'stories'].map((relativePath) => pathJoin(getThisCodebaseRootDirPath(), 'src', relativePath)), + { ignoreInitial: true }, + ) + .on('all', async (event, path) => { + console.log(chalk.bold(`${event}: ${path}`)); + + await waitForThrottle(); + + postBuild(); + }); +})(); diff --git a/src/bin/add-story.ts b/src/bin/add-story.ts new file mode 100644 index 0000000..68a4729 --- /dev/null +++ b/src/bin/add-story.ts @@ -0,0 +1,121 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import chalk from 'chalk'; +import cliSelect from 'cli-select'; +import * as fs from 'fs'; +import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from 'path'; +import { assert, Equals } from 'tsafe/assert'; +import { + ACCOUNT_THEME_PAGE_IDS, + type AccountThemePageId, + type BuildContext, + LOGIN_THEME_PAGE_IDS, + type LoginThemePageId, + THEME_TYPES, +} from './core'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; +import { getIsPrettierAvailable, runPrettier } from './tools/runPrettier'; + +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; + + console.log(chalk.cyan('Theme type:')); + + const themeType = await (async () => { + const values = THEME_TYPES.filter((themeType) => { + switch (themeType) { + case 'account': + return buildContext.implementedThemeTypes.account.isImplemented; + case 'login': + return buildContext.implementedThemeTypes.login.isImplemented; + } + // @ts-ignore + assert>(false); + }); + + assert(values.length > 0, 'No theme is implemented in this project'); + + if (values.length === 1) { + return values[0]; + } + + const { value } = await cliSelect({ + values, + }).catch(() => { + process.exit(-1); + }); + + return value; + })(); + + console.log(`→ ${themeType}`); + + console.log(chalk.cyan('Select the page you want to create a Storybook for:')); + + const { value: pageId } = await cliSelect({ + values: (() => { + switch (themeType) { + case 'login': + return [...LOGIN_THEME_PAGE_IDS]; + case 'account': + return [...ACCOUNT_THEME_PAGE_IDS]; + } + // @ts-ignore + assert>(false); + })(), + }).catch(() => { + process.exit(-1); + }); + + console.log(`→ ${pageId}`); + + const componentBasename = pageId.replace(/ftl$/, 'stories.ts'); + + const targetFilePath = pathJoin( + buildContext.themeSrcDirPath, + themeType, + 'pages', + pageId.replace(/\.ftl$/, ''), + componentBasename, + ); + + if (fs.existsSync(targetFilePath)) { + console.log(`${pathRelative(process.cwd(), targetFilePath)} already exists`); + + process.exit(-1); + } + + let sourceCode = fs + .readFileSync(pathJoin(getThisCodebaseRootDirPath(), 'stories', themeType, 'pages', componentBasename)) + .toString('utf8') + .replace(/["']\.\.\/KcPageStory["']/, "'../../KcPageStory'"); + + run_prettier: { + if (!(await getIsPrettierAvailable())) { + break run_prettier; + } + + sourceCode = await runPrettier({ + filePath: targetFilePath, + sourceCode: sourceCode, + }); + } + + { + const targetDirPath = pathDirname(targetFilePath); + + if (!fs.existsSync(targetDirPath)) { + fs.mkdirSync(targetDirPath, { recursive: true }); + } + } + + fs.writeFileSync(targetFilePath, Buffer.from(sourceCode, 'utf8')); + + console.log( + [ + `${chalk.green('✓')} ${chalk.bold( + pathJoin('.', pathRelative(process.cwd(), targetFilePath)), + )} copy pasted from the Keycloakify source code into your project`, + `You can start storybook with ${chalk.bold('npm run storybook')}`, + ].join('\n'), + ); +} diff --git a/src/bin/core.ts b/src/bin/core.ts new file mode 100644 index 0000000..514a09c --- /dev/null +++ b/src/bin/core.ts @@ -0,0 +1,10 @@ +export type { BuildContext } from 'keycloakify/bin/shared/buildContext'; +export { + ACCOUNT_THEME_PAGE_IDS, + LOGIN_THEME_PAGE_IDS, + THEME_TYPES, + type AccountThemePageId, + type LoginThemePageId, + type ThemeType, +} from 'keycloakify/bin/shared/constants'; +export * from 'keycloakify/bin/shared/customHandler'; diff --git a/src/bin/eject-page.ts b/src/bin/eject-page.ts new file mode 100644 index 0000000..7009e1d --- /dev/null +++ b/src/bin/eject-page.ts @@ -0,0 +1,326 @@ +#!/usr/bin/env node +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import chalk from 'chalk'; +import cliSelect from 'cli-select'; +import * as fs from 'fs'; +import { + basename as pathBase, + dirname as pathDirname, + join as pathJoin, + posix as pathPosix, + relative as pathRelative, + sep as pathSep, +} from 'path'; +import { assert, Equals } from 'tsafe/assert'; +import { capitalize } from 'tsafe/capitalize'; +import { + ACCOUNT_THEME_PAGE_IDS, + type AccountThemePageId, + type BuildContext, + LOGIN_THEME_PAGE_IDS, + type LoginThemePageId, + THEME_TYPES, + type ThemeType, +} from './core'; +import { getThisCodebaseRootDirPath } from './tools/getThisCodebaseRootDirPath'; +import { getIsPrettierAvailable, runPrettier } from './tools/runPrettier'; +import { replaceAll } from './tools/String.prototype.replaceAll'; +import { transformCodebase } from './tools/transformCodebase_async'; + +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; + + console.log(chalk.cyan('Theme type:')); + + const themeType = await (async () => { + const values = THEME_TYPES.filter((themeType) => { + switch (themeType) { + case 'account': + return buildContext.implementedThemeTypes.account.isImplemented; + case 'login': + return buildContext.implementedThemeTypes.login.isImplemented; + } + // @ts-ignore + assert>(false); + }); + + assert(values.length > 0, 'No theme is implemented in this project'); + + if (values.length === 1) { + return values[0]; + } + + const { value } = await cliSelect({ + values, + }).catch(() => { + process.exit(-1); + }); + + return value; + })(); + + console.log(`→ ${themeType}`); + + console.log(chalk.cyan('Select the page you want to customize:')); + + const templateValue = 'template.ftl (Layout common to every page)'; + const userProfileFormFieldsValue = + 'user-profile-commons.ftl (Renders the form of the register.ftl, login-update-profile.ftl, update-email.ftl and idp-review-user-profile.ftl)'; + + const { value: pageIdOrComponent } = await cliSelect< + LoginThemePageId | AccountThemePageId | typeof templateValue | typeof userProfileFormFieldsValue + >({ + values: (() => { + switch (themeType) { + case 'login': + return [templateValue, userProfileFormFieldsValue, ...LOGIN_THEME_PAGE_IDS]; + case 'account': + return [templateValue, ...ACCOUNT_THEME_PAGE_IDS]; + } + // @ts-ignore + assert>(false); + })(), + }).catch(() => { + process.exit(-1); + }); + + console.log(`→ ${pageIdOrComponent}`); + + const componentRelativeDirPath_posix_to_componentRelativeFilePath_posix = (params: { + componentRelativeDirPath_posix: string; + }) => { + const { componentRelativeDirPath_posix } = params; + return `${componentRelativeDirPath_posix}/${pathPosix.basename(componentRelativeDirPath_posix)}.component`; + }; + + const componentDirRelativeToThemeTypePath = (() => { + if (pageIdOrComponent === templateValue) { + return pathJoin('Template'); + } + + if (pageIdOrComponent === userProfileFormFieldsValue) { + return pathJoin('components', 'UserProfileFormFields'); + } + + return pathJoin('pages', capitalize(pageIdOrComponent.replace(/\.ftl$/, ''))); + })(); + + { + const componentDirRelativeToThemeTypePaths = [componentDirRelativeToThemeTypePath]; + + while (componentDirRelativeToThemeTypePaths.length !== 0) { + const componentDirRelativeToThemeTypePath_i = componentDirRelativeToThemeTypePaths.pop(); + + assert(componentDirRelativeToThemeTypePath_i !== undefined); + + const destDirPath = pathJoin(buildContext.themeSrcDirPath, themeType, componentDirRelativeToThemeTypePath_i); + + const dirName = pathBase(destDirPath); + const tsFilePath = pathJoin(destDirPath, `${dirName}.svelte`); + if (fs.existsSync(destDirPath) && fs.readdirSync(destDirPath).length !== 0) { + // Check if the directory contains a .ts file with the same name as the directory + if (fs.existsSync(tsFilePath)) { + if (componentDirRelativeToThemeTypePath_i === componentDirRelativeToThemeTypePath) { + console.log( + `${pageIdOrComponent.split('.ftl')[0]} is already ejected, ${pathRelative( + process.cwd(), + destDirPath, + )} already exists and contains ${dirName}.svelte`, + ); + process.exit(-1); + } + continue; + } + } + + const localThemeTypeDirPath = pathJoin(getThisCodebaseRootDirPath(), 'src', themeType); + + await transformCodebase({ + srcDirPath: pathJoin(localThemeTypeDirPath, componentDirRelativeToThemeTypePath_i), + destDirPath, + transformSourceCode: async ({ filePath, fileRelativePath, sourceCode }) => { + if (filePath.endsWith('index.ts')) { + return undefined; + } + if (filePath.endsWith('.ts')) { + let modifiedSourceCode_str = sourceCode.toString('utf8'); + + run_prettier: { + if (!(await getIsPrettierAvailable())) { + break run_prettier; + } + + modifiedSourceCode_str = await runPrettier({ + filePath: pathJoin(destDirPath, fileRelativePath), + sourceCode: modifiedSourceCode_str, + }); + } + + return { + modifiedSourceCode: Buffer.from(modifiedSourceCode_str, 'utf8'), + }; + } + const fileRelativeToThemeTypePath = pathRelative(localThemeTypeDirPath, filePath); + + let modifiedSourceCode_str = sourceCode.toString(); + + const getPosixPathRelativeToFile = (params: { pathRelativeToThemeType: string }) => { + const { pathRelativeToThemeType } = params; + + const path = pathRelative(pathDirname(fileRelativeToThemeTypePath), pathRelativeToThemeType) + .split(pathSep) + .join('/'); + + return path.startsWith('.') ? path : `./${path}`; + }; + + modifiedSourceCode_str = replaceAll( + modifiedSourceCode_str, + `@keycloakify/svelte/${themeType}/i18n`, + getPosixPathRelativeToFile({ + pathRelativeToThemeType: 'i18n', + }), + ); + + modifiedSourceCode_str = replaceAll( + modifiedSourceCode_str, + `@keycloakify/svelte/${themeType}/KcContext`, + getPosixPathRelativeToFile({ + pathRelativeToThemeType: 'KcContext', + }), + ); + + modifiedSourceCode_str = modifiedSourceCode_str.replace( + new RegExp(`@keycloakify/svelte/${themeType}/components/([^'"]+)`, 'g'), + (...[, componentDirRelativeToComponentsPath]) => { + const componentDirRelativeToThemeTypePath = pathJoin('components', componentDirRelativeToComponentsPath); + + componentDirRelativeToThemeTypePaths.push(componentDirRelativeToThemeTypePath); + + return componentRelativeDirPath_posix_to_componentRelativeFilePath_posix({ + componentRelativeDirPath_posix: getPosixPathRelativeToFile({ + pathRelativeToThemeType: componentDirRelativeToThemeTypePath, + }), + }); + }, + ); + + run_prettier: { + if (!(await getIsPrettierAvailable())) { + break run_prettier; + } + + modifiedSourceCode_str = await runPrettier({ + filePath: pathJoin(destDirPath, fileRelativePath), + sourceCode: modifiedSourceCode_str, + }); + } + + return { + modifiedSourceCode: Buffer.from(modifiedSourceCode_str, 'utf8'), + }; + }, + }); + + console.log( + `${chalk.green('✓')} ${chalk.bold( + `.${pathSep}` + pathRelative(process.cwd(), destDirPath), + )} moved from the @keycloakify/svelte to your project`, + ); + } + } + + edit_KcPage: { + if (pageIdOrComponent !== templateValue && pageIdOrComponent !== userProfileFormFieldsValue) { + break edit_KcPage; + } + + const kcAppTsFilePath = pathJoin(buildContext.themeSrcDirPath, themeType, 'KcPage.svelte'); + + const kcAppTsCode = fs.readFileSync(kcAppTsFilePath).toString('utf8'); + + const modifiedKcAppTsCode = await (async () => { + const componentRelativeDirPath_posix = componentDirRelativeToThemeTypePath.split(pathSep).join('/'); + + let sourceCode = kcAppTsCode.replace( + `@keycloakify/svelte/${themeType}/${componentRelativeDirPath_posix}`, + componentRelativeDirPath_posix_to_componentRelativeFilePath_posix({ + componentRelativeDirPath_posix: `./${componentRelativeDirPath_posix}`, + }), + ); + + run_prettier: { + if (!(await getIsPrettierAvailable())) { + break run_prettier; + } + + sourceCode = await runPrettier({ + filePath: kcAppTsFilePath, + sourceCode, + }); + } + + return sourceCode; + })(); + + if (modifiedKcAppTsCode === kcAppTsCode) { + console.log(chalk.red('Unable to automatically update KcPage.svelte, please update it manually')); + return; + } + + fs.writeFileSync(kcAppTsFilePath, Buffer.from(modifiedKcAppTsCode, 'utf8')); + + console.log( + `${chalk.green('✓')} ${chalk.bold(`.${pathSep}` + pathRelative(process.cwd(), kcAppTsFilePath))} Updated`, + ); + + return; + } + + const pageId = pageIdOrComponent; + + console.log( + [ + ``, + `You now need to update your page router:`, + ``, + `${chalk.bold( + pathJoin('.', pathRelative(process.cwd(), buildContext.themeSrcDirPath), themeType, 'KcPage.svelte'), + )}:`, + chalk.grey('```'), + `// ...`, + ``, + ...(() => { + let inGreenBlock = false; + return [ + ` const page = async () => {`, + ` switch (kcContext.pageId) {`, + `+`, + ` case '${pageId}':`, + ` return import('${componentRelativeDirPath_posix_to_componentRelativeFilePath_posix({ + componentRelativeDirPath_posix: `./${componentDirRelativeToThemeTypePath.split(pathSep).join('/')}`, + })}');`, + `+`, + ` //...`, + ` default:`, + ` return import('@keycloakify/svelte/login/DefaultPage.svelte');`, + ` }`, + ` }`, + ].map((line) => { + if (line === `+`) { + inGreenBlock = !inGreenBlock; + } + if (inGreenBlock || line.startsWith('+')) { + return chalk.green(line); + } + if (line.startsWith('-')) { + return chalk.red(line); + } + return chalk.grey(line); + }); + })(), + chalk.grey('```'), + ].join('\n'), + ); +} diff --git a/src/bin/initialize-account-theme/boilerplate/KcContext.ts b/src/bin/initialize-account-theme/boilerplate/KcContext.ts new file mode 100644 index 0000000..55b3182 --- /dev/null +++ b/src/bin/initialize-account-theme/boilerplate/KcContext.ts @@ -0,0 +1,11 @@ +import type { ExtendKcContext } from 'keycloakify/account'; +import type { KcEnvName, ThemeName } from '../kc.gen'; + +export type KcContextExtension = { + themeName: ThemeName; + properties: Record & {}; +}; + +export type KcContextExtensionPerPage = Record>; + +export type KcContext = ExtendKcContext; diff --git a/src/bin/initialize-account-theme/boilerplate/KcPage.svelte b/src/bin/initialize-account-theme/boilerplate/KcPage.svelte new file mode 100644 index 0000000..13fdae7 --- /dev/null +++ b/src/bin/initialize-account-theme/boilerplate/KcPage.svelte @@ -0,0 +1,32 @@ + + +{#await page() then { default: Page }} + +{/await} diff --git a/src/bin/initialize-account-theme/boilerplate/KcPageStory.ts b/src/bin/initialize-account-theme/boilerplate/KcPageStory.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/bin/initialize-account-theme/boilerplate/i18n.ts b/src/bin/initialize-account-theme/boilerplate/i18n.ts new file mode 100644 index 0000000..a7bab98 --- /dev/null +++ b/src/bin/initialize-account-theme/boilerplate/i18n.ts @@ -0,0 +1,11 @@ +import { i18nBuilder } from '@keycloakify/svelte/account'; +import { ThemeName } from '../kc.gen'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const { getI18n, ofTypeI18n } = i18nBuilder + .withThemeName() + .withExtraLanguages({}) // See: https://docs.keycloakify.dev/i18n/adding-support-for-extra-languages + .withCustomTranslations({}) // See: https://docs.keycloakify.dev/i18n/adding-new-translation-messages-or-changing-the-default-ones + .build(); +type I18n = typeof ofTypeI18n; +export { getI18n, type I18n }; diff --git a/src/bin/initialize-account-theme/index.ts b/src/bin/initialize-account-theme/index.ts new file mode 100644 index 0000000..d89eed9 --- /dev/null +++ b/src/bin/initialize-account-theme/index.ts @@ -0,0 +1 @@ +export * from './initialize-account-theme'; diff --git a/src/bin/initialize-account-theme/initialize-account-theme.ts b/src/bin/initialize-account-theme/initialize-account-theme.ts new file mode 100644 index 0000000..5f5720a --- /dev/null +++ b/src/bin/initialize-account-theme/initialize-account-theme.ts @@ -0,0 +1,87 @@ +import chalk from 'chalk'; +import child_process from 'child_process'; +import * as fs from 'fs'; +import { join as pathJoin, relative as pathRelative } from 'path'; +import type { BuildContext } from '../core'; +import { getThisCodebaseRootDirPath } from '../tools/getThisCodebaseRootDirPath'; +import { command as updateKcGenCommand } from '../update-kc-gen'; +import { updateAccountThemeImplementationInConfig } from './updateAccountThemeImplementationInConfig'; + +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; + + const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, 'account'); + + if (fs.existsSync(accountThemeSrcDirPath) && fs.readdirSync(accountThemeSrcDirPath).length > 0) { + console.warn( + chalk.red( + `There is already a ${pathRelative( + process.cwd(), + accountThemeSrcDirPath, + )} directory in your project. Aborting.`, + ), + ); + + process.exit(-1); + } + + exit_if_uncommitted_changes: { + let hasUncommittedChanges: boolean | undefined = undefined; + + try { + hasUncommittedChanges = + child_process + .execSync(`git status --porcelain`, { + cwd: buildContext.projectDirPath, + }) + .toString() + .trim() !== ''; + } catch { + // Probably not a git repository + break exit_if_uncommitted_changes; + } + + if (!hasUncommittedChanges) { + break exit_if_uncommitted_changes; + } + console.warn( + [ + chalk.red('Please commit or stash your changes before running this command.\n'), + "This command will modify your project's files so it's better to have a clean working directory", + 'so that you can easily see what has been changed and revert if needed.', + ].join(' '), + ); + + process.exit(-1); + } + + const accountThemeType = 'Multi-Page'; + + fs.cpSync( + pathJoin(getThisCodebaseRootDirPath(), 'src', 'bin', 'initialize-account-theme', 'boilerplate'), + accountThemeSrcDirPath, + { recursive: true }, + ); + + updateAccountThemeImplementationInConfig({ buildContext, accountThemeType }); + + updateKcGenCommand({ + buildContext: { + ...buildContext, + implementedThemeTypes: { + ...buildContext.implementedThemeTypes, + account: { + isImplemented: true, + type: accountThemeType, + }, + }, + }, + }); + + console.log( + [ + chalk.green(`The ${accountThemeType} account theme has been initialized.`), + `Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`, + ].join('\n'), + ); +} diff --git a/src/bin/initialize-account-theme/updateAccountThemeImplementationInConfig.ts b/src/bin/initialize-account-theme/updateAccountThemeImplementationInConfig.ts new file mode 100644 index 0000000..f13bb4b --- /dev/null +++ b/src/bin/initialize-account-theme/updateAccountThemeImplementationInConfig.ts @@ -0,0 +1,93 @@ +import chalk from 'chalk'; +import * as fs from 'fs'; +import { join as pathJoin } from 'path'; +import { assert, type Equals } from 'tsafe/assert'; +import { id } from 'tsafe/id'; +import { is } from 'tsafe/is'; +import { z } from 'zod'; +import type { BuildContext } from '../core'; + +export type BuildContextLike = { + bundler: BuildContext['bundler']; + projectDirPath: string; + packageJsonFilePath: string; +}; + +assert(); + +export function updateAccountThemeImplementationInConfig(params: { + buildContext: BuildContext; + accountThemeType: 'Multi-Page'; +}) { + const { buildContext, accountThemeType } = params; + + switch (buildContext.bundler) { + case 'vite': + { + const viteConfigPath = pathJoin(buildContext.projectDirPath, 'vite.config.ts'); + + if (!fs.existsSync(viteConfigPath)) { + console.log( + chalk.bold( + `You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite config`, + ), + ); + break; + } + + const viteConfigContent = fs.readFileSync(viteConfigPath).toString('utf8'); + + const modifiedViteConfigContent = viteConfigContent.replace( + /["']?accountThemeImplementation["']?\s*:\s*["']none["']/, + `accountThemeImplementation: '${accountThemeType}'`, + ); + + if (modifiedViteConfigContent === viteConfigContent) { + console.log( + chalk.yellow( + `You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite.config.ts`, + ), + ); + break; + } + + fs.writeFileSync(viteConfigPath, modifiedViteConfigContent); + } + break; + case 'webpack': + { + const parsedPackageJson = (() => { + type ParsedPackageJson = { + keycloakify: Record; + }; + + const zParsedPackageJson = (() => { + type TargetType = ParsedPackageJson; + + const zTargetType = z.object({ + keycloakify: z.record(z.unknown()), + }); + + assert, TargetType>>(); + + return id>(zTargetType); + })(); + + const parsedPackageJson = JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString('utf8')); + + zParsedPackageJson.parse(parsedPackageJson); + assert(is(parsedPackageJson)); + + return parsedPackageJson; + })(); + + parsedPackageJson.keycloakify.accountThemeImplementation = accountThemeType; + + fs.writeFileSync( + buildContext.packageJsonFilePath, + Buffer.from(JSON.stringify(parsedPackageJson, undefined, 4), 'utf8'), + ); + } + break; + } +} diff --git a/src/bin/main.ts b/src/bin/main.ts new file mode 100644 index 0000000..46d4513 --- /dev/null +++ b/src/bin/main.ts @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +import { NOT_IMPLEMENTED_EXIT_CODE, readParams } from './core'; + +const { buildContext, commandName } = readParams({ apiVersion: 'v1' }); + +(async () => { + switch (commandName) { + case 'add-story': + { + const { command } = await import('./add-story'); + command({ buildContext }); + } + return; + case 'eject-page': + { + const { command } = await import('./eject-page'); + command({ buildContext }); + } + return; + case 'update-kc-gen': + { + const { command } = await import('./update-kc-gen'); + command({ buildContext }); + } + return; + case 'initialize-account-theme': + { + const { command } = await import('./initialize-account-theme'); + command({ buildContext }); + } + return; + default: + process.exit(NOT_IMPLEMENTED_EXIT_CODE); + } +})(); diff --git a/src/bin/tools/SemVer.ts b/src/bin/tools/SemVer.ts new file mode 100644 index 0000000..8039fb9 --- /dev/null +++ b/src/bin/tools/SemVer.ts @@ -0,0 +1,107 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +export type SemVer = { + major: number; + minor: number; + patch: number; + rc?: number; + parsedFrom: string; +}; + +export namespace SemVer { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const bumpTypes = ['major', 'minor', 'patch', 'rc', 'no bump'] as const; + + export type BumpType = (typeof bumpTypes)[number]; + + export function parse(versionStr: string): SemVer { + const match = versionStr.match(/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/); + + if (!match) { + throw new Error(`${versionStr} is not a valid semantic version`); + } + + const semVer: Omit = { + major: parseInt(match[1]), + minor: parseInt(match[2]), + patch: (() => { + const str = match[3]; + + return str === undefined ? 0 : parseInt(str); + })(), + ...(() => { + const str = match[4]; + return str === undefined ? {} : { rc: parseInt(str) }; + })(), + }; + + const initialStr = stringify(semVer); + + Object.defineProperty(semVer, 'parsedFrom', { + enumerable: true, + get: function () { + const currentStr = stringify(this); + + if (currentStr !== initialStr) { + throw new Error( + `SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`, + ); + } + + return versionStr; + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return semVer as any; + } + + export function stringify(v: Omit): string { + return `${v.major}.${v.minor}.${v.patch}${v.rc === undefined ? '' : `-rc.${v.rc}`}`; + } + + /** + * + * v1 < v2 => -1 + * v1 === v2 => 0 + * v1 > v2 => 1 + * + */ + export function compare(v1: SemVer, v2: SemVer): -1 | 0 | 1 { + const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1); + const noUndefined = (n: number | undefined) => n ?? Infinity; + + for (const level of ['major', 'minor', 'patch', 'rc'] as const) { + if (noUndefined(v1[level]) !== noUndefined(v2[level])) { + return sign(noUndefined(v1[level]) - noUndefined(v2[level])); + } + } + + return 0; + } + + /* + console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0")) === -1 ) + console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0-rc.4")) === -1 ) + console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 ) + */ + + export function bumpType(params: { + versionBehind: string | SemVer; + versionAhead: string | SemVer; + }): BumpType | 'no bump' { + const versionAhead = typeof params.versionAhead === 'string' ? parse(params.versionAhead) : params.versionAhead; + const versionBehind = typeof params.versionBehind === 'string' ? parse(params.versionBehind) : params.versionBehind; + + if (compare(versionBehind, versionAhead) === 1) { + throw new Error(`Version regression ${stringify(versionBehind)} -> ${stringify(versionAhead)}`); + } + + for (const level of ['major', 'minor', 'patch', 'rc'] as const) { + if (versionBehind[level] !== versionAhead[level]) { + return level; + } + } + + return 'no bump'; + } +} diff --git a/src/bin/tools/String.prototype.replaceAll.ts b/src/bin/tools/String.prototype.replaceAll.ts new file mode 100644 index 0000000..6464bcd --- /dev/null +++ b/src/bin/tools/String.prototype.replaceAll.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export function replaceAll(string: string, searchValue: string | RegExp, replaceValue: string): string { + if ((string as any).replaceAll !== undefined) { + return (string as any).replaceAll(searchValue, replaceValue); + } + + // If the searchValue is a string + if (typeof searchValue === 'string') { + // Escape special characters in the string to be used in a regex + const escapedSearchValue = searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(escapedSearchValue, 'g'); + + return string.replace(regex, replaceValue); + } + + // If the searchValue is a global RegExp, use it directly + if (searchValue instanceof RegExp && searchValue.global) { + return string.replace(searchValue, replaceValue); + } + + // If the searchValue is a non-global RegExp, throw an error + if (searchValue instanceof RegExp) { + throw new TypeError('replaceAll must be called with a global RegExp'); + } + + // Convert searchValue to string if it's not a string or RegExp + const searchString = String(searchValue); + const regexFromString = new RegExp(searchString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); + + return string.replace(regexFromString, replaceValue); +} diff --git a/src/bin/tools/crawl.ts b/src/bin/tools/crawl.ts new file mode 100644 index 0000000..2bd5b11 --- /dev/null +++ b/src/bin/tools/crawl.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs'; +import { join as pathJoin, relative as pathRelative } from 'path'; + +const crawlRec = (dirPath: string, filePaths: string[]) => { + for (const basename of fs.readdirSync(dirPath)) { + const fileOrDirPath = pathJoin(dirPath, basename); + + if (fs.lstatSync(fileOrDirPath).isDirectory()) { + crawlRec(fileOrDirPath, filePaths); + + continue; + } + + filePaths.push(fileOrDirPath); + } +}; + +/** List all files in a given directory return paths relative to the dir_path */ +export function crawl(params: { dirPath: string; returnedPathsType: 'absolute' | 'relative to dirPath' }): string[] { + const { dirPath, returnedPathsType } = params; + + const filePaths: string[] = []; + + crawlRec(dirPath, filePaths); + + switch (returnedPathsType) { + case 'absolute': + return filePaths; + case 'relative to dirPath': + return filePaths.map((filePath) => pathRelative(dirPath, filePath)); + } +} diff --git a/src/bin/tools/fs.rmSync.ts b/src/bin/tools/fs.rmSync.ts new file mode 100644 index 0000000..6e231aa --- /dev/null +++ b/src/bin/tools/fs.rmSync.ts @@ -0,0 +1,34 @@ +import * as fs from 'fs'; +import { join as pathJoin } from 'path'; +import { SemVer } from './SemVer'; + +/** + * Polyfill of fs.rmSync(dirPath, { "recursive": true }) + * For older version of Node + */ +export function rmSync(dirPath: string, options: { recursive: true; force?: true }) { + if (SemVer.compare(SemVer.parse(process.version), SemVer.parse('14.14.0')) > 0) { + fs.rmSync(dirPath, options); + return; + } + + const { force = true } = options; + + if (force && !fs.existsSync(dirPath)) { + return; + } + + const removeDir_rec = (dirPath: string) => + fs.readdirSync(dirPath).forEach((basename) => { + const fileOrDirPath = pathJoin(dirPath, basename); + + if (fs.lstatSync(fileOrDirPath).isDirectory()) { + removeDir_rec(fileOrDirPath); + return; + } else { + fs.unlinkSync(fileOrDirPath); + } + }); + + removeDir_rec(dirPath); +} diff --git a/src/bin/tools/getThisCodebaseRootDirPath.ts b/src/bin/tools/getThisCodebaseRootDirPath.ts new file mode 100644 index 0000000..175a84b --- /dev/null +++ b/src/bin/tools/getThisCodebaseRootDirPath.ts @@ -0,0 +1,19 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +function getThisCodebaseRootDirPath_rec(dirPath: string): string { + if (fs.existsSync(path.join(dirPath, 'package.json'))) { + return dirPath; + } + return getThisCodebaseRootDirPath_rec(path.join(dirPath, '..')); +} + +let result: string | undefined = undefined; + +export function getThisCodebaseRootDirPath(): string { + if (result !== undefined) { + return result; + } + + return (result = getThisCodebaseRootDirPath_rec(__dirname)); +} diff --git a/src/bin/tools/kebabCaseToSnakeCase.ts b/src/bin/tools/kebabCaseToSnakeCase.ts new file mode 100644 index 0000000..c9ee106 --- /dev/null +++ b/src/bin/tools/kebabCaseToSnakeCase.ts @@ -0,0 +1,7 @@ +import { capitalize } from 'tsafe/capitalize'; + +export function kebabCaseToCamelCase(kebabCaseString: string): string { + const [first, ...rest] = kebabCaseString.split('-'); + + return [first, ...rest.map(capitalize)].join(''); +} diff --git a/src/bin/tools/nodeModulesBinDirPath.ts b/src/bin/tools/nodeModulesBinDirPath.ts new file mode 100644 index 0000000..f25295a --- /dev/null +++ b/src/bin/tools/nodeModulesBinDirPath.ts @@ -0,0 +1,38 @@ +import { sep as pathSep } from 'path'; + +let cache: string | undefined = undefined; + +export function getNodeModulesBinDirPath() { + if (cache !== undefined) { + return cache; + } + + const binPath = process.argv[1]; + + const segments: string[] = ['.bin']; + + let foundNodeModules = false; + + for (const segment of binPath.split(pathSep).reverse()) { + skip_segment: { + if (foundNodeModules) { + break skip_segment; + } + + if (segment === 'node_modules') { + foundNodeModules = true; + break skip_segment; + } + + continue; + } + + segments.unshift(segment); + } + + const nodeModulesBinDirPath = segments.join(pathSep); + + cache = nodeModulesBinDirPath; + + return nodeModulesBinDirPath; +} diff --git a/src/bin/tools/readThisNpmPackageVersion.ts b/src/bin/tools/readThisNpmPackageVersion.ts new file mode 100644 index 0000000..0c0cbba --- /dev/null +++ b/src/bin/tools/readThisNpmPackageVersion.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs'; +import { join as pathJoin } from 'path'; +import { assert } from 'tsafe/assert'; +import { getThisCodebaseRootDirPath } from './getThisCodebaseRootDirPath'; + +let cache: string | undefined = undefined; + +export function readThisNpmPackageVersion(): string { + if (cache !== undefined) { + return cache; + } + + const version = JSON.parse(fs.readFileSync(pathJoin(getThisCodebaseRootDirPath(), 'package.json')).toString('utf8'))[ + 'version' + ]; + + assert(typeof version === 'string'); + + cache = version; + + return version; +} diff --git a/src/bin/tools/runPrettier.ts b/src/bin/tools/runPrettier.ts new file mode 100644 index 0000000..86c91a3 --- /dev/null +++ b/src/bin/tools/runPrettier.ts @@ -0,0 +1,118 @@ +import chalk from 'chalk'; +import * as crypto from 'crypto'; +import * as fsPr from 'fs/promises'; +import { join as pathJoin, resolve as pathResolve } from 'path'; +import { assert } from 'tsafe/assert'; +import { id } from 'tsafe/id'; +import { is } from 'tsafe/is'; +import { symToStr } from 'tsafe/symToStr'; +import { getNodeModulesBinDirPath } from './nodeModulesBinDirPath'; +import { readThisNpmPackageVersion } from './readThisNpmPackageVersion'; + +getIsPrettierAvailable.cache = id(undefined); + +export async function getIsPrettierAvailable(): Promise { + if (getIsPrettierAvailable.cache !== undefined) { + return getIsPrettierAvailable.cache; + } + + const nodeModulesBinDirPath = getNodeModulesBinDirPath(); + + const prettierBinPath = pathJoin(nodeModulesBinDirPath, 'prettier'); + + const stats = await fsPr.stat(prettierBinPath).catch(() => undefined); + + const isPrettierAvailable = stats?.isFile() ?? false; + + getIsPrettierAvailable.cache = isPrettierAvailable; + + return isPrettierAvailable; +} + +type PrettierAndConfigHash = { + prettier: typeof import('prettier'); + configHash: string; +}; + +getPrettier.cache = id(undefined); + +export async function getPrettier(): Promise { + assert(getIsPrettierAvailable()); + + if (getPrettier.cache !== undefined) { + return getPrettier.cache; + } + + let prettier = id(undefined); + + import_prettier: { + // NOTE: When module is linked we want to make sure we import the correct version + // of prettier, that is the one of the project, not the one of this repo. + // So we do a sketchy eval to bypass ncc. + // We make sure to only do that when linking, otherwise we import properly. + if (readThisNpmPackageVersion().startsWith('0.0.0')) { + eval( + `${symToStr({ prettier })} = require("${pathResolve(pathJoin(getNodeModulesBinDirPath(), '..', 'prettier'))}")`, + ); + + assert(!is(prettier)); + + break import_prettier; + } + + prettier = await import('prettier'); + } + + const configHash = await (async () => { + const configFilePath = await prettier.resolveConfigFile(pathJoin(getNodeModulesBinDirPath(), '..')); + + if (configFilePath === null) { + return ''; + } + + const data = await fsPr.readFile(configFilePath); + + return crypto.createHash('sha256').update(data).digest('hex'); + })(); + + const prettierAndConfig: PrettierAndConfigHash = { + prettier, + configHash, + }; + + getPrettier.cache = prettierAndConfig; + + return prettierAndConfig; +} + +export async function runPrettier(params: { sourceCode: string; filePath: string }): Promise { + const { sourceCode, filePath } = params; + + let formattedSourceCode: string; + + try { + const { prettier } = await getPrettier(); + + const { ignored, inferredParser } = await prettier.getFileInfo(filePath, { + resolveConfig: true, + }); + + if (ignored) { + return sourceCode; + } + + const config = await prettier.resolveConfig(filePath); + + formattedSourceCode = await prettier.format(sourceCode, { + ...config, + filePath, + parser: inferredParser ?? undefined, + }); + } catch (error) { + console.log(chalk.red(`You probably need to upgrade the version of prettier in your project`)); + + throw error; + } + + return formattedSourceCode; +} diff --git a/src/bin/tools/transformCodebase.ts b/src/bin/tools/transformCodebase.ts new file mode 100644 index 0000000..d40cb1f --- /dev/null +++ b/src/bin/tools/transformCodebase.ts @@ -0,0 +1,81 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { rmSync } from '../tools/fs.rmSync'; +import { crawl } from './crawl'; + +type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) => + | { + modifiedSourceCode: Buffer; + newFileName?: string; + } + | undefined; + +/** + * Apply a transformation function to every file of directory + * If source and destination are the same this function can be used to apply the transformation in place + * like filtering out some files or modifying them. + * */ +export function transformCodebase(params: { + srcDirPath: string; + destDirPath: string; + transformSourceCode?: TransformSourceCode; +}) { + const { srcDirPath, transformSourceCode } = params; + + const isTargetSameAsSource = path.relative(srcDirPath, params.destDirPath) === ''; + + const destDirPath = isTargetSameAsSource + ? path.join(srcDirPath, '..', 'tmp_xOsPdkPsTdzPs34sOkHs') + : params.destDirPath; + + fs.mkdirSync(destDirPath, { + recursive: true, + }); + + for (const fileRelativePath of crawl({ + dirPath: srcDirPath, + returnedPathsType: 'relative to dirPath', + })) { + const filePath = path.join(srcDirPath, fileRelativePath); + const destFilePath = path.join(destDirPath, fileRelativePath); + + // NOTE: Optimization, if we don't need to transform the file, just copy + // it using the lower level implementation. + if (transformSourceCode === undefined) { + fs.mkdirSync(path.dirname(destFilePath), { + recursive: true, + }); + + fs.copyFileSync(filePath, destFilePath); + + continue; + } + + const transformSourceCodeResult = transformSourceCode({ + sourceCode: fs.readFileSync(filePath), + filePath, + fileRelativePath, + }); + + if (transformSourceCodeResult === undefined) { + continue; + } + + fs.mkdirSync(path.dirname(destFilePath), { + recursive: true, + }); + + const { newFileName, modifiedSourceCode } = transformSourceCodeResult; + + fs.writeFileSync( + path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), + modifiedSourceCode, + ); + } + + if (isTargetSameAsSource) { + rmSync(srcDirPath, { recursive: true }); + + fs.renameSync(destDirPath, srcDirPath); + } +} diff --git a/src/bin/tools/transformCodebase_async.ts b/src/bin/tools/transformCodebase_async.ts new file mode 100644 index 0000000..93012cc --- /dev/null +++ b/src/bin/tools/transformCodebase_async.ts @@ -0,0 +1,82 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { rmSync } from '../tools/fs.rmSync'; +import { crawl } from './crawl'; + +type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) => Promise< + | { + modifiedSourceCode: Buffer; + newFileName?: string; + } + | undefined +>; + +/** + * Apply a transformation function to every file of directory + * If source and destination are the same this function can be used to apply the transformation in place + * like filtering out some files or modifying them. + * */ +export async function transformCodebase(params: { + srcDirPath: string; + destDirPath: string; + transformSourceCode?: TransformSourceCode; +}) { + const { srcDirPath, transformSourceCode } = params; + + const isTargetSameAsSource = path.relative(srcDirPath, params.destDirPath) === ''; + + const destDirPath = isTargetSameAsSource + ? path.join(srcDirPath, '..', 'tmp_xOsPdkPsTdzPs34sOkHs') + : params.destDirPath; + + fs.mkdirSync(destDirPath, { + recursive: true, + }); + + for (const fileRelativePath of crawl({ + dirPath: srcDirPath, + returnedPathsType: 'relative to dirPath', + })) { + const filePath = path.join(srcDirPath, fileRelativePath); + const destFilePath = path.join(destDirPath, fileRelativePath); + + // NOTE: Optimization, if we don't need to transform the file, just copy + // it using the lower level implementation. + if (transformSourceCode === undefined) { + fs.mkdirSync(path.dirname(destFilePath), { + recursive: true, + }); + + fs.copyFileSync(filePath, destFilePath); + + continue; + } + + const transformSourceCodeResult = await transformSourceCode({ + sourceCode: fs.readFileSync(filePath), + filePath, + fileRelativePath, + }); + + if (transformSourceCodeResult === undefined) { + continue; + } + + fs.mkdirSync(path.dirname(destFilePath), { + recursive: true, + }); + + const { newFileName, modifiedSourceCode } = transformSourceCodeResult; + + fs.writeFileSync( + path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), + modifiedSourceCode, + ); + } + + if (isTargetSameAsSource) { + rmSync(srcDirPath, { recursive: true }); + + fs.renameSync(destDirPath, srcDirPath); + } +} diff --git a/src/bin/tsconfig.json b/src/bin/tsconfig.json new file mode 100644 index 0000000..01064a1 --- /dev/null +++ b/src/bin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "sourceMap": false, + "newLine": "LF", + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "downlevelIteration": true, + "noFallthroughCasesInSwitch": true, + "composite": true, + "rootDir": ".", + "module": "ES2020", + "target": "ES2017", + "esModuleInterop": true, + "lib": ["es2015", "ES2019.Object"], + "moduleResolution": "node" + }, + "include": ["**/*.ts"], + "exclude": ["initialize-account-theme/boilerplate"] +} diff --git a/src/bin/update-kc-gen.ts b/src/bin/update-kc-gen.ts new file mode 100644 index 0000000..83155a9 --- /dev/null +++ b/src/bin/update-kc-gen.ts @@ -0,0 +1,124 @@ +import type { BuildContext } from './core'; +import * as fs from 'fs'; +import { join as pathJoin } from 'path'; +import { getIsPrettierAvailable, runPrettier } from './tools/runPrettier'; +import * as crypto from 'crypto'; + +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; + + const filePath = pathJoin(buildContext.themeSrcDirPath, 'kc.gen.ts'); + const svelteFilePath = pathJoin(buildContext.themeSrcDirPath, 'kc.gen.svelte'); + + const implementedThemeTypes = (['login', 'account'] as const).filter( + (themeType) => buildContext.implementedThemeTypes[themeType].isImplemented, + ); + + const newContent = [ + ``, + `/* eslint-disable */`, + ``, + `// @ts-nocheck`, + ``, + `// noinspection JSUnusedGlobalSymbols`, + ``, + `export type ThemeName = ${buildContext.themeNames.map((themeName) => `"${themeName}"`).join(' | ')};`, + ``, + `export const themeNames: ThemeName[] = [${buildContext.themeNames.map((themeName) => `"${themeName}"`).join(', ')}];`, + ``, + `export type KcEnvName = ${buildContext.environmentVariables.length === 0 ? 'never' : buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(' | ')};`, + ``, + `export const kcEnvNames: KcEnvName[] = [${buildContext.environmentVariables.map(({ name }) => `"${name}"`).join(', ')}];`, + ``, + `export const kcEnvDefaults: Record = ${JSON.stringify( + Object.fromEntries( + buildContext.environmentVariables.map(({ name, default: defaultValue }) => [name, defaultValue]), + ), + null, + 2, + )};`, + ``, + `export type KcContext =`, + ...implementedThemeTypes.map((themeType) => ` | import("./${themeType}/KcContext").KcContext`), + ` ;`, + ``, + `declare global {`, + ` interface Window {`, + ` kcContext?: KcContext;`, + ` }`, + `}`, + ``, + ].join('\n'); + + const newSvelteContent = [ + ` import type { Component } from 'svelte';`, + ` import type { KcContext } from './kc.gen';`, + ``, + ` const props: { kcContext: KcContext; Fallback?: Component } = $props();`, + ``, + ` const KcLoginPage = import('./login/KcPage.svelte');`, + ` const { kcContext } = props;`, + ``, + ``, + `{#if kcContext.themeType === 'login'}`, + ` {#await KcLoginPage}`, + ` {#if props.Fallback}`, + ` `, + ` {/if}`, + ` {:then { default: KcPage }}`, + ` `, + ` {/await}`, + `{:else}`, + ` null`, + `{/if}`, + ].join('\n'); + + const hash = crypto.createHash('sha256').update(newContent).digest('hex'); + + skip_if_no_changes: { + if (!fs.existsSync(filePath)) { + break skip_if_no_changes; + } + + const currentContent = fs.readFileSync(filePath).toString('utf8'); + + if (!currentContent.includes(hash)) { + break skip_if_no_changes; + } + + return; + } + + let sourceCode = [ + `// This file is auto-generated by keycloakify. Do not edit it manually.`, + `// Hash: ${hash}`, + ``, + newContent, + ].join('\n'); + + let svelteSourceCode = [ + ` -{#if displayableErrors.length !== 0} - - {#each displayableErrors as displayableError, i} - {@const { errorMessage } = displayableError} - {@render errorMessage()} - {#if displayableErrors.length - 1 !== i}
{/if} - {/each} -
-{/if} +{#snippet fieldErrors()} + {@const _displayableErrors = displayableErrors.filter((error) => error.fieldIndex === fieldIndex)} + {#if _displayableErrors.length !== 0} + + {#each _displayableErrors as displayableError, i} + {@const { errorMessage } = displayableError} + {@render errorMessage()} + {#if _displayableErrors.length - 1 !== i}
{/if} + {/each} +
+ {/if} +{/snippet} +{@render fieldErrors()} diff --git a/src/lib/login/components/InputFieldByType.svelte b/src/lib/login/components/InputFieldByType.svelte index 0f126e3..81e3e6c 100644 --- a/src/lib/login/components/InputFieldByType.svelte +++ b/src/lib/login/components/InputFieldByType.svelte @@ -5,29 +5,43 @@ import PasswordWrapper from '@keycloakify/svelte/login/components/PasswordWrapper.svelte'; import SelectTag from '@keycloakify/svelte/login/components/SelectTag.svelte'; import TextareaTag from '@keycloakify/svelte/login/components/TextareaTag.svelte'; - const props: InputFieldByTypeProps = $props(); + + let { displayableErrors, ...props }: InputFieldByTypeProps = $props(); const { attribute, valueOrValues } = props; const inputType = attribute.annotations.inputType ?? ''; {#if inputType === 'textarea'} - + {:else if ['select', 'multiselect'].includes(inputType)} - + {:else if ['select-radiobuttons', 'multiselect-checkboxes'].includes(inputType)} - + {:else} {#if valueOrValues instanceof Array} {#each valueOrValues as _, i} {/each} {:else} {#snippet inputNode()} - + {/snippet} {#if ['password', 'password-confirm'].includes(attribute.name)} { + return (valueOrValues as string[]).map((value, i) => { if (i === fieldIndex) { return event.currentTarget.value; } @@ -87,7 +94,7 @@ { - const passwordInputElement: HTMLInputElement = document.getElementById(passwordInputId) as HTMLInputElement; - - assert(passwordInputElement instanceof HTMLInputElement); const unsubscribe = isPasswordRevealed.subscribe(($isPasswordRevealed) => { + const passwordInputElement: HTMLInputElement = document.getElementById(passwordInputId) as HTMLInputElement; + + assert(passwordInputElement instanceof HTMLInputElement); passwordInputElement.type = $isPasswordRevealed ? 'text' : 'password'; }); return () => unsubscribe(); diff --git a/src/lib/login/components/UserProfileFormFields.svelte b/src/lib/login/components/UserProfileFormFields.svelte index 9380174..a732b44 100644 --- a/src/lib/login/components/UserProfileFormFields.svelte +++ b/src/lib/login/components/UserProfileFormFields.svelte @@ -2,9 +2,10 @@ import FieldErrors from '@keycloakify/svelte/login/components/FieldErrors.svelte'; import GroupLabel from '@keycloakify/svelte/login/components/GroupLabel.svelte'; import InputFieldByType from '@keycloakify/svelte/login/components/InputFieldByType.svelte'; - import { useUserProfileForm } from '@keycloakify/svelte/login/lib/useUserProfileForm'; import type { UserProfileFormFieldsProps } from '@keycloakify/svelte/login/components/UserProfileFormFieldsProps'; + import { useUserProfileForm } from '@keycloakify/svelte/login/lib/useUserProfileForm'; import { onMount } from 'svelte'; + import { derived } from 'svelte/store'; import type { I18n } from '../i18n'; import type { KcContext } from '../KcContext'; @@ -34,10 +35,14 @@ }); const groupNameRef = { current: '' }; + const formFieldStates = derived(formState, ($formState) => $formState.formFieldStates); + const displayableErrors = derived(formFieldStates, ($formFieldStates) => + $formFieldStates.map((f) => f.displayableErrors), + ); -{#each $formState.formFieldStates as formFieldState} - {@const { attribute, displayableErrors, valueOrValues } = formFieldState} +{#each $formFieldStates as formFieldState, i} + {@const { attribute, valueOrValues } = formFieldState} {#if beforeField} - {@render beforeField({ attribute, dispatchFormAction, displayableErrors, valueOrValues, kcClsx, i18n })} + {@render beforeField({ + attribute, + dispatchFormAction, + displayableErrors: $displayableErrors[i], + valueOrValues, + kcClsx, + i18n, + })} {/if}
{#if attribute.annotations.inputHelperTextAfter !== undefined} @@ -96,7 +108,14 @@ {/if} {#if afterField} - {@render afterField({ attribute, dispatchFormAction, displayableErrors, valueOrValues, kcClsx, i18n })} + {@render afterField({ + attribute, + dispatchFormAction, + displayableErrors: $displayableErrors[i], + valueOrValues, + kcClsx, + i18n, + })} {/if}
diff --git a/src/lib/login/lib/useUserProfileForm.ts b/src/lib/login/lib/useUserProfileForm.ts index 218ccad..10121b5 100644 --- a/src/lib/login/lib/useUserProfileForm.ts +++ b/src/lib/login/lib/useUserProfileForm.ts @@ -2,7 +2,7 @@ import { useState } from '@keycloakify/svelte/tools/useState'; import type { Attribute, PasswordPolicies, Validators } from 'keycloakify/login/KcContext'; import * as reactlessApi from 'keycloakify/login/lib/getUserProfileApi'; -import { createRawSnippet, onMount, type EventDispatcher, type Snippet } from 'svelte'; +import { onMount, type EventDispatcher, type Snippet } from 'svelte'; import { derived, type Readable } from 'svelte/store'; import { assert, type Equals } from 'tsafe/assert'; import type { I18n } from '../i18n'; @@ -145,9 +145,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy attribute: formFieldState_reactless.attribute, valueOrValues: formFieldState_reactless.valueOrValues, displayableErrors: formFieldState_reactless.displayableErrors.map((formFieldError_reactless) => ({ - errorMessage: createRawSnippet(() => ({ - render: () => `${advancedMsg(...formFieldError_reactless.advancedMsgArgs)}`, - })), + errorMessage: advancedMsg(...formFieldError_reactless.advancedMsgArgs), errorMessageStr: advancedMsgStr(...formFieldError_reactless.advancedMsgArgs), source: formFieldError_reactless.source, fieldIndex: formFieldError_reactless.fieldIndex, diff --git a/yarn.lock b/yarn.lock index 30c44af..8a0d804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,116 +15,236 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== +"@esbuild/aix-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" + integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== + "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== +"@esbuild/android-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" + integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== + "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" + integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== + "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== +"@esbuild/android-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" + integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" + integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== + "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== +"@esbuild/darwin-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" + integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== + "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== +"@esbuild/freebsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" + integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== + "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== +"@esbuild/freebsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" + integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== + "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== +"@esbuild/linux-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" + integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== + "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-arm@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" + integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== + "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== +"@esbuild/linux-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" + integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== + "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== +"@esbuild/linux-loong64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" + integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== + "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== +"@esbuild/linux-mips64el@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" + integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== + "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== +"@esbuild/linux-ppc64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" + integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== + "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== +"@esbuild/linux-riscv64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" + integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== + "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== +"@esbuild/linux-s390x@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" + integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== + "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== +"@esbuild/netbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" + integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA== + +"@esbuild/openbsd-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" + integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== + "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== +"@esbuild/openbsd-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" + integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== + "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== +"@esbuild/sunos-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" + integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== + "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== +"@esbuild/win32-arm64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" + integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== + "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== +"@esbuild/win32-ia32@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" + integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== +"@esbuild/win32-x64@0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" + integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" @@ -419,6 +539,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/node@^22.9.3": + version "22.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.3.tgz#08f3d64b3bc6d74b162d36f60213e8a6704ef2b4" + integrity sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw== + dependencies: + undici-types "~6.19.8" + "@typescript-eslint/eslint-plugin@8.14.0": version "8.14.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz#7dc0e419c87beadc8f554bf5a42e5009ed3748dc" @@ -525,6 +652,11 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -599,6 +731,13 @@ chokidar@^4.0.0, chokidar@^4.0.1: dependencies: readdirp "^4.0.1" +cli-select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cli-select/-/cli-select-1.1.2.tgz#456dced464b3346ca661b16a0e37fc4b28db4818" + integrity sha512-PSvWb8G0PPmBNDcz/uM2LkZN3Nn5JmhUl465tTfynQAXjKzFpmHbxStM6X/+awKp5DJuAaHMzzMPefT0suGm1w== + dependencies: + ansi-escapes "^3.2.0" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -691,6 +830,36 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -947,6 +1116,13 @@ fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz#8995eb391ae6e1638d251118c7b56de7eb425471" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1417,6 +1593,11 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -1615,6 +1796,16 @@ tslib@^2.0.3, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -1631,10 +1822,15 @@ typescript-eslint@^8.14.0: "@typescript-eslint/parser" "8.14.0" "@typescript-eslint/utils" "8.14.0" -typescript@^5.5.4: - version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" - integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== +typescript@^5.6.3: + version "5.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== + +undici-types@~6.19.8: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== uri-js@^4.2.2: version "4.4.1" @@ -1695,3 +1891,8 @@ zimmerframe@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/zimmerframe/-/zimmerframe-1.1.2.tgz#5b75f1fa83b07ae2a428d51e50f58e2ae6855e5e" integrity sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w== + +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==