Compare commits
12 Commits
hmr_in_sta
...
v11.4.5
Author | SHA1 | Date | |
---|---|---|---|
606cf7ad02 | |||
5225749c7b | |||
819e3833ad | |||
b0ba37fcc4 | |||
f4829b557f | |||
60a9b5a693 | |||
c323b94a8c | |||
4bbc0241ec | |||
5a7dacfcdd | |||
7e05e1bf0c | |||
1530ca32c8 | |||
ed054f131a |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "11.3.32",
|
||||
"version": "11.4.5",
|
||||
"description": "Framework to create custom Keycloak UIs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type TemplateProps, type ClassKey } from "keycloakify/account/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { i18nBuilder } from "keycloakify/account";
|
||||
import type { ThemeName } from "../kc.gen";
|
||||
|
||||
const { useI18n, ofTypeI18n } = i18nBuilder
|
||||
.withThemeName<ThemeName>()
|
||||
.withExtraLanguages({})
|
||||
.withCustomTranslations({})
|
||||
.build();
|
||||
/** @see: https://docs.keycloakify.dev/i18n */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { useI18n, ofTypeI18n } = i18nBuilder.withThemeName<ThemeName>().build();
|
||||
|
||||
type I18n = typeof ofTypeI18n;
|
||||
|
||||
|
@ -11,7 +11,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
import type { BuildContext } from "../../shared/buildContext";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { type ThemeType, WELL_KNOWN_DIRECTORY_BASE_NAME } from "../../shared/constants";
|
||||
import {
|
||||
type ThemeType,
|
||||
WELL_KNOWN_DIRECTORY_BASE_NAME,
|
||||
KEYCLOAKIFY_SPA_DEV_SERVER_PORT
|
||||
} from "../../shared/constants";
|
||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||
|
||||
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
|
||||
@ -116,6 +120,7 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
.replace("{{themeVersion}}", buildContext.themeVersion)
|
||||
.replace("{{fieldNames}}", fieldNames.map(name => `"${name}"`).join(", "))
|
||||
.replace("{{RESOURCES_COMMON}}", WELL_KNOWN_DIRECTORY_BASE_NAME.RESOURCES_COMMON)
|
||||
.replace("{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}}", KEYCLOAKIFY_SPA_DEV_SERVER_PORT)
|
||||
.replace(
|
||||
"{{userDefinedExclusions}}",
|
||||
buildContext.kcContextExclusionsFtlCode ?? ""
|
||||
|
@ -101,7 +101,7 @@ redirect_to_dev_server: {
|
||||
break redirect_to_dev_server;
|
||||
}
|
||||
|
||||
const devSeverPort = kcContext.properties.KEYCLOAKIFY_SPA_DEV_SERVER_PORT;
|
||||
const devSeverPort = kcContext.properties.{{KEYCLOAKIFY_SPA_DEV_SERVER_PORT}};
|
||||
|
||||
if( !devSeverPort ){
|
||||
break redirect_to_dev_server;
|
||||
@ -115,7 +115,7 @@ redirect_to_dev_server: {
|
||||
|
||||
console.log(kcContext);
|
||||
|
||||
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)) );
|
||||
redirectUrl.searchParams.set("kcContext", encodeURIComponent(JSON.stringify(kcContext)));
|
||||
|
||||
window.location.href = redirectUrl.toString();
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
import * as child_process from "child_process";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { assert, is, type Equals } from "tsafe/assert";
|
||||
import { id } from "tsafe/id";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { sep as pathSep, join as pathJoin } from "path";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
||||
import * as fs from "fs";
|
||||
import { dirname as pathDirname, relative as pathRelative } from "path";
|
||||
import { z } from "zod";
|
||||
|
||||
export type BuildContextLike = {
|
||||
projectDirPath: string;
|
||||
keycloakifyBuildDirPath: string;
|
||||
bundler: BuildContext["bundler"];
|
||||
projectBuildDirPath: string;
|
||||
packageJsonFilePath: string;
|
||||
};
|
||||
|
||||
@ -23,58 +22,36 @@ export async function appBuild(params: {
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
switch (buildContext.bundler) {
|
||||
case "vite":
|
||||
return appBuild_vite({ buildContext });
|
||||
case "webpack":
|
||||
return appBuild_webpack({ buildContext });
|
||||
}
|
||||
}
|
||||
const { parsedPackageJson } = (() => {
|
||||
type ParsedPackageJson = {
|
||||
scripts?: Record<string, string>;
|
||||
};
|
||||
|
||||
async function appBuild_vite(params: {
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
const zParsedPackageJson = (() => {
|
||||
type TargetType = ParsedPackageJson;
|
||||
|
||||
assert(buildContext.bundler === "vite");
|
||||
const zTargetType = z.object({
|
||||
scripts: z.record(z.string()).optional()
|
||||
});
|
||||
|
||||
const dIsSuccess = new Deferred<boolean>();
|
||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
||||
|
||||
console.log(chalk.blue("$ npx vite build"));
|
||||
return id<z.ZodType<TargetType>>(zTargetType);
|
||||
})();
|
||||
const parsedPackageJson = JSON.parse(
|
||||
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
||||
);
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
shell: true
|
||||
});
|
||||
zParsedPackageJson.parse(parsedPackageJson);
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
return;
|
||||
}
|
||||
assert(is<ParsedPackageJson>(parsedPackageJson));
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
return { parsedPackageJson };
|
||||
})();
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", code => dIsSuccess.resolve(code === 0));
|
||||
|
||||
const isSuccess = await dIsSuccess.pr;
|
||||
|
||||
return { isAppBuildSuccess: isSuccess };
|
||||
}
|
||||
|
||||
async function appBuild_webpack(params: {
|
||||
buildContext: BuildContextLike;
|
||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
assert(buildContext.bundler === "webpack");
|
||||
|
||||
const entries = Object.entries(
|
||||
(JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8"))
|
||||
.scripts ?? {}) as Record<string, string>
|
||||
).filter(([, scriptCommand]) => scriptCommand.includes("keycloakify build"));
|
||||
const entries = Object.entries(parsedPackageJson.scripts ?? {}).filter(
|
||||
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
|
||||
);
|
||||
|
||||
if (entries.length === 0) {
|
||||
console.log(
|
||||
@ -127,6 +104,76 @@ async function appBuild_webpack(params: {
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
common_case: {
|
||||
if (appBuildSubCommands.length !== 1) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const [appBuildSubCommand] = appBuildSubCommands;
|
||||
|
||||
const isNpmRunBuild = (() => {
|
||||
for (const packageManager of ["npm", "yarn", "pnpm", "bun", "deno"]) {
|
||||
for (const doUseRun of [true, false]) {
|
||||
if (
|
||||
`${packageManager}${doUseRun ? " run " : " "}build` ===
|
||||
appBuildSubCommand
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (!isNpmRunBuild) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const { scripts } = parsedPackageJson;
|
||||
|
||||
assert(scripts !== undefined);
|
||||
|
||||
const buildCmd = scripts.build;
|
||||
|
||||
if (buildCmd !== "tsc && vite build") {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
if (scripts.prebuild !== undefined) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
if (scripts.postbuild !== undefined) {
|
||||
break common_case;
|
||||
}
|
||||
|
||||
const dIsSuccess = new Deferred<boolean>();
|
||||
|
||||
console.log(chalk.blue("$ npx vite build"));
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
shell: true
|
||||
});
|
||||
|
||||
child.stdout.on("data", data => {
|
||||
if (data.toString("utf8").includes("gzip:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
child.on("exit", code => dIsSuccess.resolve(code === 0));
|
||||
|
||||
const isSuccess = await dIsSuccess.pr;
|
||||
|
||||
return { isAppBuildSuccess: isSuccess };
|
||||
}
|
||||
|
||||
let commandCwd = pathDirname(buildContext.packageJsonFilePath);
|
||||
|
||||
for (const subCommand of appBuildSubCommands) {
|
||||
|
@ -380,14 +380,14 @@ export async function command(params: {
|
||||
const port =
|
||||
cliCommandOptions.port ?? buildContext.startKeycloakOptions.port ?? DEFAULT_PORT;
|
||||
|
||||
const devServerPort = (() => {
|
||||
const doStartDevServer = (() => {
|
||||
const hasSpaUi =
|
||||
buildContext.implementedThemeTypes.admin.isImplemented ||
|
||||
(buildContext.implementedThemeTypes.account.isImplemented &&
|
||||
buildContext.implementedThemeTypes.account.type === "Single-Page");
|
||||
|
||||
if (!hasSpaUi) {
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buildContext.bundler !== "vite") {
|
||||
@ -401,7 +401,7 @@ export async function command(params: {
|
||||
)
|
||||
);
|
||||
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keycloakMajorVersionNumber < 25) {
|
||||
@ -415,17 +415,18 @@ export async function command(params: {
|
||||
)
|
||||
);
|
||||
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
|
||||
return port + 1;
|
||||
return true;
|
||||
})();
|
||||
|
||||
if (devServerPort !== undefined) {
|
||||
startViteDevServer({
|
||||
buildContext,
|
||||
port: devServerPort
|
||||
});
|
||||
let devServerPort: number | undefined = undefined;
|
||||
|
||||
if (doStartDevServer) {
|
||||
const { port } = await startViteDevServer({ buildContext });
|
||||
|
||||
devServerPort = port;
|
||||
}
|
||||
|
||||
const SPACE_PLACEHOLDER = "SPACE_PLACEHOLDER_xKLmdPd";
|
||||
|
@ -3,6 +3,7 @@ import { assert } from "tsafe/assert";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants";
|
||||
import { Deferred } from "evt/tools/Deferred";
|
||||
|
||||
export type BuildContextLike = {
|
||||
projectDirPath: string;
|
||||
@ -12,13 +13,12 @@ assert<BuildContext extends BuildContextLike ? true : false>();
|
||||
|
||||
export function startViteDevServer(params: {
|
||||
buildContext: BuildContextLike;
|
||||
port: number;
|
||||
}): void {
|
||||
const { buildContext, port } = params;
|
||||
}): Promise<{ port: number }> {
|
||||
const { buildContext } = params;
|
||||
|
||||
console.log(chalk.blue(`$ npx vite dev --port ${port}`));
|
||||
console.log(chalk.blue(`$ npx vite dev`));
|
||||
|
||||
const child = child_process.spawn("npx", ["vite", "dev", "--port", `${port}`], {
|
||||
const child = child_process.spawn("npx", ["vite", "dev"], {
|
||||
cwd: buildContext.projectDirPath,
|
||||
env: {
|
||||
...process.env,
|
||||
@ -36,4 +36,31 @@ export function startViteDevServer(params: {
|
||||
});
|
||||
|
||||
child.stderr.on("data", data => process.stderr.write(data));
|
||||
|
||||
const dPort = new Deferred<number>();
|
||||
|
||||
{
|
||||
const onData = (data: Buffer) => {
|
||||
//Local: http://localhost:8083/
|
||||
const match = data
|
||||
.toString("utf8")
|
||||
.match(/Local:\s*http:\/\/(?:localhost|127\.0\.0\.1):(\d+)\//);
|
||||
|
||||
if (match === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.stdout.off("data", onData);
|
||||
|
||||
const port = parseInt(match[1]);
|
||||
|
||||
assert(!isNaN(port));
|
||||
|
||||
dPort.resolve(port);
|
||||
};
|
||||
|
||||
child.stdout.on("data", onData);
|
||||
}
|
||||
|
||||
return dPort.pr.then(port => ({ port }));
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export async function runPrettier(params: {
|
||||
resolveConfig: true
|
||||
});
|
||||
|
||||
if (ignored) {
|
||||
if (ignored || inferredParser === null) {
|
||||
return sourceCode;
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ export async function runPrettier(params: {
|
||||
formattedSourceCode = await prettier.format(sourceCode, {
|
||||
...config,
|
||||
filePath,
|
||||
parser: inferredParser ?? undefined
|
||||
parser: inferredParser
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useReducer, Fragment } from "react";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
|
||||
import type { KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
import type { Attribute } from "keycloakify/login/KcContext";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import type { GenericI18n_noJsx } from "../noJsx/GenericI18n_noJsx";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
|
||||
|
@ -46,7 +46,7 @@ export type I18nBuilder<
|
||||
}>
|
||||
) => I18nBuilder<
|
||||
ThemeName,
|
||||
MessageKey_themeDefined,
|
||||
string extends MessageKey_themeDefined ? never : MessageKey_themeDefined,
|
||||
LanguageTag_notInDefaultSet,
|
||||
ExcludedMethod | "withCustomTranslations"
|
||||
>;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useState } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { createGetI18n, type KcContextLike } from "../noJsx/getI18n";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import * as reactlessApi from "./getUserProfileApi/index";
|
||||
import type { PasswordPolicies, Attribute, Validators } from "keycloakify/login/KcContext";
|
||||
import { useEffect, useState, useMemo, Fragment } from "react";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState, useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState, useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useEffect, useReducer } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { assert } from "keycloakify/tools/assert";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
|
5
src/tools/JSX.ts
Normal file
5
src/tools/JSX.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
export namespace JSX {
|
||||
export interface Element extends ReactElement<any, any> {}
|
||||
}
|
@ -62,3 +62,65 @@ type I18n = typeof ofTypeI18n;
|
||||
|
||||
assert<Equals<typeof node, JSX.Element>>;
|
||||
}
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
i18n.msg("passwordConfirm");
|
||||
}
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
// @ts-expect-error
|
||||
i18n.msg("iDoNotExist");
|
||||
}
|
||||
|
||||
{
|
||||
const { ofTypeI18n } = i18nBuilder
|
||||
.withThemeName<"keycloakify-starter">()
|
||||
.withCustomTranslations({})
|
||||
.build();
|
||||
|
||||
type I18n = typeof ofTypeI18n;
|
||||
|
||||
{
|
||||
const i18n = Reflect<I18n>();
|
||||
|
||||
// @ts-expect-error
|
||||
const node = i18n.msg("iDoNotExist");
|
||||
|
||||
assert<Equals<typeof node, JSX.Element>>;
|
||||
}
|
||||
}
|
||||
|
||||
i18nBuilder.withThemeName<"my-theme-1" | "my-theme-2">().withCustomTranslations({
|
||||
en: {
|
||||
myCustomKey1: "my-custom-key-1-en",
|
||||
// @ts-expect-error
|
||||
myCustomKey2: {
|
||||
"my-theme-1": "my-theme-1-en"
|
||||
//"my-theme-2": "my-theme-2-en"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
i18nBuilder
|
||||
.withThemeName<"my-theme-1" | "my-theme-2">()
|
||||
.withExtraLanguages({
|
||||
he: {
|
||||
label: "עברית",
|
||||
getMessages: () => import("./he")
|
||||
}
|
||||
})
|
||||
.withCustomTranslations({
|
||||
en: {
|
||||
myCustomKey1: "my-custom-key-1-en",
|
||||
myCustomKey2: "my-custom-key-2-en"
|
||||
},
|
||||
// @ts-expect-error
|
||||
he: {
|
||||
myCustomKey1: "my-custom-key-1-he"
|
||||
//myCustomKey2: "my-custom-key-2-he"
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user