Add code gen for environement variables an theme name

This commit is contained in:
Joseph Garrone 2024-06-08 14:02:07 +02:00
parent e1341dfdba
commit 82d7e1371e
8 changed files with 181 additions and 75 deletions

View File

@ -8,6 +8,7 @@ import * as recast from "recast";
import * as babelParser from "@babel/parser";
import babelGenerate from "@babel/generator";
import * as babelTypes from "@babel/types";
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
export function generateMessageProperties(params: {
themeSrcDirPath: string;
@ -146,7 +147,7 @@ export function generateMessageProperties(params: {
for (const [languageTag, keyValueMap] of Object.entries(keyValueMapByLanguageTag)) {
const propertiesFileSource = Object.entries(keyValueMap)
.map(([key, value]) => `${key}=${escapeString(value)}`)
.map(([key, value]) => `${key}=${escapeStringForPropertiesFile(value)}`)
.join("\n");
out.push({
@ -164,68 +165,3 @@ export function generateMessageProperties(params: {
return out;
}
// Convert a JavaScript string to UTF-16 encoding
function toUTF16(codePoint: number): string {
if (codePoint <= 0xffff) {
// BMP character
return "\\u" + codePoint.toString(16).padStart(4, "0");
} else {
// Non-BMP character
codePoint -= 0x10000;
let highSurrogate = (codePoint >> 10) + 0xd800;
let lowSurrogate = (codePoint % 0x400) + 0xdc00;
return (
"\\u" +
highSurrogate.toString(16).padStart(4, "0") +
"\\u" +
lowSurrogate.toString(16).padStart(4, "0")
);
}
}
// Escapes special characters for use in a .properties file
function escapeString(str: string): string {
let escapedStr = "";
for (const char of [...str]) {
const codePoint = char.codePointAt(0);
if (!codePoint) continue;
switch (char) {
case "\n":
escapedStr += "\\n";
break;
case "\r":
escapedStr += "\\r";
break;
case "\t":
escapedStr += "\\t";
break;
case "\\":
escapedStr += "\\\\";
break;
case ":":
escapedStr += "\\:";
break;
case "=":
escapedStr += "\\=";
break;
case "#":
escapedStr += "\\#";
break;
case "!":
escapedStr += "\\!";
break;
case "'":
escapedStr += "''";
break;
default:
if (codePoint > 0x7f) {
escapedStr += toUTF16(codePoint); // Non-ASCII characters
} else {
escapedStr += char; // ASCII character needs no escape
}
}
}
return escapedStr;
}

View File

@ -38,6 +38,7 @@ import {
type MetaInfKeycloakTheme
} from "../../shared/metaInfKeycloakThemes";
import { objectEntries } from "tsafe/objectEntries";
import { escapeStringForPropertiesFile } from "../../tools/escapeStringForPropertiesFile";
export type BuildOptionsLike = BuildOptionsLike_kcContextExclusionsFtlCode &
BuildOptionsLike_downloadKeycloakStaticResources &
@ -50,6 +51,7 @@ export type BuildOptionsLike = BuildOptionsLike_kcContextExclusionsFtlCode &
urlPathname: string | undefined;
reactAppRootDirPath: string;
keycloakifyBuildDirPath: string;
environmentVariables: { name: string; default: string }[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
@ -261,7 +263,11 @@ export async function generateSrcMainResourcesForMainTheme(params: {
}
assert<Equals<typeof themeType, never>>(false);
})()}`,
...(buildOptions.extraThemeProperties ?? [])
...(buildOptions.extraThemeProperties ?? []),
buildOptions.environmentVariables.map(
({ name, default: defaultValue }) =>
`${name}=\${env.${name}:${escapeStringForPropertiesFile(defaultValue)}}`
)
].join("\n\n"),
"utf8"
)

View File

@ -205,6 +205,21 @@ program
}
});
program
.command({
name: "update-kc-gen",
description:
"(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
})
.task({
skip,
handler: async cliCommandOptions => {
const { command } = await import("./update-kc-gen");
await command({ cliCommandOptions });
}
});
// Fallback to build command if no command is provided
{
const [, , ...rest] = process.argv;

View File

@ -31,15 +31,17 @@ export type BuildOptions = {
assetsDirPath: string;
npmWorkspaceRootDirPath: string;
kcContextExclusionsFtlCode: string | undefined;
environmentVariables: { name: string; default: string }[];
};
export type UserProvidedBuildOptions = {
themeName?: string | string[];
environmentVariables?: { name: string; default: string }[];
extraThemeProperties?: string[];
artifactId?: string;
groupId?: string;
loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string;
themeName?: string | string[];
kcContextExclusionsFtlCode?: string;
};
@ -305,6 +307,7 @@ export function readBuildOptions(params: {
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
})(),
npmWorkspaceRootDirPath,
kcContextExclusionsFtlCode: userProvidedBuildOptions.kcContextExclusionsFtlCode
kcContextExclusionsFtlCode: userProvidedBuildOptions.kcContextExclusionsFtlCode,
environmentVariables: userProvidedBuildOptions.environmentVariables ?? []
};
}

View File

@ -0,0 +1,61 @@
import { assert } from "tsafe/assert";
import type { BuildOptions } from "./buildOptions";
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
import * as fs from "fs/promises";
import { join as pathJoin } from "path";
export type BuildOptionsLike = {
reactAppRootDirPath: string;
themeNames: string[];
environmentVariables: { name: string; default: string }[];
};
assert<BuildOptions extends BuildOptionsLike ? true : false>();
export async function generateKcGenTs(params: {
buildOptions: BuildOptionsLike;
}): Promise<void> {
const { buildOptions } = params;
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
await fs.writeFile(
pathJoin(themeSrcDirPath, "kc.gen.ts"),
Buffer.from(
[
`/* prettier-ignore-start */`,
``,
`/* eslint-disable */`,
``,
`// @ts-nocheck`,
``,
`// noinspection JSUnusedGlobalSymbols`,
``,
`// This file is auto-generated by Keycloakify`,
``,
`export type ThemeName = ${buildOptions.themeNames.map(themeName => `"${themeName}"`).join(" | ")};`,
``,
`export const themeNames: ThemeName[] = [${buildOptions.themeNames.map(themeName => `"${themeName}"`).join(", ")}];`,
``,
`export type KcEnvName = ${buildOptions.environmentVariables.length === 0 ? "never" : buildOptions.environmentVariables.map(({ name }) => `"${name}"`).join(" | ")};`,
``,
`export const KcEnvNames: KcEnvName[] = [${buildOptions.environmentVariables.map(({ name }) => `"${name}"`).join(", ")}];`,
``,
`export const kcEnvDefaults: Record<KcEnvName, string> = ${JSON.stringify(
Object.fromEntries(
buildOptions.environmentVariables.map(
({ name, default: defaultValue }) => [name, defaultValue]
)
),
null,
2
)}`,
``,
`/* prettier-ignore-end */`
].join("\n"),
"utf8"
)
);
}

View File

@ -0,0 +1,64 @@
// Convert a JavaScript string to UTF-16 encoding
function toUTF16(codePoint: number): string {
if (codePoint <= 0xffff) {
// BMP character
return "\\u" + codePoint.toString(16).padStart(4, "0");
} else {
// Non-BMP character
codePoint -= 0x10000;
let highSurrogate = (codePoint >> 10) + 0xd800;
let lowSurrogate = (codePoint % 0x400) + 0xdc00;
return (
"\\u" +
highSurrogate.toString(16).padStart(4, "0") +
"\\u" +
lowSurrogate.toString(16).padStart(4, "0")
);
}
}
// Escapes special characters for use in a .properties file
export function escapeStringForPropertiesFile(str: string): string {
let escapedStr = "";
for (const char of [...str]) {
const codePoint = char.codePointAt(0);
if (!codePoint) continue;
switch (char) {
case "\n":
escapedStr += "\\n";
break;
case "\r":
escapedStr += "\\r";
break;
case "\t":
escapedStr += "\\t";
break;
case "\\":
escapedStr += "\\\\";
break;
case ":":
escapedStr += "\\:";
break;
case "=":
escapedStr += "\\=";
break;
case "#":
escapedStr += "\\#";
break;
case "!":
escapedStr += "\\!";
break;
case "'":
escapedStr += "''";
break;
default:
if (codePoint > 0x7f) {
escapedStr += toUTF16(codePoint); // Non-ASCII characters
} else {
escapedStr += char; // ASCII character needs no escape
}
}
}
return escapedStr;
}

13
src/bin/update-kc-gen.ts Normal file
View File

@ -0,0 +1,13 @@
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import { generateKcGenTs } from "./shared/generateKcGenTs";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
cliCommandOptions
});
await generateKcGenTs({ buildOptions });
}

View File

@ -17,6 +17,7 @@ import {
type ResolvedViteConfig
} from "../bin/shared/buildOptions";
import MagicString from "magic-string";
import { generateKcGenTs } from "../bin/shared/generateKcGenTs";
export type Params = UserProvidedBuildOptions & {
postBuild?: (buildOptions: Omit<BuildOptions, "bundler">) => Promise<void>;
@ -115,13 +116,20 @@ export function keycloakify(params?: Params) {
process.exit(0);
}
await copyKeycloakResourcesToPublic({
buildOptions: readBuildOptions({
const buildOptions = readBuildOptions({
cliCommandOptions: {
reactAppRootDirPath
}
})
});
await Promise.all([
copyKeycloakResourcesToPublic({
buildOptions
}),
generateKcGenTs({
buildOptions
})
]);
},
transform: (code, id) => {
assert(command !== undefined);